186 Commits
Author SHA1 Message Date
thooge 8287242bf9 Add optional patch to support OBP Keypad 6/1 hardware 2026-09-03 17:48:44 +02:00
Norbert Walter 6ee158519d Merge pull request #246 from Scorgan01/smallDecimals
Display option for boat values with smaller decimals
2026-09-03 15:54:48 +02:00
Ulrich Meine 933b1677f6 PageXTETrack: small alignment adjustments 2026-09-02 22:07:10 +02:00
Ulrich Meine 1ea63d7612 Enable PageDST810 for small decimals and some code cleanup 2026-09-02 21:46:06 +02:00
Ulrich Meine 9186491b5e Enable pages Battery2, Generator, Solar, RollPitch for small decimals 2026-09-02 10:51:18 +02:00
Ulrich Meine c1e69dd81b Enable page Battery for small decimals; small adjustment for PageVoltage 2026-09-01 22:29:55 +02:00
Ulrich Meine 3dd13ae899 enable PageVoltage for small decimals; add formatValue overload to OBP60Formatter 2026-09-01 21:53:09 +02:00
Ulrich Meine 6c318d5a69 Enables pages Autopilot, BME280, Compass, Current for small decimals 2026-09-01 21:51:30 +02:00
Ulrich Meine 0202c5d82a Enable pages Weather, Wind, XTETrack + charts function for small decimals 2026-09-01 16:55:34 +02:00
Ulrich Meine d44dc94ec7 Enable pages WindRose, WindRoseFlex for small decimals; add minor code optimization 2026-09-01 00:07:51 +02:00
Ulrich Meine 0f98331eb4 Enable pages OneValue, TwoValue, ThreeValue,
FourValue, FourValue2 for small decimals
2026-08-31 23:54:02 +02:00
Ulrich Meine 33768440b1 OBPFormatter(): make precision selectable regardless of user setting 2026-08-31 16:35:21 +02:00
Ulrich Meine 9eb0983ee4 Fix some more issues on printBoatValue() function 2026-08-31 16:34:33 +02:00
Ulrich Meine 93084c980a Enable small decimals on PageOneValue; fix issues on PageSixValues 2026-08-31 16:33:19 +02:00
Ulrich Meine 18c1da5991 Adjust displayGetTextBounds(); fix rendering issues at printBoatValueImpl() 2026-08-26 00:36:23 +02:00
Ulrich Meine 99f88cca3f Small decimals: implement printBoatValue() function for flexible decimals font size of boat values; still some bugs 2026-08-24 23:09:19 +02:00
Ulrich Meine fecb9a8043 Test version for small decimals for boat values 2026-08-15 10:13:00 +02:00
Ulrich Meine 985d4ef3e6 Add DSEG7 italic font with 10pt + 12pt size 2026-08-15 10:00:25 +02:00
Ulrich Meine 50dc5955a0 Fix drawTextRalign(): avoid wobbling when DSEG italic font is used 2026-08-12 23:02:49 +02:00
Ulrich Meine aebc18a75d PageWeather - tiniy code clean 2026-08-11 23:17:54 +02:00
Ulrich Meine e60ead0455 Fix potential CTW under-/overflow; has to be [0..2PI] 2026-08-09 22:44:00 +02:00
Ulrich Meine eaa1f793ff PageCurrent: Limit leeway size when STW is too small 2026-07-23 00:07:17 +02:00
Ulrich Meine 88dfa84cc8 Added smoothing to PageCurrent; SET and DFT were changing rapidly 2026-07-22 23:24:17 +02:00
Ulrich Meine 6c6557f54e Fix OBPFormatter when course values > 360° were shown as e.g. 370° instead of 010° 2026-07-22 23:22:32 +02:00
Norbert Walter 58a0bbf0d4 Merge pull request #244 from Scorgan01/Charts-code-update
Fix some errors of wind chart plotting and fix tiny errors in TWD/AWD handling
2026-07-12 11:07:05 +02:00
Ulrich Meine f68e988736 Adjust check for recalculation of middle axis value for large ranges 2026-07-12 01:08:39 +02:00
Ulrich Meine 807e4cec30 Fix failing initialization of AWD history buffer 2026-07-12 00:39:54 +02:00
Ulrich Meine e129b18057 Fix how chart smoothing handles angular value averages; move smoothing calculation of data from chart function to history buffer, because new method needs too much time in chart function 2026-07-06 22:44:52 +02:00
Ulrich Meine e757ac4016 Clean code of OBPRingbuffer 2026-07-06 22:38:39 +02:00
Ulrich Meine 71e3ba79f6 Add option to either store native data or averaged data (for chart smootihing) in history buffer 2026-07-06 22:36:18 +02:00
Ulrich Meine da4c66bec2 Add a class to movingAvg class for calculation of averages for angle data 2026-07-06 22:31:28 +02:00
Ulrich Meine fb181c1b01 Next approach to calculate chart center value for wind angle data 2026-07-04 18:35:12 +02:00
Ulrich Meine 53cf497a70 Add method for reliable identification of mid value for angle buffer values to RingBuffer; fix getMedian() 2026-07-04 18:27:19 +02:00
Ulrich Meine 92ee3a0c4f Fix TWD not calculated from TWA in some situations 2026-07-04 18:25:09 +02:00
Ulrich Meine 4e07e1d22e Update OBPcharts for more reliable parameterization 2026-07-03 14:24:03 +02:00
Norbert Walter 2a8558589b Merge pull request #243 from Scorgan01/SystemPage-clean
Add Network Status page to SystemPage
2026-07-03 10:52:10 +02:00
Norbert Walter 2da5ffc4ea Merge pull request #242 from Scorgan01/master
Adjust layout of PageSixValues for 4-digit pressure and long names+units
2026-07-03 10:51:13 +02:00
Norbert Walter 4e8953ed2a Merge pull request #241 from Scorgan01/Barograph
Add PageWeather
2026-07-03 10:50:42 +02:00
Ulrich Meine 83aae1ea81 Fix typo 2026-07-02 22:23:07 +02:00
Ulrich Meine 82c0ce91a5 Add Network Status page to SystemPage 2026-07-02 21:56:53 +02:00
Ulrich Meine 2e7b0435b2 Revert upload_speed change in platformio.ini 2026-07-02 18:44:40 +02:00
Ulrich Meine b8e9e02617 Merge branch 'master' of https://github.com/Scorgan01/esp32-nmea2000-obp60 2026-07-02 16:15:44 +02:00
Ulrich Meine fe05b734de Adjust layout of PageSixValue for 4-digit pressure and long names+units 2026-07-02 16:15:40 +02:00
Ulrich Meine a93f951f18 Add PageWeather to config_obp70.json 2026-07-02 01:26:20 +02:00
Ulrich Meine 8ebde00676 Reset pressure storage rate to 1 minute 2026-07-02 00:59:45 +02:00
Ulrich Meine 2a98f49e40 Change Page Weather time interval to 6-72 hrs 2026-07-02 00:58:15 +02:00
Ulrich Meine d1b729a554 Adjust Page OneValue to 4 digit option 2026-07-02 00:45:19 +02:00
Ulrich Meine dac7459355 Add "humidity" XDR value handling to charts; adjust high/low value plot; change pressure from area to line plot 2026-07-01 23:51:50 +02:00
Ulrich Meine 775d4456a2 Complete config_obp60.json for weather page 2026-07-01 23:49:31 +02:00
Ulrich Meine 04ea564bdb Add late chart object initialization to pages with charts 2026-07-01 02:42:02 +02:00
Ulrich Meine 751f4e2296 Adjust buffer limits for some boat data 2026-07-01 02:17:38 +02:00
Ulrich Meine 3dd7f4751a Add late initialization for chart objects 2026-07-01 02:16:40 +02:00
Ulrich Meine a2822451c1 1st version of PageWeather with air pressure chart 2026-06-21 22:15:32 +02:00
Ulrich Meine 63af2807fb Improve handling of invalid values in OBPringbuffer; avoid some double coding 2026-06-21 02:15:16 +02:00
Ulrich Meine c17f8238a4 Implement MaxTWS for OBP true wind calculation 2026-06-19 00:00:08 +02:00
Ulrich Meine 8f50c6a09c Extend OBPcharts to display air pressure properly 2026-06-16 23:11:15 +02:00
Ulrich Meine e906ad6d00 Add leading zero for pressure format in OBPFormatter 2026-06-16 23:01:34 +02:00
Ulrich Meine 89482016db Align unit name for temperature in OBPFormatter between <kelvinToC> <formatXdr:C> 2026-06-07 01:22:29 +02:00
Ulrich Meine abfe68f963 add XDR boat data option to history buffer 2026-06-06 02:37:46 +02:00
Ulrich Meine 01ff478c29 Extend history buffer for flexible data storage frequency 2026-05-31 21:32:39 +02:00
Norbert Walter d0fe7a2d9b Merge pull request #240 from Scorgan01/master
Improve chart axis labelling, adjust intervals, and fix conditionless AWD calculation
2026-05-28 10:13:00 +02:00
Ulrich Meine 80800c32a3 Adjust default ranges for charts once more 2026-05-22 23:24:09 +02:00
Ulrich Meine a218da8948 fix calculate AWD conditionless 2026-05-22 23:10:51 +02:00
Ulrich Meine 1425297ef4 Adjust chart axis labelling for better intervals 2026-05-21 23:00:24 +02:00
Ulrich Meine 92a6266d71 Fix typo in config_obp60 2026-05-18 21:43:12 +02:00
Norbert Walter 7be6796a84 Merge pull request #239 from Scorgan01/PageCurrent
New PageCurrent for display of current
2026-04-30 19:02:04 +02:00
Norbert Walter 15c5c450a1 Merge pull request #238 from Scorgan01/GwWiFi
Fix Wifi AP shutdown during client WiFi reconnect attempt
2026-04-30 19:01:19 +02:00
Norbert Walter c0aa6b4010 Merge pull request #236 from thooge/master
Adapt patches to current code
2026-04-30 19:00:40 +02:00
Ulrich Meine 9c9d435390 PageCurrent: Delete old comments 2026-04-27 22:42:27 +02:00
Ulrich Meine 2d5838ebc8 PageCurrent: make ROLL value selectable in config; display name+unit right-aligned; code clean-up 2026-04-26 01:19:19 +02:00
Ulrich Meine 2f30e858c8 PageCurrent: added leeway coeff, changed boat value handling, fixed ROLL 2026-04-25 02:37:39 +02:00
Ulrich Meine 1098afa8b2 True Wind: calculate HDT only if true wind calc is requested 2026-04-25 01:23:42 +02:00
Ulrich Meine cfac5b0a05 Fix Wifi AP shutdown during client WiFi reconnect attempt 2026-04-23 23:06:05 +02:00
Ulrich Meine f84ef25a30 Initial version of Page Current 2026-04-17 23:45:11 +02:00
thooge f99656f3d7 Fix typos in OBP40 config 2026-04-10 19:40:24 +02:00
thooge ae1f402b49 Adapt patches to current code 2026-04-10 19:19:11 +02:00
Norbert Walter 3b2f73e22a Merge pull request #235 from Scorgan01/master
Fix vertical time axis label in graphical charts
2026-04-09 22:23:22 +02:00
Ulrich Meine 89dfdb35c3 Delete obsolete comments in OBPcharts.cpp 2026-04-06 01:02:54 +02:00
Ulrich Meine 3f1d2d4ee2 Fix vertikal time axis label 2026-04-06 00:58:38 +02:00
Norbert Walter 856e19cd5e Merge pull request #234 from Scorgan01/TrueWind-opt
Fixes and new 'smoothing' feature for data charts
2026-03-27 17:52:49 +01:00
Ulrich Meine 530c101421 Adjust default ranges for charts 2026-03-26 22:27:08 +01:00
Ulrich Meine 3deb80b2c6 Add smoothing option for any chart plot 2026-03-26 22:08:00 +01:00
Ulrich Meine 1f0e3cb6eb Delete unnecessary include of <movingAvg.h> in some Pages 2026-03-26 21:58:31 +01:00
Ulrich Meine a445726757 Convert class <movingAvg> to accept flexible data types 2026-03-26 21:57:17 +01:00
Scorgan01 aecc3c775f Merge branch 'norbert-walter:master' into TrueWind-opt 2026-03-26 21:44:18 +01:00
norbert-walter 0dfb1f8535 Code cleaning 2026-03-20 11:45:58 +01:00
norbert-walter ba851d7e1b Change defines 2026-03-20 11:43:35 +01:00
norbert-walter f7b1b56715 Change defines 2026-03-20 11:08:02 +01:00
norbert-walter 08153cc585 Add new board obp70 2026-03-20 10:41:42 +01:00
norbert-walter e309ec2123 Modify comments 2026-03-19 18:11:45 +01:00
Norbert Walter 7be72a8cc4 Fix for I2C initialisation (missing pin definitions) 2026-03-19 17:08:23 +00:00
norbert-walter 5fcca363f8 Modify TFT display initialisiation 2026-03-19 17:34:10 +01:00
Norbert Walter 7c6005958e Add new TFT display ILI9488 2026-03-19 16:17:22 +00:00
Norbert Walter 522759a26c Merge pull request #231 from TobiasE-github/master
mark scripts as executable
2026-03-15 21:34:46 +01:00
Norbert Walter ea63386cfc Merge pull request #230 from thooge/master
Page system rework and improvements
2026-03-15 21:33:56 +01:00
TobiasE-github f362e96749 Merge branch 'norbert-walter:master' into master 2026-03-15 20:22:16 +01:00
norbert-walter 27d0e5d1b7 Modify PageNavigation 2026-03-15 15:23:52 +01:00
TobiasE-github 53f7a1c5bb Merge branch 'norbert-walter:master' into master 2026-03-15 09:49:51 +01:00
norbert-walter 588c88dfff Fix obp60task.h for new obp70 config 2026-03-14 23:08:57 +01:00
TobiasE-github 73bc57fe9a Merge branch 'norbert-walter:master' into master 2026-03-13 22:52:11 +01:00
norbert-walter d13c4af9cb Changes 2026-03-13 21:58:29 +01:00
Norbert Walter a7cdfe700b Typo 2026-03-13 20:57:51 +00:00
Norbert Walter f165c628ae Fix NetworkClient.cpp for more robust data transmission and decompression 2026-03-13 20:28:08 +00:00
Tobias Edler 54dcb5fd5a mark scripts as executable 2026-03-13 20:48:16 +01:00
norbert-walter 604e2a15bd Fix for platformio.ini 2026-03-13 20:02:30 +01:00
norbert-walter 4e0bc88419 Fix custom config for obp70_s3 2026-03-13 19:57:44 +01:00
norbert-walter c1a49fac4b Changes 2026-03-13 18:04:09 +01:00
norbert-walter 019f7a11ef Add device OBP70 in paltformio.ini and config_obp70.json 2026-03-13 16:59:02 +01:00
norbert-walter 12fc962107 Add color replacement for b/w 2026-03-13 15:44:08 +01:00
Norbert Walter a0afd5deca Fix for NetworkCleint.cpp 2026-03-12 18:49:53 +00:00
Norbert Walter 57272f3a44 Fix for PageNavigation 2026-03-12 18:40:36 +00:00
norbert-walter 64f9058f85 Activate color map 2026-03-12 19:32:10 +01:00
Norbert Walter cd84e427a3 Color navigation map for PageNavigation. 2026-03-12 17:33:37 +00:00
thooge 86cd1ed97c Add missing functions to LedSpiTask 2026-03-08 15:22:26 +01:00
norbert-walter ec667c18f5 Fix for power shell script 2026-03-06 15:20:47 +01:00
norbert-walter af9bca4d66 Add power shell script 2026-03-06 14:59:06 +01:00
norbert-walter bc1440c428 Add code_size.sh 2026-03-06 14:48:55 +01:00
norbert-walter 5f161ce839 Modify platformio.ini 2026-03-06 10:39:18 +01:00
Norbert Walter a17db389fb Fix for ImageDecoder.cpp 2026-03-04 18:37:44 +00:00
Norbert Walter c5533ab0ef Add frame buffer for scaled picture 2026-03-04 09:02:29 +00:00
Norbert Walter 44e99cda50 Add compiler flags for scaling and antialiasing 2026-03-01 17:24:32 +00:00
Norbert Walter 2c2318adcc Scale display output to max display hight and use antialiasing 2026-03-01 17:14:46 +00:00
Norbert Walter 373878825c Fix black background 2026-03-01 16:55:26 +00:00
Norbert Walter 8cd193f2fc Fix black background 2026-03-01 16:36:20 +00:00
Norbert Walter 44b318ea0c Black background for unused area 2026-03-01 16:26:13 +00:00
Norbert Walter 9cfa9ccf67 Start TFT with black background 2026-03-01 16:15:02 +00:00
Norbert Walter 762004ac9f Implement shaddow frame buffer for TFT display 2026-03-01 15:55:34 +00:00
norbert-walter 907a817573 Add info 2026-03-01 16:08:54 +01:00
Norbert Walter 53bdc491f1 Fix crash with reboot 2026-03-01 15:06:16 +00:00
norbert-walter 6cd0965e46 Merge remote-tracking branch 'origin/master' 2026-03-01 15:54:00 +01:00
Norbert Walter f3f20f40c7 Fix crash with reboot (GwWifi + PageNavigation) 2026-03-01 15:53:53 +01:00
Norbert Walter ab0bac7577 Fix crash ith reboot (GwWifi + PageNavigation) 2026-03-01 14:52:53 +00:00
Norbert Walter a4b99fb619 Fix display refresh 2026-03-01 00:32:50 +00:00
Norbert Walter b7fab08306 Fix for display flicker by display refresh 2026-02-28 23:54:26 +00:00
Norbert Walter b10792b243 Fix for display flicker by display refresh 2026-02-28 23:42:10 +00:00
Norbert Walter 2b2d2e3fd6 Fix for drawButtonCenter() 2026-02-28 23:14:38 +00:00
Norbert Walter 75724b29eb Fix for drawButtonCenter() 2026-02-28 23:06:30 +00:00
Norbert Walter abcbf50422 Fix for displayGetTextBounds() 2026-02-28 22:58:12 +00:00
Norbert Walter 03b9294a1e Fix for drawTextCenter() 2026-02-28 22:16:16 +00:00
Norbert Walter 2e47263008 Fix for drawTextCenter() for ST7796 2026-02-28 22:10:57 +00:00
Norbert Walter ffa9c8ac83 Fix basle line for fonts 2026-02-28 18:39:05 +00:00
Norbert Walter 39c4e7fb32 Fix base line logic for fonts 2026-02-28 18:33:32 +00:00
Norbert Walter 5a30d7d022 Fix boot loop reimplement font implementation 2026-02-28 18:23:23 +00:00
Norbert Walter b29d89525a Fix boot loop 2026-02-28 18:16:03 +00:00
Norbert Walter 270642ad7c Fix for font using 2026-02-28 18:06:40 +00:00
Norbert Walter 253aaa5b2e Using new wrepper funktion in PageNavigation 2026-02-28 17:04:31 +00:00
Norbert Walter 1aac8fdcd9 Fix image converter 2026-02-28 16:56:00 +00:00
Norbert Walter 7e35c250cd Fix image converter 2026-02-28 16:42:37 +00:00
Norbert Walter 0b3b5f387b Fix image converter 2026-02-28 16:23:43 +00:00
Norbert Walter 42c147ac57 Fix image converter 2026-02-28 16:13:15 +00:00
Norbert Walter 7434601382 Fix image converter 2026-02-28 15:55:52 +00:00
Norbert Walter 2d6127912b Fix image problem with 1Bit images 2026-02-28 15:27:39 +00:00
norbert-walter 415c155949 Fix for display output to mechanical center adjusted 2026-02-28 16:18:14 +01:00
Norbert Walter 9ac0b4a2bb Rotate display an showthe content in the middle of creen 2026-02-28 14:28:09 +00:00
norbert-walter 8a9f87a8f4 Fix platformio.ini 2026-02-28 00:05:54 +01:00
norbert-walter ea6920f696 Merge branch 'tft_display' 2026-02-27 23:57:02 +01:00
norbert-walter ce4b581f7f Merge branch 'autopilot3' 2026-02-27 23:56:23 +01:00
Norbert Walter aea401589d Add 4 inch TFT display ST7796 2026-02-27 21:33:23 +00:00
thooge 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
thooge 97fcebdcb7 Added format "formatXdr:A:rd" to formatter (see #159) 2026-02-23 18:56:20 +01:00
thooge a6fd3ef599 System page with N2K device list by improved patching feature and patch 2026-02-23 15:34:10 +01:00
Ulrich Meine 79aeeed65c Show correct time line on horizontal charts 2026-02-20 23:59:59 +01:00
Scorgan01 386b023677 Merge branch 'norbert-walter:master' into TrueWind-opt 2026-02-20 23:58:54 +01:00
norbert-walter 501b9f7023 Add more infos 2026-02-20 18:09:12 +01:00
norbert-walter 94f29eed06 Typo 2026-02-20 17:58:14 +01:00
norbert-walter 6a0811f42f Add diff comments to original version 2026-02-20 17:56:30 +01:00
norbert-walter bcb618fc7b Change AsyncTCP and AsyncWebServer to newer versions 2026-02-20 17:42:00 +01:00
norbert-walter 2de3a1b084 Add backoff in PageNavigation for better handling with bad connection 2026-02-20 17:21:26 +01:00
norbert-walter c647c2fe44 Only decompress zip after a succsessfully contenet download 2026-02-20 14:54:48 +01:00
norbert-walter 25422b77d6 Fix NetworkCleint.cpp in case of bad connections 2026-02-20 14:17:57 +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
Ulrich Meine f21c8da2d2 Clear old code fragments from history buffer class 2026-02-15 21:50:20 +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
Scorgan01 1b4c952d2b Merge branch 'norbert-walter:master' into TrueWind-opt 2026-02-15 18:35:13 +01:00
Norbert Walter 00b06f458b GwWifi race condition safe, PageNavigation check the Wifi connection befor using 2026-02-15 16:11:19 +00:00
Ulrich Meine 34af16092b Calculate AWD conditionless;
restructured true wind calculation for clearer decision tree
2026-02-14 02:40:23 +01:00
norbert-walter 6c7997e369 Fix iRTC time for N2K bus 2026-02-08 22:00:22 +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
85 changed files with 11079 additions and 3243 deletions
+56
View File
@@ -0,0 +1,56 @@
{
"build": {
"arduino":{
"ldscript": "esp32s3_out.ld",
"partitions": "default_16MB.csv",
"memory_type": "qio_opi"
},
"core": "esp32",
"extra_flags": [
"-DBOARD_HAS_PSRAM",
"-DARDUINO_ESP32S3_DEV",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
"f_cpu": "240000000L",
"f_flash": "80000000L",
"flash_mode": "qio",
"hwids": [
[
"0x303A",
"0x1001"
]
],
"mcu": "esp32s3",
"variant": "obp70s3"
},
"connectivity": [
"bluetooth",
"wifi"
],
"debug": {
"default_tool": "esp-builtin",
"onboard_tools": [
"esp-builtin"
],
"openocd_target": "esp32s3.cfg"
},
"frameworks": [
"arduino",
"espidf"
],
"name": "OBP70 ESP32-S3-N16R8 16 MB QD, 8 MB PSRAM)",
"upload": {
"flash_size": "16MB",
"maximum_ram_size": 327680,
"maximum_size": 16777216,
"use_1200bps_touch": true,
"wait_for_upload_port": true,
"require_upload_port": true,
"speed": 460800
},
"url": "https://open-boat-projects.org/en/diy-multifunktionsdisplay-obp-60/",
"vendor": "Open Boat Projects"
}
+10 -1
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();}
+100 -4
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,8 +131,46 @@ 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();
connectInternal(); // Keep locked sections short to avoid cross-core stalls/WDT.
if (acquireMutex()){
WiFi.disconnect(true);
releaseMutex();
}
else{
LOG_DEBUG(GwLog::ERROR,"GwWifi: mutex timeout in loop (disconnect)");
}
delay(300);
if (!apActive) {
if (acquireMutex()){
esp_err_t stopErr=esp_wifi_stop();
releaseMutex();
if (stopErr != ESP_OK){
LOG_DEBUG(GwLog::ERROR,"GwWifi: esp_wifi_stop failed: %d",(int)stopErr);
}
}
else{
LOG_DEBUG(GwLog::ERROR,"GwWifi: mutex timeout in loop (stop)");
}
delay(100);
if (acquireMutex()){
esp_err_t startErr=esp_wifi_start();
releaseMutex();
if (startErr != ESP_OK){
LOG_DEBUG(GwLog::ERROR,"GwWifi: esp_wifi_start failed: %d",(int)startErr);
}
connectInternal();
}
else{
LOG_DEBUG(GwLog::ERROR,"GwWifi: mutex timeout in loop (start)");
}
} else {
connectInternal();
}
} }
} }
else{ else{
@@ -126,11 +191,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();
} }
+14
View File
@@ -0,0 +1,14 @@
Changes to original project (wellenvogel)
* esp32-nmea2000-obp60/gwwifi/GwWifi.cpp
- any fixes for reconnect handling
* GWStatisticsw.h
- changed time source for log messages
* esp32-nmea2000-obp60/platformio.ini
- change to newer versions for AsyncTCP and AsyncWebServer (better handling for bad WiFi connections)
From AsyncTCP-esphome @ 2.0.1
ottowinter/ESPAsyncWebServer-esphome@2.0.1
To AsyncTCP-esphome @ 2.1.1
ottowinter/ESPAsyncWebServer-esphome@3.4.0
+14
View File
@@ -0,0 +1,14 @@
#!/bin/bash
dir="$1"
total=0
while IFS= read -r -d '' file; do
lines=$(wc -l < "$file")
echo "$file : $lines"
total=$((total + lines))
done < <(find "$dir" \( -name "*.c" -o -name "*.cpp" -o -name "*.h" \) -type f -print0)
echo "-----------------------"
echo "Over all files: $total"
-217
View File
@@ -1,217 +0,0 @@
/*
Menu system for online configuration
A menu consists of a list of menuitems.
Graphical representation is stored:
upper left corner: x, y
bounding box:
A menu consists of three columns
- menu text, if selected highlighted
- menu value with optional unit
- menu description or additional data for value
*/
#include "ConfigMenu.h"
ConfigMenuItem::ConfigMenuItem(String itemtype, String itemlabel, uint16_t itemval, String itemunit) {
if (! (itemtype == "int" or itemtype == "bool")) {
valtype = "int";
} else {
valtype = itemtype;
}
label = itemlabel;
min = 0;
max = std::numeric_limits<uint16_t>::max();
value = itemval;
unit = itemunit;
step = 1;
}
void ConfigMenuItem::setRange(uint16_t valmin, uint16_t valmax, std::vector<uint16_t> valsteps) {
min = valmin;
max = valmax;
steps = valsteps;
step = steps[0];
};
bool ConfigMenuItem::checkRange(uint16_t checkval) {
return (checkval >= min) and (checkval <= max);
}
String ConfigMenuItem::getLabel() {
return label;
};
uint16_t ConfigMenuItem::getValue() {
return value;
}
bool ConfigMenuItem::setValue(uint16_t newval) {
if (valtype == "int") {
if (newval >= min and newval <= max) {
value = newval;
return true;
}
return false; // out of range
} else if (valtype == "bool") {
value = (newval != 0) ? 1 : 0;
return true;
}
return false; // invalid type
};
void ConfigMenuItem::incValue() {
// increase value by step
if (valtype == "int") {
if (value + step < max) {
value += step;
} else {
value = max;
}
} else if (valtype == "bool") {
value = !value;
}
};
void ConfigMenuItem::decValue() {
// decrease value by step
if (valtype == "int") {
if (value - step > min) {
value -= step;
} else {
value = min;
}
} else if (valtype == "bool") {
value = !value;
}
};
String ConfigMenuItem::getUnit() {
return unit;
}
uint16_t ConfigMenuItem::getStep() {
return step;
}
void ConfigMenuItem::setStep(uint16_t newstep) {
if (std::find(steps.begin(), steps.end(), newstep) == steps.end()) {
return; // invalid step: not in list of possible steps
}
step = newstep;
}
int8_t ConfigMenuItem::getPos() {
return position;
};
void ConfigMenuItem::setPos(int8_t newpos) {
position = newpos;
};
String ConfigMenuItem::getType() {
return valtype;
}
ConfigMenu::ConfigMenu(String menutitle, uint16_t menu_x, uint16_t menu_y) {
title = menutitle;
x = menu_x;
y = menu_y;
};
ConfigMenuItem* ConfigMenu::addItem(String key, String label, String valtype, uint16_t val, String valunit) {
if (items.find(key) != items.end()) {
// duplicate keys not allowed
return nullptr;
}
ConfigMenuItem *itm = new ConfigMenuItem(valtype, label, val, valunit);
items.insert(std::pair<String, ConfigMenuItem*>(key, itm));
// Append key to index, index starting with 0
int8_t ix = items.size() - 1;
index[ix] = key;
itm->setPos(ix);
return itm;
};
void ConfigMenu::setItemDimension(uint16_t itemwidth, uint16_t itemheight) {
w = itemwidth;
h = itemheight;
};
void ConfigMenu::setItemActive(String key) {
if (items.find(key) != items.end()) {
activeitem = items[key]->getPos();
} else {
activeitem = -1;
}
};
int8_t ConfigMenu::getActiveIndex() {
return activeitem;
}
ConfigMenuItem* ConfigMenu::getActiveItem() {
if (activeitem < 0) {
return nullptr;
}
return items[index[activeitem]];
};
ConfigMenuItem* ConfigMenu::getItemByIndex(uint8_t ix) {
if (ix > index.size() - 1) {
return nullptr;
}
return items[index[ix]];
};
ConfigMenuItem* ConfigMenu::getItemByKey(String key) {
if (items.find(key) == items.end()) {
return nullptr;
}
return items[key];
};
uint8_t ConfigMenu::getItemCount() {
return items.size();
};
void ConfigMenu::goPrev() {
if (activeitem == 0) {
activeitem = items.size() - 1;
} else {
activeitem--;
}
}
void ConfigMenu::goNext() {
if (activeitem == items.size() - 1) {
activeitem = 0;
} else {
activeitem++;
}
}
Point ConfigMenu::getXY() {
return {static_cast<double>(x), static_cast<double>(y)};
}
Rect ConfigMenu::getRect() {
return {static_cast<double>(x), static_cast<double>(y),
static_cast<double>(w), static_cast<double>(h)};
}
Rect ConfigMenu::getItemRect(int8_t index) {
return {static_cast<double>(x), static_cast<double>(y + index * h),
static_cast<double>(w), static_cast<double>(h)};
}
void ConfigMenu::setCallback(void (*callback)()) {
fptrCallback = callback;
}
void ConfigMenu::storeValues() {
if (fptrCallback) {
fptrCallback();
}
}
-67
View File
@@ -1,67 +0,0 @@
#pragma once
#include <Arduino.h>
#include <vector>
#include <map>
#include "Graphics.h" // for Point and Rect
class ConfigMenuItem {
private:
String label;
uint16_t value;
String unit;
String desc; // optional data to display
String valtype; // "int" | "bool" -> TODO "list"
uint16_t min;
uint16_t max;
std::vector<uint16_t> steps;
uint16_t step;
int8_t position; // counted fom 0
public:
ConfigMenuItem(String itemtype, String itemlabel, uint16_t itemval, String itemunit);
void setRange(uint16_t valmin, uint16_t valmax, std::vector<uint16_t> steps);
bool checkRange(uint16_t checkval);
String getLabel();
uint16_t getValue();
bool setValue(uint16_t newval);
void incValue();
void decValue();
String getUnit();
uint16_t getStep();
void setStep(uint16_t newstep);
int8_t getPos();
void setPos(int8_t newpos);
String getType();
};
class ConfigMenu {
private:
String title;
std::map <String,ConfigMenuItem*> items;
std::map <uint8_t,String> index;
int8_t activeitem = -1; // refers to position of item
uint16_t x;
uint16_t y;
uint16_t w;
uint16_t h;
void (*fptrCallback)();
public:
ConfigMenu(String title, uint16_t menu_x, uint16_t menu_y);
ConfigMenuItem* addItem(String key, String label, String valtype, uint16_t val, String valunit);
void setItemDimension(uint16_t itemwidth, uint16_t itemheight);
int8_t getActiveIndex();
void setItemActive(String key);
ConfigMenuItem* getActiveItem();
ConfigMenuItem* getItemByIndex(uint8_t index);
ConfigMenuItem* getItemByKey(String key);
uint8_t getItemCount();
void goPrev();
void goNext();
Point getXY();
Rect getRect();
Rect getItemRect(int8_t index);
void setCallback(void (*callback)());
void storeValues();
};
+12 -3
View File
@@ -2,13 +2,22 @@
#include <mbedtls/base64.h> #include <mbedtls/base64.h>
// Decoder for Base64 content // Decoder for Base64 content
bool ImageDecoder::decodeBase64(const String& base64, uint8_t* outBuffer, size_t outSize, size_t& decodedSize) { bool ImageDecoder::decodeBase64(const char* base64, size_t base64Len, uint8_t* outBuffer, size_t outSize, size_t& decodedSize) {
if (base64 == nullptr) {
decodedSize = 0;
return false;
}
int ret = mbedtls_base64_decode( int ret = mbedtls_base64_decode(
outBuffer, outBuffer,
outSize, outSize,
&decodedSize, &decodedSize,
(const unsigned char*)base64.c_str(), (const unsigned char*)base64,
base64.length() base64Len
); );
return (ret == 0); return (ret == 0);
} }
// Decoder for Base64 content
bool ImageDecoder::decodeBase64(const String& base64, uint8_t* outBuffer, size_t outSize, size_t& decodedSize) {
return decodeBase64(base64.c_str(), base64.length(), outBuffer, outSize, decodedSize);
}
+1
View File
@@ -5,5 +5,6 @@
class ImageDecoder { class ImageDecoder {
public: public:
bool decodeBase64(const char* base64, size_t base64Len, uint8_t* outBuffer, size_t outSize, size_t& decodedSize);
bool decodeBase64(const String& base64, uint8_t* outBuffer, size_t outSize, size_t& decodedSize); bool decodeBase64(const String& base64, uint8_t* outBuffer, size_t outSize, size_t& decodedSize);
}; };
+25 -1
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;
@@ -231,4 +255,4 @@ void handleSpiLeds(void *param){
void createSpiLedTask(LedTaskData *param){ void createSpiLedTask(LedTaskData *param){
xTaskCreate(handleSpiLeds,"handleLeds",4000,param,3,NULL); xTaskCreate(handleSpiLeds,"handleLeds",4000,param,3,NULL);
} }
+3 -1
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);
+466 -54
View File
@@ -1,16 +1,140 @@
#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"
} }
static uint32_t crc32_update(uint32_t crc, const uint8_t* data, size_t len) {
crc = ~crc;
for (size_t i = 0; i < len; ++i) {
crc ^= data[i];
for (int bit = 0; bit < 8; ++bit) {
uint32_t mask = -(int32_t)(crc & 1U);
crc = (crc >> 1) ^ (0xEDB88320U & mask);
}
}
return ~crc;
}
// Constructor // Constructor
NetworkClient::NetworkClient(size_t reserveSize) NetworkClient::NetworkClient(size_t reserveSize)
: _doc(reserveSize), : _doc(reserveSize),
_valid(false) _valid(false),
_jsonRaw(nullptr),
_jsonRawLen(0),
_imageWidth(0),
_imageHeight(0),
_numberPixels(0),
_pictureBase64(nullptr),
_pictureBase64Len(0)
{ {
} }
NetworkClient::~NetworkClient() {
if (_jsonRaw != nullptr) {
free(_jsonRaw);
_jsonRaw = nullptr;
_jsonRawLen = 0;
}
}
bool NetworkClient::findJsonIntField(const char* json, size_t len, const char* key, int& outValue) {
if (json == nullptr || key == nullptr || len == 0) {
return false;
}
char pattern[64];
int plen = snprintf(pattern, sizeof(pattern), "\"%s\"", key);
if (plen <= 0 || (size_t)plen >= sizeof(pattern)) {
return false;
}
const char* keyPos = strstr(json, pattern);
if (keyPos == nullptr) {
return false;
}
const char* end = json + len;
const char* colon = strchr(keyPos + plen, ':');
if (colon == nullptr || colon >= end) {
return false;
}
const char* p = colon + 1;
while (p < end && (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n')) {
++p;
}
if (p >= end) {
return false;
}
char* parseEnd = nullptr;
long value = strtol(p, &parseEnd, 10);
if (parseEnd == p) {
return false;
}
outValue = (int)value;
return true;
}
bool NetworkClient::extractJsonStringInPlace(char* json, size_t len, const char* key, char*& outValue, size_t& outLen) {
outValue = nullptr;
outLen = 0;
if (json == nullptr || key == nullptr || len == 0) {
return false;
}
char pattern[64];
int plen = snprintf(pattern, sizeof(pattern), "\"%s\"", key);
if (plen <= 0 || (size_t)plen >= sizeof(pattern)) {
return false;
}
char* keyPos = strstr(json, pattern);
if (keyPos == nullptr) {
return false;
}
char* end = json + len;
char* colon = strchr(keyPos + plen, ':');
if (colon == nullptr || colon >= end) {
return false;
}
char* p = colon + 1;
while (p < end && (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n')) {
++p;
}
if (p >= end || *p != '"') {
return false;
}
char* valueStart = p + 1;
char* cur = valueStart;
while (cur < end) {
if (*cur == '\\') {
++cur;
if (cur < end) {
++cur;
}
continue;
}
if (*cur == '"') {
*cur = '\0';
outValue = valueStart;
outLen = (size_t)(cur - valueStart);
return true;
}
++cur;
}
return false;
}
// Skip GZIP Header an goto DEFLATE content // Skip GZIP Header an goto DEFLATE content
int NetworkClient::skipGzipHeader(const uint8_t* data, size_t len) { int NetworkClient::skipGzipHeader(const uint8_t* data, size_t len) {
if (len < 10) return -1; if (len < 10) return -1;
@@ -48,11 +172,18 @@ int NetworkClient::skipGzipHeader(const uint8_t* data, size_t len) {
// HTTP GET + GZIP Decompression (reading in chunks) // HTTP GET + GZIP Decompression (reading in chunks)
bool NetworkClient::httpGetGzip(const String& url, uint8_t*& outData, size_t& outLen) { bool NetworkClient::httpGetGzip(const String& url, uint8_t*& outData, size_t& outLen) {
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 not with WiFi connectetd then return without any activities
if (!gwWifi.clientConnected()) {
if (DEBUGING) {Serial.println("No WiFi connection");}
return false;
}
// If frame buffer not correct allocated then return without any activities
if (!buffer) { if (!buffer) {
if (DEBUG) {Serial.println("Malloc failed (buffer");} if (DEBUGING) {Serial.println("Malloc failed buffer");}
return false; return false;
} }
@@ -63,20 +194,50 @@ bool NetworkClient::httpGetGzip(const String& url, uint8_t*& outData, size_t& ou
http.setTimeout(TCPREADTIMEOUT); // Read timeout in ms (can be adjusted in NetworkClient.h) http.setTimeout(TCPREADTIMEOUT); // Read timeout in ms (can be adjusted in NetworkClient.h)
http.begin(url); http.begin(url);
// NEW: force server to close the connection after the response (prevents "stuck" keep-alive reads)
http.addHeader("Connection", "close");
// NEW: request gzip, but we will only decompress if the server actually answers with gzip
http.addHeader("Accept-Encoding", "gzip"); http.addHeader("Accept-Encoding", "gzip");
// NEW: register headers BEFORE GET() (more reliable with Arduino HTTPClient)
if (DEBUGING) {
// We need follow key words
const char* keys[] = {
"Content-Encoding",
"Transfer-Encoding",
"Content-Length"
};
// Read header
http.collectHeaders(keys, 3);
}
int code = http.GET(); int code = http.GET();
if (code != HTTP_CODE_OK) { if (code != HTTP_CODE_OK) {
Serial.printf("HTTP ERROR: %d\n", code); Serial.printf("HTTP Client ERROR: %d (%s)\n", code, http.errorToString(code).c_str());
// Hard reset HTTP + socket // Hard reset HTTP + socket
WiFiClient* tmp = http.getStreamPtr(); WiFiClient* tmp = http.getStreamPtr();
if (tmp) tmp->stop(); // Force close TCP socket if (tmp) tmp->stop(); // Force close TCP socket
http.end(); http.end();
free(buffer); free(buffer);
return false; return false;
} }
else{
if (DEBUGING) {
String ce = http.header("Content-Encoding");
String te = http.header("Transfer-Encoding");
String cl = http.header("Content-Length");
// Print header informations
Serial.printf("Content-Encoding=%s Transfer-Encoding=%s Content-Length=%s\n",
ce.c_str(),
te.c_str(),
cl.c_str());
}
}
WiFiClient* stream = http.getStreamPtr(); WiFiClient* stream = http.getStreamPtr();
@@ -85,55 +246,251 @@ bool NetworkClient::httpGetGzip(const String& url, uint8_t*& outData, size_t& ou
const uint32_t READ_TIMEOUT = READDATATIMEOUT; // Timeout for reading data (can be adjusted in NetworkClient.h) const uint32_t READ_TIMEOUT = READDATATIMEOUT; // Timeout for reading data (can be adjusted in NetworkClient.h)
bool complete = false; bool complete = false;
bool aborting = false; // NEW: remember if we must force-close socket
while (http.connected() && !complete) { // NEW: detect if server really sent gzip
String ce = http.header("Content-Encoding");
bool isGzip = ce.equalsIgnoreCase("gzip");
size_t avail = stream->available(); // NEW: read expected body size if provided by server (prevents waiting forever for missing bytes)
int total = http.getSize(); // returns Content-Length, or -1 if unknown/chunked
if (avail == 0) { // NEW: fail fast if server claims something larger than our buffer
if (millis() - lastData > READ_TIMEOUT) { if (total > 0 && (size_t)total > capacity) {
Serial.println("TIMEOUT waiting for data!"); Serial.println("Response exceeds READLIMIT.");
break; aborting = true;
}
delay(1);
continue;
}
if (len + avail > capacity)
avail = capacity - len;
int read = stream->readBytes(buffer + len, avail);
len += read;
lastData = millis();
if (DEBUG) {Serial.printf("Read chunk: %d (total: %d)\n", read, (int)len);}
if (len < 20) continue; // Not enough data for header
int headerOffset = skipGzipHeader(buffer, len);
if (headerOffset < 0) continue;
unsigned long testLen = len * 8; // Dynamic expansion
uint8_t* test = (uint8_t*)malloc(testLen);
if (!test) continue;
unsigned long srcLen = len - headerOffset;
int res = puff(test, &testLen, buffer + headerOffset, &srcLen);
if (res == 0) {
if (DEBUG) {Serial.printf("Decompress OK! Size: %lu bytes\n", testLen);}
outData = test;
outLen = testLen;
complete = true;
break;
}
free(test);
} }
// --- Added: Force-close connection in all cases to avoid stuck TCP sockets --- // NEW: if not gzip, we will not try to decompress (prevents false "Decompress OK" / random success)
if (stream) stream->stop(); // You can either handle plain JSON here or just fail-fast.
if (!isGzip && !aborting) {
if (DEBUGING) {
Serial.println("Server response is NOT gzip (Content-Encoding != gzip).");
Serial.println("Either disable Accept-Encoding: gzip or add plain-body handling here.");
}
// --- Plain-body handling (recommended): read full body into outData as-is ---
// NEW: try to read Content-Length bytes if available (more robust)
if (total > 0 && (size_t)total > capacity) {
Serial.println("Plain response exceeds READLIMIT.");
aborting = true;
} else {
// Read until we have all bytes (Content-Length) or until connection closes + buffer drains
while ((http.connected() || (stream && stream->available())) && !aborting) {
size_t avail = stream ? stream->available() : 0;
if (avail == 0) {
if (millis() - lastData > READ_TIMEOUT) {
Serial.println("TIMEOUT waiting for data (plain)!");
aborting = true;
break;
}
delay(1);
continue;
}
if (len >= capacity) {
Serial.println("READLIMIT reached, aborting (plain).");
aborting = true;
break;
}
if (len + avail > capacity)
avail = capacity - len;
int read = stream->readBytes(buffer + len, avail);
if (read > 0) {
len += (size_t)read;
lastData = millis();
}
// NEW: stop reading as soon as we have the full response
if (total > 0 && (int)len >= total) {
break; // we got full body
}
}
}
if (aborting) {
// --- Added: Force-close connection only if aborted to avoid TCP RST storms ---
if (stream) stream->stop(); // Force close TCP socket
http.end();
free(buffer);
return false;
}
if (total > 0 && (int)len != total) {
Serial.printf("Plain response incomplete: got=%d expected=%d\n", (int)len, total);
if (stream) stream->stop();
http.end();
free(buffer);
return false;
}
// Return plain body to caller
outData = (uint8_t*)malloc(len + 1);
if (!outData) {
Serial.println("Malloc failed outData (plain).");
// --- Added: Force-close connection only if aborted to avoid TCP RST storms ---
if (stream) stream->stop(); // Force close TCP socket
http.end();
free(buffer);
return false;
}
memcpy(outData, buffer, len);
outData[len] = 0;
outLen = len;
http.end();
free(buffer);
return true;
}
// --- GZIP path (only if Content-Encoding is gzip) ---
if (!aborting) {
// NEW: read exactly Content-Length bytes when available (prevents partial-body timeout loops)
while ((http.connected() || (stream && stream->available())) && !complete && !aborting) {
size_t avail = stream ? stream->available() : 0;
if (avail == 0) {
// NEW: if Content-Length is known and we already read it all, stop immediately
if (total > 0 && (int)len >= total) {
break;
}
if (millis() - lastData > READ_TIMEOUT) {
Serial.println("TIMEOUT waiting for data!");
aborting = true; // NEW: mark abnormal exit
break;
}
delay(1);
continue;
}
// NEW: safety check if buffer limit is reached
if (len >= capacity) {
Serial.println("READLIMIT reached, aborting.");
aborting = true;
break;
}
// NEW: if Content-Length is known, do not read beyond it
if (total > 0) {
size_t remaining = (size_t)total - len;
if (avail > remaining) avail = remaining;
}
if (len + avail > capacity)
avail = capacity - len;
int read = stream->readBytes(buffer + len, avail);
if (read <= 0) {
// NEW: avoid tight loop if read returns zero
delay(1);
continue;
}
len += (size_t)read;
lastData = millis();
if (DEBUGING) {Serial.printf("Read chunk: %d (total: %d)\n", read, (int)len);}
// NEW: if Content-Length is known and fully received, we can stop reading
if (total > 0 && (int)len >= total) {
break;
}
}
// NEW: only attempt gzip parse/decompress after we have a complete body (when Content-Length is known)
// This avoids wasting heap with repeated malloc/free and reduces fragmentation over long runtimes.
if (!aborting) {
if (total > 0 && (int)len != total) {
Serial.printf("GZIP response incomplete: got=%d expected=%d\n", (int)len, total);
aborting = true;
}
}
if (!aborting) {
if (len < 20) {
aborting = true;
} else {
int headerOffset = skipGzipHeader(buffer, len);
if (headerOffset < 0) {
aborting = true;
} else {
size_t deflateLen = len - (size_t)headerOffset;
// GZIP trailer (CRC32 + ISIZE) is 8 bytes and not part of deflate stream.
if (deflateLen >= 8) {
deflateLen -= 8;
}
unsigned long srcLenForSize = (unsigned long)deflateLen;
unsigned long outNeeded = 0;
int sizeRes = puff(NIL, &outNeeded, buffer + headerOffset, &srcLenForSize);
if (sizeRes != 0) {
if (DEBUGING) {
Serial.printf("Decompress size probe failed: res=%d src=%lu\n", sizeRes, srcLenForSize);
}
aborting = true;
} else {
uint8_t* test = (uint8_t*)malloc((size_t)outNeeded + 1);
if (!test) {
Serial.println("Malloc failed test buffer, aborting.");
aborting = true;
} else {
unsigned long srcLen = (unsigned long)deflateLen;
unsigned long testLen = outNeeded;
int res = puff(test, &testLen, buffer + headerOffset, &srcLen);
if (res == 0) {
uint32_t trailerCrc =
(uint32_t)buffer[len - 8] |
((uint32_t)buffer[len - 7] << 8) |
((uint32_t)buffer[len - 6] << 16) |
((uint32_t)buffer[len - 5] << 24);
uint32_t trailerIsize =
(uint32_t)buffer[len - 4] |
((uint32_t)buffer[len - 3] << 8) |
((uint32_t)buffer[len - 2] << 16) |
((uint32_t)buffer[len - 1] << 24);
uint32_t calcCrc = crc32_update(0, test, (size_t)testLen);
uint32_t calcIsize = (uint32_t)testLen;
if (calcCrc != trailerCrc || calcIsize != trailerIsize) {
Serial.printf(
"GZIP CRC/ISIZE mismatch crc=%08lx/%08lx isize=%lu/%lu\n",
(unsigned long)calcCrc,
(unsigned long)trailerCrc,
(unsigned long)calcIsize,
(unsigned long)trailerIsize
);
free(test);
aborting = true;
} else {
test[testLen] = 0;
if (DEBUGING) {Serial.printf("Decompress OK! Size: %lu bytes\n", testLen);}
outData = test;
outLen = (size_t)testLen;
complete = true;
}
} else {
if (DEBUGING) {
Serial.printf("Decompress failed: res=%d out=%lu src=%lu\n", res, testLen, srcLen);
}
free(test);
aborting = true;
}
}
}
}
}
}
}
// --- Added: Force-close connection only if aborted to avoid TCP RST storms ---
if (aborting && stream) stream->stop(); // NEW: stop() only on abnormal termination
http.end(); http.end();
free(buffer); free(buffer);
@@ -150,6 +507,18 @@ bool NetworkClient::httpGetGzip(const String& url, uint8_t*& outData, size_t& ou
bool NetworkClient::fetchAndDecompressJson(const String& url) { bool NetworkClient::fetchAndDecompressJson(const String& url) {
_valid = false; _valid = false;
_doc.clear();
_imageWidth = 0;
_imageHeight = 0;
_numberPixels = 0;
_pictureBase64 = nullptr;
_pictureBase64Len = 0;
if (_jsonRaw != nullptr) {
free(_jsonRaw);
_jsonRaw = nullptr;
_jsonRawLen = 0;
}
uint8_t* raw = nullptr; uint8_t* raw = nullptr;
size_t rawLen = 0; size_t rawLen = 0;
@@ -159,15 +528,38 @@ bool NetworkClient::fetchAndDecompressJson(const String& url) {
return false; return false;
} }
DeserializationError err = deserializeJson(_doc, raw, rawLen); char* json = reinterpret_cast<char*>(raw);
free(raw); bool ok = true;
ok = findJsonIntField(json, rawLen, "number_pixels", _numberPixels) && ok;
ok = findJsonIntField(json, rawLen, "width", _imageWidth) && ok;
ok = findJsonIntField(json, rawLen, "height", _imageHeight) && ok;
ok = extractJsonStringInPlace(json, rawLen, "picture_base64", _pictureBase64, _pictureBase64Len) && ok;
if (err) { if (!ok) {
Serial.printf("JSON ERROR: %s\n", err.c_str()); Serial.println("JSON field extraction failed.");
free(raw);
return false; return false;
} }
if (DEBUG) {Serial.println("JSON OK!");} if (_imageWidth <= 0 || _imageHeight <= 0 || _pictureBase64Len == 0) {
Serial.printf("JSON invalid geometry/data w=%d h=%d b64=%u\n",
_imageWidth,
_imageHeight,
(unsigned int)_pictureBase64Len);
free(raw);
return false;
}
_jsonRaw = raw;
_jsonRawLen = rawLen;
if (DEBUGING) {
Serial.printf("JSON fields OK: num=%d w=%d h=%d b64=%u\n",
_numberPixels,
_imageWidth,
_imageHeight,
(unsigned int)_pictureBase64Len);
}
_valid = true; _valid = true;
return true; return true;
} }
@@ -176,6 +568,26 @@ JsonDocument& NetworkClient::json() {
return _doc; return _doc;
} }
int NetworkClient::imageWidth() const {
return _imageWidth;
}
int NetworkClient::imageHeight() const {
return _imageHeight;
}
int NetworkClient::numberPixels() const {
return _numberPixels;
}
const char* NetworkClient::pictureBase64() const {
return _pictureBase64;
}
size_t NetworkClient::pictureBase64Len() const {
return _pictureBase64Len;
}
bool NetworkClient::isValid() const { bool NetworkClient::isValid() const {
return _valid; return _valid;
} }
+16 -1
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 true // 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
@@ -12,16 +12,31 @@
class NetworkClient { class NetworkClient {
public: public:
NetworkClient(size_t reserveSize = 0); NetworkClient(size_t reserveSize = 0);
~NetworkClient();
bool fetchAndDecompressJson(const String& url); bool fetchAndDecompressJson(const String& url);
JsonDocument& json(); JsonDocument& json();
int imageWidth() const;
int imageHeight() const;
int numberPixels() const;
const char* pictureBase64() const;
size_t pictureBase64Len() const;
bool isValid() const; bool isValid() const;
private: private:
DynamicJsonDocument _doc; DynamicJsonDocument _doc;
bool _valid; bool _valid;
uint8_t* _jsonRaw;
size_t _jsonRawLen;
int _imageWidth;
int _imageHeight;
int _numberPixels;
char* _pictureBase64;
size_t _pictureBase64Len;
int skipGzipHeader(const uint8_t* data, size_t len); int skipGzipHeader(const uint8_t* data, size_t len);
bool httpGetGzip(const String& url, uint8_t*& outData, size_t& outLen); bool httpGetGzip(const String& url, uint8_t*& outData, size_t& outLen);
static bool findJsonIntField(const char* json, size_t len, const char* key, int& outValue);
static bool extractJsonStringInPlace(char* json, size_t len, const char* key, char*& outValue, size_t& outLen);
}; };
+323 -49
View File
@@ -11,6 +11,8 @@
#include "imglib.h" #include "imglib.h"
// Character sets // Character sets
#include "fonts/DSEG7Classic-BoldItalic10pt7b.h"
#include "fonts/DSEG7Classic-BoldItalic12pt7b.h"
#include "fonts/DSEG7Classic-BoldItalic16pt7b.h" #include "fonts/DSEG7Classic-BoldItalic16pt7b.h"
#include "fonts/DSEG7Classic-BoldItalic20pt7b.h" #include "fonts/DSEG7Classic-BoldItalic20pt7b.h"
#include "fonts/DSEG7Classic-BoldItalic26pt7b.h" #include "fonts/DSEG7Classic-BoldItalic26pt7b.h"
@@ -56,6 +58,61 @@ GxEPD2_BW<GxEPD2_420_SE0420NQ04, GxEPD2_420_SE0420NQ04::HEIGHT> display(GxEPD2_4
GxEPD2_BW<GxEPD2_420_SE0420NQ04, GxEPD2_420_SE0420NQ04::HEIGHT> & getdisplay(){return display;} GxEPD2_BW<GxEPD2_420_SE0420NQ04, GxEPD2_420_SE0420NQ04::HEIGHT> & getdisplay(){return display;}
#endif #endif
#ifdef TFT_DISPLAY
// panel device + offscreen shadow framebuffer
static LGFX panelDisplay;
static LGFXCanvas shadowDisplay(&panelDisplay);
static LGFXCanvas scaleDisplay(&panelDisplay);
static bool shadowDisplayInitialized = false;
static bool scaleDisplayInitialized = false;
static uint16_t scaleDisplayWidth = 0;
static uint16_t scaleDisplayHeight = 0;
LGFXCanvas & getdisplay(){return shadowDisplay;}
LGFX & getpaneldisplay(){return panelDisplay;}
LGFXCanvas & getscaleddisplay(){return scaleDisplay;}
bool initDisplayShadowBuffer(){
if (shadowDisplayInitialized) return true;
shadowDisplay.setPsram(true);
shadowDisplay.setColorDepth(16);
shadowDisplay.setTextDatum(textdatum_t::baseline_left);
if (shadowDisplay.createSprite(GxEPD_WIDTH, GxEPD_HEIGHT) == nullptr) {
shadowDisplayInitialized = false;
return false;
}
shadowDisplay.fillScreen(GxEPD_BLACK);
shadowDisplayInitialized = true;
return true;
}
bool initDisplayScaleBuffer(uint16_t width, uint16_t height){
if (scaleDisplayInitialized && scaleDisplayWidth == width && scaleDisplayHeight == height) {
return true;
}
scaleDisplay.deleteSprite();
scaleDisplay.setPsram(true);
scaleDisplay.setColorDepth(16);
scaleDisplay.setTextDatum(textdatum_t::baseline_left);
if (scaleDisplay.createSprite(width, height) == nullptr) {
scaleDisplayInitialized = false;
scaleDisplayWidth = 0;
scaleDisplayHeight = 0;
return false;
}
scaleDisplayWidth = width;
scaleDisplayHeight = height;
scaleDisplayInitialized = true;
return true;
}
#endif
// Horter I2C moduls // Horter I2C moduls
PCF8574 pcf8574_Modul1(PCF8574_I2C_ADDR1); // First digital IO modul PCF8574 from Horter PCF8574 pcf8574_Modul1(PCF8574_I2C_ADDR1); // First digital IO modul PCF8574 from Horter
@@ -86,7 +143,7 @@ void hardwareInit(GwApi *api)
GwLog *logger = api->getLogger(); GwLog *logger = api->getLogger();
GwConfigHandler *config = api->getConfig(); GwConfigHandler *config = api->getConfig();
Wire.begin(); Wire.begin(OBP_I2C_SDA, OBP_I2C_SCL);
// 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_Modul1.begin()){ // Initialize PCF8574 if(pcf8574_Modul1.begin()){ // Initialize PCF8574
@@ -175,7 +232,7 @@ void hardwareInit(GwApi *api)
void powerInit(String powermode) { void powerInit(String powermode) {
// Max Power | Only 5.0V | Min Power // Max Power | Only 5.0V | Min Power
if (powermode == "Max Power" || powermode == "Only 5.0V") { if (powermode == "Max Power" || powermode == "Only 5.0V") {
#ifdef HARDWARE_V21 #ifdef BOARD_OBP60S3
setPortPin(OBP_POWER_50, true); // Power on 5.0V rail setPortPin(OBP_POWER_50, true); // Power on 5.0V rail
#endif #endif
#ifdef BOARD_OBP40S3 #ifdef BOARD_OBP40S3
@@ -183,7 +240,7 @@ void powerInit(String powermode) {
setPortPin(OBP_POWER_SD, true); // Power on SD card setPortPin(OBP_POWER_SD, true); // Power on SD card
#endif #endif
} else { // Min Power } else { // Min Power
#ifdef HARDWARE_V21 #ifdef BOARD_OBP60S3
setPortPin(OBP_POWER_50, false); // Power off 5.0V rail setPortPin(OBP_POWER_50, false); // Power off 5.0V rail
#endif #endif
#ifdef BOARD_OBP40S3 #ifdef BOARD_OBP40S3
@@ -251,8 +308,12 @@ void deepSleep(CommonData &common){
getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(65, 175); getdisplay().setCursor(65, 175);
getdisplay().print("To wake up press key and wait 5s"); getdisplay().print("To wake up press key and wait 5s");
getdisplay().nextPage(); // Update display contents displayNextPage(); // Update display contents
#ifdef TFT_DISPLAY
getpaneldisplay().powerSave(true); // Display power save
#else
getdisplay().powerOff(); // Display power off getdisplay().powerOff(); // Display power off
#endif
setPortPin(OBP_POWER_50, false); // Power off ePaper display setPortPin(OBP_POWER_50, false); // Power off ePaper display
// Stop system // Stop system
esp_deep_sleep_start(); // Deep Sleep with weakup via touch pin esp_deep_sleep_start(); // Deep Sleep with weakup via touch pin
@@ -276,8 +337,12 @@ void deepSleep(CommonData &common){
getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(65, 175); getdisplay().setCursor(65, 175);
getdisplay().print("To wake up press wheel and wait 5s"); getdisplay().print("To wake up press wheel and wait 5s");
getdisplay().nextPage(); // Partial update displayNextPage(); // Partial update
#ifdef TFT_DISPLAY
getpaneldisplay().powerSave(true); // Display power save
#else
getdisplay().powerOff(); // Display power off getdisplay().powerOff(); // Display power off
#endif
setPortPin(OBP_POWER_EPD, false); // Power off ePaper display setPortPin(OBP_POWER_EPD, false); // Power off ePaper display
setPortPin(OBP_POWER_SD, false); // Power off SD card setPortPin(OBP_POWER_SD, false); // Power off SD card
// Stop system // Stop system
@@ -475,67 +540,193 @@ std::vector<String> wordwrap(String &line, uint16_t maxwidth) {
return lines; return lines;
} }
// Helper function to just get the exact width of a string
uint16_t getStringPixelWidth(const char* str, const GFXfont* font) {
int16_t minx = INT16_MAX;
int16_t maxx = INT16_MIN;
int16_t cursor_x = 0;
while (*str) {
char c = *str++;
if (c < font->first || c > font->last) {
continue;
}
GFXglyph* glyph = &font->glyph[c - font->first];
if (glyph->width > 0) {
int16_t glyphStart = cursor_x + glyph->xOffset;
int16_t glyphEnd = glyphStart + glyph->width;
if (glyphStart < minx) minx = glyphStart;
if (glyphEnd > maxx) maxx = glyphEnd;
}
cursor_x += glyph->xAdvance;
}
if (minx > maxx)
return 0;
return maxx - minx;
}
// Draw centered text // Draw centered text
void drawTextCenter(int16_t cx, int16_t cy, String text) { // bool dsegAdjust = false - optional, for adjustment of DSEG italic font
// returns left <x> edge of printed string
int16_t drawTextCenter(int16_t cx, int16_t cy, String text, bool dsegAdjust) {
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); displayGetTextBounds(text, 0, 0, &x1, &y1, &w, &h, dsegAdjust);
getdisplay().setCursor(cx - w / 2, cy + h / 2); int16_t cursorX = cx - (x1 + static_cast<int16_t>(w / 2));
int16_t cursorY = cy - (y1 + static_cast<int16_t>(h / 2));
getdisplay().setCursor(cursorX, cursorY);
getdisplay().print(text); getdisplay().print(text);
return cursorX;
} }
// Draw centered botton with centered text // Draw centered button 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) { 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; int16_t x1, y1;
uint16_t w, h; uint16_t w, h;
uint16_t color; displayGetTextBounds(text, 0, 0, &x1, &y1, &w, &h);
int16_t cursorX = cx - (x1 + static_cast<int16_t>(w / 2));
getdisplay().getTextBounds(text, cx, cy, &x1, &y1, &w, &h); // Find text center int16_t cursorY = cy - (y1 + static_cast<int16_t>(h / 2));
getdisplay().setCursor(cx - w/2, cy + h/2); // Set cursor to center
//getdisplay().drawPixel(cx, cy, fg); // Debug pixel for center position //getdisplay().drawPixel(cx, cy, fg); // Debug pixel for center position
if (inverted) { if (inverted) {
getdisplay().fillRoundRect(cx - sx / 2, cy - sy / 2, sx, sy, 5, fg); // Draw button getdisplay().fillRoundRect(cx - sx / 2, cy - sy / 2, sx, sy, 5, fg); // Draw button
getdisplay().setTextColor(bg); getdisplay().setTextColor(bg);
getdisplay().setCursor(cursorX, cursorY); // Set cursor to center
getdisplay().print(text); // Draw text getdisplay().print(text); // Draw text
} }
else{ else{
getdisplay().drawRoundRect(cx - sx / 2, cy - sy / 2, sx, sy, 5, fg); // Draw button getdisplay().drawRoundRect(cx - sx / 2, cy - sy / 2, sx, sy, 5, fg); // Draw button
getdisplay().setTextColor(fg); getdisplay().setTextColor(fg);
getdisplay().setCursor(cursorX, cursorY); // Set cursor to center
getdisplay().print(text); // Draw text getdisplay().print(text); // Draw text
} }
} }
// Draw right aligned text // Draw right aligned text
void drawTextRalign(int16_t x, int16_t y, String text) { // int16_t x - upper right x position for text to start printing
int16_t x1, y1; // int16_t y - upper right y position for text to start printing
uint16_t w, h; // const String& text - text to be printed
getdisplay().getTextBounds(text, 0, 150, &x1, &y1, &w, &h); // bool dsegAdjust = false - optional, for adjustment of DSEG italic font
getdisplay().setCursor(x - w - 1, y); // '-1' required since some strings wrap around w/o it // returns left <x> edge of printed string
int16_t drawTextRalign(int16_t x, int16_t y, const String& text, bool dsegAdjust) {
int16_t x1 = 0, y1 = 0;
uint16_t w = 0, h = 0;
displayGetTextBounds(text, 0, y, &x1, &y1, &w, &h, dsegAdjust);
int16_t cursorX = x - x1 - w;
getdisplay().setCursor(cursorX, y);
getdisplay().print(text); getdisplay().print(text);
return cursorX; // currently visible left edge
}
// Implementation of <printBoatValue()>; does the actual printing
static void printBoatValueImpl(const String &sBoatValue, const int16_t x, const int16_t y,
const int8_t align, const int8_t fontSize, bool smallDecs) {
const GFXfont *mainFont = nullptr;
const GFXfont *decimalFont = nullptr;
constexpr bool dsegAdjust = true;
String integerPart = "";
String decimalPart = "";
int16_t dot = 0;
uint16_t intW = 0, decW = 0;
int16_t decimalLeftX = 0;
// -----------------------------------------------------------------
// Font selection
// -----------------------------------------------------------------
if (sBoatValue.indexOf('"') != -1)
{
// LAT/LON values
if (fontSize <= 12)
mainFont = &Ubuntu_Bold12pt8b;
else if (fontSize <= 16)
mainFont = &Ubuntu_Bold16pt8b;
else if (fontSize <= 20)
mainFont = &Ubuntu_Bold20pt8b;
else
mainFont = &Ubuntu_Bold32pt8b;
smallDecs = false; // disable small decimals for LAT/LON in any case
}
else if (fontSize <= 12) {
mainFont = &DSEG7Classic_BoldItalic12pt7b;
decimalFont = &DSEG7Classic_BoldItalic10pt7b;
} else if (fontSize <= 16) {
mainFont = &DSEG7Classic_BoldItalic16pt7b;
decimalFont = &DSEG7Classic_BoldItalic12pt7b;
} else if (fontSize <= 20) {
mainFont = &DSEG7Classic_BoldItalic20pt7b;
decimalFont = &DSEG7Classic_BoldItalic16pt7b;
} else if (fontSize <= 26) {
mainFont = &DSEG7Classic_BoldItalic26pt7b;
decimalFont = &DSEG7Classic_BoldItalic20pt7b;
} else if (fontSize <= 30) {
mainFont = &DSEG7Classic_BoldItalic30pt7b;
decimalFont = &DSEG7Classic_BoldItalic26pt7b;
} else if (fontSize <= 42) {
mainFont = &DSEG7Classic_BoldItalic42pt7b;
decimalFont = &DSEG7Classic_BoldItalic30pt7b;
} else {
mainFont = &DSEG7Classic_BoldItalic60pt7b;
decimalFont = &DSEG7Classic_BoldItalic42pt7b;
}
// separate integer and decimal part of the value
dot = sBoatValue.indexOf('.');
if (dot == -1) { // no decimal point found
integerPart = sBoatValue;
decimalPart = "";
smallDecs = false; // print only integer part
} else {
integerPart = sBoatValue.substring(0, dot + 1); // keep decimal point
decimalPart = sBoatValue.substring(dot + 1);
}
// Measure integer part
int16_t ix1, iy1;
uint16_t iw, ih;
display.setFont(mainFont);
displayGetTextBounds(integerPart, 0, 0, &ix1, &iy1, &iw, &ih, dsegAdjust);
intW = ix1 + iw;
// Measure decimal part only if one actually exists
decW = 0;
if (dot != -1 && decimalPart.length() > 0) {
int16_t dx1, dy1;
uint16_t dw, dh;
display.setFont(smallDecs ? decimalFont : mainFont);
displayGetTextBounds(decimalPart, 0, 0, &dx1, &dy1, &dw, &dh, dsegAdjust);
decW = dx1 + dw;
}
const int16_t totalWidth = intW + decW;
int16_t startX;
switch (align) {
case 0: // LEFT
startX = x;
break;
case 1: // CENTER
startX = x - totalWidth / 2;
break;
case 2: // RIGHT
default:
startX = x - totalWidth;
break;
}
decimalLeftX = startX + intW;
// Draw integer part
display.setFont(mainFont);
getdisplay().setCursor(startX, y);
getdisplay().print(integerPart);
// Draw decimal part only when present
if (dot != -1 && decimalPart.length() > 0) {
display.setFont(smallDecs ? decimalFont : mainFont);
getdisplay().setCursor(decimalLeftX, y);
getdisplay().print(decimalPart);
}
}
// Print boat data value at x,y position with selectable alignment
// Version with <GwApi::BoatValue *bValue>; formats the boat data value and then prints the formatted value
// GwApi::BoatValue *bValue - boat value to print
// CommonData &commondata - <commondata> required for <formatValue()> function
// int16_t x - upper left/right x position for text to start printing
// int16_t y - upper left/right y position for text to start printing
// int8_t align - alignment [0,1,2] of the value: 0 = left, 1 = center, 2 = right
// int8_t fontSize - font size for integer part of number or for full value, if <smallDecs> is <false>/not set
// bool smallDecs - flag specifies if decimals will be printed in smaller fontsize [default = false]
void printBoatValue(GwApi::BoatValue *bValue, CommonData &commondata, const int16_t x, const int16_t y,
const int8_t align, const int8_t fontSize, const bool smallDecs)
{
String sBoatValue = formatValue(bValue, commondata).svalue;
printBoatValueImpl(sBoatValue, x, y, align, fontSize, smallDecs);
}
// Print boat data value at x,y position with selectable alignment; oerload with ready-to-print <String>; omits boat data value formatting
void printBoatValue(const String &sBoatValue, const int16_t x, const int16_t y,
const int8_t align, const int8_t fontSize, const bool smallDecs)
{
printBoatValueImpl(sBoatValue, x, y, align, fontSize, smallDecs);
} }
// Draw text inside box, normal or inverted // Draw text inside box, normal or inverted
@@ -619,7 +810,7 @@ void displayHeader(CommonData &commonData, GwApi::BoatValue *date, GwApi::BoatVa
usbRxOld = commonData.status.usbRx; usbRxOld = commonData.status.usbRx;
usbTxOld = commonData.status.usbTx; usbTxOld = commonData.status.usbTx;
#ifdef HARDWARE_V21 #ifdef BOARD_OBP60S3
// Display key lock status // Display key lock status
if (commonData.keylock) { if (commonData.keylock) {
getdisplay().drawXBitmap(170, 1, lock_bits, icon_width, icon_height, commonData.fgcolor); getdisplay().drawXBitmap(170, 1, lock_bits, icon_width, icon_height, commonData.fgcolor);
@@ -662,19 +853,20 @@ void displayHeader(CommonData &commonData, GwApi::BoatValue *date, GwApi::BoatVa
// Date and time // Date and time
String fmttype = commonData.config->getString(commonData.config->dateFormat); String fmttype = commonData.config->getString(commonData.config->dateFormat);
String timesource = commonData.config->getString(commonData.config->timeSource); String timesource = commonData.config->getString(commonData.config->timeSource);
double tz = commonData.config->getString(commonData.config->timeZone).toDouble();
getdisplay().setTextColor(commonData.fgcolor); getdisplay().setTextColor(commonData.fgcolor);
getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(230, 15); getdisplay().setCursor(230, 15);
if (timesource == "RTC" or timesource == "iRTC") { if (timesource == "RTC" or timesource == "iRTC") {
// TODO take DST into account // TODO take DST into account
if (commonData.data.rtcValid) { if (commonData.data.rtcValid) {
time_t tv = mktime(&commonData.data.rtcTime) + (int)(commonData.tz * 3600); time_t tv = mktime(&commonData.data.rtcTime) + (int)(tz * 3600);
struct tm *local_tm = localtime(&tv); struct tm *local_tm = localtime(&tv);
getdisplay().print(formatTime('m', local_tm->tm_hour, local_tm->tm_min, 0)); getdisplay().print(formatTime('m', local_tm->tm_hour, local_tm->tm_min, 0));
getdisplay().print(" "); getdisplay().print(" ");
getdisplay().print(formatDate(fmttype, local_tm->tm_year + 1900, local_tm->tm_mon + 1, local_tm->tm_mday)); getdisplay().print(formatDate(fmttype, local_tm->tm_year + 1900, local_tm->tm_mon + 1, local_tm->tm_mday));
getdisplay().print(" "); getdisplay().print(" ");
getdisplay().print(commonData.tz == 0 ? "UTC" : "LOT"); getdisplay().print(tz == 0 ? "UTC" : "LOT");
} else { } else {
drawTextRalign(396, 15, "RTC invalid"); drawTextRalign(396, 15, "RTC invalid");
} }
@@ -689,7 +881,7 @@ void displayHeader(CommonData &commonData, GwApi::BoatValue *date, GwApi::BoatVa
getdisplay().print(" "); getdisplay().print(" ");
getdisplay().print(actdate); getdisplay().print(actdate);
getdisplay().print(" "); getdisplay().print(" ");
getdisplay().print(commonData.tz == 0 ? "UTC" : "LOT"); getdisplay().print(tz == 0 ? "UTC" : "LOT");
} }
else{ else{
if(commonData.config->getBool(commonData.config->useSimuData) == true){ if(commonData.config->getBool(commonData.config->useSimuData) == true){
@@ -711,7 +903,7 @@ void displayFooter(CommonData &commonData) {
getdisplay().setFont(&Atari16px); getdisplay().setFont(&Atari16px);
getdisplay().setTextColor(commonData.fgcolor); getdisplay().setTextColor(commonData.fgcolor);
#ifdef HARDWARE_V21 #ifdef BOARD_OBP60S3
// Frame around key icon area // Frame around key icon area
if (! commonData.keylock) { if (! commonData.keylock) {
// horizontal elements // horizontal elements
@@ -946,7 +1138,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
@@ -963,6 +1155,81 @@ 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;
#ifdef TFT_DISPLAY
// LovyanGFX: compute width/height manually
bw = getdisplay().textWidth(lbl);
bh = getdisplay().fontHeight();
bx = 0; by = 0;
#else
getdisplay().getTextBounds(lbl, 0, 0, &bx, &by, &bw, &bh);
#endif
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) {
@@ -974,9 +1241,16 @@ void doImageRequest(GwApi *api, int *pageno, const PageStruct pages[MAX_PAGE_NUM
logger->logDebug(GwLog::LOG,"handle image request [%s]: %s", imgformat, filename); logger->logDebug(GwLog::LOG,"handle image request [%s]: %s", imgformat, filename);
uint8_t *fb = getdisplay().getBuffer(); // EPD framebuffer uint8_t *fb = nullptr; // EPD framebuffer
std::vector<uint8_t> imageBuffer; // image in webserver transferbuffer std::vector<uint8_t> imageBuffer; // image in webserver transferbuffer
String mimetype; String mimetype;
#ifndef TFT_DISPLAY
fb = getdisplay().getBuffer(); // available only for EPD
#endif
if (!fb) {
request->send(500, "text/plain", "screenshot not available");
return;
}
if (imgformat == "gif") { if (imgformat == "gif") {
// GIF is commpressed with LZW, so small // GIF is commpressed with LZW, so small
+644 -6
View File
@@ -5,14 +5,28 @@
#include "OBP60Hardware.h" #include "OBP60Hardware.h"
#include "LedSpiTask.h" #include "LedSpiTask.h"
#include "Graphics.h" #include "Graphics.h"
#include <GxEPD2_BW.h> // E-paper lib V2 #include <GxEPD2_BW.h> // GxEPD2 lib for b/w E-Ink displays
#include <Adafruit_FRAM_I2C.h> // I2C FRAM #include <Adafruit_FRAM_I2C.h> // I2C FRAM
#include <math.h> #include <math.h>
#ifdef TFT_DISPLAY
#if !defined(TFT_320x480_ST7796) && !defined(TFT_320x480_ILI9488)
#error "TFT_DISPLAY requires one panel type: TFT_320x480_ST7796 or TFT_320x480_ILI9488"
#endif
#if defined(TFT_320x480_ST7796) && defined(TFT_320x480_ILI9488)
#error "Select exactly one TFT panel type: TFT_320x480_ST7796 or TFT_320x480_ILI9488"
#endif
#include <LovyanGFX.hpp> // TFT LCD lib for 320x480 color displays
#undef GxEPD_WHITE
#define GxEPD_WHITE TFT_WHITE // Replacement color for white on TFT (OBPHardware.h)
#undef GxEPD_BLACK
#define GxEPD_BLACK TFT_BLACK // Replacement color for black on TFT (OBPHardware.h)
#endif
#ifdef BOARD_OBP40S3 #ifdef BOARD_OBP40S3
#include "esp_vfs_fat.h" #include "esp_vfs_fat.h"
#include "sdmmc_cmd.h" #include "sdmmc_cmd.h"
#define MOUNT_POINT "/sdcard" #define MOUNT_POINT "/sdcard"
#endif #endif
// FRAM address reservations 32kB: 0x0000 - 0x7FFF // FRAM address reservations 32kB: 0x0000 - 0x7FFF
@@ -42,6 +56,8 @@ extern sdmmc_card_t *sdcard;
#endif #endif
// Fonts declarations for display (#includes see OBP60Extensions.cpp) // Fonts declarations for display (#includes see OBP60Extensions.cpp)
extern const GFXfont DSEG7Classic_BoldItalic10pt7b;
extern const GFXfont DSEG7Classic_BoldItalic12pt7b;
extern const GFXfont DSEG7Classic_BoldItalic16pt7b; extern const GFXfont DSEG7Classic_BoldItalic16pt7b;
extern const GFXfont DSEG7Classic_BoldItalic20pt7b; extern const GFXfont DSEG7Classic_BoldItalic20pt7b;
extern const GFXfont DSEG7Classic_BoldItalic26pt7b; extern const GFXfont DSEG7Classic_BoldItalic26pt7b;
@@ -74,11 +90,627 @@ GxEPD2_BW<GxEPD2_420_GYE042A87, GxEPD2_420_GYE042A87::HEIGHT> & getdisplay();
GxEPD2_BW<GxEPD2_420_SE0420NQ04, GxEPD2_420_SE0420NQ04::HEIGHT> & getdisplay(); GxEPD2_BW<GxEPD2_420_SE0420NQ04, GxEPD2_420_SE0420NQ04::HEIGHT> & getdisplay();
#endif #endif
#ifdef TFT_DISPLAY
// LovyanGFX based display wrapper for TFT panels
class LGFX : public lgfx::LGFX_Device {
public:
lgfx::Bus_SPI _bus_instance;
#ifdef TFT_320x480_ST7796
LGFX(void) {
{
auto cfg = _bus_instance.config();
cfg.spi_host = SPI2_HOST;
cfg.spi_mode = 0;
cfg.freq_write = 80000000; // High speed ST7796
cfg.freq_read = 16000000;
cfg.pin_sclk = OBP_SPI_CLK;
cfg.pin_mosi = OBP_SPI_DIN;
cfg.pin_miso = -1;
cfg.pin_dc = OBP_SPI_DC;
_bus_instance.config(cfg);
_panel_instance.setBus(&_bus_instance);
}
{
auto cfg = _panel_instance.config();
cfg.pin_cs = OBP_SPI_CS;
cfg.pin_rst = OBP_SPI_RST;
cfg.pin_busy = -1;
cfg.panel_width = 320; // Native width resolution
cfg.panel_height = 480; // Native hight resolution
cfg.offset_x = 0; // No panel offset: full framebuffer mapping
cfg.offset_y = 0; // No panel offset: full framebuffer mapping
cfg.offset_rotation = 3; // Rotate display content conter clock wise 90 deg ST7796
cfg.dummy_read_pixel = 8;
cfg.dummy_read_bits = 1;
cfg.memory_width = 320;
cfg.memory_height = 480;
// cfg.pwm_control not available in this LovyanGFX version
cfg.invert = false;
cfg.rgb_order = false;
cfg.dlen_16bit = false;
cfg.bus_shared = true;
_panel_instance.config(cfg);
}
// No dedicated TFT PWM backlight pin configured on this board.
// Keep backlight handling outside LovyanGFX to avoid LEDC init on invalid GPIO.
setPanel(&_panel_instance);
// Match Adafruit GFX cursor semantics: y coordinate is text baseline.
setTextDatum(textdatum_t::baseline_left);
}
#endif
#ifdef TFT_320x480_ILI9488
LGFX(void) {
{
auto cfg = _bus_instance.config();
cfg.spi_host = SPI2_HOST;
cfg.spi_mode = 0;
cfg.freq_write = 40000000; // Slow speed ILI9488
cfg.freq_read = 16000000;
cfg.pin_sclk = OBP_SPI_CLK;
cfg.pin_mosi = OBP_SPI_DIN;
cfg.pin_miso = -1;
cfg.pin_dc = OBP_SPI_DC;
_bus_instance.config(cfg);
_panel_instance.setBus(&_bus_instance);
}
{
auto cfg = _panel_instance.config();
cfg.pin_cs = OBP_SPI_CS;
cfg.pin_rst = OBP_SPI_RST;
cfg.pin_busy = -1;
cfg.panel_width = 320; // Native width resolution
cfg.panel_height = 480; // Native hight resolution
cfg.offset_x = 0; // No panel offset: full framebuffer mapping
cfg.offset_y = 0; // No panel offset: full framebuffer mapping
cfg.offset_rotation = 1; // Rotate display content clock wise 90 deg ILI9488
cfg.dummy_read_pixel = 8;
cfg.dummy_read_bits = 1;
cfg.memory_width = 320;
cfg.memory_height = 480;
// cfg.pwm_control not available in this LovyanGFX version
cfg.invert = false;
cfg.rgb_order = false;
cfg.dlen_16bit = false;
cfg.bus_shared = true;
_panel_instance.config(cfg);
}
// No dedicated TFT PWM backlight pin configured on this board.
// Keep backlight handling outside LovyanGFX to avoid LEDC init on invalid GPIO.
setPanel(&_panel_instance);
// Match Adafruit GFX cursor semantics: y coordinate is text baseline.
setTextDatum(textdatum_t::baseline_left);
}
#endif
// compatibility helpers --------------------------------------------------
using lgfx::LGFX_Device::setFont;
void setFont(const lgfx::IFont* font) {
_currentAdfFont = nullptr;
lgfx::LGFX_Device::setFont(font);
}
// Adafruit GFX fonts support on TFT via LovyanGFX bridge
void setFont(const GFXfont *font) {
if (font == nullptr) {
_currentAdfFont = nullptr;
lgfx::LGFX_Device::setFont(nullptr);
return;
}
if (font->glyph == nullptr || font->bitmap == nullptr || font->last < font->first) {
_currentAdfFont = nullptr;
lgfx::LGFX_Device::setFont(nullptr);
return;
}
const uint16_t glyphCount = static_cast<uint16_t>(font->last - font->first + 1);
if (glyphCount == 0) {
lgfx::LGFX_Device::setFont(nullptr);
return;
}
if (_adfGlyphCount != glyphCount || _adfGlyphBridge == nullptr) {
if (_adfGlyphBridge != nullptr) {
free(_adfGlyphBridge);
_adfGlyphBridge = nullptr;
_adfGlyphCount = 0;
}
_adfGlyphBridge = static_cast<lgfx::GFXglyph*>(malloc(sizeof(lgfx::GFXglyph) * glyphCount));
if (_adfGlyphBridge == nullptr) {
lgfx::LGFX_Device::setFont(nullptr);
return;
}
_adfGlyphCount = glyphCount;
}
for (uint16_t index = 0; index < glyphCount; ++index) {
_adfGlyphBridge[index].bitmapOffset = font->glyph[index].bitmapOffset;
_adfGlyphBridge[index].width = font->glyph[index].width;
_adfGlyphBridge[index].height = font->glyph[index].height;
_adfGlyphBridge[index].xAdvance = font->glyph[index].xAdvance;
_adfGlyphBridge[index].xOffset = font->glyph[index].xOffset;
_adfGlyphBridge[index].yOffset = font->glyph[index].yOffset;
}
_adfFontBridge = lgfx::GFXfont(
const_cast<uint8_t*>(font->bitmap),
_adfGlyphBridge,
font->first,
font->last,
font->yAdvance
);
_currentAdfFont = font;
lgfx::LGFX_Device::setFont(&_adfFontBridge);
}
void getTextBounds(const String &txt, int16_t x, int16_t y,
int16_t *x0, int16_t *y0,
uint16_t *w, uint16_t *h) {
if (w == nullptr || h == nullptr) {
return;
}
if (_currentAdfFont == nullptr || txt.length() == 0) {
*w = textWidth(txt);
*h = fontHeight();
if (x0) *x0 = x;
if (y0) *y0 = y - static_cast<int16_t>(*h);
return;
}
const float sx = getTextSizeX();
const float sy = getTextSizeY();
int32_t cursorX = x;
int32_t cursorY = y;
int32_t minX = 0;
int32_t minY = 0;
int32_t maxX = 0;
int32_t maxY = 0;
bool hasPixel = false;
for (size_t i = 0; i < txt.length(); ++i) {
char c = txt[i];
if (c == '\r') continue;
if (c == '\n') {
cursorX = x;
cursorY += static_cast<int32_t>(_currentAdfFont->yAdvance * sy);
continue;
}
if (c < _currentAdfFont->first || c > _currentAdfFont->last) {
continue;
}
const GFXglyph* glyph = &_currentAdfFont->glyph[static_cast<uint16_t>(c) - _currentAdfFont->first];
const int32_t gw = static_cast<int32_t>(glyph->width * sx);
const int32_t gh = static_cast<int32_t>(glyph->height * sy);
const int32_t gx1 = cursorX + static_cast<int32_t>(glyph->xOffset * sx);
const int32_t gy1 = cursorY + static_cast<int32_t>(glyph->yOffset * sy);
if (gw > 0 && gh > 0) {
const int32_t gx2 = gx1 + gw - 1;
const int32_t gy2 = gy1 + gh - 1;
if (!hasPixel) {
minX = gx1; minY = gy1; maxX = gx2; maxY = gy2;
hasPixel = true;
} else {
if (gx1 < minX) minX = gx1;
if (gy1 < minY) minY = gy1;
if (gx2 > maxX) maxX = gx2;
if (gy2 > maxY) maxY = gy2;
}
}
cursorX += static_cast<int32_t>(glyph->xAdvance * sx);
}
if (hasPixel) {
if (x0) *x0 = static_cast<int16_t>(minX);
if (y0) *y0 = static_cast<int16_t>(minY);
*w = static_cast<uint16_t>(maxX - minX + 1);
*h = static_cast<uint16_t>(maxY - minY + 1);
} else {
if (x0) *x0 = x;
if (y0) *y0 = y;
*w = 0;
*h = 0;
}
}
// E-Ink interface compatibility
void setFullWindow() { /* no-op on TFT */ }
// Runtime panel offset control for TFT panels
void setPanelOffset(int16_t x, int16_t y) {
auto cfg = _panel_instance.config();
cfg.offset_x = x;
cfg.offset_y = y;
_panel_instance.config(cfg);
}
void getPanelOffset(int16_t &x, int16_t &y) {
auto cfg = _panel_instance.config();
x = cfg.offset_x;
y = cfg.offset_y;
}
private:
lgfx::GFXfont _adfFontBridge { nullptr, nullptr, 0, 0, 0 };
lgfx::GFXglyph* _adfGlyphBridge = nullptr;
uint16_t _adfGlyphCount = 0;
const GFXfont* _currentAdfFont = nullptr;
#if defined(TFT_320x480_ST7796)
lgfx::Panel_ST7796 _panel_instance;
#elif defined(TFT_320x480_ILI9488)
lgfx::Panel_ILI9488 _panel_instance;
#endif
};
class LGFXCanvas : public lgfx::LGFX_Sprite {
public:
explicit LGFXCanvas(lgfx::LGFX_Device* parent = nullptr) : lgfx::LGFX_Sprite(parent) {}
using lgfx::LGFX_Sprite::setFont;
void setFont(const GFXfont *font) {
if (font == nullptr) {
_currentAdfFont = nullptr;
lgfx::LGFX_Sprite::setFont(nullptr);
return;
}
if (font->glyph == nullptr || font->bitmap == nullptr || font->last < font->first) {
_currentAdfFont = nullptr;
lgfx::LGFX_Sprite::setFont(nullptr);
return;
}
const uint16_t glyphCount = static_cast<uint16_t>(font->last - font->first + 1);
if (glyphCount == 0) {
lgfx::LGFX_Sprite::setFont(nullptr);
return;
}
if (_adfGlyphCount != glyphCount || _adfGlyphBridge == nullptr) {
if (_adfGlyphBridge != nullptr) {
free(_adfGlyphBridge);
_adfGlyphBridge = nullptr;
_adfGlyphCount = 0;
}
_adfGlyphBridge = static_cast<lgfx::GFXglyph*>(malloc(sizeof(lgfx::GFXglyph) * glyphCount));
if (_adfGlyphBridge == nullptr) {
lgfx::LGFX_Sprite::setFont(nullptr);
return;
}
_adfGlyphCount = glyphCount;
}
for (uint16_t index = 0; index < glyphCount; ++index) {
_adfGlyphBridge[index].bitmapOffset = font->glyph[index].bitmapOffset;
_adfGlyphBridge[index].width = font->glyph[index].width;
_adfGlyphBridge[index].height = font->glyph[index].height;
_adfGlyphBridge[index].xAdvance = font->glyph[index].xAdvance;
_adfGlyphBridge[index].xOffset = font->glyph[index].xOffset;
_adfGlyphBridge[index].yOffset = font->glyph[index].yOffset;
}
_adfFontBridge = lgfx::GFXfont(
const_cast<uint8_t*>(font->bitmap),
_adfGlyphBridge,
font->first,
font->last,
font->yAdvance
);
_currentAdfFont = font;
lgfx::LGFX_Sprite::setFont(&_adfFontBridge);
}
void getTextBounds(const String &txt, int16_t x, int16_t y,
int16_t *x0, int16_t *y0,
uint16_t *w, uint16_t *h) {
if (w == nullptr || h == nullptr) {
return;
}
if (_currentAdfFont == nullptr || txt.length() == 0) {
*w = textWidth(txt);
*h = fontHeight();
if (x0) *x0 = x;
if (y0) *y0 = y - static_cast<int16_t>(*h);
return;
}
const float sx = getTextSizeX();
const float sy = getTextSizeY();
int32_t cursorX = x;
int32_t cursorY = y;
int32_t minX = 0;
int32_t minY = 0;
int32_t maxX = 0;
int32_t maxY = 0;
bool hasPixel = false;
for (size_t i = 0; i < txt.length(); ++i) {
char c = txt[i];
if (c == '\r') continue;
if (c == '\n') {
cursorX = x;
cursorY += static_cast<int32_t>(_currentAdfFont->yAdvance * sy);
continue;
}
if (c < _currentAdfFont->first || c > _currentAdfFont->last) {
continue;
}
const GFXglyph* glyph = &_currentAdfFont->glyph[static_cast<uint16_t>(c) - _currentAdfFont->first];
const int32_t gw = static_cast<int32_t>(glyph->width * sx);
const int32_t gh = static_cast<int32_t>(glyph->height * sy);
const int32_t gx1 = cursorX + static_cast<int32_t>(glyph->xOffset * sx);
const int32_t gy1 = cursorY + static_cast<int32_t>(glyph->yOffset * sy);
if (gw > 0 && gh > 0) {
const int32_t gx2 = gx1 + gw - 1;
const int32_t gy2 = gy1 + gh - 1;
if (!hasPixel) {
minX = gx1; minY = gy1; maxX = gx2; maxY = gy2;
hasPixel = true;
} else {
if (gx1 < minX) minX = gx1;
if (gy1 < minY) minY = gy1;
if (gx2 > maxX) maxX = gx2;
if (gy2 > maxY) maxY = gy2;
}
}
cursorX += static_cast<int32_t>(glyph->xAdvance * sx);
}
if (hasPixel) {
if (x0) *x0 = static_cast<int16_t>(minX);
if (y0) *y0 = static_cast<int16_t>(minY);
*w = static_cast<uint16_t>(maxX - minX + 1);
*h = static_cast<uint16_t>(maxY - minY + 1);
} else {
if (x0) *x0 = x;
if (y0) *y0 = y;
*w = 0;
*h = 0;
}
}
void setFullWindow() { /* no-op on TFT */ }
private:
lgfx::GFXfont _adfFontBridge { nullptr, nullptr, 0, 0, 0 };
lgfx::GFXglyph* _adfGlyphBridge = nullptr;
uint16_t _adfGlyphCount = 0;
const GFXfont* _currentAdfFont = nullptr;
};
LGFXCanvas & getdisplay();
LGFX & getpaneldisplay();
LGFXCanvas & getscaleddisplay();
bool initDisplayShadowBuffer();
bool initDisplayScaleBuffer(uint16_t width, uint16_t height);
#endif
// Page display return values // Page display return values
#define PAGE_OK 0 // all ok, do nothing #define PAGE_OK 0 // all ok, do nothing
#define PAGE_UPDATE 1 // page wants display to update #define PAGE_UPDATE 1 // page wants display to update
#define PAGE_HIBERNATE 2 // page wants displey to hibernate #define PAGE_HIBERNATE 2 // page wants displey to hibernate
#ifdef TFT_DISPLAY
#ifndef OBP_TFT_ENABLE_SCALING
#define OBP_TFT_ENABLE_SCALING 1
#endif
#ifndef OBP_TFT_SCALE_ANTIALIAS
#define OBP_TFT_SCALE_ANTIALIAS 1
#endif
#if OBP_TFT_SCALE_ANTIALIAS
inline uint16_t lerpRgb565(uint16_t c0, uint16_t c1, uint16_t w8) {
const uint16_t r0 = (c0 >> 11) & 0x1F;
const uint16_t g0 = (c0 >> 5) & 0x3F;
const uint16_t b0 = c0 & 0x1F;
const uint16_t r1 = (c1 >> 11) & 0x1F;
const uint16_t g1 = (c1 >> 5) & 0x3F;
const uint16_t b1 = c1 & 0x1F;
const uint16_t r = static_cast<uint16_t>(r0 + ((static_cast<int32_t>(r1) - r0) * w8 + 128) / 256);
const uint16_t g = static_cast<uint16_t>(g0 + ((static_cast<int32_t>(g1) - g0) * w8 + 128) / 256);
const uint16_t b = static_cast<uint16_t>(b0 + ((static_cast<int32_t>(b1) - b0) * w8 + 128) / 256);
return static_cast<uint16_t>((r << 11) | (g << 5) | b);
}
inline uint16_t sampleBilinearRgb565(LGFXCanvas& src, uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t wx, uint16_t wy) {
const uint16_t c00 = src.readPixel(x0, y0);
const uint16_t c10 = src.readPixel(x1, y0);
const uint16_t c01 = src.readPixel(x0, y1);
const uint16_t c11 = src.readPixel(x1, y1);
const uint16_t top = lerpRgb565(c00, c10, wx);
const uint16_t bot = lerpRgb565(c01, c11, wx);
return lerpRgb565(top, bot, wy);
}
#endif
#endif
// Draw monochrome bitmap on both E-Ink and TFT displays
// supports various packing and bit orders; optional runtime conversion for TFT
inline void drawMonochromeBitmap(
int16_t x, int16_t y,
const uint8_t *bmp,
int16_t w, int16_t h,
uint16_t color,
bool vertical=false, // true: bytes run vertically (each byte 8 pixels down)
bool lsbFirst=false, // true: least significant bit = left/top pixel
bool mirrorX=false) // true: bytes run right-to-left within each row
{
#ifdef TFT_DISPLAY
// TFT converts per‑pixel
int bytesPerRow = (w + 7) / 8;
for (int yy = 0; yy < h; yy++) {
for (int xx = 0; xx < w; xx++) {
int byteIdx;
int bitIdx;
if (vertical) {
// vertical packing: column-major bytes
int col = mirrorX ? (w - 1 - xx) : xx;
byteIdx = col * ((h + 7) / 8) + (yy / 8);
bitIdx = yy % 8;
} else {
// horizontal packing: row-major bytes
int col = mirrorX ? (w - 1 - xx) : xx;
byteIdx = yy * bytesPerRow + (col / 8);
bitIdx = col % 8;
}
uint8_t b = bmp[byteIdx];
bool pix;
if (lsbFirst) {
pix = b & (1 << bitIdx);
} else {
pix = b & (1 << (7 - bitIdx));
}
if (pix) {
getdisplay().drawPixel(x + xx, y + yy, color);
}
}
if ((yy & 0x0F) == 0) {
yield();
}
}
#else
// E‑Paper: just hand over to driver (expects MSB‑first horizontal)
getdisplay().drawBitmap(x, y, bmp, w, h, color);
#endif
}
// Display wrapper functions for E-Ink/TFT compatibility
// generic bitmap draw that accepts 1‑bit data; TFT version
// forwards to drawMonochromeBitmap whereas EPD uses native drawBitmap
inline void displayDrawBitmap(int16_t x, int16_t y,
const uint8_t *bmp,
int16_t w, int16_t h,
uint16_t color) {
#ifdef TFT_DISPLAY
drawMonochromeBitmap(x, y, bmp, w, h, color);
#else
getdisplay().drawBitmap(x, y, bmp, w, h, color);
#endif
}
inline void displayFirstPage() {
#ifdef TFT_DISPLAY
initDisplayShadowBuffer();
#else
getdisplay().firstPage();
#endif
}
inline void displayNextPage() {
#ifdef TFT_DISPLAY
if (initDisplayShadowBuffer()) {
LGFXCanvas &src = getdisplay();
LGFX &dst = getpaneldisplay();
const uint16_t srcW = GxEPD_WIDTH;
const uint16_t srcH = GxEPD_HEIGHT;
const uint16_t dstW = static_cast<uint16_t>(dst.width());
const uint16_t dstH = static_cast<uint16_t>(dst.height());
#if !OBP_TFT_ENABLE_SCALING
const uint16_t drawX = static_cast<uint16_t>((dstW > srcW) ? ((dstW - srcW) / 2U) : 0U);
const uint16_t drawY = static_cast<uint16_t>((dstH > srcH) ? ((dstH - srcH) / 2U) : 0U);
src.pushSprite(drawX, drawY);
#else
const uint16_t targetH = (dstH < 320U) ? dstH : 320U;
const uint32_t scaledW32 = (static_cast<uint32_t>(srcW) * targetH + (srcH / 2U)) / srcH;
const uint16_t targetW = static_cast<uint16_t>((scaledW32 < dstW) ? scaledW32 : dstW);
const uint16_t drawX = static_cast<uint16_t>((dstW - targetW) / 2U);
const uint16_t drawY = static_cast<uint16_t>((dstH - targetH) / 2U);
const uint16_t borderColor = src.readPixel(0, 0);
if (initDisplayScaleBuffer(dstW, dstH)) {
LGFXCanvas &scaled = getscaleddisplay();
scaled.fillScreen(borderColor);
for (uint16_t y = 0; y < targetH; ++y) {
const uint32_t syfp = (targetH > 1)
? (static_cast<uint32_t>(y) * (srcH - 1) * 256U) / (targetH - 1)
: 0;
const uint16_t sy0 = static_cast<uint16_t>(syfp >> 8);
const uint16_t sy1 = (sy0 + 1 < srcH) ? static_cast<uint16_t>(sy0 + 1) : sy0;
const uint16_t wy = static_cast<uint16_t>(syfp & 0xFFU);
for (uint16_t x = 0; x < targetW; ++x) {
const uint32_t sxfp = (targetW > 1)
? (static_cast<uint32_t>(x) * (srcW - 1) * 256U) / (targetW - 1)
: 0;
const uint16_t sx0 = static_cast<uint16_t>(sxfp >> 8);
const uint16_t sx1 = (sx0 + 1 < srcW) ? static_cast<uint16_t>(sx0 + 1) : sx0;
#if OBP_TFT_SCALE_ANTIALIAS
const uint16_t wx = static_cast<uint16_t>(sxfp & 0xFFU);
const uint16_t color = sampleBilinearRgb565(src, sx0, sy0, sx1, sy1, wx, wy);
#else
const uint16_t color = src.readPixel(sx0, sy0);
#endif
scaled.drawPixel(drawX + x, drawY + y, color);
}
if ((y & 0x0F) == 0) {
yield();
}
}
scaled.pushSprite(0, 0);
} else {
src.pushSprite(drawX, drawY);
}
#endif
}
#else
getdisplay().nextPage();
#endif
}
inline void displaySetPartialWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h) {
#ifdef TFT_DISPLAY
// TFT LCD doesn't use partial windows
(void)x; (void)y; (void)w; (void)h;
#else
getdisplay().setPartialWindow(x, y, w, h);
#endif
}
inline void displaySetFullWindow() {
#ifdef TFT_DISPLAY
// TFT LCD doesn't need setFullWindow()
#else
getdisplay().setFullWindow();
#endif
}
// replacement for getTextBounds that works with both EPD and TFT
inline void displayGetTextBounds(const String &txt, int16_t x, int16_t y,
int16_t *x0, int16_t *y0,
uint16_t *w, uint16_t *h, const bool dsegAdjust = false) {
String str = txt;
// make sure that we always have a char with max size at last position for boundary test
// for monotype italic DSEG7 font to avoid text wobbling
if (dsegAdjust && str.length() > 0 && isDigit(str[str.length() - 1])) {
str.setCharAt(str.length() - 1, '8');
}
#ifdef TFT_DISPLAY
getdisplay().getTextBounds(str, x, y, x0, y0, w, h);
#else
getdisplay().getTextBounds(str, x, y, x0, y0, w, h);
#endif
}
void fillPoly4(const std::vector<Point>& p4, uint16_t color); void fillPoly4(const std::vector<Point>& p4, uint16_t color);
void drawPoly(const std::vector<Point>& points, uint16_t color); void drawPoly(const std::vector<Point>& points, uint16_t color);
@@ -108,10 +740,12 @@ 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); int16_t drawTextCenter(int16_t cx, int16_t cy, String text, bool dsegAdjust = false);
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 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); int16_t drawTextRalign(int16_t x, int16_t y, const String& text, bool dsegAdjust = false);
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);
void printBoatValue(GwApi::BoatValue *bValue, CommonData &commondata, const int16_t x, const int16_t y, const int8_t align, const int8_t fontSize, const bool smallDecs = false);
void printBoatValue(const String &sBoatValue, const int16_t x, const int16_t y, const int8_t align, const int8_t fontSize, const bool smallDecs = false);
void displayTrendHigh(int16_t x, int16_t y, uint16_t size, uint16_t color); void displayTrendHigh(int16_t x, int16_t y, uint16_t size, uint16_t color);
void displayTrendLow(int16_t x, int16_t y, uint16_t size, uint16_t color); void displayTrendLow(int16_t x, int16_t y, uint16_t size, uint16_t color);
@@ -128,6 +762,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
+68 -30
View File
@@ -3,6 +3,7 @@
#include <Arduino.h> #include <Arduino.h>
#include "GwApi.h" #include "GwApi.h"
#include "Pagedata.h" #include "Pagedata.h"
#include "OBPDataOperations.h"
// ToDo // ToDo
// simulation data // simulation data
@@ -49,15 +50,10 @@ 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");
} }
// 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 // 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 // generates random simulation data; can be deselected to use conversion+formatting function even in simulation mode
FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool ignoreSimuDataSetting){ // String setPrecision - overrides precision setting from config; if not set, use config setting
FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool ignoreSimuDataSetting, String setPrecision) {
GwLog *logger = commondata.logger; GwLog *logger = commondata.logger;
FormattedData result; FormattedData result;
static int dayoffset = 0; static int dayoffset = 0;
@@ -75,12 +71,13 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
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]
String precision = commondata.config->getString(commondata.config->valueprecision); // [1|2] String precision = commondata.config->getString(commondata.config->valueprecision); // [1|2]
bool usesimudata = commondata.config->getBool(commondata.config->useSimuData); // [on|off]
bool usesimudata; if (setPrecision == "1" || setPrecision == "2") {
precision = setPrecision; // override precision setting from config
}
if (ignoreSimuDataSetting){ if (ignoreSimuDataSetting){
usesimudata = false; // ignore user setting for simulation data; we want to format the boat value passed to this function 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
@@ -95,21 +92,20 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
double limit_dec_10; double limit_dec_10;
double limit_dec_100; 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. //For a space that is as long as a number, '!' is used. For details see https://www.keshikan.net/fonts-e.html
//For a space that is as long as a number, '!' is used. For details see https://www.keshikan.net/fonts-e.html
// fmt_dec_1 = "!%1.1f"; // blank digit + 2 digit number, incl. 1 decimal
fmt_dec_1 = "!%1.1f"; //insert a blank digit and then display a two-digit number fmt_dec_10 = "%3.1f"; // 3 digit number, incl. 1 decimal
fmt_dec_10 = "!%2.0f"; //insert a blank digit and then display a two-digit number fmt_dec_100 = "%3.0f"; // 3 digit number, no decimal
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_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;
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_10=9.995;
limit_dec_100=99.95; 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);
@@ -216,10 +212,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
rawvalue = value->value; rawvalue = value->value;
} }
else { else {
course = M_PI_2 + float(random(-17, 17) / 100.0); // create random course/wind values with 90° +/- 10° course = M_PI_2 + float(random(-26, 26) / 100.0); // create random course/wind values with 90° +/- 15°
rawvalue = course; rawvalue = course;
} }
course = course * RAD_TO_DEG; // Unit conversion form rad to deg course = WindUtils::to360(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);
@@ -227,7 +223,7 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
result.cvalue = course; result.cvalue = course;
} }
//######################################################## //########################################################
else if (value->getFormat() == "formatKnots" && (value->getName() == "SOG" || value->getName() == "STW")){ else if (value->getFormat() == "formatKnots" && (value->getName() == "SOG" || value->getName() == "STW") || value->getName() == "DFT"){
double speed = 0; double speed = 0;
if (usesimudata == false) { if (usesimudata == false) {
speed = value->value; speed = value->value;
@@ -519,11 +515,11 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
} }
if (String(tempFormat) == "C") { if (String(tempFormat) == "C") {
temp = temp - 273.15; temp = temp - 273.15;
result.unit = "C"; result.unit = "Deg C";
} }
else if (String(tempFormat) == "F") { else if (String(tempFormat) == "F") {
temp = (temp - 273.15) * 9 / 5 + 32; temp = (temp - 273.15) * 9 / 5 + 32;
result.unit = "F"; result.unit = "Deg F";
} }
else{ else{
result.unit = "K"; result.unit = "K";
@@ -587,7 +583,11 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
rawvalue = 968 + float(random(0, 10)); rawvalue = 968 + float(random(0, 10));
pressure = rawvalue; pressure = rawvalue;
} }
snprintf(buffer, bsize, "%4.0f", pressure); if (pressure < 999.5) {
snprintf(buffer, bsize, "!%3.0f", pressure);
} else {
snprintf(buffer, bsize, "%4.0f", pressure);
}
result.unit = "hPa"; result.unit = "hPa";
result.cvalue = pressure; result.cvalue = pressure;
} }
@@ -603,7 +603,11 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
rawvalue = value->value; rawvalue = value->value;
pressure = 968 + float(random(0, 10)); pressure = 968 + float(random(0, 10));
} }
snprintf(buffer, bsize, "%4.0f", pressure); if (pressure < 999.5) {
snprintf(buffer, bsize, "!%3.0f", pressure);
} else {
snprintf(buffer, bsize, "%4.0f", pressure);
}
result.unit = "mBar"; result.unit = "mBar";
result.cvalue = pressure; result.cvalue = pressure;
} }
@@ -835,7 +839,7 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
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;
@@ -900,6 +904,40 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
return result; return result;
} }
// Convert and format boat value from SI to user defined format
FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata) {
return formatValue(value, commondata, false, String("-1")); // call <formatValue> with standard setting for simulation data and precision setting from config
}
// Convert and format boat value from SI to user defined format with simulation data setting and precision setting from config
FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool ignoreSimuDataSetting) {
return formatValue(value, commondata, ignoreSimuDataSetting, String("-1"));
}
// Convert and format boat value from SI to user defined format with standard setting for simulation data and specified precision
FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, String setPrecision) {
return formatValue(value, commondata, false, setPrecision);
}
// Format double value from SI to user defined format and convert to string user defined precision setting
String formatValue(const double &value, const String &vFormat, CommonData &commondata)
{
GwApi::BoatValue tmpBVal("dummy"); // temporary boat value for string formatter
String sVal;
tmpBVal.setFormat(vFormat);
tmpBVal.valid = true;
tmpBVal.value = value;
sVal = formatValue(&tmpBVal, commondata, false, String("-1")).svalue; // Formatted value as string including unit conversion and switching decimal places
if (sVal.length() > 0 && sVal[0] == '!') {
sVal = sVal.substring(1); // cut leading "!" created at OBPFormatter; doesn't work for other fonts than 7SEG
}
return sVal;
}
// Helper method for conversion of any data value from SI to user defined format // 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) double convertValue(const double &value, const String &name, const String &format, CommonData &commondata)
{ {
+6 -2
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
#if defined HARDWARE_V20 || HARDWARE_V21 #if defined BOARD_OBP60S3 || defined BOARD_OBP70S3
// Direction pin for RS485 NMEA0183 // Direction pin for RS485 NMEA0183
#define OBP_DIRECTION_PIN 18 #define OBP_DIRECTION_PIN 18
// I2C // I2C
@@ -34,13 +34,17 @@
#define PCF8574_I2C_ADDR1 0x20 // First digital out module #define PCF8574_I2C_ADDR1 0x20 // First digital out module
// FRAM (e.g. MB85RC256V) // FRAM (e.g. MB85RC256V)
#define FRAM_I2C_ADDR 0x50 #define FRAM_I2C_ADDR 0x50
// SPI (E-Ink display, Extern Bus) // SPI (E-paper display, TFT display Extern Bus)
#define OBP_SPI_CS 39 #define OBP_SPI_CS 39
#define OBP_SPI_DC 40 #define OBP_SPI_DC 40
#define OBP_SPI_RST 41 #define OBP_SPI_RST 41
#define OBP_SPI_BUSY 42 #define OBP_SPI_BUSY 42
#define OBP_SPI_CLK 38 #define OBP_SPI_CLK 38
#define OBP_SPI_DIN 48 #define OBP_SPI_DIN 48
#define OBP_TFT_OFFSET_X 10 // ST7796, ILI9488 operating x-offset for centered 400x300 content
#define OBP_TFT_OFFSET_Y -20 // ST7796, ILI9488 operating y-offset for centered 400x300 content
#define TFT_BLACK 0x0109 // Replacement color for black on TFT (RGB565)
#define TFT_WHITE 0xFFFF // Replacement color for white on TFT (RGB565)
#define SHOW_TIME 6000 // Show time in [ms] for logo and WiFi QR code #define SHOW_TIME 6000 // Show time in [ms] for logo and WiFi QR code
#define FULL_REFRESH_TIME 600 // Refresh cycle time in [s][600...3600] for full display update (very important healcy function) #define FULL_REFRESH_TIME 600 // Refresh cycle time in [s][600...3600] for full display update (very important healcy function)
#define GxEPD_WIDTH 400 // Display width #define GxEPD_WIDTH 400 // Display width
+1 -1
View File
@@ -58,7 +58,7 @@ void initKeys(CommonData &commonData) {
commonData.keydata[5].h = height; commonData.keydata[5].h = height;
} }
#if defined HARDWARE_V20 || HARDWARE_V21 #ifdef BOARD_OBP60S3
// 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) {
+1 -1
View File
@@ -39,7 +39,7 @@ void qrWiFi(String ssid, String passwd, uint16_t fgcolor, uint16_t bgcolor){
getdisplay().setTextColor(fgcolor); getdisplay().setTextColor(fgcolor);
getdisplay().setCursor(140, 285); getdisplay().setCursor(140, 285);
getdisplay().print("WiFi"); getdisplay().print("WiFi");
getdisplay().nextPage(); // Full Refresh displayNextPage(); // Full Refresh
} }
#endif #endif
+199 -140
View File
@@ -1,5 +1,4 @@
#include "OBPDataOperations.h" #include "OBPDataOperations.h"
//#include "BoatDataCalibration.h" // Functions lib for data instance calibration
// --- Class CalibrationData --------------- // --- Class CalibrationData ---------------
CalibrationData::CalibrationData(GwLog* log) CalibrationData::CalibrationData(GwLog* log)
@@ -108,7 +107,6 @@ void CalibrationData::readConfig(GwConfigHandler* config)
LOG_DEBUG(GwLog::LOG, "Calibration data type added: %s, offset: %f, slope: %f, smoothing: %f", instance.c_str(), 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); 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 // Handle calibrationMap and calibrate all boat data values
@@ -155,8 +153,7 @@ bool CalibrationData::calibrateInstance(GwApi::BoatValue* boatDataValue)
if (format == "formatWind") { // instance is of type angle if (format == "formatWind") { // instance is of type angle
dataValue = (dataValue * slope) + offset; dataValue = (dataValue * slope) + offset;
// dataValue = WindUtils::toPI(dataValue); dataValue = WindUtils::to2PI(dataValue); // wind angle data is stored in [0..2PI] format
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 } else if (format == "formatCourse") { // instance is of type direction
dataValue = (dataValue * slope) + offset; dataValue = (dataValue * slope) + offset;
@@ -169,7 +166,6 @@ bool CalibrationData::calibrateInstance(GwApi::BoatValue* boatDataValue)
dataValue = (dataValue * slope) + offset; dataValue = (dataValue * slope) + offset;
} }
boatDataValue->value = dataValue; // update boat data value with calibrated value boatDataValue->value = dataValue; // update boat data value with calibrated value
calibrationMap[instance].value = dataValue; // store the calibrated value in the list calibrationMap[instance].value = dataValue; // store the calibrated value in the list
calibrationMap[instance].isCalibrated = true; calibrationMap[instance].isCalibrated = true;
@@ -211,108 +207,107 @@ bool CalibrationData::smoothInstance(GwApi::BoatValue* boatDataValue)
calibrationMap[instance].value = dataValue; // store the smoothed value in the list calibrationMap[instance].value = dataValue; // store the smoothed value in the list
calibrationMap[instance].isCalibrated = true; 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; return true;
} }
// --- End Class CalibrationData --------------- // --- End Class CalibrationData ---------------
// --- Class HstryBuf --------------- // --- Class HstryBuf ---------------
HstryBuf::HstryBuf(const String& name, int size, BoatValueList* boatValues, GwLog* log) HstryBuf::HstryBuf(const String& name, const int size, BoatValueList* boatValues, const bool smooth, GwLog* log)
: logger(log) : logger(log)
, smoothing(smooth)
, boatDataName(name) , boatDataName(name)
{ {
hstryBuf.resize(size); hstryBuf.resize(size);
boatValue = boatValues->findValueOrCreate(name); boatValue = boatValues->findValueOrCreate(name);
} }
void HstryBuf::init(const String& format, int updFreq, int mltplr, double minVal, double maxVal) void HstryBuf::init(const String& format, int updFreq, double mltplr, double minVal, double maxVal)
{ {
hstryBuf.setMetaData(boatDataName, format, updFreq, mltplr, minVal, maxVal); hstryBuf.setMetaData(boatDataName, format, updFreq, mltplr, minVal, maxVal);
hstryMin = minVal; hstryMin = minVal;
hstryMax = maxVal; hstryMax = maxVal;
bufUpdateTime = 0;
if (!boatValue->valid) { if (!boatValue->valid) {
boatValue->setFormat(format);
boatValue->value = std::numeric_limits<double>::max(); // mark current value invalid boatValue->value = std::numeric_limits<double>::max(); // mark current value invalid
} }
}
void HstryBuf::add(double value) isTypeAngle = format == "formatCourse" || format == "formatWind" || format == "formatRot";
{ // initialize correct variant of buffer for averaging history data
if (value >= hstryMin && value <= hstryMax) { if (smoothing) {
hstryBuf.add(value); if (isTypeAngle) {
// LOG_DEBUG(GwLog::DEBUG, "HstryBuf::add: name: %s, value: %.3f", hstryBuf.getName(), value); chrtAvgAngle.begin();
} else {
chrtAvg.begin();
}
} }
} }
void HstryBuf::handle(bool useSimuData, CommonData& common) void HstryBuf::add(const double value)
{ {
// GwApi::BoatValue* tmpBVal; double bufVal = value;
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 if (value >= hstryMin && value <= hstryMax) {
// 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) { if (smoothing) {
// Calibrate boat value before adding it to history buffer if (isTypeAngle) {
//calibrationData.calibrateInstance(tmpBVal.get(), logger); bufVal = chrtAvgAngle.reading(value);
//add(tmpBVal->value); } else {
add(boatValue->value); bufVal = chrtAvg.reading(value);
}
}
} else if (useSimuData) { // add simulated value to history buffer hstryBuf.add(bufVal);
double simSIValue = formatValue(tmpBVal.get(), common).value; // simulated value is generated at <formatValue>; here: retreive SI value // LOG_DEBUG(GwLog::DEBUG, "HstryBuf::add: name: %s, value: %.3f, value buffer: %.3f", hstryBuf.getName(), value, hstryBuf.getLast());
add(simSIValue); }
} else { }
// here we will add invalid (DBL_MAX) value; this will mark periods of missing data in buffer together with a timestamp
void HstryBuf::handle(const bool useSimuData, CommonData& common)
{
if ((millis() - bufUpdateTime) >= hstryBuf.getUpdFreq()) {
bufUpdateTime = millis();
if (boatValue->valid) {
add(boatValue->value);
} else if (useSimuData) { // add simulated value to history buffer
std::unique_ptr<GwApi::BoatValue> tmpBVal; // Temp variable to get formatted and converted data value from OBP60Formatter
tmpBVal = std::unique_ptr<GwApi::BoatValue>(new GwApi::BoatValue(boatDataName)); // create temporary boat value for retrieval of simulation value
tmpBVal->setFormat(boatValue->getFormat());
tmpBVal->value = boatValue->value;
tmpBVal->valid = boatValue->valid;
double simSIValue = formatValue(tmpBVal.get(), common).value; // simulated value is generated at <formatValue>; here: retreive SI value
add(simSIValue);
} else {
// TODO: add invalid (DBL_MAX) value; this will mark periods of missing data in buffer together with a timestamp
}
} }
} }
// --- End Class HstryBuf --------------- // --- End Class HstryBuf ---------------
// --- Class HstryBuffers --------------- // --- Class HstryBuffers ---------------
HstryBuffers::HstryBuffers(int size, BoatValueList* boatValues, GwLog* log) HstryBuffers::HstryBuffers(const int size, BoatValueList* boatValues, GwLog* log)
: size(size) : size(size)
, boatValueList(boatValues) , boatValueList(boatValues)
, logger(log) , logger(log) { };
{
// collect boat values for true wind calculation
// should all have been already created at true wind object initialization
// potentially to be moved to history buffer handling
awaBVal = boatValueList->findValueOrCreate("AWA");
hdtBVal = boatValueList->findValueOrCreate("HDT");
hdmBVal = boatValueList->findValueOrCreate("HDM");
varBVal = boatValueList->findValueOrCreate("VAR");
cogBVal = boatValueList->findValueOrCreate("COG");
sogBVal = boatValueList->findValueOrCreate("SOG");
awdBVal = boatValueList->findValueOrCreate("AWD");
}
// Create history buffer for boat data type // Create history buffer for boat data type
void HstryBuffers::addBuffer(const String& name) void HstryBuffers::addBuffer(const String& name, const bool smooth)
{ {
if (HstryBuffers::getBuffer(name) != nullptr) { // buffer for this data type already exists if (HstryBuffers::getBuffer(name) != nullptr) { // buffer for this data type already exists
return; return;
} }
if (bufferParams.find(name) == bufferParams.end()) { // requested boat data type is not supported in list of <bufferParams>
auto it = bufferParams.find(name);
if (it == bufferParams.end() && !name.startsWith("xdr")) { // requested boat data type is not supported in list of <bufferParams>
// we take any "XDR" type, though
return; return;
} }
hstryBuffers[name] = std::unique_ptr<HstryBuf>(new HstryBuf(name, size, boatValueList, logger)); // create buffer only; initialization with metadata can only be done later after boat data have been updated first time
hstryBuffers[name] = std::unique_ptr<HstryBuf>(new HstryBuf(name, size, boatValueList, smooth, logger));
// Initialize metadata for buffer LOG_DEBUG(GwLog::LOG, "HstryBuffers: new buffer added: name: %s", name);
String valueFormat = bufferParams[name].format; // Data format of boat data type
// String valueFormat = boatValueList->findValueOrCreate(name)->getFormat().c_str(); // Unfortunately, format is not yet available during system initialization
int hstryUpdFreq = bufferParams[name].hstryUpdFreq; // Update frequency for history buffers in ms
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
hstryBuffers[name]->init(valueFormat, hstryUpdFreq, mltplr, bufferMinVal, bufferMaxVal);
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);
} }
// Handle all registered history buffers // Handle all registered history buffers
@@ -320,6 +315,35 @@ void HstryBuffers::handleHstryBufs(bool useSimuData, CommonData& common)
{ {
for (auto& bufMap : hstryBuffers) { for (auto& bufMap : hstryBuffers) {
auto& buf = bufMap.second; auto& buf = bufMap.second;
if (!buf->hasMetaData()) { // meta data initialization for buffer has not been done before
String valueFormat = boatValueList->findValueOrCreate(buf->boatDataName)->getFormat().c_str();
// LOG_DEBUG(GwLog::DEBUG, "HstryBuffers: value name: %s, format: %s", boatValueList->findValueOrCreate(buf->boatDataName)->getName().c_str(), valueFormat);
if (!valueFormat.isEmpty()) {
String lookupKey = buf->boatDataName;
if (buf->boatDataName.startsWith("xdr")) { // current boat data is of type "XDR"
lookupKey = valueFormat;
}
auto it = bufferParams.find(lookupKey);
if (it == bufferParams.end()) { // requested boat data type is not supported in list of <bufferParams>
return;
}
HistoryParams params = it->second; // this is the metadata for the new buffer
int hstryUpdFreq = params.hstryUpdFreq; // Update frequency for history buffers in ms
double mltplr = params.mltplr; // default multiplier which transforms original <double> value into buffer type format
double bufferMinVal = params.bufferMinVal; // Min value for this history buffer
double bufferMaxVal = params.bufferMaxVal; // Max value for this history buffer
hstryBuffers[buf->boatDataName]->init(valueFormat, hstryUpdFreq, mltplr, bufferMinVal, bufferMaxVal);
buf->metaDataDefined = true;
LOG_DEBUG(GwLog::LOG, "HstryBuffers::handleBufs: metadata added: name: %s, format: %s, frequency: %d, multiplier: %f, min value: %.2f, max value: %.2f", buf->boatDataName, valueFormat, hstryUpdFreq,
mltplr, bufferMinVal, bufferMaxVal);
}
}
buf->handle(useSimuData, common); buf->handle(useSimuData, common);
} }
} }
@@ -329,8 +353,9 @@ RingBuffer<uint16_t>* HstryBuffers::getBuffer(const String& name)
auto it = hstryBuffers.find(name); auto it = hstryBuffers.find(name);
if (it != hstryBuffers.end()) { if (it != hstryBuffers.end()) {
return &it->second->hstryBuf; return &it->second->hstryBuf;
} else {
return nullptr;
} }
return nullptr;
} }
// --- End Class HstryBuffers --------------- // --- End Class HstryBuffers ---------------
@@ -396,56 +421,73 @@ void WindUtils::addPolar(const double* phi1, const double* r1,
toPol(&x1, &y1, phi, r); toPol(&x1, &y1, phi, r);
} }
void WindUtils::calcTwdSA(const double* AWA, const double* AWS, void WindUtils::calcTwdSA(const double* AWA, const double* AWS, const double* AWD, const double* CTW, const double* STW, const double* HDT,
const double* CTW, const double* STW, const double* HDT, double* TWA, double* TWS, double* TWD)
double* TWD, double* TWS, double* TWA, double* AWD)
{ {
*AWD = *AWA + *HDT;
*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 to [0..360°] (2PI) and TWA to [-180..180] (PI) // Normalize to [0..2PI]
*TWD = to2PI(*TWD); *TWD = to2PI(*TWD);
*TWA = toPI(*TWD - *HDT); *TWA = to2PI(*TWD - *HDT); // for internal storage we normalize wind angle to [0..2PI], as well
} }
double WindUtils::calcHDT(const double* hdmVal, const double* varVal, const double* cogVal, const double* sogVal) // calculate HDT from either HDM + VAR, HDM only, or COG
bool WindUtils::calcHDT(const double* hdmVal, const double* varVal, const double* cogVal, const double* sogVal, double* hdtVal)
{ {
double hdt;
double minSogVal = 0.1; // SOG below this value (m/s) is assumed to be data noise from GPS sensor
if (*hdmVal != DBL_MAX) {
hdt = *hdmVal + (*varVal != DBL_MAX ? *varVal : 0.0); // Use corrected HDM if HDT is not available (or just HDM if VAR is not available)
hdt = to2PI(hdt);
} else if (*cogVal != DBL_MAX && *sogVal >= minSogVal) {
hdt = *cogVal; // Use COG as fallback if HDT and HDM are not available, and SOG is not data noise
} else {
hdt = DBL_MAX; // Cannot calculate HDT without valid HDM or HDM+VAR or COG
}
return hdt;
}
bool WindUtils::calcWinds(const double* awaVal, const double* awsVal,
const double* cogVal, const double* stwVal, const double* sogVal, const double* hdtVal,
const double* hdmVal, const double* varVal, double* twdVal, double* twsVal, double* twaVal, double* awdVal)
{
double stw, hdt, ctw;
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) {
hdt = *hdtVal; // Use HDT if available // HDT already available-> nothing to do
return true;
}
if (*hdmVal != DBL_MAX) {
*hdtVal = *hdmVal + (*varVal != DBL_MAX ? *varVal : 0.0); // Use corrected HDM if HDT is not available (or just HDM if VAR is not available)
*hdtVal = to2PI(*hdtVal);
} else if (*cogVal != DBL_MAX && *sogVal >= minSogVal) {
*hdtVal = *cogVal; // Use COG as fallback if HDT and HDM are not available, and SOG is not data noise
} else { } else {
hdt = calcHDT(hdmVal, varVal, cogVal, sogVal); *hdtVal = DBL_MAX; // Cannot calculate HDT without valid HDM or HDM+VAR or COG
return false;
}
return true;
}
// calculate wind direction (either TWD or AWD) from wind angle (TWA or AWA) and HDT
bool WindUtils::calcWD(const double* waVal, const double* hdtVal, double* wdVal)
{
if (*waVal != DBL_MAX && *hdtVal != DBL_MAX) {
*wdVal = *waVal + *hdtVal;
*wdVal = to2PI(*wdVal);
} else {
*wdVal = DBL_MAX; // Cannot calculate wind direction WD without valid wind angle WA and HDT
return false;
}
return true;
}
// calculate full set of true winds (TWA, TWS, TWD) apparent winds and more data
bool WindUtils::calcTrueWinds(const double* awaVal, const double* awsVal, const double* awd,
const double* cogVal, const double* stwVal, const double* sogVal, const double* hdtVal,
double* twaVal, double* twsVal, double* twdVal)
{
double stw, ctw;
double twd, tws, twa;
double minSogVal = 0.1; // SOG below this value (m/s) is assumed to be data noise from GPS sensor
if ((*awaVal == DBL_MAX) || (*awsVal == DBL_MAX)) {
// Cannot calculate true winds at all without valid AWA, AWS
return false;
} }
if (*cogVal != DBL_MAX && *sogVal >= minSogVal) { // if SOG is data noise, we don't trust COG if (*cogVal != DBL_MAX && *sogVal >= minSogVal) { // if SOG is data noise, we don't trust COG
ctw = *cogVal; // Use COG for CTW if available ctw = *cogVal; // Use COG for CTW if available
} else { } else {
ctw = hdt; // 2nd approximation for CTW; hdt must exist if we reach this part of the code ctw = *hdtVal; // 2nd approximation for CTW; HDT must exist if we reach this part of code
} }
if (*stwVal != DBL_MAX) { if (*stwVal != DBL_MAX) {
@@ -456,28 +498,34 @@ bool WindUtils::calcWinds(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;
} }
// LOG_DEBUG(GwLog::DEBUG, "WindUtils:calcWinds: HDT: %.1f, CTW %.1f, STW %.1f", hdt, ctw, stw);
if ((*awaVal == DBL_MAX) || (*awsVal == DBL_MAX)) { calcTwdSA(awaVal, awsVal, awd, &ctw, &stw, hdtVal, &twa, &tws, &twd);
// Cannot calculate true wind without valid AWA, AWS; other checks are done earlier *twaVal = twa;
return false; *twsVal = tws;
} else { *twdVal = twd;
calcTwdSA(awaVal, awsVal, &ctw, &stw, &hdt, &twd, &tws, &twa, &awd);
*twdVal = twd;
*twsVal = tws;
*twaVal = twa;
*awdVal = awd;
return true; return true;
}
} }
// Calculate true wind data and add to obp60task boat data list // Set max wind speed
bool WindUtils::addWinds() void WindUtils::setMaxWs(GwApi::BoatValue* wsMaxValue, const double* wsVal)
{
static double maxWs = 0.0; // maintain own maxTWS value in obp user task; core gateway would reset MaxTWS obp boat value if true wind data is not available
if (*wsVal != DBL_MAX && *wsVal > maxWs) {
maxWs = *wsVal;
}
if (maxWs > 0.0 && maxWs >= wsMaxValue->value) {
wsMaxValue->value = maxWs; // overwrite core gateway value each second again with own user task value if that value is larger
wsMaxValue->valid = true;
}
};
// Calculate true wind data and add to obp60task boat data list
bool WindUtils::handleWinds(bool calcWinds)
{ {
double twd, tws, twa, awd, hdt;
bool twCalculated = false; bool twCalculated = false;
bool awdCalculated = false;
double awaVal = awaBVal->valid ? awaBVal->value : DBL_MAX; double awaVal = awaBVal->valid ? awaBVal->value : DBL_MAX;
double awsVal = awsBVal->valid ? awsBVal->value : DBL_MAX; double awsVal = awsBVal->valid ? awsBVal->value : DBL_MAX;
@@ -487,49 +535,60 @@ bool WindUtils::addWinds()
double hdtVal = hdtBVal->valid ? hdtBVal->value : DBL_MAX; double hdtVal = hdtBVal->valid ? hdtBVal->value : DBL_MAX;
double hdmVal = hdmBVal->valid ? hdmBVal->value : DBL_MAX; double hdmVal = hdmBVal->valid ? hdmBVal->value : DBL_MAX;
double varVal = varBVal->valid ? varBVal->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, double twaVal = twaBVal->valid ? twaBVal->value : DBL_MAX;
// 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 twsVal = twsBVal->valid ? twsBVal->value : DBL_MAX;
// Check if TWD can be calculated from TWA and HDT/HDM if (calcHDT(&hdmVal, &varVal, &cogVal, &sogVal, &hdtVal)) {
if (twaBVal->valid) { hdtBVal->value = hdtVal;
if (!twdBVal->valid) { hdtBVal->valid = true;
if (hdtVal != DBL_MAX) { } else {
hdt = hdtVal; // Use HDT if available // determination of HDT was not possible due to missing prerequisite data
} else { hdtBVal->valid = false;
hdt = calcHDT(&hdmVal, &varVal, &cogVal, &sogVal); return false;
} }
twd = twaBVal->value + hdt;
twd = to2PI(twd); // calculate AWD if it does not exist yet and AWA is available
if (!awdBVal->valid) {
if (calcWD(&awaVal, &hdtVal, &awd)) {
awdBVal->value = awd;
awdBVal->valid = true;
} else {
awdBVal->valid = false;
}
}
// calculate TWD if it does not exist yet and TWA is available
if (!twdBVal->valid) {
if (calcWD(&twaVal, &hdtVal, &twd)) {
twdBVal->value = twd; twdBVal->value = twd;
twdBVal->valid = true; twdBVal->valid = true;
} else {
twdBVal->valid = false;
} }
}
} else { if (!calcWinds) { // don't calculate true winds from apparent winds if not set in configuration
// Calculate true winds and AWD; if true winds exist, use at least AWD calculation return twCalculated;
twCalculated = calcWinds(&awaVal, &awsVal, &cogVal, &stwVal, &sogVal, &hdtVal, &hdmVal, &varVal, &twd, &tws, &twa, &awd); }
if (!twaBVal->valid || !twsBVal->valid || !twdBVal->valid) {
// calculate true winds at least one of three true wind values does not exist
twCalculated = calcTrueWinds(&awaVal, &awsVal, &awd, &cogVal, &stwVal, &sogVal, &hdtVal, &twa, &tws, &twd);
if (twCalculated) { // Replace values only, if successfully calculated and not already available if (twCalculated) { // Replace values only, if successfully calculated and not already available
if (!twdBVal->valid) { if (!twaBVal->valid) {
twdBVal->value = twd; twaBVal->value = twa;
twdBVal->valid = true; twaBVal->valid = true;
} }
if (!twsBVal->valid) { if (!twsBVal->valid) {
twsBVal->value = tws; twsBVal->value = tws;
twsBVal->valid = true; twsBVal->valid = true;
} }
if (!twaBVal->valid) { if (!twdBVal->valid) {
//twaBVal->value = twa; twdBVal->value = twd;
twaBVal->value = to2PI(twa); // convert to [0..360], because pages cannot display negative values properly yet twdBVal->valid = true;
twaBVal->valid = true;
}
if (!awdBVal->valid) {
awdBVal->value = awd;
awdBVal->valid = true;
} }
} }
} }
// LOG_DEBUG(GwLog::DEBUG, "WindUtils:addWinds: twCalculated %d, TWD %.1f, TWA %.1f, TWS %.2f kn, AWD: %.1f", twCalculated, twdBVal->value * RAD_TO_DEG,
// twaBVal->value * RAD_TO_DEG, twsBVal->value * 3.6 / 1.852, awdBVal->value * RAD_TO_DEG);
return twCalculated; return twCalculated;
} }
+66 -35
View File
@@ -3,6 +3,7 @@
#include "OBPRingBuffer.h" #include "OBPRingBuffer.h"
#include "Pagedata.h" #include "Pagedata.h"
#include "obp60task.h" #include "obp60task.h"
#include "movingAvg.h"
#include <map> #include <map>
#include <unordered_map> #include <unordered_map>
@@ -19,7 +20,7 @@ private:
} tCalibrationData; } tCalibrationData;
std::unordered_map<std::string, tCalibrationData> calibrationMap; // list of calibration data instances 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 std::unordered_map<std::string, double> lastValue; // array for last smoothed value of boat data values
GwLog* logger; GwLog* logger;
static constexpr int8_t MAX_CALIBRATION_DATA = 4; // maximum number of calibration data instances static constexpr int8_t MAX_CALIBRATION_DATA = 4; // maximum number of calibration data instances
@@ -32,108 +33,138 @@ public:
bool smoothInstance(GwApi::BoatValue* boatDataValue); // Smooth single boat data value bool smoothInstance(GwApi::BoatValue* boatDataValue); // Smooth single boat data value
}; };
// Class for a single history buffer of boat values
class HstryBuf { class HstryBuf {
private: private:
RingBuffer<uint16_t> hstryBuf; // Circular buffer to store history values RingBuffer<uint16_t> hstryBuf; // Circular buffer to store history values
movingAvg<double> chrtAvg {6}; // Store average of the last 6 chart values if chart gradient shall be smoothed
movingAvgAngle<double> chrtAvgAngle {6}; // Store average of the last 6 chart values (angle data) if chart gradient shall be smoothed
String boatDataName; String boatDataName;
double hstryMin; double hstryMin;
double hstryMax; double hstryMax;
bool metaDataDefined = false;
bool smoothing = false;
bool isTypeAngle = false;
unsigned long bufUpdateTime;
GwApi::BoatValue* boatValue; GwApi::BoatValue* boatValue;
GwLog* logger; GwLog* logger;
friend class HstryBuffers; friend class HstryBuffers;
public: public:
HstryBuf(const String& name, int size, BoatValueList* boatValues, GwLog* log); HstryBuf(const String& name, const int size, BoatValueList* boatValues, const bool smooth, GwLog* log);
void init(const String& format, int updFreq, int mltplr, double minVal, double maxVal); bool hasMetaData() const { return metaDataDefined; };
void init(const String& format, int updFreq, double mltplr, double minVal, double maxVal);
void add(double value); void add(double value);
void handle(bool useSimuData, CommonData& common); void handle(bool useSimuData, CommonData& common);
}; };
// Manage list of history buffers for supported boat data; used by boat data charts
class HstryBuffers { class HstryBuffers {
private: private:
std::map<String, std::unique_ptr<HstryBuf>> hstryBuffers; std::map<String, std::unique_ptr<HstryBuf>> hstryBuffers;
int size; // size of all history buffers int size; // size of all history buffers
BoatValueList* boatValueList; BoatValueList* boatValueList;
GwLog* logger; GwLog* logger;
GwApi::BoatValue *awaBVal, *hdtBVal, *hdmBVal, *varBVal, *cogBVal, *sogBVal, *awdBVal; // boat values for true wind calculation
struct HistoryParams { struct HistoryParams {
int hstryUpdFreq; // update frequency of history buffer (documentation only) int hstryUpdFreq; // update frequency of history buffer (documentation only)
int mltplr; // specifies actual value precision being storable: double mltplr; // specifies actual value precision being storable:
// [10000: 0 - 6.5535 | 1000: 0 - 65.535 | 100: 0 - 650.35 | 10: 0 - 6503.5 // [10000: 0 - 6.5535 | 1000: 0 - 65.535 | 100: 0 - 655.35 | 10: 0 - 6553.5 | 1: 0 - 65535 | 0.1: 0 - 655350]
double bufferMinVal; // minimum valid data value double bufferMinVal; // minimum valid data value
double bufferMaxVal; // maximum valid data value double bufferMaxVal; // maximum valid data value
String format; // format of data type
}; };
// Define buffer parameters for supported boat data type
std::map<String, HistoryParams> bufferParams = { std::map<String, HistoryParams> bufferParams = {
{ "AWA", { 1000, 10000, 0.0, M_TWOPI, "formatWind" } }, { "AWA", { 1000, 10000, 0.0, M_TWOPI } },
{ "AWD", { 1000, 10000, 0.0, M_TWOPI, "formatCourse" } }, { "AWD", { 1000, 10000, 0.0, M_TWOPI } },
{ "AWS", { 1000, 1000, 0.0, 65.0, "formatKnots" } }, { "AWS", { 1000, 1000, 0.0, 65.0 } },
{ "COG", { 1000, 10000, 0.0, M_TWOPI, "formatCourse" } }, { "COG", { 1000, 10000, 0.0, M_TWOPI } },
{ "DBS", { 1000, 100, 0.0, 650.0, "formatDepth" } }, { "DBK", { 1000, 100, 0.0, 650.0 } },
{ "DBT", { 1000, 100, 0.0, 650.0, "formatDepth" } }, { "DBS", { 1000, 100, 0.0, 650.0 } },
{ "DPT", { 1000, 100, 0.0, 650.0, "formatDepth" } }, { "DBT", { 1000, 100, 0.0, 650.0 } },
{ "HDM", { 1000, 10000, 0.0, M_TWOPI, "formatCourse" } }, { "DPT", { 1000, 100, 0.0, 650.0 } },
{ "HDT", { 1000, 10000, 0.0, M_TWOPI, "formatCourse" } }, { "HDM", { 1000, 10000, 0.0, M_TWOPI } },
{ "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" { "HDT", { 1000, 10000, 0.0, M_TWOPI } },
{ "SOG", { 1000, 1000, 0.0, 65.0, "formatKnots" } }, { "ROT", { 1000, 10000, -M_PI / 180.0 * 99.0, M_PI / 180.0 * 99.0 } }, // min/max is -/+ 99 degrees for "rate of turn"
{ "STW", { 1000, 1000, 0.0, 65.0, "formatKnots" } }, { "SOG", { 1000, 1000, 0.0, 65.0 } },
{ "TWA", { 1000, 10000, 0.0, M_TWOPI, "formatWind" } }, { "STW", { 1000, 1000, 0.0, 65.0 } },
{ "TWD", { 1000, 10000, 0.0, M_TWOPI, "formatCourse" } }, { "TWA", { 1000, 10000, 0.0, M_TWOPI } },
{ "TWS", { 1000, 1000, 0.0, 65.0, "formatKnots" } }, { "TWD", { 1000, 10000, 0.0, M_TWOPI } },
{ "WTemp", { 1000, 100, 233.0, 650.0, "kelvinToC" } } // [-50..376] °C { "TWS", { 1000, 1000, 0.0, 65.0 } },
{ "WTemp", { 1000, 100, 263.15, 403.15 } }, // water temp [-10..130] °C
{ "formatXdr:C:K", { 1000, 100, 223.15, 423.15 } }, // temperature [-50..150] deg celsius
{ "formatXdr:P:B", { 60000, 1000, 0, 65.0 } }, // pressure [0..65] bar
{ "formatXdr:P:P", { 60000, 0.1, 0, 650000 } }, // pressure [0..6500] hPa
{ "formatXdr:H:P", { 1000, 100, 0, 100 } }, // humidity [0..100] percent
{ "formatXdr:I:A", { 1000, 100, 0, 650.0 } }, // current [0..650] amperes
{ "formatXdr:U:V", { 1000, 1000, 0, 65.0 } }, // voltage [0..65] volts
{ "formatXdr:T:R", { 1000, 1, 0, 30000 } }, // tachometer [0..30000] rpm
{ "formatXdr:V:L", { 10000, 100, 0, 650 } }, // volume [0..650] litres
{ "formatXdr:V:M", { 10000, 10000, 0, 6.50 } }, // volume [0..6.5] m^3
}; };
public: public:
HstryBuffers(int size, BoatValueList* boatValues, GwLog* log); HstryBuffers(int size, BoatValueList* boatValues, GwLog* log);
void addBuffer(const String& name); void addBuffer(const String& name, const bool smooth);
void handleHstryBufs(bool useSimuData, CommonData& common); void handleHstryBufs(bool useSimuData, CommonData& common);
RingBuffer<uint16_t>* getBuffer(const String& name); RingBuffer<uint16_t>* getBuffer(const String& name);
}; };
class WindUtils { class WindUtils {
private: private:
GwApi::BoatValue *twaBVal, *twsBVal, *twdBVal; GwApi::BoatValue *twaBVal, *twsBVal, *twdBVal, *maxtwsBVal;
GwApi::BoatValue *awaBVal, *awsBVal, *awdBVal, *cogBVal, *stwBVal, *sogBVal, *hdtBVal, *hdmBVal, *varBVal; GwApi::BoatValue *awaBVal, *awsBVal, *awdBVal;
GwApi::BoatValue *cogBVal, *stwBVal, *sogBVal, *hdtBVal, *hdmBVal, *varBVal;
double twd, tws, twa, awd;
static constexpr double DBL_MAX = std::numeric_limits<double>::max(); static constexpr double DBL_MAX = std::numeric_limits<double>::max();
GwLog* logger; GwLog* logger;
// specify missing data for boat value type AWD; AWD is not available in core gateway and need to be specified here
void defineAWD() {
awdBVal->setFormat("formatCourse");
awdBVal->valid = false;
}
public: public:
WindUtils(BoatValueList* boatValues, GwLog* log) WindUtils(BoatValueList* boatValues, GwLog* log)
: logger(log) : logger(log)
{ {
twaBVal = boatValues->findValueOrCreate("TWA"); twaBVal = boatValues->findValueOrCreate("TWA");
twsBVal = boatValues->findValueOrCreate("TWS"); twsBVal = boatValues->findValueOrCreate("TWS");
maxtwsBVal = boatValues->findValueOrCreate("MaxTws");
twdBVal = boatValues->findValueOrCreate("TWD"); 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");
hdtBVal = boatValues->findValueOrCreate("HDT"); hdtBVal = boatValues->findValueOrCreate("HDT");
hdmBVal = boatValues->findValueOrCreate("HDM"); hdmBVal = boatValues->findValueOrCreate("HDM");
varBVal = boatValues->findValueOrCreate("VAR"); varBVal = boatValues->findValueOrCreate("VAR");
awdBVal = boatValues->findValueOrCreate("AWD");
defineAWD();
}; };
static double to2PI(double a); static double to2PI(double a);
static double toPI(double a); static double toPI(double a);
static double to360(double a); static double to360(double a);
static double to180(double a); static double to180(double a);
void toCart(const double* phi, const double* r, double* x, double* y); void toCart(const double* phi, const double* r, double* x, double* y);
void toPol(const double* x, const double* y, double* phi, double* r); void toPol(const double* x, const double* y, double* phi, double* r);
void addPolar(const double* phi1, const double* r1, void addPolar(const double* phi1, const double* r1,
const double* phi2, const double* r2, const double* phi2, const double* r2,
double* phi, double* r); double* phi, double* r);
void calcTwdSA(const double* AWA, const double* AWS, void calcTwdSA(const double* AWA, const double* AWS, const double* AWD, const double* CTW, const double* STW, const double* HDT,
const double* CTW, const double* STW, const double* HDT, double* TWA, double* TWS, double* TWD);
double* TWD, double* TWS, double* TWA, double* AWD); bool calcHDT(const double* hdmVal, const double* varVal, const double* cogVal, const double* sogVal, double* hdtVal);
static double calcHDT(const double* hdmVal, const double* varVal, const double* cogVal, const double* sogVal); bool calcWD(const double* waVal, const double* hdtVal, double* wdVal);
bool calcWinds(const double* awaVal, const double* awsVal, bool calcTrueWinds(const double* awaVal, const double* awsVal, const double* awd,
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, double* awdVal); double* twdVal, double* twsVal, double* twaVal);
bool addWinds(); void setMaxWs(GwApi::BoatValue *wsMaxValue, const double * wsVal);
void setMaxWs() { setMaxWs(maxtwsBVal, &tws); };
bool handleWinds(bool calcWinds);
}; };
+9 -5
View File
@@ -45,9 +45,9 @@ private:
size_t last; // Points to the last (newest) valid element size_t last; // Points to the last (newest) valid element
size_t count; // Number of valid elements currently in buffer size_t count; // Number of valid elements currently in buffer
bool is_Full; // Indicates that all buffer elements are used and ringing is in use bool is_Full; // Indicates that all buffer elements are used and ringing is in use
T MIN_VAL; // lowest possible value of buffer of type <T> T NUMLIMIT_LOW; // internally lowest possible value of buffer of type <T>
T MAX_VAL; // highest possible value of buffer of type <T> -> indicates invalid value in buffer T NUMLIMIT_HIGH; // internally highest possible value of buffer of type <T>
double dblMIN_VAL, dblMAX_VAL; // MIN_VAL, MAX_VAL in double format double BUFMIN_VAL, BUFMAX_VAL; // lowest/highest possible buffer value considering multiplier -> externally used
mutable SemaphoreHandle_t bufLocker; mutable SemaphoreHandle_t bufLocker;
// metadata for buffer // metadata for buffer
@@ -55,8 +55,8 @@ private:
String dataFmt; // Format of boat data in buffer String dataFmt; // Format of boat data in buffer
int updFreq; // Update frequency in milliseconds int updFreq; // Update frequency in milliseconds
double mltplr; // Multiplier which transforms original <double> value into buffer type format double mltplr; // Multiplier which transforms original <double> value into buffer type format
double smallest; // Value range of buffer: smallest value; needs to be => MIN_VAL double lowest; // low value range for boat data in this buffer; needs to be => BUFMIN_VAL
double largest; // Value range of buffer: biggest value; needs to be < MAX_VAL, since MAX_VAL indicates invalid entries double highest; // high value range for boat data in this buffer; needs to be < BUFMAX_VAL, since BUFMAX_VAL indicates invalid entries
void initCommon(); void initCommon();
@@ -64,10 +64,12 @@ public:
RingBuffer(); RingBuffer();
RingBuffer(size_t size); RingBuffer(size_t size);
void setMetaData(String name, String format, int updateFrequency, double multiplier, double minValue, double maxValue); // Set meta data for buffer void setMetaData(String name, String format, int updateFrequency, double multiplier, double minValue, double maxValue); // Set meta data for buffer
void setFormat(String format); // Specify format of buffer
bool getMetaData(String& name, String& format, int& updateFrequency, double& multiplier, double& minValue, double& maxValue); // Get meta data of buffer bool getMetaData(String& name, String& format, int& updateFrequency, double& multiplier, double& minValue, double& maxValue); // Get meta data of buffer
bool getMetaData(String& name, String& format); bool getMetaData(String& name, String& format);
String getName() const; // Get buffer name String getName() const; // Get buffer name
String getFormat() const; // Get buffer data format String getFormat() const; // Get buffer data format
int getUpdFreq() const; // Get buffer update frequency
void add(const double& value); // Add a new value to buffer void add(const double& value); // Add a new value to buffer
double get(size_t index) const; // Get value at specific position (0-based index from oldest to newest) double get(size_t index) const; // Get value at specific position (0-based index from oldest to newest)
double getFirst() const; // Get the first (oldest) value in buffer double getFirst() const; // Get the first (oldest) value in buffer
@@ -78,6 +80,8 @@ public:
double getMax(size_t amount) const; // Get maximum value of the last <amount> values of buffer double getMax(size_t amount) const; // Get maximum value of the last <amount> values of buffer
double getMid() const; // Get mid value between <min> and <max> value in buffer double getMid() const; // Get mid value between <min> and <max> value in buffer
double getMid(size_t amount) const; // Get mid value between <min> and <max> value of the last <amount> values of buffer double getMid(size_t amount) const; // Get mid value between <min> and <max> value of the last <amount> values of buffer
double getCircularMid() const; // Get mid value of circle (degree) values of buffer
double getCircularMid(size_t amount) const; // Get mid value of circle (degree) values of the last <amount> values of buffer
double getMedian() const; // Get the median value in buffer double getMedian() const; // Get the median value in buffer
double getMedian(size_t amount) const; // Get the median value of the last <amount> values of buffer double getMedian(size_t amount) const; // Get the median value of the last <amount> values of buffer
size_t getCapacity() const; // Get the buffer capacity (maximum size) size_t getCapacity() const; // Get the buffer capacity (maximum size)
+134 -110
View File
@@ -1,24 +1,7 @@
#include "OBPRingBuffer.h"
#include <algorithm> #include <algorithm>
#include <limits> #include <limits>
#include <cmath> #include <cmath>
template <typename T>
void RingBuffer<T>::initCommon()
{
MIN_VAL = std::numeric_limits<T>::lowest();
MAX_VAL = std::numeric_limits<T>::max();
dblMIN_VAL = static_cast<double>(MIN_VAL);
dblMAX_VAL = static_cast<double>(MAX_VAL);
dataName = "";
dataFmt = "";
updFreq = -1;
mltplr = 1;
smallest = dblMIN_VAL;
largest = dblMAX_VAL;
bufLocker = xSemaphoreCreateMutex();
}
template <typename T> template <typename T>
RingBuffer<T>::RingBuffer() RingBuffer<T>::RingBuffer()
: capacity(0) : capacity(0)
@@ -44,7 +27,23 @@ RingBuffer<T>::RingBuffer(size_t size)
initCommon(); initCommon();
buffer.reserve(size); buffer.reserve(size);
buffer.resize(size, MAX_VAL); // MAX_VAL indicate invalid values buffer.resize(size, NUMLIMIT_HIGH); // NUMLIMIT_HIGH indicate invalid values
}
template <typename T>
void RingBuffer<T>::initCommon()
{
NUMLIMIT_LOW = std::numeric_limits<T>::lowest();
NUMLIMIT_HIGH = std::numeric_limits<T>::max();
dataName = "";
dataFmt = "";
updFreq = -1;
mltplr = 1;
BUFMIN_VAL = static_cast<double>(NUMLIMIT_LOW);
BUFMAX_VAL = static_cast<double>(NUMLIMIT_HIGH);
lowest = BUFMIN_VAL;
highest = BUFMAX_VAL;
bufLocker = xSemaphoreCreateMutex();
} }
// Specify meta data of buffer content // Specify meta data of buffer content
@@ -56,8 +55,18 @@ void RingBuffer<T>::setMetaData(String name, String format, int updateFrequency,
dataFmt = format; dataFmt = format;
updFreq = updateFrequency; updFreq = updateFrequency;
mltplr = multiplier; mltplr = multiplier;
smallest = std::max(dblMIN_VAL, minValue); BUFMIN_VAL = static_cast<double>(NUMLIMIT_LOW) / mltplr; // lowest possible buffer value; converted to external view
largest = std::min(dblMAX_VAL, maxValue); BUFMAX_VAL = static_cast<double>(NUMLIMIT_HIGH) / mltplr; // highest possible buffer value; converted to external view
lowest = std::max(BUFMIN_VAL, minValue); // low value range, set by user
highest = std::min(std::nextafter(BUFMAX_VAL, -std::numeric_limits<double>::infinity()), maxValue); // high value range, set by user; maximum is 1 tick lower than BUFMAX_VAL
}
// Specify format of buffer content
template <typename T>
void RingBuffer<T>::setFormat(String format)
{
GWSYNCHRONIZED(&bufLocker);
dataFmt = format;
} }
// Get meta data of buffer content // Get meta data of buffer content
@@ -73,8 +82,8 @@ bool RingBuffer<T>::getMetaData(String& name, String& format, int& updateFrequen
format = dataFmt; format = dataFmt;
updateFrequency = updFreq; updateFrequency = updFreq;
multiplier = mltplr; multiplier = mltplr;
minValue = smallest; minValue = lowest;
maxValue = largest; maxValue = highest;
return true; return true;
} }
@@ -106,13 +115,20 @@ String RingBuffer<T>::getFormat() const
return dataFmt; return dataFmt;
} }
// Get buffer update frequency
template <typename T>
int RingBuffer<T>::getUpdFreq() const
{
return updFreq;
}
// Add a new value to buffer // Add a new value to buffer
template <typename T> template <typename T>
void RingBuffer<T>::add(const double& value) void RingBuffer<T>::add(const double& value)
{ {
GWSYNCHRONIZED(&bufLocker); GWSYNCHRONIZED(&bufLocker);
if (value < smallest || value > largest) { if (value < lowest || value > highest) {
buffer[head] = MAX_VAL; // Store MAX_VAL if value is out of range buffer[head] = NUMLIMIT_HIGH; // Store maximum buffer value if data value is out of range
} else { } else {
buffer[head] = static_cast<T>(std::round(value * mltplr)); buffer[head] = static_cast<T>(std::round(value * mltplr));
} }
@@ -136,11 +152,11 @@ double RingBuffer<T>::get(size_t index) const
{ {
GWSYNCHRONIZED(&bufLocker); GWSYNCHRONIZED(&bufLocker);
if (isEmpty() || index < 0 || index >= count) { if (isEmpty() || index < 0 || index >= count) {
return dblMAX_VAL; return BUFMAX_VAL;
} }
size_t realIndex = (first + index) % capacity; size_t realIndex = (first + index) % capacity;
return static_cast<double>(buffer[realIndex] / mltplr); return static_cast<double>(buffer[realIndex] / mltplr); // is BUFMAX_VAL if value is invalid
} }
// Operator[] for convenient access (same as get()) // Operator[] for convenient access (same as get())
@@ -155,7 +171,7 @@ template <typename T>
double RingBuffer<T>::getFirst() const double RingBuffer<T>::getFirst() const
{ {
if (isEmpty()) { if (isEmpty()) {
return dblMAX_VAL; return BUFMAX_VAL;
} }
return get(0); return get(0);
} }
@@ -165,7 +181,7 @@ template <typename T>
double RingBuffer<T>::getLast() const double RingBuffer<T>::getLast() const
{ {
if (isEmpty()) { if (isEmpty()) {
return dblMAX_VAL; return BUFMAX_VAL;
} }
return get(count - 1); return get(count - 1);
} }
@@ -174,19 +190,7 @@ double RingBuffer<T>::getLast() const
template <typename T> template <typename T>
double RingBuffer<T>::getMin() const double RingBuffer<T>::getMin() const
{ {
if (isEmpty()) { return getMin(getCurrentSize());
return dblMAX_VAL;
}
double minVal = dblMAX_VAL;
double value;
for (size_t i = 0; i < count; i++) {
value = get(i);
if (value < minVal && value != dblMAX_VAL) {
minVal = value;
}
}
return minVal;
} }
// Get minimum value of the last <amount> values of buffer // Get minimum value of the last <amount> values of buffer
@@ -194,16 +198,16 @@ template <typename T>
double RingBuffer<T>::getMin(size_t amount) const double RingBuffer<T>::getMin(size_t amount) const
{ {
if (isEmpty() || amount <= 0) { if (isEmpty() || amount <= 0) {
return dblMAX_VAL; return BUFMAX_VAL;
} }
if (amount > count) if (amount > count)
amount = count; amount = count;
double minVal = dblMAX_VAL; double minVal = BUFMAX_VAL;
double value; double value;
for (size_t i = 0; i < amount; i++) { for (size_t i = 0; i < amount; i++) {
value = get(count - 1 - i); value = get(count - 1 - i);
if (value < minVal && value != dblMAX_VAL) { if (value < minVal && value != BUFMAX_VAL) {
minVal = value; minVal = value;
} }
} }
@@ -214,22 +218,7 @@ double RingBuffer<T>::getMin(size_t amount) const
template <typename T> template <typename T>
double RingBuffer<T>::getMax() const double RingBuffer<T>::getMax() const
{ {
if (isEmpty()) { return getMax(getCurrentSize());
return dblMAX_VAL;
}
double maxVal = dblMIN_VAL;
double value;
for (size_t i = 0; i < count; i++) {
value = get(i);
if (value > maxVal && value != dblMAX_VAL) {
maxVal = value;
}
}
if (maxVal == dblMIN_VAL) { // no change of initial value -> buffer has only invalid values (MAX_VAL)
maxVal = dblMAX_VAL;
}
return maxVal;
} }
// Get maximum value of the last <amount> values of buffer // Get maximum value of the last <amount> values of buffer
@@ -237,21 +226,21 @@ template <typename T>
double RingBuffer<T>::getMax(size_t amount) const double RingBuffer<T>::getMax(size_t amount) const
{ {
if (isEmpty() || amount <= 0) { if (isEmpty() || amount <= 0) {
return dblMAX_VAL; return BUFMAX_VAL;
} }
if (amount > count) if (amount > count)
amount = count; amount = count;
double maxVal = dblMIN_VAL; double maxVal = BUFMIN_VAL;
double value; double value;
for (size_t i = 0; i < amount; i++) { for (size_t i = 0; i < amount; i++) {
value = get(count - 1 - i); value = get(count - 1 - i);
if (value > maxVal && value != dblMAX_VAL) { if (value > maxVal && value != BUFMAX_VAL) {
maxVal = value; maxVal = value;
} }
} }
if (maxVal == dblMIN_VAL) { // no change of initial value -> buffer has only invalid values (MAX_VAL) if (maxVal == BUFMIN_VAL) { // no change of initial value -> buffer has only invalid values (BUFMAX_VAL)
maxVal = dblMAX_VAL; maxVal = BUFMAX_VAL;
} }
return maxVal; return maxVal;
} }
@@ -260,11 +249,7 @@ double RingBuffer<T>::getMax(size_t amount) const
template <typename T> template <typename T>
double RingBuffer<T>::getMid() const double RingBuffer<T>::getMid() const
{ {
if (isEmpty()) { return getMid(getCurrentSize());
return dblMAX_VAL;
}
return (getMin() + getMax()) / 2;
} }
// Get mid value between <min> and <max> value of the last <amount> values of buffer // Get mid value between <min> and <max> value of the last <amount> values of buffer
@@ -272,7 +257,7 @@ template <typename T>
double RingBuffer<T>::getMid(size_t amount) const double RingBuffer<T>::getMid(size_t amount) const
{ {
if (isEmpty() || amount <= 0) { if (isEmpty() || amount <= 0) {
return dblMAX_VAL; return BUFMAX_VAL;
} }
if (amount > count) if (amount > count)
@@ -285,29 +270,7 @@ double RingBuffer<T>::getMid(size_t amount) const
template <typename T> template <typename T>
double RingBuffer<T>::getMedian() const double RingBuffer<T>::getMedian() const
{ {
if (isEmpty()) { return getMedian(getCurrentSize());
return dblMAX_VAL;
}
// Create a temporary vector with current valid elements
std::vector<T> temp;
temp.reserve(count);
for (size_t i = 0; i < count; i++) {
temp.push_back(get(i));
}
// Sort to find median
std::sort(temp.begin(), temp.end());
if (count % 2 == 1) {
// Odd number of elements
return static_cast<double>(temp[count / 2]);
} else {
// Even number of elements - return average of middle two
// Note: For integer types, this truncates. For floating point, it's exact.
return static_cast<double>((temp[count / 2 - 1] + temp[count / 2]) / 2);
}
} }
// Get the median value of the last <amount> values of buffer // Get the median value of the last <amount> values of buffer
@@ -315,13 +278,13 @@ template <typename T>
double RingBuffer<T>::getMedian(size_t amount) const double RingBuffer<T>::getMedian(size_t amount) const
{ {
if (isEmpty() || amount <= 0) { if (isEmpty() || amount <= 0) {
return dblMAX_VAL; return BUFMAX_VAL;
} }
if (amount > count) if (amount > count)
amount = count; amount = count;
// Create a temporary vector with current valid elements // Create a temporary vector with current valid elements
std::vector<T> temp; std::vector<double> temp;
temp.reserve(amount); temp.reserve(amount);
for (size_t i = 0; i < amount; i++) { for (size_t i = 0; i < amount; i++) {
@@ -331,16 +294,84 @@ double RingBuffer<T>::getMedian(size_t amount) const
// Sort to find median // Sort to find median
std::sort(temp.begin(), temp.end()); std::sort(temp.begin(), temp.end());
if (temp[0] == BUFMAX_VAL) { // 1st element of sorted vector is already BUFMAX_VAL -> only invalid entries in buffer, so we return invalid value
return BUFMAX_VAL;
}
if (amount % 2 == 1) { if (amount % 2 == 1) {
// Odd number of elements // Odd number of elements
return static_cast<double>(temp[amount / 2]); return temp[amount / 2];
} else { } else {
// Even number of elements - return average of middle two // Even number of elements - return average of middle two
// Note: For integer types, this truncates. For floating point, it's exact. return (temp[amount / 2 - 1] + temp[amount / 2]) / 2;
return static_cast<double>((temp[amount / 2 - 1] + temp[amount / 2]) / 2);
} }
} }
// Get mid value of circle (degree) values of buffer
template <typename T>
double RingBuffer<T>::getCircularMid() const
{
return getCircularMid(getCurrentSize());
}
// Get mid value of circle (degree) values of the last <amount> values of buffer
template <typename T>
double RingBuffer<T>::getCircularMid(size_t amount) const
{
if (isEmpty() || amount <= 0) {
return BUFMAX_VAL;
}
if (amount > count)
amount = count;
std::vector<double> a;
// Create a temporary vector with current valid elements
std::vector<double> temp;
temp.reserve(amount);
for (size_t i = 0; i < amount; i++) {
temp.push_back(get(count - 1 - i));
}
// Sort to find largest gap
std::sort(temp.begin(), temp.end());
if (temp[0] == BUFMAX_VAL) { // 1st element of sorted vector is already BUFMAX_VAL -> only invalid entries in buffer, so we return invalid value
return BUFMAX_VAL;
}
// Find the largest gap
double largestGap = BUFMIN_VAL;
std::size_t gapIndex = 0;
for (std::size_t i = 0; i < temp.size(); ++i)
{
std::size_t next = (i + 1) % temp.size();
double gap;
if (next == 0)
gap = (temp[0] + M_TWOPI) - temp[i];
else
gap = temp[next] - temp[i];
if (gap > largestGap)
{
largestGap = gap;
gapIndex = i;
}
}
double start = temp[(gapIndex + 1) % temp.size()]; // Start of occupied arc = first angle after largest gap
double arcWidth = M_TWOPI - largestGap; // Width of occupied arc
arcWidth = start + arcWidth / 2.0;
arcWidth = fmod(arcWidth, M_TWOPI);
if (arcWidth < 0.0) {
arcWidth += M_TWOPI;
}
return arcWidth; // Midpoint of occupied arc
}
// Get the buffer capacity (maximum size) // Get the buffer capacity (maximum size)
template <typename T> template <typename T>
size_t RingBuffer<T>::getCapacity() const size_t RingBuffer<T>::getCapacity() const
@@ -387,14 +418,14 @@ bool RingBuffer<T>::isFull() const
template <typename T> template <typename T>
double RingBuffer<T>::getMinVal() const double RingBuffer<T>::getMinVal() const
{ {
return dblMIN_VAL; return BUFMIN_VAL;
} }
// Get highest possible value for buffer; used for unset/invalid buffer data // Get highest possible value for buffer; used for unset/invalid buffer data
template <typename T> template <typename T>
double RingBuffer<T>::getMaxVal() const double RingBuffer<T>::getMaxVal() const
{ {
return dblMAX_VAL; return BUFMAX_VAL;
} }
// Clear buffer // Clear buffer
@@ -423,21 +454,14 @@ void RingBuffer<T>::resize(size_t newSize)
buffer.clear(); buffer.clear();
buffer.reserve(newSize); buffer.reserve(newSize);
buffer.resize(newSize, MAX_VAL); buffer.resize(newSize, NUMLIMIT_HIGH);
} }
// Get all current values in native buffer format as a vector // Get all current values in native buffer format as a vector
template <typename T> template <typename T>
std::vector<double> RingBuffer<T>::getAllValues() const std::vector<double> RingBuffer<T>::getAllValues() const
{ {
std::vector<double> result; return getAllValues(getCurrentSize());
result.reserve(count);
for (size_t i = 0; i < count; i++) {
result.push_back(get(i));
}
return result;
} }
// Get last <amount> values in native buffer format as a vector // Get last <amount> values in native buffer format as a vector
+29 -28
View File
@@ -66,8 +66,8 @@ void sensorTask(void *param){
const int avgsize = 300; const int avgsize = 300;
constexpr int arrayBatV{avgsize}; constexpr int arrayBatV{avgsize};
constexpr int arrayBatC{avgsize}; constexpr int arrayBatC{avgsize};
movingAvg batV(arrayBatV); movingAvg<int> batV(arrayBatV);
movingAvg batC(arrayBatC); movingAvg<int> batC(arrayBatC);
batV.begin(); batV.begin();
batC.begin(); batC.begin();
@@ -371,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;
@@ -432,17 +432,17 @@ void sensorTask(void *param){
iRTC RTC GPS N2K iRTC RTC GPS N2K
0 0 0 (1) 0 0 0 (1)
0 0 (1) (X) 0 0 (1) X
0 (1) 0 (X) 0 (1) 0 X
0 1 <-(1) (X) 0 1 <-(1) X
(1) 0 0 (X) (1) 0 0 X
1 0 (1) (X) 1 0 (1) X
1 ->(1) 0 (X) 1 ->(1) 0 X
1 1 <-(1) (X) 1 1 <-(1) X
*/ */
// If RTC DS1388 ready, then copy iRTC and GPS data to RTC all 1min // If RTC DS1388 ready, then copy iRTC and GPS data to RTC all 1 min
if(millis() > starttime11 + 1*60*1000){ if(millis() > starttime11 + 1*60*1000){
starttime11 = millis(); starttime11 = millis();
// Set RTC chip via iRTC (NTP) // Set RTC chip via iRTC (NTP)
@@ -475,7 +475,7 @@ void sensorTask(void *param){
// Adjust RTC time as unix time value // Adjust RTC time as unix time value
ds1388.adjust(adjusttime); ds1388.adjust(adjusttime);
} }
} }
} }
// Set RTC chip via N2K or 183 in case the internal GPS is off (only one time) // Set RTC chip via N2K or 183 in case the internal GPS is off (only one time)
@@ -524,7 +524,7 @@ 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();
if(!isnan(daysAt1970) && !isnan(sysTime)){ if(!isnan(daysAt1970) && !isnan(sysTime)){
//api->getLogger()->logDebug(GwLog::LOG,"RTC time: %04d/%02d/%02d %02d:%02d:%02d",sensors.rtcTime.tm_year+1900,sensors.rtcTime.tm_mon, sensors.rtcTime.tm_mday, sensors.rtcTime.tm_hour, sensors.rtcTime.tm_min, sensors.rtcTime.tm_sec); //api->getLogger()->logDebug(GwLog::LOG,"RTC time: %04d/%02d/%02d %02d:%02d:%02d",sensors.rtcTime.tm_year+1900,sensors.rtcTime.tm_mon+1, sensors.rtcTime.tm_mday, sensors.rtcTime.tm_hour, sensors.rtcTime.tm_min, sensors.rtcTime.tm_sec);
//api->getLogger()->logDebug(GwLog::LOG,"Send PGN126992: %10d %10d",daysAt1970, (uint16_t)sysTime); //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);
@@ -533,25 +533,26 @@ void sensorTask(void *param){
} }
// Send date and time from software RTC (iRTC) // Send date and time from software RTC (iRTC)
if (iRTC_ready == true && RTC_ready == false && GPS_ready == false) { if (iRTC_ready == true && RTC_ready == false && GPS_ready == false) {
// Use internal RTC feature sensors.rtcTime = rtc.getTimeStruct();
sensors.rtcTime = rtc.getTimeStruct(); // Save software RTC values in SensorData
// TODO implement daysAt1970 and sysTime as methods of DateTime
const short daysOfYear[12] = {0,31,59,90,120,151,181,212,243,273,304,334}; const short daysOfYear[12] = {0,31,59,90,120,151,181,212,243,273,304,334};
uint16_t switchYear = ((sensors.rtcTime.tm_year-1)-1968)/4 - ((sensors.rtcTime.tm_year-1)-1900)/100 + ((sensors.rtcTime.tm_year-1)-1600)/400; int year = sensors.rtcTime.tm_year + 1900;
long daysAt1970 = (sensors.rtcTime.tm_year-1970)*365 + switchYear + daysOfYear[sensors.rtcTime.tm_mon-1] + sensors.rtcTime.tm_mday-1; int month = sensors.rtcTime.tm_mon;
// If switch year then add one day int day = sensors.rtcTime.tm_mday;
if ((sensors.rtcTime.tm_mon > 2) && (sensors.rtcTime.tm_year % 4 == 0 && (sensors.rtcTime.tm_year % 100 != 0 || sensors.rtcTime.tm_year % 400 == 0))) { 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; daysAt1970 += 1;
} }
// N2K sysTime is double in n2klib double sysTime = sensors.rtcTime.tm_hour * 3600.0 + sensors.rtcTime.tm_min * 60.0 + sensors.rtcTime.tm_sec;
double sysTime = (sensors.rtcTime.tm_hour * 3600) + (sensors.rtcTime.tm_min * 60) + 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);
if(!isnan(daysAt1970) && !isnan(sysTime)){ //api->getLogger()->logDebug(GwLog::LOG,"Send PGN126992: %10d %10d",daysAt1970, (uint16_t)sysTime);
//api->getLogger()->logDebug(GwLog::LOG,"RTC time: %04d/%02d/%02d %02d:%02d:%02d",sensors.rtcTime.tm_year+1900,sensors.rtcTime.tm_mon, sensors.rtcTime.tm_mday, sensors.rtcTime.tm_hour, sensors.rtcTime.tm_min, sensors.rtcTime.tm_sec); SetN2kPGN126992(N2kMsg, 0, daysAt1970, sysTime, N2ktimes_LocalCrystalClock);
//api->getLogger()->logDebug(GwLog::LOG,"Send PGN126992: %10d %10d",daysAt1970, (uint16_t)sysTime); api->sendN2kMessage(N2kMsg);
SetN2kPGN126992(N2kMsg,0,daysAt1970,sysTime,N2ktimes_LocalCrystalClock);
api->sendN2kMessage(N2kMsg);
}
} }
} }
// Send 1Wire data for all temperature sensors to N2K all 2s // Send 1Wire data for all temperature sensors to N2K all 2s
+271 -194
View File
@@ -3,14 +3,6 @@
#include "OBPDataOperations.h" #include "OBPDataOperations.h"
#include "OBPRingBuffer.h" #include "OBPRingBuffer.h"
std::map<String, ChartProps> Chart::dfltChrtDta = {
{ "formatWind", { 60.0 * DEG_TO_RAD, 10.0 * DEG_TO_RAD } }, // default course range 60 degrees
{ "formatCourse", { 60.0 * DEG_TO_RAD, 10.0 * DEG_TO_RAD } }, // default course range 60 degrees
{ "formatKnots", { 7.71, 2.56 } }, // default speed range in m/s
{ "formatDepth", { 15.0, 5.0 } }, // default depth range in m
{ "kelvinToC", { 30.0, 5.0 } } // default temp range in °C/K
};
// --- Class Chart --------------- // --- Class Chart ---------------
// Chart - object holding the actual chart, incl. data buffer and format definition // Chart - object holding the actual chart, incl. data buffer and format definition
@@ -18,9 +10,8 @@ std::map<String, ChartProps> Chart::dfltChrtDta = {
// <dfltRng> default range of chart, e.g. 30 = [0..30] // <dfltRng> default range of chart, e.g. 30 = [0..30]
// <common> common program data; required for logger and color data // <common> common program data; required for logger and color data
// <useSimuData> flag to indicate if simulation data is active // <useSimuData> flag to indicate if simulation data is active
Chart::Chart(RingBuffer<uint16_t>& dataBuf, double dfltRng, CommonData& common, bool useSimuData) Chart::Chart(RingBuffer<uint16_t>& dataBuf, CommonData& common, bool useSimuData)
: dataBuf(dataBuf) : dataBuf(dataBuf)
, dfltRng(dfltRng)
, commonData(&common) , commonData(&common)
, useSimuData(useSimuData) , useSimuData(useSimuData)
{ {
@@ -28,30 +19,55 @@ Chart::Chart(RingBuffer<uint16_t>& dataBuf, double dfltRng, CommonData& common,
fgColor = commonData->fgcolor; fgColor = commonData->fgcolor;
bgColor = commonData->bgcolor; bgColor = commonData->bgcolor;
// display dimensions (avoid calling width()/height() on incomplete LGFX type)
#ifdef TFT_DISPLAY
dWidth = 480;
dHeight = 320;
#else
dWidth = getdisplay().width(); dWidth = getdisplay().width();
dHeight = getdisplay().height(); dHeight = getdisplay().height();
#endif
init();
};
Chart::~Chart()
{
}
bool Chart::init()
{
chrtMin = 0.0;
chrtMax = 0.0;
chrtMid = 0.0;
chrtRng = 0.0;
dataBuf.getMetaData(dbName, dbFormat); dataBuf.getMetaData(dbName, dbFormat);
dbMIN_VAL = dataBuf.getMinVal(); dbMIN_VAL = dataBuf.getMinVal();
dbMAX_VAL = dataBuf.getMaxVal(); dbMAX_VAL = dataBuf.getMaxVal();
bufSize = dataBuf.getCapacity(); bufSize = dataBuf.getCapacity();
// LOG_DEBUG(GwLog::DEBUG, "Chart Init: dbMIN_VAL: %.2f, dbMAX_VAL: %.2fd, bufSize: %d", dbMIN_VAL, dbMAX_VAL, bufSize);
// Initialize chart data format; shorter version of standard format indicator // Initialize chart data format; shorter version of standard format indicator
if (dbFormat == "formatCourse" || dbFormat == "formatWind" || dbFormat == "formatRot") { if (dbFormat == "formatCourse" || dbFormat == "formatWind") {
chrtDataFmt = WIND; // Chart is showing data of course / wind <degree> format chrtDataFmt = WIND; // Chart is showing data of course / wind <degree> format
} else if (dbFormat == "formatRot") { } else if (dbFormat == "formatRot") {
chrtDataFmt = ROTATION; // Chart is showing data of rotational <degree> format chrtDataFmt = ROTATION; // Chart is showing data of rotational <degree> format
} else if (dbFormat == "formatKnots") { } else if (dbFormat == "formatKnots") {
chrtDataFmt = SPEED; // Chart is showing data of speed or windspeed format chrtDataFmt = SPEED; // Chart is showing data of speed or windspeed format
} else if (dbFormat == "formatDepth") { } else if (dbFormat == "formatDepth") {
chrtDataFmt = DEPTH; // Chart ist showing data of <depth> format chrtDataFmt = DEPTH;
} else if (dbFormat == "kelvinToC") { } else if (dbFormat == "kelvinToC") {
chrtDataFmt = TEMPERATURE; // Chart ist showing data of <temp> format chrtDataFmt = TEMPERATURE;
} else if (dbFormat.startsWith("formatXdr:P")) {
chrtDataFmt = PRESSURE;
} else if (dbFormat.startsWith("formatXdr:H")) {
chrtDataFmt = HUMIDITY;
} else { } else {
chrtDataFmt = OTHER; // Chart is showing any other data format chrtDataFmt = OTHER; // Chart is showing any other data format
} }
// "0" value is the same for any data format but for user defined temperature format // "0" value is the same for any data format but for user defined temperature and pressure format
zeroValue = 0.0; zeroValue = 0.0;
if (chrtDataFmt == TEMPERATURE) { if (chrtDataFmt == TEMPERATURE) {
tempFormat = commonData->config->getString(commonData->config->tempFormat); // [K|°C|°F] tempFormat = commonData->config->getString(commonData->config->tempFormat); // [K|°C|°F]
@@ -62,6 +78,8 @@ Chart::Chart(RingBuffer<uint16_t>& dataBuf, double dfltRng, CommonData& common,
} else if (tempFormat == "F") { } else if (tempFormat == "F") {
zeroValue = 255.37; zeroValue = 255.37;
} }
} else if (chrtDataFmt == PRESSURE) {
zeroValue = 98000.0; // typical low pressure area value
} }
// Read default range and range step for this chart type // Read default range and range step for this chart type
@@ -69,7 +87,7 @@ Chart::Chart(RingBuffer<uint16_t>& dataBuf, double dfltRng, CommonData& common,
dfltRng = dfltChrtDta[dbFormat].range; dfltRng = dfltChrtDta[dbFormat].range;
rngStep = dfltChrtDta[dbFormat].step; rngStep = dfltChrtDta[dbFormat].step;
} else { } else {
dfltRng = 15.0; dfltRng = 10.0;
rngStep = 5.0; rngStep = 5.0;
} }
@@ -78,14 +96,19 @@ Chart::Chart(RingBuffer<uint16_t>& dataBuf, double dfltRng, CommonData& common,
chrtMax = chrtMin + dfltRng; chrtMax = chrtMin + dfltRng;
chrtMid = (chrtMin + chrtMax) / 2; chrtMid = (chrtMin + chrtMax) / 2;
chrtRng = dfltRng; chrtRng = dfltRng;
recalcRngMid = true; // initialize <chrtMid> and chart borders on first screen call recalcRngMid = true; // initialize <chrtMid> and chart borders on first chart display call
allLeft = true;
allRight = true;
LOG_DEBUG(GwLog::DEBUG, "Chart Init: dWidth: %d, dHeight: %d, timAxis: %d, valAxis: %d, cRoot {x,y}: %d, %d, dbname: %s, rngStep: %.4f, chrtDataFmt: %d", if (dbFormat.isEmpty()) { // data buffer may not exist yet, because boat data object is not available yet
dWidth, dHeight, timAxis, valAxis, cRoot.x, cRoot.y, dbName, rngStep, chrtDataFmt); initValid = false; // chart object will get invalid data during initialization
}; } else {
initValid = true;
}
LOG_DEBUG(GwLog::DEBUG, "Chart Init: dWidth: %d, dHeight: %d, timAxis: %d, valAxis: %d, cRoot {x,y}: %d, %d, dbname: %s, rngStep: %.4f, chrtDataFmt: %d, initValid: %d",
dWidth, dHeight, timAxis, valAxis, cRoot.x, cRoot.y, dbName, rngStep, chrtDataFmt, initValid);
Chart::~Chart() return isValid();
{
} }
// Perform all actions to draw chart // Perform all actions to draw chart
@@ -95,7 +118,7 @@ Chart::~Chart()
// <prntName>; print data name on horizontal half chart [true|false] // <prntName>; print data name on horizontal half chart [true|false]
// <showCurrValue>: print current boat data value [true|false] // <showCurrValue>: print current boat data value [true|false]
// <currValue>: current boat data value; used only for test on valid data // <currValue>: current boat data value; used only for test on valid data
void Chart::showChrt(char chrtDir, int8_t chrtSz, const int8_t chrtIntv, bool prntName, bool showCurrValue, GwApi::BoatValue currValue) void Chart::showChrt(const ChrtDir chrtDir, ChrtSize chrtSz, const int8_t chrtIntv, bool prntName, bool showCurrValue, GwApi::BoatValue currValue)
{ {
if (!setChartDimensions(chrtDir, chrtSz)) { if (!setChartDimensions(chrtDir, chrtSz)) {
return; // wrong chart dimension parameters return; // wrong chart dimension parameters
@@ -110,73 +133,83 @@ void Chart::showChrt(char chrtDir, int8_t chrtSz, const int8_t chrtIntv, bool pr
return; return;
} }
if (showCurrValue) { // show latest value from history buffer; this should be the most current one if (showCurrValue) {
currValue.value = dataBuf.getLast(); currValue.value = dataBuf.getLast(); // show latest value from history buffer; this should be the most current one
currValue.valid = currValue.value != dbMAX_VAL; currValue.valid = currValue.value != dbMAX_VAL;
prntCurrValue(chrtDir, currValue); prntCurrValue(chrtDir, currValue);
} }
} }
// define dimensions and start points for chart // define dimensions and start points for chart
bool Chart::setChartDimensions(const char direction, const int8_t size) bool Chart::setChartDimensions(const ChrtDir chrtDir, const ChrtSize chrtSz)
{ {
if ((direction != HORIZONTAL && direction != VERTICAL) || (size < 0 || size > 2)) { if ((chrtDir != HORIZONTAL && chrtDir != VERTICAL) || (chrtSz < 0 || chrtSz > 3)) {
LOG_DEBUG(GwLog::ERROR, "obp60:setChartDimensions %s: wrong parameters", dataBuf.getName()); LOG_DEBUG(GwLog::ERROR, "obp60:setChartDimensions %s: wrong parameters", dataBuf.getName());
return false; return false;
} }
if (direction == HORIZONTAL) { if (chrtDir == HORIZONTAL) {
// horizontal chart timeline direction // horizontal chart timeline direction
timAxis = dWidth - 1; timAxis = dWidth - 1;
switch (size) { switch (chrtSz) {
case 0: case ChrtSize::FULL_SIZE:
valAxis = dHeight - top - bottom; valAxis = dHeight - top - bottom;
cRoot = { 0, top - 1 }; cRoot = { 0, top - 1 };
break; break;
case 1: case HALF_SIZE_LEFT_TOP:
valAxis = (dHeight - top - bottom) / 2 - hGap; valAxis = (dHeight - top - bottom) / 2 - hGap;
cRoot = { 0, top - 1 }; cRoot = { 0, top - 1 };
break; break;
case 2: case HALF_SIZE_RIGHT_BOTTOM:
valAxis = (dHeight - top - bottom) / 2 - hGap; valAxis = (dHeight - top - bottom) / 2 - hGap;
cRoot = { 0, top + (valAxis + hGap) + hGap - 1 }; cRoot = { 0, top + (valAxis + hGap) + hGap - 1 };
break; break;
case TWO_THIRD_TOP:
valAxis = (dHeight - top - bottom) * 0.667 - hGap;
cRoot = { 0, top - 1 };
break;
default: // same as case 0; should never happen
valAxis = dHeight - top - bottom;
cRoot = { 0, top - 1 };
} }
} else if (direction == VERTICAL) { } else {
// vertical chart timeline direction // vertical chart timeline direction
timAxis = dHeight - top - bottom; timAxis = dHeight - top - bottom;
switch (size) { switch (chrtSz) {
case 0: case FULL_SIZE:
valAxis = dWidth - 1; valAxis = dWidth - 1;
cRoot = { 0, top - 1 }; cRoot = { 0, top - 1 };
break; break;
case 1: case HALF_SIZE_LEFT_TOP:
valAxis = dWidth / 2 - vGap; valAxis = dWidth / 2 - vGap;
cRoot = { 0, top - 1 }; cRoot = { 0, top - 1 };
break; break;
case 2: case HALF_SIZE_RIGHT_BOTTOM:
valAxis = dWidth / 2 - vGap; valAxis = dWidth / 2 - vGap;
cRoot = { dWidth / 2 + vGap - 1, top - 1 }; cRoot = { dWidth / 2 + vGap, top - 1 };
break; break;
default: // same as case 0; should never happen
valAxis = dWidth - 1;
cRoot = { 0, top - 1 };
} }
} }
//LOG_DEBUG(GwLog::DEBUG, "obp60:setChartDimensions %s: direction: %c, size: %d, dWidth: %d, dHeight: %d, timAxis: %d, valAxis: %d, cRoot{%d, %d}, top: %d, bottom: %d, hGap: %d, vGap: %d", // LOG_DEBUG(GwLog::DEBUG, "obp60:setChartDimensions %s: chrtDir: %c, size: %d, dWidth: %d, dHeight: %d, timAxis: %d, valAxis: %d, cRoot{%d, %d}, top: %d, bottom: %d, hGap: %d, vGap: %d",
// dataBuf.getName(), direction, size, dWidth, dHeight, timAxis, valAxis, cRoot.x, cRoot.y, top, bottom, hGap, vGap); // dataBuf.getName(), chrtDir, size, dWidth, dHeight, timAxis, valAxis, cRoot.x, cRoot.y, top, bottom, hGap, vGap);
return true; return true;
} }
// draw chart // draw chart
void Chart::drawChrt(const char chrtDir, const int8_t chrtIntv, GwApi::BoatValue& currValue) void Chart::drawChrt(const ChrtDir chrtDir, const int8_t chrtIntv, GwApi::BoatValue& currValue)
{ {
double chrtScale; // Scale for data values in pixels per value double chrtScale; // Scale for data values in pixels per value
getBufferStartNSize(chrtIntv); getBufferStartNSize(chrtIntv);
// LOG_DEBUG(GwLog::DEBUG, "Chart:drawChart: min: %.1f, mid: %.1f, max: %.1f, rng: %.1f", chrtMin, chrtMid, chrtMax, chrtRng); // LOG_DEBUG(GwLog::DEBUG, "Chart:drawChart: min: %.1f, mid: %.1f, max: %.1f, rng: %.1f", chrtMin, chrtMid, chrtMax, chrtRng);
calcChrtBorders(chrtMin, chrtMid, chrtMax, chrtRng); calcChrtBorders(chrtMin, chrtMid, chrtMax, chrtRng);
chrtScale = double(valAxis) / chrtRng; // Chart scale: pixels per value step chrtScale = double(valAxis) / chrtRng; // Chart scale: pixels per value step
// LOG_DEBUG(GwLog::DEBUG, "Chart:drawChart: min: %.1f, mid: %.1f, max: %.1f, rng: %.1f", chrtMin, chrtMid, chrtMax, chrtRng); // LOG_DEBUG(GwLog::DEBUG, "Chart:drawChart: min: %.1f, mid: %.1f, max: %.1f, rng: %.1f, data valid: %d", chrtMin, chrtMid, chrtMax, chrtRng, currValue.valid);
// Do we have valid buffer data? // Do we have valid buffer data?
if (dataBuf.getMax() == dbMAX_VAL) { // only <MAX_VAL> values in buffer -> no valid wind data available if (dataBuf.getMax() == dbMAX_VAL) { // only <MAX_VAL> values in buffer -> no valid wind data available
@@ -191,7 +224,7 @@ void Chart::drawChrt(const char chrtDir, const int8_t chrtIntv, GwApi::BoatValue
numNoData++; numNoData++;
bufDataValid = true; bufDataValid = true;
if (numNoData > THRESHOLD_NO_DATA) { // If more than 4 invalid values in a row, flag for invalid data if (numNoData > THRESHOLD_NO_DATA * (dataBuf.getUpdFreq() / 1000)) { // If more than <THRESHOLD> invalid values in a row, flag for invalid data
bufDataValid = false; bufDataValid = false;
return; return;
} }
@@ -206,17 +239,15 @@ void Chart::getBufferStartNSize(const int8_t chrtIntv)
count = dataBuf.getCurrentSize(); count = dataBuf.getCurrentSize();
currIdx = dataBuf.getLastIdx(); currIdx = dataBuf.getLastIdx();
numAddedBufVals = (currIdx - lastAddedIdx + bufSize) % bufSize; // Number of values added to buffer since last display numAddedBufVals = (currIdx - lastAddedIdx + bufSize) % bufSize; // Number of values added to buffer since last display
lastAddedIdx = currIdx;
if (chrtIntv != oldChrtIntv || count == 1) { if (chrtIntv != oldChrtIntv || count == 1) { // new data interval selected by user
// new data interval selected by user; this is only x * 230 values instead of 240 seconds (4 minutes) per interval step
numBufVals = min(count, (timAxis - MIN_FREE_VALUES) * chrtIntv); // keep free or release MIN_FREE_VALUES on chart for plotting of new values numBufVals = min(count, (timAxis - MIN_FREE_VALUES) * chrtIntv); // keep free or release MIN_FREE_VALUES on chart for plotting of new values
bufStart = max(0, count - numBufVals); bufStart = max(0, count - numBufVals);
lastAddedIdx = currIdx;
oldChrtIntv = chrtIntv; oldChrtIntv = chrtIntv;
} else { } else {
numBufVals = numBufVals + numAddedBufVals; numBufVals = numBufVals + numAddedBufVals;
lastAddedIdx = currIdx;
if (count == bufSize) { if (count == bufSize) {
bufStart = max(0, bufStart - numAddedBufVals); bufStart = max(0, bufStart - numAddedBufVals);
} }
@@ -228,6 +259,7 @@ void Chart::calcChrtBorders(double& rngMin, double& rngMid, double& rngMax, doub
{ {
if (chrtDataFmt == WIND || chrtDataFmt == ROTATION) { if (chrtDataFmt == WIND || chrtDataFmt == ROTATION) {
// calculate rngMid
if (chrtDataFmt == ROTATION) { if (chrtDataFmt == ROTATION) {
// if chart data is of type 'rotation', we want to have <rndMid> always to be '0' // if chart data is of type 'rotation', we want to have <rndMid> always to be '0'
rngMid = 0; rngMid = 0;
@@ -242,22 +274,12 @@ void Chart::calcChrtBorders(double& rngMin, double& rngMid, double& rngMax, doub
if (recalcRngMid) { if (recalcRngMid) {
// Set rngMid // Set rngMid
rngMid = dataBuf.getMid(numBufVals); rngMid = dataBuf.getCircularMid(numBufVals);
if (rngMid == dbMAX_VAL) { if (rngMid == dbMAX_VAL) {
rngMid = 0; rngMid = 0;
} else { } else {
rngMid = std::round(rngMid / rngStep) * rngStep; // Set new center value; round to next <rngStep> value rngMid = std::round(rngMid / rngStep) * rngStep; // round new center value to next <rngStep> value
// Check if range between 'min' and 'max' is > 180° or crosses '0'
rngMin = dataBuf.getMin(numBufVals);
rngMax = dataBuf.getMax(numBufVals);
rng = (rngMax >= rngMin ? rngMax - rngMin : M_TWOPI - rngMin + rngMax);
rng = std::max(rng, dfltRng); // keep at least default chart range
if (rng > M_PI) { // If wind range > 180°, adjust wndCenter to smaller wind range end
rngMid = WindUtils::to2PI(rngMid + M_PI);
}
} }
recalcRngMid = false; // Reset flag for <rngMid> determination recalcRngMid = false; // Reset flag for <rngMid> determination
@@ -268,16 +290,11 @@ void Chart::calcChrtBorders(double& rngMin, double& rngMid, double& rngMax, doub
// check and adjust range between left, mid, and right chart limit // check and adjust range between left, mid, and right chart limit
double halfRng = rng / 2.0; // we calculate with range between <rngMid> and edges double halfRng = rng / 2.0; // we calculate with range between <rngMid> and edges
double tmpRng = getAngleRng(rngMid, numBufVals); double tmpRng = getCircularRng(rngMid, numBufVals);
tmpRng = (tmpRng == dbMAX_VAL ? 0 : std::ceil(tmpRng / rngStep) * rngStep); tmpRng = (tmpRng == dbMAX_VAL ? 0 : std::ceil(tmpRng / rngStep) * rngStep);
// LOG_DEBUG(GwLog::DEBUG, "calcChrtBorders: tmpRng: %.1f°, halfRng: %.1f°", tmpRng * RAD_TO_DEG, halfRng * RAD_TO_DEG);
if (tmpRng > halfRng) { // expand chart range to new value if (tmpRng > halfRng) { // expand chart range to new value
halfRng = tmpRng; halfRng = tmpRng;
} } else if (tmpRng + rngStep < halfRng) { // Contract chart range for higher resolution if possible
else if (tmpRng + rngStep < halfRng) { // Contract chart range for higher resolution if possible
halfRng = std::max(dfltRng / 2.0, tmpRng); halfRng = std::max(dfltRng / 2.0, tmpRng);
} }
@@ -286,7 +303,6 @@ void Chart::calcChrtBorders(double& rngMin, double& rngMid, double& rngMax, doub
rngMax = WindUtils::to2PI(rngMax); rngMax = WindUtils::to2PI(rngMax);
rng = halfRng * 2.0; rng = halfRng * 2.0;
// LOG_DEBUG(GwLog::DEBUG, "calcChrtBorders: rngMin: %.1f°, rngMid: %.1f°, rngMax: %.1f°, tmpRng: %.1f°, rng: %.1f°, rngStep: %.1f°", rngMin * RAD_TO_DEG, rngMid * RAD_TO_DEG, rngMax * RAD_TO_DEG, // LOG_DEBUG(GwLog::DEBUG, "calcChrtBorders: rngMin: %.1f°, rngMid: %.1f°, rngMax: %.1f°, tmpRng: %.1f°, rng: %.1f°, rngStep: %.1f°", rngMin * RAD_TO_DEG, rngMid * RAD_TO_DEG, rngMax * RAD_TO_DEG,
// tmpRng * RAD_TO_DEG, rng * RAD_TO_DEG, rngStep * RAD_TO_DEG); // tmpRng * RAD_TO_DEG, rng * RAD_TO_DEG, rngStep * RAD_TO_DEG);
@@ -295,8 +311,15 @@ void Chart::calcChrtBorders(double& rngMin, double& rngMid, double& rngMax, doub
double currMinVal = dataBuf.getMin(numBufVals); double currMinVal = dataBuf.getMin(numBufVals);
double currMaxVal = dataBuf.getMax(numBufVals); double currMaxVal = dataBuf.getMax(numBufVals);
if (currMinVal == dbMAX_VAL || currMaxVal == dbMAX_VAL) { if (currMinVal == dbMAX_VAL || currMaxVal == dbMAX_VAL) { // no valid data
return; // no valid data auto chkIsNaN = [](double& val) { if (std::isnan(val)) val = 0.0; }; // define inline function
// range values can be undefined if boat data type has not been created at time of chart printing (e.g. XDR data types)
// we set them to "0.0" then
chkIsNaN(rngMin);
chkIsNaN(rngMid);
chkIsNaN(rngMax);
chkIsNaN(rng);
return;
} }
// check if current chart border have to be adjusted // check if current chart border have to be adjusted
@@ -319,14 +342,13 @@ void Chart::calcChrtBorders(double& rngMin, double& rngMid, double& rngMax, doub
rngMid = (rngMin + rngMax) / 2.0; rngMid = (rngMin + rngMax) / 2.0;
rng = rngMax - rngMin; rng = rngMax - rngMin;
// LOG_DEBUG(GwLog::DEBUG, "calcChrtRange-end: currMinVal: %.1f, currMaxVal: %.1f, rngMin: %.1f, rngMid: %.1f, rngMax: %.1f, rng: %.1f, rngStep: %.1f, zeroValue: %.1f, dbMIN_VAL: %.1f", // LOG_DEBUG(GwLog::DEBUG, "calcChrtRange-end: currMinVal: %.1f, currMaxVal: %.1f, rngMin: %.1f, rngMid: %.1f, rngMax: %.1f, rng: %.1f, rngStep: %.1f, zeroValue: %.1f, dbMIN_VAL: %.1f",
// currMinVal, currMaxVal, rngMin, rngMid, rngMax, rng, rngStep, zeroValue, dbMIN_VAL); // currMinVal, currMaxVal, rngMin, rngMid, rngMax, rng, rngStep, zeroValue, dbMIN_VAL);
} }
} }
// Draw chart graph // Draw chart graph
void Chart::drawChartLines(const char direction, const int8_t chrtIntv, const double chrtScale) void Chart::drawChartLines(const ChrtDir chrtDir, const int8_t chrtIntv, const double chrtScale)
{ {
double chrtVal; // Current data value double chrtVal; // Current data value
Pos point, prevPoint; // current and previous chart point Pos point, prevPoint; // current and previous chart point
@@ -339,14 +361,12 @@ void Chart::drawChartLines(const char direction, const int8_t chrtIntv, const do
chrtPrevVal = dbMAX_VAL; chrtPrevVal = dbMAX_VAL;
} else { } else {
point = setCurrentChartPoint(i, direction, chrtVal, chrtScale); point = setChartPoint(i, chrtDir, chrtVal, chrtScale);
// if (i >= (numBufVals / chrtIntv) - 5) // log chart data of x lines (adjust for test purposes)
// if (i >= (numBufVals / chrtIntv) - 5) // log chart data of 1 line (adjust for test purposes) // LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Chart: i: %d, chrtVal: %.2f, chrtMin: %.2f, {x,y} {%d,%d}", i, chrtVal, chrtMin, point.x, point.y);
// LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Chart: i: %d, chrtVal: %.2f, chrtMin: %.2f, {x,y} {%d,%d}", i, chrtVal, chrtMin, x, y);
if ((i == 0) || (chrtPrevVal == dbMAX_VAL)) { if ((i == 0) || (chrtPrevVal == dbMAX_VAL)) {
// just a dot for 1st chart point or after some invalid values prevPoint = point; // just a dot for 1st chart point or after some invalid values
prevPoint = point;
} else if (chrtDataFmt == WIND || chrtDataFmt == ROTATION) { } else if (chrtDataFmt == WIND || chrtDataFmt == ROTATION) {
// cross borders check for degree values; shift values to [-PI..0..PI]; when crossing borders, range is 2x PI degrees // cross borders check for degree values; shift values to [-PI..0..PI]; when crossing borders, range is 2x PI degrees
@@ -360,7 +380,7 @@ void Chart::drawChartLines(const char direction, const int8_t chrtIntv, const do
// LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Chart: crossedBorders: %d, chrtVal: %.2f, chrtPrevVal: %.2f", crossedBorders, chrtVal, chrtPrevVal); // LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Chart: crossedBorders: %d, chrtVal: %.2f, chrtPrevVal: %.2f", crossedBorders, chrtVal, chrtPrevVal);
bool wrappingFromHighToLow = normCurrVal < normPrevVal; // Determine which edge we're crossing bool wrappingFromHighToLow = normCurrVal < normPrevVal; // Determine which edge we're crossing
if (direction == HORIZONTAL) { if (chrtDir == HORIZONTAL) {
int ySplit = wrappingFromHighToLow ? (cRoot.y + valAxis) : cRoot.y; int ySplit = wrappingFromHighToLow ? (cRoot.y + valAxis) : cRoot.y;
drawBoldLine(prevPoint.x, prevPoint.y, point.x, ySplit); drawBoldLine(prevPoint.x, prevPoint.y, point.x, ySplit);
prevPoint.y = wrappingFromHighToLow ? cRoot.y : (cRoot.y + valAxis); prevPoint.y = wrappingFromHighToLow ? cRoot.y : (cRoot.y + valAxis);
@@ -371,10 +391,19 @@ void Chart::drawChartLines(const char direction, const int8_t chrtIntv, const do
prevPoint.x = wrappingFromHighToLow ? cRoot.x : (cRoot.x + valAxis); prevPoint.x = wrappingFromHighToLow ? cRoot.x : (cRoot.x + valAxis);
} }
} }
// test position of chart point against chart middle; ignore first values which we don't show next time anyway
if (i > MIN_FREE_VALUES) {
double d = WindUtils::toPI(chrtVal - chrtMid);
if (d > 0) // point is right of mid point
allLeft = false;
if (d < 0) // point is left of mid point
allRight = false;
}
} }
if (chrtDataFmt == DEPTH) { if (chrtDataFmt == DEPTH) {
if (direction == HORIZONTAL) { // horizontal chart if (chrtDir == HORIZONTAL) { // horizontal chart
drawBoldLine(point.x, point.y, point.x, cRoot.y + valAxis); drawBoldLine(point.x, point.y, point.x, cRoot.y + valAxis);
} else { // vertical chart } else { // vertical chart
drawBoldLine(point.x, point.y, cRoot.x + valAxis, point.y); drawBoldLine(point.x, point.y, cRoot.x + valAxis, point.y);
@@ -391,31 +420,36 @@ void Chart::drawChartLines(const char direction, const int8_t chrtIntv, const do
if (i >= timAxis - 1) { if (i >= timAxis - 1) {
oldChrtIntv = 0; // force reset of buffer start and number of values to show in next display loop oldChrtIntv = 0; // force reset of buffer start and number of values to show in next display loop
if (chrtDataFmt == WIND) { // degree of course or wind if (chrtDataFmt == WIND) {
recalcRngMid = true; if (allLeft || allRight || chrtRng == M_TWOPI) { // check if we should recalculate chart axis middle value
LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: chart end: timAxis: %d, i: %d, bufStart: %d, numBufVals: %d, recalcRngCntr: %d", timAxis, i, bufStart, numBufVals, recalcRngMid); recalcRngMid = true;
}
// LOG_DEBUG(GwLog::DEBUG, "OBPcharts: WIND chart end: timAxis: %d, i: %d, bufStart: %d, numBufVals: %d, recalcRngCntr: %d, allLeft: %d, allRight: %d", timAxis, i, bufStart,
// numBufVals, recalcRngMid, allLeft, allRight);
allLeft = true; // reset marker for all left/right chart points
allRight = true;
} }
break; break;
} }
taskYIELD(); // we run for 50-150ms; be polite to other tasks with same priority taskYIELD(); // we run for 50-150ms, so we want to be polite to other tasks with same priority
} }
} }
// Set current chart point to draw // Set current chart point to draw
Pos Chart::setCurrentChartPoint(const int i, const char direction, const double chrtVal, const double chrtScale) Pos Chart::setChartPoint(const int i, const ChrtDir chrtDir, const double chrtVal, const double chrtScale)
{ {
Pos currentPoint; Pos currentPoint;
if (direction == HORIZONTAL) { if (chrtDir == HORIZONTAL) {
currentPoint.x = cRoot.x + i; // Position in chart area currentPoint.x = cRoot.x + i; // Position in chart area
if (chrtDataFmt == WIND || chrtDataFmt == ROTATION) { // degree type value if (chrtDataFmt == WIND || chrtDataFmt == ROTATION) { // degree type value
currentPoint.y = cRoot.y + static_cast<int>((WindUtils::to2PI(chrtVal - chrtMin) * chrtScale) + 0.5); // calculate chart point and round currentPoint.y = cRoot.y + static_cast<int>((WindUtils::to2PI(chrtVal - chrtMin) * chrtScale) + 0.5); // calculate chart point and round
} else if (chrtDataFmt == SPEED or chrtDataFmt == TEMPERATURE) { // speed or temperature data format -> print low values at bottom } else if (chrtDataFmt == WIND || chrtDataFmt == ROTATION || chrtDataFmt == DEPTH) { // print low values at bottom
currentPoint.y = cRoot.y + valAxis - static_cast<int>(((chrtVal - chrtMin) * chrtScale) + 0.5); // calculate chart point and round
} else { // any other data format
currentPoint.y = cRoot.y + static_cast<int>(((chrtVal - chrtMin) * chrtScale) + 0.5); // calculate chart point and round currentPoint.y = cRoot.y + static_cast<int>(((chrtVal - chrtMin) * chrtScale) + 0.5); // calculate chart point and round
} else { // any other data format
currentPoint.y = cRoot.y + valAxis - static_cast<int>(((chrtVal - chrtMin) * chrtScale) + 0.5); // calculate chart point and round
} }
} else { // vertical chart } else { // vertical chart
@@ -432,58 +466,60 @@ Pos Chart::setCurrentChartPoint(const int i, const char direction, const double
} }
// chart time axis label + lines // chart time axis label + lines
void Chart::drawChrtTimeAxis(const char chrtDir, const int8_t chrtSz, const int8_t chrtIntv) void Chart::drawChrtTimeAxis(const ChrtDir chrtDir, const ChrtSize chrtSz, const int8_t chrtIntv)
{ {
float axSlots, intv, i; int axSlots, intv, i, timeRng;
char sTime[6]; char sTime[6];
int timeRng = chrtIntv * 4; // chart time interval: [1] 4 min., [2] 8 min., [3] 12 min., [4] 16 min., [8] 32 min. int tOffset;
getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setTextColor(fgColor); getdisplay().setTextColor(fgColor);
axSlots = 5; // number of axis labels intv = 60; // print axis label each 60 pixels = seconds
intv = timAxis / (axSlots - 1); // minutes per chart axis interval (interval is 1 less than axSlots) axSlots = timAxis / intv; // number of axis labels; value will be truncated to full number
i = timeRng; // Chart axis label start at -32, -16, -12, ... minutes
if (chrtDir == HORIZONTAL) { if (chrtDir == HORIZONTAL) {
getdisplay().fillRect(0, cRoot.y, dWidth, 2, fgColor); getdisplay().fillRect(0, cRoot.y, dWidth, 2, fgColor);
for (float j = 0; j < timAxis - 1; j += intv) { // fill time axis with values but keep area free on right hand side for value label i = axSlots * chrtIntv * -1; // current minute label
// LOG_DEBUG(GwLog::DEBUG, "Chart::drawChrtTimeAxis: intv: %d, axSlots: %d, timAxis: %d, chrtIntv: %d", intv, axSlots, timAxis, chrtIntv);
for (int j = intv; j < timAxis - 1; j += intv) { // fill time axis with values but keep area free on right hand side for value label
// draw text with appropriate offset // draw text with appropriate offset
int tOffset = j == 0 ? 13 : -4; tOffset = j == 0 ? 13 : -4;
snprintf(sTime, sizeof(sTime), "-%.0f", i); snprintf(sTime, sizeof(sTime), "%d", i);
drawTextCenter(cRoot.x + j + tOffset, cRoot.y - 8, sTime); drawTextCenter(cRoot.x + j + tOffset, cRoot.y - 8, sTime);
getdisplay().drawLine(cRoot.x + j, cRoot.y, cRoot.x + j, cRoot.y + 5, fgColor); // draw short vertical time mark getdisplay().drawLine(cRoot.x + j, cRoot.y, cRoot.x + j, cRoot.y + 5, fgColor); // draw short vertical time mark
i -= chrtIntv; i += chrtIntv;
} }
} else { // vertical chart } else { // vertical chart
i = chrtIntv * -1; // current minute label
for (float j = intv; j < timAxis - 1; j += intv) { // don't print time label at upper and lower end of time axis for (float j = intv; j < timAxis - 1; j += intv) { // don't print time label at upper and lower end of time axis
i -= chrtIntv; // we start not at top chart position snprintf(sTime, sizeof(sTime), "%d", i);
snprintf(sTime, sizeof(sTime), "-%.0f", i); getdisplay().drawLine(cRoot.x, cRoot.y + j, cRoot.x + valAxis - 2, cRoot.y + j, fgColor); // Grid line
getdisplay().drawLine(cRoot.x, cRoot.y + j, cRoot.x + valAxis, cRoot.y + j, fgColor); // Grid line
if (chrtSz == FULL_SIZE) { // full size chart if (chrtSz == FULL_SIZE) { // full size chart
getdisplay().fillRect(0, cRoot.y + j - 9, 32, 15, bgColor); // clear small area to remove potential chart lines getdisplay().fillRect(0, cRoot.y + j - 9, 32, 15, bgColor); // clear small area to remove potential chart lines
getdisplay().setCursor((4 - strlen(sTime)) * 7, cRoot.y + j + 3); // time value; print left screen; value right-formated getdisplay().setCursor((4 - strlen(sTime)) * 7, cRoot.y + j + 3); // time value; print left screen; value right-formated
getdisplay().printf("%s", sTime); // Range value getdisplay().printf("%s", sTime); // time value
} else if (chrtSz == HALF_SIZE_RIGHT) { // half size chart; right side } else if (chrtSz == HALF_SIZE_RIGHT_BOTTOM) { // half size chart; right side
drawTextCenter(dWidth / 2, cRoot.y + j, sTime); // time value; print mid screen drawTextCenter(dWidth / 2, cRoot.y + j, sTime); // time value; print mid screen
} }
i -= chrtIntv;
} }
} }
} }
// chart value axis labels + lines // chart value axis labels + lines
void Chart::drawChrtValAxis(const char chrtDir, const int8_t chrtSz, bool prntName) void Chart::drawChrtValAxis(const ChrtDir chrtDir, const ChrtSize chrtSz, const bool prntName)
{ {
const GFXfont* font; const GFXfont* font;
constexpr bool NO_LABEL = false; // constexpr bool NO_LABEL = false;
constexpr bool LABEL = true; // constexpr bool LABEL = true;
getdisplay().setTextColor(fgColor); getdisplay().setTextColor(fgColor);
@@ -491,29 +527,33 @@ void Chart::drawChrtValAxis(const char chrtDir, const int8_t chrtSz, bool prntNa
if (chrtSz == FULL_SIZE) { if (chrtSz == FULL_SIZE) {
font = &Ubuntu_Bold12pt8b; if (prntName) {
// print buffer data name on left hand side of time axis (max. size 5 characters)
// print buffer data name on right hand side of time axis (max. size 5 characters) font = &Ubuntu_Bold12pt8b;
getdisplay().setFont(font); getdisplay().setFont(font);
drawTextRalign(cRoot.x + timAxis, cRoot.y - 3, dbName.substring(0, 5)); getdisplay().fillRect(cRoot.x + timAxis - 57, cRoot.y + 2, 58, 20, bgColor); // clear small area to remove potential chart lines
String name = xdrDelete(dbName); // Value name
drawTextRalign(cRoot.x + timAxis - 1, cRoot.y + 19, name.substring(0, 5));
}
if (chrtDataFmt == WIND) { if (chrtDataFmt == WIND) {
prntHorizChartThreeValueAxisLabel(font); prntHorizChartThreeValueAxisLabel(font);
return; return;
} }
// for any other data formats print multiple axis value lines on full charts // for any other data formats print multiple axis value lines on full charts
font = &Ubuntu_Bold10pt8b;
prntHorizChartMultiValueAxisLabel(font); prntHorizChartMultiValueAxisLabel(font);
return; return;
} else { // half size chart -> just print edge values + middle chart line } else { // half size chart -> just print edge values + middle chart line
font = &Ubuntu_Bold10pt8b; font = &Ubuntu_Bold10pt8b;
if (prntName) { if (prntName) {
// print buffer data name on right hand side of time axis (max. size 5 characters) // print buffer data name on right hand side of time axis (max. size 6 characters)
getdisplay().setFont(font); getdisplay().setFont(font);
drawTextRalign(cRoot.x + timAxis, cRoot.y - 3, dbName.substring(0, 5)); getdisplay().fillRect(cRoot.x + timAxis - 57, cRoot.y + 2, 58, 16, bgColor); // clear small area to remove potential chart lines
String name = xdrDelete(dbName); // Value name
drawTextRalign(cRoot.x + timAxis - 1, cRoot.y + 16, name.substring(0, 6));
} }
prntHorizChartThreeValueAxisLabel(font); prntHorizChartThreeValueAxisLabel(font);
@@ -522,53 +562,67 @@ void Chart::drawChrtValAxis(const char chrtDir, const int8_t chrtSz, bool prntNa
} else { // vertical chart } else { // vertical chart
if (chrtSz == FULL_SIZE) { if (prntName) {
font = &Ubuntu_Bold12pt8b; if (chrtSz == FULL_SIZE) {
getdisplay().setFont(font); // use larger font font = &Ubuntu_Bold12pt8b;
drawTextRalign(cRoot.x + (valAxis * 0.42), cRoot.y - 2, dbName.substring(0, 6)); // print buffer data name (max. size 5 characters) } else {
font = &Ubuntu_Bold10pt8b;
} else { }
getdisplay().setFont(font);
font = &Ubuntu_Bold10pt8b; String name = xdrDelete(dbName); // Value name
drawTextRalign(cRoot.x + (valAxis * 0.42), cRoot.y - 2, name.substring(0, 6)); // print buffer data name (max. size 6 characters)
} }
font = &Ubuntu_Bold10pt8b;
prntVerticChartThreeValueAxisLabel(font); prntVerticChartThreeValueAxisLabel(font);
} }
} }
// Print current data value // Print current data value
void Chart::prntCurrValue(const char direction, GwApi::BoatValue& currValue) void Chart::prntCurrValue(const ChrtDir chrtDir, GwApi::BoatValue& currValue)
{ {
const int xPosVal = (direction == HORIZONTAL) ? cRoot.x + (timAxis / 2) - 56 : cRoot.x + 32; static constexpr int8_t LEFT = 0;
const int yPosVal = (direction == HORIZONTAL) ? cRoot.y + valAxis - 7 : cRoot.y + timAxis - 7; static constexpr int8_t CENTER = 1;
static constexpr int8_t RIGHT = 2;
static constexpr bool smallDecimals = true; // print data value always with small decimals
const int xPosVal = (chrtDir == HORIZONTAL) ? cRoot.x + (timAxis / 2) - 74 : cRoot.x + 31;
const int yPosVal = (chrtDir == HORIZONTAL) ? cRoot.y + valAxis : cRoot.y + timAxis;
FormattedData frmtDbData = formatValue(&currValue, *commonData, NO_SIMUDATA); FormattedData frmtDbData = formatValue(&currValue, *commonData, NO_SIMUDATA);
String sdbValue = frmtDbData.svalue; // value as formatted string String sdbValue = frmtDbData.svalue; // value as formatted string
String dbUnit = frmtDbData.unit; // Unit of value; limit length to 3 characters String dbUnit = frmtDbData.unit; // Unit of value; limit length to 3 characters
getdisplay().fillRect(xPosVal - 1, yPosVal - 35, 128, 41, bgColor); // Clear area for TWS value // box
getdisplay().drawRect(xPosVal, yPosVal - 34, 126, 40, fgColor); // Draw box for TWS value getdisplay().fillRect(xPosVal - 1, yPosVal - 40, 148, 39, bgColor); // Clear area for value
getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b); getdisplay().drawRect(xPosVal, yPosVal - 39, 146, 37, fgColor); // Draw box for value
getdisplay().setCursor(xPosVal + 1, yPosVal);
getdisplay().print(sdbValue); // value
// value
printBoatValue(sdbValue, xPosVal + 93, yPosVal - 6, RIGHT, 16, smallDecimals);
// getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b);
// getdisplay().setCursor(xPosVal + 1, yPosVal - 6);
// getdisplay().print(sdbValue);
// name
getdisplay().setFont(&Ubuntu_Bold10pt8b); getdisplay().setFont(&Ubuntu_Bold10pt8b);
getdisplay().setCursor(xPosVal + 76, yPosVal - 17); getdisplay().setCursor(xPosVal + 97, yPosVal - 23);
getdisplay().print(dbName.substring(0, 3)); // Name, limited to 3 characters String name = xdrDelete(dbName);
getdisplay().print(name.substring(0, 3)); // Name, limited to 3 characters
// unit
getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(xPosVal + 76, yPosVal + 0); getdisplay().setCursor(xPosVal + 98, yPosVal - 8);
getdisplay().print(dbUnit); // Unit getdisplay().print(dbUnit);
} }
// print message for no valid data availabletemplate <typename T> // print message for no valid data availabletemplate <typename T>
void Chart::prntNoValidData(const char direction) void Chart::prntNoValidData(const ChrtDir chrtDir)
{ {
Pos p; Pos p;
getdisplay().setFont(&Ubuntu_Bold10pt8b); getdisplay().setFont(&Ubuntu_Bold10pt8b);
if (direction == HORIZONTAL) { if (chrtDir == HORIZONTAL) {
p.x = cRoot.x + (timAxis / 2); p.x = cRoot.x + (timAxis / 2);
p.y = cRoot.y + (valAxis / 2) - 10; p.y = cRoot.y + (valAxis / 2) - 10;
} else { } else {
@@ -583,7 +637,7 @@ void Chart::prntNoValidData(const char direction)
} }
// Get maximum difference of last <amount> of dataBuf ringbuffer values to center chart; for angle data only // Get maximum difference of last <amount> of dataBuf ringbuffer values to center chart; for angle data only
double Chart::getAngleRng(const double center, size_t amount) double Chart::getCircularRng(const double center, size_t amount)
{ {
size_t count = dataBuf.getCurrentSize(); size_t count = dataBuf.getCurrentSize();
@@ -617,8 +671,8 @@ double Chart::getAngleRng(const double center, size_t amount)
return (maxRng != dbMIN_VAL ? maxRng : dbMAX_VAL); // Return range from <mid> to <max> return (maxRng != dbMIN_VAL ? maxRng : dbMAX_VAL); // Return range from <mid> to <max>
} }
// print value axis label with only three values: top, mid, and bottom for vertical chart // print value axis label with only three values: top, mid, and bottom for vertical chart
void Chart::prntVerticChartThreeValueAxisLabel(const GFXfont* font) void Chart::prntVerticChartThreeValueAxisLabel(const GFXfont* font)
{ {
double cVal; double cVal;
char sVal[7]; char sVal[7];
@@ -654,13 +708,13 @@ void Chart::prntHorizChartThreeValueAxisLabel(const GFXfont* font)
double axLabel; double axLabel;
double chrtMin, chrtMid, chrtMax; double chrtMin, chrtMid, chrtMax;
int xOffset, yOffset; // offset for text position of x axis label for different font sizes int xOffset, yOffset; // offset for text position of x axis label for different font sizes
String sVal; char sVal[11]; // data value have max. 6 digits + decimal point + 2 decimals + sign
if (font == &Ubuntu_Bold10pt8b) { if (font == &Ubuntu_Bold10pt8b) {
xOffset = 39; xOffset = 32;
yOffset = 16; yOffset = 16;
} else if (font == &Ubuntu_Bold12pt8b) { } else if (font == &Ubuntu_Bold12pt8b) {
xOffset = 51; xOffset = 42;
yOffset = 18; yOffset = 18;
} }
getdisplay().setFont(font); getdisplay().setFont(font);
@@ -669,77 +723,99 @@ void Chart::prntHorizChartThreeValueAxisLabel(const GFXfont* font)
chrtMin = convertValue(this->chrtMin, dbName, dbFormat, *commonData); chrtMin = convertValue(this->chrtMin, dbName, dbFormat, *commonData);
chrtMid = convertValue(this->chrtMid, dbName, dbFormat, *commonData); chrtMid = convertValue(this->chrtMid, dbName, dbFormat, *commonData);
chrtMax = convertValue(this->chrtMax, dbName, dbFormat, *commonData); chrtMax = convertValue(this->chrtMax, dbName, dbFormat, *commonData);
chrtMin = std::round(chrtMin * 100.0) / 100.0;
chrtMid = std::round(chrtMid * 100.0) / 100.0;
chrtMax = std::round(chrtMax * 100.0) / 100.0;
// print top axis label // print top axis label
axLabel = (chrtDataFmt == SPEED || chrtDataFmt == TEMPERATURE) ? chrtMax : chrtMin; axLabel = (chrtDataFmt == WIND || chrtDataFmt == ROTATION || chrtDataFmt == DEPTH) ? chrtMin : chrtMax;
sVal = formatLabel(axLabel); formatLabel(axLabel).toCharArray(sVal, 11);
getdisplay().fillRect(cRoot.x, cRoot.y + 2, xOffset + 3, yOffset, bgColor); // Clear small area to remove potential chart lines if (chrtDataFmt == PRESSURE) {
drawTextRalign(cRoot.x + xOffset, cRoot.y + yOffset, sVal); // range value snprintf(sVal, sizeof(sVal), "%4.0f", axLabel);
getdisplay().fillRect(cRoot.x, cRoot.y + 2, xOffset + 12, yOffset, bgColor); // Clear small area to remove potential chart lines
drawTextRalign(cRoot.x + xOffset + 11, cRoot.y + yOffset, sVal); // range value
} else {
if (char* dot = strchr(sVal, '.'))
*dot = '\0'; // no decimal for top axis label
getdisplay().fillRect(cRoot.x, cRoot.y + 2, xOffset + 3, yOffset, bgColor); // Clear small area to remove potential chart lines
drawTextRalign(cRoot.x + xOffset, cRoot.y + yOffset, sVal); // range value
}
// print mid axis label // print mid axis label
axLabel = chrtMid; axLabel = chrtMid;
sVal = formatLabel(axLabel); formatLabel(axLabel).toCharArray(sVal, 11);
getdisplay().fillRect(cRoot.x, cRoot.y + (valAxis / 2) - 8, xOffset + 3, 16, bgColor); // Clear small area to remove potential chart lines if (chrtDataFmt == PRESSURE) { // print 4-digit value
drawTextRalign(cRoot.x + xOffset, cRoot.y + (valAxis / 2) + 6, sVal); // range value getdisplay().fillRect(cRoot.x, cRoot.y + (valAxis / 2) - 8, xOffset + 12, yOffset, bgColor); // Clear small area to remove potential chart lines
getdisplay().drawLine(cRoot.x + xOffset + 3, cRoot.y + (valAxis / 2), cRoot.x + timAxis, cRoot.y + (valAxis / 2), fgColor); drawTextRalign(cRoot.x + xOffset + 11, cRoot.y + (valAxis / 2) + 6, sVal); // range value
getdisplay().drawLine(cRoot.x + xOffset + 14, cRoot.y + (valAxis / 2), cRoot.x + timAxis, cRoot.y + (valAxis / 2), fgColor);
} else { // print 3-digit value
getdisplay().fillRect(cRoot.x, cRoot.y + (valAxis / 2) - 8, xOffset + 3, yOffset, bgColor); // Clear small area to remove potential chart lines
drawTextRalign(cRoot.x + xOffset, cRoot.y + (valAxis / 2) + 6, sVal); // range value
getdisplay().drawLine(cRoot.x + xOffset + 3, cRoot.y + (valAxis / 2), cRoot.x + timAxis, cRoot.y + (valAxis / 2), fgColor);
}
// print bottom axis label // print bottom axis label
axLabel = (chrtDataFmt == SPEED || chrtDataFmt == TEMPERATURE) ? chrtMin : chrtMax; axLabel = (chrtDataFmt == WIND || chrtDataFmt == ROTATION || chrtDataFmt == DEPTH) ? chrtMax : chrtMin;
sVal = formatLabel(axLabel); formatLabel(axLabel).toCharArray(sVal, 11);
getdisplay().fillRect(cRoot.x, cRoot.y + valAxis - 14, xOffset + 3, 15, bgColor); // Clear small area to remove potential chart lines if (chrtDataFmt == PRESSURE) {
drawTextRalign(cRoot.x + xOffset, cRoot.y + valAxis, sVal); // range value getdisplay().fillRect(cRoot.x, cRoot.y + valAxis - 14, xOffset + 12, yOffset, bgColor); // Clear small area to remove potential chart lines
getdisplay().drawLine(cRoot.x + xOffset + 3, cRoot.y + valAxis, cRoot.x + timAxis, cRoot.y + valAxis, fgColor); drawTextRalign(cRoot.x + xOffset + 11, cRoot.y + valAxis, sVal); // range value
getdisplay().drawLine(cRoot.x + xOffset + 14, cRoot.y + valAxis, cRoot.x + timAxis, cRoot.y + valAxis, fgColor);
} else {
if (char* dot = strchr(sVal, '.'))
*dot = '\0'; // no decimal for bottom axis label
getdisplay().fillRect(cRoot.x, cRoot.y + valAxis - 14, xOffset + 3, yOffset, bgColor); // Clear small area to remove potential chart lines
drawTextRalign(cRoot.x + xOffset, cRoot.y + valAxis, sVal); // range value
getdisplay().drawLine(cRoot.x + xOffset + 3, cRoot.y + valAxis, cRoot.x + timAxis, cRoot.y + valAxis, fgColor);
}
} }
// print value axis label with multiple axis lines for horizontal chart // print value axis label with multiple axis lines for horizontal chart
void Chart::prntHorizChartMultiValueAxisLabel(const GFXfont* font) void Chart::prntHorizChartMultiValueAxisLabel(const GFXfont* font)
{ {
double chrtMin, chrtMax, chrtRng; double chrtMin, chrtMax, chrtRng;
double axSlots, axIntv, axLabel;
int xOffset; // offset for text position of x axis label for different font sizes int xOffset; // offset for text position of x axis label for different font sizes
String sVal; char sVal[11]; // data value have max. 6 digits + decimal point + 2 decimals + sign
if (font == &Ubuntu_Bold10pt8b) { if (font == &Ubuntu_Bold10pt8b) {
xOffset = 38; xOffset = 32;
} else if (font == &Ubuntu_Bold12pt8b) { } else if (font == &Ubuntu_Bold12pt8b) {
xOffset = 50; xOffset = 42;
} }
getdisplay().setFont(font); getdisplay().setFont(font);
chrtMin = convertValue(this->chrtMin, dbName, dbFormat, *commonData); chrtMin = convertValue(this->chrtMin, dbName, dbFormat, *commonData);
// chrtMin = std::floor(chrtMin / rngStep) * rngStep;
chrtMin = std::round(chrtMin * 100.0) / 100.0;
chrtMax = convertValue(this->chrtMax, dbName, dbFormat, *commonData); chrtMax = convertValue(this->chrtMax, dbName, dbFormat, *commonData);
// chrtMax = std::ceil(chrtMax / rngStep) * rngStep; chrtRng = chrtMax - chrtMin;
chrtMax = std::round(chrtMax * 100.0) / 100.0;
chrtRng = std::round((chrtMax - chrtMin) * 100) / 100;
axSlots = valAxis / static_cast<double>(VALAXIS_STEP); // number of axis labels (and we want to have a double calculation, no integer) double axIntv = chrtRng / VALAXIS_SLOTS; // axis label interval
axIntv = chrtRng / axSlots; double axLabel = chrtMin + axIntv; // current axis label
axLabel = chrtMin + axIntv; double chrtScale = double(valAxis) / chrtRng; // Chart scale: pixels per value step
// LOG_DEBUG(GwLog::DEBUG, "Chart::printHorizMultiValueAxisLabel: chrtRng: %.2f, th-chrtRng: %.2f, axSlots: %.2f, axIntv: %.2f, axLabel: %.2f, chrtMin: %.2f, chrtMid: %.2f, chrtMax: %.2f", chrtRng, this->chrtRng, axSlots, axIntv, axLabel, this->chrtMin, chrtMid, chrtMax); double valAxisStep = axIntv * chrtScale; // pixel per axis label interval
// LOG_DEBUG(GwLog::DEBUG, "Chart::printHorizMultiValueAxisLabel: chrtRng: %.2f, th-chrtRng: %.2f, axSlots: %.2f, axIntv: %.2f, axLabel: %.2f, chrtMin: %.2f, chrtMid: %.2f, chrtMax: %.2f", chrtRng, this->chrtRng, VALAXIS_SLOTS, axIntv, axLabel, this->chrtMin, chrtMid, chrtMax);
int loopStrt, loopEnd, loopStp; int loopStrt, loopEnd, loopStp;
if (chrtDataFmt == SPEED || chrtDataFmt == TEMPERATURE || chrtDataFmt == OTHER) { if (chrtDataFmt == WIND || chrtDataFmt == ROTATION || chrtDataFmt == DEPTH) {
// High value at top
loopStrt = valAxis - VALAXIS_STEP;
loopEnd = VALAXIS_STEP / 2;
loopStp = VALAXIS_STEP * -1;
} else {
// Low value at top // Low value at top
loopStrt = VALAXIS_STEP; loopStrt = valAxisStep;
loopEnd = valAxis - (VALAXIS_STEP / 2); loopEnd = valAxis - (valAxisStep / 2);
loopStp = VALAXIS_STEP; loopStp = valAxisStep;
} else {
// high value at top
loopStrt = valAxis - valAxisStep;
loopEnd = valAxisStep / 2;
loopStp = valAxisStep * -1;
} }
for (int j = loopStrt; (loopStp > 0) ? (j < loopEnd) : (j > loopEnd); j += loopStp) { for (int j = loopStrt; (loopStp > 0) ? (j < loopEnd) : (j > loopEnd); j += loopStp) {
sVal = formatLabel(axLabel); formatLabel(axLabel).toCharArray(sVal, 11);
getdisplay().fillRect(cRoot.x, cRoot.y + j - 11, xOffset + 3, 21, bgColor); // Clear small area to remove potential chart lines if (chrtDataFmt == PRESSURE) { // print 4-digit value
drawTextRalign(cRoot.x + xOffset, cRoot.y + j + 7, sVal); // range value getdisplay().fillRect(cRoot.x, cRoot.y + j - 11, xOffset + 12, 21, bgColor); // Clear small area to remove potential chart lines
getdisplay().drawLine(cRoot.x + xOffset + 3, cRoot.y + j, cRoot.x + timAxis, cRoot.y + j, fgColor); drawTextRalign(cRoot.x + xOffset + 11, cRoot.y + j + 7, sVal); // range value
getdisplay().drawLine(cRoot.x + xOffset + 14, cRoot.y + j, cRoot.x + timAxis, cRoot.y + j, fgColor);
} else { // print 3-digit value
getdisplay().fillRect(cRoot.x, cRoot.y + j - 11, xOffset + 3, 21, bgColor); // Clear small area to remove potential chart lines
drawTextRalign(cRoot.x + xOffset, cRoot.y + j + 7, sVal); // range value
getdisplay().drawLine(cRoot.x + xOffset + 3, cRoot.y + j, cRoot.x + timAxis, cRoot.y + j, fgColor);
}
axLabel += axIntv; axLabel += axIntv;
} }
@@ -782,20 +858,21 @@ String Chart::formatLabel(const double& label)
{ {
char sVal[11]; char sVal[11];
if (dbFormat == "formatCourse" || dbFormat == "formatWind") { if (chrtDataFmt == WIND) {
// Format 3 numbers with prefix zero snprintf(sVal, sizeof(sVal), "%03.0f", label); // Format 3 numbers with prefix zero
snprintf(sVal, sizeof(sVal), "%03.0f", label);
} else if (dbFormat == "formatRot") { } else if (chrtDataFmt == ROTATION) {
if (label > -10 && label < 10) { if (label > -9.995 && label < 9.995) {
snprintf(sVal, sizeof(sVal), "%3.2f", label); snprintf(sVal, sizeof(sVal), "%3.2f", label);
} else { } else {
snprintf(sVal, sizeof(sVal), "%3.0f", label); snprintf(sVal, sizeof(sVal), "%3.0f", label);
} }
}
else { } else if (chrtDataFmt == PRESSURE) {
if (label < 10) { snprintf(sVal, sizeof(sVal), "%4.0f", label);
} else {
if (label < 9.95) {
snprintf(sVal, sizeof(sVal), "%3.1f", label); snprintf(sVal, sizeof(sVal), "%3.1f", label);
} else { } else {
snprintf(sVal, sizeof(sVal), "%3.0f", label); snprintf(sVal, sizeof(sVal), "%3.0f", label);
+55 -31
View File
@@ -2,6 +2,7 @@
#pragma once #pragma once
#include "Pagedata.h" #include "Pagedata.h"
#include "OBP60Extensions.h" #include "OBP60Extensions.h"
#include "movingAvg.h"
struct Pos { struct Pos {
int x; int x;
@@ -18,6 +19,30 @@ class RingBuffer;
class GwLog; class GwLog;
class Chart { class Chart {
public:
enum ChrtDir {
HORIZONTAL,
VERTICAL
};
enum ChrtSize {
FULL_SIZE,
HALF_SIZE_LEFT_TOP,
HALF_SIZE_RIGHT_BOTTOM,
TWO_THIRD_TOP
};
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;
Chart(RingBuffer<uint16_t>& dataBuf, CommonData& common, bool useSimuData); // Chart object of data chart
~Chart();
bool init(); // initialize chart object parameters
bool isValid() { return initValid; }; // Checks if chart object has been fully initialized
void showChrt(const ChrtDir chrtDir, const ChrtSize chrtSz, const int8_t chrtIntv, bool prntName, bool showCurrValue, GwApi::BoatValue currValue); // Perform all actions to draw chart
protected: protected:
CommonData* commonData; CommonData* commonData;
GwLog* logger; GwLog* logger;
@@ -28,28 +53,24 @@ protected:
SPEED, SPEED,
DEPTH, DEPTH,
TEMPERATURE, TEMPERATURE,
PRESSURE,
HUMIDITY,
OTHER 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 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 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 int8_t VALAXIS_SLOTS = 5; // no. of value axis labels
static constexpr bool NO_SIMUDATA = true; // switch off simulation feature of <formatValue> function static constexpr bool NO_SIMUDATA = true; // switch off simulation feature of <formatValue> function
RingBuffer<uint16_t>& dataBuf; // Buffer to display RingBuffer<uint16_t>& dataBuf; // Buffer to display
//char chrtDir; // Chart timeline direction: 'H' = horizontal, 'V' = vertical bool initValid = false; // indicates whether object holds valid initialization data or not
//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
bool smoothCharts; // flag to indicate if charts shall be printed with smoothed gradient
String tempFormat; // user defined format for temperature String tempFormat; // user defined format for temperature
double zeroValue; // "0" SI value for temperature double zeroValue; // "0" SI value for temperature
@@ -66,7 +87,8 @@ protected:
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 recalcRngMid = false; // Flag for re-calculation of mid value of chart for wind data types bool recalcRngMid; // Flag for re-calculation of mid value of chart for wind data types
bool allLeft, allRight; // indicate whether all chart points are left or right of middle value (only for wind data type)
String dbName, dbFormat; // Name and format of data buffer String dbName, dbFormat; // Name and format of data buffer
ChrtDataFormat chrtDataFmt; // Data format of chart boat data type ChrtDataFormat chrtDataFmt; // Data format of chart boat data type
@@ -77,40 +99,42 @@ protected:
int numBufVals; // number of wind values available for current interval selection int numBufVals; // number of wind values available for current interval selection
int bufStart; // 1st data value in buffer to show int bufStart; // 1st data value in buffer to show
int numAddedBufVals; // Number of values added to buffer since last display int numAddedBufVals; // Number of values added to buffer since last display
size_t currIdx; // Current index in TWD history buffer size_t currIdx; // Current index in history buffer
size_t lastIdx; // Last index of TWD history buffer size_t lastIdx; // Last index of history buffer
size_t lastAddedIdx = 0; // Last index of TWD history buffer when new data was added size_t lastAddedIdx = 0; // Last index of history buffer when new data was added
int numNoData; // Counter for multiple invalid data values in a row 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
double chrtPrevVal; // Last data value in chart area double chrtPrevVal; // Last data value in chart area
int x, y; // x and y coordinates for drawing
int prevX, prevY; // Last x and y coordinates for drawing int prevX, prevY; // Last x and y coordinates for drawing
bool setChartDimensions(const char direction, const int8_t size); //define dimensions and start points for chart // Default ranges for various boat data types
void drawChrt(const char chrtDir, const int8_t chrtIntv, GwApi::BoatValue& currValue); // Draw chart line // Parameters: <1st value> default range, <2nd value> step for range adjustment
std::map<String, ChartProps> dfltChrtDta = {
{ "formatWind", { 60.0 * DEG_TO_RAD, 10.0 * DEG_TO_RAD } }, // default wind range 60 degrees
{ "formatCourse", { 60.0 * DEG_TO_RAD, 10.0 * DEG_TO_RAD } }, // default course range 60 degrees
{ "formatKnots", { 2.572, 2.572 } }, // default speed range in m/s
{ "formatDepth", { 10.0, 5.0 } }, // default depth range in m
{ "kelvinToC", { 20.0, 5.0 } }, // default temp range in °C/K
{ "formatXdr:P:P", { 4000.0, 1000.0 } } // default pressure range in Pascal (hPa * 100); XDR <B> (bar) format is represented in gateway in the same way
};
bool setChartDimensions(const ChrtDir direction, const ChrtSize chrtSz); // define dimensions and start points for chart
void drawChrt(const ChrtDir 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 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 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 void drawChartLines(const ChrtDir 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 Pos setChartPoint(const int i, const ChrtDir chrtDir, 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 drawChrtTimeAxis(const ChrtDir chrtDir, const ChrtSize 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 drawChrtValAxis(const ChrtDir chrtDir, const ChrtSize chrtSz, const 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 prntCurrValue(const ChrtDir chrtDir, GwApi::BoatValue& currValue); // Add current boat data value to chart
void prntNoValidData(const char chrtDir); // print message for no valid data available void prntNoValidData(const ChrtDir chrtDir); // print message for no valid data available
double getAngleRng(const double center, size_t amount); // Calculate range between chart center and edges double getCircularRng(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 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 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 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 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 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) String formatLabel(const double& label); // Format current axis label for printing w/o data format conversion (has been done earlier)
public:
// Define default chart range and range step for each boat data type
static std::map<String, ChartProps> dfltChrtDta;
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
}; };
-771
View File
@@ -1,771 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3
/*
This page is in experimental stage so be warned!
North is up.
Anchor page with background map from mapservice
Boatdata used
DBS - Water depth
HDT - Boat heading, true
AWS - Wind strength; Boat not moving so we assume AWS=TWS and AWD=TWD
AWD - Wind direction
LAT/LON - Boat position, current
HDOP - Position error, horizontal
Raise function in device OBP40 has to be done inside config mode
because of limited number of buttons.
TODO
gzip for data transfer,
miniz.c from ROM?
manually inflating with tinflate from ROM
Save position in FRAM
Alarm: gps fix lost
switch unit feet/meter
force map update if new position is different from old position by
a certain level (e.g. 10m)
windlass integration
chain counter
Map service options / URL parameters
- mandatory
lat: latitude
lon: longitude
width: image width in px (400)
height: image height in px (300)
- optional
zoom: zoom level, default 15
mrot: map rotation angle in degrees
mtype: map type, default="Open Street Map"
dtype: dithering type, default="Atkinson"
cutout: image cutout type 0=none
alpha: alpha blending for cutout
tab: tab size, 0=none
border: border line zize in px, default 2
symbol: synmol number, default=2 triangle
srot: symbol rotation in degrees
ssize: symbol size in px, default=15
grid: show map grid
*/
#include <WiFi.h>
#include <HTTPClient.h>
#include "Pagedata.h"
#include "OBP60Extensions.h"
#include "ConfigMenu.h"
// #include "miniz.h" // devices without PSRAM use <rom/miniz.h>
// extern "C" {
#include "rom/miniz.h"
// }
#define anchor_width 16
#define anchor_height 16
static unsigned char anchor_bits[] PROGMEM = {
0x80, 0x01, 0x40, 0x02, 0x40, 0x02, 0x80, 0x01, 0xf0, 0x0f, 0x80, 0x01,
0x80, 0x01, 0x88, 0x11, 0x8c, 0x31, 0x8e, 0x71, 0x84, 0x21, 0x86, 0x61,
0x86, 0x61, 0xfc, 0x3f, 0xf8, 0x1f, 0x80, 0x01 };
class PageAnchor : public Page
{
private:
char mode = 'N'; // (N)ormal, (C)onfig
int8_t editmode = -1; // marker for menu/edit/set function
ConfigMenu *menu;
//uint8_t *mapbuf = new uint8_t[10000]; // 8450 Byte without header
//int mapbuf_size = 10000;
//uint8_t *mapbuf = (uint8_t*) heap_caps_malloc(mapbuf_size, MALLOC_CAP_SPIRAM);
GFXcanvas1 *canvas;
const uint16_t map_width = 264;
const uint16_t map_height = 260;
bool map_valid = false;
char map_service = 'R'; // (O)BP Service, (L)ocal Service, (R)emote Service
double map_lat = 0; // current center of valid map
double map_lon = 0;
String server_name; // server with map service
uint16_t server_port = 80;
String tile_path;
String lengthformat;
double scale = 50; // Radius of display circle in meter, depends on lat
uint8_t zoom = 15; // map zoom level
bool alarm = false;
bool alarm_enabled = false;
uint8_t alarm_range;
uint8_t chain_length;
uint8_t chain = 0;
bool anchor_set = false;
double anchor_lat;
double anchor_lon;
double anchor_depth;
int anchor_ts; // time stamp anchor dropped
GwApi::BoatValue *bv_dbs; // depth below surface
GwApi::BoatValue *bv_hdt; // true heading
GwApi::BoatValue *bv_aws; // apparent wind speed
GwApi::BoatValue *bv_awd; // apparent wind direction
GwApi::BoatValue *bv_lat; // latitude, current
GwApi::BoatValue *bv_lon; // longitude, current
GwApi::BoatValue *bv_hdop; // horizontal position error
bool simulation = false;
int last_mapsize = 0;
String errmsg = "";
int loops;
int readbytes = 0;
void displayModeNormal(PageData &pageData) {
// get currrent boatvalues
bv_dbs = pageData.values[0]; // DBS
String sval_dbs = formatValue(bv_dbs, *commonData).svalue;
String sunit_dbs = formatValue(bv_dbs, *commonData).unit;
bv_hdt = pageData.values[1]; // HDT
String sval_hdt = formatValue(bv_hdt, *commonData).svalue;
bv_aws = pageData.values[2]; // AWS
String sval_aws = formatValue(bv_aws, *commonData).svalue;
String sunit_aws = formatValue(bv_aws, *commonData).unit;
bv_awd = pageData.values[3]; // AWD
String sval_awd = formatValue(bv_awd, *commonData).svalue;
bv_lat = pageData.values[4]; // LAT
String sval_lat = formatValue(bv_lat, *commonData).svalue;
bv_lon = pageData.values[5]; // LON
String sval_lon = formatValue(bv_lon, *commonData).svalue;
bv_hdop = pageData.values[6]; // HDOP
String sval_hdop = formatValue(bv_hdop, *commonData).svalue;
String sunit_hdop = formatValue(bv_hdop, *commonData).unit;
commonData->logger->logDebug(GwLog::DEBUG, "Drawing at PageAnchor; DBS=%f, HDT=%f, AWS=%f", bv_dbs->value, bv_hdt->value, bv_aws->value);
// Draw canvas with background map
// rhumb(map_lat, map_lon, bv_lat->value, bv_lon->value)
int posdiff = 0;
if (map_valid) {
if (bv_lat->valid and bv_lon->valid) {
// calculate movement since last map refresh
posdiff = rhumb(map_lat, map_lon, bv_lat->value, bv_lon->value);
if (posdiff > 25) {
map_lat = bv_lat->value;
map_lon = bv_lon->value;
map_valid = getBackgroundMap(map_lat, map_lon, zoom);
if (map_valid) {
// prepare visible space for anchor-symbol or boat
canvas->fillCircle(132, 130, 12, commonData->fgcolor);
}
}
}
getdisplay().drawBitmap(68, 20, canvas->getBuffer(), map_width, map_height, commonData->fgcolor);
}
Point c = {200, 150}; // center = anchor position
uint16_t r = 125;
// Circle as map border
getdisplay().drawCircle(c.x, c.y, r, commonData->fgcolor);
getdisplay().drawCircle(c.x, c.y, r + 1, commonData->fgcolor);
Point b = {200, 180}; // boat position while dropping anchor
const std::vector<Point> pts_boat = { // polygon lines
{b.x - 5, b.y},
{b.x - 5, b.y - 10},
{b.x, b.y - 16},
{b.x + 5, b.y - 10},
{b.x + 5, b.y}
};
//rotatePoints then draw lines
// TODO rotate boat according to current heading
if (bv_hdt->valid) {
if (map_valid) {
Point b1 = rotatePoint(c, {b.x, b.y - 8}, bv_hdt->value * RAD_TO_DEG);
getdisplay().fillCircle(b1.x, b1.y, 10, commonData->bgcolor);
}
drawPoly(rotatePoints(c, pts_boat, bv_hdt->value * RAD_TO_DEG), commonData->fgcolor);
} else {
// no heading available: draw north oriented
if (map_valid) {
getdisplay().fillCircle(b.x, b.y - 8, 10, commonData->bgcolor);
}
drawPoly(pts_boat, commonData->fgcolor);
}
// Draw wind arrow
const std::vector<Point> pts_wind = {
{c.x, c.y - r + 25},
{c.x - 12, c.y - r - 4},
{c.x, c.y - r + 6},
{c.x + 12, c.y - r - 4}
};
if (bv_awd->valid) {
fillPoly4(rotatePoints(c, pts_wind, bv_awd->value), commonData->fgcolor);
}
// Title and corner value headings
getdisplay().setTextColor(commonData->fgcolor);
getdisplay().setFont(&Ubuntu_Bold10pt8b);
// Left
getdisplay().setCursor(8, 36);
getdisplay().print("Anchor");
getdisplay().setCursor(8, 210);
getdisplay().print("Depth");
// Right
drawTextRalign(392, 80, "Chain");
drawTextRalign(392, 210, "Wind");
// Units
getdisplay().setCursor(8, 272);
getdisplay().print(sunit_dbs);
drawTextRalign(392, 272, sunit_aws);
// drawTextRalign(392, 100, lengthformat); // chain unit not implemented
// Corner values
getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(8, 54);
getdisplay().print(anchor_set ? "Dropped" : "Ready"); // Anchor state
getdisplay().setCursor(8, 72);
getdisplay().print("Alarm: "); // Alarm state
getdisplay().print(alarm_enabled ? "on" : "off");
getdisplay().setCursor(8, 120);
getdisplay().print("Zoom");
getdisplay().setCursor(8, 136);
getdisplay().print(zoom);
getdisplay().setCursor(8, 160);
getdisplay().print("diff");
getdisplay().setCursor(8, 176);
if (map_valid and bv_lat->valid and bv_lon->valid) {
getdisplay().print(String(posdiff));
} else {
getdisplay().print("n/a");
}
// Chain out TODO lengthformat ft/m
drawTextRalign(392, 96, String(chain) + " m");
drawTextRalign(392, 96+16, "of " + String(chain_length) + " m");
getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b);
// Depth
getdisplay().setCursor(8, 250);
getdisplay().print(sval_dbs);
// Wind
getdisplay().setCursor(320, 250);
getdisplay().print(sval_aws);
// Position of boat in center of map
getdisplay().setFont(&IBM8x8px);
drawTextRalign(392, 34, sval_lat);
drawTextRalign(392, 44, sval_lon);
// quality
String hdop = "HDOP: ";
if (bv_hdop->valid) {
hdop += String(round(bv_hdop->value));
} else {
hdop += " n/a";
}
drawTextRalign(392, 54, hdop);
// zoom scale
getdisplay().drawLine(c.x + 10, c.y, c.x + r - 4, c.y, commonData->fgcolor);
// arrow left
getdisplay().drawLine(c.x + 10, c.y, c.x + 16, c.y - 4, commonData->fgcolor);
getdisplay().drawLine(c.x + 10, c.y, c.x + 16, c.y + 4, commonData->fgcolor);
// arrow right
getdisplay().drawLine(c.x + r - 4, c.y, c.x + r - 10, c.y - 4, commonData->fgcolor);
getdisplay().drawLine(c.x + r - 4, c.y, c.x + r - 10, c.y + 4, commonData->fgcolor);
getdisplay().setFont(&Ubuntu_Bold8pt8b);
drawTextCenter(c.x + r / 2, c.y + 8, String(scale, 0) + "m");
// draw anchor symbol (as bitmap)
getdisplay().drawXBitmap(c.x - anchor_width / 2, c.y - anchor_height / 2,
anchor_bits, anchor_width, anchor_height, commonData->fgcolor);
}
void displayModeConfig(PageData &pageData) {
getdisplay().setTextColor(commonData->fgcolor);
getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().setCursor(8, 48);
getdisplay().print("Anchor configuration");
getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(8, 260);
getdisplay().print("Press BACK to leave config");
/* getdisplay().setCursor(8, 68);
getdisplay().printf("Server: %s", server_name.c_str());
getdisplay().setCursor(8, 88);
getdisplay().printf("Port: %d", server_port);
getdisplay().setCursor(8, 108);
getdisplay().printf("Tilepath: %s", tile_path.c_str());
getdisplay().setCursor(8, 128);
getdisplay().printf("Last mapsize: %d", last_mapsize);
getdisplay().setCursor(8, 148);
getdisplay().printf("Last error: %s", errmsg);
getdisplay().setCursor(8, 168);
getdisplay().printf("Loops: %d, Readbytes: %d", loops, readbytes);
*/
GwApi::BoatValue *bv_lat = pageData.values[4]; // LAT
GwApi::BoatValue *bv_lon = pageData.values[5]; // LON
if (!bv_lat->valid or !bv_lon->valid) {
getdisplay().setCursor(8, 228);
getdisplay().printf("No valid position: background map disabled");
}
// Display menu
getdisplay().setFont(&Ubuntu_Bold8pt8b);
for (int i = 0 ; i < menu->getItemCount(); i++) {
ConfigMenuItem *itm = menu->getItemByIndex(i);
if (!itm) {
commonData->logger->logDebug(GwLog::ERROR, "Menu item not found: %d", i);
} else {
Rect r = menu->getItemRect(i);
bool inverted = (i == menu->getActiveIndex());
drawTextBoxed(r, itm->getLabel(), commonData->fgcolor, commonData->bgcolor, inverted, false);
if (inverted and editmode > 0) {
// triangle as edit marker
getdisplay().fillTriangle(r.x + r.w + 20, r.y, r.x + r.w + 30, r.y + r.h / 2, r.x + r.w + 20, r.y + r.h, commonData->fgcolor);
}
getdisplay().setCursor(r.x + r.w + 40, r.y + r.h - 4);
if (itm->getType() == "int") {
getdisplay().print(itm->getValue());
getdisplay().print(itm->getUnit());
} else {
getdisplay().print(itm->getValue() == 0 ? "No" : "Yes");
}
}
}
}
public:
PageAnchor(CommonData &common)
{
commonData = &common;
common.logger->logDebug(GwLog::LOG,"Instantiate PageAnchor");
String mapsource = common.config->getString(common.config->mapsource);
if (mapsource == "Local Service") {
map_service = 'L';
server_name = common.config->getString(common.config->ipAddress);
server_port = common.config->getInt(common.config->localPort);
tile_path = "";
} else if (mapsource == "Remote Service") {
map_service = 'R';
server_name = common.config->getString(common.config->mapServer);
tile_path = common.config->getString(common.config->mapTilePath);
} else { // OBP Service or undefined
map_service = 'O';
server_name = "norbert-walter.dnshome.de";
tile_path = "";
}
zoom = common.config->getInt(common.config->zoomlevel);
lengthformat = common.config->getString(common.config->lengthFormat);
chain_length = common.config->getInt(common.config->chainLength);
if (simulation) {
map_lat = 53.56938345759218;
map_lon = 9.679658234303275;
}
canvas = new GFXcanvas1(264, 260); // Byte aligned, no padding!
// Initialize config menu
menu = new ConfigMenu("Options", 40, 80);
menu->setItemDimension(150, 20);
ConfigMenuItem *newitem;
newitem = menu->addItem("chain", "Chain out", "int", 0, "m");
newitem->setRange(0, 200, {1, 2, 5, 10});
newitem = menu->addItem("alarm", "Alarm", "bool", 0, "");
newitem = menu->addItem("alarm", "Alarm range", "int", 50, "m");
newitem->setRange(0, 200, {1, 2, 5, 10});
newitem = menu->addItem("raise", "Raise Anchor", "bool", 0, "");
newitem = menu->addItem("zoom", "Zoom", "int", 15, "");
newitem->setRange(14, 17, {1});
menu->setItemActive("chain");
}
void setupKeys(){
Page::setupKeys();
commonData->keydata[0].label = "CFG";
#ifdef BOARD_OBP40S3
commonData->keydata[1].label = "DROP";
#endif
#ifdef BOARD_OBP60S3
commonData->keydata[4].label = "DROP";
#endif
}
// TODO OBP40 / OBP60 different handling
int handleKey(int key) {
commonData->logger->logDebug(GwLog::LOG, "Page Anchor handle key %d", key);
if (key == 1) { // Switch between normal and config mode
if (mode == 'N') {
mode = 'C';
#ifdef BOARD_OBP40S3
commonData->keydata[0].label = "BACK";
commonData->keydata[1].label = "EDIT";
#endif
} else {
mode = 'N';
#ifdef BOARD_OBP40S3
commonData->keydata[0].label = "CFG";
commonData->keydata[1].label = anchor_set ? "RAISE": "DROP";
#endif
#ifdef BOARD_OBP60S3
commonData->keydata[4].label = anchor_set ? "RAISE": "DROP";
#endif
}
return 0;
}
if (key == 2) {
if (mode == 'N') {
anchor_set = !anchor_set;
commonData->keydata[1].label = anchor_set ? "ALARM": "DROP";
if (anchor_set) {
anchor_lat = bv_lat->value;
anchor_lon = bv_lon->value;
anchor_depth = bv_dbs->value;
// TODO set timestamp
// anchor_ts =
}
return 0;
} else if (mode == 'C') {
// Change edit mode
if (editmode > 0) {
editmode = 0;
commonData->keydata[1].label = "EDIT";
} else {
editmode = 1;
commonData->keydata[1].label = "OK";
}
}
}
if (key == 9) {
// OBP40 Down
if (mode == 'C') {
if (editmode > 0) {
// decrease current menu item
menu->getActiveItem()->decValue();
} else {
// move to next menu item
menu->goNext();
}
return 0;
}
}
if (key == 10) {
// OBP40 Up
if (mode == 'C') {
if (editmode > 0) {
// increase current menu item
ConfigMenuItem *itm = menu->getActiveItem();
commonData->logger->logDebug(GwLog::LOG, "step = %d", itm->getStep());
itm->incValue();
} else {
// move to previous menu item
menu->goPrev();
}
return 0;
}
}
// Code for keylock
if (key == 11) {
commonData->keylock = !commonData->keylock;
return 0;
}
return key;
}
int rhumb(double lat1, double lon1, double lat2, double lon2) {
// calc distance in m between two geo points
static const double degToRad = M_PI / 180.0;
lat1 = degToRad * lat1;
lon1 = degToRad * lon1;
lat2 = degToRad * lat2;
lon2 = degToRad * lon2;
double dlon = lon2 - lon1;
double dlat = lat2 - lat1;
double mlat = (lat1 + lat2) / 2;
return (int) (6371000 * sqrt(pow(dlat, 2) + pow(cos(mlat) * dlon, 2)));
}
bool getBackgroundMap(double lat, double lon, uint8_t zoom) {
// HTTP-Request for map
// TODO über pagedata -> status abfragen?
if (WiFi.status() != WL_CONNECTED) {
return false;
}
bool valid = false;
HTTPClient http;
const char* headerKeys[] = { "Content-Encoding", "Content-Length" };
http.collectHeaders(headerKeys, 2);
String url = "http://" + server_name + "/" + tile_path;
String parameter = "?lat=" + String(lat, 6) + "&lon=" + String(lon, 6)+ "&zoom=" + String(zoom)
+ "&width=" + String(map_width) + "&height=" + String(map_height);
commonData->logger->logDebug(GwLog::LOG, "HTTP query: %s", String(url + parameter).c_str());
http.begin(url + parameter);
http.addHeader("Accept-Encoding", "deflate");
int httpCode = http.GET();
if (httpCode > 0) {
commonData->logger->logDebug(GwLog::LOG, "HTTP GET result code: %d", httpCode);
if (httpCode == HTTP_CODE_OK) {
WiFiClient* stream = http.getStreamPtr();
int size = http.getSize();
String encoding = http.header("Content-Encoding");
commonData->logger->logDebug(GwLog::LOG, "HTTP size: %d, encoding: '%s'", size, encoding);
bool is_gzip = encoding.equalsIgnoreCase("deflate");
uint8_t header[14]; // max: P4<LF>wwww wwww<LF>
int header_size = 0;
bool header_read = false;
int n = 0;
int ix = 0;
uint8_t* buf = canvas->getBuffer();
if (is_gzip) {
/* gzip compressed data
* has to be decompressed into a buffer big enough
* to hold the whole data.
* so the PBM header is included
* search a method to use that as canvas without
* additional copy
*/
commonData->logger->logDebug(GwLog::LOG, "Map received in gzip encoding");
#define HEADER_MAX 24
#define HTTP_CHUNK 512
uint8_t in_buf[HTTP_CHUNK];
uint8_t header_buf[HEADER_MAX];
tinfl_decompressor decomp;
tinfl_init(&decomp);
size_t bitmap_written = 0;
size_t header_written = 0;
bool header_done = false;
int row_bytes = 0;
size_t expected_bitmap = 0;
while (stream->connected() || stream->available()) {
int bytes_read = stream->read(in_buf, HTTP_CHUNK);
if (bytes_read <= 0) break;
commonData->logger->logDebug(GwLog::LOG, "stream: bytes_read=%d", bytes_read);
size_t in_ofs = 0; // offset
while (in_ofs < (size_t)bytes_read) {
size_t in_size = bytes_read - in_ofs;
size_t out_size;
uint8_t *out_ptr;
uint8_t *out_ptr_next;
if (!header_done) {
if (header_written >= HEADER_MAX) {
commonData->logger->logDebug(GwLog::LOG, "PBM header too large");
return false;
}
out_ptr = header_buf + header_written;
out_size = HEADER_MAX - header_written;
} else {
out_ptr = buf + bitmap_written;
out_size = expected_bitmap - bitmap_written;
}
commonData->logger->logDebug(GwLog::LOG, "in_size=%d, out_size=%d", in_size, out_size);
// TODO correct loop !!!
// tinfl_status tinfl_decompress(
// tinfl_decompressor *r,
// const mz_uint8 *pIn_buf_next,
// size_t *pIn_buf_size,
// mz_uint8 *pOut_buf_start
// mz_uint8 *pOut_buf_next,
// size_t *pOut_buf_size,
// const mz_uint32 decomp_flags)
tinfl_status status = tinfl_decompress(
&decomp,
in_buf + in_ofs, // start address in input buffer
&in_size, // number of bytes to process
out_ptr, // start of output buffer
out_ptr, // next write position in output buffer
&out_size, // free size in output buffer
// TINFL_FLAG_PARSE_ZLIB_HEADER |
TINFL_FLAG_HAS_MORE_INPUT |
TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF
);
if (status < 0) {
commonData->logger->logDebug(GwLog::LOG, "Decompression error (%d)", status);
return false;
}
in_ofs += in_size;
commonData->logger->logDebug(GwLog::LOG, "in_size=%d, in_ofs=%d", in_size, in_ofs);
if (!header_done) {
commonData->logger->logDebug(GwLog::LOG, "Decoding header");
header_written += out_size;
// Detect header end: two '\n'
char *first_nl = strchr((char*)header_buf, '\n');
if (!first_nl) continue;
char *second_nl = strchr(first_nl + 1, '\n');
if (!second_nl) continue;
// Null-terminate header for sscanf
header_buf[header_written < HEADER_MAX ? header_written : HEADER_MAX - 1] = 0;
// Check magic
if (strncmp((char*)header_buf, "P4", 2) != 0) {
commonData->logger->logDebug(GwLog::LOG, "Invalid PBM magic");
return false;
}
// Parse width and height strictly
int header_width = 0, header_height = 0;
if (sscanf((char*)header_buf, "P4\n%d %d", &header_width, &header_height) != 2) {
commonData->logger->logDebug(GwLog::LOG, "Failed to parse PBM dimensions");
return false;
}
if (header_width != map_width || header_height != map_height) {
commonData->logger->logDebug(GwLog::LOG, "PBM size mismatch: header %dx%d, requested %dx%d\n",
header_width, header_height, map_width, map_height);
return false;
}
commonData->logger->logDebug(GwLog::LOG, "Header: %dx%d", header_width, header_height);
// Compute row bytes and expected bitmap size
row_bytes = (header_width + 7) / 8;
commonData->logger->logDebug(GwLog::LOG, "row_bytes=%d", row_bytes);
expected_bitmap = (size_t)row_bytes * header_height;
commonData->logger->logDebug(GwLog::LOG, "expected_bitmap=%d", expected_bitmap);
// Copy any extra decompressed bitmap after header
size_t header_size = (second_nl + 1) - (char*)header_buf;
commonData->logger->logDebug(GwLog::LOG, "header_size=%d", header_size);
size_t extra_bitmap = header_written - header_size;
commonData->logger->logDebug(GwLog::LOG, "extra bitmap=%d", extra_bitmap);
header_done = true;
if (extra_bitmap > 0) {
memcpy(buf, header_buf + header_size, extra_bitmap);
bitmap_written = extra_bitmap;
}
} else {
bitmap_written += out_size;
if (bitmap_written >= expected_bitmap) {
commonData->logger->logDebug(GwLog::LOG, "Image fully received");
}
}
commonData->logger->logDebug(GwLog::LOG, "bitmap_written=%d", bitmap_written);
}
}
} else {
// uncompressed data
commonData->logger->logDebug(GwLog::LOG, "Map received uncompressed");
while (stream->available()) {
uint8_t b = stream->read();
n += 1;
if ((! header_read) and (n < 13) ) {
header[n-1] = b;
if ((n > 3) and (b == 0x0a)) {
header_read = true;
header_size = n;
header[n] = 0;
}
} else {
// write image data to canvas buffer
buf[ix++] = b;
}
}
if (n == size) {
valid = true;
}
}
commonData->logger->logDebug(GwLog::LOG, "HTTP: final bytesRead=%d, header-size=%d", n, header_size);
} else {
commonData->logger->logDebug(GwLog::LOG, "HTTP result #%d", httpCode);
}
} else {
commonData->logger->logDebug(GwLog::ERROR, "HTTP error #%d", httpCode);
}
http.end();
return valid;
}
void displayNew(PageData &pageData){
GwApi::BoatValue *bv_lat = pageData.values[4]; // LAT
GwApi::BoatValue *bv_lon = pageData.values[5]; // LON
// check if valid data available
if (!bv_lat->valid or !bv_lon->valid) {
map_valid = false;
return;
}
errmsg = "";
map_lat = bv_lat->value; // save for later comparison
map_lon = bv_lon->value;
map_valid = getBackgroundMap(map_lat, map_lon, zoom);
if (map_valid) {
// prepare visible space for anchor-symbol or boat
canvas->fillCircle(132, 130, 10, commonData->fgcolor);
}
};
void display_side_keys() {
// An rechter Seite neben dem Rad inc, dec, set etc ?
}
int displayPage(PageData &pageData) {
// Logging boat values
commonData->logger->logDebug(GwLog::LOG, "Drawing at PageAnchor; Mode=%c", mode);
// Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
if (mode == 'N') {
displayModeNormal(pageData);
} else if (mode == 'C') {
displayModeConfig(pageData);
}
return PAGE_UPDATE;
};
};
static Page *createPage(CommonData &common){
return new PageAnchor(common);
}
/**
* with the code below we make this page known to the PageTask
* we give it a type (name) that can be selected in the config
* we define which function is to be called
* and we provide the number of user parameters we expect
* this will be number of BoatValue pointers in pageData.values
*/
PageDescription registerPageAnchor(
"Anchor", // Page name
createPage, // Action
0, // Number of bus values depends on selection in Web configuration
{"DBS", "HDT", "AWS", "AWD", "LAT", "LON", "HDOP"}, // Names of bus values undepends on selection in Web configuration (refer GwBoatData.h)
true // Show display header on/off
);
#endif
+32 -22
View File
@@ -5,8 +5,9 @@
// These constants have to match the declaration below in : // These constants have to match the declaration below in :
// PageDescription registerPageAutopilot( // PageDescription registerPageAutopilot(
// {"HDM","HDT", "COG", "STW", "SOG", "DBT","XTE", "DTW", "BTW"}, // Bus values we need in the page // {"HDM","HDT", "COG", "STW", "SOG", "DBT","XTE", "DTW", "BTW", "RPOS", "ROT"}, // Bus values we need in the page
const int HowManyValues = 9;
const int HowManyValues = 11;
const int AverageValues = 4; const int AverageValues = 4;
@@ -19,14 +20,21 @@ const int ShowDBT = 5;
const int ShowXTE = 6; const int ShowXTE = 6;
const int ShowDTW = 7; const int ShowDTW = 7;
const int ShowBTW = 8; 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_X0 = 200; // X center point of compass band
const int Compass_Y0 = 220; // Y position of compass lines const int Compass_Y0 = 90; // Y position of compass lines
const int Compass_LineLength = 22; // Length of compass lines //const int Compass_LineLength = 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 const float Compass_LineDelta = 8.0;// Compass band: 1deg = 5 Pixels, 10deg = 50 Pixels
class PageAutopilot : public Page class PageAutopilot : public Page
{ {
static constexpr int8_t LEFT = 0;
static constexpr int8_t CENTER = 1;
static constexpr int8_t RIGHT = 2;
int WhichDataCompass = ShowHDM; // Start value int WhichDataCompass = ShowHDM; // Start value
int WhichDataDisplay = ShowHDM; // Start value int WhichDataDisplay = ShowHDM; // Start value
@@ -38,8 +46,11 @@ class PageAutopilot : public Page
virtual void setupKeys(){ virtual void setupKeys(){
Page::setupKeys(); Page::setupKeys();
commonData->keydata[0].label = "CMP"; commonData->keydata[0].label = "-10";
commonData->keydata[1].label = "SRC"; 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){ virtual int handleKey(int key){
@@ -69,8 +80,8 @@ class PageAutopilot : public Page
GwLog *logger = commonData->logger; GwLog *logger = commonData->logger;
// Old values for hold function // Old values for hold function
static String OldDataText[HowManyValues] = {"", "", "","", "", "","", "", ""}; static String OldDataText[HowManyValues] = {"", "", "", "", "", "","", "", "", "", ""};
static String OldDataUnits[HowManyValues] = {"", "", "","", "", "","", "", ""}; static String OldDataUnits[HowManyValues] = {"", "", "", "", "", "","", "", "", "", ""};
// Get config data // Get config data
String lengthformat = config->getString(config->lengthFormat); String lengthformat = config->getString(config->lengthFormat);
@@ -78,6 +89,7 @@ class PageAutopilot : public Page
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);
bool smallDecimals = config->getBool(config->smallDecimals);
GwApi::BoatValue *bvalue; GwApi::BoatValue *bvalue;
String DataName[HowManyValues]; String DataName[HowManyValues];
@@ -106,15 +118,13 @@ class PageAutopilot : public Page
setBlinkingLED(false); setBlinkingLED(false);
setFlashLED(false); setFlashLED(false);
} }
if (bvalue == NULL) return PAGE_OK; // WTF why this statement?
//*********************************************************** //***********************************************************
// Set display in partial refresh mode // Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
getdisplay().setTextColor(commonData->fgcolor); getdisplay().setTextColor(commonData->fgcolor);
/*
// Horizontal line 2 pix top & bottom // Horizontal line 2 pix top & bottom
// Print data on top half // Print data on top half
getdisplay().fillRect(0, 130, 400, 2, commonData->fgcolor); getdisplay().fillRect(0, 130, 400, 2, commonData->fgcolor);
@@ -125,20 +135,18 @@ class PageAutopilot : public Page
getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().setCursor(10, 120); getdisplay().setCursor(10, 120);
getdisplay().print(DataUnits[WhichDataDisplay]); getdisplay().print(DataUnits[WhichDataDisplay]);
getdisplay().setCursor(190, 120);
getdisplay().setFont(&DSEG7Classic_BoldItalic42pt7b);
if(holdvalues == false){ if(holdvalues == false){
getdisplay().print(DataText[WhichDataDisplay]); // Real value as formated string printBoatValue(DataText[WhichDataDisplay], 190, 120, LEFT, 42, smallDecimals); // Real value as formated string
} }
else{ else{
getdisplay().print(OldDataText[WhichDataDisplay]); // Old value as formated string printBoatValue(OldDataText[WhichDataDisplay], 190, 120, LEFT, 42, smallDecimals); // Old value as formated string
} }
if(DataValid[WhichDataDisplay] == true){ if(DataValid[WhichDataDisplay] == true){
OldDataText[WhichDataDisplay] = DataText[WhichDataDisplay]; // Save the old value OldDataText[WhichDataDisplay] = DataText[WhichDataDisplay]; // Save the old value
OldDataUnits[WhichDataDisplay] = DataUnits[WhichDataDisplay]; // Save the old unit OldDataUnits[WhichDataDisplay] = DataUnits[WhichDataDisplay]; // Save the old unit
} }
*/
// Now draw compass band // Now draw compass band
// Get the data // Get the data
double TheAngle = DataValue[WhichDataCompass]; double TheAngle = DataValue[WhichDataCompass];
@@ -152,13 +160,13 @@ class PageAutopilot : public Page
buffer[0]=0; buffer[0]=0;
getdisplay().setFont(&Ubuntu_Bold16pt8b); getdisplay().setFont(&Ubuntu_Bold16pt8b);
getdisplay().setCursor(10, Compass_Y0-60); getdisplay().setCursor(10, Compass_Y0-40);
getdisplay().print(DataName[WhichDataCompass]); // Page name getdisplay().print(DataName[WhichDataCompass]); // Page name
// Draw compass base line and pointer // Draw compass base line and pointer
getdisplay().fillRect(0, Compass_Y0, 400, 3, commonData->fgcolor); getdisplay().fillRect(0, Compass_Y0, 400, 3, commonData->fgcolor);
getdisplay().fillTriangle(Compass_X0,Compass_Y0-40,Compass_X0-10,Compass_Y0-80,Compass_X0+10,Compass_Y0-80,commonData->fgcolor); //getdisplay().fillTriangle(Compass_X0,Compass_Y0-40,Compass_X0-10,Compass_Y0-80,Compass_X0+10,Compass_Y0-80,commonData->fgcolor);
getdisplay().fillTriangle(Compass_X0,Compass_Y0-30,Compass_X0-10,Compass_Y0-60,Compass_X0+10,Compass_Y0-60,commonData->fgcolor);
// Draw trendlines // Draw trendlines
for ( int i = 1; i < abs(TheTrend) / 2; i++){ for ( int i = 1; i < abs(TheTrend) / 2; i++){
int x1; int x1;
@@ -238,6 +246,8 @@ class PageAutopilot : public Page
// if ( x_test > 390) // if ( x_test > 390)
// x_test = 320; // x_test = 320;
displayRudderPosition(DataValue[ShowSOG], 20, 200, 160, commonData->fgcolor, commonData->bgcolor);
return PAGE_UPDATE; return PAGE_UPDATE;
}; };
@@ -256,7 +266,7 @@ PageDescription registerPageAutopilot(
"Autopilot", // Page name "Autopilot", // Page name
createPage, // Action createPage, // Action
0, // Number of bus values depends on selection in Web configuration 0, // Number of bus values depends on selection in Web configuration
{"HDM","HDT", "COG", "STW", "SOG", "DBT","XTE", "DTW", "BTW"}, // Bus values we need in the page {"HDM","HDT", "COG", "STW", "SOG", "DBT","XTE", "DTW", "BTW", "RPOS", "ROT"}, // Bus values we need in the page
true // Show display header on/off true // Show display header on/off
); );
+9 -16
View File
@@ -5,6 +5,10 @@
class PageBME280 : public Page class PageBME280 : public Page
{ {
static constexpr int8_t LEFT = 0;
static constexpr int8_t CENTER = 1;
static constexpr int8_t RIGHT = 2;
public: public:
PageBME280(CommonData &common){ PageBME280(CommonData &common){
commonData = &common; commonData = &common;
@@ -37,6 +41,7 @@ class PageBME280 : public Page
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 useenvsensor = config->getString(config->useEnvSensor); String useenvsensor = config->getString(config->useEnvSensor);
bool smallDecimals = config->getBool(config->smallDecimals);
// Get sensor values #1 // Get sensor values #1
String name1 = "Temp"; // Value name String name1 = "Temp"; // Value name
@@ -105,7 +110,7 @@ class PageBME280 : public Page
//*********************************************************** //***********************************************************
// Set display in partial refresh mode // Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
getdisplay().setTextColor(commonData->fgcolor); getdisplay().setTextColor(commonData->fgcolor);
@@ -121,12 +126,8 @@ class PageBME280 : public Page
getdisplay().setCursor(20, 90); getdisplay().setCursor(20, 90);
getdisplay().print(unit1); // Unit getdisplay().print(unit1); // Unit
// Switch font if format for any values
getdisplay().setFont(&DSEG7Classic_BoldItalic30pt7b);
getdisplay().setCursor(180, 90);
// Show bus data // Show bus data
getdisplay().print(svalue1); // Real value as formated string printBoatValue(svalue1, 380, 90, RIGHT, 30, smallDecimals); // Real value as formated string
// ############### Horizontal Line ################ // ############### Horizontal Line ################
@@ -145,12 +146,8 @@ class PageBME280 : public Page
getdisplay().setCursor(20, 180); getdisplay().setCursor(20, 180);
getdisplay().print(unit2); // Unit getdisplay().print(unit2); // Unit
// Switch font if format for any values
getdisplay().setFont(&DSEG7Classic_BoldItalic30pt7b);
getdisplay().setCursor(180, 180);
// Show bus data // Show bus data
getdisplay().print(svalue2); // Real value as formated string printBoatValue(svalue2, 380, 180, RIGHT, 30, smallDecimals); // Real value as formated string
// ############### Horizontal Line ################ // ############### Horizontal Line ################
@@ -169,12 +166,8 @@ class PageBME280 : public Page
getdisplay().setCursor(20, 270); getdisplay().setCursor(20, 270);
getdisplay().print(unit3); // Unit getdisplay().print(unit3); // Unit
// Switch font if format for any values
getdisplay().setFont(&DSEG7Classic_BoldItalic30pt7b);
getdisplay().setCursor(140, 270);
// Show bus data // Show bus data
getdisplay().print(svalue3); // Real value as formated string printBoatValue(svalue3, 380, 270, RIGHT, 30, smallDecimals); // Real value as formated string
return PAGE_UPDATE; return PAGE_UPDATE;
}; };
+21 -28
View File
@@ -5,6 +5,10 @@
class PageBattery : public Page class PageBattery : public Page
{ {
static constexpr int8_t LEFT = 0;
static constexpr int8_t CENTER = 1;
static constexpr int8_t RIGHT = 2;
int average = 0; // Average type [0...3], 0=off, 1=10s, 2=60s, 3=300s int average = 0; // Average type [0...3], 0=off, 1=10s, 2=60s, 3=300s
public: public:
@@ -56,6 +60,7 @@ class PageBattery : public Page
String backlightMode = config->getString(config->backlight); String backlightMode = config->getString(config->backlight);
String powsensor1 = config->getString(config->usePowSensor1); String powsensor1 = config->getString(config->usePowSensor1);
bool simulation = config->getBool(config->useSimuData); bool simulation = config->getBool(config->useSimuData);
bool smallDecimals = config->getBool(config->smallDecimals);
// Get voltage value // Get voltage value
String name1 = "VBat"; // Value name String name1 = "VBat"; // Value name
@@ -158,7 +163,7 @@ class PageBattery : public Page
//*********************************************************** //***********************************************************
// Set display in partial refresh mode // Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
// Show average settings // Show average settings
getdisplay().setTextColor(commonData->fgcolor); getdisplay().setTextColor(commonData->fgcolor);
@@ -218,17 +223,13 @@ class PageBattery : public Page
getdisplay().setCursor(20, 90); getdisplay().setCursor(20, 90);
getdisplay().print(unit1); // Unit getdisplay().print(unit1); // Unit
// Show value // Show bus value
getdisplay().setFont(&DSEG7Classic_BoldItalic30pt7b);
getdisplay().setCursor(180, 90);
// Show bus data
if(String(powsensor1) != "off"){ if(String(powsensor1) != "off"){
getdisplay().print(value1,2); // Real value as formated string svalue1 = formatValue(value1, String("formatXdr:U:V"), *commonData);
} } else{
else{ svalue1 = "---";
getdisplay().print("---"); // No sensor data (sensor is off)
} }
printBoatValue(svalue1, 380, 90, RIGHT, 30, smallDecimals); // Real value as formated string
// ############### Horizontal Line ################ // ############### Horizontal Line ################
@@ -247,17 +248,13 @@ class PageBattery : public Page
getdisplay().setCursor(20, 180); getdisplay().setCursor(20, 180);
getdisplay().print(unit2); // Unit getdisplay().print(unit2); // Unit
// Show value // Show bus value
getdisplay().setFont(&DSEG7Classic_BoldItalic30pt7b);
getdisplay().setCursor(180, 180);
// Show bus data
if(String(powsensor1) != "off"){ if(String(powsensor1) != "off"){
getdisplay().print(value2,1); // Real value as formated string svalue2 = formatValue(value2, String("formatXdr:U:V"), *commonData);
} } else{
else{ svalue2 = "---";
getdisplay().print("---"); // No sensor data (sensor is off)
} }
printBoatValue(svalue2, 380, 180, RIGHT, 30, smallDecimals); // Real value as formated string
// ############### Horizontal Line ################ // ############### Horizontal Line ################
@@ -276,17 +273,13 @@ class PageBattery : public Page
getdisplay().setCursor(20, 270); getdisplay().setCursor(20, 270);
getdisplay().print(unit3); // Unit getdisplay().print(unit3); // Unit
// Show value // Show bus value
getdisplay().setFont(&DSEG7Classic_BoldItalic30pt7b);
getdisplay().setCursor(180, 270);
// Show bus data
if(String(powsensor1) != "off"){ if(String(powsensor1) != "off"){
getdisplay().print(value3,1); // Real value as formated string svalue3 = formatValue(value3, String("formatXdr:U:V"), *commonData);
} } else{
else{ svalue3 = "---";
getdisplay().print("---"); // No sensor data (sensor is off)
} }
printBoatValue(svalue3, 380, 270, RIGHT, 30, smallDecimals); // Real value as formated string
return PAGE_UPDATE; return PAGE_UPDATE;
}; };
+35 -52
View File
@@ -2,10 +2,13 @@
#include "Pagedata.h" #include "Pagedata.h"
#include "OBP60Extensions.h" #include "OBP60Extensions.h"
#include "movingAvg.h" // Lib for moving average building
class PageBattery2 : public Page class PageBattery2 : public Page
{ {
static constexpr int8_t LEFT = 0;
static constexpr int8_t CENTER = 1;
static constexpr int8_t RIGHT = 2;
bool init = false; // Marker for init done bool init = false; // Marker for init done
int average = 0; // Average type [0...3], 0=off, 1=10s, 2=60s, 3=300s int average = 0; // Average type [0...3], 0=off, 1=10s, 2=60s, 3=300s
bool trend = true; // Trend indicator [0|1], 0=off, 1=on bool trend = true; // Trend indicator [0|1], 0=off, 1=on
@@ -66,6 +69,7 @@ public:
String batType = config->getString(config->batteryType); String batType = config->getString(config->batteryType);
String backlightMode = config->getString(config->backlight); String backlightMode = config->getString(config->backlight);
String powerSensor = config->getString(config->usePowSensor1); String powerSensor = config->getString(config->usePowSensor1);
bool smallDecimals = config->getBool(config->smallDecimals);
double value1 = 0; // Battery voltage double value1 = 0; // Battery voltage
double value2 = 0; // Battery current double value2 = 0; // Battery current
@@ -184,7 +188,7 @@ public:
//*********************************************************** //***********************************************************
// Set display in partial refresh mode // Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
getdisplay().setTextColor(commonData->fgcolor); getdisplay().setTextColor(commonData->fgcolor);
@@ -199,20 +203,16 @@ public:
getdisplay().print(batType); getdisplay().print(batType);
// Show voltage type // Show voltage type
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b);
getdisplay().setCursor(10, 140);
int bvoltage = 0; int bvoltage = 0;
if(String(batVoltage) == "12V") bvoltage = 12; if(String(batVoltage) == "12V") bvoltage = 12;
else bvoltage = 24; else bvoltage = 24;
getdisplay().print(bvoltage); printBoatValue(String(bvoltage), 10, 140, LEFT, 20, smallDecimals);
getdisplay().setFont(&Ubuntu_Bold16pt8b); getdisplay().setFont(&Ubuntu_Bold16pt8b);
getdisplay().print("V"); getdisplay().print("V");
// Show battery capacity // Show battery capacity
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b); String svalueCapacity = (batCapacity <= 999) ? String(batCapacity) : String(float(batCapacity/1000.0), 1);
getdisplay().setCursor(10, 200); printBoatValue(svalueCapacity, 10, 200, LEFT, 20, smallDecimals);
if(batCapacity <= 999) getdisplay().print(batCapacity, 0);
if(batCapacity > 999) getdisplay().print(float(batCapacity/1000.0), 1);
getdisplay().setFont(&Ubuntu_Bold16pt8b); getdisplay().setFont(&Ubuntu_Bold16pt8b);
if(batCapacity <= 999) getdisplay().print("Ah"); if(batCapacity <= 999) getdisplay().print("Ah");
if(batCapacity > 999) getdisplay().print("kAh"); if(batCapacity > 999) getdisplay().print("kAh");
@@ -249,20 +249,19 @@ public:
} }
// Show fill level in percent // Show fill level in percent
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b); printBoatValue(String(batPercentage), 150, 200, LEFT, 20, smallDecimals);
getdisplay().setCursor(150, 200);
getdisplay().print(batPercentage);
getdisplay().setFont(&Ubuntu_Bold16pt8b); getdisplay().setFont(&Ubuntu_Bold16pt8b);
getdisplay().print("%"); getdisplay().print("%");
// Show time to full discharge // Show time to full discharge
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b); String svalueRange;
getdisplay().setCursor(150, 260);
if((powerSensor == "INA219" || powerSensor == "INA226") && simulation == false){ if((powerSensor == "INA219" || powerSensor == "INA226") && simulation == false){
if(batRange < 9.9) getdisplay().print(batRange, 1); svalueRange = (batRange < 9.9) ? String(batRange, 1) : String(batRange, 0);
else getdisplay().print(batRange, 0);
} }
else getdisplay().print("--"); else{
svalueRange = "--";
}
printBoatValue(svalueRange, 150, 260, LEFT, 20, smallDecimals);
getdisplay().setFont(&Ubuntu_Bold16pt8b); getdisplay().setFont(&Ubuntu_Bold16pt8b);
getdisplay().print("h"); getdisplay().print("h");
@@ -283,54 +282,38 @@ public:
getdisplay().print("Sensor Modul"); getdisplay().print("Sensor Modul");
// Reading bus data or using simulation data // Reading bus data or using simulation data
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b); String svalue1;
getdisplay().setCursor(260, 140); // Check for valid real data, display also if hold values activated
if(simulation == true){ if(valid1 == true || holdvalues == true){
if(batVoltage == "12V"){ svalue1 = formatValue(value1, String("formatXdr:U:V"), *commonData);
value1 = 12.0; } else {
} svalue1 = "---"; // Missing bus data
if(batVoltage == "24V"){
value1 = 24.0;
}
value1 += float(random(0, 5)) / 10; // Simulation data
getdisplay().print(value1,1);
}
else{
// Check for valid real data, display also if hold values activated
if(valid1 == true || holdvalues == true){
// Resolution switching
if(value1 <= 9.9) getdisplay().print(value1, 2);
if(value1 > 9.9 && value1 <= 99.9)getdisplay().print(value1, 1);
if(value1 > 99.9) getdisplay().print(value1, 0);
}
else{
getdisplay().print("---"); // Missing bus data
}
} }
printBoatValue(svalue1, 355, 140, RIGHT, 20, smallDecimals);
getdisplay().setFont(&Ubuntu_Bold16pt8b); getdisplay().setFont(&Ubuntu_Bold16pt8b);
getdisplay().print("V"); getdisplay().print("V");
// Show actual current in A // Show actual current in A
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b); String svalue2;
getdisplay().setCursor(260, 200);
if((powerSensor == "INA219" || powerSensor == "INA226") && simulation == false){ if((powerSensor == "INA219" || powerSensor == "INA226") && simulation == false){
if(value2 <= 9.9) getdisplay().print(value2, 2); svalue2 = formatValue(value2, String("formatXdr:I:A"), *commonData);
if(value2 > 9.9 && value2 <= 99.9)getdisplay().print(value2, 1);
if(value2 > 99.9) getdisplay().print(value2, 0);
} }
else getdisplay().print("---"); else{
svalue2 = "---";
}
printBoatValue(svalue2, 355, 200, RIGHT, 20, smallDecimals);
getdisplay().setFont(&Ubuntu_Bold16pt8b); getdisplay().setFont(&Ubuntu_Bold16pt8b);
getdisplay().print("A"); getdisplay().print("A");
// Show actual consumption in W // Show actual consumption in W
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b); String svalue3;
getdisplay().setCursor(260, 260);
if((powerSensor == "INA219" || powerSensor == "INA226") && simulation == false){ if((powerSensor == "INA219" || powerSensor == "INA226") && simulation == false){
if(value3 <= 9.9) getdisplay().print(value3, 2); svalue3 = formatValue(value3, String("formatXdr:G:"), *commonData);
if(value3 > 9.9 && value3 <= 99.9)getdisplay().print(value3, 1);
if(value3 > 99.9) getdisplay().print(value3, 0);
} }
else getdisplay().print("---"); else{
svalue3 = "---";
}
printBoatValue(svalue3, 355, 260, RIGHT, 20, smallDecimals);
getdisplay().setFont(&Ubuntu_Bold16pt8b); getdisplay().setFont(&Ubuntu_Bold16pt8b);
getdisplay().print("W"); getdisplay().print("W");
+6 -6
View File
@@ -375,7 +375,7 @@ public:
//*********************************************************** //***********************************************************
// Set display in partial refresh mode // Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
getdisplay().setTextColor(commonData->fgcolor); getdisplay().setTextColor(commonData->fgcolor);
@@ -442,8 +442,8 @@ public:
uint16_t wDigit, hDigit; uint16_t wDigit, hDigit;
uint16_t wColon, hColon; uint16_t wColon, hColon;
getdisplay().getTextBounds("00", 0, 0, &x0, &y0, &wDigit, &hDigit); displayGetTextBounds("00", 0, 0, &x0, &y0, &wDigit, &hDigit);
getdisplay().getTextBounds(":", 0, 0, &x0, &y0, &wColon, &hColon); displayGetTextBounds(":", 0, 0, &x0, &y0, &wColon, &hColon);
uint16_t totalWidth = 3 * wDigit + 2 * wColon; uint16_t totalWidth = 3 * wDigit + 2 * wColon;
@@ -453,7 +453,7 @@ public:
// Draw time string centered // Draw time string centered
int16_t x1b, y1b; int16_t x1b, y1b;
uint16_t wb, hb; uint16_t wb, hb;
getdisplay().getTextBounds(timeStr, 0, 0, &x1b, &y1b, &wb, &hb); displayGetTextBounds(timeStr, 0, 0, &x1b, &y1b, &wb, &hb);
int16_t textX = (static_cast<int16_t>(getdisplay().width()) - static_cast<int16_t>(wb)) / 2; int16_t textX = (static_cast<int16_t>(getdisplay().width()) - static_cast<int16_t>(wb)) / 2;
int16_t textY = centerY + hb / 2; int16_t textY = centerY + hb / 2;
@@ -520,7 +520,7 @@ public:
int16_t x1b, y1b; int16_t x1b, y1b;
uint16_t wb, hb; uint16_t wb, hb;
getdisplay().getTextBounds(timeStr, 0, 0, &x1b, &y1b, &wb, &hb); displayGetTextBounds(timeStr, 0, 0, &x1b, &y1b, &wb, &hb);
int16_t x = (static_cast<int16_t>(getdisplay().width()) - static_cast<int16_t>(wb)) / 2; int16_t x = (static_cast<int16_t>(getdisplay().width()) - static_cast<int16_t>(wb)) / 2;
int16_t y = 150 + hb / 2; int16_t y = 150 + hb / 2;
@@ -665,7 +665,7 @@ public:
// Print text centered on position x, y // Print text centered on position x, y
int16_t x1c, y1c; // Return values of getTextBounds int16_t x1c, y1c; // Return values of getTextBounds
uint16_t wc, hc; // Return values of getTextBounds uint16_t wc, hc; // Return values of getTextBounds
getdisplay().getTextBounds(ii, int(x), int(y), &x1c, &y1c, &wc, &hc); // Calc width of new string displayGetTextBounds(ii, int(x), int(y), &x1c, &y1c, &wc, &hc); // Calc width of new string
getdisplay().setCursor(x - wc / 2, y + hc / 2); getdisplay().setCursor(x - wc / 2, y + hc / 2);
if (i % 90 == 0) { if (i % 90 == 0) {
getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setFont(&Ubuntu_Bold12pt8b);
+9 -6
View File
@@ -24,6 +24,10 @@ const float Compass_LineDelta = 8.0;// Compass band: 1deg = 5 Pixels, 10deg = 50
class PageCompass : public Page class PageCompass : public Page
{ {
static constexpr int8_t LEFT = 0;
static constexpr int8_t CENTER = 1;
static constexpr int8_t RIGHT = 2;
int WhichDataCompass = ShowHDM; int WhichDataCompass = ShowHDM;
int WhichDataDisplay = ShowHDM; int WhichDataDisplay = ShowHDM;
@@ -75,6 +79,7 @@ class PageCompass : public Page
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);
bool smallDecimals = config->getBool(config->smallDecimals);
GwApi::BoatValue *bvalue; GwApi::BoatValue *bvalue;
String DataName[HowManyValues]; String DataName[HowManyValues];
@@ -109,7 +114,7 @@ class PageCompass : public Page
//*********************************************************** //***********************************************************
// Set display in partial refresh mode // Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
getdisplay().setTextColor(commonData->fgcolor); getdisplay().setTextColor(commonData->fgcolor);
// Horizontal line 2 pix top & bottom // Horizontal line 2 pix top & bottom
@@ -122,14 +127,12 @@ class PageCompass : public Page
getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().setCursor(10, 120); getdisplay().setCursor(10, 120);
getdisplay().print(DataUnits[WhichDataDisplay]); getdisplay().print(DataUnits[WhichDataDisplay]);
getdisplay().setCursor(190, 120);
getdisplay().setFont(&DSEG7Classic_BoldItalic42pt7b);
if(holdvalues == false){ if(holdvalues == false){
getdisplay().print(DataText[WhichDataDisplay]); // Real value as formated string printBoatValue(DataText[WhichDataDisplay], 190, 120, LEFT, 42, smallDecimals); // Real value as formated string
} }
else{ else{
getdisplay().print(OldDataText[WhichDataDisplay]); // Old value as formated string printBoatValue(OldDataText[WhichDataDisplay], 190, 120, LEFT, 42, smallDecimals); // Old value as formated string
} }
if(DataValid[WhichDataDisplay] == true){ if(DataValid[WhichDataDisplay] == true){
OldDataText[WhichDataDisplay] = DataText[WhichDataDisplay]; // Save the old value OldDataText[WhichDataDisplay] = DataText[WhichDataDisplay]; // Save the old value
+519
View File
@@ -0,0 +1,519 @@
#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3
#include "Pagedata.h"
#include "OBP60Extensions.h"
#include "OBPDataOperations.h"
#include <unordered_map>
// leeway K coefficient selection options from OBP configuration page
static const std::unordered_map<std::string, int> leeKMap = {
{ "---", 0 },
{ "Racer [2]", 2 },
{ "Multihull Draggerboard [4]", 4 },
{ "Performance Cruiser [6]", 6 },
{ "Family Cruiser [9]", 9 },
{ "Heavy Displacement [13]", 13 },
{ "Multihull Fixed Keel [16]", 16 }
};
enum bValueIdx {
ROLL = 0,
SET,
DFT,
HDT,
STW,
COG,
SOG,
HDM,
VAR,
AWA,
NUM_VALS
};
// Screen coordinates for boat value, name, unit
struct Points {
int16_t x1, y1;
int16_t x2, y2;
int16_t x3, y3;
};
// Screen coordinates for four boat data values (top-left, bottom-left, top-right, bottom-right)
static constexpr Points POS[] = {
{ 101, 65, 10, 95, 10, 115 }, // Position top left for value, name, unit
{ 101, 270, 10, 220, 10, 190 }, // Position bottom left
{ 395, 65, 390, 95, 390, 115 }, // Position top right
{ 395, 270, 390, 220, 390, 190 } // Position bottom right
};
// Define wave visual (XBM Format)
static constexpr int WAVE_W = 132;
static constexpr int WAVE_H = 20;
static const uint8_t wave_bitmap[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x80, 0x00, 0xff, 0xf8, 0x00, 0x00, 0xff, 0x80, 0x00,
0x7f, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xf0, 0x03, 0xff, 0xfe, 0x00, 0x01, 0xff, 0xf0,
0x01, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xbf, 0xe0, 0x3f, 0xc0, 0xe2, 0x07,
0xff, 0x1f, 0xf8, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0x80, 0x07, 0xff, 0x80,
0x00, 0xff, 0xff, 0xe0, 0x07, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf0, 0x07, 0xfe, 0x00, 0x00, 0x78,
0x0f, 0x80, 0x3f, 0xff, 0x80, 0x00, 0x00, 0x07, 0x00, 0x00, 0x0f, 0xfe, 0x00, 0x01, 0xff, 0x00,
0x00, 0x3f, 0xf0, 0x07, 0xfe, 0x00, 0x00, 0x00, 0x3f, 0xf0, 0x00, 0x3f, 0xff, 0x80, 0x07, 0xff,
0xe0, 0x00, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x7e, 0x00, 0xff, 0xff, 0xf0, 0x3f,
0xe3, 0xf8, 0x01, 0xc0, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xe7, 0xfc, 0x0f, 0xff,
0xff, 0x00, 0x3f, 0x00, 0x00, 0x1f, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xf0, 0x03,
0xff, 0xfc, 0x00, 0x03, 0xf0, 0x00, 0x07, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc0,
0x00, 0x7f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c,
0x00, 0x00, 0x07, 0x80, 0xfc, 0x00, 0x00, 0x3f, 0xc0, 0x00, 0x07, 0xe0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xe0, 0x00, 0x03, 0xff, 0x80, 0x01, 0xff, 0xf8, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x1e, 0x00, 0x0f, 0xff, 0xf0, 0x07, 0xff, 0xff, 0x00, 0xfc, 0x0f, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x03, 0xc0, 0x3e, 0x03, 0xff, 0xff, 0xe0, 0x3f, 0xff, 0xe0, 0x01, 0xe0,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xf8, 0x00, 0x7f, 0xff, 0x00, 0x07, 0xff, 0x80, 0x00,
0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xe0, 0x00, 0x1f, 0xfc, 0x00, 0x00, 0xfe, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00
};
class PageCurrent : public Page {
private:
static constexpr int8_t LEFT = 0;
static constexpr int8_t CENTER = 1;
static constexpr int8_t RIGHT = 2;
GwLog* logger;
int width; // Screen width
int height; // Screen height
bool keylock = false; // Keylock
bool useSimuData;
bool holdValues;
String flashLED;
String backlightMode;
bool smallDecimals;
String leeKStd;
double leeK;
movingAvgAngle<double> SetAvg {8}; // Store average of the last 8 values of SET angle values
movingAvg<double> DftAvg {8}; // Store average of the last 8 values of DFT speed value
// Old values for hold function
String sValueOld[NUM_VALS] = { "", "", "", "", "", "", "", "", "", "" };
String unitOld[NUM_VALS] = { "", "", "", "", "", "", "", "", "", "" };
struct Current {
double set; // direction TO which current flows (0..2*PI, true dir)
double dft; // current drift speed (m/s)
};
double calcLeeway(
double leeK, // leeway coefficient
double roll, // heel of boat (rad)
double stw // speed through water (m/s)
)
{
static constexpr double MINSTW = 0.5; // minimum speed (m/s) for leeway calculation to prevent math explosion
static constexpr double MAXLAY = 30.0 * DEG_TO_RAD; // cap leeway at this level of degree;
if (stw < MINSTW) {
return 0;
}
double lay = leeK * roll / (stw * stw);
return std::min(lay, MAXLAY);
}
double calcCTW(
double awa, // apparent wind angle (0..2PI -> representation within OBP60)
double hdt, // heading true (rad)
double lay // leeway angle (rad)
)
{
double ctw; // course through water (rad true)
if (awa >= M_PI) { // apparent wind > 180° -> comes from port
ctw = WindUtils::to2PI(hdt + lay);
} else { // wind comes from starboard
ctw = WindUtils::to2PI(hdt - lay);
}
return ctw;
}
Current calcSetAndDrift(
double lay, // leeway angle (rad)
double sog, // speed over ground (m/s)
double cog, // course over ground (rad true)
double stw, // speed through water (m/s)
double hdt, // heading true (rad)
double awa // apparent wind angle (rad)
)
{
Current crnt;
double ctw; // course through water (rad true)
ctw = calcCTW(awa, hdt, lay);
// Ground velocity vector (east, north)
double vg_x = sog * std::sin(cog);
double vg_y = sog * std::cos(cog);
// Water-relative velocity vector (east, north)
double vw_x = stw * std::sin(ctw);
double vw_y = stw * std::cos(ctw);
// Current vector = ground - water
double vc_x = vg_x - vw_x;
double vc_y = vg_y - vw_y;
crnt.dft = std::sqrt(vc_x * vc_x + vc_y * vc_y);
crnt.set = std::atan2(vc_x, vc_y); // atan2(x, y) because 0° = North, clockwise positive
crnt.set = WindUtils::to2PI(crnt.set); // internal respresentation of wind is [0..2PI]
// LOG_DEBUG(GwLog::DEBUG, "PageCurrent-setDrift: sog: %.3f, ctw: %.3f, stw: %.3f, vc_x: %.3f, vc_y: %.3f, set: %.3f, drift: %.3f",
// sog, ctw, stw, vc_x, vc_y, crnt.set, crnt.dft);
return crnt;
};
// Draw compass rose with boat direction up; top heading of compass rose in degree [0..2PI]
void drawCompassRose(double topHeading)
{
int radius = 110;
String sDegree;
int centerX = 200;
int centerY = 150;
getdisplay().fillCircle(centerX, centerY, radius + 10, commonData->fgcolor);
getdisplay().fillCircle(centerX, centerY, radius + 7, commonData->bgcolor);
getdisplay().setFont(&Ubuntu_Bold8pt8b);
for (int i = 0; i < 360; i += 10) {
float topAngle = (float)(i * DEG_TO_RAD - topHeading); // Calculate the relative angle for drawing; subtracting topHeading shifts the entire rose
float sinVal = sin(topAngle);
float cosVal = cos(topAngle);
// Coordinates for the labels (positioned slightly inside the rose)
float xLabel = centerX + (radius - 27) * sinVal;
float yLabel = centerY - (radius - 24) * cosVal;
if (i % 30 == 0) {
// Draw scale markers (triangles)
float dx = 1;
float xx1 = -dx;
float xx2 = +dx;
float yy1 = -(radius - 10);
float yy2 = -(radius + 10);
getdisplay().fillTriangle(centerX + (int)(cosVal * xx1 - sinVal * yy1), centerY + (int)(sinVal * xx1 + cosVal * yy1),
centerX + (int)(cosVal * xx2 - sinVal * yy1), centerY + (int)(sinVal * xx2 + cosVal * yy1),
centerX + (int)(cosVal * xx1 - sinVal * yy2), centerY + (int)(sinVal * xx1 + cosVal * yy2), commonData->fgcolor);
getdisplay().fillTriangle(centerX + (int)(cosVal * xx2 - sinVal * yy1), centerY + (int)(sinVal * xx2 + cosVal * yy1),
centerX + (int)(cosVal * xx1 - sinVal * yy2), centerY + (int)(sinVal * xx1 + cosVal * yy2),
centerX + (int)(cosVal * xx2 - sinVal * yy2), centerY + (int)(sinVal * xx2 + cosVal * yy2), commonData->fgcolor);
// Print degree labels
sDegree = String(i);
int16_t x1, y1;
uint16_t w, h;
displayGetTextBounds(sDegree, (int)xLabel, (int)yLabel, &x1, &y1, &w, &h);
getdisplay().setCursor(xLabel - w / 2, yLabel + h / 2);
getdisplay().print(sDegree);
} else {
// Draw dots for intermediate 10 degree steps
float xDot = centerX + radius * sinVal;
float yDot = centerY - radius * cosVal;
getdisplay().fillCircle((int)xDot, (int)yDot, 2, commonData->fgcolor);
}
}
}
void drawRotatedArrow(
float size, // size of the arrow = speed in m/s [0.5..5.2]
float direction // angle the arrow is pointing to [0..2PI]
)
{
static constexpr double MINSIZE = 0.05; // minimum current set (size) (m/s) for arrow size
if (size < MINSIZE) { // no arrow for current set below 0.05 m/s
return;
}
// size [0.5 .. 5.2] -> length [60 .. 130]
float maxsize = constrain(size, 0.5, 5.2);
float length = 60.0 + ((maxsize - 0.5) / (5.2 - 0.5)) * (130.0 - 60.0);
int16_t cx = width / 2;
int16_t cy = height / 2;
// geometry (arrow upwards)
float h = length / 2.0;
float w = length / 4.0;
float headW = length / 1.5;
float splitY = -h / 3;
struct Pt {
float x, y;
};
// 7 edges of arrow
Pt p[7] = {
{ -w / 2, h }, { w / 2, h }, { w / 2, splitY }, { headW / 2, splitY },
{ 0, -h }, { -headW / 2, splitY }, { -w / 2, splitY }
};
// edges of arrow with reduced size of 1 px for thicker frame
float offset = 1.0;
Pt p_in[7] = {
{ -w / 2 + offset, h - offset }, { w / 2 - offset, h - offset },
{ w / 2 - offset, splitY + offset }, { headW / 2 - offset, splitY + offset },
{ 0, -h + offset }, { -headW / 2 + offset, splitY + offset },
{ -w / 2 + offset, splitY + offset }
};
// calculate rotation
float s = sin(direction);
float c = cos(direction);
int16_t rx[7], ry[7];
for (int i = 0; i < 7; i++) {
// rotate frame
rx[i] = cx + (int16_t)(p[i].x * c - p[i].y * s);
ry[i] = cy + (int16_t)(p[i].x * s + p[i].y * c);
}
// fill arrow with white area to delete any background data
getdisplay().fillTriangle(rx[0], ry[0], rx[1], ry[1], rx[2], ry[2], commonData->bgcolor);
getdisplay().fillTriangle(rx[0], ry[0], rx[2], ry[2], rx[6], ry[6], commonData->bgcolor);
getdisplay().fillTriangle(rx[5], ry[5], rx[4], ry[4], rx[3], ry[3], commonData->bgcolor);
// draw arrow frame with "overdraw" for constant thickness
for (int i = 0; i < 7; i++) {
int next = (i + 1) % 7;
int16_t x0 = rx[i], y0 = ry[i];
int16_t x1 = rx[next], y1 = ry[next];
// original line
getdisplay().drawLine(x0, y0, x1, y1, commonData->fgcolor);
// offset by 1 pixel in x or y depending on delta
if (abs(x1 - x0) > abs(y1 - y0)) {
getdisplay().drawLine(x0, y0 + 1, x1, y1 + 1, commonData->fgcolor);
} else {
getdisplay().drawLine(x0 + 1, y0, x1 + 1, y1, commonData->fgcolor);
}
}
}
public:
PageCurrent(CommonData& common)
{
commonData = &common;
logger = commonData->logger;
LOG_DEBUG(GwLog::LOG, "Instantiate PageCurrent");
width = getdisplay().width(); // Screen width
height = getdisplay().height(); // Screen height
// Get config data
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);
smallDecimals = commonData->config->getBool(commonData->config->smallDecimals);
leeKStd = commonData->config->getString(commonData->config->leeKStd);
auto it = leeKMap.find(leeKStd.c_str());
if (it != leeKMap.end()) {
leeK = it->second;
} else if (leeKStd == "Individual value") {
leeK = (commonData->config->getString(commonData->config->leeK)).toDouble();
}
LOG_DEBUG(GwLog::DEBUG, "PageCurrent: leeKStd: %s, leeK: %.3f", leeKStd.c_str(), leeK);
// Initialize average buffer for SET and DFT
SetAvg.begin();
DftAvg.begin();
}
virtual int handleKey(int key)
{
// Keylock function
if (key == 11) { // Code for keylock
commonData->keylock = !commonData->keylock;
return 0; // Commit the key
}
return key;
}
virtual void displayNew(PageData& pageData)
{
#ifdef BOARD_OBP60S3
// Clear optical warning
if (flashLED == "Limit Violation") {
setBlinkingLED(false);
setFlashLED(false);
}
#endif
}
int displayPage(PageData& pageData)
{
double lay;
Current current;
LOG_DEBUG(GwLog::LOG, "Display PageCurrent");
// Get boat values for page
// 0=ROLL, 1=SET, 2=DFT, 3=HDT, 4=STW, 5=COG, 6=SOG, 7=HDM, 8=VAR, 9=AWA
std::vector<GwApi::BoatValue*> bValue(pageData.values.begin(), pageData.values.end());
LOG_DEBUG(GwLog::DEBUG, "PageCurrent: printing #1: %s, %.3f, valid: %d, #2: %s, %.3f, valid: %d, #3: %s, %.3f, valid: %d, #4: %s, %.3f, valid: %d, #5: %s, %.3f, valid: %d",
bValue[ROLL]->getName().c_str(), bValue[ROLL]->value, bValue[ROLL]->valid, bValue[SET]->getName().c_str(), bValue[SET]->value, bValue[SET]->valid,
bValue[DFT]->getName().c_str(), bValue[DFT]->value, bValue[DFT]->valid, bValue[HDT]->getName().c_str(), bValue[HDT]->value, bValue[HDT]->valid,
bValue[STW]->getName().c_str(), bValue[STW]->value, bValue[STW]->valid);
// Calculate current
//***********************************************************
if (!bValue[SET]->valid || !bValue[DFT]->valid) { // If SET or DRIFT not available, try to calculate them
if (bValue[SOG]->valid && bValue[COG]->valid && bValue[STW]->valid) {
// calculate current only if all required values are available; we accept missing AWA, because this is only required for leeway adjustment
if (!bValue[HDT]->valid) { // HDT not available
if (bValue[HDM]->valid) {
if (bValue[VAR]->valid) {
bValue[HDT]->value = bValue[HDM]->value + bValue[VAR]->value; // Use corrected HDM if HDT is not available
bValue[HDT]->value = WindUtils::to2PI(bValue[HDT]->value);
} else {
// if HDT cannot be fully substituted by HDM+VAR, continue with HDM only
bValue[HDT] = bValue[HDM];
}
bValue[HDT]->valid = true;
}
}
if (!bValue[ROLL]->valid || (bValue[ROLL]->getFormat() != "formatXdr:A:D" && bValue[ROLL]->getFormat() != "formatXdr:A:rd" && bValue[ROLL]->getFormat() != "formatCourse")) {
bValue[ROLL]->value = 0; // delete last value if roll value is not valid anymore (gateway keeps last value) or wrong type selected by user
}
LOG_DEBUG(GwLog::DEBUG, "PageCurrent: Roll: %s, %.3f, valid: %d, format: %s",
bValue[ROLL]->getName().c_str(), bValue[ROLL]->value, bValue[ROLL]->valid, bValue[ROLL]->getFormat().c_str());
lay = calcLeeway(leeK, bValue[ROLL]->value, bValue[STW]->value);
if (bValue[HDT]->valid) { // That's either HDT or HDM
double awaVal = (bValue[AWA]->valid ? bValue[AWA]->value : 0.0); // catch potentially missing AWA
current = calcSetAndDrift(lay, bValue[SOG]->value, bValue[COG]->value, bValue[STW]->value, bValue[HDT]->value, awaVal);
bValue[SET]->value = current.set;
bValue[SET]->setFormat("formatCourse");
bValue[SET]->valid = true;
bValue[DFT]->value = current.dft;
bValue[DFT]->setFormat("formatKnots");
bValue[DFT]->valid = true;
}
}
}
// averaging current for smoother output
if (bValue[SET]->valid && bValue[DFT]->valid) { // Are SET and DRIFT now available?
bValue[SET]->value = SetAvg.reading(bValue[SET]->value);
bValue[DFT]->value = DftAvg.reading(bValue[DFT]->value);
}
LOG_DEBUG(GwLog::DEBUG, "PageCurrent: leeKStd: %s, leeK: %.3f, lay: %.3f, roll: %.3f, stw: %.3f, awa: %.3f, hdt: %.3f, cog: %.3f, sog: %.3f, set: %.3f, dft: %.3f",
leeKStd.c_str(), leeK, lay, bValue[ROLL]->value, bValue[STW]->value, bValue[AWA]->value, bValue[HDT]->value, bValue[COG]->value, bValue[SOG]->value,
bValue[SET]->value, bValue[DFT]->value);
// Draw page
//***********************************************************
displaySetPartialWindow(0, 0, width, height); // Set partial update
getdisplay().setTextColor(commonData->fgcolor);
static const int bValType[] = { HDT, STW, SET, DFT }; // Sequence of boat data types to show on page
for (int i = 0; i < 4; i++) {
String name = xdrDelete(bValue[bValType[i]]->getName()); // Value name
name = name.substring(0, 4); // String length limit for value name
String sValue = formatValue(bValue[bValType[i]], *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
String unit = formatValue(bValue[bValType[i]], *commonData).unit; // Unit of value
unit = unit.substring(0, 6); // String length limit for value unit
// Show boat data value
if (!holdValues || useSimuData) {
printBoatValue(sValue, POS[i].x1, POS[i].y1, RIGHT, 20, smallDecimals);
} else {
printBoatValue(sValueOld[i], POS[i].x1, POS[i].y1, RIGHT, 20, smallDecimals);
}
// Show name
getdisplay().setFont(&Ubuntu_Bold12pt8b);
if (i < 2) { // left side values
getdisplay().setCursor(POS[i].x2, POS[i].y2);
getdisplay().print(name);
} else { // right side values
drawTextRalign(POS[i].x2, POS[i].y2, name);
}
String dUnit; // Unit to show
if (holdValues) {
dUnit = unitOld[i];
} else {
dUnit = unit;
}
// Show unit
getdisplay().setFont(&Ubuntu_Bold8pt8b);
if (i < 2) { // left side values
getdisplay().setCursor(POS[i].x3, POS[i].y3);
getdisplay().print(dUnit);
} else { // right side values
drawTextRalign(POS[i].x3, POS[i].y3, dUnit);
}
if (bValue[bValType[i]]->valid) {
sValueOld[i] = sValue; // Save the old value
unitOld[i] = unit; // Save the old unit
}
}
// Horizontal separator left
getdisplay().fillRect(0, 149, 60, 2, commonData->fgcolor);
// Horizontal separator right
getdisplay().fillRect(339, 149, 60, 2, commonData->fgcolor);
if (bValue[HDT]->valid) {
drawCompassRose(bValue[HDT]->value);
} else {
drawCompassRose(0.0);
};
getdisplay().drawBitmap((width - WAVE_W) / 2, (height - WAVE_H) / 2, wave_bitmap, WAVE_W, WAVE_H, commonData->fgcolor);
if (bValue[SET]->valid && bValue[DFT]->valid) {
drawRotatedArrow(bValue[DFT]->value, WindUtils::to2PI(bValue[SET]->value - bValue[HDT]->value));
}
return PAGE_UPDATE;
};
};
static Page* createPage(CommonData& common)
{
return new PageCurrent(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 registerPageCurrent(
"Current", // Page name
createPage, // Action
1, // Number of bus values depends on selection in Web configuration -> boat value for ROLL
{ "SET", "DFT", "HDT", "STW", "COG", "SOG", "HDM", "VAR", "AWA" }, // Bus values we need in the page
true // Show display header on/off
);
#endif
+38 -29
View File
@@ -5,6 +5,11 @@
class PageDST810 : public Page class PageDST810 : public Page
{ {
private:
static constexpr int8_t LEFT = 0;
static constexpr int8_t CENTER = 1;
static constexpr int8_t RIGHT = 2;
public: public:
PageDST810(CommonData &common){ PageDST810(CommonData &common){
commonData = &common; commonData = &common;
@@ -24,6 +29,9 @@ public:
GwConfigHandler *config = commonData->config; GwConfigHandler *config = commonData->config;
GwLog *logger = commonData->logger; GwLog *logger = commonData->logger;
int valueX, valueY;
int DsegFontSize;
// Old values for hold function // Old values for hold function
static String svalue1old = ""; static String svalue1old = "";
static String unit1old = ""; static String unit1old = "";
@@ -40,6 +48,7 @@ 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);
bool smallDecimals = config->getBool(config->smallDecimals);
// Get boat values #1 // Get boat values #1
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)
@@ -91,7 +100,7 @@ public:
//*********************************************************** //***********************************************************
// Set display in partial refresh mode // Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
getdisplay().setTextColor(commonData->fgcolor); getdisplay().setTextColor(commonData->fgcolor);
@@ -100,7 +109,8 @@ public:
// Show name // Show name
getdisplay().setFont(&Ubuntu_Bold20pt8b); getdisplay().setFont(&Ubuntu_Bold20pt8b);
getdisplay().setCursor(20, 55); getdisplay().setCursor(20, 55);
getdisplay().print("Depth"); // Page name // getdisplay().print("Depth"); // Page name
getdisplay().print(name1); // Page name
// Show unit // Show unit
getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setFont(&Ubuntu_Bold12pt8b);
@@ -112,16 +122,15 @@ public:
getdisplay().print(unit1old); getdisplay().print(unit1old);
} }
// Set font
getdisplay().setFont(&DSEG7Classic_BoldItalic30pt7b);
getdisplay().setCursor(180, 90);
// Show bus data // Show bus data
valueX = 380;
valueY = 90;
DsegFontSize = 30;
if(holdvalues == false){ if(holdvalues == false){
getdisplay().print(svalue1); // Real value as formated string printBoatValue(svalue1, valueX, valueY, RIGHT, DsegFontSize, smallDecimals); // Real value as formated string
} }
else{ else{
getdisplay().print(svalue1old); // Old value as formated string printBoatValue(svalue1old, valueX, valueY, RIGHT, DsegFontSize, smallDecimals); // Old value as formated string
} }
if(valid1 == true){ if(valid1 == true){
svalue1old = svalue1; // Save the old value svalue1old = svalue1; // Save the old value
@@ -138,7 +147,8 @@ public:
// Show name // Show name
getdisplay().setFont(&Ubuntu_Bold20pt8b); getdisplay().setFont(&Ubuntu_Bold20pt8b);
getdisplay().setCursor(20, 145); getdisplay().setCursor(20, 145);
getdisplay().print("Speed"); // Page name // getdisplay().print("Speed"); // Page name
getdisplay().print(name2); // Page name
// Show unit // Show unit
getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setFont(&Ubuntu_Bold12pt8b);
@@ -150,16 +160,15 @@ public:
getdisplay().print(unit2old); getdisplay().print(unit2old);
} }
// Setfont
getdisplay().setFont(&DSEG7Classic_BoldItalic30pt7b);
getdisplay().setCursor(180, 180);
// Show bus data // Show bus data
valueX = 380;
valueY = 180;
DsegFontSize = 30;
if(holdvalues == false){ if(holdvalues == false){
getdisplay().print(svalue2); // Real value as formated string printBoatValue(svalue2, valueX, valueY, RIGHT, DsegFontSize, smallDecimals); // Real value as formated string
} }
else{ else{
getdisplay().print(svalue2old); // Old value as formated string printBoatValue(svalue2old, valueX, valueY, RIGHT, DsegFontSize, smallDecimals); // Old value as formated string
} }
if(valid2 == true){ if(valid2 == true){
svalue2old = svalue2; // Save the old value svalue2old = svalue2; // Save the old value
@@ -176,7 +185,8 @@ public:
// Show name // Show name
getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().setCursor(20, 220); getdisplay().setCursor(20, 220);
getdisplay().print("Log"); // Page name // getdisplay().print("Log"); // Page name
getdisplay().print(name3); // Page name
// Show unit // Show unit
getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setFont(&Ubuntu_Bold8pt8b);
@@ -188,16 +198,15 @@ public:
getdisplay().print(unit3old); getdisplay().print(unit3old);
} }
// Set font
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b);
getdisplay().setCursor(80, 270);
// Show bus data // Show bus data
valueX = 190;
valueY = 270;
DsegFontSize = 20;
if(holdvalues == false){ if(holdvalues == false){
getdisplay().print(svalue3); // Real value as formated string printBoatValue(svalue3, valueX, valueY, RIGHT, DsegFontSize, smallDecimals); // Real value as formated string
} }
else{ else{
getdisplay().print(svalue3old); // Old value as formated string printBoatValue(svalue3old, valueX, valueY, RIGHT, DsegFontSize, smallDecimals); // Old value as formated string
} }
if(valid3 == true){ if(valid3 == true){
svalue3old = svalue3; // Save the old value svalue3old = svalue3; // Save the old value
@@ -214,7 +223,8 @@ public:
// Show name // Show name
getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().setCursor(220, 220); getdisplay().setCursor(220, 220);
getdisplay().print("Temp"); // Page name // getdisplay().print("Temp"); // Page name
getdisplay().print(name4); // Page name
// Show unit // Show unit
getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setFont(&Ubuntu_Bold8pt8b);
@@ -226,16 +236,15 @@ public:
getdisplay().print(unit4old); getdisplay().print(unit4old);
} }
// Set font
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b);
getdisplay().setCursor(280, 270);
// Show bus data // Show bus data
valueX = 390;
valueY = 270;
DsegFontSize = 20;
if(holdvalues == false){ if(holdvalues == false){
getdisplay().print(svalue4); // Real value as formated string printBoatValue(svalue4, valueX, valueY, RIGHT, DsegFontSize, smallDecimals); // Real value as formated string
} }
else{ else{
getdisplay().print(svalue4old); // Old value as formated string printBoatValue(svalue4old, valueX, valueY, RIGHT, DsegFontSize, smallDecimals); // Old value as formated string
} }
if(valid4 == true){ if(valid4 == true){
svalue4old = svalue4; // Save the old value svalue4old = svalue4; // Save the old value
+1 -1
View File
@@ -106,7 +106,7 @@ bool button5 = false;
//*********************************************************** //***********************************************************
// Set display in partial refresh mode // Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update displaySetPartialWindow(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);
// Write text // Write text
+1 -1
View File
@@ -138,7 +138,7 @@ class PageFluid : public Page
//*********************************************************** //***********************************************************
// Set display in partial refresh mode // Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height());
getdisplay().setTextColor(commonData->fgcolor); getdisplay().setTextColor(commonData->fgcolor);
+47 -40
View File
@@ -5,6 +5,11 @@
class PageFourValues : public Page class PageFourValues : public Page
{ {
private:
static constexpr int8_t LEFT = 0;
static constexpr int8_t CENTER = 1;
static constexpr int8_t RIGHT = 2;
public: public:
PageFourValues(CommonData &common){ PageFourValues(CommonData &common){
commonData = &common; commonData = &common;
@@ -24,6 +29,9 @@ class PageFourValues : public Page
GwConfigHandler *config = commonData->config; GwConfigHandler *config = commonData->config;
GwLog *logger = commonData->logger; GwLog *logger = commonData->logger;
int valueX, valueY;
int DsegFontSize;
// Old values for hold function // Old values for hold function
static String svalue1old = ""; static String svalue1old = "";
static String unit1old = ""; static String unit1old = "";
@@ -35,11 +43,9 @@ class PageFourValues : public Page
static String unit4old = ""; static String unit4old = "";
// Get config data // Get config data
String lengthformat = config->getString(config->lengthFormat);
// bool simulation = config->getBool(config->useSimuData);
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); bool smallDecimals = config->getBool(config->smallDecimals);
// Get boat values #1 // Get boat values #1
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)
@@ -91,7 +97,7 @@ class PageFourValues : public Page
//*********************************************************** //***********************************************************
// Set display in partial refresh mode // Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
getdisplay().setTextColor(commonData->fgcolor); getdisplay().setTextColor(commonData->fgcolor);
@@ -104,7 +110,7 @@ class PageFourValues : public Page
// Show unit // Show unit
getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(20, 65); getdisplay().setCursor(20, 67);
if(holdvalues == false){ if(holdvalues == false){
getdisplay().print(unit1); // Unit getdisplay().print(unit1); // Unit
} }
@@ -113,25 +119,26 @@ class PageFourValues : public Page
} }
// Switch font if format for any values // Switch font if format for any values
valueX = 380;
if(bvalue1->getFormat() == "formatLatitude" || bvalue1->getFormat() == "formatLongitude"){ if(bvalue1->getFormat() == "formatLatitude" || bvalue1->getFormat() == "formatLongitude"){
getdisplay().setFont(&Ubuntu_Bold12pt8b); valueY = 55;
getdisplay().setCursor(120, 55); DsegFontSize = 12;
} }
else if(bvalue1->getFormat() == "formatTime" || bvalue1->getFormat() == "formatDate"){ else if(bvalue1->getFormat() == "formatTime" || bvalue1->getFormat() == "formatDate"){
getdisplay().setFont(&Ubuntu_Bold12pt8b); valueY = 67;
getdisplay().setCursor(150, 58); DsegFontSize = 20;
} }
else{ else{
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b); valueY = 67;
getdisplay().setCursor(180, 65); DsegFontSize = 20;
} }
// Show bus data // Show bus data
if(holdvalues == false){ if(holdvalues == false){
getdisplay().print(svalue1); // Real value as formated string printBoatValue(svalue1, valueX, valueY, RIGHT, DsegFontSize, smallDecimals); // Real value as formated string
} }
else{ else{
getdisplay().print(svalue1old); // Old value as formated string printBoatValue(svalue1old, valueX, valueY, RIGHT, DsegFontSize, smallDecimals); // Old value as formated string
} }
if(valid1 == true){ if(valid1 == true){
svalue1old = svalue1; // Save the old value svalue1old = svalue1; // Save the old value
@@ -152,7 +159,7 @@ class PageFourValues : public Page
// Show unit // Show unit
getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(20, 133); getdisplay().setCursor(20, 135);
if(holdvalues == false){ if(holdvalues == false){
getdisplay().print(unit2); // Unit getdisplay().print(unit2); // Unit
} }
@@ -162,24 +169,24 @@ class PageFourValues : public Page
// Switch font if format for any values // Switch font if format for any values
if(bvalue2->getFormat() == "formatLatitude" || bvalue2->getFormat() == "formatLongitude"){ if(bvalue2->getFormat() == "formatLatitude" || bvalue2->getFormat() == "formatLongitude"){
getdisplay().setFont(&Ubuntu_Bold12pt8b); valueY = 123;
getdisplay().setCursor(120, 123); DsegFontSize = 12;
} }
else if(bvalue2->getFormat() == "formatTime" || bvalue2->getFormat() == "formatDate"){ else if(bvalue2->getFormat() == "formatTime" || bvalue2->getFormat() == "formatDate"){
getdisplay().setFont(&Ubuntu_Bold12pt8b); valueY = 135;
getdisplay().setCursor(150, 123); DsegFontSize = 20;
} }
else{ else{
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b); valueY = 135;
getdisplay().setCursor(180, 133); DsegFontSize = 20;
} }
// Show bus data // Show bus data
if(holdvalues == false){ if(holdvalues == false){
getdisplay().print(svalue2); // Real value as formated string printBoatValue(svalue2, valueX, valueY, RIGHT, DsegFontSize, smallDecimals); // Real value as formated string
} }
else{ else{
getdisplay().print(svalue2old); // Old value as formated string printBoatValue(svalue2old, valueX, valueY, RIGHT, DsegFontSize, smallDecimals); // Old value as formated string
} }
if(valid2 == true){ if(valid2 == true){
svalue2old = svalue2; // Save the old value svalue2old = svalue2; // Save the old value
@@ -200,7 +207,7 @@ class PageFourValues : public Page
// Show unit // Show unit
getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(20, 201); getdisplay().setCursor(20, 203);
if(holdvalues == false){ if(holdvalues == false){
getdisplay().print(unit3); // Unit getdisplay().print(unit3); // Unit
} }
@@ -210,24 +217,24 @@ class PageFourValues : public Page
// Switch font if format for any values // Switch font if format for any values
if(bvalue3->getFormat() == "formatLatitude" || bvalue3->getFormat() == "formatLongitude"){ if(bvalue3->getFormat() == "formatLatitude" || bvalue3->getFormat() == "formatLongitude"){
getdisplay().setFont(&Ubuntu_Bold12pt8b); valueY = 191;
getdisplay().setCursor(120, 191); DsegFontSize = 16;
} }
else if(bvalue3->getFormat() == "formatTime" || bvalue3->getFormat() == "formatDate"){ else if(bvalue3->getFormat() == "formatTime" || bvalue3->getFormat() == "formatDate"){
getdisplay().setFont(&Ubuntu_Bold12pt8b); valueY = 203;
getdisplay().setCursor(150, 191); DsegFontSize = 20;
} }
else{ else{
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b); valueY = 203;
getdisplay().setCursor(180, 201); DsegFontSize = 20;
} }
// Show bus data // Show bus data
if(holdvalues == false){ if(holdvalues == false){
getdisplay().print(svalue3); // Real value as formated string printBoatValue(svalue3, valueX, valueY, RIGHT, DsegFontSize, smallDecimals); // Real value as formated string
} }
else{ else{
getdisplay().print(svalue3old); // Old value as formated string printBoatValue(svalue3old, valueX, valueY, RIGHT, DsegFontSize, smallDecimals); // Old value as formated string
} }
if(valid3 == true){ if(valid3 == true){
svalue3old = svalue3; // Save the old value svalue3old = svalue3; // Save the old value
@@ -248,7 +255,7 @@ class PageFourValues : public Page
// Show unit // Show unit
getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(20, 269); getdisplay().setCursor(20, 271);
if(holdvalues == false){ if(holdvalues == false){
getdisplay().print(unit4); // Unit getdisplay().print(unit4); // Unit
} }
@@ -258,24 +265,24 @@ class PageFourValues : public Page
// Switch font if format for any values // Switch font if format for any values
if(bvalue4->getFormat() == "formatLatitude" || bvalue4->getFormat() == "formatLongitude"){ if(bvalue4->getFormat() == "formatLatitude" || bvalue4->getFormat() == "formatLongitude"){
getdisplay().setFont(&Ubuntu_Bold12pt8b); valueY = 271;
getdisplay().setCursor(120, 259); DsegFontSize = 16;
} }
else if(bvalue4->getFormat() == "formatTime" || bvalue4->getFormat() == "formatDate"){ else if(bvalue4->getFormat() == "formatTime" || bvalue4->getFormat() == "formatDate"){
getdisplay().setFont(&Ubuntu_Bold12pt8b); valueY = 271;
getdisplay().setCursor(150, 259); DsegFontSize = 20;
} }
else{ else{
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b); valueY = 271;
getdisplay().setCursor(180, 269); DsegFontSize = 20;
} }
// Show bus data // Show bus data
if(holdvalues == false){ if(holdvalues == false){
getdisplay().print(svalue4); // Real value as formated string printBoatValue(svalue4, valueX, valueY, RIGHT, DsegFontSize, smallDecimals); // Real value as formated string
} }
else{ else{
getdisplay().print(svalue4old); // Old value as formated string printBoatValue(svalue4old, valueX, valueY, RIGHT, DsegFontSize, smallDecimals); // Old value as formated string
} }
if(valid4 == true){ if(valid4 == true){
svalue4old = svalue4; // Save the old value svalue4old = svalue4; // Save the old value
+53 -44
View File
@@ -5,6 +5,11 @@
class PageFourValues2 : public Page class PageFourValues2 : public Page
{ {
private:
static constexpr int8_t LEFT = 0;
static constexpr int8_t CENTER = 1;
static constexpr int8_t RIGHT = 2;
public: public:
PageFourValues2(CommonData &common){ PageFourValues2(CommonData &common){
commonData = &common; commonData = &common;
@@ -24,6 +29,9 @@ class PageFourValues2 : public Page
GwConfigHandler *config = commonData->config; GwConfigHandler *config = commonData->config;
GwLog *logger = commonData->logger; GwLog *logger = commonData->logger;
int valueX, valueY;
int DsegFontSize;
// Old values for hold function // Old values for hold function
static String svalue1old = ""; static String svalue1old = "";
static String unit1old = ""; static String unit1old = "";
@@ -35,11 +43,9 @@ class PageFourValues2 : public Page
static String unit4old = ""; static String unit4old = "";
// Get config data // Get config data
String lengthformat = config->getString(config->lengthFormat);
// bool simulation = config->getBool(config->useSimuData);
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); bool smallDecimals = config->getBool(config->smallDecimals);
// Get boat values #1 // Get boat values #1
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)
@@ -91,7 +97,7 @@ class PageFourValues2 : public Page
//*********************************************************** //***********************************************************
// Set display in partial refresh mode // Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
getdisplay().setTextColor(commonData->fgcolor); getdisplay().setTextColor(commonData->fgcolor);
@@ -113,25 +119,26 @@ class PageFourValues2 : public Page
} }
// Switch font if format for any values // Switch font if format for any values
valueX = 380;
if(bvalue1->getFormat() == "formatLatitude" || bvalue1->getFormat() == "formatLongitude"){ if(bvalue1->getFormat() == "formatLatitude" || bvalue1->getFormat() == "formatLongitude"){
getdisplay().setFont(&Ubuntu_Bold12pt8b); valueY = 90;
getdisplay().setCursor(100, 90); DsegFontSize = 20;
} }
else if(bvalue1->getFormat() == "formatTime" || bvalue1->getFormat() == "formatDate"){ else if(bvalue1->getFormat() == "formatTime" || bvalue1->getFormat() == "formatDate"){
getdisplay().setFont(&Ubuntu_Bold12pt8b); valueY = 80;
getdisplay().setCursor(180, 77); DsegFontSize = 20;
} }
else{ else{
getdisplay().setFont(&DSEG7Classic_BoldItalic30pt7b); valueY = 90;
getdisplay().setCursor(180, 90); DsegFontSize = 30;
} }
// Show bus data // Show bus data
if(holdvalues == false){ if(holdvalues == false){
getdisplay().print(svalue1); // Real value as formated string printBoatValue(svalue1, valueX, valueY, RIGHT, DsegFontSize, smallDecimals); // Real value as formated string
} }
else{ else{
getdisplay().print(svalue1old); // Old value as formated string printBoatValue(svalue1old, valueX, valueY, RIGHT, DsegFontSize, smallDecimals); // Old value as formated string
} }
if(valid1 == true){ if(valid1 == true){
svalue1old = svalue1; // Save the old value svalue1old = svalue1; // Save the old value
@@ -162,24 +169,24 @@ class PageFourValues2 : public Page
// Switch font if format for any values // Switch font if format for any values
if(bvalue2->getFormat() == "formatLatitude" || bvalue2->getFormat() == "formatLongitude"){ if(bvalue2->getFormat() == "formatLatitude" || bvalue2->getFormat() == "formatLongitude"){
getdisplay().setFont(&Ubuntu_Bold12pt8b); valueY = 180;
getdisplay().setCursor(100, 180); DsegFontSize = 20;
} }
else if(bvalue2->getFormat() == "formatTime" || bvalue2->getFormat() == "formatDate"){ else if(bvalue2->getFormat() == "formatTime" || bvalue2->getFormat() == "formatDate"){
getdisplay().setFont(&Ubuntu_Bold12pt8b); valueY = 170;
getdisplay().setCursor(180, 158); DsegFontSize = 20;
} }
else{ else{
getdisplay().setFont(&DSEG7Classic_BoldItalic30pt7b); valueY = 180;
getdisplay().setCursor(180, 180); DsegFontSize = 30;
} }
// Show bus data // Show bus data
if(holdvalues == false){ if(holdvalues == false){
getdisplay().print(svalue2); // Real value as formated string printBoatValue(svalue2, valueX, valueY, RIGHT, DsegFontSize, smallDecimals); // Real value as formated string
} }
else{ else{
getdisplay().print(svalue2old); // Old value as formated string printBoatValue(svalue2old, valueX, valueY, RIGHT, DsegFontSize, smallDecimals); // Old value as formated string
} }
if(valid2 == true){ if(valid2 == true){
svalue2old = svalue2; // Save the old value svalue2old = svalue2; // Save the old value
@@ -194,13 +201,13 @@ class PageFourValues2 : public Page
// ############### Value 3 ################ // ############### Value 3 ################
// Show name // Show name
getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setFont(&Ubuntu_Bold16pt8b);
getdisplay().setCursor(20, 220); getdisplay().setCursor(20, 223);
getdisplay().print(name3); // Page name getdisplay().print(name3); // Page name
// Show unit // Show unit
getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setFont(&Ubuntu_Bold10pt8b);
getdisplay().setCursor(20, 240); getdisplay().setCursor(20, 270);
if(holdvalues == false){ if(holdvalues == false){
getdisplay().print(unit3); // Unit getdisplay().print(unit3); // Unit
} }
@@ -209,25 +216,26 @@ class PageFourValues2 : public Page
} }
// Switch font if format for any values // Switch font if format for any values
valueX = 190;
if(bvalue3->getFormat() == "formatLatitude" || bvalue3->getFormat() == "formatLongitude"){ if(bvalue3->getFormat() == "formatLatitude" || bvalue3->getFormat() == "formatLongitude"){
getdisplay().setFont(&Ubuntu_Bold8pt8b); valueY = 250;
getdisplay().setCursor(50, 240); DsegFontSize = 10;
} }
else if(bvalue3->getFormat() == "formatTime" || bvalue3->getFormat() == "formatDate"){ else if(bvalue3->getFormat() == "formatTime" || bvalue3->getFormat() == "formatDate"){
getdisplay().setFont(&Ubuntu_Bold8pt8b); valueY = 253;
getdisplay().setCursor(100, 240); DsegFontSize = 10;
} }
else{ else{
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b); valueY = 270;
getdisplay().setCursor(80, 270); DsegFontSize = 20;
} }
// Show bus data // Show bus data
if(holdvalues == false){ if(holdvalues == false){
getdisplay().print(svalue3); // Real value as formated string printBoatValue(svalue3, valueX, valueY, RIGHT, DsegFontSize, smallDecimals); // Real value as formated string
} }
else{ else{
getdisplay().print(svalue3old); // Old value as formated string printBoatValue(svalue3old, valueX, valueY, RIGHT, DsegFontSize, smallDecimals); // Old value as formated string
} }
if(valid3 == true){ if(valid3 == true){
svalue3old = svalue3; // Save the old value svalue3old = svalue3; // Save the old value
@@ -242,13 +250,13 @@ class PageFourValues2 : public Page
// ############### Value 4 ################ // ############### Value 4 ################
// Show name // Show name
getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setFont(&Ubuntu_Bold16pt8b);
getdisplay().setCursor(220, 220); getdisplay().setCursor(220, 223);
getdisplay().print(name4); // Page name getdisplay().print(name4); // Page name
// Show unit // Show unit
getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setFont(&Ubuntu_Bold10pt8b);
getdisplay().setCursor(220, 240); getdisplay().setCursor(220, 270);
if(holdvalues == false){ if(holdvalues == false){
getdisplay().print(unit4); // Unit getdisplay().print(unit4); // Unit
} }
@@ -257,25 +265,26 @@ class PageFourValues2 : public Page
} }
// Switch font if format for any values // Switch font if format for any values
valueX = 390;
if(bvalue4->getFormat() == "formatLatitude" || bvalue4->getFormat() == "formatLongitude"){ if(bvalue4->getFormat() == "formatLatitude" || bvalue4->getFormat() == "formatLongitude"){
getdisplay().setFont(&Ubuntu_Bold8pt8b); valueY = 250;
getdisplay().setCursor(250, 240); DsegFontSize = 10;
} }
else if(bvalue4->getFormat() == "formatTime" || bvalue4->getFormat() == "formatDate"){ else if(bvalue4->getFormat() == "formatTime" || bvalue4->getFormat() == "formatDate"){
getdisplay().setFont(&Ubuntu_Bold8pt8b); valueY = 253;
getdisplay().setCursor(300, 240); DsegFontSize = 10;
} }
else{ else{
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b); valueY = 270;
getdisplay().setCursor(280, 270); DsegFontSize = 20;
} }
// Show bus data // Show bus data
if(holdvalues == false){ if(holdvalues == false){
getdisplay().print(svalue4); // Real value as formated string printBoatValue(svalue4, valueX, valueY, RIGHT, DsegFontSize, smallDecimals); // Real value as formated string
} }
else{ else{
getdisplay().print(svalue4old); // Old value as formated string printBoatValue(svalue4old, valueX, valueY, RIGHT, DsegFontSize, smallDecimals); // Old value as formated string
} }
if(valid4 == true){ if(valid4 == true){
svalue4old = svalue4; // Save the old value svalue4old = svalue4; // Save the old value
+28 -47
View File
@@ -2,10 +2,13 @@
#include "Pagedata.h" #include "Pagedata.h"
#include "OBP60Extensions.h" #include "OBP60Extensions.h"
#include "movingAvg.h" // Lib for moving average building
class PageGenerator : public Page class PageGenerator : public Page
{ {
static constexpr int8_t LEFT = 0;
static constexpr int8_t CENTER = 1;
static constexpr int8_t RIGHT = 2;
public: public:
PageGenerator(CommonData &common){ PageGenerator(CommonData &common){
commonData = &common; commonData = &common;
@@ -33,6 +36,7 @@ public:
int genPower = config->getInt(config->genPower); int genPower = config->getInt(config->genPower);
String backlightMode = config->getString(config->backlight); String backlightMode = config->getString(config->backlight);
String powerSensor = config->getString(config->usePowSensor3); String powerSensor = config->getString(config->usePowSensor3);
bool smallDecimals = config->getBool(config->smallDecimals);
double value1 = 0; // Solar voltage double value1 = 0; // Solar voltage
double value2 = 0; // Solar current double value2 = 0; // Solar current
@@ -83,7 +87,7 @@ public:
//*********************************************************** //***********************************************************
// Set display in partial refresh mode // Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
getdisplay().setTextColor(commonData->fgcolor); getdisplay().setTextColor(commonData->fgcolor);
@@ -96,20 +100,16 @@ public:
getdisplay().print("Generator"); getdisplay().print("Generator");
// Show voltage type // Show voltage type
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b);
getdisplay().setCursor(10, 140);
int bvoltage = 0; int bvoltage = 0;
if(String(batVoltage) == "12V") bvoltage = 12; if(String(batVoltage) == "12V") bvoltage = 12;
else bvoltage = 24; else bvoltage = 24;
getdisplay().print(bvoltage); printBoatValue(String(bvoltage), 10, 140, LEFT, 20, smallDecimals);
getdisplay().setFont(&Ubuntu_Bold16pt8b); getdisplay().setFont(&Ubuntu_Bold16pt8b);
getdisplay().print("V"); getdisplay().print("V");
// Show solar power // Show solar power
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b); String svalueGenPower = (genPower <= 999) ? String(genPower) : String(float(genPower/1000.0), 1);
getdisplay().setCursor(10, 200); printBoatValue(svalueGenPower, 10, 200, LEFT, 20, smallDecimals);
if(genPower <= 999) getdisplay().print(genPower, 0);
if(genPower > 999) getdisplay().print(float(genPower/1000.0), 1);
getdisplay().setFont(&Ubuntu_Bold16pt8b); getdisplay().setFont(&Ubuntu_Bold16pt8b);
if(genPower <= 999) getdisplay().print("W"); if(genPower <= 999) getdisplay().print("W");
if(genPower > 999) getdisplay().print("kW"); if(genPower > 999) getdisplay().print("kW");
@@ -125,9 +125,7 @@ public:
generatorGraphic(200, 95, commonData->fgcolor, commonData->bgcolor); generatorGraphic(200, 95, commonData->fgcolor, commonData->bgcolor);
// Show load level in percent // Show load level in percent
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b); printBoatValue(String(genPercentage), 150, 200, LEFT, 20, smallDecimals);
getdisplay().setCursor(150, 200);
getdisplay().print(genPercentage);
getdisplay().setFont(&Ubuntu_Bold16pt8b); getdisplay().setFont(&Ubuntu_Bold16pt8b);
getdisplay().print("%"); getdisplay().print("%");
getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setFont(&Ubuntu_Bold8pt8b);
@@ -152,54 +150,37 @@ public:
getdisplay().print("Sensor Modul"); getdisplay().print("Sensor Modul");
// Reading bus data or using simulation data // Reading bus data or using simulation data
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b); String svalue1;
getdisplay().setCursor(260, 140); if(valid1 == true || holdvalues == true){
if(simulation == true){ svalue1 = formatValue(value1, String("formatXdr:U:V"), *commonData);
if(batVoltage == "12V"){ } else {
value1 = 12.0; svalue1 = "---"; // Missing bus data
}
if(batVoltage == "24V"){
value1 = 24.0;
}
value1 += float(random(0, 5)) / 10; // Simulation data
getdisplay().print(value1,1);
}
else{
// Check for valid real data, display also if hold values activated
if(valid1 == true || holdvalues == true){
// Resolution switching
if(value1 <= 9.9) getdisplay().print(value1, 2);
if(value1 > 9.9 && value1 <= 99.9)getdisplay().print(value1, 1);
if(value1 > 99.9) getdisplay().print(value1, 0);
}
else{
getdisplay().print("---"); // Missing bus data
}
} }
printBoatValue(svalue1, 355, 140, RIGHT, 20, smallDecimals);
getdisplay().setFont(&Ubuntu_Bold16pt8b); getdisplay().setFont(&Ubuntu_Bold16pt8b);
getdisplay().print("V"); getdisplay().print("V");
// Show actual current in A // Show actual current in A
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b); String svalue2;
getdisplay().setCursor(260, 200);
if((powerSensor == "INA219" || powerSensor == "INA226") && simulation == false){ if((powerSensor == "INA219" || powerSensor == "INA226") && simulation == false){
if(value2 <= 9.9) getdisplay().print(value2, 2); svalue2 = formatValue(value2, String("formatXdr:I:A"), *commonData);
if(value2 > 9.9 && value2 <= 99.9)getdisplay().print(value2, 1);
if(value2 > 99.9) getdisplay().print(value2, 0);
} }
else getdisplay().print("---"); else{
svalue2 = "---";
}
printBoatValue(svalue2, 355, 200, RIGHT, 20, smallDecimals);
getdisplay().setFont(&Ubuntu_Bold16pt8b); getdisplay().setFont(&Ubuntu_Bold16pt8b);
getdisplay().print("A"); getdisplay().print("A");
// Show actual consumption in W // Show actual consumption in W
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b); String svalue3;
getdisplay().setCursor(260, 260);
if((powerSensor == "INA219" || powerSensor == "INA226") && simulation == false){ if((powerSensor == "INA219" || powerSensor == "INA226") && simulation == false){
if(value3 <= 9.9) getdisplay().print(value3, 2); svalue3 = formatValue(value3, String("formatXdr:G:"), *commonData);
if(value3 > 9.9 && value3 <= 99.9)getdisplay().print(value3, 1);
if(value3 > 99.9) getdisplay().print(value3, 0);
} }
else getdisplay().print("---"); else{
svalue3 = "---";
}
printBoatValue(svalue3, 355, 260, RIGHT, 20, smallDecimals);
getdisplay().setFont(&Ubuntu_Bold16pt8b); getdisplay().setFont(&Ubuntu_Bold16pt8b);
getdisplay().print("W"); getdisplay().print("W");
+2 -2
View File
@@ -68,7 +68,7 @@ public:
//*********************************************************** //***********************************************************
// Set display in partial refresh mode // Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
//******************************************************************************************* //*******************************************************************************************
@@ -105,7 +105,7 @@ public:
// Print text centered on position x, y // Print text centered on position x, y
int16_t x1, y1; // Return values of getTextBounds int16_t x1, y1; // Return values of getTextBounds
uint16_t w, h; // Return values of getTextBounds uint16_t w, h; // Return values of getTextBounds
getdisplay().getTextBounds(ii, int(x), int(y), &x1, &y1, &w, &h); // Calc width of new string displayGetTextBounds(ii, int(x), int(y), &x1, &y1, &w, &h); // Calc width of new string
getdisplay().setCursor(x-w/2, y+h/2); getdisplay().setCursor(x-w/2, y+h/2);
if(i % 30 == 0){ if(i % 30 == 0){
getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setFont(&Ubuntu_Bold8pt8b);
+206 -33
View File
@@ -4,6 +4,7 @@
#include "OBP60Extensions.h" #include "OBP60Extensions.h"
#include "NetworkClient.h" // Network connection #include "NetworkClient.h" // Network connection
#include "ImageDecoder.h" // Image decoder for navigation map #include "ImageDecoder.h" // Image decoder for navigation map
#include <mbedtls/base64.h>
#include "Logo_OBP_400x300_sw.h" #include "Logo_OBP_400x300_sw.h"
@@ -12,6 +13,54 @@
NetworkClient net(JSON_BUFFER); // Define network client NetworkClient net(JSON_BUFFER); // Define network client
ImageDecoder decoder; // Define image decoder ImageDecoder decoder; // Define image decoder
#ifdef TFT_DISPLAY
// Set to true to render a generated RGB565 color-bar test image.
static constexpr bool kShowRgb565StripeTestImage = false;
static void drawRgb565Image(int16_t x, int16_t y, const uint16_t *img, int16_t w, int16_t h) {
if (img == nullptr || w <= 0 || h <= 0) {
return;
}
for (int16_t yy = 0; yy < h; ++yy) {
const uint16_t *row = img + ((size_t)yy * (size_t)w);
for (int16_t xx = 0; xx < w; ++xx) {
getdisplay().drawPixel(x + xx, y + yy, row[xx]);
}
if ((yy & 0x0F) == 0) {
yield();
}
}
}
static void createRgb565StripeImage(uint16_t *img, int16_t w, int16_t h) {
if (img == nullptr || w <= 0 || h <= 0) {
return;
}
static const uint16_t stripes[] = {
0xF800, // red
0xFD20, // orange
0xFFE0, // yellow
0x07E0, // green
0x07FF, // cyan
0x001F, // blue
0xF81F, // magenta
0xFFFF // white
};
const int stripeCount = (int)(sizeof(stripes) / sizeof(stripes[0]));
for (int16_t y = 0; y < h; ++y) {
uint16_t *row = img + ((size_t)y * (size_t)w);
for (int16_t x = 0; x < w; ++x) {
int idx = ((int)x * stripeCount) / (int)w;
if (idx >= stripeCount) {
idx = stripeCount - 1;
}
row[x] = stripes[idx];
}
}
}
#endif
class PageNavigation : public Page class PageNavigation : public Page
{ {
// Values for buttons // Values for buttons
@@ -24,13 +73,19 @@ bool showValues = false; // Show values HDT, SOG, DBT in navigation map
int imageBackupWidth = 0; int imageBackupWidth = 0;
int imageBackupHeight = 0; int imageBackupHeight = 0;
size_t imageBackupSize = 0; size_t imageBackupSize = 0;
size_t imageBackupCapacity = 0;
bool hasImageBackup = false; bool hasImageBackup = false;
bool imageBackupIsRgb565 = 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); imageBackupCapacity = (size_t)GxEPD_WIDTH * (size_t)GxEPD_HEIGHT;
#ifdef TFT_DISPLAY
imageBackupCapacity *= 2U;
#endif
imageBackupData = (uint8_t*)heap_caps_malloc(imageBackupCapacity, MALLOC_CAP_SPIRAM);
} }
// Set botton labels // Set botton labels
@@ -295,6 +350,18 @@ bool showValues = false; // Show values HDT, SOG, DBT in navigation map
mType = 9; mType = 9;
dType = 1; dType = 1;
} }
else if(mapType == "C-Map"){
mType = 103486987;
dType = 1;
}
else if(mapType == "Garmin Fish"){
mType = 113486987;
dType = 1;
}
else if(mapType == "Garmin Nav"){
mType = 123486987;
dType = 1;
}
else{ else{
mType = 1; mType = 1;
dType = 1; dType = 1;
@@ -347,22 +414,33 @@ bool showValues = false; // Show values HDT, SOG, DBT in navigation map
// URL to OBP Maps Converter // URL to OBP Maps Converter
// For more details see: https://github.com/norbert-walter/maps-converter // For more details see: https://github.com/norbert-walter/maps-converter
String url = String("http://") + server + ":" + port + // OBP Server String url = String("http://") + server + ":" + port + // OBP Server
String("/get_image_json?") + // Service: Output B&W picture as JSON (Base64 + gzip) String("/get_image_json?") + // Service: Output B&W picture as JSON (Base64 + gzip)
"zoom=" + zoom + // Default zoom level: 15 #ifdef TFT_DISPLAY
"oformat=3" + // Image output format in JSON: 3=RGB565 format
#else
"oformat=4" + // Image output format in JSON: 4=b/w 1-Bit format
#endif
"&zoom=" + zoom + // Default zoom level: 15
"&lat=" + String(latitude, 6) + // Latitude "&lat=" + String(latitude, 6) + // Latitude
"&lon=" + String(longitude, 6) + // Longitude "&lon=" + String(longitude, 6) + // Longitude
"&mrot=" + mapRot + // Rotation angle navigation map in degree "&mrot=" + mapRot + // Rotation angle navigation map in degree
"&mtype=" + mType + // Default Map: Open Street Map "&mtype=" + mType + // Default Map: Open Street Map
"&dtype=" + dType + // Dithering type: Atkinson dithering #ifdef TFT_DISPLAY
"&width=400" + // With navigation map "&itype=1" + // Image type: 1=Color
"&height=250" + // Height navigation map #else
"&cutout=0" + // No picture cutouts "&itype=4" + // Image type: 4=b/w with dithering
"&tab=0" + // No tab size #endif
"&border=2" + // Border line size: 2 pixel "&dtype=" + dType + // Dithering type: Atkinson dithering (only activ when itype=4 otherwise inactive)
"&symbol=2" + // Symbol: Triangle "&width=400" + // With navigation map
"&height=250" + // Height navigation map
"&cutout=0" + // No picture cutouts (tab, border and alpha are unused when cutout=0)
"&tab=0" + // No tab size (only available when sqare cutouts selected coutout=3...7)
"&border=2" + // Border line size: 2 pixel (only available when sqare cutouts selected)
"&alpha=80" + // Alpha for tabs: 80% visible (only available when sqare cutouts selected)
"&symbol=2" + // Symbol: Triangle
"&srot=" + symbolRot + // Symbol rotation angle "&srot=" + symbolRot + // Symbol rotation angle
"&ssize=15" + // Symbole size: 15 pixel "&ssize=15" + // Symbole size: 15 pixel (center pointer)
"&grid=" + mapGrid // Show grid: On "&grid=" + mapGrid // Show grid: On
; ;
// Draw page // Draw page
@@ -371,19 +449,45 @@ bool showValues = false; // Show values HDT, SOG, DBT in navigation map
// ############### Draw Navigation Map ################ // ############### Draw Navigation Map ################
// Set display in partial refresh mode // Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
getdisplay().setTextColor(commonData->fgcolor); getdisplay().setTextColor(commonData->fgcolor);
// NEW: simple exponential backoff for 1 Hz polling (prevents connection-refused storms)
static uint32_t nextAllowedMs = 0;
static uint8_t failCount = 0;
uint32_t now = millis();
// NEW: if we are in backoff window, skip network call and use backup immediately
bool allowFetch = ((int32_t)(now - nextAllowedMs) >= 0);
// If a network connection to URL then load the navigation map // If a network connection to URL then load the navigation map
if (net.fetchAndDecompressJson(url)) { if (allowFetch && net.fetchAndDecompressJson(url)) {
auto& json = net.json(); // Extract JSON content // NEW: reset backoff on success
int numPix = json["number_pixels"] | 0; // Read number of pixels failCount = 0;
imgWidth = json["width"] | 0; // Read width of image nextAllowedMs = now + 1000; // keep 1 Hz on success
imgHeight = json["height"] | 0; // Read height og image
const char* b64src = json["picture_base64"].as<const char*>(); // Read picture as Base64 content int numPix = net.numberPixels(); // Read number of pixels
size_t b64len = strlen(b64src); // Calculate length of Base64 content imgWidth = net.imageWidth(); // Read width of image
imgHeight = net.imageHeight(); // Read height of image
size_t requiredBytesMono = 0;
size_t requiredBytesRgb565 = 0;
if (imgWidth > 0 && imgHeight > 0){
requiredBytesMono = (size_t)((imgWidth + 7) / 8) * (size_t)imgHeight;
requiredBytesRgb565 = (size_t)imgWidth * (size_t)imgHeight * 2U;
}
if (requiredBytesMono == 0){
LOG_DEBUG(GwLog::ERROR,"Error PageNavigation: invalid image geometry w=%d h=%d",imgWidth,imgHeight);
return PAGE_UPDATE;
}
const char* b64src = net.pictureBase64(); // Read picture as Base64 content
if (b64src == nullptr){
LOG_DEBUG(GwLog::ERROR,"Error PageNavigation: picture_base64 missing");
return PAGE_UPDATE;
}
size_t b64len = net.pictureBase64Len(); // Calculate length of Base64 content
// Copy Base64 content in PSRAM // Copy Base64 content in PSRAM
char* b64 = (char*) heap_caps_malloc(b64len + 1, MALLOC_CAP_SPIRAM); // Allcate PSRAM for Base64 content char* b64 = (char*) heap_caps_malloc(b64len + 1, MALLOC_CAP_SPIRAM); // Allcate PSRAM for Base64 content
if (!b64) { if (!b64) {
@@ -393,32 +497,81 @@ bool showValues = false; // Show values HDT, SOG, DBT in navigation map
memcpy(b64, b64src, b64len + 1); // Copy Base64 content in PSRAM memcpy(b64, b64src, b64len + 1); // Copy Base64 content in PSRAM
// Set image buffer in PSRAM // Set image buffer in PSRAM
//size_t imgSize = getdisplay().width() * getdisplay().height(); size_t imgSize = (numPix > 0) ? (size_t)numPix : requiredBytesMono; // Calculate image size
size_t imgSize = numPix; // Calculate image size if (imgSize < requiredBytesMono){
imgSize = requiredBytesMono;
}
#ifdef TFT_DISPLAY
if (imgSize < requiredBytesRgb565){
imgSize = requiredBytesRgb565;
}
#endif
uint8_t* imageData = (uint8_t*) heap_caps_malloc(imgSize, MALLOC_CAP_SPIRAM); // Allocate PSRAM for image uint8_t* imageData = (uint8_t*) heap_caps_malloc(imgSize, MALLOC_CAP_SPIRAM); // Allocate PSRAM for image
if (!imageData) { if (!imageData) {
LOG_DEBUG(GwLog::ERROR,"Error PageNavigation: PPSRAM alloc image buffer failed"); LOG_DEBUG(GwLog::ERROR,"Error PageNavigation: PSRAM alloc image buffer failed");
free(b64); free(b64);
return PAGE_UPDATE; return PAGE_UPDATE;
} }
// Decode Base64 content to image // Decode Base64 content to image
size_t decodedSize = 0; size_t decodedSize = 0;
decoder.decodeBase64(b64, imageData, imgSize, decodedSize); bool decodeOk = decoder.decodeBase64(b64, b64len, imageData, imgSize, decodedSize);
if (!decodeOk || decodedSize < requiredBytesMono){
int base64Ret = mbedtls_base64_decode(
nullptr,
0,
&decodedSize,
(const unsigned char*)b64,
b64len
);
LOG_DEBUG(GwLog::ERROR,
"Error PageNavigation: decode failed (ok=%d, decoded=%u, required=%u, b64ret=%d)",
decodeOk ? 1 : 0,
(unsigned int)decodedSize,
(unsigned int)requiredBytesMono,
base64Ret
);
free(b64);
free(imageData);
return PAGE_UPDATE;
}
// Copy actual navigation man to ackup map bool imageIsRgb565 = false;
#ifdef TFT_DISPLAY
imageIsRgb565 = (decodedSize >= requiredBytesRgb565);
#endif
#ifdef TFT_DISPLAY
if (kShowRgb565StripeTestImage) {
createRgb565StripeImage(reinterpret_cast<uint16_t*>(imageData), imgWidth, imgHeight);
decodedSize = requiredBytesRgb565;
imageIsRgb565 = true;
}
#endif
// Copy actual navigation map to backup map
imageBackupWidth = imgWidth; imageBackupWidth = imgWidth;
imageBackupHeight = imgHeight; imageBackupHeight = imgHeight;
imageBackupSize = imgSize; imageBackupSize = imgSize;
if (decodedSize > 0) { if (decodedSize > 0 && imageBackupData != nullptr) {
memcpy(imageBackupData, imageData, decodedSize); size_t copySize = (decodedSize > imageBackupCapacity) ? imageBackupCapacity : decodedSize;
imageBackupSize = decodedSize; memcpy(imageBackupData, imageData, copySize);
imageBackupSize = copySize;
} }
hasImageBackup = true; imageBackupIsRgb565 = imageIsRgb565;
hasImageBackup = (imageBackupData != nullptr);
lostCounter = 0; lostCounter = 0;
// Show image (navigation map) // Show image (navigation map)
getdisplay().drawBitmap(0, 25, imageData, imgWidth, imgHeight, commonData->fgcolor); #ifdef TFT_DISPLAY
if (imageIsRgb565) {
drawRgb565Image(0, 25, reinterpret_cast<const uint16_t*>(imageData), imgWidth, imgHeight);
} else {
displayDrawBitmap(0, 25, imageData, imgWidth, imgHeight, commonData->fgcolor);
}
#else
displayDrawBitmap(0, 25, imageData, imgWidth, imgHeight, commonData->fgcolor);
#endif
// Clean PSRAM // Clean PSRAM
free(b64); free(b64);
@@ -426,12 +579,33 @@ bool showValues = false; // Show values HDT, SOG, DBT in navigation map
} }
// If no network connection then use backup navigation map // If no network connection then use backup navigation map
else{ else{
// NEW: update backoff only if we actually attempted a fetch (not when skipping due to backoff)
if (allowFetch) {
// NEW: exponential backoff: 1s,2s,4s,8s,16s,30s (capped)
if (failCount < 6) failCount++;
uint32_t backoffMs = 1000u << failCount;
if (backoffMs > 30000u) backoffMs = 30000u;
nextAllowedMs = now + backoffMs;
} else {
// NEW: we are currently backing off; do not increase failCount further
// nextAllowedMs stays unchanged
}
// Show backup image (backup navigation map) // Show backup image (backup navigation map)
if (hasImageBackup) { if (hasImageBackup) {
getdisplay().drawBitmap(0, 25, imageBackupData, imageBackupWidth, imageBackupHeight, commonData->fgcolor); #ifdef TFT_DISPLAY
if (imageBackupIsRgb565) {
drawRgb565Image(0, 25, reinterpret_cast<const uint16_t*>(imageBackupData), imageBackupWidth, imageBackupHeight);
} else {
displayDrawBitmap(0, 25, imageBackupData, imageBackupWidth, imageBackupHeight, commonData->fgcolor);
}
#else
displayDrawBitmap(0, 25, imageBackupData, imageBackupWidth, imageBackupHeight, commonData->fgcolor);
#endif
} }
// Show info: Connection lost when 5 page refreshes has a connection lost to the map server // Show connection lost info when 5 page refreshes has a connection lost to the map server
// Short connection losts are uncritical // Short connection losts are uncritical
if(lostCounter >= 5){ if(lostCounter >= 5){
getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setFont(&Ubuntu_Bold12pt8b);
@@ -444,7 +618,6 @@ bool showValues = false; // Show values HDT, SOG, DBT in navigation map
lostCounter++; // Increment lost counter lostCounter++; // Increment lost counter
} }
// ############### Draw Values ################ // ############### Draw Values ################
getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setFont(&Ubuntu_Bold12pt8b);
+61 -54
View File
@@ -19,17 +19,6 @@ private:
HALF 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 width; // Screen width
int height; // Screen height int height; // Screen height
@@ -42,8 +31,11 @@ private:
bool useSimuData; bool useSimuData;
bool holdValues; bool holdValues;
String flashLED; String flashLED;
String backlightMode; bool smallDecimals;
String tempFormat;
static constexpr int8_t LEFT = 0;
static constexpr int8_t CENTER = 1;
static constexpr int8_t RIGHT = 2;
// Old values for hold function // Old values for hold function
String sValue1Old = ""; String sValue1Old = "";
@@ -51,38 +43,41 @@ private:
// Data buffer pointer (owned by HstryBuffers) // Data buffer pointer (owned by HstryBuffers)
RingBuffer<uint16_t>* dataHstryBuf = nullptr; RingBuffer<uint16_t>* dataHstryBuf = nullptr;
std::unique_ptr<Chart> dataChart; // Chart object std::unique_ptr<Chart> dataChart = nullptr; // Chart object
// display data value in display <mode> [FULL|HALF] // display data value in display <mode> [FULL|HALF]
void showData(GwApi::BoatValue* bValue1, DisplayMode mode) void showData(GwApi::BoatValue* bValue1, DisplayMode mode)
{ {
int nameXoff, nameYoff, unitXoff, unitYoff, value1Xoff, value1Yoff; int nameXoff, nameYoff, unitX, unitY, unitXoff, unitYoff, valueX, valueY, valueXoff, valueYoff;
const GFXfont *nameFnt, *unitFnt, *valueFnt1, *valueFnt2, *valueFnt3; const GFXfont *nameFnt, *unitFnt;
int valueFontSize1, valueFontSize2, valueFontSize3, DsegFontSize;
if (mode == FULL) { // full size data display if (mode == FULL) { // full size data display
nameXoff = 0; nameXoff = 0;
nameYoff = 0; nameYoff = 0;
nameFnt = &Ubuntu_Bold32pt8b; nameFnt = &Ubuntu_Bold32pt8b;
unitX = 380;
unitY = 100;
unitXoff = 0; unitXoff = 0;
unitYoff = 0; unitYoff = 0;
unitFnt = &Ubuntu_Bold20pt8b; unitFnt = &Ubuntu_Bold20pt8b;
value1Xoff = 0; valueXoff = 0;
value1Yoff = 0; valueYoff = 0;
valueFnt1 = &Ubuntu_Bold20pt8b; valueFontSize1 = 20;
valueFnt2 = &Ubuntu_Bold32pt8b; valueFontSize2 = 30;
valueFnt3 = &DSEG7Classic_BoldItalic60pt7b; valueFontSize3 = 60;
} else { // half size data and chart display } else { // half size data and chart display
nameXoff = -10; nameXoff = -10;
nameYoff = -34; nameYoff = -34;
nameFnt = &Ubuntu_Bold20pt8b; nameFnt = &Ubuntu_Bold20pt8b;
unitXoff = -295; unitX = 10;
unitYoff = -119; unitY = 121;
unitFnt = &Ubuntu_Bold12pt8b; unitFnt = &Ubuntu_Bold12pt8b;
valueFnt1 = &Ubuntu_Bold12pt8b; valueXoff = 0;
value1Xoff = 153; valueYoff = -119;
value1Yoff = -119; valueFontSize1 = 12;
valueFnt2 = &Ubuntu_Bold20pt8b; valueFontSize2 = 20;
valueFnt3 = &DSEG7Classic_BoldItalic42pt7b; valueFontSize3 = 42;
} }
String name1 = xdrDelete(bValue1->getName()); // Value name String name1 = xdrDelete(bValue1->getName()); // Value name
@@ -99,32 +94,38 @@ private:
getdisplay().print(name1); // name getdisplay().print(name1); // name
// Show unit // Show unit
String unitName;
getdisplay().setFont(unitFnt); getdisplay().setFont(unitFnt);
getdisplay().setCursor(305 + unitXoff, 240 + unitYoff);
if (holdValues) { if (holdValues) {
getdisplay().print(unit1Old); // name unitName = unit1Old;
} else { } else {
getdisplay().print(unit1); // name unitName = unit1;
} }
if (mode == FULL) {
drawTextRalign(unitX, unitY, unitName);
} else {
getdisplay().setCursor(unitX, unitY);
getdisplay().print(unitName);
}
// Switch font depending on value format and adjust position // Switch font depending on value format and adjust position
valueX = 380 + valueXoff;
if (bValue1->getFormat() == "formatLatitude" || bValue1->getFormat() == "formatLongitude") { if (bValue1->getFormat() == "formatLatitude" || bValue1->getFormat() == "formatLongitude") {
getdisplay().setFont(valueFnt1); valueY = 180 + valueYoff;
getdisplay().setCursor(20 + value1Xoff, 180 + value1Yoff); DsegFontSize = valueFontSize1;
} else if (bValue1->getFormat() == "formatTime" || bValue1->getFormat() == "formatDate") { } else if (bValue1->getFormat() == "formatTime" || bValue1->getFormat() == "formatDate") {
getdisplay().setFont(valueFnt2); valueY = 200 + valueYoff;
getdisplay().setCursor(20 + value1Xoff, 200 + value1Yoff); DsegFontSize = valueFontSize2;
} else { } else {
getdisplay().setFont(valueFnt3); valueY = 240 + valueYoff;
getdisplay().setCursor(20 + value1Xoff, 240 + value1Yoff); DsegFontSize = valueFontSize3;
} }
// Show bus data // Show bus data
if (!holdValues || useSimuData) { if (!holdValues || useSimuData) {
getdisplay().print(sValue1); // Real value as formated string printBoatValue(sValue1, valueX, valueY, RIGHT, DsegFontSize, smallDecimals);
} else { } else {
getdisplay().print(sValue1Old); // Old value as formated string printBoatValue(sValue1Old, valueX, valueY, RIGHT, DsegFontSize, smallDecimals);
} }
if (valid1 == true) { if (valid1 == true) {
@@ -138,18 +139,19 @@ public:
{ {
commonData = &common; commonData = &common;
logger = commonData->logger; logger = commonData->logger;
GwConfigHandler *config = commonData->config;
LOG_DEBUG(GwLog::LOG, "Instantiate PageOneValue"); LOG_DEBUG(GwLog::LOG, "Instantiate PageOneValue");
width = getdisplay().width(); // Screen width width = getdisplay().width(); // Screen width
height = getdisplay().height(); // Screen height height = getdisplay().height(); // Screen height
getdisplay().setTextWrap(false);
// Get config data // Get config data
// lengthformat = commonData->config->getString(commonData->config->lengthFormat); useSimuData = config->getBool(config->useSimuData);
useSimuData = commonData->config->getBool(commonData->config->useSimuData);
holdValues = commonData->config->getBool(commonData->config->holdvalues); holdValues = commonData->config->getBool(commonData->config->holdvalues);
flashLED = commonData->config->getString(commonData->config->flashLED); flashLED = commonData->config->getString(commonData->config->flashLED);
backlightMode = commonData->config->getString(commonData->config->backlight); smallDecimals = commonData->config->getBool(commonData->config->smallDecimals);
tempFormat = commonData->config->getString(commonData->config->tempFormat); // [K|°C|°F]
} }
virtual void setupKeys() virtual void setupKeys()
@@ -209,8 +211,6 @@ public:
dataIntv = 3; dataIntv = 3;
} else if (dataIntv == 3) { } else if (dataIntv == 3) {
dataIntv = 4; dataIntv = 4;
} else if (dataIntv == 4) {
dataIntv = 8;
} else { } else {
dataIntv = 1; dataIntv = 1;
} }
@@ -245,10 +245,10 @@ public:
dataHstryBuf = pageData.hstryBuffers->getBuffer(bValName1); dataHstryBuf = pageData.hstryBuffers->getBuffer(bValName1);
if (dataHstryBuf) { if (dataHstryBuf) {
dataChart.reset(new Chart(*dataHstryBuf, Chart::dfltChrtDta[bValFormat].range, *commonData, useSimuData)); dataChart.reset(new Chart(*dataHstryBuf, *commonData, useSimuData));
LOG_DEBUG(GwLog::DEBUG, "PageOneValue: Created chart objects for %s", bValName1); LOG_DEBUG(GwLog::DEBUG, "PageOneValue: Created chart object for %s", bValName1);
} else { } else {
LOG_DEBUG(GwLog::DEBUG, "PageOneValue: No chart objects available for %s", bValName1); LOG_DEBUG(GwLog::DEBUG, "PageOneValue: No chart object available for %s", bValName1);
} }
} }
@@ -262,12 +262,13 @@ public:
// Get boat value for page // Get boat value for page
GwApi::BoatValue* bValue1 = pageData.values[0]; // Page boat data element GwApi::BoatValue* bValue1 = pageData.values[0]; // Page boat data element
#ifdef BOARD_OBP60S3
// 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);
} }
#endif
if (bValue1 == NULL) if (bValue1 == NULL)
return PAGE_OK; // no data, no page to display return PAGE_OK; // no data, no page to display
@@ -276,21 +277,27 @@ public:
// Draw page // Draw page
//*********************************************************** //***********************************************************
getdisplay().setPartialWindow(0, 0, width, height); // Set partial update displaySetPartialWindow(0, 0, width, height); // Set partial update
if (pageMode == VALUE || dataHstryBuf == nullptr) { if (dataChart) { // Check only if dataChart object exists at all
if (!dataChart->isValid()) {
dataChart->init(); // try late initialization if chart object could not be properly initialized earlier due to missing boat data
}
}
if (pageMode == VALUE || dataChart == nullptr) {
// show only data value; ignore other pageMode options if no chart supported boat data history buffer is available // show only data value; ignore other pageMode options if no chart supported boat data history buffer is available
showData(bValue1, FULL); showData(bValue1, FULL);
} else if (pageMode == CHART) { // show only data chart } else if (pageMode == CHART) { // show only data chart
if (dataChart) { if (dataChart) {
dataChart->showChrt(HORIZONTAL, FULL_SIZE, dataIntv, PRNT_NAME, PRNT_VALUE, *bValue1); dataChart->showChrt(Chart::HORIZONTAL, Chart::FULL_SIZE, dataIntv, Chart::PRNT_NAME, Chart::PRNT_VALUE, *bValue1);
} }
} else if (pageMode == BOTH) { // show data value and chart } else if (pageMode == BOTH) { // show data value and chart
showData(bValue1, HALF); showData(bValue1, HALF);
if (dataChart) { if (dataChart) {
dataChart->showChrt(HORIZONTAL, HALF_SIZE_BOTTOM, dataIntv, NO_PRNT_NAME, NO_PRNT_VALUE, *bValue1); dataChart->showChrt(Chart::HORIZONTAL, Chart::HALF_SIZE_RIGHT_BOTTOM, dataIntv, Chart::NO_PRNT_NAME, Chart::NO_PRNT_VALUE, *bValue1);
} }
} }
+27 -27
View File
@@ -5,6 +5,10 @@
class PageRollPitch : public Page class PageRollPitch : public Page
{ {
static constexpr int8_t LEFT = 0;
static constexpr int8_t CENTER = 1;
static constexpr int8_t RIGHT = 2;
public: public:
PageRollPitch(CommonData &common){ PageRollPitch(CommonData &common){
commonData = &common; commonData = &common;
@@ -44,12 +48,15 @@ public:
double rolloffset = roffset.toFloat()/360*(2*M_PI); double rolloffset = roffset.toFloat()/360*(2*M_PI);
String poffset = config->getString(config->pitchOffset); String poffset = config->getString(config->pitchOffset);
double pitchoffset = poffset.toFloat()/360*(2*M_PI); double pitchoffset = poffset.toFloat()/360*(2*M_PI);
bool smallDecimals = config->getBool(config->smallDecimals);
// Get boat values for roll // Get boat values for roll
GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list (xdrRoll) GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list (xdrRoll)
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, 5); // String length limit for value name
bool valid1 = bvalue1->valid; // Valid information bool valid1 = bvalue1->valid; // Valid information
String unit1 = formatValue(bvalue1, *commonData).unit; // Unit of value
unit1 = unit1.substring(0, 4); // String length limit for value unit
if(valid1 == true){ if(valid1 == true){
value1 = bvalue1->value + rolloffset; // Raw value for pitch value1 = bvalue1->value + rolloffset; // Raw value for pitch
} }
@@ -73,8 +80,10 @@ public:
// Get boat values for pitch // Get boat values for pitch
GwApi::BoatValue *bvalue2 = pageData.values[1]; // Second element in list (xdrPitch) GwApi::BoatValue *bvalue2 = pageData.values[1]; // Second element in list (xdrPitch)
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, 5); // String length limit for value name
bool valid2 = bvalue2->valid; // Valid information bool valid2 = bvalue2->valid; // Valid information
String unit2 = formatValue(bvalue2, *commonData).unit; // Unit of value
unit2 = unit2.substring(0, 4); // String length limit for value unit
if(valid2 == true){ if(valid2 == true){
value2 = bvalue2->value + pitchoffset; // Raw value for pitch value2 = bvalue2->value + pitchoffset; // Raw value for pitch
} }
@@ -86,7 +95,7 @@ public:
value2 = 0; value2 = 0;
} }
} }
if(value2/(2*PI)*360 > -10 && value2/(2*M_PI)*360 < 10){ if(value2/(2*M_PI)*360 > -10 && value2/(2*M_PI)*360 < 10){
svalue2 = String(value2/(2*M_PI)*360,1); // Convert raw value to string svalue2 = String(value2/(2*M_PI)*360,1); // Convert raw value to string
} }
else{ else{
@@ -116,58 +125,49 @@ public:
//*********************************************************** //***********************************************************
// Set display in partial refresh mode // Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
getdisplay().setTextColor(commonData->fgcolor); getdisplay().setTextColor(commonData->fgcolor);
// Show roll limit // Show roll limit
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b); printBoatValue(String(rolllimit), 10, 65, LEFT, 20, smallDecimals); // Value
getdisplay().setCursor(10, 65);
getdisplay().print(rolllimit); // Value
//getdisplay().print(svalue1); // Value
getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().setCursor(10, 95); getdisplay().setCursor(10, 95);
getdisplay().print("Limit"); // Name getdisplay().print("Limit"); // Name
getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(10, 115); getdisplay().setCursor(10, 115);
getdisplay().print("DEG"); getdisplay().print("Deg");
// Horizintal separator left // Horizintal separator left
getdisplay().fillRect(0, 149, 60, 3, commonData->fgcolor); getdisplay().fillRect(0, 149, 60, 3, commonData->fgcolor);
// Show roll value // Show roll value
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b); if(holdvalues == false) printBoatValue(svalue1, 101, 270, RIGHT, 20, smallDecimals); // Value
getdisplay().setCursor(10, 270); else printBoatValue(svalue1old, 101, 270, RIGHT, 20, smallDecimals);
if(holdvalues == false) getdisplay().print(svalue1); // Value
else getdisplay().print(svalue1old);
getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().setCursor(10, 220); getdisplay().setCursor(10, 220);
getdisplay().print(name1); // Name getdisplay().print(name1); // Name
getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(10, 190); getdisplay().setCursor(10, 190);
getdisplay().print("Deg"); getdisplay().print(unit1); // Unit
// Horizintal separator right // Horizintal separator right
getdisplay().fillRect(340, 149, 80, 3, commonData->fgcolor); getdisplay().fillRect(340, 149, 80, 3, commonData->fgcolor);
// Show pitch value // Show pitch value
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b); if(holdvalues == false) printBoatValue(svalue2, 395, 270, RIGHT, 20, smallDecimals); // Value
getdisplay().setCursor(295, 270); else printBoatValue(svalue2old, 395, 270, RIGHT, 20, smallDecimals);
if(holdvalues == false) getdisplay().print(svalue2); // Value
else getdisplay().print(svalue2old);
getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().setCursor(335, 220); drawTextRalign(395, 220, name2); // Name
getdisplay().print(name2); // Name
getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(335, 190); getdisplay().setCursor(335, 190);
getdisplay().print("Deg"); getdisplay().print(unit2); // Unit
//******************************************************************************************* //*******************************************************************************************
// Draw instrument // Draw instrument
int rInstrument = 100; // Radius of instrument int rInstrument = 100; // Radius of instrument
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
@@ -194,7 +194,7 @@ public:
// Print text centered on position x, y // Print text centered on position x, y
int16_t x1, y1; // Return values of getTextBounds int16_t x1, y1; // Return values of getTextBounds
uint16_t w, h; // Return values of getTextBounds uint16_t w, h; // Return values of getTextBounds
getdisplay().getTextBounds(ii, int(x), int(y), &x1, &y1, &w, &h); // Calc width of new string displayGetTextBounds(ii, int(x), int(y), &x1, &y1, &w, &h); // Calc width of new string
getdisplay().setCursor(x-w/2, y+h/2); getdisplay().setCursor(x-w/2, y+h/2);
if(i % 20 == 0){ if(i % 20 == 0){
getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setFont(&Ubuntu_Bold8pt8b);
@@ -264,11 +264,11 @@ public:
int y0 = 150; int y0 = 150;
int x1 = x0 + 50*cos(value1); int x1 = x0 + 50*cos(value1);
int y1 = y0 + 50*sin(value1); int y1 = y0 + 50*sin(value1);
int x2 = x0 + 50*cos(value1 - pi/2); int x2 = x0 + 50*cos(value1 - M_PI/2);
int y2 = y0 + 50*sin(value1 - pi/2); int y2 = y0 + 50*sin(value1 - M_PI/2);
getdisplay().fillTriangle(x0, y0, x1, y1, x2, y2, commonData->bgcolor); // Clear half top side of boat circle (right triangle) getdisplay().fillTriangle(x0, y0, x1, y1, x2, y2, commonData->bgcolor); // Clear half top side of boat circle (right triangle)
x1 = x0 + 50*cos(value1 + pi); x1 = x0 + 50*cos(value1 + M_PI);
y1 = y0 + 50*sin(value1 + pi); y1 = y0 + 50*sin(value1 + M_PI);
getdisplay().fillTriangle(x0, y0, x1, y1, x2, y2, commonData->bgcolor); // Clear half top side of boat circle (left triangle) getdisplay().fillTriangle(x0, y0, x1, y1, x2, y2, commonData->bgcolor); // Clear half top side of boat circle (left triangle)
getdisplay().fillRect(150, 160, 100, 4, commonData->fgcolor); // Water line getdisplay().fillRect(150, 160, 100, 4, commonData->fgcolor); // Water line
+2 -2
View File
@@ -72,7 +72,7 @@ public:
//*********************************************************** //***********************************************************
// Set display in partial refresh mode // Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
//******************************************************************************************* //*******************************************************************************************
@@ -110,7 +110,7 @@ public:
// Print text centered on position x, y // Print text centered on position x, y
int16_t x1, y1; // Return values of getTextBounds int16_t x1, y1; // Return values of getTextBounds
uint16_t w, h; // Return values of getTextBounds uint16_t w, h; // Return values of getTextBounds
getdisplay().getTextBounds(ii, int(x), int(y), &x1, &y1, &w, &h); // Calc width of new string displayGetTextBounds(ii, int(x), int(y), &x1, &y1, &w, &h); // Calc width of new string
getdisplay().setCursor(x-w/2, y+h/2); getdisplay().setCursor(x-w/2, y+h/2);
if(i % 30 == 0){ if(i % 30 == 0){
getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setFont(&Ubuntu_Bold8pt8b);
+144 -144
View File
@@ -3,168 +3,168 @@
#include "Pagedata.h" #include "Pagedata.h"
#include "OBP60Extensions.h" #include "OBP60Extensions.h"
const int SixValues_x1 = 5; class PageSixValues : public Page {
const int SixValues_DeltaX = 200; private:
GwLog* logger;
GwConfigHandler* config;
const int SixValues_y1 = 23; bool holdValues;
const int SixValues_DeltaY = 83; String flashLED;
String backlightMode;
bool smallDecimals;
const int HowManyValues = 6; static constexpr int8_t LEFT = 0;
static constexpr int8_t CENTER = 1;
static constexpr int8_t RIGHT = 2;
class PageSixValues : public Page static constexpr int SixValues_x1 = 5;
{ static constexpr int SixValues_DeltaX = 200;
public: static constexpr int SixValues_y1 = 22;
PageSixValues(CommonData &common){ static constexpr int SixValues_DeltaY = 83;
static constexpr int HowManyValues = 6;
// Old values for hold function
String OldDataText[HowManyValues] = { "", "", "", "", "", "" };
String OldDataUnit[HowManyValues] = { "", "", "", "", "", "" };
public:
PageSixValues(CommonData& common)
{
commonData = &common; commonData = &common;
common.logger->logDebug(GwLog::LOG,"Instantiate PageSixValues"); config = commonData->config;
logger = commonData->logger;
LOG_DEBUG(GwLog::LOG, "Instantiate PageSixValues");
// Get config data
holdValues = config->getBool(config->holdvalues);
flashLED = config->getString(config->flashLED);
backlightMode = config->getString(config->backlight);
smallDecimals = config->getBool(config->smallDecimals);
} }
virtual int handleKey(int key){ virtual int handleKey(int key)
{
// Code for keylock // Code for keylock
if(key == 11){ if (key == 11) {
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){ int displayPage(PageData& pageData)
GwConfigHandler *config = commonData->config; {
GwLog *logger = commonData->logger; GwApi::BoatValue* bvalue;
String DataName[HowManyValues];
double DataValue[HowManyValues];
bool DataValid[HowManyValues];
String DataText[HowManyValues];
String DataUnit[HowManyValues];
String DataFormat[HowManyValues];
int8_t DsegFontSize = 0;
for (int i = 0; i < HowManyValues; i++) {
// Old values for hold function bvalue = pageData.values[i];
static String OldDataText[HowManyValues] = {"", "", "", "", "", ""}; DataName[i] = xdrDelete(bvalue->getName());
static String OldDataUnits[HowManyValues] = {"", "", "", "", "", ""}; DataName[i] = DataName[i].substring(0, 6); // String length limit for value name
DataValue[i] = bvalue->value; // Value as double in SI unit
// Get config data DataValid[i] = bvalue->valid;
String lengthformat = config->getString(config->lengthFormat); DataText[i] = formatValue(bvalue, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
// bool simulation = config->getBool(config->useSimuData); DataUnit[i] = formatValue(bvalue, *commonData).unit;
bool holdvalues = config->getBool(config->holdvalues); DataFormat[i] = bvalue->getFormat(); // Unit of value
String flashLED = config->getString(config->flashLED); }
String backlightMode = config->getString(config->backlight);
GwApi::BoatValue *bvalue;
String DataName[HowManyValues];
double DataValue[HowManyValues];
bool DataValid[HowManyValues];
String DataText[HowManyValues];
String DataUnits[HowManyValues];
String DataFormat[HowManyValues];
for (int i = 0; i < HowManyValues; i++){
bvalue = pageData.values[i];
DataName[i] = xdrDelete(bvalue->getName());
DataName[i] = DataName[i].substring(0, 6); // String length limit for value name
DataValue[i] = bvalue->value; // Value as double in SI unit
DataValid[i] = bvalue->valid;
DataText[i] = formatValue(bvalue, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
DataUnits[i] = formatValue(bvalue, *commonData).unit;
DataFormat[i] = bvalue->getFormat(); // Unit of value
}
// Optical warning by limit violation (unused)
if(String(flashLED) == "Limit Violation"){
setBlinkingLED(false);
setFlashLED(false);
}
if (bvalue == NULL) return PAGE_OK; // WTF why this statement?
// Draw page
//***********************************************************
// Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
getdisplay().setTextColor(commonData->fgcolor);
for (int i = 0; i < ( HowManyValues / 2 ); i++){ #ifdef BOARD_OBP60S3
if (i < (HowManyValues / 2) - 1) { // Don't draw horizontal line after last line of values -> standard design // Optical warning by limit violation (unused)
// Horizontal line 3 pix if (String(flashLED) == "Limit Violation") {
getdisplay().fillRect(0, SixValues_y1+(i+1)*SixValues_DeltaY, 400, 3, commonData->fgcolor); setBlinkingLED(false);
} setFlashLED(false);
for (int j = 0; j < 2; j++){ }
int ValueIndex = i * 2 + j; #endif
int x0 = SixValues_x1 + j * SixValues_DeltaX; if (bvalue == NULL)
int y0 = SixValues_y1 + i * SixValues_DeltaY; return PAGE_OK;
LOG_DEBUG(GwLog::LOG,"Drawing at PageSixValue: %d %s %f %s", ValueIndex, DataName[ValueIndex], DataValue[ValueIndex], DataFormat[ValueIndex] );
// Draw page
// Show name //***********************************************************
getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().setCursor(x0, y0+25); // Set display in partial refresh mode
getdisplay().print(DataName[ValueIndex]); // Page name displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
getdisplay().setTextWrap(false);
// Show unit getdisplay().setTextColor(commonData->fgcolor);
getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(x0, y0+72); for (int i = 0; i < (HowManyValues / 2); i++) {
if(holdvalues == false){ if (i < (HowManyValues / 2) - 1) { // Don't draw horizontal line after last line of values
getdisplay().print(DataUnits[ValueIndex]); // Unit // Horizontal line 3 px
} getdisplay().fillRect(0, SixValues_y1 + (i + 1) * SixValues_DeltaY, 400, 3, commonData->fgcolor);
else{
getdisplay().print(OldDataUnits[ValueIndex]);
}
// Switch font if format for any values
if(DataFormat[ValueIndex] == "formatLatitude" || DataFormat[ValueIndex] == "formatLongitude"){
getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().setCursor(x0+10, y0+60);
}
else if(DataFormat[ValueIndex] == "formatTime" || DataFormat[ValueIndex] == "formatDate"){
getdisplay().setFont(&Ubuntu_Bold16pt8b);
getdisplay().setCursor(x0+20,y0+55);
}
// pressure in hPa
else if(DataFormat[ValueIndex] == "formatXdr:P:P"){
getdisplay().setFont(&DSEG7Classic_BoldItalic26pt7b);
getdisplay().setCursor(x0+5, y0+70);
}
// RPM
else if(DataFormat[ValueIndex] == "formatXdr:T:R"){
getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b);
getdisplay().setCursor(x0+25, y0+70);
}
else{
getdisplay().setFont(&DSEG7Classic_BoldItalic26pt7b);
if ( DataText[ValueIndex][0] == '-' )
getdisplay().setCursor(x0+25, y0+70);
else
getdisplay().setCursor(x0+65, y0+70);
}
// Show bus data
if(holdvalues == false){
getdisplay().print(DataText[ValueIndex]); // Real value as formated string
}
else{
getdisplay().print(OldDataText[ValueIndex]); // Old value as formated string
}
if(DataValid[ValueIndex] == true){
OldDataText[ValueIndex] = DataText[ValueIndex]; // Save the old value
OldDataUnits[ValueIndex] = DataUnits[ValueIndex]; // Save the old unit
}
}
// Vertical line 3 pix
getdisplay().fillRect(SixValues_x1+SixValues_DeltaX-8, SixValues_y1+i*SixValues_DeltaY, 3, SixValues_DeltaY, commonData->fgcolor);
} }
return PAGE_UPDATE; for (int j = 0; j < 2; j++) {
}; int ValueIndex = i * 2 + j;
int x0 = SixValues_x1 + j * SixValues_DeltaX;
int y0 = SixValues_y1 + i * SixValues_DeltaY;
//LOG_DEBUG(GwLog::DEBUG, "Drawing at PageSixValue: %d %s %f %s %s", ValueIndex, DataName[ValueIndex],
// DataValue[ValueIndex], DataUnit[ValueIndex], DataFormat[ValueIndex]);
// Show name
getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().setCursor(x0, y0 + 25);
getdisplay().print(DataName[ValueIndex]); // Page name
// Show unit
getdisplay().setFont(&Ubuntu_Bold8pt8b);
if (holdValues == false) {
drawTextRalign(x0 + 187, y0 + 19, DataUnit[ValueIndex]); // Unit
} else {
drawTextRalign(x0 + 187, y0 + 19, OldDataUnit[ValueIndex]);
}
// Set font size depending on data type
if (DataFormat[ValueIndex] == "formatLatitude" || DataFormat[ValueIndex] == "formatLongitude") {
DsegFontSize = 12;
} else if (DataFormat[ValueIndex] == "formatTime" || DataFormat[ValueIndex] == "formatDate") {
DsegFontSize = 16;
}
else if (DataFormat[ValueIndex] == "formatXdr:T:R") { // RPM
DsegFontSize = 20;
}
else {
DsegFontSize = 26;
}
// Show bus data value
if (holdValues == false) {
printBoatValue(DataText[ValueIndex], x0 + 187, y0 + 79, RIGHT, DsegFontSize, smallDecimals);
} else {
printBoatValue(OldDataText[ValueIndex], x0 + 187, y0 + 79, RIGHT, DsegFontSize, smallDecimals);
}
if (DataValid[ValueIndex] == true) {
OldDataText[ValueIndex] = DataText[ValueIndex]; // Save the old value
OldDataUnit[ValueIndex] = DataUnit[ValueIndex]; // Save the old unit
}
}
// Vertical line 3 px
getdisplay().fillRect(SixValues_x1 + SixValues_DeltaX - 8, SixValues_y1 + i * SixValues_DeltaY, 3, SixValues_DeltaY, commonData->fgcolor);
}
return PAGE_UPDATE;
}; };
static Page *createPage(CommonData &common){ };
static Page* createPage(CommonData& common)
{
return new PageSixValues(common); return new PageSixValues(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 * and we provide the number of user parameters we expect
* this will be number of BoatValue pointers in pageData.values * this will be number of BoatValue pointers in pageData.values
*/ */
PageDescription registerPageSixValues( PageDescription registerPageSixValues(
"SixValues", // Page name "SixValues", // Page name
createPage, // Action createPage, // Action
6, // Number of bus values depends on selection in Web configuration 6, // Number of bus values depends on selection in Web configuration
true // Show display header on/off true // Show display header on/off
); );
#endif #endif
+5 -5
View File
@@ -73,7 +73,7 @@ public:
//*********************************************************** //***********************************************************
// Set display in partial refresh mode // Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
// current position // current position
getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setFont(&Ubuntu_Bold8pt8b);
@@ -105,19 +105,19 @@ public:
uint16_t w, h; uint16_t w, h;
getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().getTextBounds("N", 0, 150, &x1, &y1, &w, &h); displayGetTextBounds("N", 0, 150, &x1, &y1, &w, &h);
getdisplay().setCursor(c.x - w / 2, c.y - r + h + 3); getdisplay().setCursor(c.x - w / 2, c.y - r + h + 3);
getdisplay().print("N"); getdisplay().print("N");
getdisplay().getTextBounds("S", 0, 150, &x1, &y1, &w, &h); displayGetTextBounds("S", 0, 150, &x1, &y1, &w, &h);
getdisplay().setCursor(c.x - w / 2, c.y + r - 3); getdisplay().setCursor(c.x - w / 2, c.y + r - 3);
getdisplay().print("S"); getdisplay().print("S");
getdisplay().getTextBounds("E", 0, 150, &x1, &y1, &w, &h); displayGetTextBounds("E", 0, 150, &x1, &y1, &w, &h);
getdisplay().setCursor(c.x + r - w - 3, c.y + h / 2); getdisplay().setCursor(c.x + r - w - 3, c.y + h / 2);
getdisplay().print("E"); getdisplay().print("E");
getdisplay().getTextBounds("W", 0, 150, &x1, &y1, &w, &h); displayGetTextBounds("W", 0, 150, &x1, &y1, &w, &h);
getdisplay().setCursor(c.x - r + 3 , c.y + h / 2); getdisplay().setCursor(c.x - r + 3 , c.y + h / 2);
getdisplay().print("W"); getdisplay().print("W");
+28 -47
View File
@@ -2,10 +2,13 @@
#include "Pagedata.h" #include "Pagedata.h"
#include "OBP60Extensions.h" #include "OBP60Extensions.h"
#include "movingAvg.h" // Lib for moving average building
class PageSolar : public Page class PageSolar : public Page
{ {
static constexpr int8_t LEFT = 0;
static constexpr int8_t CENTER = 1;
static constexpr int8_t RIGHT = 2;
public: public:
PageSolar(CommonData &common){ PageSolar(CommonData &common){
commonData = &common; commonData = &common;
@@ -32,6 +35,7 @@ public:
int solPower = config->getInt(config->solarPower); int solPower = config->getInt(config->solarPower);
String backlightMode = config->getString(config->backlight); String backlightMode = config->getString(config->backlight);
String powerSensor = config->getString(config->usePowSensor2); String powerSensor = config->getString(config->usePowSensor2);
bool smallDecimals = config->getBool(config->smallDecimals);
double value1 = 0; // Solar voltage double value1 = 0; // Solar voltage
double value2 = 0; // Solar current double value2 = 0; // Solar current
@@ -82,7 +86,7 @@ public:
//*********************************************************** //***********************************************************
// Set display in partial refresh mode // Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
getdisplay().setTextColor(commonData->fgcolor); getdisplay().setTextColor(commonData->fgcolor);
@@ -92,20 +96,16 @@ public:
getdisplay().print("Solar"); getdisplay().print("Solar");
// Show voltage type // Show voltage type
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b);
getdisplay().setCursor(10, 140);
int bvoltage = 0; int bvoltage = 0;
if(String(batVoltage) == "12V") bvoltage = 12; if(String(batVoltage) == "12V") bvoltage = 12;
else bvoltage = 24; else bvoltage = 24;
getdisplay().print(bvoltage); printBoatValue(String(bvoltage), 10, 140, LEFT, 20, smallDecimals);
getdisplay().setFont(&Ubuntu_Bold16pt8b); getdisplay().setFont(&Ubuntu_Bold16pt8b);
getdisplay().print("V"); getdisplay().print("V");
// Show solar power // Show solar power
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b); String svalueSolPower = (solPower <= 999) ? String(solPower) : String(float(solPower/1000.0), 1);
getdisplay().setCursor(10, 200); printBoatValue(svalueSolPower, 10, 200, LEFT, 20, smallDecimals);
if(solPower <= 999) getdisplay().print(solPower, 0);
if(solPower > 999) getdisplay().print(float(solPower/1000.0), 1);
getdisplay().setFont(&Ubuntu_Bold16pt8b); getdisplay().setFont(&Ubuntu_Bold16pt8b);
if(solPower <= 999) getdisplay().print("W"); if(solPower <= 999) getdisplay().print("W");
if(solPower > 999) getdisplay().print("kW"); if(solPower > 999) getdisplay().print("kW");
@@ -121,9 +121,7 @@ public:
solarGraphic(150, 45, commonData->fgcolor, commonData->bgcolor); solarGraphic(150, 45, commonData->fgcolor, commonData->bgcolor);
// Show load level in percent // Show load level in percent
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b); printBoatValue(String(solPercentage), 150, 200, LEFT, 20, smallDecimals);
getdisplay().setCursor(150, 200);
getdisplay().print(solPercentage);
getdisplay().setFont(&Ubuntu_Bold16pt8b); getdisplay().setFont(&Ubuntu_Bold16pt8b);
getdisplay().print("%"); getdisplay().print("%");
getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setFont(&Ubuntu_Bold8pt8b);
@@ -148,54 +146,37 @@ public:
getdisplay().print("Sensor Modul"); getdisplay().print("Sensor Modul");
// Reading bus data or using simulation data // Reading bus data or using simulation data
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b); String svalue1;
getdisplay().setCursor(260, 140); if(valid1 == true || holdvalues == true){
if(simulation == true){ svalue1 = formatValue(value1, String("formatXdr:U:V"), *commonData);
if(batVoltage == "12V"){ } else {
value1 = 12.0; svalue1 = "---"; // Missing bus data
}
if(batVoltage == "24V"){
value1 = 24.0;
}
value1 += float(random(0, 5)) / 10; // Simulation data
getdisplay().print(value1,1);
}
else{
// Check for valid real data, display also if hold values activated
if(valid1 == true || holdvalues == true){
// Resolution switching
if(value1 <= 9.9) getdisplay().print(value1, 2);
if(value1 > 9.9 && value1 <= 99.9)getdisplay().print(value1, 1);
if(value1 > 99.9) getdisplay().print(value1, 0);
}
else{
getdisplay().print("---"); // Missing bus data
}
} }
printBoatValue(svalue1, 355, 140, RIGHT, 20, smallDecimals);
getdisplay().setFont(&Ubuntu_Bold16pt8b); getdisplay().setFont(&Ubuntu_Bold16pt8b);
getdisplay().print("V"); getdisplay().print("V");
// Show actual current in A // Show actual current in A
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b); String svalue2;
getdisplay().setCursor(260, 200);
if((powerSensor == "INA219" || powerSensor == "INA226") && simulation == false){ if((powerSensor == "INA219" || powerSensor == "INA226") && simulation == false){
if(value2 <= 9.9) getdisplay().print(value2, 2); svalue2 = formatValue(value2, String("formatXdr:I:A"), *commonData);
if(value2 > 9.9 && value2 <= 99.9)getdisplay().print(value2, 1);
if(value2 > 99.9) getdisplay().print(value2, 0);
} }
else getdisplay().print("---"); else{
svalue2 = "---";
}
printBoatValue(svalue2, 355, 200, RIGHT, 20, smallDecimals);
getdisplay().setFont(&Ubuntu_Bold16pt8b); getdisplay().setFont(&Ubuntu_Bold16pt8b);
getdisplay().print("A"); getdisplay().print("A");
// Show actual consumption in W // Show actual consumption in W
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b); String svalue3;
getdisplay().setCursor(260, 260);
if((powerSensor == "INA219" || powerSensor == "INA226") && simulation == false){ if((powerSensor == "INA219" || powerSensor == "INA226") && simulation == false){
if(value3 <= 9.9) getdisplay().print(value3, 2); svalue3 = formatValue(value3, String("formatXdr:G:"), *commonData);
if(value3 > 9.9 && value3 <= 99.9)getdisplay().print(value3, 1);
if(value3 > 99.9) getdisplay().print(value3, 0);
} }
else getdisplay().print("---"); else{
svalue3 = "---";
}
printBoatValue(svalue3, 355, 260, RIGHT, 20, smallDecimals);
getdisplay().setFont(&Ubuntu_Bold16pt8b); getdisplay().setFont(&Ubuntu_Bold16pt8b);
getdisplay().print("W"); getdisplay().print("W");
File diff suppressed because it is too large Load Diff
+35 -21
View File
@@ -5,6 +5,11 @@
class PageThreeValues : public Page class PageThreeValues : public Page
{ {
private:
static constexpr int8_t LEFT = 0;
static constexpr int8_t CENTER = 1;
static constexpr int8_t RIGHT = 2;
public: public:
PageThreeValues(CommonData &common){ PageThreeValues(CommonData &common){
commonData = &common; commonData = &common;
@@ -24,6 +29,9 @@ class PageThreeValues : public Page
GwConfigHandler *config = commonData->config; GwConfigHandler *config = commonData->config;
GwLog *logger = commonData->logger; GwLog *logger = commonData->logger;
int valueX, valueY;
int DsegFontSize;
// Old values for hold function // Old values for hold function
static String svalue1old = ""; static String svalue1old = "";
static String unit1old = ""; static String unit1old = "";
@@ -33,11 +41,10 @@ class PageThreeValues : public Page
static String unit3old = ""; static String unit3old = "";
// Get config data // Get config data
String lengthformat = config->getString(config->lengthFormat);
// bool simulation = config->getBool(config->useSimuData); // bool simulation = config->getBool(config->useSimuData);
bool holdvalues = config->getBool(config->holdvalues); bool holdvalues = config->getBool(config->holdvalues);
String flashLED = config->getString(config->flashLED); String flashLED = config->getString(config->flashLED);
String backlightMode = config->getString(config->backlight); bool smallDecimals = config->getBool(config->smallDecimals);
// Get boat values #1 // Get boat values #1
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)
@@ -80,7 +87,7 @@ class PageThreeValues : public Page
//*********************************************************** //***********************************************************
/// Set display in partial refresh mode /// Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
// ############### Value 1 ################ // ############### Value 1 ################
@@ -101,25 +108,26 @@ class PageThreeValues : public Page
} }
// Switch font if format for any values // Switch font if format for any values
valueX = 380;
if(bvalue1->getFormat() == "formatLatitude" || bvalue1->getFormat() == "formatLongitude"){ if(bvalue1->getFormat() == "formatLatitude" || bvalue1->getFormat() == "formatLongitude"){
getdisplay().setFont(&Ubuntu_Bold20pt8b); valueY = 90;
getdisplay().setCursor(50, 90); DsegFontSize = 20;
} }
else if(bvalue1->getFormat() == "formatTime" || bvalue1->getFormat() == "formatDate"){ else if(bvalue1->getFormat() == "formatTime" || bvalue1->getFormat() == "formatDate"){
getdisplay().setFont(&Ubuntu_Bold20pt8b); valueY = 80;
getdisplay().setCursor(170, 68); DsegFontSize = 20;
} }
else{ else{
getdisplay().setFont(&DSEG7Classic_BoldItalic30pt7b); valueY = 90;
getdisplay().setCursor(180, 90); DsegFontSize = 30;
} }
// Show bus data // Show bus data
if(holdvalues == false){ if(holdvalues == false){
getdisplay().print(svalue1); // Real value as formated string printBoatValue(svalue1, valueX, valueY, RIGHT, DsegFontSize, smallDecimals); // Real value as formated string
} }
else{ else{
getdisplay().print(svalue1old); // Old value as formated string printBoatValue(svalue1old, valueX, valueY, RIGHT, DsegFontSize, smallDecimals); // Old value as formated string
} }
if(valid1 == true){ if(valid1 == true){
svalue1old = svalue1; // Save the old value svalue1old = svalue1; // Save the old value
@@ -150,24 +158,30 @@ class PageThreeValues : public Page
// Switch font if format for any values // Switch font if format for any values
if(bvalue2->getFormat() == "formatLatitude" || bvalue2->getFormat() == "formatLongitude"){ if(bvalue2->getFormat() == "formatLatitude" || bvalue2->getFormat() == "formatLongitude"){
valueY = 180;
DsegFontSize = 20;
getdisplay().setFont(&Ubuntu_Bold20pt8b); getdisplay().setFont(&Ubuntu_Bold20pt8b);
getdisplay().setCursor(50, 180); getdisplay().setCursor(50, 180);
} }
else if(bvalue2->getFormat() == "formatTime" || bvalue2->getFormat() == "formatDate"){ else if(bvalue2->getFormat() == "formatTime" || bvalue2->getFormat() == "formatDate"){
valueY = 170;
DsegFontSize = 20;
getdisplay().setFont(&Ubuntu_Bold20pt8b); getdisplay().setFont(&Ubuntu_Bold20pt8b);
getdisplay().setCursor(170, 158); getdisplay().setCursor(170, 158);
} }
else{ else{
valueY = 180;
DsegFontSize = 30;
getdisplay().setFont(&DSEG7Classic_BoldItalic30pt7b); getdisplay().setFont(&DSEG7Classic_BoldItalic30pt7b);
getdisplay().setCursor(180, 180); getdisplay().setCursor(180, 180);
} }
// Show bus data // Show bus data
if(holdvalues == false){ if(holdvalues == false){
getdisplay().print(svalue2); // Real value as formated string printBoatValue(svalue2, valueX, valueY, RIGHT, DsegFontSize, smallDecimals); // Real value as formated string
} }
else{ else{
getdisplay().print(svalue2old); // Old value as formated string printBoatValue(svalue2old, valueX, valueY, RIGHT, DsegFontSize, smallDecimals); // Real value as formated string
} }
if(valid2 == true){ if(valid2 == true){
svalue2old = svalue2; // Save the old value svalue2old = svalue2; // Save the old value
@@ -198,24 +212,24 @@ class PageThreeValues : public Page
// Switch font if format for any values // Switch font if format for any values
if(bvalue3->getFormat() == "formatLatitude" || bvalue3->getFormat() == "formatLongitude"){ if(bvalue3->getFormat() == "formatLatitude" || bvalue3->getFormat() == "formatLongitude"){
getdisplay().setFont(&Ubuntu_Bold20pt8b); valueY = 270;
getdisplay().setCursor(50, 270); DsegFontSize = 20;
} }
else if(bvalue3->getFormat() == "formatTime" || bvalue3->getFormat() == "formatDate"){ else if(bvalue3->getFormat() == "formatTime" || bvalue3->getFormat() == "formatDate"){
getdisplay().setFont(&Ubuntu_Bold20pt8b); valueY = 260;
getdisplay().setCursor(170, 248); DsegFontSize = 20;
} }
else{ else{
getdisplay().setFont(&DSEG7Classic_BoldItalic30pt7b); valueY = 270;
getdisplay().setCursor(180, 270); DsegFontSize = 30;
} }
// Show bus data // Show bus data
if(holdvalues == false){ if(holdvalues == false){
getdisplay().print(svalue3); // Real value as formated string printBoatValue(svalue3, valueX, valueY, RIGHT, DsegFontSize, smallDecimals); // Real value as formated string
} }
else{ else{
getdisplay().print(svalue3old); // Old value as formated string printBoatValue(svalue3old, valueX, valueY, RIGHT, DsegFontSize, smallDecimals); // Real value as formated string
} }
if(valid3 == true){ if(valid3 == true){
svalue3old = svalue3; // Save the old value svalue3old = svalue3; // Save the old value
+40 -39
View File
@@ -20,17 +20,6 @@ private:
HALF 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 static constexpr int YOFFSET = 130; // y offset for display of 2nd boat value
int width; // Screen width int width; // Screen width
@@ -45,8 +34,11 @@ private:
bool useSimuData; bool useSimuData;
bool holdValues; bool holdValues;
String flashLED; String flashLED;
String backlightMode; bool smallDecimals;
String tempFormat;
static constexpr int8_t LEFT = 0;
static constexpr int8_t CENTER = 1;
static constexpr int8_t RIGHT = 2;
// Data buffer pointer (owned by HstryBuffers) // Data buffer pointer (owned by HstryBuffers)
static constexpr int NUMVALUES = 2; // two data values in this page static constexpr int NUMVALUES = 2; // two data values in this page
@@ -60,8 +52,10 @@ private:
// display data values in display <mode> [FULL|HALF] // display data values in display <mode> [FULL|HALF]
void showData(const std::vector<GwApi::BoatValue*>& bValue, DisplayMode mode) void showData(const std::vector<GwApi::BoatValue*>& bValue, DisplayMode mode)
{ {
getdisplay().setTextColor(commonData->fgcolor); int valueX, valueY;
int DsegFontSize;
getdisplay().setTextColor(commonData->fgcolor);
int numValues = bValue.size(); // do we have to handle 1 or 2 values? int numValues = bValue.size(); // do we have to handle 1 or 2 values?
for (int i = 0; i < numValues; i++) { for (int i = 0; i < numValues; i++) {
@@ -89,22 +83,23 @@ private:
} }
// Switch font depending on value format and adjust position // Switch font depending on value format and adjust position
valueX = 380;
if (bValue[i]->getFormat() == "formatLatitude" || bValue[i]->getFormat() == "formatLongitude") { if (bValue[i]->getFormat() == "formatLatitude" || bValue[i]->getFormat() == "formatLongitude") {
getdisplay().setFont(&Ubuntu_Bold20pt8b); valueY = 125 + yOffset;
getdisplay().setCursor(50, 125 + yOffset); DsegFontSize = 20;
} else if (bValue[i]->getFormat() == "formatTime" || bValue[i]->getFormat() == "formatDate") { } else if (bValue[i]->getFormat() == "formatTime" || bValue[i]->getFormat() == "formatDate") {
getdisplay().setFont(&Ubuntu_Bold20pt8b); valueY = 105 + yOffset;
getdisplay().setCursor(170, 105 + yOffset); DsegFontSize = 20;
} else { // Default font for other formats } else { // Default font for other formats
getdisplay().setFont(&DSEG7Classic_BoldItalic42pt7b); valueY = 125 + yOffset;
getdisplay().setCursor(180, 125 + yOffset); DsegFontSize = 42;
} }
// Show bus data // Show bus data
if (!holdValues || useSimuData) { if (!holdValues || useSimuData) {
getdisplay().print(sValue); // Real value as formated string printBoatValue(sValue, valueX, valueY, RIGHT, DsegFontSize, smallDecimals);
} else { } else {
getdisplay().print(sValueOld[i]); // Old value as formated string printBoatValue(sValueOld[i], valueX, valueY, RIGHT, DsegFontSize, smallDecimals);
} }
if (valid == true) { if (valid == true) {
@@ -127,14 +122,13 @@ public:
width = getdisplay().width(); // Screen width width = getdisplay().width(); // Screen width
height = getdisplay().height(); // Screen height height = getdisplay().height(); // Screen height
getdisplay().setTextWrap(false);
// Get config data // Get config data
// lengthformat = commonData->config->getString(commonData->config->lengthFormat);
useSimuData = commonData->config->getBool(commonData->config->useSimuData); useSimuData = commonData->config->getBool(commonData->config->useSimuData);
holdValues = commonData->config->getBool(commonData->config->holdvalues); holdValues = commonData->config->getBool(commonData->config->holdvalues);
flashLED = commonData->config->getString(commonData->config->flashLED); flashLED = commonData->config->getString(commonData->config->flashLED);
backlightMode = commonData->config->getString(commonData->config->backlight); smallDecimals = commonData->config->getBool(commonData->config->smallDecimals);
tempFormat = commonData->config->getString(commonData->config->tempFormat); // [K|°C|°F]
} }
virtual void setupKeys() virtual void setupKeys()
@@ -203,7 +197,7 @@ public:
#if defined BOARD_OBP60S3 #if defined BOARD_OBP60S3
if (key == 5 && pageMode != VALUES) { if (key == 5 && pageMode != VALUES) {
#elif defined BOARD_OBP40S3 #elif defined BOARD_OBP40S3
if (key == 2 && pageMode != VALUES) { if (key == 2 && pageMode != VALUES) {
#endif #endif
if (dataIntv == 1) { if (dataIntv == 1) {
dataIntv = 2; dataIntv = 2;
@@ -211,8 +205,6 @@ public:
dataIntv = 3; dataIntv = 3;
} else if (dataIntv == 3) { } else if (dataIntv == 3) {
dataIntv = 4; dataIntv = 4;
} else if (dataIntv == 4) {
dataIntv = 8;
} else { } else {
dataIntv = 1; dataIntv = 1;
} }
@@ -248,8 +240,8 @@ public:
dataHstryBuf[i] = pageData.hstryBuffers->getBuffer(bValName); dataHstryBuf[i] = pageData.hstryBuffers->getBuffer(bValName);
if (dataHstryBuf[i]) { if (dataHstryBuf[i]) {
dataChart[i].reset(new Chart(*dataHstryBuf[i], Chart::dfltChrtDta[bValFormat].range, *commonData, useSimuData)); dataChart[i].reset(new Chart(*dataHstryBuf[i], *commonData, useSimuData));
LOG_DEBUG(GwLog::DEBUG, "PageTwoValues: Created chart object%d for %s", i, bValName.c_str()); LOG_DEBUG(GwLog::DEBUG, "PageTwoValues: Created chart object %d for %s", i, bValName.c_str());
} else { } else {
LOG_DEBUG(GwLog::DEBUG, "PageTwoValues: No chart object available for %s", bValName.c_str()); LOG_DEBUG(GwLog::DEBUG, "PageTwoValues: No chart object available for %s", bValName.c_str());
} }
@@ -268,12 +260,13 @@ public:
bValue.push_back(pageData.values[0]); // Page boat data element 1 bValue.push_back(pageData.values[0]); // Page boat data element 1
bValue.push_back(pageData.values[1]); // Page boat data element 2 bValue.push_back(pageData.values[1]); // Page boat data element 2
#ifdef BOARD_OBP60S3
// 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);
} }
#endif
if (bValue[0] == NULL && bValue[1] == NULL) if (bValue[0] == NULL && bValue[1] == NULL)
return PAGE_OK; // no data, no page to display return PAGE_OK; // no data, no page to display
@@ -283,37 +276,45 @@ public:
// Draw page // Draw page
//*********************************************************** //***********************************************************
getdisplay().setPartialWindow(0, 0, width, height); // Set partial update displaySetPartialWindow(0, 0, width, height); // Set partial update
if (pageMode == VALUES || (dataHstryBuf[0] == nullptr && dataHstryBuf[1] == nullptr)) { for (int i = 0; i < NUMVALUES; i++) {
if (dataChart[i]) { // Check only if dataChart object exists at all
if (!dataChart[i]->isValid()) {
dataChart[i]->init(); // try late initialization if chart object could not be properly initialized earlier due to missing boat data
}
}
}
if (pageMode == VALUES || (dataChart[0] == nullptr && dataChart[1] == nullptr)) {
// show only data value; ignore other pageMode options if no chart supported boat data history buffer is available // show only data value; ignore other pageMode options if no chart supported boat data history buffer is available
showData(bValue, FULL); showData(bValue, FULL);
} else if (pageMode == VAL1_CHART) { // show data value 1 and chart } else if (pageMode == VAL1_CHART) { // show data value 1 and chart
showData({ bValue[0] }, HALF); showData({ bValue[0] }, HALF);
if (dataChart[0]) { if (dataChart[0]) {
dataChart[0]->showChrt(HORIZONTAL, HALF_SIZE_BOTTOM, dataIntv, NO_PRNT_NAME, NO_PRNT_VALUE, *bValue[0]); dataChart[0]->showChrt(Chart::HORIZONTAL, Chart::HALF_SIZE_RIGHT_BOTTOM, dataIntv, Chart::NO_PRNT_NAME, Chart::NO_PRNT_VALUE, *bValue[0]);
} }
} else if (pageMode == VAL2_CHART) { // show data value 2 and chart } else if (pageMode == VAL2_CHART) { // show data value 2 and chart
showData({ bValue[1] }, HALF); showData({ bValue[1] }, HALF);
if (dataChart[1]) { if (dataChart[1]) {
dataChart[1]->showChrt(HORIZONTAL, HALF_SIZE_BOTTOM, dataIntv, NO_PRNT_NAME, NO_PRNT_VALUE, *bValue[1]); dataChart[1]->showChrt(Chart::HORIZONTAL, Chart::HALF_SIZE_RIGHT_BOTTOM, dataIntv, Chart::NO_PRNT_NAME, Chart::NO_PRNT_VALUE, *bValue[1]);
} }
} else if (pageMode == CHARTS) { // show both data charts } else if (pageMode == CHARTS) { // show both data charts
if (dataChart[0]) { if (dataChart[0]) {
if (dataChart[1]) { if (dataChart[1]) {
dataChart[0]->showChrt(HORIZONTAL, HALF_SIZE_TOP, dataIntv, PRNT_NAME, PRNT_VALUE, *bValue[0]); dataChart[0]->showChrt(Chart::HORIZONTAL, Chart::HALF_SIZE_LEFT_TOP, dataIntv, Chart::PRNT_NAME, Chart::PRNT_VALUE, *bValue[0]);
} else { } else {
dataChart[0]->showChrt(HORIZONTAL, FULL_SIZE, dataIntv, PRNT_NAME, PRNT_VALUE, *bValue[0]); dataChart[0]->showChrt(Chart::HORIZONTAL, Chart::FULL_SIZE, dataIntv, Chart::PRNT_NAME, Chart::PRNT_VALUE, *bValue[0]);
} }
} }
if (dataChart[1]) { if (dataChart[1]) {
if (dataChart[0]) { if (dataChart[0]) {
dataChart[1]->showChrt(HORIZONTAL, HALF_SIZE_BOTTOM, dataIntv, PRNT_NAME, PRNT_VALUE, *bValue[1]); dataChart[1]->showChrt(Chart::HORIZONTAL, Chart::HALF_SIZE_RIGHT_BOTTOM, dataIntv, Chart::PRNT_NAME, Chart::PRNT_VALUE, *bValue[1]);
} else { } else {
dataChart[1]->showChrt(HORIZONTAL, FULL_SIZE, dataIntv, PRNT_NAME, PRNT_VALUE, *bValue[1]); dataChart[1]->showChrt(Chart::HORIZONTAL, Chart::FULL_SIZE, dataIntv, Chart::PRNT_NAME, Chart::PRNT_VALUE, *bValue[1]);
} }
} }
} }
+17 -36
View File
@@ -2,15 +2,18 @@
#include "Pagedata.h" #include "Pagedata.h"
#include "OBP60Extensions.h" #include "OBP60Extensions.h"
#include "movingAvg.h" // Lib for moving average building
class PageVoltage : public Page class PageVoltage : public Page
{ {
bool init = false; // Marker for init done bool init = false; // Marker for init done
uint8_t average = 0; // Average type [0...3], 0=off, 1=10s, 2=60s, 3=300s uint8_t average = 0; // Average type [0...3], 0=off, 1=10s, 2=60s, 3=300s
bool trend = true; // Trend indicator [0|1], 0=off, 1=on bool trend = true; // Trend indicator [0|1], 0=off, 1=on
double raw = 0; double raw = 0;
char mode = 'D'; // display mode (A)nalog | (D)igital char mode = 'D'; // display mode (A)nalog | (D)igital
static constexpr int8_t LEFT = 0;
static constexpr int8_t CENTER = 1;
static constexpr int8_t RIGHT = 2;
public: public:
PageVoltage(CommonData &common){ PageVoltage(CommonData &common){
@@ -111,8 +114,10 @@ public:
String batVoltage = config->getString(config->batteryVoltage); String batVoltage = config->getString(config->batteryVoltage);
String batType = config->getString(config->batteryType); String batType = config->getString(config->batteryType);
String backlightMode = config->getString(config->backlight); String backlightMode = config->getString(config->backlight);
bool smallDecimals = config->getBool(config->smallDecimals);
double value1 = 0; double value1 = 0;
String svalue = "---";
double valueTrend = 0; // Average over 10 values double valueTrend = 0; // Average over 10 values
// Get voltage value // Get voltage value
@@ -193,7 +198,7 @@ public:
//*********************************************************** //***********************************************************
// Set display in partial refresh mode // Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
if (mode == 'D') { if (mode == 'D') {
// Display mode digital // Display mode digital
@@ -235,36 +240,12 @@ public:
printAvg(average, 320, 84, true); printAvg(average, 320, 84, true);
// Reading bus data or using simulation data // Reading bus data or using simulation data
getdisplay().setFont(&DSEG7Classic_BoldItalic60pt7b); if(valid1 == true || holdvalues == true){
getdisplay().setCursor(20, 240); svalue = formatValue(value1, String("formatXdr:U:V"), *commonData);
if(simulation == true){ } else {
if(batVoltage == "12V"){ svalue = "---"; // Missing bus data
value1 = 12.0;
}
if(batVoltage == "24V"){
value1 = 24.0;
}
value1 += float(random(0, 5)) / 10; // Simulation data
getdisplay().print(value1,1);
}
else{
// Check for valid real data, display also if hold values activated
if(valid1 == true || holdvalues == true){
// Resolution switching
if(value1 < 10){
getdisplay().print(value1,2);
}
if(value1 >= 10 && value1 < 100){
getdisplay().print(value1,1);
}
if(value1 >= 100){
getdisplay().print(value1,0);
}
}
else{
getdisplay().print("---"); // Missing bus data
}
} }
printBoatValue(svalue, 300, 240, RIGHT, 60, smallDecimals);
// Show trend indicator // Show trend indicator
if(trend == true){ if(trend == true){
+280
View File
@@ -0,0 +1,280 @@
#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3
#include "Pagedata.h"
#include "OBP60Extensions.h"
#include "OBPDataOperations.h"
#include "OBPcharts.h"
class PageWeather : public Page {
private:
GwLog* logger;
enum PageMode {
VAL_CHART,
CHART
};
static constexpr int8_t LEFT = 0;
static constexpr int8_t CENTER = 1;
static constexpr int8_t RIGHT = 2;
static constexpr int XOFFSET = 133; // x offset for display of boat values
int width; // Screen width
int height; // Screen height
bool keylock = false; // Keylock
PageMode pageMode = VAL_CHART; // Page display mode
int8_t dataIntv = 1; // Update interval for barograph history chart:
// (1)|(3)|(6)|(12)|(24) x 60 min. for 1, 3, 6, 12, 24 hours history chart
// String lengthformat;
bool useSimuData;
bool holdValues;
String flashLED;
bool smallDecimals;
static constexpr int NUMVALUES = 4; // number of boat values used on this page
static constexpr int NUMCHARTS = 1; // one data buffer used on this page
// Data buffer pointer (owned by HstryBuffers)
RingBuffer<uint16_t>* dataHstryBuf[NUMCHARTS] = { nullptr };
std::unique_ptr<Chart> dataChart[NUMCHARTS]; // Chart object
// Old values for hold function
String sValueOld[NUMVALUES] = { "", "", "", "" };
String unitOld[NUMVALUES] = { "", "", "", "" };
// display data values in display mode <HALF>
void showData(const std::vector<GwApi::BoatValue*>& bValue)
{
getdisplay().setTextColor(commonData->fgcolor);
int numValues = bValue.size(); // How many values do we have to handle? We will ignore value no. 1
for (int i = 1; i < numValues; i++) {
String name = xdrDelete(bValue[i]->getName()); // Value name
name = name.substring(0, 7); // 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
int xOffset = XOFFSET * (i - 1);
// Print name
getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().setCursor(5 + xOffset, 213);
getdisplay().print(name); // name
// Print unit
getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(5 + xOffset, 229);
if (holdValues) {
getdisplay().print(unitOld[i]); // name
} else {
getdisplay().print(unit); // name
}
// Print value
int valueX = XOFFSET * i - 5;
int valueY = 275;
int DsegFontSize = 20;
if (!holdValues || useSimuData) {
printBoatValue(sValue, valueX, valueY, RIGHT, DsegFontSize, smallDecimals); // Real value as formated string
} else {
printBoatValue(sValueOld[i], valueX, valueY, RIGHT, DsegFontSize, smallDecimals); // Old value as formated string
}
if (valid) { // Save value for hold function
sValueOld[i] = sValue;
unitOld[i] = unit;
}
}
// print lines for data separation of bottom data values
getdisplay().fillRect(0, 191, 400, 2, commonData->fgcolor); // horizontal line
getdisplay().fillRect(133, 192, 2, 84, commonData->fgcolor); // vertical lines
getdisplay().fillRect(266, 192, 2, 84, commonData->fgcolor);
}
public:
PageWeather(CommonData& common)
{
commonData = &common;
logger = commonData->logger;
LOG_DEBUG(GwLog::LOG, "Instantiate PageWeather");
width = getdisplay().width(); // Screen width
height = getdisplay().height(); // Screen height
// Get config data
useSimuData = commonData->config->getBool(commonData->config->useSimuData);
holdValues = commonData->config->getBool(commonData->config->holdvalues);
flashLED = commonData->config->getString(commonData->config->flashLED);
smallDecimals = commonData->config->getBool(commonData->config->smallDecimals);
}
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";
commonData->keydata[ZOOM_KEY].label = "ZOOM";
} 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/half chart | full chart
if (key == 1) {
switch (pageMode) {
case VAL_CHART:
pageMode = CHART;
break;
case CHART:
pageMode = VAL_CHART;
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) {
#elif defined BOARD_OBP40S3
if (key == 2) {
#endif
if (dataIntv == 1) {
dataIntv = 2;
} else if (dataIntv == 2) {
dataIntv = 4;
} else if (dataIntv == 4) {
dataIntv = 8;
} else if (dataIntv == 8) {
dataIntv = 12;
} else {
dataIntv = 1;
}
return 0; // Commit the key
}
}
// Keylock function
if (key == 11) { // Code for keylock
commonData->keylock = !commonData->keylock;
return 0; // Commit the key
}
return key;
}
virtual void displayNew(PageData& pageData)
{
#ifdef BOARD_OBP60S3
// Clear optical warning
if (flashLED == "Limit Violation") {
setBlinkingLED(false);
setFlashLED(false);
}
#endif
for (int i = 0; i < NUMCHARTS; i++) {
if (!dataChart[i]) { // Create chart objects if they don't exist
GwApi::BoatValue* bValue = pageData.values[i]; // Page boat data element
String bValName = bValue->getName(); // Value name
String bValFormat = bValue->getFormat(); // Value format
dataHstryBuf[i] = pageData.hstryBuffers->getBuffer(bValName);
if (dataHstryBuf[i]) {
dataChart[i].reset(new Chart(*dataHstryBuf[i], *commonData, useSimuData));
LOG_DEBUG(GwLog::DEBUG, "PageWeather: Created chart object %d for %s, format: %s", i, bValName.c_str(), dataHstryBuf[i]->getFormat().c_str());
} else {
LOG_DEBUG(GwLog::DEBUG, "PageWeather: No chart object available for %s", bValName.c_str());
}
}
}
}
int displayPage(PageData& pageData)
{
LOG_DEBUG(GwLog::LOG, "Display PageWeather");
// Get latest boat values for page
std::vector<GwApi::BoatValue*> bValue;
for (int i = 0; i < NUMVALUES; i++) {
bValue.push_back(pageData.values[i]);
}
if (bValue[0] == NULL && bValue[1] == NULL && bValue[2] == NULL && bValue[3] == NULL) {
return PAGE_OK; // no data, no page to display
}
if (dataHstryBuf == nullptr) { // no buffer for main boat data item, no page display
return PAGE_OK;
}
LOG_DEBUG(GwLog::DEBUG, "PageWeather: printing #1: %s, %.3f, %s, #2: %s, %.3f, %s, #3: %s, %.3f, %s, #4: %s, %.3f, %s",
bValue[0]->getName().c_str(), bValue[0]->value, bValue[0]->getFormat().c_str(), bValue[1]->getName().c_str(), bValue[1]->value, bValue[1]->getFormat().c_str(),
bValue[2]->getName().c_str(), bValue[2]->value, bValue[2]->getFormat().c_str(), bValue[3]->getName().c_str(), bValue[3]->value, bValue[3]->getFormat().c_str());
// Draw page
//***********************************************************
displaySetPartialWindow(0, 0, width, height); // Set partial update
if (dataChart[0]) { // Check only if dataChart object exists at all
if (!dataChart[0]->isValid()) {
dataChart[0]->init(); // try late initialization if chart object could not be properly initialized earlier due to missing boat data
}
}
if (pageMode == VAL_CHART) {
if (dataChart[0]) {
dataChart[0]->showChrt(Chart::HORIZONTAL, Chart::TWO_THIRD_TOP, dataIntv, Chart::PRNT_NAME, Chart::PRNT_VALUE, *bValue[0]);
}
showData(bValue);
} else if (pageMode == CHART && dataChart[0]) { // show only data chart, but that has to exist
dataChart[0]->showChrt(Chart::HORIZONTAL, Chart::FULL_SIZE, dataIntv, Chart::PRNT_NAME, Chart::PRNT_VALUE, *bValue[0]);
}
return PAGE_UPDATE;
};
};
static Page* createPage(CommonData& common)
{
return new PageWeather(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 registerPageWeather(
"Weather", // Page name
createPage, // Action
4, // Number of bus values depends on selection in Web configuration (air baro pressure, air temperature, humidity, true wind speed)
{ }, // Bus values we need in the page
true // Show display header on/off
);
#endif
+1 -1
View File
@@ -63,7 +63,7 @@ public:
if (mode == 'W') { if (mode == 'W') {
getdisplay().setFullWindow(); getdisplay().setFullWindow();
} else { } else {
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
} }
if (mode == 'L') { if (mode == 'L') {
+12 -15
View File
@@ -213,6 +213,10 @@ static unsigned char front_bits[] PROGMEM = {
class PageWind : public Page class PageWind : public Page
{ {
static constexpr int8_t LEFT = 0;
static constexpr int8_t CENTER = 1;
static constexpr int8_t RIGHT = 2;
bool keylock = false; // Keylock bool keylock = false; // Keylock
int8_t lp = 80; // Pointer length int8_t lp = 80; // Pointer length
char mode = 'N'; // page mode (N)ormal | (L)ens | e(X)ample char mode = 'N'; // page mode (N)ormal | (L)ens | e(X)ample
@@ -306,11 +310,10 @@ public:
static String unit2old = ""; static String unit2old = "";
// Get config data // Get config data
String lengthformat = config->getString(config->lengthFormat);
bool simulation = config->getBool(config->useSimuData); bool simulation = config->getBool(config->useSimuData);
bool holdvalues = config->getBool(config->holdvalues); bool holdvalues = config->getBool(config->holdvalues);
String flashLED = config->getString(config->flashLED); String flashLED = config->getString(config->flashLED);
String backlightMode = config->getString(config->backlight); bool smallDecimals = config->getBool(config->smallDecimals);
GwApi::BoatValue *bvalue1; // Value 1 for speed on top GwApi::BoatValue *bvalue1; // Value 1 for speed on top
GwApi::BoatValue *bvalue2; // Value 2 for angle on bottom GwApi::BoatValue *bvalue2; // Value 2 for angle on bottom
@@ -358,7 +361,7 @@ public:
//*********************************************************** //***********************************************************
// Set display in partial refresh mode // Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
getdisplay().setTextColor(commonData->fgcolor); getdisplay().setTextColor(commonData->fgcolor);
@@ -512,17 +515,14 @@ public:
} }
// Wind speed as decimal number // Wind speed as decimal number
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b);
getdisplay().setCursor(150, 250);
if (holdvalues == false) { if (holdvalues == false) {
getdisplay().print(svalue1); printBoatValue(svalue1, 260, 250, RIGHT, 26, smallDecimals);
} else { } else {
getdisplay().print(svalue1old); printBoatValue(svalue1old, 260, 250, RIGHT, 26, smallDecimals);
} }
// unit // unit
getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(220, 265); drawTextRalign(260, 265, unit1);
getdisplay().print("kts");
} }
else { else {
// Normal mode // Normal mode
@@ -584,17 +584,14 @@ public:
} }
// Wind speed as decimal number // Wind speed as decimal number
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b);
getdisplay().setCursor(150, 250);
if (holdvalues == false) { if (holdvalues == false) {
getdisplay().print(svalue1); printBoatValue(svalue1, 260, 250, RIGHT, 26, smallDecimals);
} else { } else {
getdisplay().print(svalue1old); printBoatValue(svalue1old, 260, 250, RIGHT, 26, smallDecimals);
} }
// unit // unit
getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(220, 265); drawTextRalign(260, 265, unit1);
getdisplay().print("kts");
// Wind pointer (angle) // Wind pointer (angle)
if (bvalue2->valid or simulation) { if (bvalue2->valid or simulation) {
+15 -23
View File
@@ -11,33 +11,22 @@ class PageWindPlot : public Page {
private: private:
GwLog* logger; GwLog* logger;
enum ChartMode { enum PageMode {
DIRECTION, DIRECTION,
SPEED, SPEED,
BOTH 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
ChartMode chrtMode = DIRECTION; PageMode 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
int8_t 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) seconds for up to 32 min. history chart
bool useSimuData; bool useSimuData;
// bool holdValues; // bool holdValues;
String flashLED; String flashLED;
@@ -168,10 +157,10 @@ public:
twsHstry = pageData.hstryBuffers->getBuffer("TWS"); twsHstry = pageData.hstryBuffers->getBuffer("TWS");
if (twdHstry) { if (twdHstry) {
twdChart.reset(new Chart(*twdHstry, Chart::dfltChrtDta["formatCourse"].range, *commonData, useSimuData)); twdChart.reset(new Chart(*twdHstry, *commonData, useSimuData));
} }
if (twsHstry) { if (twsHstry) {
twsChart.reset(new Chart(*twsHstry, Chart::dfltChrtDta["formatKnots"].range, *commonData, useSimuData)); twsChart.reset(new Chart(*twsHstry, *commonData, useSimuData));
} }
} }
@@ -180,10 +169,10 @@ public:
awsHstry = pageData.hstryBuffers->getBuffer("AWS"); awsHstry = pageData.hstryBuffers->getBuffer("AWS");
if (awdHstry) { if (awdHstry) {
awdChart.reset(new Chart(*awdHstry, Chart::dfltChrtDta["formatCourse"].range, *commonData, useSimuData)); awdChart.reset(new Chart(*awdHstry, *commonData, useSimuData));
} }
if (awsHstry) { if (awsHstry) {
awsChart.reset(new Chart(*awsHstry, Chart::dfltChrtDta["formatKnots"].range, *commonData, useSimuData)); awsChart.reset(new Chart(*awsHstry, *commonData, useSimuData));
} }
if (twdHstry && twsHstry && awdHstry && awsHstry) { if (twdHstry && twsHstry && awdHstry && awsHstry) {
LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: Created wind charts"); LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: Created wind charts");
@@ -221,25 +210,28 @@ public:
//*********************************************************** //***********************************************************
// Set display in partial refresh mode // Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, width, height); // Set partial update displaySetPartialWindow(0, 0, width, height); // Set partial update
getdisplay().setTextColor(commonData->fgcolor); getdisplay().setTextColor(commonData->fgcolor);
if (chrtMode == DIRECTION) { if (chrtMode == DIRECTION) {
if (wdChart) { if (wdChart) {
wdChart->showChrt(VERTICAL, FULL_SIZE, dataIntv, PRNT_NAME, PRNT_VALUE, *wdBVal); wdChart->showChrt(Chart::VERTICAL, Chart::FULL_SIZE, dataIntv, Chart::PRNT_NAME, Chart::PRNT_VALUE, *wdBVal);
} }
} else if (chrtMode == SPEED) { } else if (chrtMode == SPEED) {
if (wsChart) { if (wsChart) {
wsChart->showChrt(HORIZONTAL, FULL_SIZE, dataIntv, PRNT_NAME, PRNT_VALUE, *wsBVal); if (dataIntv == 8) {
dataIntv = 1; // horizontal charts show max. 4 x 7 min. only; no factor 8 multiplier
}
wsChart->showChrt(Chart::HORIZONTAL, Chart::FULL_SIZE, dataIntv, Chart::PRNT_NAME, Chart::PRNT_VALUE, *wsBVal);
} }
} else if (chrtMode == BOTH) { } else if (chrtMode == BOTH) {
if (wdChart) { if (wdChart) {
wdChart->showChrt(VERTICAL, HALF_SIZE_LEFT, dataIntv, PRNT_NAME, PRNT_VALUE, *wdBVal); wdChart->showChrt(Chart::VERTICAL, Chart::HALF_SIZE_LEFT_TOP, dataIntv, Chart::NO_PRNT_NAME, Chart::PRNT_VALUE, *wdBVal);
} }
if (wsChart) { if (wsChart) {
wsChart->showChrt(VERTICAL, HALF_SIZE_RIGHT, dataIntv, PRNT_NAME, PRNT_VALUE, *wsBVal); wsChart->showChrt(Chart::VERTICAL, Chart::HALF_SIZE_RIGHT_BOTTOM, dataIntv, Chart::NO_PRNT_NAME, Chart::PRNT_VALUE, *wsBVal);
} }
} }
+35 -66
View File
@@ -5,7 +5,10 @@
class PageWindRose : public Page class PageWindRose : public Page
{ {
int16_t lp = 80; // Pointer length private:
static constexpr int8_t LEFT = 0;
static constexpr int8_t CENTER = 1;
static constexpr int8_t RIGHT = 2;
public: public:
PageWindRose(CommonData &common){ PageWindRose(CommonData &common){
@@ -27,6 +30,9 @@ public:
GwConfigHandler *config = commonData->config; GwConfigHandler *config = commonData->config;
GwLog *logger = commonData->logger; GwLog *logger = commonData->logger;
int valueX, valueY;
int DsegFontSize;
static String svalue1old = ""; static String svalue1old = "";
static String unit1old = ""; static String unit1old = "";
static String svalue2old = ""; static String svalue2old = "";
@@ -41,19 +47,18 @@ public:
static String unit6old = ""; static String unit6old = "";
// Get config data // Get config data
String lengthformat = config->getString(config->lengthFormat);
bool simulation = config->getBool(config->useSimuData); bool simulation = config->getBool(config->useSimuData);
bool holdvalues = config->getBool(config->holdvalues); bool holdvalues = config->getBool(config->holdvalues);
String flashLED = config->getString(config->flashLED); String flashLED = config->getString(config->flashLED);
String backlightMode = config->getString(config->backlight); bool smallDecimals = config->getBool(config->smallDecimals);
// Get boat value for AWA // Get boat value for AWA
GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list (only one value by PageOneValue) GwApi::BoatValue *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
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
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
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
String unit1 = formatValue(bvalue1, *commonData).unit; // Unit of value String unit1 = formatValue(bvalue1, *commonData).unit; // Unit of value
if(valid1 == true){ if(valid1 == true){
@@ -140,14 +145,11 @@ public:
//*********************************************************** //***********************************************************
// Set display in partial refresh mode // Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
getdisplay().setTextColor(commonData->fgcolor); getdisplay().setTextColor(commonData->fgcolor);
// Show values AWA // Show values AWA
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b); printBoatValue(svalue1, 101, 65, RIGHT, 20, smallDecimals); // value
getdisplay().setCursor(10, 65);
getdisplay().print(svalue1); // Value
getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().setCursor(10, 95); getdisplay().setCursor(10, 95);
getdisplay().print(name1); // Name getdisplay().print(name1); // Name
@@ -161,13 +163,11 @@ public:
getdisplay().print(unit1old); // Unit getdisplay().print(unit1old); // Unit
} }
// Horizintal separator left // Horizontal separator left
getdisplay().fillRect(0, 149, 60, 3, commonData->fgcolor); getdisplay().fillRect(0, 149, 60, 3, commonData->fgcolor);
// Show values AWS // Show values AWS
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b); printBoatValue(svalue2, 101, 270, RIGHT, 20, smallDecimals); // value
getdisplay().setCursor(10, 270);
getdisplay().print(svalue2); // Value
getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().setCursor(10, 220); getdisplay().setCursor(10, 220);
getdisplay().print(name2); // Name getdisplay().print(name2); // Name
@@ -182,14 +182,7 @@ public:
} }
// Show values TWD // Show values TWD
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b); printBoatValue(svalue3, 391, 65, RIGHT, 20, smallDecimals); // value
getdisplay().setCursor(295, 65);
if(valid3 == true){
getdisplay().print(abs(value3 * 180 / PI), 0); // Value
}
else{
getdisplay().print("---"); // Value
}
getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().setCursor(335, 95); getdisplay().setCursor(335, 95);
getdisplay().print(name3); // Name getdisplay().print(name3); // Name
@@ -207,9 +200,7 @@ public:
getdisplay().fillRect(340, 149, 80, 3, commonData->fgcolor); getdisplay().fillRect(340, 149, 80, 3, commonData->fgcolor);
// Show values TWS // Show values TWS
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b); printBoatValue(svalue4, 391, 270, RIGHT, 20, smallDecimals); // value
getdisplay().setCursor(295, 270);
getdisplay().print(svalue4); // Value
getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().setCursor(335, 220); getdisplay().setCursor(335, 220);
getdisplay().print(name4); // Name getdisplay().print(name4); // Name
@@ -227,7 +218,7 @@ public:
// Draw wind rose // Draw wind rose
int rInstrument = 110; // Radius of grafic instrument int rInstrument = 110; // Radius of grafic instrument
float pi = 3.141592; String sDegree;
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
@@ -237,56 +228,38 @@ public:
for(int i=0; i<360; i=i+10) for(int i=0; i<360; i=i+10)
{ {
// 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*M_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*M_PI); // y-coordinate cots
const char *ii = "";
switch (i)
{
case 0: ii="0"; break;
case 30 : ii="30"; break;
case 60 : ii="60"; break;
case 90 : ii="90"; break;
case 120 : ii="120"; break;
case 150 : ii="150"; break;
case 180 : ii="180"; break;
case 210 : ii="210"; break;
case 240 : ii="240"; break;
case 270 : ii="270"; break;
case 300 : ii="300"; break;
case 330 : ii="330"; break;
default: break;
}
// Print text centered on position x, y
int16_t x1, y1; // Return values of getTextBounds
uint16_t w, h; // Return values of getTextBounds
getdisplay().getTextBounds(ii, int(x), int(y), &x1, &y1, &w, &h); // Calc width of new string
getdisplay().setCursor(x-w/2, y+h/2);
if(i % 30 == 0){ if(i % 30 == 0){
// Print text centered on position x, y
int16_t x1, y1; // Return values of getTextBounds
uint16_t w, h; // Return values of getTextBounds
sDegree = String(i);
getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().print(ii); displayGetTextBounds(sDegree, int(x), int(y), &x1, &y1, &w, &h); // Calc width of new string
} getdisplay().setCursor(x-w/2, y+h/2);
getdisplay().print(sDegree);
// Draw sub scale with dots // Draw sub scale with lines (two triangles)
float x1c = 200 + rInstrument*sin(i/180.0*pi);
float y1c = 150 - rInstrument*cos(i/180.0*pi);
getdisplay().fillCircle((int)x1c, (int)y1c, 2, commonData->fgcolor);
float sinx=sin(i/180.0*pi);
float cosx=cos(i/180.0*pi);
// Draw sub scale with lines (two triangles)
if(i % 30 == 0){
float dx=2; // Line thickness = 2*dx+1 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);
float sinx = sin(i/180.0*M_PI);
float cosx = cos(i/180.0*M_PI);
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);
} else {
// Draw sub scale with dots
float x1c = 200 + rInstrument*sin(i/180.0*M_PI);
float y1c = 150 - rInstrument*cos(i/180.0*M_PI);
getdisplay().fillCircle((int)x1c, (int)y1c, 2, commonData->fgcolor);
} }
} }
@@ -323,9 +296,7 @@ public:
//******************************************************************************************* //*******************************************************************************************
// Show values DBT // Show values DBT
getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b); printBoatValue(svalue5, 200, 200, CENTER, 16, smallDecimals); // value
getdisplay().setCursor(160, 200);
getdisplay().print(svalue5); // Value
getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(190, 215); getdisplay().setCursor(190, 215);
getdisplay().print(" "); getdisplay().print(" ");
@@ -337,9 +308,7 @@ public:
} }
// Show values STW // Show values STW
getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b); printBoatValue(svalue6, 200, 130, CENTER, 16, smallDecimals); // value
getdisplay().setCursor(160, 130);
getdisplay().print(svalue6); // Value
getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(190, 90); getdisplay().setCursor(190, 90);
getdisplay().print(" "); getdisplay().print(" ");
+66 -94
View File
@@ -5,8 +5,11 @@
class PageWindRoseFlex : public Page class PageWindRoseFlex : public Page
{ {
int16_t lp = 80; // Pointer length private:
char source = 'A'; // data source (A)pparent | (T)rue static constexpr int8_t LEFT = 0;
static constexpr int8_t CENTER = 1;
static constexpr int8_t RIGHT = 2;
char source = 'A'; // data source (A)pparent | (T)rue
public: public:
PageWindRoseFlex(CommonData &common){ PageWindRoseFlex(CommonData &common){
@@ -42,6 +45,9 @@ public:
GwConfigHandler *config = commonData->config; GwConfigHandler *config = commonData->config;
GwLog *logger = commonData->logger; GwLog *logger = commonData->logger;
int valueX, valueY;
int DsegFontSize;
static String svalue1old = ""; static String svalue1old = "";
static String unit1old = ""; static String unit1old = "";
static String svalue2old = ""; static String svalue2old = "";
@@ -61,11 +67,10 @@ public:
// Get config data // Get config data
String lengthformat = config->getString(config->lengthFormat);
bool simulation = config->getBool(config->useSimuData); bool simulation = config->getBool(config->useSimuData);
bool holdvalues = config->getBool(config->holdvalues); bool holdvalues = config->getBool(config->holdvalues);
String flashLED = config->getString(config->flashLED); String flashLED = config->getString(config->flashLED);
String backlightMode = config->getString(config->backlight); bool smallDecimals = config->getBool(config->smallDecimals);
GwApi::BoatValue *bvalue1; // Value 1 for angle GwApi::BoatValue *bvalue1; // Value 1 for angle
GwApi::BoatValue *bvalue2; // Value 2 for speed GwApi::BoatValue *bvalue2; // Value 2 for speed
@@ -79,10 +84,11 @@ 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
double value1 = bvalue1->value; // Value as double in SI unit double value1 = bvalue1->value; // Value as double in SI unit
value1 = formatValue(bvalue1, *commonData).value;// Format only nesaccery for simulation data for pointer
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
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
} }
@@ -97,12 +103,9 @@ public:
name2 = name2.substring(0, 6); // String length limit for value name name2 = name2.substring(0, 6); // String length limit for value name
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) {
value2 = 0.62731; // some random value
}
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
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
} }
@@ -185,7 +188,6 @@ public:
unit6old = unit6; // Save old unit unit6old = unit6; // 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);
@@ -200,14 +202,11 @@ public:
//*********************************************************** //***********************************************************
// Set display in partial refresh mode // Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
getdisplay().setTextColor(commonData->fgcolor); getdisplay().setTextColor(commonData->fgcolor);
// Show AWS or TWS top left // Show AWS or TWS top left
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b); printBoatValue(svalue2, 101, 65, RIGHT, 20, smallDecimals); // value
getdisplay().setCursor(10, 65);
getdisplay().print(svalue2); // Value
getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().setCursor(10, 95); getdisplay().setCursor(10, 95);
getdisplay().print(name2); // Name getdisplay().print(name2); // Name
@@ -225,9 +224,7 @@ public:
getdisplay().fillRect(0, 149, 60, 3, commonData->fgcolor); getdisplay().fillRect(0, 149, 60, 3, commonData->fgcolor);
// Show value 3 (=first user-configured parameter) at bottom left // Show value 3 (=first user-configured parameter) at bottom left
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b); printBoatValue(svalue3, 101, 270, RIGHT, 20, smallDecimals); // value
getdisplay().setCursor(10, 270);
getdisplay().print(svalue3); // Value
getdisplay().setFont(&name3font); getdisplay().setFont(&name3font);
getdisplay().setCursor(10, 220); getdisplay().setCursor(10, 220);
getdisplay().print(name3); // Name getdisplay().print(name3); // Name
@@ -242,9 +239,7 @@ public:
} }
// Show value 4 (=second user-configured parameter) at top right // Show value 4 (=second user-configured parameter) at top right
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b); printBoatValue(svalue4, 395, 65, RIGHT, 20, smallDecimals); // value
getdisplay().setCursor(295, 65);
getdisplay().print(svalue4); // Value
getdisplay().setFont(&name4font); getdisplay().setFont(&name4font);
getdisplay().setCursor(325, 95); getdisplay().setCursor(325, 95);
getdisplay().print(name4); // Name getdisplay().print(name4); // Name
@@ -258,13 +253,11 @@ public:
getdisplay().print(unit4old); // Unit getdisplay().print(unit4old); // Unit
} }
// Horizintal separator right // Horizontal separator right
getdisplay().fillRect(340, 149, 80, 3, commonData->fgcolor); getdisplay().fillRect(340, 149, 80, 3, commonData->fgcolor);
// Show value 5 (=third user-configured parameter) at bottom right // Show value 5 (=third user-configured parameter) at bottom right
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b); printBoatValue(svalue5, 395, 270, RIGHT, 20, smallDecimals); // value
getdisplay().setCursor(295, 270);
getdisplay().print(svalue5); // Value
getdisplay().setFont(&name5font); getdisplay().setFont(&name5font);
getdisplay().setCursor(325, 220); getdisplay().setCursor(325, 220);
getdisplay().print(name5); // Name getdisplay().print(name5); // Name
@@ -283,6 +276,7 @@ public:
// Draw wind rose // Draw wind rose
int rInstrument = 110; // Radius of grafic instrument int rInstrument = 110; // Radius of grafic instrument
String sDegree;
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
@@ -294,53 +288,36 @@ public:
// Scaling values // Scaling values
float x = 200 + (rInstrument-30)*sin(i/180.0*M_PI); // x-coordinate dots float x = 200 + (rInstrument-30)*sin(i/180.0*M_PI); // x-coordinate dots
float y = 150 - (rInstrument-30)*cos(i/180.0*M_PI); // y-coordinate dots float y = 150 - (rInstrument-30)*cos(i/180.0*M_PI); // y-coordinate dots
const char *ii = "";
switch (i) {
case 0: ii="0"; break;
case 30 : ii="30"; break;
case 60 : ii="60"; break;
case 90 : ii="90"; break;
case 120 : ii="120"; break;
case 150 : ii="150"; break;
case 180 : ii="180"; break;
case 210 : ii="210"; break;
case 240 : ii="240"; break;
case 270 : ii="270"; break;
case 300 : ii="300"; break;
case 330 : ii="330"; break;
default: break;
}
// Print text centered on position x, y
int16_t x1, y1; // Return values of getTextBounds
uint16_t w, h; // Return values of getTextBounds
getdisplay().getTextBounds(ii, int(x), int(y), &x1, &y1, &w, &h); // Calc width of new string
getdisplay().setCursor(x-w/2, y+h/2);
if(i % 30 == 0){ if(i % 30 == 0){
// Print text centered on position x, y
int16_t x1, y1; // Return values of getTextBounds
uint16_t w, h; // Return values of getTextBounds
sDegree = String(i);
getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().print(ii); displayGetTextBounds(sDegree, int(x), int(y), &x1, &y1, &w, &h); // Calc width of new string
} getdisplay().setCursor(x-w/2, y+h/2);
getdisplay().print(sDegree);
// Draw sub scale with dots // Draw sub scale with lines (two triangles)
float x1c = 200 + rInstrument*sin(i/180.0*M_PI);
float y1c = 150 - rInstrument*cos(i/180.0*M_PI);
getdisplay().fillCircle((int)x1c, (int)y1c, 2, commonData->fgcolor);
float sinx=sin(i/180.0*M_PI);
float cosx=cos(i/180.0*M_PI);
// Draw sub scale with lines (two triangles)
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);
float sinx = sin(i/180.0*M_PI);
float cosx = cos(i/180.0*M_PI);
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);
} else {
// Draw sub scale with dots
float x1c = 200 + rInstrument*sin(i/180.0*M_PI);
float y1c = 150 - rInstrument*cos(i/180.0*M_PI);
getdisplay().fillCircle((int)x1c, (int)y1c, 2, commonData->fgcolor);
} }
} }
@@ -376,47 +353,42 @@ public:
getdisplay().fillCircle(200, 150, startwidth + 4, commonData->bgcolor); getdisplay().fillCircle(200, 150, startwidth + 4, commonData->bgcolor);
getdisplay().setFont(&Ubuntu_Bold10pt8b); getdisplay().setFont(&Ubuntu_Bold10pt8b);
if (source=='A'){ if (source=='A'){
getdisplay().setCursor(193, 155); getdisplay().setCursor(193, 155);
} }
else { else {
getdisplay().setCursor(195, 156); getdisplay().setCursor(195, 156);
} }
getdisplay().print({source}); getdisplay().print({source});
//******************************************************************************************* //*******************************************************************************************
// Show value6 (=fourth user-configured parameter) // Show value6 (=fourth user-configured parameter)
if ( cos(value1) > 0){ if ( cos(value1) > 0){
//pointer points upwards //pointer points upwards
getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b); printBoatValue(svalue6, 200, 200, CENTER, 16, smallDecimals); // value
getdisplay().setCursor(160, 200); getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().print(svalue6); // Value getdisplay().setCursor(190, 215);
getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().print(" ");
getdisplay().setCursor(190, 215); if(holdvalues == false){
getdisplay().print(" "); getdisplay().print(unit6); // Unit
if(holdvalues == false){ }
getdisplay().print(unit6); // Unit else{
} getdisplay().print(unit6old); // Unit
else{ }
getdisplay().print(unit6old); // Unit }
} else{
} // pointer points downwards
else{ printBoatValue(svalue6, 200, 130, CENTER, 16, smallDecimals); // value
// pointer points downwards getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b); getdisplay().setCursor(190, 90);
getdisplay().setCursor(160, 130); getdisplay().print(" ");
getdisplay().print(svalue6); // Value if(holdvalues == false){
getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().print(unit6); // Unit
getdisplay().setCursor(190, 90); }
getdisplay().print(" "); else{
if(holdvalues == false){ getdisplay().print(unit6old); // Unit
getdisplay().print(unit6); // Unit }
} }
else{
getdisplay().print(unit6old); // Unit
}
}
return PAGE_UPDATE; return PAGE_UPDATE;
}; };
+20 -22
View File
@@ -28,8 +28,13 @@ static unsigned char ship_bits[] PROGMEM = {
class PageXTETrack : public Page class PageXTETrack : public Page
{ {
static constexpr int8_t LEFT = 0;
static constexpr int8_t CENTER = 1;
static constexpr int8_t RIGHT = 2;
bool simulation = false; bool simulation = false;
bool holdvalues = false; bool holdvalues = false;
bool smallDecimals = false;
public: public:
PageXTETrack(CommonData &common){ PageXTETrack(CommonData &common){
@@ -37,6 +42,7 @@ class PageXTETrack : public Page
common.logger->logDebug(GwLog::LOG,"Instantiate PageXTETrack"); common.logger->logDebug(GwLog::LOG,"Instantiate PageXTETrack");
simulation = common.config->getBool(common.config->useSimuData); simulation = common.config->getBool(common.config->useSimuData);
holdvalues = common.config->getBool(common.config->holdvalues); holdvalues = common.config->getBool(common.config->holdvalues);
smallDecimals = common.config->getBool(common.config->smallDecimals);
} }
void drawSegment(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, void drawSegment(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1,
@@ -89,50 +95,39 @@ class PageXTETrack : public Page
//*********************************************************** //***********************************************************
// Set display in partial refresh mode // Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
getdisplay().setTextColor(commonData->fgcolor); getdisplay().setTextColor(commonData->fgcolor);
// descriptions // descriptions
getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(50, 188); getdisplay().setCursor(30, 188);
getdisplay().print("Cross-track error"); getdisplay().print("Cross-track error");
getdisplay().setCursor(270, 188); getdisplay().setCursor(270, 188);
getdisplay().print("Track"); getdisplay().print("Track");
getdisplay().setCursor(45, 275); getdisplay().setCursor(25, 275);
getdisplay().print("Distance to waypoint"); getdisplay().print("Distance to waypoint");
getdisplay().setCursor(260, 275); getdisplay().setCursor(280, 275);
getdisplay().print("Bearing"); getdisplay().print("Bearing");
// values // values
getdisplay().setFont(&DSEG7Classic_BoldItalic30pt7b);
int16_t x, y;
uint16_t w, h;
GwApi::BoatValue *bv_xte = pageData.values[0]; // XTE GwApi::BoatValue *bv_xte = pageData.values[0]; // XTE
String sval_xte = formatValue(bv_xte, *commonData).svalue; String sval_xte = formatValue(bv_xte, *commonData).svalue;
getdisplay().getTextBounds(sval_xte, 0, 0, &x, &y, &w, &h); // printBoatValue(sval_xte, 160, 170, RIGHT, 30, smallDecimals);
getdisplay().setCursor(160-w, 170); printBoatValue(sval_xte, 180, 170, RIGHT, 30, smallDecimals);
getdisplay().print(sval_xte);
GwApi::BoatValue *bv_cog = pageData.values[1]; // COG GwApi::BoatValue *bv_cog = pageData.values[1]; // COG
String sval_cog = formatValue(bv_cog, *commonData).svalue; String sval_cog = formatValue(bv_cog, *commonData).svalue;
getdisplay().getTextBounds(sval_cog, 0, 0, &x, &y, &w, &h); printBoatValue(sval_cog, 360, 170, RIGHT, 30, smallDecimals);
getdisplay().setCursor(360-w, 170);
getdisplay().print(sval_cog);
GwApi::BoatValue *bv_dtw = pageData.values[2]; // DTW GwApi::BoatValue *bv_dtw = pageData.values[2]; // DTW
String sval_dtw = formatValue(bv_dtw, *commonData).svalue; String sval_dtw = formatValue(bv_dtw, *commonData).svalue;
getdisplay().getTextBounds(sval_dtw, 0, 0, &x, &y, &w, &h); // printBoatValue(sval_dtw, 160, 257, RIGHT, 30, smallDecimals);
getdisplay().setCursor(160-w, 257); printBoatValue(sval_dtw, 160, 257, RIGHT, 30, smallDecimals);
getdisplay().print(sval_dtw);
GwApi::BoatValue *bv_btw = pageData.values[3]; // BTW GwApi::BoatValue *bv_btw = pageData.values[3]; // BTW
String sval_btw = formatValue(bv_btw, *commonData).svalue; String sval_btw = formatValue(bv_btw, *commonData).svalue;
getdisplay().getTextBounds(sval_btw, 0, 0, &x, &y, &w, &h); printBoatValue(sval_btw, 368, 257, RIGHT, 30, smallDecimals);
getdisplay().setCursor(360-w, 257);
getdisplay().print(sval_btw);
bool valid = bv_cog->valid && bv_btw->valid; bool valid = bv_cog->valid && bv_btw->valid;
@@ -148,8 +143,11 @@ class PageXTETrack : public Page
sval_wpname = "Tonne 122"; sval_wpname = "Tonne 122";
} }
int16_t x, y;
uint16_t w, h;
getdisplay().setFont(&Ubuntu_Bold10pt8b); getdisplay().setFont(&Ubuntu_Bold10pt8b);
getdisplay().getTextBounds(sval_wpname, 0, 150, &x, &y, &w, &h); displayGetTextBounds(sval_wpname, 0, 150, &x, &y, &w, &h);
// TODO if text don't fix use smaller font size. // TODO if text don't fix use smaller font size.
// if smallest size does not fit use 2 lines // if smallest size does not fit use 2 lines
// last resort: clip with ellipsis // last resort: clip with ellipsis
+3 -2
View File
@@ -110,7 +110,6 @@ typedef struct{
AlarmData alarm; AlarmData alarm;
GwApi::BoatValue *time = nullptr; GwApi::BoatValue *time = nullptr;
GwApi::BoatValue *date = nullptr; GwApi::BoatValue *date = nullptr;
float tz = 0.0; // timezone from config
uint16_t fgcolor; uint16_t fgcolor;
uint16_t bgcolor; uint16_t bgcolor;
bool keylock = false; bool keylock = false;
@@ -127,7 +126,7 @@ class Page{
virtual void displayNew(PageData &pageData){} virtual void displayNew(PageData &pageData){}
virtual void leavePage(PageData &pageData){} virtual void leavePage(PageData &pageData){}
virtual void setupKeys() { virtual void setupKeys() {
#ifdef HARDWARE_V21 #ifdef BOARD_OBP60S3
commonData->keydata[0].label = ""; commonData->keydata[0].label = "";
commonData->keydata[1].label = ""; commonData->keydata[1].label = "";
commonData->keydata[2].label = "#LEFT"; commonData->keydata[2].label = "#LEFT";
@@ -206,6 +205,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); FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool ignoreSimuDataSetting);
FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, String setPrecision);
String formatValue(const double &value, const String &vFormat, CommonData &commondata);
// Helper method for conversion of any data value from SI to user defined format (defined in OBP60Formatter) // 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 &format, CommonData &commondata);
+16
View File
@@ -0,0 +1,16 @@
param(
[string]$dir = "."
)
$total = 0
Get-ChildItem -Path $dir -Recurse -File | Where-Object {
$_.Extension -in ".c", ".cpp", ".h"
} | ForEach-Object {
$lines = [System.Linq.Enumerable]::Count([System.IO.File]::ReadLines($_.FullName))
Write-Output "$($_.FullName) : $lines"
$total += $lines
}
Write-Output "-----------------------------"
Write-Output "Over all files: $total"
+144 -57
View File
@@ -76,19 +76,43 @@
} }
}, },
{ {
"name": "chainLength", "name": "leeKStd",
"label": "Anchor Chain Length [m]", "label": "Leeway Coefficient",
"type": "number", "type": "list",
"default": "0", "default": "---",
"check": "checkMinMax", "description": "Boat coefficient K for leeway",
"min": 0, "list": [
"max": 255, "---",
"description": "The length of the anchor chain [0...255m]", "Racer [2]",
"Multihull Draggerboard [4]",
"Performance Cruiser [6]",
"Family Cruiser [9]",
"Heavy Displacement [13]",
"Multihull Fixed Keel [16]",
"Individual value"
],
"category": "OBP40 Settings", "category": "OBP40 Settings",
"capabilities": { "capabilities": {
"obp40": "true" "obp40": "true"
} }
}, },
{
"name": "leeK",
"label": "Leeway Coefficient",
"type": "number",
"default": "0.00",
"check": "checkMinMax",
"min": 0,
"max": 20,
"description": "Boat coefficient K for leeway - individual setting [0..20]",
"category": "OBP40 Settings",
"capabilities": {
"obp40":"true"
},
"condition": [
{ "leeKStd": ["Individual value"] }
]
},
{ {
"name": "fuelTank", "name": "fuelTank",
"label": "Fuel Tank [l]", "label": "Fuel Tank [l]",
@@ -244,6 +268,17 @@
"obp40": "true" "obp40": "true"
} }
}, },
{
"name": "smoothCharts",
"label": "Smooth chart lines",
"type": "boolean",
"default": "false",
"description": "Print chart lines with a smoothed gradient",
"category": "OBP40 Settings",
"capabilities": {
"obp40": "true"
}
},
{ {
"name": "lengthFormat", "name": "lengthFormat",
"label": "Length Format", "label": "Length Format",
@@ -692,7 +727,7 @@
"type": "string", "type": "string",
"default": "text1", "default": "text1",
"description": "Button name", "description": "Button name",
"category": "OBP40 IO-Modul1", "category": "OBP40 IO-Module1",
"capabilities": { "capabilities": {
"obp40":"true" "obp40":"true"
} }
@@ -703,7 +738,7 @@
"type": "string", "type": "string",
"default": "text2", "default": "text2",
"description": "Button name", "description": "Button name",
"category": "OBP40 IO-Modul1", "category": "OBP40 IO-Module1",
"capabilities": { "capabilities": {
"obp40":"true" "obp40":"true"
} }
@@ -714,7 +749,7 @@
"type": "string", "type": "string",
"default": "text3", "default": "text3",
"description": "Button name", "description": "Button name",
"category": "OBP40 IO-Modul1", "category": "OBP40 IO-Module1",
"capabilities": { "capabilities": {
"obp40":"true" "obp40":"true"
} }
@@ -725,7 +760,7 @@
"type": "string", "type": "string",
"default": "text4", "default": "text4",
"description": "Button name", "description": "Button name",
"category": "OBP40 IO-Modul1", "category": "OBP40 IO-Module1",
"capabilities": { "capabilities": {
"obp40":"true" "obp40":"true"
} }
@@ -736,7 +771,7 @@
"type": "string", "type": "string",
"default": "text5", "default": "text5",
"description": "Button name", "description": "Button name",
"category": "OBP40 IO-Modul1", "category": "OBP40 IO-Module1",
"capabilities": { "capabilities": {
"obp40":"true" "obp40":"true"
} }
@@ -1081,8 +1116,7 @@
"description": "Type of map source, cloud service or local service", "description": "Type of map source, cloud service or local service",
"list": [ "list": [
"OBP Service", "OBP Service",
"Local Service", "Local Service"
"Remote Service"
], ],
"category": "OBP40 Navigation", "category": "OBP40 Navigation",
"capabilities": { "capabilities": {
@@ -1119,34 +1153,6 @@
{ "mapsource": ["Local Service"] } { "mapsource": ["Local Service"] }
] ]
}, },
{
"name": "mapServer",
"label": "map server",
"type": "string",
"default": "",
"description": "Server for converting map tiles. Use only one hostname or IP address",
"category": "OBP40 Navigation",
"capabilities": {
"obp40": "true"
},
"condition": [
{ "mapsource": ["Remote Service"] }
]
},
{
"name": "mapTilePath",
"label": "map tile path",
"type": "string",
"default": "map.php",
"description": "Path to converter access e.g. index.php or map.php",
"category": "OBP40 Navigation",
"capabilities": {
"obp40": "true"
},
"condition": [
{ "mapsource": ["Remote Service"] }
]
},
{ {
"name": "maptype", "name": "maptype",
"label": "Map Type", "label": "Map Type",
@@ -1288,8 +1294,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"},
@@ -1349,10 +1355,10 @@
}, },
{ {
"name": "valueprecision", "name": "valueprecision",
"label": "Display value precision", "label": "Value precision",
"type": "list", "type": "list",
"default": "2", "default": "2",
"description": "Maximum number of decimal places to display [1|2]", "description": "Number of decimal precision on display: [1|2] decimals",
"list": [ "list": [
"1", "1",
"2" "2"
@@ -1362,6 +1368,17 @@
"obp40":"true" "obp40":"true"
} }
}, },
{
"name": "smallDecimals",
"label": "Display small decimals",
"type": "boolean",
"default": "true",
"description": "Show decimals of values in smaller size [on|off]",
"category": "OBP40 Display",
"capabilities": {
"obp40": "true"
}
},
{ {
"name": "backlight", "name": "backlight",
"label": "Backlight Mode", "label": "Backlight Mode",
@@ -1552,6 +1569,7 @@
"obp40": "true" "obp40": "true"
} }
}, },
{ {
"name": "page1type", "name": "page1type",
"label": "Type", "label": "Type",
@@ -1559,12 +1577,13 @@
"default": "Voltage", "default": "Voltage",
"description": "Type of page for page 1", "description": "Type of page for page 1",
"list": [ "list": [
"Anchor", "Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
"Clock", "Clock",
"Compass", "Compass",
"Current",
"DigitalOut", "DigitalOut",
"DST810", "DST810",
"Fluid", "Fluid",
@@ -1582,6 +1601,7 @@
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"Weather",
"WhitePage", "WhitePage",
"Wind", "Wind",
"WindPlot", "WindPlot",
@@ -1620,6 +1640,7 @@
}, },
"condition": { "condition": {
"page1type": [ "page1type": [
"Current",
"Fluid", "Fluid",
"FourValues", "FourValues",
"FourValues2", "FourValues2",
@@ -1628,6 +1649,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -1662,6 +1684,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -1694,6 +1717,7 @@
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -1725,6 +1749,7 @@
"FourValues", "FourValues",
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -1890,12 +1915,13 @@
"default": "WindRose", "default": "WindRose",
"description": "Type of page for page 2", "description": "Type of page for page 2",
"list": [ "list": [
"Anchor", "Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
"Clock", "Clock",
"Compass", "Compass",
"Current",
"DigitalOut", "DigitalOut",
"DST810", "DST810",
"Fluid", "Fluid",
@@ -1913,6 +1939,7 @@
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"Weather",
"WhitePage", "WhitePage",
"Wind", "Wind",
"WindPlot", "WindPlot",
@@ -1950,6 +1977,7 @@
}, },
"condition": { "condition": {
"page2type": [ "page2type": [
"Current",
"Fluid", "Fluid",
"FourValues", "FourValues",
"FourValues2", "FourValues2",
@@ -1958,6 +1986,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -1991,6 +2020,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2022,6 +2052,7 @@
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2052,6 +2083,7 @@
"FourValues", "FourValues",
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2212,12 +2244,13 @@
"default": "OneValue", "default": "OneValue",
"description": "Type of page for page 3", "description": "Type of page for page 3",
"list": [ "list": [
"Anchor", "Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
"Clock", "Clock",
"Compass", "Compass",
"Current",
"DigitalOut", "DigitalOut",
"DST810", "DST810",
"Fluid", "Fluid",
@@ -2235,6 +2268,7 @@
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"Weather",
"WhitePage", "WhitePage",
"Wind", "Wind",
"WindPlot", "WindPlot",
@@ -2271,6 +2305,7 @@
}, },
"condition": { "condition": {
"page3type": [ "page3type": [
"Current",
"Fluid", "Fluid",
"FourValues", "FourValues",
"FourValues2", "FourValues2",
@@ -2279,6 +2314,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2311,6 +2347,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2341,6 +2378,7 @@
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2370,6 +2408,7 @@
"FourValues", "FourValues",
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2525,12 +2564,13 @@
"default": "TwoValues", "default": "TwoValues",
"description": "Type of page for page 4", "description": "Type of page for page 4",
"list": [ "list": [
"Anchor", "Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
"Clock", "Clock",
"Compass", "Compass",
"Current",
"DigitalOut", "DigitalOut",
"DST810", "DST810",
"Fluid", "Fluid",
@@ -2548,6 +2588,7 @@
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"Weather",
"WhitePage", "WhitePage",
"Wind", "Wind",
"WindPlot", "WindPlot",
@@ -2583,6 +2624,7 @@
}, },
"condition": { "condition": {
"page4type": [ "page4type": [
"Current",
"Fluid", "Fluid",
"FourValues", "FourValues",
"FourValues2", "FourValues2",
@@ -2591,6 +2633,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2622,6 +2665,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2651,6 +2695,7 @@
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2679,6 +2724,7 @@
"FourValues", "FourValues",
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2829,12 +2875,13 @@
"default": "ThreeValues", "default": "ThreeValues",
"description": "Type of page for page 5", "description": "Type of page for page 5",
"list": [ "list": [
"Anchor", "Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
"Clock", "Clock",
"Compass", "Compass",
"Current",
"DigitalOut", "DigitalOut",
"DST810", "DST810",
"Fluid", "Fluid",
@@ -2852,6 +2899,7 @@
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"Weather",
"WhitePage", "WhitePage",
"Wind", "Wind",
"WindPlot", "WindPlot",
@@ -2886,6 +2934,7 @@
}, },
"condition": { "condition": {
"page5type": [ "page5type": [
"Current",
"Fluid", "Fluid",
"FourValues", "FourValues",
"FourValues2", "FourValues2",
@@ -2894,6 +2943,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2924,6 +2974,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2979,6 +3030,7 @@
"FourValues", "FourValues",
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3124,12 +3176,13 @@
"default": "FourValues", "default": "FourValues",
"description": "Type of page for page 6", "description": "Type of page for page 6",
"list": [ "list": [
"Anchor", "Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
"Clock", "Clock",
"Compass", "Compass",
"Current",
"DigitalOut", "DigitalOut",
"DST810", "DST810",
"Fluid", "Fluid",
@@ -3147,6 +3200,7 @@
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"Weather",
"WhitePage", "WhitePage",
"Wind", "Wind",
"WindPlot", "WindPlot",
@@ -3180,6 +3234,7 @@
}, },
"condition": { "condition": {
"page6type": [ "page6type": [
"Current",
"Fluid", "Fluid",
"FourValues", "FourValues",
"FourValues2", "FourValues2",
@@ -3188,6 +3243,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3217,6 +3273,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3244,6 +3301,7 @@
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3270,6 +3328,7 @@
"FourValues", "FourValues",
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3410,12 +3469,13 @@
"default": "FourValues2", "default": "FourValues2",
"description": "Type of page for page 7", "description": "Type of page for page 7",
"list": [ "list": [
"Anchor", "Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
"Clock", "Clock",
"Compass", "Compass",
"Current",
"DigitalOut", "DigitalOut",
"DST810", "DST810",
"Fluid", "Fluid",
@@ -3433,6 +3493,7 @@
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"Weather",
"WhitePage", "WhitePage",
"Wind", "Wind",
"WindPlot", "WindPlot",
@@ -3465,6 +3526,7 @@
}, },
"condition": { "condition": {
"page7type": [ "page7type": [
"Current",
"Fluid", "Fluid",
"FourValues", "FourValues",
"FourValues2", "FourValues2",
@@ -3473,6 +3535,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3501,6 +3564,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3527,6 +3591,7 @@
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3552,6 +3617,7 @@
"FourValues", "FourValues",
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3687,12 +3753,13 @@
"default": "Clock", "default": "Clock",
"description": "Type of page for page 8", "description": "Type of page for page 8",
"list": [ "list": [
"Anchor", "Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
"Clock", "Clock",
"Compass", "Compass",
"Current",
"DigitalOut", "DigitalOut",
"DST810", "DST810",
"Fluid", "Fluid",
@@ -3710,6 +3777,7 @@
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"Weather",
"WhitePage", "WhitePage",
"Wind", "Wind",
"WindPlot", "WindPlot",
@@ -3741,6 +3809,7 @@
}, },
"condition": { "condition": {
"page8type": [ "page8type": [
"Current",
"Fluid", "Fluid",
"FourValues", "FourValues",
"FourValues2", "FourValues2",
@@ -3749,6 +3818,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3776,6 +3846,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3801,6 +3872,7 @@
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3825,6 +3897,7 @@
"FourValues", "FourValues",
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3955,12 +4028,13 @@
"default": "RollPitch", "default": "RollPitch",
"description": "Type of page for page 9", "description": "Type of page for page 9",
"list": [ "list": [
"Anchor", "Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
"Clock", "Clock",
"Compass", "Compass",
"Current",
"DigitalOut", "DigitalOut",
"DST810", "DST810",
"Fluid", "Fluid",
@@ -3978,6 +4052,7 @@
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"Weather",
"WhitePage", "WhitePage",
"Wind", "Wind",
"WindPlot", "WindPlot",
@@ -4008,6 +4083,7 @@
}, },
"condition": { "condition": {
"page9type": [ "page9type": [
"Current",
"Fluid", "Fluid",
"FourValues", "FourValues",
"FourValues2", "FourValues2",
@@ -4016,6 +4092,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -4042,6 +4119,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -4066,6 +4144,7 @@
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -4089,6 +4168,7 @@
"FourValues", "FourValues",
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -4214,12 +4294,13 @@
"default": "Battery2", "default": "Battery2",
"description": "Type of page for page 10", "description": "Type of page for page 10",
"list": [ "list": [
"Anchor", "Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
"Clock", "Clock",
"Compass", "Compass",
"Current",
"DigitalOut", "DigitalOut",
"DST810", "DST810",
"Fluid", "Fluid",
@@ -4237,6 +4318,7 @@
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"Weather",
"WhitePage", "WhitePage",
"Wind", "Wind",
"WindPlot", "WindPlot",
@@ -4266,6 +4348,7 @@
}, },
"condition": { "condition": {
"page10type": [ "page10type": [
"Current",
"Fluid", "Fluid",
"FourValues", "FourValues",
"FourValues2", "FourValues2",
@@ -4274,6 +4357,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -4299,6 +4383,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -4322,6 +4407,7 @@
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -4344,6 +4430,7 @@
"FourValues", "FourValues",
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
+137 -47
View File
@@ -76,18 +76,42 @@
} }
}, },
{ {
"name": "chainLength", "name": "leeKStd",
"label": "Anchor Chain Length [m]", "label": "Leeway Coefficient",
"type": "list",
"default": "---",
"description": "Boat coefficient K for leeway",
"list": [
"---",
"Racer [2]",
"Multihull Draggerboard [4]",
"Performance Cruiser [6]",
"Family Cruiser [9]",
"Heavy Displacement [13]",
"Multihull Fixed Keel [16]",
"Individual value"
],
"category": "OBP60 Settings",
"capabilities": {
"obp60": "true"
}
},
{
"name": "leeK",
"label": "Leeway Coefficient",
"type": "number", "type": "number",
"default": "0", "default": "0.00",
"check": "checkMinMax", "check": "checkMinMax",
"min": 0, "min": 0,
"max": 255, "max": 20,
"description": "The length of the anchor chain [0...255m]", "description": "Boat coefficient K for leeway - individual setting [0..20]",
"category": "OBP60 Settings", "category": "OBP60 Settings",
"capabilities": { "capabilities": {
"obp60":"true" "obp60":"true"
} },
"condition": [
{ "leeKStd": ["Individual value"] }
]
}, },
{ {
"name": "fuelTank", "name": "fuelTank",
@@ -244,6 +268,17 @@
"obp60": "true" "obp60": "true"
} }
}, },
{
"name": "smoothCharts",
"label": "Smooth chart lines",
"type": "boolean",
"default": "false",
"description": "Print chart lines with a smoothed gradient",
"category": "OBP60 Settings",
"capabilities": {
"obp60": "true"
}
},
{ {
"name": "lengthFormat", "name": "lengthFormat",
"label": "Length Format", "label": "Length Format",
@@ -1107,34 +1142,6 @@
{ "mapsource": ["Local Service"] } { "mapsource": ["Local Service"] }
] ]
}, },
{
"name": "mapServer",
"label": "map server",
"type": "string",
"default": "",
"description": "Server for converting map tiles. Use only one hostname or IP address",
"category": "OBP60 Navigation",
"capabilities": {
"obp60": "true"
},
"condition": [
{ "mapsource": ["Remote Service"] }
]
},
{
"name": "mapTilePath",
"label": "map tile path",
"type": "string",
"default": "map.php",
"description": "Path to converter access e.g. index.php or map.php",
"category": "OBP40 Navigation",
"capabilities": {
"obp40": "true"
},
"condition": [
{ "mapsource": ["Remote Service"] }
]
},
{ {
"name": "maptype", "name": "maptype",
"label": "Map Type", "label": "Map Type",
@@ -1277,8 +1284,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"}
], ],
@@ -1336,10 +1344,10 @@
}, },
{ {
"name": "valueprecision", "name": "valueprecision",
"label": "Display value precision", "label": "Value precision",
"type": "list", "type": "list",
"default": "2", "default": "2",
"description": "Maximum number of decimal places to display [1|2]", "description": "Number of decimal precision on display: [1|2] decimals",
"list": [ "list": [
"1", "1",
"2" "2"
@@ -1349,6 +1357,17 @@
"obp60":"true" "obp60":"true"
} }
}, },
{
"name": "smallDecimals",
"label": "Display small decimals",
"type": "boolean",
"default": "true",
"description": "Show decimals of values in smaller size [on|off]",
"category": "OBP60 Display",
"capabilities": {
"obp60": "true"
}
},
{ {
"name": "backlight", "name": "backlight",
"label": "Backlight Mode", "label": "Backlight Mode",
@@ -1528,6 +1547,7 @@
"obp60":"true" "obp60":"true"
} }
}, },
{ {
"name": "page1type", "name": "page1type",
"label": "Type", "label": "Type",
@@ -1535,12 +1555,13 @@
"default": "Voltage", "default": "Voltage",
"description": "Type of page for page 1", "description": "Type of page for page 1",
"list": [ "list": [
"Anchor", "Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
"Clock", "Clock",
"Compass", "Compass",
"Current",
"DigitalOut", "DigitalOut",
"DST810", "DST810",
"Fluid", "Fluid",
@@ -1558,6 +1579,7 @@
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"Weather",
"WhitePage", "WhitePage",
"Wind", "Wind",
"WindPlot", "WindPlot",
@@ -1596,6 +1618,7 @@
}, },
"condition": { "condition": {
"page1type": [ "page1type": [
"Current",
"Fluid", "Fluid",
"FourValues", "FourValues",
"FourValues2", "FourValues2",
@@ -1604,6 +1627,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -1638,6 +1662,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -1670,6 +1695,7 @@
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -1701,6 +1727,7 @@
"FourValues", "FourValues",
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -1836,12 +1863,13 @@
"default": "WindRose", "default": "WindRose",
"description": "Type of page for page 2", "description": "Type of page for page 2",
"list": [ "list": [
"Anchor", "Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
"Clock", "Clock",
"Compass", "Compass",
"Current",
"DigitalOut", "DigitalOut",
"DST810", "DST810",
"Fluid", "Fluid",
@@ -1859,6 +1887,7 @@
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"Weather",
"WhitePage", "WhitePage",
"Wind", "Wind",
"WindPlot", "WindPlot",
@@ -1896,6 +1925,7 @@
}, },
"condition": { "condition": {
"page2type": [ "page2type": [
"Current",
"Fluid", "Fluid",
"FourValues", "FourValues",
"FourValues2", "FourValues2",
@@ -1904,6 +1934,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -1937,6 +1968,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -1968,6 +2000,7 @@
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -1998,6 +2031,7 @@
"FourValues", "FourValues",
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2129,12 +2163,13 @@
"default": "OneValue", "default": "OneValue",
"description": "Type of page for page 3", "description": "Type of page for page 3",
"list": [ "list": [
"Anchor", "Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
"Clock", "Clock",
"Compass", "Compass",
"Current",
"DigitalOut", "DigitalOut",
"DST810", "DST810",
"Fluid", "Fluid",
@@ -2152,6 +2187,7 @@
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"Weather",
"WhitePage", "WhitePage",
"Wind", "Wind",
"WindPlot", "WindPlot",
@@ -2188,6 +2224,7 @@
}, },
"condition": { "condition": {
"page3type": [ "page3type": [
"Current",
"Fluid", "Fluid",
"FourValues", "FourValues",
"FourValues2", "FourValues2",
@@ -2196,6 +2233,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2228,6 +2266,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2258,6 +2297,7 @@
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2287,6 +2327,7 @@
"FourValues", "FourValues",
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2414,12 +2455,13 @@
"default": "TwoValues", "default": "TwoValues",
"description": "Type of page for page 4", "description": "Type of page for page 4",
"list": [ "list": [
"Anchor", "Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
"Clock", "Clock",
"Compass", "Compass",
"Current",
"DigitalOut", "DigitalOut",
"DST810", "DST810",
"Fluid", "Fluid",
@@ -2437,6 +2479,7 @@
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"Weather",
"WhitePage", "WhitePage",
"Wind", "Wind",
"WindPlot", "WindPlot",
@@ -2472,6 +2515,7 @@
}, },
"condition": { "condition": {
"page4type": [ "page4type": [
"Current",
"Fluid", "Fluid",
"FourValues", "FourValues",
"FourValues2", "FourValues2",
@@ -2480,6 +2524,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2511,6 +2556,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2540,6 +2586,7 @@
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2568,6 +2615,7 @@
"FourValues", "FourValues",
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2691,12 +2739,13 @@
"default": "ThreeValues", "default": "ThreeValues",
"description": "Type of page for page 5", "description": "Type of page for page 5",
"list": [ "list": [
"Anchor", "Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
"Clock", "Clock",
"Compass", "Compass",
"Current",
"DigitalOut", "DigitalOut",
"DST810", "DST810",
"Fluid", "Fluid",
@@ -2714,6 +2763,7 @@
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"Weather",
"WhitePage", "WhitePage",
"Wind", "Wind",
"WindPlot", "WindPlot",
@@ -2748,6 +2798,7 @@
}, },
"condition": { "condition": {
"page5type": [ "page5type": [
"Current",
"Fluid", "Fluid",
"FourValues", "FourValues",
"FourValues2", "FourValues2",
@@ -2756,6 +2807,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2786,6 +2838,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2814,6 +2867,7 @@
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2841,6 +2895,7 @@
"FourValues", "FourValues",
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2960,12 +3015,13 @@
"default": "FourValues", "default": "FourValues",
"description": "Type of page for page 6", "description": "Type of page for page 6",
"list": [ "list": [
"Anchor", "Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
"Clock", "Clock",
"Compass", "Compass",
"Current",
"DigitalOut", "DigitalOut",
"DST810", "DST810",
"Fluid", "Fluid",
@@ -2983,6 +3039,7 @@
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"Weather",
"WhitePage", "WhitePage",
"Wind", "Wind",
"WindPlot", "WindPlot",
@@ -3016,6 +3073,7 @@
}, },
"condition": { "condition": {
"page6type": [ "page6type": [
"Current",
"Fluid", "Fluid",
"FourValues", "FourValues",
"FourValues2", "FourValues2",
@@ -3024,6 +3082,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3053,6 +3112,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3080,6 +3140,7 @@
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3106,6 +3167,7 @@
"FourValues", "FourValues",
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3221,12 +3283,13 @@
"default": "FourValues2", "default": "FourValues2",
"description": "Type of page for page 7", "description": "Type of page for page 7",
"list": [ "list": [
"Anchor", "Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
"Clock", "Clock",
"Compass", "Compass",
"Current",
"DigitalOut", "DigitalOut",
"DST810", "DST810",
"Fluid", "Fluid",
@@ -3244,6 +3307,7 @@
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"Weather",
"WhitePage", "WhitePage",
"Wind", "Wind",
"WindPlot", "WindPlot",
@@ -3276,6 +3340,7 @@
}, },
"condition": { "condition": {
"page7type": [ "page7type": [
"Current",
"Fluid", "Fluid",
"FourValues", "FourValues",
"FourValues2", "FourValues2",
@@ -3284,6 +3349,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3312,6 +3378,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3338,6 +3405,7 @@
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3363,6 +3431,7 @@
"FourValues", "FourValues",
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3474,12 +3543,13 @@
"default": "Clock", "default": "Clock",
"description": "Type of page for page 8", "description": "Type of page for page 8",
"list": [ "list": [
"Anchor", "Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
"Clock", "Clock",
"Compass", "Compass",
"Current",
"DigitalOut", "DigitalOut",
"DST810", "DST810",
"Fluid", "Fluid",
@@ -3497,6 +3567,7 @@
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"Weather",
"WhitePage", "WhitePage",
"Wind", "Wind",
"WindPlot", "WindPlot",
@@ -3528,6 +3599,7 @@
}, },
"condition": { "condition": {
"page8type": [ "page8type": [
"Current",
"Fluid", "Fluid",
"FourValues", "FourValues",
"FourValues2", "FourValues2",
@@ -3536,6 +3608,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3563,6 +3636,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3588,6 +3662,7 @@
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3612,6 +3687,7 @@
"FourValues", "FourValues",
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3719,12 +3795,13 @@
"default": "RollPitch", "default": "RollPitch",
"description": "Type of page for page 9", "description": "Type of page for page 9",
"list": [ "list": [
"Anchor", "Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
"Clock", "Clock",
"Compass", "Compass",
"Current",
"DigitalOut", "DigitalOut",
"DST810", "DST810",
"Fluid", "Fluid",
@@ -3742,6 +3819,7 @@
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"Weather",
"WhitePage", "WhitePage",
"Wind", "Wind",
"WindPlot", "WindPlot",
@@ -3772,6 +3850,7 @@
}, },
"condition": { "condition": {
"page9type": [ "page9type": [
"Current",
"Fluid", "Fluid",
"FourValues", "FourValues",
"FourValues2", "FourValues2",
@@ -3780,6 +3859,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3806,6 +3886,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3830,6 +3911,7 @@
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3853,6 +3935,7 @@
"FourValues", "FourValues",
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3956,12 +4039,13 @@
"default": "Battery2", "default": "Battery2",
"description": "Type of page for page 10", "description": "Type of page for page 10",
"list": [ "list": [
"Anchor", "Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
"Clock", "Clock",
"Compass", "Compass",
"Current",
"DigitalOut", "DigitalOut",
"DST810", "DST810",
"Fluid", "Fluid",
@@ -3979,6 +4063,7 @@
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"Weather",
"WhitePage", "WhitePage",
"Wind", "Wind",
"WindPlot", "WindPlot",
@@ -4008,6 +4093,7 @@
}, },
"condition": { "condition": {
"page10type": [ "page10type": [
"Current",
"Fluid", "Fluid",
"FourValues", "FourValues",
"FourValues2", "FourValues2",
@@ -4016,6 +4102,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -4041,6 +4128,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -4064,6 +4152,7 @@
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -4086,6 +4175,7 @@
"FourValues", "FourValues",
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
File diff suppressed because it is too large Load Diff
+4 -1
View File
@@ -3,4 +3,7 @@ Debugging tool
log.txt = text file with error messages from terminal console log.txt = text file with error messages from terminal console
tools/decoder.py -p ESP32S3 -t ~/.platformio/packages/toolchain-xtensa-esp32s3/ -e .pio/build/obp60_s3/firmware.elf log.txt cd /home/norbert/esp32-nmea2000-obp60
tools/decoder.py -p ESP32S3 -t ~/.platformio/packages/toolchain-xtensa-esp32s3/ -e .pio/build/obp60_s3/firmware.elf log.txt
tools/decoder.py -p ESP32S3 -t ~/.platformio/packages/toolchain-xtensa-esp32s3/ -e .pio/build/obp70_s3/firmware.elf /home/norbert/Dokumente/Multifunktionsdisplay_OBP60/Crashes/20260313/log.txt
+16 -6
View File
@@ -2,6 +2,14 @@
import subprocess 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 patching = False
epdtype = "unknown" epdtype = "unknown"
@@ -46,11 +54,13 @@ if patching:
print("patchdir not found, no patches applied") print("patchdir not found, no patches applied")
else: else:
patchfiles = [f for f in os.listdir(patchdir)] patchfiles = [f for f in os.listdir(patchdir)]
for p in patchfiles: if len(patchfiles) > 0:
patch = os.path.join(patchdir, p) for p in patchfiles:
print(f"applying {patch}") patch = os.path.join(patchdir, p)
res = subprocess.run(["git", "apply", patch], capture_output=True, text=True) print(f"applying {patch}")
if res.returncode != 0: res = subprocess.run(["git", "apply", patch], capture_output=True, text=True)
print(res.stderr) if res.returncode != 0:
print(res.stderr)
env.AddPostAction("$PROGPATH", cleanup_patches)
else: else:
print("no patches found") print("no patches found")
@@ -0,0 +1,278 @@
const uint8_t DSEG7Classic_BoldItalic10pt7bBitmaps[] PROGMEM = {
0xFE, 0x18, 0x61, 0x86, 0x18, 0x61, 0x86, 0x18, 0x61, 0x87, 0xF0, 0xFE,
0x18, 0x61, 0x86, 0x18, 0x61, 0x86, 0x18, 0x61, 0x87, 0xF0, 0xFE, 0x18,
0x61, 0x86, 0x18, 0x61, 0x86, 0x18, 0x61, 0x87, 0xF0, 0xFE, 0x18, 0x61,
0x86, 0x18, 0x61, 0x86, 0x18, 0x61, 0x87, 0xF0, 0xFE, 0x18, 0x61, 0x86,
0x18, 0x61, 0x86, 0x18, 0x61, 0x87, 0xF0, 0xFE, 0x18, 0x61, 0x86, 0x18,
0x61, 0x86, 0x18, 0x61, 0x87, 0xF0, 0xFE, 0x18, 0x61, 0x86, 0x18, 0x61,
0x86, 0x18, 0x61, 0x87, 0xF0, 0xFE, 0x18, 0x61, 0x86, 0x18, 0x61, 0x86,
0x18, 0x61, 0x87, 0xF0, 0xFE, 0x18, 0x61, 0x86, 0x18, 0x61, 0x86, 0x18,
0x61, 0x87, 0xF0, 0xFE, 0x18, 0x61, 0x86, 0x18, 0x61, 0x86, 0x18, 0x61,
0x87, 0xF0, 0xFE, 0x18, 0x61, 0x86, 0x18, 0x61, 0x86, 0x18, 0x61, 0x87,
0xF0, 0xFF, 0x7F, 0x80, 0xF0, 0xFE, 0x18, 0x61, 0x86, 0x18, 0x61, 0x86,
0x18, 0x61, 0x87, 0xF0, 0x1F, 0xF8, 0xBF, 0xD3, 0x00, 0xCC, 0x07, 0x70,
0x1D, 0xC0, 0x77, 0x01, 0xDC, 0x07, 0x70, 0x19, 0x00, 0x24, 0x00, 0x98,
0x06, 0x60, 0x19, 0x80, 0x66, 0x03, 0xB8, 0x0E, 0xE0, 0x3B, 0x80, 0xEB,
0xFF, 0x9F, 0xF8, 0x13, 0x77, 0x77, 0x76, 0x22, 0x66, 0x6E, 0xEE, 0xE6,
0x1F, 0xF8, 0x3F, 0xD0, 0x00, 0xC0, 0x07, 0x00, 0x1C, 0x00, 0x70, 0x01,
0xC0, 0x07, 0x00, 0x18, 0x7F, 0xA5, 0xFE, 0x18, 0x00, 0x60, 0x01, 0x80,
0x06, 0x00, 0x38, 0x00, 0xE0, 0x03, 0x80, 0x0B, 0xFE, 0x1F, 0xF8, 0x3F,
0xF0, 0xFF, 0x40, 0x06, 0x00, 0x70, 0x03, 0x80, 0x1C, 0x00, 0xE0, 0x07,
0x00, 0x31, 0xFE, 0x8F, 0xF4, 0x00, 0x60, 0x03, 0x00, 0x18, 0x01, 0xC0,
0x0E, 0x00, 0x70, 0x03, 0x9F, 0xFD, 0xFF, 0x80, 0x40, 0x0B, 0x00, 0xD8,
0x0F, 0xC0, 0x7E, 0x03, 0xF0, 0x1F, 0x80, 0xFC, 0x06, 0xBF, 0xD1, 0xFE,
0x80, 0x0C, 0x00, 0x60, 0x03, 0x00, 0x38, 0x01, 0xC0, 0x0E, 0x00, 0x70,
0x01, 0x80, 0x3F, 0xF5, 0xFE, 0x60, 0x06, 0x00, 0xE0, 0x0E, 0x00, 0xE0,
0x0E, 0x00, 0xE0, 0x0B, 0xFC, 0x3F, 0xD0, 0x03, 0x00, 0x30, 0x03, 0x00,
0x70, 0x07, 0x00, 0x70, 0x07, 0x7F, 0xFF, 0xFC, 0x1F, 0xF9, 0x7F, 0x8C,
0x00, 0x60, 0x07, 0x00, 0x38, 0x01, 0xC0, 0x0E, 0x00, 0x70, 0x02, 0xFF,
0x17, 0xFA, 0xC0, 0x36, 0x01, 0xB0, 0x0D, 0x80, 0xFC, 0x07, 0xE0, 0x3F,
0x01, 0xEF, 0xFE, 0xFF, 0xC0, 0x3F, 0xF2, 0xFF, 0x58, 0x06, 0xC0, 0x7E,
0x03, 0xF0, 0x1F, 0x80, 0xFC, 0x07, 0xE0, 0x34, 0x00, 0x80, 0x04, 0x00,
0x60, 0x03, 0x00, 0x18, 0x01, 0xC0, 0x0E, 0x00, 0x70, 0x03, 0x80, 0x0C,
0x1F, 0xF8, 0xBF, 0xD3, 0x00, 0xCC, 0x07, 0x70, 0x1D, 0xC0, 0x77, 0x01,
0xDC, 0x07, 0x70, 0x19, 0x7F, 0xA5, 0xFE, 0x98, 0x06, 0x60, 0x19, 0x80,
0x66, 0x03, 0xB8, 0x0E, 0xE0, 0x3B, 0x80, 0xEB, 0xFF, 0x9F, 0xF8, 0x3F,
0xF2, 0xFF, 0x58, 0x06, 0xC0, 0x7E, 0x03, 0xF0, 0x1F, 0x80, 0xFC, 0x07,
0xE0, 0x35, 0xFE, 0x8F, 0xF4, 0x00, 0x60, 0x03, 0x00, 0x18, 0x01, 0xC0,
0x0E, 0x00, 0x70, 0x03, 0x9F, 0xFD, 0xFF, 0x80, 0x67, 0x00, 0x00, 0x00,
0x4E, 0x40, 0xFE, 0x18, 0x61, 0x86, 0x18, 0x61, 0x86, 0x18, 0x61, 0x87,
0xF0, 0xFE, 0x18, 0x61, 0x86, 0x18, 0x61, 0x86, 0x18, 0x61, 0x87, 0xF0,
0xFE, 0x18, 0x61, 0x86, 0x18, 0x61, 0x86, 0x18, 0x61, 0x87, 0xF0, 0xFE,
0x18, 0x61, 0x86, 0x18, 0x61, 0x86, 0x18, 0x61, 0x87, 0xF0, 0xFE, 0x18,
0x61, 0x86, 0x18, 0x61, 0x86, 0x18, 0x61, 0x87, 0xF0, 0xFE, 0x18, 0x61,
0x86, 0x18, 0x61, 0x86, 0x18, 0x61, 0x87, 0xF0, 0x1F, 0xF8, 0xBF, 0xD3,
0x00, 0xCC, 0x07, 0x70, 0x1D, 0xC0, 0x77, 0x01, 0xDC, 0x07, 0x70, 0x19,
0x7F, 0xA5, 0xFE, 0x98, 0x06, 0x60, 0x19, 0x80, 0x66, 0x03, 0xB8, 0x0E,
0xE0, 0x3B, 0x80, 0xE8, 0x01, 0x80, 0x20, 0x01, 0x80, 0x0C, 0x00, 0xE0,
0x07, 0x00, 0x38, 0x01, 0xC0, 0x0E, 0x00, 0x5F, 0xE2, 0xFF, 0x58, 0x06,
0xC0, 0x36, 0x01, 0xB0, 0x1F, 0x80, 0xFC, 0x07, 0xE0, 0x3D, 0xFF, 0xDF,
0xF8, 0x1F, 0xE5, 0xFE, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0xE0, 0x0E,
0x00, 0xE0, 0x0B, 0xFE, 0x7F, 0xE0, 0x00, 0x04, 0x00, 0x30, 0x01, 0xC0,
0x07, 0x00, 0x1C, 0x00, 0x70, 0x01, 0xC0, 0x06, 0x1F, 0xE9, 0x7F, 0xA6,
0x01, 0x98, 0x06, 0x60, 0x19, 0x80, 0xEE, 0x03, 0xB8, 0x0E, 0xE0, 0x3A,
0xFF, 0xE7, 0xFE, 0x00, 0x1F, 0xF9, 0x7F, 0x8C, 0x00, 0x60, 0x07, 0x00,
0x38, 0x01, 0xC0, 0x0E, 0x00, 0x70, 0x02, 0xFF, 0x17, 0xF8, 0xC0, 0x06,
0x00, 0x30, 0x01, 0x80, 0x1C, 0x00, 0xE0, 0x07, 0x00, 0x2F, 0xF8, 0xFF,
0xC0, 0x1F, 0xF9, 0x7F, 0x8C, 0x00, 0x60, 0x07, 0x00, 0x38, 0x01, 0xC0,
0x0E, 0x00, 0x70, 0x02, 0xFF, 0x17, 0xF8, 0xC0, 0x06, 0x00, 0x30, 0x01,
0x80, 0x1C, 0x00, 0xE0, 0x07, 0x00, 0x20, 0x00, 0x1F, 0xF9, 0x7F, 0x8C,
0x00, 0x60, 0x07, 0x00, 0x38, 0x01, 0xC0, 0x0E, 0x00, 0x70, 0x02, 0x00,
0x10, 0x02, 0xC0, 0x36, 0x01, 0xB0, 0x0D, 0x80, 0xFC, 0x07, 0xE0, 0x3F,
0x01, 0xEF, 0xFE, 0xFF, 0xC0, 0x20, 0x01, 0x80, 0x0C, 0x00, 0xE0, 0x07,
0x00, 0x38, 0x01, 0xC0, 0x0E, 0x00, 0x5F, 0xE2, 0xFF, 0x58, 0x06, 0xC0,
0x36, 0x01, 0xB0, 0x1F, 0x80, 0xFC, 0x07, 0xE0, 0x3C, 0x00, 0xC0, 0x2D,
0xBF, 0xFF, 0x60, 0x00, 0x04, 0x00, 0x30, 0x01, 0xC0, 0x07, 0x00, 0x1C,
0x00, 0x70, 0x01, 0xC0, 0x06, 0x00, 0x09, 0x00, 0x26, 0x01, 0x98, 0x06,
0x60, 0x19, 0x80, 0xEE, 0x03, 0xB8, 0x0E, 0xE0, 0x3A, 0xFF, 0xE7, 0xFE,
0x00, 0x1F, 0xF9, 0x7F, 0x8C, 0x00, 0x60, 0x07, 0x00, 0x38, 0x01, 0xC0,
0x0E, 0x00, 0x70, 0x02, 0xFF, 0x17, 0xFA, 0xC0, 0x36, 0x01, 0xB0, 0x0D,
0x80, 0xFC, 0x07, 0xE0, 0x3F, 0x01, 0xE0, 0x06, 0x20, 0x03, 0x00, 0x30,
0x07, 0x00, 0x70, 0x07, 0x00, 0x70, 0x07, 0x00, 0x40, 0x04, 0x00, 0x60,
0x06, 0x00, 0x60, 0x06, 0x00, 0xE0, 0x0E, 0x00, 0xE0, 0x0B, 0xFE, 0x7F,
0xE0, 0x1F, 0xF8, 0xBF, 0xD3, 0x00, 0xCC, 0x07, 0x70, 0x1D, 0xC0, 0x77,
0x01, 0xDC, 0x07, 0x70, 0x19, 0x00, 0x24, 0x00, 0x98, 0x06, 0x60, 0x19,
0x80, 0x66, 0x03, 0xB8, 0x0E, 0xE0, 0x3B, 0x80, 0xE8, 0x01, 0x80, 0x1F,
0xE2, 0xFF, 0x58, 0x06, 0xC0, 0x36, 0x01, 0xB0, 0x1F, 0x80, 0xFC, 0x07,
0xE0, 0x3C, 0x00, 0xC0, 0x1F, 0xE2, 0xFF, 0x58, 0x06, 0xC0, 0x36, 0x01,
0xB0, 0x1F, 0x80, 0xFC, 0x07, 0xE0, 0x3D, 0xFF, 0xDF, 0xF8, 0x1F, 0xF8,
0xBF, 0xD3, 0x00, 0xCC, 0x07, 0x70, 0x1D, 0xC0, 0x77, 0x01, 0xDC, 0x07,
0x70, 0x19, 0x7F, 0xA5, 0xFE, 0x18, 0x00, 0x60, 0x01, 0x80, 0x06, 0x00,
0x38, 0x00, 0xE0, 0x03, 0x80, 0x08, 0x00, 0x00, 0x3F, 0xF2, 0xFF, 0x58,
0x06, 0xC0, 0x7E, 0x03, 0xF0, 0x1F, 0x80, 0xFC, 0x07, 0xE0, 0x35, 0xFE,
0x8F, 0xF4, 0x00, 0x60, 0x03, 0x00, 0x18, 0x01, 0xC0, 0x0E, 0x00, 0x70,
0x03, 0x80, 0x0C, 0x1F, 0xE5, 0xFE, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00,
0xE0, 0x0E, 0x00, 0xE0, 0x08, 0x00, 0x40, 0x06, 0x00, 0x60, 0x0E, 0x00,
0xE0, 0x0E, 0x00, 0xE0, 0x0E, 0x00, 0xBF, 0xC3, 0xFD, 0x00, 0x30, 0x03,
0x00, 0x30, 0x07, 0x00, 0x70, 0x07, 0x00, 0x77, 0xFF, 0xFF, 0xC0, 0x20,
0x03, 0x00, 0x30, 0x07, 0x00, 0x70, 0x07, 0x00, 0x70, 0x07, 0x00, 0x5F,
0xE5, 0xFE, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0xE0, 0x0E, 0x00, 0xE0,
0x0B, 0xFE, 0x7F, 0xE0, 0x40, 0x0B, 0x00, 0xD8, 0x06, 0xC0, 0x36, 0x03,
0xF0, 0x1F, 0x80, 0xFC, 0x07, 0xBF, 0xFB, 0xFF, 0x00, 0x20, 0x04, 0xC0,
0x33, 0x01, 0xDC, 0x07, 0x70, 0x1D, 0xC0, 0x77, 0x01, 0xDC, 0x06, 0x40,
0x09, 0x00, 0x26, 0x01, 0x98, 0x06, 0x60, 0x19, 0x80, 0xEE, 0x03, 0xB8,
0x0E, 0xE0, 0x3A, 0xFF, 0xE7, 0xFE, 0x00, 0x20, 0x04, 0xC0, 0x33, 0x01,
0xDC, 0x07, 0x70, 0x1D, 0xC0, 0x77, 0x01, 0xDC, 0x06, 0x5F, 0xE9, 0x7F,
0xA6, 0x01, 0x98, 0x06, 0x60, 0x19, 0x80, 0xEE, 0x03, 0xB8, 0x0E, 0xE0,
0x3A, 0xFF, 0xE7, 0xFE, 0x00, 0x20, 0x04, 0xC0, 0x33, 0x01, 0xDC, 0x07,
0x70, 0x1D, 0xC0, 0x77, 0x01, 0xDC, 0x06, 0x5F, 0xE9, 0x7F, 0xA6, 0x01,
0x98, 0x06, 0x60, 0x19, 0x80, 0xEE, 0x03, 0xB8, 0x0E, 0xE0, 0x3A, 0x00,
0x60, 0x40, 0x0B, 0x00, 0xD8, 0x0F, 0xC0, 0x7E, 0x03, 0xF0, 0x1F, 0x80,
0xFC, 0x06, 0xBF, 0xD1, 0xFE, 0x80, 0x0C, 0x00, 0x60, 0x03, 0x00, 0x38,
0x01, 0xC0, 0x0E, 0x00, 0x73, 0xFF, 0xBF, 0xF0, 0x1F, 0xF8, 0x3F, 0xD0,
0x00, 0xC0, 0x07, 0x00, 0x1C, 0x00, 0x70, 0x01, 0xC0, 0x07, 0x00, 0x18,
0x00, 0x24, 0x00, 0x18, 0x00, 0x60, 0x01, 0x80, 0x06, 0x00, 0x38, 0x00,
0xE0, 0x03, 0x80, 0x0B, 0xFE, 0x1F, 0xF8, 0xFE, 0x18, 0x61, 0x86, 0x18,
0x61, 0x86, 0x18, 0x61, 0x87, 0xF0, 0xFE, 0x18, 0x61, 0x86, 0x18, 0x61,
0x86, 0x18, 0x61, 0x87, 0xF0, 0xFE, 0x18, 0x61, 0x86, 0x18, 0x61, 0x86,
0x18, 0x61, 0x87, 0xF0, 0xFE, 0x18, 0x61, 0x86, 0x18, 0x61, 0x86, 0x18,
0x61, 0x87, 0xF0, 0x7F, 0xDF, 0xF8, 0xFE, 0x18, 0x61, 0x86, 0x18, 0x61,
0x86, 0x18, 0x61, 0x87, 0xF0, 0x1F, 0xF8, 0xBF, 0xD3, 0x00, 0xCC, 0x07,
0x70, 0x1D, 0xC0, 0x77, 0x01, 0xDC, 0x07, 0x70, 0x19, 0x7F, 0xA5, 0xFE,
0x98, 0x06, 0x60, 0x19, 0x80, 0x66, 0x03, 0xB8, 0x0E, 0xE0, 0x3B, 0x80,
0xE8, 0x01, 0x80, 0x20, 0x01, 0x80, 0x0C, 0x00, 0xE0, 0x07, 0x00, 0x38,
0x01, 0xC0, 0x0E, 0x00, 0x5F, 0xE2, 0xFF, 0x58, 0x06, 0xC0, 0x36, 0x01,
0xB0, 0x1F, 0x80, 0xFC, 0x07, 0xE0, 0x3D, 0xFF, 0xDF, 0xF8, 0x1F, 0xE5,
0xFE, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0xE0, 0x0E, 0x00, 0xE0, 0x0B,
0xFE, 0x7F, 0xE0, 0x00, 0x04, 0x00, 0x30, 0x01, 0xC0, 0x07, 0x00, 0x1C,
0x00, 0x70, 0x01, 0xC0, 0x06, 0x1F, 0xE9, 0x7F, 0xA6, 0x01, 0x98, 0x06,
0x60, 0x19, 0x80, 0xEE, 0x03, 0xB8, 0x0E, 0xE0, 0x3A, 0xFF, 0xE7, 0xFE,
0x00, 0x1F, 0xF9, 0x7F, 0x8C, 0x00, 0x60, 0x07, 0x00, 0x38, 0x01, 0xC0,
0x0E, 0x00, 0x70, 0x02, 0xFF, 0x17, 0xF8, 0xC0, 0x06, 0x00, 0x30, 0x01,
0x80, 0x1C, 0x00, 0xE0, 0x07, 0x00, 0x2F, 0xF8, 0xFF, 0xC0, 0x1F, 0xF9,
0x7F, 0x8C, 0x00, 0x60, 0x07, 0x00, 0x38, 0x01, 0xC0, 0x0E, 0x00, 0x70,
0x02, 0xFF, 0x17, 0xF8, 0xC0, 0x06, 0x00, 0x30, 0x01, 0x80, 0x1C, 0x00,
0xE0, 0x07, 0x00, 0x20, 0x00, 0x1F, 0xF9, 0x7F, 0x8C, 0x00, 0x60, 0x07,
0x00, 0x38, 0x01, 0xC0, 0x0E, 0x00, 0x70, 0x02, 0x00, 0x10, 0x02, 0xC0,
0x36, 0x01, 0xB0, 0x0D, 0x80, 0xFC, 0x07, 0xE0, 0x3F, 0x01, 0xEF, 0xFE,
0xFF, 0xC0, 0x20, 0x01, 0x80, 0x0C, 0x00, 0xE0, 0x07, 0x00, 0x38, 0x01,
0xC0, 0x0E, 0x00, 0x5F, 0xE2, 0xFF, 0x58, 0x06, 0xC0, 0x36, 0x01, 0xB0,
0x1F, 0x80, 0xFC, 0x07, 0xE0, 0x3C, 0x00, 0xC0, 0x2D, 0xBF, 0xFF, 0x60,
0x00, 0x04, 0x00, 0x30, 0x01, 0xC0, 0x07, 0x00, 0x1C, 0x00, 0x70, 0x01,
0xC0, 0x06, 0x00, 0x09, 0x00, 0x26, 0x01, 0x98, 0x06, 0x60, 0x19, 0x80,
0xEE, 0x03, 0xB8, 0x0E, 0xE0, 0x3A, 0xFF, 0xE7, 0xFE, 0x00, 0x1F, 0xF9,
0x7F, 0x8C, 0x00, 0x60, 0x07, 0x00, 0x38, 0x01, 0xC0, 0x0E, 0x00, 0x70,
0x02, 0xFF, 0x17, 0xFA, 0xC0, 0x36, 0x01, 0xB0, 0x0D, 0x80, 0xFC, 0x07,
0xE0, 0x3F, 0x01, 0xE0, 0x06, 0x20, 0x03, 0x00, 0x30, 0x07, 0x00, 0x70,
0x07, 0x00, 0x70, 0x07, 0x00, 0x40, 0x04, 0x00, 0x60, 0x06, 0x00, 0x60,
0x06, 0x00, 0xE0, 0x0E, 0x00, 0xE0, 0x0B, 0xFE, 0x7F, 0xE0, 0x1F, 0xF8,
0xBF, 0xD3, 0x00, 0xCC, 0x07, 0x70, 0x1D, 0xC0, 0x77, 0x01, 0xDC, 0x07,
0x70, 0x19, 0x00, 0x24, 0x00, 0x98, 0x06, 0x60, 0x19, 0x80, 0x66, 0x03,
0xB8, 0x0E, 0xE0, 0x3B, 0x80, 0xE8, 0x01, 0x80, 0x1F, 0xE2, 0xFF, 0x58,
0x06, 0xC0, 0x36, 0x01, 0xB0, 0x1F, 0x80, 0xFC, 0x07, 0xE0, 0x3C, 0x00,
0xC0, 0x1F, 0xE2, 0xFF, 0x58, 0x06, 0xC0, 0x36, 0x01, 0xB0, 0x1F, 0x80,
0xFC, 0x07, 0xE0, 0x3D, 0xFF, 0xDF, 0xF8, 0x1F, 0xF8, 0xBF, 0xD3, 0x00,
0xCC, 0x07, 0x70, 0x1D, 0xC0, 0x77, 0x01, 0xDC, 0x07, 0x70, 0x19, 0x7F,
0xA5, 0xFE, 0x18, 0x00, 0x60, 0x01, 0x80, 0x06, 0x00, 0x38, 0x00, 0xE0,
0x03, 0x80, 0x08, 0x00, 0x00, 0x3F, 0xF2, 0xFF, 0x58, 0x06, 0xC0, 0x7E,
0x03, 0xF0, 0x1F, 0x80, 0xFC, 0x07, 0xE0, 0x35, 0xFE, 0x8F, 0xF4, 0x00,
0x60, 0x03, 0x00, 0x18, 0x01, 0xC0, 0x0E, 0x00, 0x70, 0x03, 0x80, 0x0C,
0x1F, 0xE5, 0xFE, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0xE0, 0x0E, 0x00,
0xE0, 0x08, 0x00, 0x40, 0x06, 0x00, 0x60, 0x0E, 0x00, 0xE0, 0x0E, 0x00,
0xE0, 0x0E, 0x00, 0xBF, 0xC3, 0xFD, 0x00, 0x30, 0x03, 0x00, 0x30, 0x07,
0x00, 0x70, 0x07, 0x00, 0x77, 0xFF, 0xFF, 0xC0, 0x20, 0x03, 0x00, 0x30,
0x07, 0x00, 0x70, 0x07, 0x00, 0x70, 0x07, 0x00, 0x5F, 0xE5, 0xFE, 0x60,
0x06, 0x00, 0x60, 0x06, 0x00, 0xE0, 0x0E, 0x00, 0xE0, 0x0B, 0xFE, 0x7F,
0xE0, 0x40, 0x0B, 0x00, 0xD8, 0x06, 0xC0, 0x36, 0x03, 0xF0, 0x1F, 0x80,
0xFC, 0x07, 0xBF, 0xFB, 0xFF, 0x00, 0x20, 0x04, 0xC0, 0x33, 0x01, 0xDC,
0x07, 0x70, 0x1D, 0xC0, 0x77, 0x01, 0xDC, 0x06, 0x40, 0x09, 0x00, 0x26,
0x01, 0x98, 0x06, 0x60, 0x19, 0x80, 0xEE, 0x03, 0xB8, 0x0E, 0xE0, 0x3A,
0xFF, 0xE7, 0xFE, 0x00, 0x20, 0x04, 0xC0, 0x33, 0x01, 0xDC, 0x07, 0x70,
0x1D, 0xC0, 0x77, 0x01, 0xDC, 0x06, 0x5F, 0xE9, 0x7F, 0xA6, 0x01, 0x98,
0x06, 0x60, 0x19, 0x80, 0xEE, 0x03, 0xB8, 0x0E, 0xE0, 0x3A, 0xFF, 0xE7,
0xFE, 0x00, 0x20, 0x04, 0xC0, 0x33, 0x01, 0xDC, 0x07, 0x70, 0x1D, 0xC0,
0x77, 0x01, 0xDC, 0x06, 0x5F, 0xE9, 0x7F, 0xA6, 0x01, 0x98, 0x06, 0x60,
0x19, 0x80, 0xEE, 0x03, 0xB8, 0x0E, 0xE0, 0x3A, 0x00, 0x60, 0x40, 0x0B,
0x00, 0xD8, 0x0F, 0xC0, 0x7E, 0x03, 0xF0, 0x1F, 0x80, 0xFC, 0x06, 0xBF,
0xD1, 0xFE, 0x80, 0x0C, 0x00, 0x60, 0x03, 0x00, 0x38, 0x01, 0xC0, 0x0E,
0x00, 0x73, 0xFF, 0xBF, 0xF0, 0x1F, 0xF8, 0x3F, 0xD0, 0x00, 0xC0, 0x07,
0x00, 0x1C, 0x00, 0x70, 0x01, 0xC0, 0x07, 0x00, 0x18, 0x00, 0x24, 0x00,
0x18, 0x00, 0x60, 0x01, 0x80, 0x06, 0x00, 0x38, 0x00, 0xE0, 0x03, 0x80,
0x0B, 0xFE, 0x1F, 0xF8, 0xFE, 0x18, 0x61, 0x86, 0x18, 0x61, 0x86, 0x18,
0x61, 0x87, 0xF0, 0xFE, 0x18, 0x61, 0x86, 0x18, 0x61, 0x86, 0x18, 0x61,
0x87, 0xF0, 0xFE, 0x18, 0x61, 0x86, 0x18, 0x61, 0x86, 0x18, 0x61, 0x87,
0xF0, 0xFE, 0x18, 0x61, 0x86, 0x18, 0x61, 0x86, 0x18, 0x61, 0x87, 0xF0 };
const GFXglyph DSEG7Classic_BoldItalic10pt7bGlyphs[] PROGMEM = {
{ 0, 0, 0, 4, 0, 1 }, // 0x20 ' '
{ 0, 0, 0, 16, 0, 1 }, // 0x21 '!'
{ 0, 6, 14, 7, 1, -13 }, // 0x22 '"'
{ 11, 6, 14, 7, 1, -13 }, // 0x23 '#'
{ 22, 6, 14, 7, 1, -13 }, // 0x24 '$'
{ 33, 6, 14, 7, 1, -13 }, // 0x25 '%'
{ 44, 6, 14, 7, 1, -13 }, // 0x26 '&'
{ 55, 6, 14, 7, 1, -13 }, // 0x27 '''
{ 66, 6, 14, 7, 1, -13 }, // 0x28 '('
{ 77, 6, 14, 7, 1, -13 }, // 0x29 ')'
{ 88, 6, 14, 7, 1, -13 }, // 0x2A '*'
{ 99, 6, 14, 7, 1, -13 }, // 0x2B '+'
{ 110, 6, 14, 7, 1, -13 }, // 0x2C ','
{ 121, 9, 2, 16, 4, -10 }, // 0x2D '-'
{ 124, 2, 2, 0, -2, -1 }, // 0x2E '.'
{ 125, 6, 14, 7, 1, -13 }, // 0x2F '/'
{ 136, 14, 20, 16, 1, -19 }, // 0x30 '0'
{ 171, 4, 18, 16, 11, -18 }, // 0x31 '1'
{ 180, 14, 20, 16, 1, -19 }, // 0x32 '2'
{ 215, 13, 20, 16, 2, -19 }, // 0x33 '3'
{ 248, 13, 18, 16, 2, -18 }, // 0x34 '4'
{ 278, 12, 20, 16, 2, -19 }, // 0x35 '5'
{ 308, 13, 20, 16, 1, -19 }, // 0x36 '6'
{ 341, 13, 19, 16, 2, -19 }, // 0x37 '7'
{ 372, 14, 20, 16, 1, -19 }, // 0x38 '8'
{ 407, 13, 20, 16, 2, -19 }, // 0x39 '9'
{ 440, 4, 11, 4, 0, -14 }, // 0x3A ':'
{ 446, 6, 14, 7, 1, -13 }, // 0x3B ';'
{ 457, 6, 14, 7, 1, -13 }, // 0x3C '<'
{ 468, 6, 14, 7, 1, -13 }, // 0x3D '='
{ 479, 6, 14, 7, 1, -13 }, // 0x3E '>'
{ 490, 6, 14, 7, 1, -13 }, // 0x3F '?'
{ 501, 6, 14, 7, 1, -13 }, // 0x40 '@'
{ 512, 14, 19, 16, 1, -19 }, // 0x41 'A'
{ 546, 13, 19, 16, 1, -18 }, // 0x42 'B'
{ 577, 12, 11, 16, 1, -10 }, // 0x43 'C'
{ 594, 14, 19, 16, 1, -18 }, // 0x44 'D'
{ 628, 13, 20, 16, 1, -19 }, // 0x45 'E'
{ 661, 13, 19, 16, 1, -19 }, // 0x46 'F'
{ 692, 13, 20, 16, 1, -19 }, // 0x47 'G'
{ 725, 13, 18, 16, 1, -18 }, // 0x48 'H'
{ 755, 3, 9, 16, 11, -9 }, // 0x49 'I'
{ 759, 14, 19, 16, 1, -18 }, // 0x4A 'J'
{ 793, 13, 19, 16, 1, -19 }, // 0x4B 'K'
{ 824, 12, 19, 16, 1, -18 }, // 0x4C 'L'
{ 853, 14, 19, 16, 1, -19 }, // 0x4D 'M'
{ 887, 13, 10, 16, 1, -10 }, // 0x4E 'N'
{ 904, 13, 11, 16, 1, -10 }, // 0x4F 'O'
{ 922, 14, 19, 16, 1, -19 }, // 0x50 'P'
{ 956, 13, 19, 16, 2, -19 }, // 0x51 'Q'
{ 987, 12, 10, 16, 1, -10 }, // 0x52 'R'
{ 1002, 12, 19, 16, 2, -18 }, // 0x53 'S'
{ 1031, 12, 19, 16, 1, -18 }, // 0x54 'T'
{ 1060, 13, 10, 16, 1, -9 }, // 0x55 'U'
{ 1077, 14, 19, 16, 1, -18 }, // 0x56 'V'
{ 1111, 14, 19, 16, 1, -18 }, // 0x57 'W'
{ 1145, 14, 18, 16, 1, -18 }, // 0x58 'X'
{ 1177, 13, 19, 16, 2, -18 }, // 0x59 'Y'
{ 1208, 14, 20, 16, 1, -19 }, // 0x5A 'Z'
{ 1243, 6, 14, 7, 1, -13 }, // 0x5B '['
{ 1254, 6, 14, 7, 1, -13 }, // 0x5C '\'
{ 1265, 6, 14, 7, 1, -13 }, // 0x5D ']'
{ 1276, 6, 14, 7, 1, -13 }, // 0x5E '^'
{ 1287, 11, 2, 16, 2, -1 }, // 0x5F '_'
{ 1290, 6, 14, 7, 1, -13 }, // 0x60 '`'
{ 1301, 14, 19, 16, 1, -19 }, // 0x61 'a'
{ 1335, 13, 19, 16, 1, -18 }, // 0x62 'b'
{ 1366, 12, 11, 16, 1, -10 }, // 0x63 'c'
{ 1383, 14, 19, 16, 1, -18 }, // 0x64 'd'
{ 1417, 13, 20, 16, 1, -19 }, // 0x65 'e'
{ 1450, 13, 19, 16, 1, -19 }, // 0x66 'f'
{ 1481, 13, 20, 16, 1, -19 }, // 0x67 'g'
{ 1514, 13, 18, 16, 1, -18 }, // 0x68 'h'
{ 1544, 3, 9, 16, 11, -9 }, // 0x69 'i'
{ 1548, 14, 19, 16, 1, -18 }, // 0x6A 'j'
{ 1582, 13, 19, 16, 1, -19 }, // 0x6B 'k'
{ 1613, 12, 19, 16, 1, -18 }, // 0x6C 'l'
{ 1642, 14, 19, 16, 1, -19 }, // 0x6D 'm'
{ 1676, 13, 10, 16, 1, -10 }, // 0x6E 'n'
{ 1693, 13, 11, 16, 1, -10 }, // 0x6F 'o'
{ 1711, 14, 19, 16, 1, -19 }, // 0x70 'p'
{ 1745, 13, 19, 16, 2, -19 }, // 0x71 'q'
{ 1776, 12, 10, 16, 1, -10 }, // 0x72 'r'
{ 1791, 12, 19, 16, 2, -18 }, // 0x73 's'
{ 1820, 12, 19, 16, 1, -18 }, // 0x74 't'
{ 1849, 13, 10, 16, 1, -9 }, // 0x75 'u'
{ 1866, 14, 19, 16, 1, -18 }, // 0x76 'v'
{ 1900, 14, 19, 16, 1, -18 }, // 0x77 'w'
{ 1934, 14, 18, 16, 1, -18 }, // 0x78 'x'
{ 1966, 13, 19, 16, 2, -18 }, // 0x79 'y'
{ 1997, 14, 20, 16, 1, -19 }, // 0x7A 'z'
{ 2032, 6, 14, 7, 1, -13 }, // 0x7B '{'
{ 2043, 6, 14, 7, 1, -13 }, // 0x7C '|'
{ 2054, 6, 14, 7, 1, -13 }, // 0x7D '}'
{ 2065, 6, 14, 7, 1, -13 } }; // 0x7E '~'
const GFXfont DSEG7Classic_BoldItalic10pt7b PROGMEM = {
(uint8_t *)DSEG7Classic_BoldItalic10pt7bBitmaps,
(GFXglyph *)DSEG7Classic_BoldItalic10pt7bGlyphs,
0x20, 0x7E, 22 };
// Approx. 2748 bytes
@@ -0,0 +1,352 @@
const uint8_t DSEG7Classic_BoldItalic12pt7bBitmaps[] PROGMEM = {
0xFF, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0x83, 0x06, 0x0C, 0x18, 0x30,
0x60, 0xFF, 0xFF, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0x83, 0x06, 0x0C,
0x18, 0x30, 0x60, 0xFF, 0xFF, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0x83,
0x06, 0x0C, 0x18, 0x30, 0x60, 0xFF, 0xFF, 0x06, 0x0C, 0x18, 0x30, 0x60,
0xC1, 0x83, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xFF, 0xFF, 0x06, 0x0C, 0x18,
0x30, 0x60, 0xC1, 0x83, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xFF, 0xFF, 0x06,
0x0C, 0x18, 0x30, 0x60, 0xC1, 0x83, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xFF,
0xFF, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0x83, 0x06, 0x0C, 0x18, 0x30,
0x60, 0xFF, 0xFF, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0x83, 0x06, 0x0C,
0x18, 0x30, 0x60, 0xFF, 0xFF, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0x83,
0x06, 0x0C, 0x18, 0x30, 0x60, 0xFF, 0xFF, 0x06, 0x0C, 0x18, 0x30, 0x60,
0xC1, 0x83, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xFF, 0xFF, 0x06, 0x0C, 0x18,
0x30, 0x60, 0xC1, 0x83, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xFF, 0x7F, 0xEF,
0xFC, 0x7D, 0x80, 0xFF, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0x83, 0x06,
0x0C, 0x18, 0x30, 0x60, 0xFF, 0x0F, 0xFF, 0x07, 0xFF, 0x4D, 0xFF, 0x67,
0x00, 0x73, 0x80, 0x39, 0xC0, 0x1C, 0xE0, 0x0E, 0x70, 0x07, 0x38, 0x03,
0x9C, 0x03, 0x9C, 0x00, 0xCC, 0x00, 0x26, 0x00, 0x13, 0x80, 0x39, 0xC0,
0x1C, 0xE0, 0x0E, 0x70, 0x07, 0x38, 0x03, 0x9C, 0x01, 0xCE, 0x00, 0xE7,
0x00, 0xE3, 0xFF, 0xF1, 0xFF, 0xE8, 0x7F, 0xF8, 0x08, 0xCE, 0x73, 0x9C,
0xE7, 0x71, 0x84, 0x27, 0x39, 0xCE, 0x73, 0x9D, 0xC6, 0x10, 0x0F, 0xFF,
0x07, 0xFF, 0x41, 0xFF, 0x60, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1C, 0x00,
0x0E, 0x00, 0x07, 0x00, 0x03, 0x80, 0x03, 0x80, 0x00, 0xC1, 0xFF, 0xA6,
0xFF, 0xC3, 0x80, 0x01, 0xC0, 0x00, 0xE0, 0x00, 0x70, 0x00, 0x38, 0x00,
0x1C, 0x00, 0x0E, 0x00, 0x07, 0x00, 0x03, 0xFF, 0xC1, 0xFF, 0xE0, 0x7F,
0xF8, 0x1F, 0xFE, 0x1F, 0xFD, 0x0F, 0xFB, 0x00, 0x07, 0x00, 0x07, 0x00,
0x07, 0x00, 0x07, 0x00, 0x07, 0x00, 0x07, 0x00, 0x0E, 0x00, 0x06, 0x1F,
0xFA, 0x1F, 0xFA, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x0E, 0x00,
0x0E, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x1C, 0x3F, 0xFC, 0x7F, 0xF4, 0x7F,
0xF8, 0x00, 0x01, 0x60, 0x03, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70,
0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x0E, 0xE0, 0x06, 0xDF, 0xFA, 0x1F,
0xFA, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x0E, 0x00,
0x0E, 0x00, 0x0E, 0x00, 0x1C, 0x00, 0x0C, 0x00, 0x04, 0x1F, 0xFE, 0x3F,
0xF9, 0xBF, 0xE3, 0x80, 0x07, 0x00, 0x0E, 0x00, 0x1C, 0x00, 0x38, 0x00,
0x70, 0x00, 0xE0, 0x03, 0x80, 0x06, 0xFF, 0xC1, 0xFF, 0xA0, 0x01, 0xC0,
0x03, 0x80, 0x07, 0x00, 0x0E, 0x00, 0x1C, 0x00, 0x38, 0x00, 0x70, 0x01,
0xC7, 0xFF, 0x9F, 0xFD, 0x3F, 0xFC, 0x0F, 0xFF, 0x0F, 0xFE, 0x37, 0xFC,
0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00,
0x38, 0x00, 0x70, 0x00, 0x6F, 0xFC, 0x6F, 0xFD, 0x70, 0x07, 0x70, 0x07,
0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x0E,
0x7F, 0xFE, 0x7F, 0xFA, 0x3F, 0xFC, 0x1F, 0xFE, 0x1F, 0xFD, 0x6F, 0xFB,
0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07,
0x70, 0x0E, 0xE0, 0x06, 0xC0, 0x02, 0x00, 0x02, 0x00, 0x0E, 0x00, 0x0E,
0x00, 0x0E, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x1C,
0x00, 0x0C, 0x00, 0x04, 0x0F, 0xFF, 0x07, 0xFF, 0x4D, 0xFF, 0x67, 0x00,
0x73, 0x80, 0x39, 0xC0, 0x1C, 0xE0, 0x0E, 0x70, 0x07, 0x38, 0x03, 0x9C,
0x03, 0x9C, 0x00, 0xCD, 0xFF, 0xA6, 0xFF, 0xD3, 0x80, 0x39, 0xC0, 0x1C,
0xE0, 0x0E, 0x70, 0x07, 0x38, 0x03, 0x9C, 0x01, 0xCE, 0x00, 0xE7, 0x00,
0xE3, 0xFF, 0xF1, 0xFF, 0xE8, 0x7F, 0xF8, 0x1F, 0xFE, 0x1F, 0xFD, 0x6F,
0xFB, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70,
0x07, 0x70, 0x0E, 0xE0, 0x06, 0xDF, 0xFA, 0x1F, 0xFA, 0x00, 0x0E, 0x00,
0x0E, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x0E, 0x00,
0x1C, 0x3F, 0xFC, 0x7F, 0xF4, 0x7F, 0xF8, 0x37, 0x30, 0x00, 0x00, 0x00,
0x6E, 0x60, 0xFF, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0x83, 0x06, 0x0C,
0x18, 0x30, 0x60, 0xFF, 0xFF, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0x83,
0x06, 0x0C, 0x18, 0x30, 0x60, 0xFF, 0xFF, 0x06, 0x0C, 0x18, 0x30, 0x60,
0xC1, 0x83, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xFF, 0xFF, 0x06, 0x0C, 0x18,
0x30, 0x60, 0xC1, 0x83, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xFF, 0xFF, 0x06,
0x0C, 0x18, 0x30, 0x60, 0xC1, 0x83, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xFF,
0xFF, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0x83, 0x06, 0x0C, 0x18, 0x30,
0x60, 0xFF, 0x0F, 0xFF, 0x07, 0xFF, 0x4D, 0xFF, 0x67, 0x00, 0x73, 0x80,
0x39, 0xC0, 0x1C, 0xE0, 0x0E, 0x70, 0x07, 0x38, 0x03, 0x9C, 0x03, 0x9C,
0x00, 0xCD, 0xFF, 0xA6, 0xFF, 0xD3, 0x80, 0x39, 0xC0, 0x1C, 0xE0, 0x0E,
0x70, 0x07, 0x38, 0x03, 0x9C, 0x01, 0xCE, 0x00, 0xE7, 0x00, 0xE3, 0x00,
0x31, 0x00, 0x08, 0x00, 0x00, 0x30, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38,
0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x70, 0x00, 0x6F,
0xFC, 0x6F, 0xFD, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70,
0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x0E, 0x7F, 0xFE, 0x7F, 0xFA, 0x3F,
0xFC, 0x0F, 0xFD, 0xBF, 0xF7, 0x00, 0x1C, 0x00, 0x70, 0x01, 0xC0, 0x07,
0x00, 0x1C, 0x00, 0x70, 0x01, 0xC0, 0x07, 0xFF, 0x9F, 0xFE, 0x3F, 0xFC,
0x00, 0x00, 0x80, 0x00, 0xC0, 0x00, 0xE0, 0x00, 0x70, 0x00, 0x38, 0x00,
0x1C, 0x00, 0x0E, 0x00, 0x07, 0x00, 0x07, 0x00, 0x01, 0x83, 0xFF, 0x4D,
0xFF, 0xA7, 0x00, 0x73, 0x80, 0x39, 0xC0, 0x1C, 0xE0, 0x0E, 0x70, 0x07,
0x38, 0x03, 0x9C, 0x01, 0xCE, 0x01, 0xC7, 0xFF, 0xE3, 0xFF, 0xD0, 0xFF,
0xF0, 0x0F, 0xFF, 0x0F, 0xFE, 0x37, 0xFC, 0x38, 0x00, 0x38, 0x00, 0x38,
0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x70, 0x00, 0x6F,
0xFC, 0x6F, 0xFC, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70,
0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x7F, 0xF8, 0x7F, 0xF8, 0x3F,
0xFC, 0x0F, 0xFF, 0x0F, 0xFE, 0x37, 0xFC, 0x38, 0x00, 0x38, 0x00, 0x38,
0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x70, 0x00, 0x6F,
0xFC, 0x6F, 0xFC, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70,
0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x60, 0x00, 0x40, 0x00, 0x0F,
0xFF, 0x0F, 0xFE, 0x37, 0xFC, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38,
0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x70, 0x00, 0x60, 0x00, 0x60,
0x01, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70,
0x07, 0x70, 0x07, 0x70, 0x0E, 0x7F, 0xFE, 0x7F, 0xFA, 0x3F, 0xFC, 0x00,
0x00, 0x30, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38,
0x00, 0x38, 0x00, 0x38, 0x00, 0x70, 0x00, 0x6F, 0xFC, 0x6F, 0xFD, 0x70,
0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70,
0x07, 0x70, 0x0E, 0x60, 0x06, 0x40, 0x02, 0x17, 0x77, 0x77, 0x77, 0xE6,
0x20, 0x00, 0x00, 0x80, 0x00, 0xC0, 0x00, 0xE0, 0x00, 0x70, 0x00, 0x38,
0x00, 0x1C, 0x00, 0x0E, 0x00, 0x07, 0x00, 0x07, 0x00, 0x01, 0x80, 0x00,
0x4C, 0x00, 0x27, 0x00, 0x73, 0x80, 0x39, 0xC0, 0x1C, 0xE0, 0x0E, 0x70,
0x07, 0x38, 0x03, 0x9C, 0x01, 0xCE, 0x01, 0xC7, 0xFF, 0xE3, 0xFF, 0xD0,
0xFF, 0xF0, 0x0F, 0xFF, 0x0F, 0xFE, 0x37, 0xFC, 0x38, 0x00, 0x38, 0x00,
0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x70, 0x00,
0x6F, 0xFC, 0x6F, 0xFD, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07,
0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x0E, 0x60, 0x06, 0x40, 0x02,
0x00, 0x00, 0xC0, 0x03, 0x80, 0x0E, 0x00, 0x38, 0x00, 0xE0, 0x03, 0x80,
0x0E, 0x00, 0x38, 0x01, 0xC0, 0x06, 0x00, 0x18, 0x00, 0x70, 0x01, 0xC0,
0x07, 0x00, 0x1C, 0x00, 0x70, 0x01, 0xC0, 0x07, 0x00, 0x1C, 0x00, 0x7F,
0xF9, 0xFF, 0xE3, 0xFF, 0xC0, 0x0F, 0xFF, 0x07, 0xFF, 0x4D, 0xFF, 0x67,
0x00, 0x73, 0x80, 0x39, 0xC0, 0x1C, 0xE0, 0x0E, 0x70, 0x07, 0x38, 0x03,
0x9C, 0x03, 0x9C, 0x00, 0xCC, 0x00, 0x26, 0x00, 0x13, 0x80, 0x39, 0xC0,
0x1C, 0xE0, 0x0E, 0x70, 0x07, 0x38, 0x03, 0x9C, 0x01, 0xCE, 0x00, 0xE7,
0x00, 0xE3, 0x00, 0x31, 0x00, 0x08, 0x0F, 0xFC, 0x6F, 0xFD, 0x70, 0x07,
0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07,
0x70, 0x0E, 0x60, 0x06, 0x40, 0x02, 0x0F, 0xFC, 0x6F, 0xFD, 0x70, 0x07,
0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07,
0x70, 0x0E, 0x7F, 0xFE, 0x7F, 0xFA, 0x3F, 0xFC, 0x0F, 0xFF, 0x07, 0xFF,
0x4D, 0xFF, 0x67, 0x00, 0x73, 0x80, 0x39, 0xC0, 0x1C, 0xE0, 0x0E, 0x70,
0x07, 0x38, 0x03, 0x9C, 0x03, 0x9C, 0x00, 0xCD, 0xFF, 0xA6, 0xFF, 0xC3,
0x80, 0x01, 0xC0, 0x00, 0xE0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1C, 0x00,
0x0E, 0x00, 0x07, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x1F, 0xFE, 0x1F,
0xFD, 0x6F, 0xFB, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70,
0x07, 0x70, 0x07, 0x70, 0x0E, 0xE0, 0x06, 0xDF, 0xFA, 0x1F, 0xFA, 0x00,
0x0E, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x0E, 0x00,
0x0E, 0x00, 0x1C, 0x00, 0x0C, 0x00, 0x04, 0x0F, 0xFD, 0xBF, 0xF7, 0x00,
0x1C, 0x00, 0x70, 0x01, 0xC0, 0x07, 0x00, 0x1C, 0x00, 0x70, 0x01, 0xC0,
0x06, 0x00, 0x10, 0x00, 0x00, 0x00, 0xC0, 0x01, 0xC0, 0x03, 0x80, 0x07,
0x00, 0x0E, 0x00, 0x1C, 0x00, 0x38, 0x00, 0x70, 0x01, 0xC0, 0x03, 0x7F,
0xE0, 0xFF, 0xD0, 0x00, 0xE0, 0x01, 0xC0, 0x03, 0x80, 0x07, 0x00, 0x0E,
0x00, 0x1C, 0x00, 0x38, 0x00, 0xE3, 0xFF, 0xCF, 0xFE, 0x9F, 0xFE, 0x00,
0x00, 0x00, 0xC0, 0x03, 0x80, 0x0E, 0x00, 0x38, 0x00, 0xE0, 0x03, 0x80,
0x0E, 0x00, 0x38, 0x01, 0xC0, 0x06, 0xFF, 0xDB, 0xFF, 0x70, 0x01, 0xC0,
0x07, 0x00, 0x1C, 0x00, 0x70, 0x01, 0xC0, 0x07, 0x00, 0x1C, 0x00, 0x7F,
0xF9, 0xFF, 0xE3, 0xFF, 0xC0, 0x60, 0x01, 0x70, 0x07, 0x70, 0x07, 0x70,
0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x0E, 0x7F,
0xFE, 0x7F, 0xFA, 0x3F, 0xFC, 0x00, 0x00, 0x98, 0x00, 0xCE, 0x00, 0xE7,
0x00, 0x73, 0x80, 0x39, 0xC0, 0x1C, 0xE0, 0x0E, 0x70, 0x07, 0x38, 0x07,
0x38, 0x01, 0x98, 0x00, 0x4C, 0x00, 0x27, 0x00, 0x73, 0x80, 0x39, 0xC0,
0x1C, 0xE0, 0x0E, 0x70, 0x07, 0x38, 0x03, 0x9C, 0x01, 0xCE, 0x01, 0xC7,
0xFF, 0xE3, 0xFF, 0xD0, 0xFF, 0xF0, 0x00, 0x00, 0x98, 0x00, 0xCE, 0x00,
0xE7, 0x00, 0x73, 0x80, 0x39, 0xC0, 0x1C, 0xE0, 0x0E, 0x70, 0x07, 0x38,
0x07, 0x38, 0x01, 0x9B, 0xFF, 0x4D, 0xFF, 0xA7, 0x00, 0x73, 0x80, 0x39,
0xC0, 0x1C, 0xE0, 0x0E, 0x70, 0x07, 0x38, 0x03, 0x9C, 0x01, 0xCE, 0x01,
0xC7, 0xFF, 0xE3, 0xFF, 0xD0, 0xFF, 0xF0, 0x00, 0x00, 0x98, 0x00, 0xCE,
0x00, 0xE7, 0x00, 0x73, 0x80, 0x39, 0xC0, 0x1C, 0xE0, 0x0E, 0x70, 0x07,
0x38, 0x07, 0x38, 0x01, 0x9B, 0xFF, 0x4D, 0xFF, 0xA7, 0x00, 0x73, 0x80,
0x39, 0xC0, 0x1C, 0xE0, 0x0E, 0x70, 0x07, 0x38, 0x03, 0x9C, 0x01, 0xCE,
0x01, 0xC6, 0x00, 0x62, 0x00, 0x10, 0x00, 0x01, 0x60, 0x03, 0x70, 0x07,
0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x0E,
0xE0, 0x06, 0xDF, 0xFA, 0x1F, 0xFA, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x0E,
0x00, 0x0E, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x1C, 0x3F, 0xFC,
0x7F, 0xF4, 0x7F, 0xF8, 0x0F, 0xFF, 0x07, 0xFF, 0x41, 0xFF, 0x60, 0x00,
0x70, 0x00, 0x38, 0x00, 0x1C, 0x00, 0x0E, 0x00, 0x07, 0x00, 0x03, 0x80,
0x03, 0x80, 0x00, 0xC0, 0x00, 0x26, 0x00, 0x03, 0x80, 0x01, 0xC0, 0x00,
0xE0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1C, 0x00, 0x0E, 0x00, 0x07, 0x00,
0x03, 0xFF, 0xC1, 0xFF, 0xE0, 0x7F, 0xF8, 0xFF, 0x06, 0x0C, 0x18, 0x30,
0x60, 0xC1, 0x83, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xFF, 0xFF, 0x06, 0x0C,
0x18, 0x30, 0x60, 0xC1, 0x83, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xFF, 0xFF,
0x06, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0x83, 0x06, 0x0C, 0x18, 0x30, 0x60,
0xFF, 0xFF, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0x83, 0x06, 0x0C, 0x18,
0x30, 0x60, 0xFF, 0x3F, 0xF3, 0xFF, 0x9F, 0xFE, 0xFF, 0x06, 0x0C, 0x18,
0x30, 0x60, 0xC1, 0x83, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xFF, 0x0F, 0xFF,
0x07, 0xFF, 0x4D, 0xFF, 0x67, 0x00, 0x73, 0x80, 0x39, 0xC0, 0x1C, 0xE0,
0x0E, 0x70, 0x07, 0x38, 0x03, 0x9C, 0x03, 0x9C, 0x00, 0xCD, 0xFF, 0xA6,
0xFF, 0xD3, 0x80, 0x39, 0xC0, 0x1C, 0xE0, 0x0E, 0x70, 0x07, 0x38, 0x03,
0x9C, 0x01, 0xCE, 0x00, 0xE7, 0x00, 0xE3, 0x00, 0x31, 0x00, 0x08, 0x00,
0x00, 0x30, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38,
0x00, 0x38, 0x00, 0x38, 0x00, 0x70, 0x00, 0x6F, 0xFC, 0x6F, 0xFD, 0x70,
0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70,
0x07, 0x70, 0x0E, 0x7F, 0xFE, 0x7F, 0xFA, 0x3F, 0xFC, 0x0F, 0xFD, 0xBF,
0xF7, 0x00, 0x1C, 0x00, 0x70, 0x01, 0xC0, 0x07, 0x00, 0x1C, 0x00, 0x70,
0x01, 0xC0, 0x07, 0xFF, 0x9F, 0xFE, 0x3F, 0xFC, 0x00, 0x00, 0x80, 0x00,
0xC0, 0x00, 0xE0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1C, 0x00, 0x0E, 0x00,
0x07, 0x00, 0x07, 0x00, 0x01, 0x83, 0xFF, 0x4D, 0xFF, 0xA7, 0x00, 0x73,
0x80, 0x39, 0xC0, 0x1C, 0xE0, 0x0E, 0x70, 0x07, 0x38, 0x03, 0x9C, 0x01,
0xCE, 0x01, 0xC7, 0xFF, 0xE3, 0xFF, 0xD0, 0xFF, 0xF0, 0x0F, 0xFF, 0x0F,
0xFE, 0x37, 0xFC, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38,
0x00, 0x38, 0x00, 0x38, 0x00, 0x70, 0x00, 0x6F, 0xFC, 0x6F, 0xFC, 0x70,
0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70,
0x00, 0x70, 0x00, 0x7F, 0xF8, 0x7F, 0xF8, 0x3F, 0xFC, 0x0F, 0xFF, 0x0F,
0xFE, 0x37, 0xFC, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38,
0x00, 0x38, 0x00, 0x38, 0x00, 0x70, 0x00, 0x6F, 0xFC, 0x6F, 0xFC, 0x70,
0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70,
0x00, 0x70, 0x00, 0x60, 0x00, 0x40, 0x00, 0x0F, 0xFF, 0x0F, 0xFE, 0x37,
0xFC, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38,
0x00, 0x38, 0x00, 0x70, 0x00, 0x60, 0x00, 0x60, 0x01, 0x70, 0x07, 0x70,
0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70,
0x0E, 0x7F, 0xFE, 0x7F, 0xFA, 0x3F, 0xFC, 0x00, 0x00, 0x30, 0x00, 0x38,
0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38,
0x00, 0x70, 0x00, 0x6F, 0xFC, 0x6F, 0xFD, 0x70, 0x07, 0x70, 0x07, 0x70,
0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x0E, 0x60,
0x06, 0x40, 0x02, 0x17, 0x77, 0x77, 0x77, 0xE6, 0x20, 0x00, 0x00, 0x80,
0x00, 0xC0, 0x00, 0xE0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1C, 0x00, 0x0E,
0x00, 0x07, 0x00, 0x07, 0x00, 0x01, 0x80, 0x00, 0x4C, 0x00, 0x27, 0x00,
0x73, 0x80, 0x39, 0xC0, 0x1C, 0xE0, 0x0E, 0x70, 0x07, 0x38, 0x03, 0x9C,
0x01, 0xCE, 0x01, 0xC7, 0xFF, 0xE3, 0xFF, 0xD0, 0xFF, 0xF0, 0x0F, 0xFF,
0x0F, 0xFE, 0x37, 0xFC, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00,
0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x70, 0x00, 0x6F, 0xFC, 0x6F, 0xFD,
0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07,
0x70, 0x07, 0x70, 0x0E, 0x60, 0x06, 0x40, 0x02, 0x00, 0x00, 0xC0, 0x03,
0x80, 0x0E, 0x00, 0x38, 0x00, 0xE0, 0x03, 0x80, 0x0E, 0x00, 0x38, 0x01,
0xC0, 0x06, 0x00, 0x18, 0x00, 0x70, 0x01, 0xC0, 0x07, 0x00, 0x1C, 0x00,
0x70, 0x01, 0xC0, 0x07, 0x00, 0x1C, 0x00, 0x7F, 0xF9, 0xFF, 0xE3, 0xFF,
0xC0, 0x0F, 0xFF, 0x07, 0xFF, 0x4D, 0xFF, 0x67, 0x00, 0x73, 0x80, 0x39,
0xC0, 0x1C, 0xE0, 0x0E, 0x70, 0x07, 0x38, 0x03, 0x9C, 0x03, 0x9C, 0x00,
0xCC, 0x00, 0x26, 0x00, 0x13, 0x80, 0x39, 0xC0, 0x1C, 0xE0, 0x0E, 0x70,
0x07, 0x38, 0x03, 0x9C, 0x01, 0xCE, 0x00, 0xE7, 0x00, 0xE3, 0x00, 0x31,
0x00, 0x08, 0x0F, 0xFC, 0x6F, 0xFD, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07,
0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x0E, 0x60, 0x06,
0x40, 0x02, 0x0F, 0xFC, 0x6F, 0xFD, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07,
0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x0E, 0x7F, 0xFE,
0x7F, 0xFA, 0x3F, 0xFC, 0x0F, 0xFF, 0x07, 0xFF, 0x4D, 0xFF, 0x67, 0x00,
0x73, 0x80, 0x39, 0xC0, 0x1C, 0xE0, 0x0E, 0x70, 0x07, 0x38, 0x03, 0x9C,
0x03, 0x9C, 0x00, 0xCD, 0xFF, 0xA6, 0xFF, 0xC3, 0x80, 0x01, 0xC0, 0x00,
0xE0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1C, 0x00, 0x0E, 0x00, 0x07, 0x00,
0x03, 0x00, 0x01, 0x00, 0x00, 0x1F, 0xFE, 0x1F, 0xFD, 0x6F, 0xFB, 0x70,
0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70,
0x0E, 0xE0, 0x06, 0xDF, 0xFA, 0x1F, 0xFA, 0x00, 0x0E, 0x00, 0x0E, 0x00,
0x0E, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x1C, 0x00,
0x0C, 0x00, 0x04, 0x0F, 0xFD, 0xBF, 0xF7, 0x00, 0x1C, 0x00, 0x70, 0x01,
0xC0, 0x07, 0x00, 0x1C, 0x00, 0x70, 0x01, 0xC0, 0x06, 0x00, 0x10, 0x00,
0x00, 0x00, 0xC0, 0x01, 0xC0, 0x03, 0x80, 0x07, 0x00, 0x0E, 0x00, 0x1C,
0x00, 0x38, 0x00, 0x70, 0x01, 0xC0, 0x03, 0x7F, 0xE0, 0xFF, 0xD0, 0x00,
0xE0, 0x01, 0xC0, 0x03, 0x80, 0x07, 0x00, 0x0E, 0x00, 0x1C, 0x00, 0x38,
0x00, 0xE3, 0xFF, 0xCF, 0xFE, 0x9F, 0xFE, 0x00, 0x00, 0x00, 0xC0, 0x03,
0x80, 0x0E, 0x00, 0x38, 0x00, 0xE0, 0x03, 0x80, 0x0E, 0x00, 0x38, 0x01,
0xC0, 0x06, 0xFF, 0xDB, 0xFF, 0x70, 0x01, 0xC0, 0x07, 0x00, 0x1C, 0x00,
0x70, 0x01, 0xC0, 0x07, 0x00, 0x1C, 0x00, 0x7F, 0xF9, 0xFF, 0xE3, 0xFF,
0xC0, 0x60, 0x01, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70,
0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x0E, 0x7F, 0xFE, 0x7F, 0xFA, 0x3F,
0xFC, 0x00, 0x00, 0x98, 0x00, 0xCE, 0x00, 0xE7, 0x00, 0x73, 0x80, 0x39,
0xC0, 0x1C, 0xE0, 0x0E, 0x70, 0x07, 0x38, 0x07, 0x38, 0x01, 0x98, 0x00,
0x4C, 0x00, 0x27, 0x00, 0x73, 0x80, 0x39, 0xC0, 0x1C, 0xE0, 0x0E, 0x70,
0x07, 0x38, 0x03, 0x9C, 0x01, 0xCE, 0x01, 0xC7, 0xFF, 0xE3, 0xFF, 0xD0,
0xFF, 0xF0, 0x00, 0x00, 0x98, 0x00, 0xCE, 0x00, 0xE7, 0x00, 0x73, 0x80,
0x39, 0xC0, 0x1C, 0xE0, 0x0E, 0x70, 0x07, 0x38, 0x07, 0x38, 0x01, 0x9B,
0xFF, 0x4D, 0xFF, 0xA7, 0x00, 0x73, 0x80, 0x39, 0xC0, 0x1C, 0xE0, 0x0E,
0x70, 0x07, 0x38, 0x03, 0x9C, 0x01, 0xCE, 0x01, 0xC7, 0xFF, 0xE3, 0xFF,
0xD0, 0xFF, 0xF0, 0x00, 0x00, 0x98, 0x00, 0xCE, 0x00, 0xE7, 0x00, 0x73,
0x80, 0x39, 0xC0, 0x1C, 0xE0, 0x0E, 0x70, 0x07, 0x38, 0x07, 0x38, 0x01,
0x9B, 0xFF, 0x4D, 0xFF, 0xA7, 0x00, 0x73, 0x80, 0x39, 0xC0, 0x1C, 0xE0,
0x0E, 0x70, 0x07, 0x38, 0x03, 0x9C, 0x01, 0xCE, 0x01, 0xC6, 0x00, 0x62,
0x00, 0x10, 0x00, 0x01, 0x60, 0x03, 0x70, 0x07, 0x70, 0x07, 0x70, 0x07,
0x70, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70, 0x0E, 0xE0, 0x06, 0xDF, 0xFA,
0x1F, 0xFA, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x0E,
0x00, 0x0E, 0x00, 0x0E, 0x00, 0x1C, 0x3F, 0xFC, 0x7F, 0xF4, 0x7F, 0xF8,
0x0F, 0xFF, 0x07, 0xFF, 0x41, 0xFF, 0x60, 0x00, 0x70, 0x00, 0x38, 0x00,
0x1C, 0x00, 0x0E, 0x00, 0x07, 0x00, 0x03, 0x80, 0x03, 0x80, 0x00, 0xC0,
0x00, 0x26, 0x00, 0x03, 0x80, 0x01, 0xC0, 0x00, 0xE0, 0x00, 0x70, 0x00,
0x38, 0x00, 0x1C, 0x00, 0x0E, 0x00, 0x07, 0x00, 0x03, 0xFF, 0xC1, 0xFF,
0xE0, 0x7F, 0xF8, 0xFF, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0x83, 0x06,
0x0C, 0x18, 0x30, 0x60, 0xFF, 0xFF, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC1,
0x83, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xFF, 0xFF, 0x06, 0x0C, 0x18, 0x30,
0x60, 0xC1, 0x83, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xFF, 0xFF, 0x06, 0x0C,
0x18, 0x30, 0x60, 0xC1, 0x83, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xFF };
const GFXglyph DSEG7Classic_BoldItalic12pt7bGlyphs[] PROGMEM = {
{ 0, 0, 0, 5, 0, 1 }, // 0x20 ' '
{ 0, 0, 0, 20, 0, 1 }, // 0x21 '!'
{ 0, 7, 16, 9, 1, -15 }, // 0x22 '"'
{ 14, 7, 16, 9, 1, -15 }, // 0x23 '#'
{ 28, 7, 16, 9, 1, -15 }, // 0x24 '$'
{ 42, 7, 16, 9, 1, -15 }, // 0x25 '%'
{ 56, 7, 16, 9, 1, -15 }, // 0x26 '&'
{ 70, 7, 16, 9, 1, -15 }, // 0x27 '''
{ 84, 7, 16, 9, 1, -15 }, // 0x28 '('
{ 98, 7, 16, 9, 1, -15 }, // 0x29 ')'
{ 112, 7, 16, 9, 1, -15 }, // 0x2A '*'
{ 126, 7, 16, 9, 1, -15 }, // 0x2B '+'
{ 140, 7, 16, 9, 1, -15 }, // 0x2C ','
{ 154, 11, 2, 20, 4, -12 }, // 0x2D '-'
{ 157, 3, 3, 0, -3, -2 }, // 0x2E '.'
{ 159, 7, 16, 9, 1, -15 }, // 0x2F '/'
{ 173, 17, 24, 20, 1, -23 }, // 0x30 '0'
{ 224, 5, 22, 20, 13, -22 }, // 0x31 '1'
{ 238, 17, 24, 20, 1, -23 }, // 0x32 '2'
{ 289, 16, 24, 20, 2, -23 }, // 0x33 '3'
{ 337, 16, 22, 20, 2, -22 }, // 0x34 '4'
{ 381, 15, 24, 20, 2, -23 }, // 0x35 '5'
{ 426, 16, 24, 20, 1, -23 }, // 0x36 '6'
{ 474, 16, 23, 20, 2, -23 }, // 0x37 '7'
{ 520, 17, 24, 20, 1, -23 }, // 0x38 '8'
{ 571, 16, 24, 20, 2, -23 }, // 0x39 '9'
{ 619, 4, 13, 5, 0, -17 }, // 0x3A ':'
{ 626, 7, 16, 9, 1, -15 }, // 0x3B ';'
{ 640, 7, 16, 9, 1, -15 }, // 0x3C '<'
{ 654, 7, 16, 9, 1, -15 }, // 0x3D '='
{ 668, 7, 16, 9, 1, -15 }, // 0x3E '>'
{ 682, 7, 16, 9, 1, -15 }, // 0x3F '?'
{ 696, 7, 16, 9, 1, -15 }, // 0x40 '@'
{ 710, 17, 23, 20, 1, -23 }, // 0x41 'A'
{ 759, 16, 23, 20, 1, -22 }, // 0x42 'B'
{ 805, 14, 13, 20, 1, -12 }, // 0x43 'C'
{ 828, 17, 23, 20, 1, -22 }, // 0x44 'D'
{ 877, 16, 24, 20, 1, -23 }, // 0x45 'E'
{ 925, 16, 23, 20, 1, -23 }, // 0x46 'F'
{ 971, 16, 24, 20, 1, -23 }, // 0x47 'G'
{ 1019, 16, 22, 20, 1, -22 }, // 0x48 'H'
{ 1063, 4, 11, 20, 13, -11 }, // 0x49 'I'
{ 1069, 17, 23, 20, 1, -22 }, // 0x4A 'J'
{ 1118, 16, 23, 20, 1, -23 }, // 0x4B 'K'
{ 1164, 14, 23, 20, 1, -22 }, // 0x4C 'L'
{ 1205, 17, 23, 20, 1, -23 }, // 0x4D 'M'
{ 1254, 16, 12, 20, 1, -12 }, // 0x4E 'N'
{ 1278, 16, 13, 20, 1, -12 }, // 0x4F 'O'
{ 1304, 17, 23, 20, 1, -23 }, // 0x50 'P'
{ 1353, 16, 23, 20, 2, -23 }, // 0x51 'Q'
{ 1399, 14, 12, 20, 1, -12 }, // 0x52 'R'
{ 1420, 15, 23, 20, 2, -22 }, // 0x53 'S'
{ 1464, 14, 23, 20, 1, -22 }, // 0x54 'T'
{ 1505, 16, 12, 20, 1, -11 }, // 0x55 'U'
{ 1529, 17, 23, 20, 1, -22 }, // 0x56 'V'
{ 1578, 17, 23, 20, 1, -22 }, // 0x57 'W'
{ 1627, 17, 22, 20, 1, -22 }, // 0x58 'X'
{ 1674, 16, 23, 20, 2, -22 }, // 0x59 'Y'
{ 1720, 17, 24, 20, 1, -23 }, // 0x5A 'Z'
{ 1771, 7, 16, 9, 1, -15 }, // 0x5B '['
{ 1785, 7, 16, 9, 1, -15 }, // 0x5C '\'
{ 1799, 7, 16, 9, 1, -15 }, // 0x5D ']'
{ 1813, 7, 16, 9, 1, -15 }, // 0x5E '^'
{ 1827, 13, 3, 20, 2, -2 }, // 0x5F '_'
{ 1832, 7, 16, 9, 1, -15 }, // 0x60 '`'
{ 1846, 17, 23, 20, 1, -23 }, // 0x61 'a'
{ 1895, 16, 23, 20, 1, -22 }, // 0x62 'b'
{ 1941, 14, 13, 20, 1, -12 }, // 0x63 'c'
{ 1964, 17, 23, 20, 1, -22 }, // 0x64 'd'
{ 2013, 16, 24, 20, 1, -23 }, // 0x65 'e'
{ 2061, 16, 23, 20, 1, -23 }, // 0x66 'f'
{ 2107, 16, 24, 20, 1, -23 }, // 0x67 'g'
{ 2155, 16, 22, 20, 1, -22 }, // 0x68 'h'
{ 2199, 4, 11, 20, 13, -11 }, // 0x69 'i'
{ 2205, 17, 23, 20, 1, -22 }, // 0x6A 'j'
{ 2254, 16, 23, 20, 1, -23 }, // 0x6B 'k'
{ 2300, 14, 23, 20, 1, -22 }, // 0x6C 'l'
{ 2341, 17, 23, 20, 1, -23 }, // 0x6D 'm'
{ 2390, 16, 12, 20, 1, -12 }, // 0x6E 'n'
{ 2414, 16, 13, 20, 1, -12 }, // 0x6F 'o'
{ 2440, 17, 23, 20, 1, -23 }, // 0x70 'p'
{ 2489, 16, 23, 20, 2, -23 }, // 0x71 'q'
{ 2535, 14, 12, 20, 1, -12 }, // 0x72 'r'
{ 2556, 15, 23, 20, 2, -22 }, // 0x73 's'
{ 2600, 14, 23, 20, 1, -22 }, // 0x74 't'
{ 2641, 16, 12, 20, 1, -11 }, // 0x75 'u'
{ 2665, 17, 23, 20, 1, -22 }, // 0x76 'v'
{ 2714, 17, 23, 20, 1, -22 }, // 0x77 'w'
{ 2763, 17, 22, 20, 1, -22 }, // 0x78 'x'
{ 2810, 16, 23, 20, 2, -22 }, // 0x79 'y'
{ 2856, 17, 24, 20, 1, -23 }, // 0x7A 'z'
{ 2907, 7, 16, 9, 1, -15 }, // 0x7B '{'
{ 2921, 7, 16, 9, 1, -15 }, // 0x7C '|'
{ 2935, 7, 16, 9, 1, -15 }, // 0x7D '}'
{ 2949, 7, 16, 9, 1, -15 } }; // 0x7E '~'
const GFXfont DSEG7Classic_BoldItalic12pt7b PROGMEM = {
(uint8_t *)DSEG7Classic_BoldItalic12pt7bBitmaps,
(GFXglyph *)DSEG7Classic_BoldItalic12pt7bGlyphs,
0x20, 0x7E, 26 };
// Approx. 3635 bytes
-67
View File
@@ -1,67 +0,0 @@
// Arduino Moving Average Library
// https://github.com/JChristensen/movingAvg
// Copyright (C) 2018 by Jack Christensen and licensed under
// GNU GPL v3.0, https://www.gnu.org/licenses/gpl.html
#include <movingAvg.h>
// initialize - allocate the interval array
void movingAvg::begin()
{
m_readings = new int[m_interval];
}
// add a new reading and return the new moving average
int movingAvg::reading(int newReading)
{
// add each new data point to the sum until the m_readings array is filled
if (m_nbrReadings < m_interval) {
++m_nbrReadings;
m_sum += newReading;
}
// once the array is filled, subtract the oldest data point and add the new one
else {
m_sum = m_sum - m_readings[m_next] + newReading;
}
m_readings[m_next] = newReading;
if (++m_next >= m_interval) m_next = 0;
return (m_sum + m_nbrReadings / 2) / m_nbrReadings;
}
// just return the current moving average
int movingAvg::getAvg()
{
return (m_sum + m_nbrReadings / 2) / m_nbrReadings;
}
// return the average for a subset of the data, the most recent nPoints readings.
// for invalid values of nPoints, return zero.
int movingAvg::getAvg(int nPoints)
{
if (nPoints < 1 || nPoints > m_interval || nPoints > m_nbrReadings) {
return 0;
}
else {
long sum{0};
int i = m_next;
for (int n=0; n<nPoints; ++n) {
if (i == 0) {
i = m_interval - 1;
}
else {
--i;
}
sum += m_readings[i];
}
return (sum + nPoints / 2) / nPoints;
}
}
// start the moving average over again
void movingAvg::reset()
{
m_nbrReadings = 0;
m_sum = 0;
m_next = 0;
}
+45 -8
View File
@@ -3,27 +3,64 @@
// Copyright (C) 2018 by Jack Christensen and licensed under // Copyright (C) 2018 by Jack Christensen and licensed under
// GNU GPL v3.0, https://www.gnu.org/licenses/gpl.html // GNU GPL v3.0, https://www.gnu.org/licenses/gpl.html
// Extended to template class for handling of multiple data types
#ifndef MOVINGAVG_H_INCLUDED #ifndef MOVINGAVG_H_INCLUDED
#define MOVINGAVG_H_INCLUDED #define MOVINGAVG_H_INCLUDED
template <typename T>
class movingAvg class movingAvg
{ {
public: public:
movingAvg(int interval) movingAvg(int interval)
: m_interval{interval}, m_nbrReadings{0}, m_sum{0}, m_next{0} {} : m_interval{interval}, m_nbrReadings{0}, m_sum{0}, m_next{0}, m_readings{nullptr} {}
~movingAvg() { delete[] m_readings; }
void begin(); void begin();
int reading(int newReading); T reading(T newReading);
int getAvg(); T getAvg();
int getAvg(int nPoints); T getAvg(int nPoints);
int getCount() {return m_nbrReadings;} int getCount() { return m_nbrReadings; }
void reset(); void reset();
int* getReadings() {return m_readings;} T* getReadings() { return m_readings; }
T to2PI(T a);
private: private:
int m_interval; // number of data points for the moving average int m_interval; // number of data points for the moving average
int m_nbrReadings; // number of readings int m_nbrReadings; // number of readings
long m_sum; // sum of the m_readings array // Sum type adapts to T: long for integers, T for floating point
using SumType = typename std::conditional<std::is_floating_point<T>::value, T, long>::type;
SumType m_sum;
int m_next; // index to the next reading int m_next; // index to the next reading
int* m_readings; // pointer to the dynamically allocated interval array T* m_readings; // pointer to the dynamically allocated interval array
}; };
// moving average for angle type of data (wind, course, rotation)
// makes sense for data types double and float; angle in radians [0..2pi]
template <typename T>
class movingAvgAngle
{
public:
movingAvgAngle(int interval)
: m_interval{interval}, m_nbrReadings{0}, m_sumSin{0}, m_sumCos{0}, m_next{0}, m_buffer{nullptr} {}
~movingAvgAngle() { delete[] m_buffer; }
void begin();
T reading(T newReading);
T getAvg();
T getAvg(int nPoints);
int getCount() { return m_nbrReadings; }
void reset();
T* getReadings() { return m_buffer; }
T to2PI(T a);
private:
int m_interval; // number of data points for the moving average
int m_nbrReadings; // number of readings
double m_sumSin, m_sumCos; // sum for angle values should always be double for precision reasons, regardless of class type
int m_next; // index to the next reading
T* m_buffer; // pointer to the dynamically allocated interval array
};
// Include the implementation to satisfy template instantiation requirements
#include "movingAvg.tpp"
#endif #endif
+183
View File
@@ -0,0 +1,183 @@
// Arduino Moving Average Library
// https://github.com/JChristensen/movingAvg
// Copyright (C) 2018 by Jack Christensen and licensed under
// GNU GPL v3.0, https://www.gnu.org/licenses/gpl.html
// Extended to template class for handling of multiple data types
// initialize - allocate the interval array
template <typename T>
void movingAvg<T>::begin()
{
m_readings = new T[m_interval];
}
// add a new reading and return the new moving average
template <typename T>
T movingAvg<T>::reading(T newReading)
{
// add each new data point to the sum until the m_readings array is filled
if (m_nbrReadings < m_interval) {
++m_nbrReadings;
m_sum += newReading;
}
// once the array is filled, subtract the oldest data point and add the new one
else {
m_sum = m_sum - m_readings[m_next] + newReading;
}
m_readings[m_next] = newReading;
if (++m_next >= m_interval) m_next = 0;
return getAvg();
}
// just return the current moving average
template <typename T>
T movingAvg<T>::getAvg()
{
if (m_nbrReadings == 0) return 0;
// Apply rounding for integers only
if (std::is_floating_point<T>::value) {
return m_sum / m_nbrReadings;
} else {
return (m_sum + (SumType)m_nbrReadings / 2) / m_nbrReadings;
}
}
// return the average for a subset of the data, the most recent nPoints readings.
// for invalid values of nPoints, return zero.
template <typename T>
T movingAvg<T>::getAvg(int nPoints)
{
if (nPoints < 1 || nPoints > m_interval || nPoints > m_nbrReadings) {
return 0;
}
SumType sum{0};
int i = m_next;
for (int n=0; n<nPoints; ++n) {
if (i == 0) {
i = m_interval - 1;
}
else {
--i;
}
sum += m_readings[i];
}
if (std::is_floating_point<T>::value) {
return sum / nPoints;
} else {
return (sum + (SumType)nPoints / 2) / nPoints; //
}
}
// start the moving average over again
template <typename T>
void movingAvg<T>::reset()
{
m_nbrReadings = 0;
m_sum = 0;
m_next = 0;
}
// --- End Class movingAvg ---------------
// --- Class MovingAvgAngle ---------------
template <typename T>
void movingAvgAngle<T>::begin()
{
m_buffer = new T[m_interval];
}
// add a new reading and return the new moving average
template <typename T>
T movingAvgAngle<T>::reading(T newReading)
{
double s = std::sin(newReading);
double c = std::cos(newReading);
// add each new data point to the sum until the m_readings array is filled
if (m_nbrReadings < m_interval) {
++m_nbrReadings;
m_sumSin += s;
m_sumCos += c;
} else {
// array is filled; subtract the oldest data point and add the new one
m_sumSin = m_sumSin - sin(m_buffer[m_next]) + s;
m_sumCos = m_sumCos - cos(m_buffer[m_next]) + c;
}
m_buffer[m_next] = newReading;
if (++m_next >= m_interval)
m_next = 0;
return getAvg();
}
// return the current moving average
template <typename T>
T movingAvgAngle<T>::getAvg()
{
if (m_nbrReadings == 0)
return 0;
// check size of vector; if near 0, set it to 0
const double len = m_sumSin * m_sumSin + m_sumCos * m_sumCos;
if (len < 1e-24)
return 0.0; // average direction undefined
return static_cast<T>(to2PI(std::atan2(m_sumSin, m_sumCos)));
}
// return the current moving average for a subset of the data, the most recent nPoints readings.
// for invalid values of nPoints, return zero.
template <typename T>
T movingAvgAngle<T>::getAvg(int nPoints)
{
if (nPoints < 1 || nPoints > m_interval || nPoints > m_nbrReadings)
return 0;
double sumSin = 0.0;
double sumCos = 0.0;
int i = m_next;
for (int n = 0; n < nPoints; ++n) {
if (i == 0)
i = m_interval - 1;
else
--i;
sumSin += std::sin(m_buffer[i]);
sumCos += std::cos(m_buffer[i]);
}
// check size of vector; if near 0, set it to 0
const double len = m_sumSin * m_sumSin + m_sumCos * m_sumCos;
if (len < 1e-24)
return 0.0; // average direction undefined
return static_cast<T>(to2PI(std::atan2(sumSin, sumCos)));
}
// start the moving average over again
template <typename T>
void movingAvgAngle<T>::reset()
{
m_nbrReadings = 0;
m_sumSin = 0;
m_sumCos = 0;
m_next = 0;
}
template <typename T>
T movingAvgAngle<T>::to2PI(T a)
{
a = fmod(a, M_TWOPI);
if (a < 0.0) {
a += M_TWOPI;
}
return a;
}
// --- End class MovingAvgAngle ---------------
+94 -55
View File
@@ -9,7 +9,6 @@
#include <NMEA0183.h> // NMEA0183 #include <NMEA0183.h> // NMEA0183
#include <NMEA0183Msg.h> #include <NMEA0183Msg.h>
#include <NMEA0183Messages.h> #include <NMEA0183Messages.h>
#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 "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
@@ -234,6 +233,8 @@ void registerAllPages(PageList &list){
list.add(&registerPageClock); list.add(&registerPageClock);
extern PageDescription registerPageCompass; extern PageDescription registerPageCompass;
list.add(&registerPageCompass); list.add(&registerPageCompass);
extern PageDescription registerPageCurrent;
list.add(&registerPageCurrent);
extern PageDescription registerPageWhite; extern PageDescription registerPageWhite;
list.add(&registerPageWhite); list.add(&registerPageWhite);
extern PageDescription registerPageBME280; extern PageDescription registerPageBME280;
@@ -264,8 +265,8 @@ void registerAllPages(PageList &list){
list.add(&registerPageDigitalOut); list.add(&registerPageDigitalOut);
extern PageDescription registerPageAutopilot; extern PageDescription registerPageAutopilot;
list.add(&registerPageAutopilot); list.add(&registerPageAutopilot);
extern PageDescription registerPageAnchor; extern PageDescription registerPageWeather;
list.add(&registerPageAnchor); list.add(&registerPageWeather);
} }
// Undervoltage detection for shutdown display // Undervoltage detection for shutdown display
@@ -286,8 +287,12 @@ void underVoltageError(CommonData &common) {
getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(65, 175); getdisplay().setCursor(65, 175);
getdisplay().print("Charge battery and restart system"); getdisplay().print("Charge battery and restart system");
getdisplay().nextPage(); // Partial update displayNextPage(); // Partial update
#ifdef TFT_DISPLAY
getpaneldisplay().powerSave(true); // Display power save
#else
getdisplay().powerOff(); // Display power off getdisplay().powerOff(); // Display power off
#endif
setPortPin(OBP_POWER_EPD, false); // Power off ePaper display setPortPin(OBP_POWER_EPD, false); // Power off ePaper display
setPortPin(OBP_POWER_SD, false); // Power off SD card setPortPin(OBP_POWER_SD, false); // Power off SD card
#else #else
@@ -297,7 +302,7 @@ void underVoltageError(CommonData &common) {
buzzer(TONE4, 20); // Buzzer tone 4kHz 20ms buzzer(TONE4, 20); // Buzzer tone 4kHz 20ms
setPortPin(OBP_POWER_50, false); // Power rail 5.0V Off setPortPin(OBP_POWER_50, false); // Power rail 5.0V Off
// Shutdown EInk display // Shutdown EInk display
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
getdisplay().fillScreen(common.bgcolor);// Clear screen getdisplay().fillScreen(common.bgcolor);// Clear screen
getdisplay().setTextColor(common.fgcolor); getdisplay().setTextColor(common.fgcolor);
getdisplay().setFont(&Ubuntu_Bold20pt8b); getdisplay().setFont(&Ubuntu_Bold20pt8b);
@@ -306,8 +311,12 @@ void underVoltageError(CommonData &common) {
getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(65, 175); getdisplay().setCursor(65, 175);
getdisplay().print("To wake up repower system"); getdisplay().print("To wake up repower system");
getdisplay().nextPage(); // Partial update displayNextPage(); // Partial update
#ifdef TFT_DISPLAY
getpaneldisplay().powerSave(true); // Display power save
#else
getdisplay().powerOff(); // Display power off getdisplay().powerOff(); // Display power off
#endif
#endif #endif
while (true) { while (true) {
esp_deep_sleep_start(); // Deep Sleep without wakeup. Wakeup only after power cycle (restart). esp_deep_sleep_start(); // Deep Sleep without wakeup. Wakeup only after power cycle (restart).
@@ -318,7 +327,7 @@ inline bool underVoltageDetection(float voffset, float vslope) {
// Read supply voltage // Read supply voltage
#if defined VOLTAGE_SENSOR && defined LIPO_ACCU_1200 #if defined VOLTAGE_SENSOR && defined LIPO_ACCU_1200
float actVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.53) * 2; // Vin = 1/2 for OBP40 float actVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.53) * 2; // Vin = 1/2 for OBP40
float minVoltage = 3.65; // Absolut minimum volatge for 3,7V LiPo accu float minVoltage = 3.65; // Absolut minimum voltage for 3,7V LiPo accu
#else #else
float actVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.17) * 20; // Vin = 1/20 for OBP60 float actVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.17) * 20; // Vin = 1/20 for OBP60
float minVoltage = MIN_VOLTAGE; float minVoltage = MIN_VOLTAGE;
@@ -327,6 +336,7 @@ inline bool underVoltageDetection(float voffset, float vslope) {
return (calVoltage < minVoltage); return (calVoltage < minVoltage);
} }
// OBP60 Task // OBP60 Task
//#################################################################################### //####################################################################################
void OBP60Task(GwApi *api){ void OBP60Task(GwApi *api){
@@ -334,7 +344,7 @@ void OBP60Task(GwApi *api){
// return; // return;
GwLog *logger=api->getLogger(); GwLog *logger=api->getLogger();
GwConfigHandler *config=api->getConfig(); GwConfigHandler *config=api->getConfig();
#if defined HARDWARE_V20 || HARDWARE_V21 #ifdef BOARD_OBP60S3
startLedTask(api); startLedTask(api);
#endif #endif
PageList allPages; PageList allPages;
@@ -343,7 +353,7 @@ void OBP60Task(GwApi *api){
commonData.logger=logger; commonData.logger=logger;
commonData.config=config; commonData.config=config;
#if defined HARDWARE_V20 || HARDWARE_V21 #ifdef BOARD_OBP60S3
// Keyboard coordinates for page footer // Keyboard coordinates for page footer
initKeys(commonData); initKeys(commonData);
#endif #endif
@@ -377,36 +387,47 @@ void OBP60Task(GwApi *api){
#ifdef DISPLAY_GDEY042T81 #ifdef DISPLAY_GDEY042T81
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
#elif defined(TFT_DISPLAY)
getpaneldisplay().init(); // Init for TFT LCD panel
#else #else
getdisplay().init(115200); // Init for normal displays getdisplay().init(115200); // Init for normal displays
#endif #endif
#ifdef TFT_DISPLAY
getpaneldisplay().setRotation(0); // Set display orientation (horizontal)
getpaneldisplay().setPanelOffset(0, 0); // Use full native framebuffer coordinates
getpaneldisplay().fillScreen(0x0000); // Initialize full TFT screen to black (native RGB565)
getpaneldisplay().setPanelOffset(OBP_TFT_OFFSET_X, OBP_TFT_OFFSET_Y); // Restore configured operating panel offset
#else
getdisplay().setRotation(0); // Set display orientation (horizontal) getdisplay().setRotation(0); // Set display orientation (horizontal)
getdisplay().setFullWindow(); // Set full Refresh #endif
getdisplay().firstPage(); // set first page displaySetFullWindow(); // Set full Refresh (E-Ink only)
displayFirstPage(); // set first page
getdisplay().fillScreen(commonData.bgcolor); getdisplay().fillScreen(commonData.bgcolor);
getdisplay().setTextColor(commonData.fgcolor); getdisplay().setTextColor(commonData.fgcolor);
getdisplay().nextPage(); // Full Refresh displayNextPage(); // Full Refresh
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update (E-Ink only)
getdisplay().fillScreen(commonData.bgcolor); getdisplay().fillScreen(commonData.bgcolor);
getdisplay().nextPage(); // Fast Refresh displayNextPage(); // Fast Refresh
getdisplay().nextPage(); // Fast Refresh displayNextPage(); // Fast Refresh
if(String(displaymode) == "Logo + QR Code" || String(displaymode) == "Logo"){ if(String(displaymode) == "Logo + QR Code" || String(displaymode) == "Logo"){
getdisplay().fillScreen(commonData.bgcolor); getdisplay().fillScreen(commonData.bgcolor);
getdisplay().drawBitmap(0, 0, gImage_Logo_OBP_400x300_sw, getdisplay().width(), getdisplay().height(), commonData.fgcolor); // Draw start logo // draw the fixed-size logo via generic display wrapper
getdisplay().nextPage(); // Fast Refresh displayDrawBitmap(0, 0, gImage_Logo_OBP_400x300_sw,
getdisplay().nextPage(); // Fast Refresh 400, 300, commonData.fgcolor);
displayNextPage(); // Fast Refresh
displayNextPage(); // Fast Refresh
delay(SHOW_TIME); // Logo show time delay(SHOW_TIME); // Logo show time
if(String(displaymode) == "Logo + QR Code"){ if(String(displaymode) == "Logo + QR Code"){
getdisplay().fillScreen(commonData.bgcolor); getdisplay().fillScreen(commonData.bgcolor);
qrWiFi(systemname, wifipass, commonData.fgcolor, commonData.bgcolor); // Show QR code for WiFi connection qrWiFi(systemname, wifipass, commonData.fgcolor, commonData.bgcolor); // Show QR code for WiFi connection
getdisplay().nextPage(); // Fast Refresh displayNextPage(); // Fast Refresh
getdisplay().nextPage(); // Fast Refresh displayNextPage(); // Fast Refresh
delay(SHOW_TIME); // QR code show time delay(SHOW_TIME); // QR code show time
} }
getdisplay().fillScreen(commonData.bgcolor); getdisplay().fillScreen(commonData.bgcolor);
getdisplay().nextPage(); // Fast Refresh displayNextPage(); // Fast Refresh
getdisplay().nextPage(); // Fast Refresh displayNextPage(); // Fast Refresh
} }
// Init pages // Init pages
@@ -434,13 +455,20 @@ void OBP60Task(GwApi *api){
#endif #endif
LOG_DEBUG(GwLog::LOG,"...done"); LOG_DEBUG(GwLog::LOG,"...done");
int lastPage=-1; // initialize with an impiossible value, so we can detect wether we are during startup and no page has been displayed yet int lastPage=-1; // initialize with an impossible value, so we can detect wether we are during startup and no page has been displayed yet
BoatValueList boatValues; //all the boat values for the api query BoatValueList boatValues; //all the boat values for the api query
HstryBuffers hstryBufferList(1920, &boatValues, logger); // Create empty list of boat data history buffers (1.920 values = seconds = 32 min.) HstryBuffers hstryBufferList(1920, &boatValues, logger); // Create empty list of boat data history buffers (1.920 values = seconds = 32 min.)
WindUtils trueWind(&boatValues, logger); // Create helper object for true wind calculation WindUtils trueWind(&boatValues, logger); // Create helper object for true wind calculation
CalibrationData calibrationDataList(logger); // all boat data types which are supposed to be calibrated CalibrationData calibrationDataList(logger); // all boat data types which are supposed to be calibrated
// Read user settings from config file
bool calcTrueWnds = api->getConfig()->getBool(api->getConfig()->calcTrueWnds, false);
bool smoothCharts = api->getConfig()->getBool(api->getConfig()->smoothCharts, false);
bool useSimuData = api->getConfig()->getBool(api->getConfig()->useSimuData, false);
// Read user calibration data settings from config file
calibrationDataList.readConfig(config);
//fill the page data from config //fill the page data from config
numPages=config->getInt(config->visiblePages,1); numPages=config->getInt(config->visiblePages,1);
if (numPages < 1) numPages=1; if (numPages < 1) numPages=1;
@@ -481,26 +509,21 @@ void OBP60Task(GwApi *api){
pages[i].parameters.values.push_back(value); pages[i].parameters.values.push_back(value);
} }
// Read the specified boat data types of relevant pages and create a history buffer for each type // Read the specified boat data types of relevant pages and create a history buffer for each type for later use in charts
if (pages[i].parameters.pageName == "OneValue" || pages[i].parameters.pageName == "TwoValues" || pages[i].parameters.pageName == "WindPlot") { // applies only to pages that uses charts
if (pages[i].parameters.pageName == "OneValue" || pages[i].parameters.pageName == "TwoValues"
|| pages[i].parameters.pageName == "WindPlot" || pages[i].parameters.pageName == "Weather") {
for (auto pVal : pages[i].parameters.values) { for (auto pVal : pages[i].parameters.values) {
hstryBufferList.addBuffer(pVal->getName()); hstryBufferList.addBuffer(pVal->getName(), smoothCharts);
} }
} }
// Add list of history buffers to page parameters // Add list of history buffers to page parameters
pages[i].parameters.hstryBuffers = &hstryBufferList; 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 user settings from config file
bool calcTrueWnds = api->getConfig()->getBool(api->getConfig()->calcTrueWnds, false);
bool useSimuData = api->getConfig()->getBool(api->getConfig()->useSimuData, false);
// Read user calibration data settings from config file
calibrationDataList.readConfig(config);
// 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
api->registerRequestHandler("screenshot", [api, &pageNumber, pages](AsyncWebServerRequest *request) { api->registerRequestHandler("screenshot", [api, &pageNumber, pages](AsyncWebServerRequest *request) {
@@ -527,8 +550,8 @@ void OBP60Task(GwApi *api){
// Configuration values for main loop // Configuration values for main loop
String gpsFix = api->getConfig()->getConfigItem(api->getConfig()->flashLED,true)->asString(); String gpsFix = api->getConfig()->getConfigItem(api->getConfig()->flashLED,true)->asString();
String gpsOn=api->getConfig()->getConfigItem(api->getConfig()->useGPS,true)->asString(); String gpsOn=api->getConfig()->getConfigItem(api->getConfig()->useGPS,true)->asString();
float tz = api->getConfig()->getConfigItem(api->getConfig()->timeZone,true)->asFloat();
commonData.tz = api->getConfig()->getConfigItem(api->getConfig()->timeZone,true)->asFloat();
commonData.backlight.mode = backlightMapping(config->getConfigItem(config->backlight,true)->asString()); commonData.backlight.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 = uint(config->getConfigItem(config->blBrightness,true)->asInt()); commonData.backlight.brightness = uint(config->getConfigItem(config->blBrightness,true)->asInt());
@@ -703,7 +726,7 @@ void OBP60Task(GwApi *api){
starttime5 = millis(); starttime5 = millis();
if(time->valid == true && date->valid == true && lat->valid == true && lon->valid == true){ if(time->valid == true && date->valid == true && lat->valid == true && lon->valid == true){
// Provide sundata to all pages // Provide sundata to all pages
commonData.sundata = calcSunsetSunrise(time->value , date->value, lat->value, lon->value, commonData.tz); commonData.sundata = calcSunsetSunrise(time->value , date->value, lat->value, lon->value, tz);
// Backlight with sun control // Backlight with sun control
if (commonData.backlight.mode == BacklightMode::SUN) { if (commonData.backlight.mode == BacklightMode::SUN) {
// if(String(backlight) == "Control by Sun"){ // if(String(backlight) == "Control by Sun"){
@@ -716,7 +739,7 @@ void OBP60Task(GwApi *api){
} }
} else if (homevalid and commonData.data.rtcValid) { } else if (homevalid and commonData.data.rtcValid) {
// No gps fix but valid home location and time configured // No gps fix but valid home location and time configured
commonData.sundata = calcSunsetSunriseRTC(&commonData.data.rtcTime, homelat, homelon, commonData.tz); commonData.sundata = calcSunsetSunriseRTC(&commonData.data.rtcTime, homelat, homelon, tz);
} }
} }
@@ -724,25 +747,29 @@ void OBP60Task(GwApi *api){
if(millis() > starttime4 + 8000 && delayedDisplayUpdate == true){ if(millis() > starttime4 + 8000 && delayedDisplayUpdate == true){
starttime1 = millis(); starttime1 = millis();
starttime2 = millis(); starttime2 = millis();
getdisplay().setFullWindow(); // Set full update displaySetFullWindow(); // Set full update
#ifdef TFT_DISPLAY
// TFT LCD doesn't need refresh operations
#else
if(fastrefresh == "true"){ if(fastrefresh == "true"){
getdisplay().nextPage(); // Full update displayNextPage(); // Full update
} }
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
#endif #endif
getdisplay().firstPage(); // Full update displayFirstPage(); // Full update
getdisplay().nextPage(); // Full update displayNextPage(); // Full update
// getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update // getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
// getdisplay().fillScreen(commonData.bgcolor); // Clear display // getdisplay().fillScreen(commonData.bgcolor); // Clear display
// getdisplay().nextPage(); // Partial update // getdisplay().nextPage(); // Partial update
// getdisplay().nextPage(); // Partial update // getdisplay().nextPage(); // Partial update
} }
#endif
delayedDisplayUpdate = false; delayedDisplayUpdate = false;
} }
@@ -752,31 +779,38 @@ void OBP60Task(GwApi *api){
starttime1 = millis(); starttime1 = millis();
starttime2 = millis(); starttime2 = millis();
LOG_DEBUG(GwLog::DEBUG,"E-Ink full refresh first 5 min"); LOG_DEBUG(GwLog::DEBUG,"E-Ink full refresh first 5 min");
getdisplay().setFullWindow(); // Set full update displaySetFullWindow(); // Set full update
#ifdef TFT_DISPLAY
// TFT LCD doesn't need refresh operations
#else
if(fastrefresh == "true"){ if(fastrefresh == "true"){
getdisplay().nextPage(); // Full update displayNextPage(); // Full update
} }
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
#endif #endif
getdisplay().firstPage(); // Full update displayFirstPage(); // Full update
getdisplay().nextPage(); // Full update displayNextPage(); // Full update
// getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update // getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
// getdisplay().fillScreen(commonData.bgcolor); // Clear display // getdisplay().fillScreen(commonData.bgcolor); // Clear display
// getdisplay().nextPage(); // Partial update // getdisplay().nextPage(); // Partial update
// getdisplay().nextPage(); // Partial update // getdisplay().nextPage(); // Partial update
} }
#endif
} }
// Subtask E-Ink full refresh // Subtask E-Ink full refresh
if(millis() > starttime2 + fullrefreshtime * 60 * 1000){ if(millis() > starttime2 + fullrefreshtime * 60 * 1000){
starttime2 = millis(); starttime2 = millis();
LOG_DEBUG(GwLog::DEBUG,"E-Ink full refresh"); LOG_DEBUG(GwLog::DEBUG,"E-Ink full refresh");
#ifdef TFT_DISPLAY
// TFT LCD: no special refresh
#else
getdisplay().setFullWindow(); // Set full update getdisplay().setFullWindow(); // Set full update
if(fastrefresh == "true"){ if(fastrefresh == "true"){
getdisplay().nextPage(); // Full update getdisplay().nextPage(); // Full update
@@ -784,7 +818,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
@@ -796,6 +830,7 @@ void OBP60Task(GwApi *api){
// getdisplay().nextPage(); // Partial update // getdisplay().nextPage(); // Partial update
// getdisplay().nextPage(); // Partial update // getdisplay().nextPage(); // Partial update
} }
#endif
} }
// Refresh display data, default all 1s // Refresh display data, default all 1s
@@ -808,16 +843,18 @@ void OBP60Task(GwApi *api){
if(millis() > starttime3 + pagetime){ if(millis() > starttime3 + pagetime){
LOG_DEBUG(GwLog::DEBUG,"Page with refreshtime=%d", pagetime); LOG_DEBUG(GwLog::DEBUG,"Page with refreshtime=%d", pagetime);
starttime3 = millis(); starttime3 = millis();
bool pageChanged = (lastPage != pageNumber);
//refresh data from api //refresh data from api
api->getBoatDataValues(boatValues.numValues,boatValues.allBoatValues); api->getBoatDataValues(boatValues.numValues,boatValues.allBoatValues);
api->getStatus(commonData.status); api->getStatus(commonData.status);
if (calcTrueWnds) { // ulong startHandl = millis();
trueWind.addWinds(); // calculate true wind data from apparent wind values trueWind.handleWinds(calcTrueWnds); // calculate true wind data from apparent wind values
} trueWind.setMaxWs(); // maintain MaxTWS value in any case; invalid TWS value is considered automatically; MaxAWS is provided by core gateway if AWS is available
calibrationDataList.handleCalibration(&boatValues); // Process calibration for all boat data in <calibrationDataList> calibrationDataList.handleCalibration(&boatValues); // Process calibration for all boat data in <calibrationDataList>
hstryBufferList.handleHstryBufs(useSimuData, commonData); // Handle history buffers for certain boat data for windplot page and other usage hstryBufferList.handleHstryBufs(useSimuData, commonData); // Handle history buffers for certain boat data for charts and other usage
// LOG_DEBUG(GwLog::DEBUG, "obp60task: data handling: %d ms", millis() - startHandl);
// Clear display // Clear display
// getdisplay().fillRect(0, 0, getdisplay().width(), getdisplay().height(), commonData.bgcolor); // getdisplay().fillRect(0, 0, getdisplay().width(), getdisplay().height(), commonData.bgcolor);
@@ -844,16 +881,16 @@ void OBP60Task(GwApi *api){
if (currentPage == NULL){ if (currentPage == NULL){
LOG_DEBUG(GwLog::ERROR,"page number %d not found", pageNumber); LOG_DEBUG(GwLog::ERROR,"page number %d not found", pageNumber);
// Error handling for missing page // Error handling for missing page
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
getdisplay().fillScreen(commonData.bgcolor); // Clear display getdisplay().fillScreen(commonData.bgcolor); // Clear display
getdisplay().drawXBitmap(200 - unknown_width / 2, 150 - unknown_height / 2, unknown_bits, unknown_width, unknown_height, commonData.fgcolor); getdisplay().drawXBitmap(200 - unknown_width / 2, 150 - unknown_height / 2, unknown_bits, unknown_width, unknown_height, commonData.fgcolor);
getdisplay().setCursor(140, 250); getdisplay().setCursor(140, 250);
getdisplay().setFont(&Atari16px); getdisplay().setFont(&Atari16px);
getdisplay().print("Here be dragons!"); getdisplay().print("Here be dragons!");
getdisplay().nextPage(); // Partial update (fast) displayNextPage(); // Partial update (fast)
} }
else{ else{
if (lastPage != pageNumber){ if (pageChanged){
if (lastPage != -1){ // skip cleanup if we are during startup, and no page has been displayed yet. 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
@@ -873,10 +910,12 @@ void OBP60Task(GwApi *api){
displayAlarm(commonData); displayAlarm(commonData);
} }
if (ret & PAGE_UPDATE) { if (ret & PAGE_UPDATE) {
getdisplay().nextPage(); // Partial update (fast) displayNextPage(); // Partial update (fast)
} }
if (ret & PAGE_HIBERNATE) { if (ret & PAGE_HIBERNATE) {
#ifndef TFT_DISPLAY
getdisplay().hibernate(); getdisplay().hibernate();
#endif
} }
} }
+9 -3
View File
@@ -3,7 +3,7 @@
//we only compile for some boards //we only compile for some boards
#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3 #if defined BOARD_OBP60S3 || defined BOARD_OBP40S3
#define USBSerial Serial #define USBSerial Serial
#ifdef HARDWARE_V21 #ifdef BOARD_OBP60S3
// CAN NMEA2000 // CAN NMEA2000
#define ESP32_CAN_TX_PIN 46 #define ESP32_CAN_TX_PIN 46
#define ESP32_CAN_RX_PIN 3 #define ESP32_CAN_RX_PIN 3
@@ -35,13 +35,19 @@
// OBP60 Task // OBP60 Task
void OBP60Task(GwApi *param); void OBP60Task(GwApi *param);
DECLARE_USERTASK_PARAM(OBP60Task, 35000); // Need 35k RAM as stack size DECLARE_USERTASK_PARAM(OBP60Task, 35000); // Need 35k RAM as stack size
#ifdef HARDWARE_V21 #if defined(BOARD_OBP60S3) && defined(TFT_DISPLAY)
DECLARE_CAPABILITY(obp70,true);
#endif
#if defined(BOARD_OBP60S3) && !defined(TFT_DISPLAY)
DECLARE_CAPABILITY(obp60,true); DECLARE_CAPABILITY(obp60,true);
#endif #endif
#ifdef BOARD_OBP40S3 #ifdef BOARD_OBP40S3
DECLARE_CAPABILITY(obp40,true) DECLARE_CAPABILITY(obp40,true)
#endif #endif
#ifdef BOARD_OBP60S3 #if defined(BOARD_OBP60S3) && defined(TFT_DISPLAY)
DECLARE_STRING_CAPABILITY(HELP_URL, "https://obp60-v2-docu.readthedocs.io/en/latest/"); // Link to help pages
#endif
#if defined(BOARD_OBP60S3) && !defined(TFT_DISPLAY)
DECLARE_STRING_CAPABILITY(HELP_URL, "https://obp60-v2-docu.readthedocs.io/en/latest/"); // Link to help pages DECLARE_STRING_CAPABILITY(HELP_URL, "https://obp60-v2-docu.readthedocs.io/en/latest/"); // Link to help pages
#endif #endif
#ifdef BOARD_OBP40S3 #ifdef BOARD_OBP40S3
+103
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 9e2bc99..34eaeac 100644
--- a/lib/obp60task/OBP60Extensions.h
+++ b/lib/obp60task/OBP60Extensions.h
@@ -29,6 +29,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 95d5d1e..01fcd32 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();
+115
View File
@@ -0,0 +1,115 @@
diff --git a/lib/boatData/GwBoatData.cpp b/lib/boatData/GwBoatData.cpp
index 424ebe4..b717097 100644
--- a/lib/boatData/GwBoatData.cpp
+++ b/lib/boatData/GwBoatData.cpp
@@ -6,6 +6,7 @@
#define GWTYPE_UINT32 2
#define GWTYPE_UINT16 3
#define GWTYPE_INT16 4
+#define GWTYPE_STRING 5
#define GWTYPE_USER 100
class GwBoatItemTypes
@@ -15,6 +16,7 @@ public:
static int getType(const uint16_t &x) { return GWTYPE_UINT16; }
static int getType(const int16_t &x) { return GWTYPE_INT16; }
static int getType(const double &x) { return GWTYPE_DOUBLE; }
+ static int getType(const String &x) { return GWTYPE_STRING; }
static int getType(const GwSatInfoList &x) { return GWTYPE_USER + 1; }
};
@@ -252,6 +254,10 @@ static void writeToString(GwTextWriter *writer, const int16_t &value)
{
writer->writeInteger(value);
}
+static void writeToString(GwTextWriter *writer, String value)
+{
+ writer->writeString(value.c_str());
+}
static void writeToString(GwTextWriter *writer, GwSatInfoList &value)
{
writer->writeInteger(value.getNumSats());
@@ -288,6 +294,8 @@ template class GwBoatItem<double>;
template class GwBoatItem<uint32_t>;
template class GwBoatItem<uint16_t>;
template class GwBoatItem<int16_t>;
+template class GwBoatItem<String>;
+
void GwSatInfoList::houseKeeping(unsigned long ts)
{
if (ts == 0)
diff --git a/lib/boatData/GwBoatData.h b/lib/boatData/GwBoatData.h
index ae50b3f..5b32f70 100644
--- a/lib/boatData/GwBoatData.h
+++ b/lib/boatData/GwBoatData.h
@@ -58,6 +58,7 @@ class GwBoatItemBase{
GWSC(formatRot);
GWSC(formatDate);
GWSC(formatTime);
+ GWSC(formatName);
protected:
int type;
unsigned long lastSet=0;
@@ -92,6 +93,7 @@ class GwBoatItemBase{
virtual int getLastSource(){return lastUpdateSource;}
virtual void refresh(unsigned long ts=0){uls(ts);}
virtual double getDoubleValue()=0;
+ virtual String getStringValue()=0;
String getName(){return name;}
const String & getFormat() const{return format;}
virtual void setInvalidTime(GwConfigHandler *cfg);
@@ -120,7 +122,19 @@ template<class T> class GwBoatItem : public GwBoatItemBase{
if (! isValid(millis())) return defaultv;
return data;
}
- virtual double getDoubleValue(){return (double)data;}
+ virtual double getDoubleValue(){
+ if constexpr (std::is_same<T, String>::value) {
+ return 0.0;
+ // TODO any better ideas?
+ // perhaps return std::numeric_limits<double>::quiet_NaN();
+ } else {
+ return (double)data;
+ }
+ }
+ virtual String getStringValue(){
+ return (String)data;
+ }
+
virtual void fillString();
virtual void toJsonDoc(GwJsonDocument *doc, unsigned long minTime);
virtual int getLastSource(){return lastUpdateSource;}
@@ -235,6 +249,7 @@ class GwBoatData{
GWBOATDATA(double,XTE,formatXte) // cross track error
GWBOATDATA(double,WPLat,formatLatitude) // waypoint latitude
GWBOATDATA(double,WPLon,formatLongitude) // waypoint longitude
+ GWBOATDATA(String,WPName,formatName) // waypoint name
GWSPECBOATDATA(GwBoatDataSatList,SatInfo,GwSatInfoList::toType,formatFixed0);
public:
GwBoatData(GwLog *logger, GwConfigHandler *cfg);
diff --git a/lib/nmea0183ton2k/NMEA0183DataToN2K.cpp b/lib/nmea0183ton2k/NMEA0183DataToN2K.cpp
index 6989570..238af38 100644
--- a/lib/nmea0183ton2k/NMEA0183DataToN2K.cpp
+++ b/lib/nmea0183ton2k/NMEA0183DataToN2K.cpp
@@ -355,6 +355,7 @@ private:
AppendN2kRouteWPInfo(n2kMsg,destinationId,rmb.destID,rmb.latitude,rmb.longitude);
send(n2kMsg,msg.sourceId);
}
+ boatData->WPName->update(String(rmb.destID), msg.sourceId);
}
void convertRMC(const SNMEA0183Msg &msg)
{
diff --git a/platformio.ini b/platformio.ini
index b738131..e131a6e 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -68,6 +68,9 @@ lib_ldf_mode = off
monitor_speed = 115200
build_flags =
-D PIO_ENV_BUILD=$PIOENV
+ -std=gnu++17
+build_unflags =
+ -std=gnu++11
[sensors]
; collect the libraries for sensors here
+255
View File
@@ -0,0 +1,255 @@
diff --git a/lib/api/GwApi.h b/lib/api/GwApi.h
index 88f9690..bde7670 100644
--- a/lib/api/GwApi.h
+++ b/lib/api/GwApi.h
@@ -137,6 +137,7 @@ class GwApi{
* thread safe methods - can directly be called from a user task
*/
virtual GwRequestQueue *getQueue()=0;
+ virtual QueueHandle_t getKbQueue()=0;
virtual void sendN2kMessage(const tN2kMsg &msg, bool convert=true)=0;
/**
* deprecated - sourceId will be ignored
diff --git a/lib/config/GwConverterConfig.h b/lib/config/GwConverterConfig.h
index 4a93a71..34ed7bf 100644
--- a/lib/config/GwConverterConfig.h
+++ b/lib/config/GwConverterConfig.h
@@ -71,6 +71,7 @@ class GwConverterConfig{
int rmcInterval=1000;
int rmcCheckTime=4000;
int winst312=256;
+ int swBankInstance=0;
bool unmappedXdr=false;
unsigned long xdrTimeout=60000;
std::vector<WindMapping> windMappings;
@@ -113,4 +114,4 @@ class GwConverterConfig{
};
-#endif
\ No newline at end of file
+#endif
diff --git a/lib/nmea2kto0183/N2kDataToNMEA0183.cpp b/lib/nmea2kto0183/N2kDataToNMEA0183.cpp
index dbf6af5..9fe405b 100644
--- a/lib/nmea2kto0183/N2kDataToNMEA0183.cpp
+++ b/lib/nmea2kto0183/N2kDataToNMEA0183.cpp
@@ -1572,6 +1572,29 @@ private:
finalizeXdr();
}
+ void Handle127502(const tN2kMsg &msg){
+ // switch bank control / receive remote key strokes
+ unsigned char instance=-1;
+ tN2kBinaryStatus bankstatus;
+ LOG_DEBUG(GwLog::LOG,"received switch bank control");
+ // check if we are addressed and our configured instance is used
+ if (! ParseN2kPGN127502(msg,instance,bankstatus)) {
+ LOG_DEBUG(GwLog::DEBUG,"unable to parse PGN %d",msg.PGN);
+ return;
+ }
+ if (! (instance == config.swBankInstance)) {
+ LOG_DEBUG(GwLog::DEBUG,"switch bank instance #%d ignored",instance);
+ return;
+ }
+ for (uint8_t i=1; i<=28; i++) {
+ tN2kOnOff keystatus = N2kGetStatusOnBinaryStatus(bankstatus, i);
+ if (keystatus == 1) {
+ // key pressed: send key to queue
+ xQueueSend(keyboardQueue, &i, 0);
+ }
+ }
+ }
+
void registerConverters()
{
//register all converter functions
@@ -1607,6 +1630,7 @@ private:
converters.registerConverter(127488UL, &N2kToNMEA0183Functions::Handle127488);
converters.registerConverter(130316UL, &N2kToNMEA0183Functions::Handle130316);
converters.registerConverter(127257UL, &N2kToNMEA0183Functions::Handle127257);
+ converters.registerConverter(127502UL, &N2kToNMEA0183Functions::Handle127502);
#define HANDLE_AIS
#ifdef HANDLE_AIS
converters.registerConverter(129038UL, &N2kToNMEA0183Functions::HandleAISClassAPosReport); // AIS Class A Position Report, Message Type 1
@@ -1620,13 +1644,15 @@ private:
public:
N2kToNMEA0183Functions(GwLog *logger, GwBoatData *boatData,
SendNMEA0183MessageCallback callback,
- String talkerId, GwXDRMappings *xdrMappings, const GwConverterConfig &cfg)
+ String talkerId, GwXDRMappings *xdrMappings, const GwConverterConfig &cfg,
+ QueueHandle_t kbQueue)
: N2kDataToNMEA0183(logger, boatData, callback,talkerId)
{
this->logger = logger;
this->boatData = boatData;
this->xdrMappings=xdrMappings;
this->config=cfg;
+ this->keyboardQueue=kbQueue;
registerConverters();
}
virtual void loop(unsigned long lastExtRmc) override
@@ -1642,8 +1668,8 @@ private:
N2kDataToNMEA0183* N2kDataToNMEA0183::create(GwLog *logger, GwBoatData *boatData,
SendNMEA0183MessageCallback callback, String talkerId, GwXDRMappings *xdrMappings,
- const GwConverterConfig &cfg){
- LOG_DEBUG(GwLog::LOG,"creating N2kToNMEA0183");
- return new N2kToNMEA0183Functions(logger,boatData,callback, talkerId,xdrMappings,cfg);
+ const GwConverterConfig &cfg, QueueHandle_t kbQueue){
+ LOG_DEBUG(GwLog::LOG,"creating N2kToNMEA0183");
+ return new N2kToNMEA0183Functions(logger,boatData,callback, talkerId,xdrMappings,cfg,kbQueue);
}
//*****************************************************************************
diff --git a/lib/nmea2kto0183/N2kDataToNMEA0183.h b/lib/nmea2kto0183/N2kDataToNMEA0183.h
index 61e8ee2..f7d8c75 100644
--- a/lib/nmea2kto0183/N2kDataToNMEA0183.h
+++ b/lib/nmea2kto0183/N2kDataToNMEA0183.h
@@ -43,6 +43,7 @@ protected:
GwBoatData *boatData;
int sourceId=0;
char talkerId[3];
+ QueueHandle_t keyboardQueue;
SendNMEA0183MessageCallback sendNMEA0183MessageCallback;
void SendMessage(const tNMEA0183Msg &NMEA0183Msg);
N2kDataToNMEA0183(GwLog *logger, GwBoatData *boatData,
@@ -50,7 +51,8 @@ protected:
public:
static N2kDataToNMEA0183* create(GwLog *logger, GwBoatData *boatData, SendNMEA0183MessageCallback callback,
- String talkerId, GwXDRMappings *xdrMappings,const GwConverterConfig &cfg);
+ String talkerId, GwXDRMappings *xdrMappings,const GwConverterConfig &cfg,
+ QueueHandle_t kbQueue);
virtual void HandleMsg(const tN2kMsg &N2kMsg, int sourceId) = 0;
virtual void loop(unsigned long lastRmc);
virtual ~N2kDataToNMEA0183(){}
@@ -59,4 +61,4 @@ public:
virtual void toJson(GwJsonDocument *json)=0;
virtual String handledKeys()=0;
};
-#endif
\ No newline at end of file
+#endif
diff --git a/lib/obp60task/obp60task.cpp b/lib/obp60task/obp60task.cpp
index 1058ae0..ae11a81 100644
--- a/lib/obp60task/obp60task.cpp
+++ b/lib/obp60task/obp60task.cpp
@@ -348,6 +348,8 @@ void OBP60Task(GwApi *api){
tN2kMsg N2kMsg;
+ QueueHandle_t keyboardQueue = api->getKbQueue();
+
LOG_DEBUG(GwLog::LOG,"obp60task started");
for (auto it=allPages.pages.begin();it != allPages.pages.end();it++){
LOG_DEBUG(GwLog::LOG,"found registered page %s",(*it)->pageName.c_str());
@@ -622,7 +624,16 @@ void OBP60Task(GwApi *api){
if((String(gpsFix) == "GPS Fix Lost" && hdop->value > hdopAccuracy && hdop->valid == true) || (String(gpsFix) == "GPS Fix Lost" && hdop->valid == false)){
setFlashLED(true);
}
-
+
+ // Keyboard messages from remote
+ uint8_t remotekey = 0;
+ if (xQueueReceive(keyboardQueue, &remotekey, 0) == pdPASS) {
+ LOG_DEBUG(GwLog::LOG, "OBP received remote key: %d", remotekey);
+ // inject into internal keyboard queue
+ int injectkey = static_cast<int>(remotekey);
+ xQueueSend(allParameters.queue, &injectkey, 0);
+ }
+
// Check the keyboard message
int keyboardMessage=0;
while (xQueueReceive(allParameters.queue,&keyboardMessage,0)){
diff --git a/lib/usercode/GwUserCode.cpp b/lib/usercode/GwUserCode.cpp
index 1b007f8..b891422 100644
--- a/lib/usercode/GwUserCode.cpp
+++ b/lib/usercode/GwUserCode.cpp
@@ -189,6 +189,10 @@ public:
{
return api->getQueue();
}
+ virtual QueueHandle_t getKbQueue()
+ {
+ return api->getKbQueue();
+ }
virtual void sendN2kMessage(const tN2kMsg &msg,bool convert)
{
GWSYNCHRONIZED(mainLock);
@@ -428,4 +432,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 6daaa51..50625d8 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -158,6 +158,8 @@ GwCounter<unsigned long> countNMEA2KIn("countNMEA2000in");
GwCounter<unsigned long> countNMEA2KOut("countNMEA2000out");
GwIntervalRunner timers;
+QueueHandle_t keyboardQueue = NULL;
+
bool checkPass(String hash){
return config.checkPass(hash);
}
@@ -269,6 +271,10 @@ public:
{
return &mainQueue;
}
+ virtual QueueHandle_t getKbQueue()
+ {
+ return keyboardQueue;
+ }
virtual void sendN2kMessage(const tN2kMsg &msg,bool convert)
{
handleN2kMessage(msg,sourceId,!convert);
@@ -860,6 +866,8 @@ void setup() {
webserver.begin();
xdrMappings.begin();
logger.flush();
+ // remote keyboard support
+ keyboardQueue = xQueueCreate(10, sizeof(uint8_t));
GwConverterConfig converterConfig;
converterConfig.init(&config,&logger);
nmea0183Converter= N2kDataToNMEA0183::create(&logger, &boatData,
@@ -869,7 +877,8 @@ void setup() {
,
config.getString(config.talkerId,String("GP")),
&xdrMappings,
- converterConfig
+ converterConfig,
+ keyboardQueue
);
toN2KConverter= NMEA0183DataToN2K::create(&logger,&boatData,[](const tN2kMsg &msg, int sourceId)->bool{
diff --git a/web/config.json b/web/config.json
index 26d3166..bef7790 100644
--- a/web/config.json
+++ b/web/config.json
@@ -88,6 +88,16 @@
"description":"the brightness of the led (0..255)",
"category":"system"
},
+ {
+ "name": "swBankInstance",
+ "type": "number",
+ "default": 0,
+ "min": 0,
+ "max": 252,
+ "check": "checkMinMax",
+ "description": "the instance of switch bank to receive data for (0..252)",
+ "category": "system"
+ },
{
"name": "talkerId",
"label": "NMEA0183 ID",
@@ -1287,4 +1297,4 @@
"check": "checkXDR",
"category": "xdr30"
}
-]
\ No newline at end of file
+]
+61 -4
View File
@@ -4,9 +4,66 @@
#by uncommenting the next line #by uncommenting the next line
default_envs = default_envs =
obp70_s3
obp60_s3 obp60_s3
obp40_s3 obp40_s3
[env:obp70_s3]
platform = espressif32@6.8.1
board_build.variants_dir = variants
board = obp70_s3_n16r8 #ESP32-S3 N16R8, 16MB flash, 8MB PSRAM, production series
board_build.partitions = default_16MB.csv #ESP32-S3 N16, 16MB flash
custom_config = lib/obp60task/config_obp70.json
custom_script = lib/obp60task/extra_task.py
framework = arduino
lib_deps =
${basedeps.lib_deps}
Wire
SPI
ESP32time
HTTPClient
WiFiClientSecure
esphome/AsyncTCP-esphome@2.1.1
robtillaart/PCF8574@0.3.9
adafruit/Adafruit Unified Sensor @ 1.1.13
blemasle/MCP23017@2.0.0
adafruit/Adafruit BusIO@1.5.0
adafruit/Adafruit GFX Library@1.11.9
#zinggjm/GxEPD2@1.5.8
#https://github.com/ZinggJM/GxEPD2
https://github.com/thooge/GxEPD2
sstaub/Ticker@4.4.0
adafruit/Adafruit BMP280 Library@2.6.2
adafruit/Adafruit BME280 Library@2.2.2
adafruit/Adafruit BMP085 Library@1.2.1
enjoyneering/HTU21D@1.2.1
robtillaart/INA226@0.2.0
paulstoffregen/OneWire@2.3.8
milesburton/DallasTemperature@3.11.0
signetica/SunRise@2.0.2
adafruit/Adafruit FRAM I2C@2.0.3
lovyan03/LovyanGFX@1.2.19
build_flags=
#https://thingpulse.com/usb-settings-for-logging-with-the-esp32-s3-in-platformio/?srsltid=AfmBOopGskbkr4GoeVkNlFaZXe_zXkLceKF6Rn-tmoXABCeAR2vWsdHL
# -D CORE_DEBUG_LEVEL=1 #Debug level for CPU core via CDC (serial device)
# -D TIME=$UNIX_TIME #Set PC time for RTC (only settable via VSC)
-D DISABLE_DIAGNOSTIC_OUTPUT #Disable diagnostic output for GxEPD2 lib
-D BOARD_OBP60S3 #Board OBP60 V2.1 with ESP32S3
# -D HARDWARE_V20 #OBP60 hardware revision V2.0
-D HARDWARE_V21 #OBP60 hardware revision V2.1
-D TFT_DISPLAY #Enable TFT LCD display path (instead of E-Ink)
# -D TFT_320x480_ST7796 #TFT panel type: ST7796 (320x480, 80 MHz), best performance
-D TFT_320x480_ILI9488 #TFT panel type: ILI9488 (320x480, 40 MHz), lower performance
-D OBP_TFT_ENABLE_SCALING=1 #TFT scaling on/off (1=scale to max Y=320, 0=no scaling)
-D OBP_TFT_SCALE_ANTIALIAS=1 #Antialiasing for TFT scaling (1=on, 0=off)
# -D ENABLE_PATCHES #enable patching of gateway code
${env.build_flags}
#CONFIG_ESP_TASK_WDT_TIMEOUT_S = 10 #Task Watchdog timeout period (seconds) [1...60] 5 default
upload_port = /dev/ttyACM0 #OBP60 download via USB-C direct
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_speed = 230400
monitor_speed = 115200
[env:obp60_s3] [env:obp60_s3]
platform = espressif32@6.8.1 platform = espressif32@6.8.1
board_build.variants_dir = variants board_build.variants_dir = variants
@@ -26,7 +83,7 @@ lib_deps =
ESP32time ESP32time
HTTPClient HTTPClient
WiFiClientSecure WiFiClientSecure
esphome/AsyncTCP-esphome@2.0.1 esphome/AsyncTCP-esphome@2.1.1
robtillaart/PCF8574@0.3.9 robtillaart/PCF8574@0.3.9
adafruit/Adafruit Unified Sensor @ 1.1.13 adafruit/Adafruit Unified Sensor @ 1.1.13
blemasle/MCP23017@2.0.0 blemasle/MCP23017@2.0.0
@@ -82,7 +139,7 @@ lib_deps =
ESP32time ESP32time
HTTPClient HTTPClient
WiFiClientSecure WiFiClientSecure
esphome/AsyncTCP-esphome@2.0.1 esphome/AsyncTCP-esphome@2.1.1
robtillaart/PCF8574@0.3.9 robtillaart/PCF8574@0.3.9
adafruit/Adafruit Unified Sensor @ 1.1.13 adafruit/Adafruit Unified Sensor @ 1.1.13
blemasle/MCP23017@2.0.0 blemasle/MCP23017@2.0.0
@@ -107,8 +164,8 @@ build_flags=
-D HARDWARE_V10 #OBP40 hardware revision V1.0 SKU:DIE07300S V1.1 (CrowPanel 4.2) -D HARDWARE_V10 #OBP40 hardware revision V1.0 SKU:DIE07300S V1.1 (CrowPanel 4.2)
-D DISPLAY_GDEY042T81 #new E-Ink display from Good Display (Waveshare), R10 2.2 ohm - good (contast lost by shunshine) -D DISPLAY_GDEY042T81 #new E-Ink display from Good Display (Waveshare), R10 2.2 ohm - good (contast lost by shunshine)
#-D DISPLAY_ZJY400300-042CAAMFGN #alternativ E-Ink display from ZZE Technology, R10 2.2 ohm - very good #-D DISPLAY_ZJY400300-042CAAMFGN #alternativ E-Ink display from ZZE Technology, R10 2.2 ohm - very good
#-D LIPO_ACCU_1200 #Hardware extension, LiPo accu 3,7V 1200mAh -D LIPO_ACCU_1200 #Hardware extension, LiPo accu 3,7V 1200mAh
#-D VOLTAGE_SENSOR #Hardware extension, LiPo voltage sensor with two resistors -D VOLTAGE_SENSOR #Hardware extension, LiPo voltage sensor with two resistors
#-D ENABLE_PATCHES #enable patching of gateway code #-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
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
+10
View File
@@ -0,0 +1,10 @@
#!/bin/bash
# This script compile the software für OBP60-S3
# Attention! Start this cript only in the Gitpod Docker container.
# Start the script with: bash run
# Compile the firmware
echo "Compiling Firmware"
platformio run -e obp70_s3
+10 -2
View File
@@ -1,5 +1,13 @@
#pragma once #pragma once
#include <Arduino.h> #include <Arduino.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
static inline int64_t gwMonotonicUs(){
TickType_t ticks=xTaskGetTickCount();
return ((int64_t)ticks) * ((int64_t)portTICK_PERIOD_MS) * 1000;
}
class TimeAverage{ class TimeAverage{
double factor=0.3; double factor=0.3;
double current=0; double current=0;
@@ -70,7 +78,7 @@ class TimeMonitor{
} }
void reset(){ void reset(){
if (last != 0 && start != 0) loop->add(last-start); if (last != 0 && start != 0) loop->add(last-start);
start=esp_timer_get_time(); start=gwMonotonicUs();
for (size_t i=0;i<len;i++) current[i]=0; for (size_t i=0;i<len;i++) current[i]=0;
count++; count++;
@@ -114,7 +122,7 @@ class TimeMonitor{
for (size_t i=0;i<index;i++){ for (size_t i=0;i<index;i++){
if (current[i] != 0) sv=current[i]; if (current[i] != 0) sv=current[i];
} }
int64_t now=esp_timer_get_time(); int64_t now=gwMonotonicUs();
last=now; last=now;
current[index]=now; current[index]=now;
int64_t currentv=now-sv; int64_t currentv=now-sv;
+4 -4
View File
@@ -21,8 +21,8 @@ lib_deps =
ttlappalainen_NMEA2000=https://github.com/wellenvogel/NMEA2000.git#20251126 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.1.1
ottowinter/ESPAsyncWebServer-esphome@2.0.1 ottowinter/ESPAsyncWebServer-esphome@3.4.0
FS FS
Preferences Preferences
ESPmDNS ESPmDNS
@@ -34,8 +34,8 @@ lib_deps=
ttlappalainen_NMEA2000=symlink://../NMEA2000 ttlappalainen_NMEA2000=symlink://../NMEA2000
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.1.1
ottowinter/ESPAsyncWebServer-esphome@2.0.1 ottowinter/ESPAsyncWebServer-esphome@3.4.0
FS FS
Preferences Preferences
ESPmDNS ESPmDNS
+1 -1
View File
@@ -767,7 +767,7 @@ void loopFunction(void *){
//if(Serial1.available()) {} //if(Serial1.available()) {}
//if(Serial.available()) {} //if(Serial.available()) {}
//if(Serial2.available()) {} //if(Serial2.available()) {}
//delay(1); vTaskDelay(1);
} }
} }
const String USERPREFIX="/api/user/"; const String USERPREFIX="/api/user/";
+74
View File
@@ -0,0 +1,74 @@
#ifndef Pins_Arduino_h
#define Pins_Arduino_h
#include <stdint.h>
#include "soc/soc_caps.h"
#define USB_VID 0x303a
#define USB_PID 0x1001
/*
#define EXTERNAL_NUM_INTERRUPTS 46
#define NUM_DIGITAL_PINS 48
#define NUM_ANALOG_INPUTS 20
// Multi Function Display OBP60 V2.0
static const uint8_t LED_BUILTIN = SOC_GPIO_PIN_COUNT+48;
#define BUILTIN_LED LED_BUILTIN // backward compatibility
#define LED_BUILTIN LED_BUILTIN
#define RGB_BUILTIN LED_BUILTIN
#define RGB_BRIGHTNESS 64
#define analogInputToDigitalPin(p) (((p)<20)?(analogChannelToDigitalPin(p)):-1)
#define digitalPinToInterrupt(p) (((p)<48)?(p):-1)
#define digitalPinHasPWM(p) (p < 46)
*/
static const uint8_t TX = 43;
static const uint8_t RX = 44;
static const uint8_t SDA = 47;
static const uint8_t SCL = 21;
static const uint8_t SS = 39;
static const uint8_t MOSI = 48;
static const uint8_t MISO = 11;
static const uint8_t SCK = 38;
static const uint8_t A0 = 1;
static const uint8_t A1 = 2;
static const uint8_t A2 = 3;
static const uint8_t A3 = 4;
static const uint8_t A4 = 5;
static const uint8_t A5 = 6;
static const uint8_t A6 = 7;
static const uint8_t A7 = 8;
static const uint8_t A8 = 9;
static const uint8_t A9 = 10;
static const uint8_t A10 = 11;
static const uint8_t A11 = 12;
static const uint8_t A12 = 13;
static const uint8_t A13 = 14;
static const uint8_t A14 = 15;
static const uint8_t A15 = 16;
static const uint8_t A16 = 17;
static const uint8_t A17 = 18;
static const uint8_t A18 = 19;
static const uint8_t A19 = 20;
static const uint8_t T1 = 1;
static const uint8_t T2 = 2;
static const uint8_t T3 = 3;
static const uint8_t T4 = 4;
static const uint8_t T5 = 5;
static const uint8_t T6 = 6;
static const uint8_t T7 = 7;
static const uint8_t T8 = 8;
static const uint8_t T9 = 9;
static const uint8_t T10 = 10;
static const uint8_t T11 = 11;
static const uint8_t T12 = 12;
static const uint8_t T13 = 13;
static const uint8_t T14 = 14;
#endif /* Pins_Arduino_h */