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
82 changed files with 11081 additions and 2061 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"
+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);
}; };
+24
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;
+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);
}; };
+319 -22
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
@@ -476,42 +541,192 @@ std::vector<String> wordwrap(String &line, uint16_t maxwidth) {
} }
// 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
@@ -595,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);
@@ -688,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
@@ -923,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
@@ -940,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) {
@@ -951,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
+28 -27
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)
@@ -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
}; };
+31 -21
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];
@@ -107,14 +119,12 @@ class PageAutopilot : public Page
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);
+8 -5
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);
+208 -35
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{
// Show backup image (backup navigation map)
if (hasImageBackup) { // NEW: update backoff only if we actually attempted a fetch (not when skipping due to backoff)
getdisplay().drawBitmap(0, 25, imageBackupData, imageBackupWidth, imageBackupHeight, commonData->fgcolor); 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 info: Connection lost when 5 page refreshes has a connection lost to the map server // Show backup image (backup navigation map)
if (hasImageBackup) {
#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 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);
+133 -133
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++) {
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
DataUnit[i] = formatValue(bvalue, *commonData).unit;
DataFormat[i] = bvalue->getFormat(); // Unit of value
}
// Old values for hold function #ifdef BOARD_OBP60S3
static String OldDataText[HowManyValues] = {"", "", "", "", "", ""}; // Optical warning by limit violation (unused)
static String OldDataUnits[HowManyValues] = {"", "", "", "", "", ""}; if (String(flashLED) == "Limit Violation") {
setBlinkingLED(false);
setFlashLED(false);
}
#endif
if (bvalue == NULL)
return PAGE_OK;
// Get config data // Draw page
String lengthformat = config->getString(config->lengthFormat); //***********************************************************
// bool simulation = config->getBool(config->useSimuData);
bool holdvalues = config->getBool(config->holdvalues);
String flashLED = config->getString(config->flashLED);
String backlightMode = config->getString(config->backlight);
GwApi::BoatValue *bvalue; // Set display in partial refresh mode
String DataName[HowManyValues]; displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
double DataValue[HowManyValues]; getdisplay().setTextWrap(false);
bool DataValid[HowManyValues]; getdisplay().setTextColor(commonData->fgcolor);
String DataText[HowManyValues];
String DataUnits[HowManyValues];
String DataFormat[HowManyValues];
for (int i = 0; i < HowManyValues; i++){ for (int i = 0; i < (HowManyValues / 2); i++) {
bvalue = pageData.values[i]; if (i < (HowManyValues / 2) - 1) { // Don't draw horizontal line after last line of values
DataName[i] = xdrDelete(bvalue->getName()); // Horizontal line 3 px
DataName[i] = DataName[i].substring(0, 6); // String length limit for value name getdisplay().fillRect(0, SixValues_y1 + (i + 1) * SixValues_DeltaY, 400, 3, commonData->fgcolor);
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) for (int j = 0; j < 2; j++) {
if(String(flashLED) == "Limit Violation"){ int ValueIndex = i * 2 + j;
setBlinkingLED(false); int x0 = SixValues_x1 + j * SixValues_DeltaX;
setFlashLED(false); 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]);
if (bvalue == NULL) return PAGE_OK; // WTF why this statement? // Show name
getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().setCursor(x0, y0 + 25);
getdisplay().print(DataName[ValueIndex]); // Page name
// Draw page // Show unit
//*********************************************************** getdisplay().setFont(&Ubuntu_Bold8pt8b);
if (holdValues == false) {
// Set display in partial refresh mode drawTextRalign(x0 + 187, y0 + 19, DataUnit[ValueIndex]); // Unit
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update } else {
getdisplay().setTextColor(commonData->fgcolor); drawTextRalign(x0 + 187, y0 + 19, OldDataUnit[ValueIndex]);
for (int i = 0; i < ( HowManyValues / 2 ); i++){
if (i < (HowManyValues / 2) - 1) { // Don't draw horizontal line after last line of values -> standard design
// Horizontal line 3 pix
getdisplay().fillRect(0, SixValues_y1+(i+1)*SixValues_DeltaY, 400, 3, commonData->fgcolor);
} }
for (int j = 0; j < 2; j++){
int ValueIndex = i * 2 + j;
int x0 = SixValues_x1 + j * SixValues_DeltaX;
int y0 = SixValues_y1 + i * SixValues_DeltaY;
LOG_DEBUG(GwLog::LOG,"Drawing at PageSixValue: %d %s %f %s", ValueIndex, DataName[ValueIndex], DataValue[ValueIndex], DataFormat[ValueIndex] );
// Show name // Set font size depending on data type
getdisplay().setFont(&Ubuntu_Bold12pt8b); if (DataFormat[ValueIndex] == "formatLatitude" || DataFormat[ValueIndex] == "formatLongitude") {
getdisplay().setCursor(x0, y0+25); DsegFontSize = 12;
getdisplay().print(DataName[ValueIndex]); // Page name } else if (DataFormat[ValueIndex] == "formatTime" || DataFormat[ValueIndex] == "formatDate") {
DsegFontSize = 16;
// Show unit }
getdisplay().setFont(&Ubuntu_Bold8pt8b); else if (DataFormat[ValueIndex] == "formatXdr:T:R") { // RPM
getdisplay().setCursor(x0, y0+72); DsegFontSize = 20;
if(holdvalues == false){ }
getdisplay().print(DataUnits[ValueIndex]); // Unit else {
} DsegFontSize = 26;
else{ }
getdisplay().print(OldDataUnits[ValueIndex]);
} // Show bus data value
if (holdValues == false) {
// Switch font if format for any values printBoatValue(DataText[ValueIndex], x0 + 187, y0 + 79, RIGHT, DsegFontSize, smallDecimals);
if(DataFormat[ValueIndex] == "formatLatitude" || DataFormat[ValueIndex] == "formatLongitude"){ } else {
getdisplay().setFont(&Ubuntu_Bold12pt8b); printBoatValue(OldDataText[ValueIndex], x0 + 187, y0 + 79, RIGHT, DsegFontSize, smallDecimals);
getdisplay().setCursor(x0+10, y0+60); }
}
else if(DataFormat[ValueIndex] == "formatTime" || DataFormat[ValueIndex] == "formatDate"){ if (DataValid[ValueIndex] == true) {
getdisplay().setFont(&Ubuntu_Bold16pt8b); OldDataText[ValueIndex] = DataText[ValueIndex]; // Save the old value
getdisplay().setCursor(x0+20,y0+55); OldDataUnit[ValueIndex] = DataUnit[ValueIndex]; // Save the old unit
}
// 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);
} }
// Vertical line 3 px
getdisplay().fillRect(SixValues_x1 + SixValues_DeltaX - 8, SixValues_y1 + i * SixValues_DeltaY, 3, SixValues_DeltaY, commonData->fgcolor);
}
return PAGE_UPDATE; 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 -1
View File
@@ -126,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";
@@ -205,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"
+148 -9
View File
@@ -75,6 +75,44 @@
"obp40": "true" "obp40": "true"
} }
}, },
{
"name": "leeKStd",
"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": "OBP40 Settings",
"capabilities": {
"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]",
@@ -230,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",
@@ -678,7 +727,7 @@
"type": "string", "type": "string",
"default": "text1", "default": "text1",
"description": "Button name", "description": "Button name",
"category": "OBP60 IO-Modul1", "category": "OBP40 IO-Module1",
"capabilities": { "capabilities": {
"obp40":"true" "obp40":"true"
} }
@@ -689,7 +738,7 @@
"type": "string", "type": "string",
"default": "text2", "default": "text2",
"description": "Button name", "description": "Button name",
"category": "OBP60 IO-Modul1", "category": "OBP40 IO-Module1",
"capabilities": { "capabilities": {
"obp40":"true" "obp40":"true"
} }
@@ -700,7 +749,7 @@
"type": "string", "type": "string",
"default": "text3", "default": "text3",
"description": "Button name", "description": "Button name",
"category": "OBP60 IO-Modul1", "category": "OBP40 IO-Module1",
"capabilities": { "capabilities": {
"obp40":"true" "obp40":"true"
} }
@@ -711,7 +760,7 @@
"type": "string", "type": "string",
"default": "text4", "default": "text4",
"description": "Button name", "description": "Button name",
"category": "OBP60 IO-Modul1", "category": "OBP40 IO-Module1",
"capabilities": { "capabilities": {
"obp40":"true" "obp40":"true"
} }
@@ -722,7 +771,7 @@
"type": "string", "type": "string",
"default": "text5", "default": "text5",
"description": "Button name", "description": "Button name",
"category": "OBP60 IO-Modul1", "category": "OBP40 IO-Module1",
"capabilities": { "capabilities": {
"obp40":"true" "obp40":"true"
} }
@@ -1245,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"},
@@ -1306,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"
@@ -1319,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",
@@ -1517,11 +1577,13 @@
"default": "Voltage", "default": "Voltage",
"description": "Type of page for page 1", "description": "Type of page for page 1",
"list": [ "list": [
"Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
"Clock", "Clock",
"Compass", "Compass",
"Current",
"DigitalOut", "DigitalOut",
"DST810", "DST810",
"Fluid", "Fluid",
@@ -1539,6 +1601,7 @@
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"Weather",
"WhitePage", "WhitePage",
"Wind", "Wind",
"WindPlot", "WindPlot",
@@ -1577,6 +1640,7 @@
}, },
"condition": { "condition": {
"page1type": [ "page1type": [
"Current",
"Fluid", "Fluid",
"FourValues", "FourValues",
"FourValues2", "FourValues2",
@@ -1585,6 +1649,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -1619,6 +1684,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -1651,6 +1717,7 @@
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -1682,6 +1749,7 @@
"FourValues", "FourValues",
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -1847,11 +1915,13 @@
"default": "WindRose", "default": "WindRose",
"description": "Type of page for page 2", "description": "Type of page for page 2",
"list": [ "list": [
"Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
"Clock", "Clock",
"Compass", "Compass",
"Current",
"DigitalOut", "DigitalOut",
"DST810", "DST810",
"Fluid", "Fluid",
@@ -1869,6 +1939,7 @@
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"Weather",
"WhitePage", "WhitePage",
"Wind", "Wind",
"WindPlot", "WindPlot",
@@ -1906,6 +1977,7 @@
}, },
"condition": { "condition": {
"page2type": [ "page2type": [
"Current",
"Fluid", "Fluid",
"FourValues", "FourValues",
"FourValues2", "FourValues2",
@@ -1914,6 +1986,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -1947,6 +2020,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -1978,6 +2052,7 @@
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2008,6 +2083,7 @@
"FourValues", "FourValues",
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2168,11 +2244,13 @@
"default": "OneValue", "default": "OneValue",
"description": "Type of page for page 3", "description": "Type of page for page 3",
"list": [ "list": [
"Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
"Clock", "Clock",
"Compass", "Compass",
"Current",
"DigitalOut", "DigitalOut",
"DST810", "DST810",
"Fluid", "Fluid",
@@ -2190,6 +2268,7 @@
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"Weather",
"WhitePage", "WhitePage",
"Wind", "Wind",
"WindPlot", "WindPlot",
@@ -2226,6 +2305,7 @@
}, },
"condition": { "condition": {
"page3type": [ "page3type": [
"Current",
"Fluid", "Fluid",
"FourValues", "FourValues",
"FourValues2", "FourValues2",
@@ -2234,6 +2314,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2266,6 +2347,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2296,6 +2378,7 @@
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2325,6 +2408,7 @@
"FourValues", "FourValues",
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2480,11 +2564,13 @@
"default": "TwoValues", "default": "TwoValues",
"description": "Type of page for page 4", "description": "Type of page for page 4",
"list": [ "list": [
"Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
"Clock", "Clock",
"Compass", "Compass",
"Current",
"DigitalOut", "DigitalOut",
"DST810", "DST810",
"Fluid", "Fluid",
@@ -2502,6 +2588,7 @@
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"Weather",
"WhitePage", "WhitePage",
"Wind", "Wind",
"WindPlot", "WindPlot",
@@ -2537,6 +2624,7 @@
}, },
"condition": { "condition": {
"page4type": [ "page4type": [
"Current",
"Fluid", "Fluid",
"FourValues", "FourValues",
"FourValues2", "FourValues2",
@@ -2545,6 +2633,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2576,6 +2665,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2605,6 +2695,7 @@
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2633,6 +2724,7 @@
"FourValues", "FourValues",
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2783,11 +2875,13 @@
"default": "ThreeValues", "default": "ThreeValues",
"description": "Type of page for page 5", "description": "Type of page for page 5",
"list": [ "list": [
"Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
"Clock", "Clock",
"Compass", "Compass",
"Current",
"DigitalOut", "DigitalOut",
"DST810", "DST810",
"Fluid", "Fluid",
@@ -2805,6 +2899,7 @@
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"Weather",
"WhitePage", "WhitePage",
"Wind", "Wind",
"WindPlot", "WindPlot",
@@ -2839,6 +2934,7 @@
}, },
"condition": { "condition": {
"page5type": [ "page5type": [
"Current",
"Fluid", "Fluid",
"FourValues", "FourValues",
"FourValues2", "FourValues2",
@@ -2847,6 +2943,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2877,6 +2974,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2932,6 +3030,7 @@
"FourValues", "FourValues",
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3077,11 +3176,13 @@
"default": "FourValues", "default": "FourValues",
"description": "Type of page for page 6", "description": "Type of page for page 6",
"list": [ "list": [
"Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
"Clock", "Clock",
"Compass", "Compass",
"Current",
"DigitalOut", "DigitalOut",
"DST810", "DST810",
"Fluid", "Fluid",
@@ -3099,6 +3200,7 @@
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"Weather",
"WhitePage", "WhitePage",
"Wind", "Wind",
"WindPlot", "WindPlot",
@@ -3132,6 +3234,7 @@
}, },
"condition": { "condition": {
"page6type": [ "page6type": [
"Current",
"Fluid", "Fluid",
"FourValues", "FourValues",
"FourValues2", "FourValues2",
@@ -3140,6 +3243,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3169,6 +3273,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3196,6 +3301,7 @@
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3222,6 +3328,7 @@
"FourValues", "FourValues",
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3362,11 +3469,13 @@
"default": "FourValues2", "default": "FourValues2",
"description": "Type of page for page 7", "description": "Type of page for page 7",
"list": [ "list": [
"Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
"Clock", "Clock",
"Compass", "Compass",
"Current",
"DigitalOut", "DigitalOut",
"DST810", "DST810",
"Fluid", "Fluid",
@@ -3384,6 +3493,7 @@
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"Weather",
"WhitePage", "WhitePage",
"Wind", "Wind",
"WindPlot", "WindPlot",
@@ -3416,6 +3526,7 @@
}, },
"condition": { "condition": {
"page7type": [ "page7type": [
"Current",
"Fluid", "Fluid",
"FourValues", "FourValues",
"FourValues2", "FourValues2",
@@ -3424,6 +3535,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3452,6 +3564,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3478,6 +3591,7 @@
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3503,6 +3617,7 @@
"FourValues", "FourValues",
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3638,11 +3753,13 @@
"default": "Clock", "default": "Clock",
"description": "Type of page for page 8", "description": "Type of page for page 8",
"list": [ "list": [
"Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
"Clock", "Clock",
"Compass", "Compass",
"Current",
"DigitalOut", "DigitalOut",
"DST810", "DST810",
"Fluid", "Fluid",
@@ -3660,6 +3777,7 @@
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"Weather",
"WhitePage", "WhitePage",
"Wind", "Wind",
"WindPlot", "WindPlot",
@@ -3691,6 +3809,7 @@
}, },
"condition": { "condition": {
"page8type": [ "page8type": [
"Current",
"Fluid", "Fluid",
"FourValues", "FourValues",
"FourValues2", "FourValues2",
@@ -3699,6 +3818,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3726,6 +3846,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3751,6 +3872,7 @@
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3775,6 +3897,7 @@
"FourValues", "FourValues",
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3905,11 +4028,13 @@
"default": "RollPitch", "default": "RollPitch",
"description": "Type of page for page 9", "description": "Type of page for page 9",
"list": [ "list": [
"Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
"Clock", "Clock",
"Compass", "Compass",
"Current",
"DigitalOut", "DigitalOut",
"DST810", "DST810",
"Fluid", "Fluid",
@@ -3927,6 +4052,7 @@
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"Weather",
"WhitePage", "WhitePage",
"Wind", "Wind",
"WindPlot", "WindPlot",
@@ -3957,6 +4083,7 @@
}, },
"condition": { "condition": {
"page9type": [ "page9type": [
"Current",
"Fluid", "Fluid",
"FourValues", "FourValues",
"FourValues2", "FourValues2",
@@ -3965,6 +4092,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3991,6 +4119,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -4015,6 +4144,7 @@
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -4038,6 +4168,7 @@
"FourValues", "FourValues",
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -4163,11 +4294,13 @@
"default": "Battery2", "default": "Battery2",
"description": "Type of page for page 10", "description": "Type of page for page 10",
"list": [ "list": [
"Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
"Clock", "Clock",
"Compass", "Compass",
"Current",
"DigitalOut", "DigitalOut",
"DST810", "DST810",
"Fluid", "Fluid",
@@ -4185,6 +4318,7 @@
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"Weather",
"WhitePage", "WhitePage",
"Wind", "Wind",
"WindPlot", "WindPlot",
@@ -4214,6 +4348,7 @@
}, },
"condition": { "condition": {
"page10type": [ "page10type": [
"Current",
"Fluid", "Fluid",
"FourValues", "FourValues",
"FourValues2", "FourValues2",
@@ -4222,6 +4357,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -4247,6 +4383,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -4270,6 +4407,7 @@
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -4292,6 +4430,7 @@
"FourValues", "FourValues",
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
+144 -3
View File
@@ -75,6 +75,44 @@
"obp60":"true" "obp60":"true"
} }
}, },
{
"name": "leeKStd",
"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",
"default": "0.00",
"check": "checkMinMax",
"min": 0,
"max": 20,
"description": "Boat coefficient K for leeway - individual setting [0..20]",
"category": "OBP60 Settings",
"capabilities": {
"obp60":"true"
},
"condition": [
{ "leeKStd": ["Individual value"] }
]
},
{ {
"name": "fuelTank", "name": "fuelTank",
"label": "Fuel Tank [l]", "label": "Fuel Tank [l]",
@@ -230,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",
@@ -1235,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"}
], ],
@@ -1294,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"
@@ -1307,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",
@@ -1494,11 +1555,13 @@
"default": "Voltage", "default": "Voltage",
"description": "Type of page for page 1", "description": "Type of page for page 1",
"list": [ "list": [
"Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
"Clock", "Clock",
"Compass", "Compass",
"Current",
"DigitalOut", "DigitalOut",
"DST810", "DST810",
"Fluid", "Fluid",
@@ -1516,6 +1579,7 @@
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"Weather",
"WhitePage", "WhitePage",
"Wind", "Wind",
"WindPlot", "WindPlot",
@@ -1554,6 +1618,7 @@
}, },
"condition": { "condition": {
"page1type": [ "page1type": [
"Current",
"Fluid", "Fluid",
"FourValues", "FourValues",
"FourValues2", "FourValues2",
@@ -1562,6 +1627,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -1596,6 +1662,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -1628,6 +1695,7 @@
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -1659,6 +1727,7 @@
"FourValues", "FourValues",
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -1794,11 +1863,13 @@
"default": "WindRose", "default": "WindRose",
"description": "Type of page for page 2", "description": "Type of page for page 2",
"list": [ "list": [
"Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
"Clock", "Clock",
"Compass", "Compass",
"Current",
"DigitalOut", "DigitalOut",
"DST810", "DST810",
"Fluid", "Fluid",
@@ -1816,6 +1887,7 @@
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"Weather",
"WhitePage", "WhitePage",
"Wind", "Wind",
"WindPlot", "WindPlot",
@@ -1853,6 +1925,7 @@
}, },
"condition": { "condition": {
"page2type": [ "page2type": [
"Current",
"Fluid", "Fluid",
"FourValues", "FourValues",
"FourValues2", "FourValues2",
@@ -1861,6 +1934,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -1894,6 +1968,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -1925,6 +2000,7 @@
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -1955,6 +2031,7 @@
"FourValues", "FourValues",
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2086,11 +2163,13 @@
"default": "OneValue", "default": "OneValue",
"description": "Type of page for page 3", "description": "Type of page for page 3",
"list": [ "list": [
"Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
"Clock", "Clock",
"Compass", "Compass",
"Current",
"DigitalOut", "DigitalOut",
"DST810", "DST810",
"Fluid", "Fluid",
@@ -2108,6 +2187,7 @@
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"Weather",
"WhitePage", "WhitePage",
"Wind", "Wind",
"WindPlot", "WindPlot",
@@ -2144,6 +2224,7 @@
}, },
"condition": { "condition": {
"page3type": [ "page3type": [
"Current",
"Fluid", "Fluid",
"FourValues", "FourValues",
"FourValues2", "FourValues2",
@@ -2152,6 +2233,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2184,6 +2266,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2214,6 +2297,7 @@
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2243,6 +2327,7 @@
"FourValues", "FourValues",
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2370,11 +2455,13 @@
"default": "TwoValues", "default": "TwoValues",
"description": "Type of page for page 4", "description": "Type of page for page 4",
"list": [ "list": [
"Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
"Clock", "Clock",
"Compass", "Compass",
"Current",
"DigitalOut", "DigitalOut",
"DST810", "DST810",
"Fluid", "Fluid",
@@ -2392,6 +2479,7 @@
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"Weather",
"WhitePage", "WhitePage",
"Wind", "Wind",
"WindPlot", "WindPlot",
@@ -2427,6 +2515,7 @@
}, },
"condition": { "condition": {
"page4type": [ "page4type": [
"Current",
"Fluid", "Fluid",
"FourValues", "FourValues",
"FourValues2", "FourValues2",
@@ -2435,6 +2524,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2466,6 +2556,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2495,6 +2586,7 @@
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2523,6 +2615,7 @@
"FourValues", "FourValues",
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2646,11 +2739,13 @@
"default": "ThreeValues", "default": "ThreeValues",
"description": "Type of page for page 5", "description": "Type of page for page 5",
"list": [ "list": [
"Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
"Clock", "Clock",
"Compass", "Compass",
"Current",
"DigitalOut", "DigitalOut",
"DST810", "DST810",
"Fluid", "Fluid",
@@ -2668,6 +2763,7 @@
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"Weather",
"WhitePage", "WhitePage",
"Wind", "Wind",
"WindPlot", "WindPlot",
@@ -2702,6 +2798,7 @@
}, },
"condition": { "condition": {
"page5type": [ "page5type": [
"Current",
"Fluid", "Fluid",
"FourValues", "FourValues",
"FourValues2", "FourValues2",
@@ -2710,6 +2807,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2740,6 +2838,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2768,6 +2867,7 @@
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2795,6 +2895,7 @@
"FourValues", "FourValues",
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -2914,11 +3015,13 @@
"default": "FourValues", "default": "FourValues",
"description": "Type of page for page 6", "description": "Type of page for page 6",
"list": [ "list": [
"Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
"Clock", "Clock",
"Compass", "Compass",
"Current",
"DigitalOut", "DigitalOut",
"DST810", "DST810",
"Fluid", "Fluid",
@@ -2936,6 +3039,7 @@
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"Weather",
"WhitePage", "WhitePage",
"Wind", "Wind",
"WindPlot", "WindPlot",
@@ -2969,6 +3073,7 @@
}, },
"condition": { "condition": {
"page6type": [ "page6type": [
"Current",
"Fluid", "Fluid",
"FourValues", "FourValues",
"FourValues2", "FourValues2",
@@ -2977,6 +3082,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3006,6 +3112,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3033,6 +3140,7 @@
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3059,6 +3167,7 @@
"FourValues", "FourValues",
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3174,11 +3283,13 @@
"default": "FourValues2", "default": "FourValues2",
"description": "Type of page for page 7", "description": "Type of page for page 7",
"list": [ "list": [
"Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
"Clock", "Clock",
"Compass", "Compass",
"Current",
"DigitalOut", "DigitalOut",
"DST810", "DST810",
"Fluid", "Fluid",
@@ -3196,6 +3307,7 @@
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"Weather",
"WhitePage", "WhitePage",
"Wind", "Wind",
"WindPlot", "WindPlot",
@@ -3228,6 +3340,7 @@
}, },
"condition": { "condition": {
"page7type": [ "page7type": [
"Current",
"Fluid", "Fluid",
"FourValues", "FourValues",
"FourValues2", "FourValues2",
@@ -3236,6 +3349,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3264,6 +3378,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3290,6 +3405,7 @@
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3315,6 +3431,7 @@
"FourValues", "FourValues",
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3426,11 +3543,13 @@
"default": "Clock", "default": "Clock",
"description": "Type of page for page 8", "description": "Type of page for page 8",
"list": [ "list": [
"Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
"Clock", "Clock",
"Compass", "Compass",
"Current",
"DigitalOut", "DigitalOut",
"DST810", "DST810",
"Fluid", "Fluid",
@@ -3448,6 +3567,7 @@
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"Weather",
"WhitePage", "WhitePage",
"Wind", "Wind",
"WindPlot", "WindPlot",
@@ -3479,6 +3599,7 @@
}, },
"condition": { "condition": {
"page8type": [ "page8type": [
"Current",
"Fluid", "Fluid",
"FourValues", "FourValues",
"FourValues2", "FourValues2",
@@ -3487,6 +3608,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3514,6 +3636,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3539,6 +3662,7 @@
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3563,6 +3687,7 @@
"FourValues", "FourValues",
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3670,11 +3795,13 @@
"default": "RollPitch", "default": "RollPitch",
"description": "Type of page for page 9", "description": "Type of page for page 9",
"list": [ "list": [
"Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
"Clock", "Clock",
"Compass", "Compass",
"Current",
"DigitalOut", "DigitalOut",
"DST810", "DST810",
"Fluid", "Fluid",
@@ -3692,6 +3819,7 @@
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"Weather",
"WhitePage", "WhitePage",
"Wind", "Wind",
"WindPlot", "WindPlot",
@@ -3722,6 +3850,7 @@
}, },
"condition": { "condition": {
"page9type": [ "page9type": [
"Current",
"Fluid", "Fluid",
"FourValues", "FourValues",
"FourValues2", "FourValues2",
@@ -3730,6 +3859,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3756,6 +3886,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3780,6 +3911,7 @@
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3803,6 +3935,7 @@
"FourValues", "FourValues",
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3906,11 +4039,13 @@
"default": "Battery2", "default": "Battery2",
"description": "Type of page for page 10", "description": "Type of page for page 10",
"list": [ "list": [
"Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
"Clock", "Clock",
"Compass", "Compass",
"Current",
"DigitalOut", "DigitalOut",
"DST810", "DST810",
"Fluid", "Fluid",
@@ -3928,6 +4063,7 @@
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"Weather",
"WhitePage", "WhitePage",
"Wind", "Wind",
"WindPlot", "WindPlot",
@@ -3957,6 +4093,7 @@
}, },
"condition": { "condition": {
"page10type": [ "page10type": [
"Current",
"Fluid", "Fluid",
"FourValues", "FourValues",
"FourValues2", "FourValues2",
@@ -3965,6 +4102,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -3990,6 +4128,7 @@
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"TwoValues", "TwoValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -4013,6 +4152,7 @@
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"ThreeValues", "ThreeValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
@@ -4035,6 +4175,7 @@
"FourValues", "FourValues",
"FourValues2", "FourValues2",
"SixValues", "SixValues",
"Weather",
"WindRoseFlex" "WindRoseFlex"
], ],
"visiblePages": [ "visiblePages": [
File diff suppressed because it is too large Load Diff
+3
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
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/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 ---------------
+91 -50
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,6 +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 registerPageWeather;
list.add(&registerPageWeather);
} }
// Undervoltage detection for shutdown display // Undervoltage detection for shutdown display
@@ -284,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
@@ -295,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);
@@ -304,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).
@@ -316,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;
@@ -325,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){
@@ -332,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;
@@ -341,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
@@ -375,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
@@ -432,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;
@@ -479,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) {
@@ -722,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;
} }
@@ -750,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
@@ -782,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
@@ -794,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
@@ -806,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);
@@ -842,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
@@ -871,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
+]
+59 -2
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
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 */