Explorar o código

Merge remote-tracking branch 'upstream/rolling' into analog-digit-early-digit-test

Frank Haverland hai 1 ano
pai
achega
488ae174a4
Modificáronse 100 ficheiros con 5991 adicións e 3026 borrados
  1. 0 0
      .github/ISSUE_TEMPLATE/bug_report.yaml
  2. 0 0
      .github/ISSUE_TEMPLATE/config.yaml
  3. 0 0
      .github/label-commenter-config.yaml
  4. 30 29
      .github/workflows/build.yaml
  5. 0 0
      .github/workflows/clear_cache.yaml
  6. 0 0
      .github/workflows/manual-update-webinstaller.yaml
  7. 5 4
      .github/workflows/reply-bot.yaml
  8. 57 15
      Changelog.md
  9. 3 0
      code/README.md
  10. 699 421
      code/components/jomjol_controlcamera/ClassControllCamera.cpp
  11. 93 42
      code/components/jomjol_controlcamera/ClassControllCamera.h
  12. 152 0
      code/components/jomjol_controlcamera/ov2640_sharpness.cpp
  13. 11 0
      code/components/jomjol_controlcamera/ov2640_sharpness.h
  14. 142 133
      code/components/jomjol_controlcamera/server_camera.cpp
  15. 0 1
      code/components/jomjol_controlcamera/server_camera.h
  16. 1 1
      code/components/jomjol_fileserver_ota/miniz/miniz.h
  17. 1 1
      code/components/jomjol_fileserver_ota/server_file.cpp
  18. 402 393
      code/components/jomjol_flowcontroll/ClassFlowAlignment.cpp
  19. 51 54
      code/components/jomjol_flowcontroll/ClassFlowAlignment.h
  20. 245 252
      code/components/jomjol_flowcontroll/ClassFlowCNNGeneral.cpp
  21. 1 0
      code/components/jomjol_flowcontroll/ClassFlowDefineTypes.h
  22. 3 1
      code/components/jomjol_flowcontroll/ClassFlowInfluxDB.cpp
  23. 4 2
      code/components/jomjol_flowcontroll/ClassFlowInfluxDBv2.cpp
  24. 280 303
      code/components/jomjol_flowcontroll/ClassFlowPostProcessing.cpp
  25. 440 101
      code/components/jomjol_flowcontroll/ClassFlowTakeImage.cpp
  26. 9 24
      code/components/jomjol_flowcontroll/ClassFlowTakeImage.h
  27. 801 336
      code/components/jomjol_flowcontroll/MainFlowControl.cpp
  28. 61 8
      code/components/jomjol_flowcontroll/MainFlowControl.h
  29. 1 1
      code/components/jomjol_helper/CMakeLists.txt
  30. 624 414
      code/components/jomjol_helper/Helper.cpp
  31. 1 0
      code/components/jomjol_helper/Helper.h
  32. 1 0
      code/components/jomjol_helper/sdcard_check.cpp
  33. 651 0
      code/components/jomjol_helper/sdcard_init.c
  34. 111 0
      code/components/jomjol_helper/sdcard_init.h
  35. 53 1
      code/components/jomjol_image_proc/CImageBasis.cpp
  36. 2 0
      code/components/jomjol_image_proc/CImageBasis.h
  37. 309 353
      code/components/jomjol_image_proc/CRotateImage.cpp
  38. 27 28
      code/components/jomjol_image_proc/CRotateImage.h
  39. 13 28
      code/components/jomjol_influxdb/interface_influxdb.cpp
  40. 2 2
      code/components/jomjol_influxdb/interface_influxdb.h
  41. 3 0
      code/components/jomjol_mqtt/server_mqtt.cpp
  42. 15 1
      code/components/jomjol_tfliteclass/CTfLiteClass.cpp
  43. 1 1
      code/components/jomjol_time_sntp/CMakeLists.txt
  44. 46 13
      code/components/jomjol_time_sntp/time_sntp.cpp
  45. 2 0
      code/components/jomjol_time_sntp/time_sntp.h
  46. 2 2
      code/components/jomjol_wlan/connect_wlan.cpp
  47. 3 3
      code/dependencies.lock
  48. 1 9
      code/include/defines.h
  49. 28 31
      code/main/main.cpp
  50. 22 0
      code/main/server_main.cpp
  51. 1 1
      code/platformio.ini
  52. 0 5
      code/sdkconfig.defaults
  53. 11 1
      code/test/components/jomjol-flowcontroll/test_flow_postrocess_helper.cpp
  54. 2 0
      code/test/components/jomjol-flowcontroll/test_flow_pp_negative.cpp
  55. 20 11
      code/test/test_suite_flowcontroll.cpp
  56. 3 0
      param-docs/.idea/.gitignore
  57. 8 0
      param-docs/.idea/generate-param-docs.iml
  58. 6 0
      param-docs/.idea/inspectionProfiles/profiles_settings.xml
  59. 4 0
      param-docs/.idea/misc.xml
  60. 8 0
      param-docs/.idea/modules.xml
  61. 6 0
      param-docs/.idea/vcs.xml
  62. 17 0
      param-docs/README.md
  63. 46 0
      param-docs/expert-params.txt
  64. 95 0
      param-docs/generate-template-param-doc-pages.py
  65. 4 0
      param-docs/hidden-in-ui.txt
  66. 14 0
      param-docs/parameter-pages/Alignment/AlignmentAlgo.md
  67. 11 0
      param-docs/parameter-pages/Alignment/FlipImageSize.md
  68. 10 0
      param-docs/parameter-pages/Alignment/InitialMirror.md
  69. 12 0
      param-docs/parameter-pages/Alignment/InitialRotate.md
  70. 14 0
      param-docs/parameter-pages/Alignment/SearchFieldX.md
  71. 14 0
      param-docs/parameter-pages/Alignment/SearchFieldY.md
  72. 10 0
      param-docs/parameter-pages/Analog/CNNGoodThreshold.md
  73. 5 0
      param-docs/parameter-pages/Analog/ExtendedResolution.md
  74. 4 0
      param-docs/parameter-pages/Analog/Model.md
  75. 7 0
      param-docs/parameter-pages/Analog/ROIImagesLocation.md
  76. 6 0
      param-docs/parameter-pages/Analog/ROIImagesRetention.md
  77. 12 0
      param-docs/parameter-pages/AutoTimer/AutoStart.md
  78. 7 0
      param-docs/parameter-pages/AutoTimer/Interval.md
  79. 6 0
      param-docs/parameter-pages/DataLogging/DataFilesRetention.md
  80. 8 0
      param-docs/parameter-pages/DataLogging/DataLogActive.md
  81. 16 0
      param-docs/parameter-pages/Debug/LogLevel.md
  82. 6 0
      param-docs/parameter-pages/Debug/LogfilesRetention.md
  83. 10 0
      param-docs/parameter-pages/Digits/CNNGoodThreshold.md
  84. 4 0
      param-docs/parameter-pages/Digits/Model.md
  85. 7 0
      param-docs/parameter-pages/Digits/ROIImagesLocation.md
  86. 6 0
      param-docs/parameter-pages/Digits/ROIImagesRetention.md
  87. 21 0
      param-docs/parameter-pages/GPIO/IO0.md
  88. 19 0
      param-docs/parameter-pages/GPIO/IO1.md
  89. 19 0
      param-docs/parameter-pages/GPIO/IO12.md
  90. 19 0
      param-docs/parameter-pages/GPIO/IO13.md
  91. 19 0
      param-docs/parameter-pages/GPIO/IO3.md
  92. 20 0
      param-docs/parameter-pages/GPIO/IO4.md
  93. 5 0
      param-docs/parameter-pages/GPIO/LEDColor.md
  94. 4 0
      param-docs/parameter-pages/GPIO/LEDNumbers.md
  95. 3 0
      param-docs/parameter-pages/GPIO/LEDType.md
  96. 8 0
      param-docs/parameter-pages/GPIO/MainTopicMQTT.md
  97. 7 0
      param-docs/parameter-pages/InfluxDB/Database.md
  98. 4 0
      param-docs/parameter-pages/InfluxDB/NUMBER.Field.md
  99. 7 0
      param-docs/parameter-pages/InfluxDB/NUMBER.Measurement.md
  100. 7 0
      param-docs/parameter-pages/InfluxDB/Uri.md

+ 0 - 0
.github/ISSUE_TEMPLATE/bug_report.yml → .github/ISSUE_TEMPLATE/bug_report.yaml


+ 0 - 0
.github/ISSUE_TEMPLATE/config.yml → .github/ISSUE_TEMPLATE/config.yaml


+ 0 - 0
.github/label-commenter-config.yml → .github/label-commenter-config.yaml


+ 30 - 29
.github/workflows/build.yml → .github/workflows/build.yaml

@@ -15,7 +15,7 @@ jobs:
       with:
         concurrent_skipping: same_content_newer
     
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
       with:
         submodules: recursive
 
@@ -25,28 +25,28 @@ jobs:
         echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
 
     - name: Update PIP cache on every commit
-      uses: actions/cache@v3.2.3
+      uses: actions/cache@v4
       with:
         path: ~/.cache/pip
         key: pip-${{ github.run_id }}
         restore-keys: pip # This matches above key as it is only used as a prefix. it the restores the nearest cache, see https://github.com/restore-keys:/blob/main/tips-and-workarounds.md#update-a-cache
 
     - name: Update PlatformIO cache on every commit
-      uses: actions/cache@v3.2.3
+      uses: actions/cache@v4
       with:
         path: ~/.platformio
         key: platformio-${{ github.run_id }}
         restore-keys: platformio # This matches above key as it is only used as a prefix. it the restores the nearest cache, see https://github.com/restore-keys:/blob/main/tips-and-workarounds.md#update-a-cache
           
     - name: Update Build cache on every commit
-      uses: actions/cache@v3.2.3
+      uses: actions/cache@v4
       with:
         path: ./code/.pio/
         key: build-${{ github.run_id }}
         restore-keys: build # This matches above key as it is only used as a prefix. it the restores the nearest cache, see https://github.com/restore-keys:/blob/main/tips-and-workarounds.md#update-a-cache
           
     - name: Update generated-files cache on every commit
-      uses: actions/cache@v3.2.3
+      uses: actions/cache@v4
       with:
         path: |
           ./code/.pio/build/esp32cam/firmware.bin
@@ -57,7 +57,7 @@ jobs:
         restore-keys: generated-files # This matches above key as it is only used as a prefix. it the restores the nearest cache, see https://github.com/restore-keys:/blob/main/tips-and-workarounds.md#update-a-cache          
 
     - name: Set up Python
-      uses: actions/setup-python@v4
+      uses: actions/setup-python@v5
       with:
         python-version: '3.10'
 
@@ -70,7 +70,7 @@ jobs:
       #run: echo "Testing... ${{ github.ref_name }}, ${{ steps.vars.outputs.sha_short }}" > ./sd-card/html/version.txt; mkdir -p ./code/.pio/build/esp32cam/; cd ./code/.pio/build/esp32cam/; echo "${{ steps.vars.outputs.sha_short }}" > firmware.bin; cp firmware.bin partitions.bin; cp firmware.bin bootloader.bin # Testing
       run: cd code; platformio run --environment esp32cam
 
-    - name: Prepare Web UI (copy data from repo, generate tooltip pages and update hashes in all files)
+    - name: Prepare Web UI (generate tooltip pages and update hashes in all files)
       run: |
         rm -rf ./html
         mkdir html
@@ -79,7 +79,7 @@ jobs:
         python -m pip install markdown
         mkdir html/param-tooltips
         cd tools/parameter-tooltip-generator
-        bash generate-param-doc-tooltips.sh
+        python generate-param-doc-tooltips.py
         cd ../..
 
         cp -r ./sd-card/html/* ./html/
@@ -101,10 +101,10 @@ jobs:
     needs: build
 
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
 
     - name: Update generated-files cache on every commit
-      uses: actions/cache@v3.2.3
+      uses: actions/cache@v4
       with:
         path: |
           ./code/.pio/build/esp32cam/firmware.bin
@@ -115,7 +115,7 @@ jobs:
         restore-keys: generated-files # This matches above key as it is only used as a prefix. it the restores the nearest cache, see https://github.com/restore-keys:/blob/main/tips-and-workarounds.md#update-a-cache
 
     - name: Update update cache on every commit
-      uses: actions/cache@v3.2.3
+      uses: actions/cache@v4
       with:
         path: update
         key: update-${{ github.run_id }}
@@ -144,7 +144,7 @@ jobs:
         cp ./sd-card/config/*.tflite ./update/config/ 2>/dev/null || true
 
     - name: Upload update as update.zip artifact (Firmware + Web UI + CNN)
-      uses: actions/upload-artifact@v3
+      uses: actions/upload-artifact@v4
       with:
         name: "AI-on-the-edge-device__update__${{ steps.vars.outputs.branch }}_(${{ steps.vars.outputs.sha_short }})"
         path: ./update/*
@@ -164,10 +164,10 @@ jobs:
     needs: build
 
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
 
     - name: Update generated-files cache on every commit
-      uses: actions/cache@v3.2.3
+      uses: actions/cache@v4
       with:
         path: |
           ./code/.pio/build/esp32cam/firmware.bin
@@ -178,7 +178,7 @@ jobs:
         restore-keys: generated-files # This matches above key as it is only used as a prefix. it the restores the nearest cache, see https://github.com/restore-keys:/blob/main/tips-and-workarounds.md#update-a-cache
           
     - name: Update remote_setup cache on every commit
-      uses: actions/cache@v3.2.3
+      uses: actions/cache@v4
       with:
         path: remote_setup
         key: remote_setup-${{ github.run_id }}
@@ -205,7 +205,7 @@ jobs:
         cp ./sd-card/config/* ./remote_setup/config/ 2>/dev/null || true
 
     - name: Upload remote_setup as remote_setup.zip artifact (Firmware + Web UI + Config)
-      uses: actions/upload-artifact@v3
+      uses: actions/upload-artifact@v4
       with:
         name: "AI-on-the-edge-device__remote-setup__${{ steps.vars.outputs.branch }}_(${{ steps.vars.outputs.sha_short }})"
         path: ./remote_setup/*
@@ -220,10 +220,10 @@ jobs:
     needs: build
 
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
 
     - name: Update generated-files cache on every commit
-      uses: actions/cache@v3.2.3
+      uses: actions/cache@v4
       with:
         path: |
           ./code/.pio/build/esp32cam/firmware.bin
@@ -234,7 +234,7 @@ jobs:
         restore-keys: generated-files # This matches above key as it is only used as a prefix. it the restores the nearest cache, see https://github.com/restore-keys:/blob/main/tips-and-workarounds.md#update-a-cache
 
     - name: Update manual_setup cache on every commit
-      uses: actions/cache@v3.2.3
+      uses: actions/cache@v4
       with:
         path: manual_setup
         key: manual_setup-${{ github.run_id }}
@@ -263,7 +263,7 @@ jobs:
         cd ./manual_setup
   
     - name: Upload manual_setup.zip artifact (Firmware + Bootloader + Partitions + Web UI)
-      uses: actions/upload-artifact@v3
+      uses: actions/upload-artifact@v4
       with:
         name: "AI-on-the-edge-device__manual-setup__${{ steps.vars.outputs.branch }}_(${{ steps.vars.outputs.sha_short }})"
         path: ./manual_setup
@@ -284,24 +284,24 @@ jobs:
       id-token: write
 
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
     
     - name: Update update cache on every commit
-      uses: actions/cache@v3.2.3
+      uses: actions/cache@v4
       with:
         path: update
         key: update-${{ github.run_id }}
         restore-keys: update # This matches above key as it is only used as a prefix. it the restores the nearest cache, see https://github.com/restore-keys:/blob/main/tips-and-workarounds.md#update-a-cache
     
     - name: Update remote_setup cache on every commit
-      uses: actions/cache@v3.2.3
+      uses: actions/cache@v4
       with:
         path: remote_setup
         key: remote_setup-${{ github.run_id }}
         restore-keys: remote_setup # This matches above key as it is only used as a prefix. it the restores the nearest cache, see https://github.com/restore-keys:/blob/main/tips-and-workarounds.md#update-a-cache
     
     - name: Update manual_setup cache on every commit
-      uses: actions/cache@v3.2.3
+      uses: actions/cache@v4
       with:
         path: manual_setup
         key: manual_setup-${{ github.run_id }}
@@ -396,7 +396,7 @@ jobs:
 
     steps:
     - name: Checkout
-      uses: actions/checkout@v3
+      uses: actions/checkout@v4
       
     - name: Get version of last release
       id: last_release
@@ -410,20 +410,21 @@ jobs:
       run: |
         echo "Updating Web installer to use firmware from ${{ steps.last_release.outputs.tag_name }}..."
         rm -f docs/binary/firmware.bin
-        wget https://github.com/jomjol/AI-on-the-edge-device/releases/download/${{ steps.last_release.outputs.tag_name }}/AI-on-the-edge-device__update__${{ steps.last_release.outputs.tag_name }}.zip
+        wget ${{ github.server_url }}/${{ github.repository }}/releases/download/${{ steps.last_release.outputs.tag_name }}/AI-on-the-edge-device__update__${{ steps.last_release.outputs.tag_name }}.zip
         unzip AI-on-the-edge-device__update__${{ steps.last_release.outputs.tag_name }}.zip
         cp -f firmware.bin docs/binary/firmware.bin
         echo "Updating index and manifest file..."
         sed -i 's/$VERSION/${{ steps.last_release.outputs.tag_name }}/g' docs/index.html
         sed -i 's/$VERSION/${{ steps.last_release.outputs.tag_name }}/g' docs/manifest.json
+
     - name: Setup Pages
-      uses: actions/configure-pages@v2
+      uses: actions/configure-pages@v4
 
     - name: Upload artifact
-      uses: actions/upload-pages-artifact@v1
+      uses: actions/upload-pages-artifact@v2
       with:
         path: 'docs'
 
     - name: Deploy to GitHub Pages
       id: deployment
-      uses: actions/deploy-pages@v1   
+      uses: actions/deploy-pages@v3  # Note: v4 does not work!

+ 0 - 0
.github/workflows/clear_cache.yml → .github/workflows/clear_cache.yaml


+ 0 - 0
.github/workflows/manual-update-webinstaller.yml → .github/workflows/manual-update-webinstaller.yaml


+ 5 - 4
.github/workflows/reply-bot.yml → .github/workflows/reply-bot.yaml

@@ -1,5 +1,5 @@
 # Reply Bot
-# It uses the configuration in .github/label-commenter-config.yml
+# It uses the configuration in .github/label-commenter-config.yaml
 # See https://github.com/peaceiris/actions-label-commenter
 
 name: Reply-Bot
@@ -25,7 +25,7 @@ jobs:
       
       ####################################################################
       ## Remove labels again (issues only)
-      ## Make sure to also add the reply message to .github/label-commenter-config.yml!
+      ## Make sure to also add the reply message to .github/label-commenter-config.yaml!
       ## This currently seems no longer to work due to changes on the actions-cool/issues-helper!
       ####################################################################
 #      - name: Remove 'Logfile' label again (issues only)
@@ -74,6 +74,7 @@ jobs:
       ## Write the response
       ####################################################################
       - name: Write Response
-        uses: peaceiris/actions-label-commenter@c2d00660c86f2b9ed0fb35b372c451558eba85b3
+        uses: peaceiris/actions-label-commenter@v1
         with:
-          repo-token: "${{ secrets.GITHUB_TOKEN }}"
+          github_token: "${{ secrets.GITHUB_TOKEN }}"
+          config_file: .github/label-commenter-config.yaml

+ 57 - 15
Changelog.md

@@ -1,8 +1,60 @@
-## [unreleased] - 2023-12-21
+## [update] - 2024-03-30
 
-### Changes
+For a full list of changes see [Full list of changes](https://github.com/jomjol/AI-on-the-edge-device/compare/v15.6.0...v15.7.0)
+
+#### Core Changes
+
+- New tflite-Model for Analog (v13.0.0)
+- New tflite-Model for Digital Hybrid (v7.0.0)
+
+#### Bug Fixes
+
+- tbd
+
+## [15.7.0] - 2024-02-17
+
+For a full list of changes see [Full list of changes](https://github.com/jomjol/AI-on-the-edge-device/compare/v15.6.0...v15.7.0)
+
+#### Core Changes
+- Added new camera settings (See `Settings > Alignment > Reference Image and Camera Settings`). You might need to re-create the reference image and alignment marks. Note worthy:
+  - You can now crop the image
+  - Support to configure sharpness, grayscale, negatoive and exposure
+- Enhanced various WebUI pages with better explanations and usability
+- Add Firmware Version to MQTT
+
+#### Bug Fixes
+- Reverted "Implemented late analog / digital transition [#2778](https://github.com/jomjol/AI-on-the-edge-device/pull/2778) (introduced in `v15.5`) as is seems to cause issues for many users.
+
+
+## [15.6.0] - 2024-02-09
+
+For a full list of changes see [Full list of changes](https://github.com/jomjol/AI-on-the-edge-device/compare/v15.5.0...v15.6.0)
+
+#### Fixed
+
+* Fixed issues with the SD-Card initialization
+
+## [15.5.0] - 2024-02-02
+
+For a full list of changes see [Full list of changes](https://github.com/jomjol/AI-on-the-edge-device/compare/v15.4.0...v15.5.0)
+
+#### Changed
+
+ - Update PlattformIO to v6.5.0, which means esp-idf to v5.1
+ - Enhance busy notification
+ - Implemented late analog / digital transition
+
+#### Fixed
+
+* ATA-TRIM: workaround for old SD-cards with no trim function to work with esp-idf v5.x
+* InfluxDB: Modified the time conversions to be more stable (UTC vs. local time shifts)
+* Fix negatives on extended resolution false
+* Show chip infos on info page
+* Fix memory leaks in tflite integration
 
-For a full list of changes see [Full list of changes](https://github.com/jomjol/AI-on-the-edge-device/compare/rolling...v15.3.0)
+## [15.4.0] - 2023-12-22
+
+For a full list of changes see [Full list of changes](https://github.com/jomjol/AI-on-the-edge-device/compare/v15.3.0...v15.4.0)
 
 #### Changed
 
@@ -18,6 +70,8 @@ For a full list of changes see [Full list of changes](https://github.com/jomjol/
    - dig-class100-0167_s2_q.tflite
    - dig-class11_1700_s2.tflite
    - ana-cont_1208_s2_q.tflite
+  
+ - Added config entries for MQTT TLS
 
 
 #### Fixed
@@ -27,13 +81,10 @@ For a full list of changes see [Full list of changes](https://github.com/jomjol/
 * Minor html response bugfix 
 
  - Memory leakage (MQTT)
-
    
 
 ## [15.3.0] - 2023-07-22
 
-### Changes
-
 For a full list of changes see [Full list of changes](https://github.com/jomjol/AI-on-the-edge-device/compare/v15.3.0...v15.2.4)
 
 #### Changed
@@ -43,13 +94,8 @@ For a full list of changes see [Full list of changes](https://github.com/jomjol/
    - ana-cont_1207_s2_q.tflite
    - dig-cont_0620_s3_q.tflite
 
-
-
-
 ## [15.2.4] - 2023-05-02
 
-### Changes
-
 For a full list of changes see [Full list of changes](https://github.com/jomjol/AI-on-the-edge-device/compare/v15.2.1...v15.2.4)
 
 #### Changed
@@ -73,8 +119,6 @@ For a full list of changes see [Full list of changes](https://github.com/jomjol/
 
 ## [15.2.0] - 2023-04-23
 
-### Changes
-
 For a full list of changes see [Full list of changes](https://github.com/jomjol/AI-on-the-edge-device/compare/v15.1.1...v15.2.0)
 
 #### Added
@@ -104,8 +148,6 @@ For a full list of changes see [Full list of changes](https://github.com/jomjol/
 
 ## [15.1.1] - 2023-03-23
 
-### Changes
-
 For a full list of changes see [Full list of changes](https://github.com/jomjol/AI-on-the-edge-device/compare/v15.1.0...v15.1.1)
 
 #### Added

+ 3 - 0
code/README.md

@@ -69,3 +69,6 @@ pio device monitor -p /dev/ttyUSB0
 - `pio run --target erase` to erase the flash
 - `pio run --target upload` this will upload the `bootloader.bin, partitions.bin,firmware.bin` from the `code/.pio/build/esp32cam/` folder. 
 - `pio device monitor` to observe the logs via uart
+
+# Update Parameters
+If you create or rename a parameter, make sure to update its documentation in `../param-docs/parameter-pages`! Check the `../param-docs/README.md` for more information.

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 699 - 421
code/components/jomjol_controlcamera/ClassControllCamera.cpp


+ 93 - 42
code/components/jomjol_controlcamera/ClassControllCamera.h

@@ -15,49 +15,100 @@
 #include "CImageBasis.h"
 #include "../../include/defines.h"
 
-class CCamera {
-    protected:
-        int ActualQuality;
-        framesize_t ActualResolution;
-        int brightness, contrast, saturation;
-        bool isFixedExposure;
-        int waitbeforepicture_org;
-        int led_intensity = 4095;
-
-        void ledc_init(void);
-        bool CameraInitSuccessful = false;
-        bool demoMode = false;
-
-        bool loadNextDemoImage(camera_fb_t *fb);
-        long GetFileSize(std::string filename);
-
-    public:
-        int image_height, image_width;
-        
-        CCamera();
-        esp_err_t InitCam();
-
-        void LightOnOff(bool status);
-        void LEDOnOff(bool status);
-        esp_err_t CaptureToHTTP(httpd_req_t *req, int delay = 0);
-        esp_err_t CaptureToStream(httpd_req_t *req, bool FlashlightOn);
-        void SetQualitySize(int qual, framesize_t resol);
-        bool SetBrightnessContrastSaturation(int _brightness, int _contrast, int _saturation);
-        void GetCameraParameter(httpd_req_t *req, int &qual, framesize_t &resol);
-        void SetLEDIntensity(float _intrel);
-        bool testCamera(void);
-        void EnableAutoExposure(int flash_duration);
-        bool getCameraInitSuccessful();
-        void useDemoMode(void);
-       
-
-        framesize_t TextToFramesize(const char * text);
-
-        esp_err_t CaptureToFile(std::string nm, int delay = 0);
-        esp_err_t CaptureToBasisImage(CImageBasis *_Image, int delay = 0);
-};
+typedef enum
+{
+    OV2640_MODE_UXGA,
+    OV2640_MODE_SVGA,
+    OV2640_MODE_CIF
+} ov2640_sensor_mode_t;
 
+typedef struct
+{
+    framesize_t ImageFrameSize = FRAMESIZE_VGA; // 0 - 10
+    gainceiling_t ImageGainceiling;             // Image gain (GAINCEILING_x2, x4, x8, x16, x32, x64 or x128)
 
-extern CCamera Camera;
+    int ImageQuality;    // 0 - 63
+    int ImageBrightness; // (-2 to 2) - set brightness
+    int ImageContrast;   //-2 - 2
+    int ImageSaturation; //-2 - 2
+    int ImageSharpness;  //-2 - 2
+    bool ImageAutoSharpness;
+    int ImageSpecialEffect; // 0 - 6
+    int ImageWbMode;        // 0 to 4 - if awb_gain enabled (0 - Auto, 1 - Sunny, 2 - Cloudy, 3 - Office, 4 - Home)
+    int ImageAwb;           // white balance enable (0 or 1)
+    int ImageAwbGain;       // Auto White Balance enable (0 or 1)
+    int ImageAec;           // auto exposure off (1 or 0)
+    int ImageAec2;          // automatic exposure sensor  (0 or 1)
+    int ImageAeLevel;       // auto exposure levels (-2 to 2)
+    int ImageAecValue;      // set exposure manually  (0-1200)
+    int ImageAgc;           // auto gain off (1 or 0)
+    int ImageAgcGain;       // set gain manually (0 - 30)
+    int ImageBpc;           // black pixel correction
+    int ImageWpc;           // white pixel correction
+    int ImageRawGma;        // (1 or 0)
+    int ImageLenc;          // lens correction (1 or 0)
+    int ImageHmirror;       // (0 or 1) flip horizontally
+    int ImageVflip;         // Invert image (0 or 1)
+    int ImageDcw;           // downsize enable (1 or 0)
+
+    int ImageWidth;
+    int ImageHeight;
+
+    int ImageLedIntensity;
+
+    bool ImageZoomEnabled;
+    int ImageZoomMode;
+    int ImageZoomOffsetX;
+    int ImageZoomOffsetY;
+    int ImageZoomSize;
+
+    int WaitBeforePicture;
+    bool isImageSize;
+
+    bool CameraInitSuccessful;
+    bool changedCameraSettings;
+    bool DemoMode;
+    bool SaveAllFiles;
+} camera_controll_config_temp_t;
+
+extern camera_controll_config_temp_t CCstatus;
 
+class CCamera
+{
+protected:
+    void ledc_init(void);
+    bool loadNextDemoImage(camera_fb_t *fb);
+    long GetFileSize(std::string filename);
+    void SetCamWindow(sensor_t *s, int resolution, int xOffset, int yOffset, int xTotal, int yTotal, int xOutput, int yOutput);
+    void SetImageWidthHeightFromResolution(framesize_t resol);
+
+public:
+    CCamera(void);
+    esp_err_t InitCam(void);
+	
+    void LightOnOff(bool status);
+    void LEDOnOff(bool status);
+
+    esp_err_t setSensorDatenFromCCstatus(void);
+    esp_err_t getSensorDatenToCCstatus(void);
+
+    esp_err_t CaptureToHTTP(httpd_req_t *req, int delay = 0);
+    esp_err_t CaptureToStream(httpd_req_t *req, bool FlashlightOn);
+
+    void SetQualityZoomSize(int qual, framesize_t resol, bool zoomEnabled, int zoomOffsetX, int zoomOffsetY, int imageSize);
+    void SetZoomSize(bool zoomEnabled, int zoomOffsetX, int zoomOffsetY, int imageSize);
+    void SetCamSharpness(bool _autoSharpnessEnabled, int _sharpnessLevel);
+    
+	void SetLEDIntensity(float _intrel);
+    bool testCamera(void);
+    bool getCameraInitSuccessful(void);
+    void useDemoMode(void);
+
+    framesize_t TextToFramesize(const char *text);
+	
+    esp_err_t CaptureToFile(std::string nm, int delay = 0);
+    esp_err_t CaptureToBasisImage(CImageBasis *_Image, int delay = 0);
+};
+
+extern CCamera Camera;
 #endif

+ 152 - 0
code/components/jomjol_controlcamera/ov2640_sharpness.cpp

@@ -0,0 +1,152 @@
+#include <stdint.h>
+#include "esp_camera.h"
+#include "ov2640_sharpness.h"
+
+
+const static uint8_t OV2640_SHARPNESS_AUTO[]=
+{
+	//reg, val, mask
+    0xFF, 0x00, 0xFF,
+    0x92, 0x01, 0xFF,
+    0x93, 0x20, 0x20,
+    0x00, 0x00, 0x00
+};
+
+const static uint8_t OV2640_SHARPNESS_MANUAL[]=
+{
+	//reg, val, mask
+    0xFF, 0x00, 0xFF,
+    0x92, 0x01, 0xFF,
+    0x93, 0x00, 0x20,
+    0x00, 0x00, 0x00
+};
+
+const static uint8_t OV2640_SHARPNESS_LEVEL0[]=
+{
+	//reg, val, mask
+    0xFF, 0x00, 0xFF,
+    0x92, 0x01, 0xFF,
+    0x93, 0xC0, 0x1F,
+    0x00, 0x00, 0x00
+};
+const static uint8_t OV2640_SHARPNESS_LEVEL1[]=
+{
+	//reg, val, mask
+    0xFF, 0x00, 0xFF,
+    0x92, 0x01, 0xFF,
+    0x93, 0xC1, 0x1F,
+    0x00, 0x00, 0x00
+};
+const static uint8_t OV2640_SHARPNESS_LEVEL2[]=
+{
+	//reg, val, mask
+    0xFF, 0x00, 0xFF,
+    0x92, 0x01, 0xFF,
+    0x93, 0xC2, 0x1F,
+    0x00, 0x00, 0x00
+};
+const static uint8_t OV2640_SHARPNESS_LEVEL3[]=
+{
+	//reg, val, mask
+    0xFF, 0x00, 0xFF,
+    0x92, 0x01, 0xFF,
+    0x93, 0xC4, 0x1F,
+    0x00, 0x00, 0x00
+};
+const static uint8_t OV2640_SHARPNESS_LEVEL4[]=
+{
+	//reg, val, mask
+    0xFF, 0x00, 0xFF,
+    0x92, 0x01, 0xFF,
+    0x93, 0xC8, 0x1F,
+    0x00, 0x00, 0x00
+};
+const static uint8_t OV2640_SHARPNESS_LEVEL5[]=
+{
+	//reg, val, mask
+    0xFF, 0x00, 0xFF,
+    0x92, 0x01, 0xFF,
+    0x93, 0xD0, 0x1F,
+    0x00, 0x00, 0x00
+};
+const static uint8_t OV2640_SHARPNESS_LEVEL6[]=
+{
+	//reg, val, mask
+    0xFF, 0x00, 0xFF,
+    0x92, 0x01, 0xFF,
+    0x93, 0xDF, 0x1F,
+    0x00, 0x00, 0x00
+};
+
+const static uint8_t *OV2640_SETTING_SHARPNESS[]=
+{
+    OV2640_SHARPNESS_LEVEL0, // -3 sharpness
+    OV2640_SHARPNESS_LEVEL1,
+    OV2640_SHARPNESS_LEVEL2,
+    OV2640_SHARPNESS_LEVEL3,
+    OV2640_SHARPNESS_LEVEL4,
+    OV2640_SHARPNESS_LEVEL5,
+    OV2640_SHARPNESS_LEVEL6  // +3 sharpness
+};
+
+#define OV2640_MAXLEVEL_SHARPNESS 6
+
+
+static int table_mask_write(sensor_t *sensor, const uint8_t* ptab)
+{
+    uint8_t address;
+    uint8_t value;
+    uint8_t orgval;
+    uint8_t mask;
+    const uint8_t *pdata = ptab;
+
+    if (pdata == NULL)
+	{
+        return -1;
+	}
+
+    while (1)
+    {   
+        address = *pdata++;
+        value = *pdata++;
+        mask = *pdata++;
+		
+        if ((address == 0) && (value == 0) && (mask == 0))
+		{
+            break;
+		}
+		
+        sensor->set_reg(sensor, address, mask, value);
+    }   
+
+    return 0;
+}
+
+
+int ov2640_enable_auto_sharpness(sensor_t *sensor)
+{
+    table_mask_write(sensor, OV2640_SHARPNESS_AUTO);
+	
+    return 0;
+}
+
+
+int ov2640_set_sharpness(sensor_t *sensor, int sharpness)
+{
+	int sharpness_temp = 0;
+	
+    if (sharpness < -3)
+	{
+        sharpness_temp = -3;
+	}
+	
+    if (sharpness > OV2640_MAXLEVEL_SHARPNESS - 3)
+	{
+        sharpness_temp = OV2640_MAXLEVEL_SHARPNESS - 3;
+	}	
+
+    table_mask_write(sensor, OV2640_SHARPNESS_MANUAL);
+    table_mask_write(sensor, OV2640_SETTING_SHARPNESS[sharpness_temp + 3]);
+	
+    return 0;
+}

+ 11 - 0
code/components/jomjol_controlcamera/ov2640_sharpness.h

@@ -0,0 +1,11 @@
+#pragma once
+
+#ifndef OV2640_SHARPNESS_H
+#define OV2640_SHARPNESS_H
+
+#include "esp_camera.h"
+
+int ov2640_enable_auto_sharpness(sensor_t *sensor);
+int ov2640_set_sharpness(sensor_t *sensor, int sharpness); // -3 to +3, -4 for auto-sharpness
+
+#endif

+ 142 - 133
code/components/jomjol_controlcamera/server_camera.cpp

@@ -5,6 +5,7 @@
 
 #include "esp_camera.h"
 #include "ClassControllCamera.h"
+#include "MainFlowControl.h"
 
 #include "ClassLogFile.h"
 #include "esp_log.h"
@@ -13,183 +14,183 @@
 
 static const char *TAG = "server_cam";
 
-
-void PowerResetCamera(){
-
-        ESP_LOGD(TAG, "Resetting camera by power down line");
-        gpio_config_t conf;
-        conf.intr_type = GPIO_INTR_DISABLE;
-        conf.pin_bit_mask = 1LL << GPIO_NUM_32;
-        conf.mode = GPIO_MODE_OUTPUT;
-        conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
-        conf.pull_up_en = GPIO_PULLUP_DISABLE;
-        gpio_config(&conf);
-
-        // carefull, logic is inverted compared to reset pin
-        gpio_set_level(GPIO_NUM_32, 1);
-        vTaskDelay(1000 / portTICK_PERIOD_MS);
-        gpio_set_level(GPIO_NUM_32, 0);
-        vTaskDelay(1000 / portTICK_PERIOD_MS);
+void PowerResetCamera()
+{
+    ESP_LOGD(TAG, "Resetting camera by power down line");
+    gpio_config_t conf;
+    conf.intr_type = GPIO_INTR_DISABLE;
+    conf.pin_bit_mask = 1LL << GPIO_NUM_32;
+    conf.mode = GPIO_MODE_OUTPUT;
+    conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+    conf.pull_up_en = GPIO_PULLUP_DISABLE;
+    gpio_config(&conf);
+
+    // carefull, logic is inverted compared to reset pin
+    gpio_set_level(GPIO_NUM_32, 1);
+    vTaskDelay(1000 / portTICK_PERIOD_MS);
+    gpio_set_level(GPIO_NUM_32, 0);
+    vTaskDelay(1000 / portTICK_PERIOD_MS);
 }
 
-
 esp_err_t handler_lightOn(httpd_req_t *req)
 {
-    #ifdef DEBUG_DETAIL_ON   
-        LogFile.WriteHeapInfo("handler_lightOn - Start");
-        ESP_LOGD(TAG, "handler_lightOn uri: %s", req->uri);
-    #endif
+#ifdef DEBUG_DETAIL_ON
+    LogFile.WriteHeapInfo("handler_lightOn - Start");
+    ESP_LOGD(TAG, "handler_lightOn uri: %s", req->uri);
+#endif
 
-    if (Camera.getCameraInitSuccessful()) 
+    if (Camera.getCameraInitSuccessful())
     {
         Camera.LightOnOff(true);
-        const char* resp_str = (const char*) req->user_ctx;
+        const char *resp_str = (const char *)req->user_ctx;
         httpd_resp_send(req, resp_str, strlen(resp_str));
     }
-    else 
+    else
     {
         httpd_resp_send_err(req, HTTPD_403_FORBIDDEN, "Camera not initialized: REST API /lighton not available!");
         return ESP_ERR_NOT_FOUND;
     }
 
-    #ifdef DEBUG_DETAIL_ON   
-        LogFile.WriteHeapInfo("handler_lightOn - Done");
-    #endif
+#ifdef DEBUG_DETAIL_ON
+    LogFile.WriteHeapInfo("handler_lightOn - Done");
+#endif
 
     return ESP_OK;
 }
 
-
 esp_err_t handler_lightOff(httpd_req_t *req)
 {
-    #ifdef DEBUG_DETAIL_ON   
-        LogFile.WriteHeapInfo("handler_lightOff - Start");
-        ESP_LOGD(TAG, "handler_lightOff uri: %s", req->uri);
-    #endif
+#ifdef DEBUG_DETAIL_ON
+    LogFile.WriteHeapInfo("handler_lightOff - Start");
+    ESP_LOGD(TAG, "handler_lightOff uri: %s", req->uri);
+#endif
 
-    if (Camera.getCameraInitSuccessful()) 
+    if (Camera.getCameraInitSuccessful())
     {
         Camera.LightOnOff(false);
-        const char* resp_str = (const char*) req->user_ctx;
-        httpd_resp_send(req, resp_str, strlen(resp_str));       
+        const char *resp_str = (const char *)req->user_ctx;
+        httpd_resp_send(req, resp_str, strlen(resp_str));
     }
-    else 
+    else
     {
         httpd_resp_send_err(req, HTTPD_403_FORBIDDEN, "Camera not initialized: REST API /lightoff not available!");
         return ESP_ERR_NOT_FOUND;
     }
 
-    #ifdef DEBUG_DETAIL_ON   
-        LogFile.WriteHeapInfo("handler_lightOff - Done");
-    #endif
+#ifdef DEBUG_DETAIL_ON
+    LogFile.WriteHeapInfo("handler_lightOff - Done");
+#endif
 
     return ESP_OK;
 }
 
-
 esp_err_t handler_capture(httpd_req_t *req)
 {
-    #ifdef DEBUG_DETAIL_ON   
-        LogFile.WriteHeapInfo("handler_capture - Start");
-    #endif
+#ifdef DEBUG_DETAIL_ON
+    LogFile.WriteHeapInfo("handler_capture - Start");
+#endif
 
-    if (Camera.getCameraInitSuccessful()) 
+    if (Camera.getCameraInitSuccessful())
     {
-        int quality;
-        framesize_t res;
-
-        Camera.GetCameraParameter(req, quality, res);
-
-        #ifdef DEBUG_DETAIL_ON   
-            ESP_LOGD(TAG, "Size: %d, Quality: %d", res, quality);
-        #endif
+#ifdef DEBUG_DETAIL_ON
+        ESP_LOGD(TAG, "Size: %d, Quality: %d", res, quality);
+#endif
 
-        Camera.SetQualitySize(quality, res);
+        // wenn die Kameraeinstellungen durch Erstellen eines neuen Referenzbildes verändert wurden, müssen sie neu gesetzt werden
+        if (CFstatus.changedCameraSettings)
+        {
+            Camera.setSensorDatenFromCCstatus(); // CCstatus >>> Kamera
+            Camera.SetQualityZoomSize(CCstatus.ImageQuality, CCstatus.ImageFrameSize, CCstatus.ImageZoomEnabled, CCstatus.ImageZoomOffsetX, CCstatus.ImageZoomOffsetY, CCstatus.ImageZoomSize);
+            CFstatus.changedCameraSettings = false;
+        }
 
         esp_err_t result;
         result = Camera.CaptureToHTTP(req);
 
-        #ifdef DEBUG_DETAIL_ON   
-            LogFile.WriteHeapInfo("handler_capture - Done");
-        #endif
+#ifdef DEBUG_DETAIL_ON
+        LogFile.WriteHeapInfo("handler_capture - Done");
+#endif
 
         return result;
     }
-        else 
+    else
     {
         httpd_resp_send_err(req, HTTPD_403_FORBIDDEN, "Camera not initialized: REST API /capture not available!");
         return ESP_ERR_NOT_FOUND;
     }
 }
 
-
 esp_err_t handler_capture_with_light(httpd_req_t *req)
 {
-    #ifdef DEBUG_DETAIL_ON  
-        LogFile.WriteHeapInfo("handler_capture_with_light - Start");
-    #endif
-    
-    if (Camera.getCameraInitSuccessful()) 
+#ifdef DEBUG_DETAIL_ON
+    LogFile.WriteHeapInfo("handler_capture_with_light - Start");
+#endif
+
+    if (Camera.getCameraInitSuccessful())
     {
         char _query[100];
         char _delay[10];
-
-        int quality;
-        framesize_t res;    
         int delay = 2500;
 
         if (httpd_req_get_url_query_str(req, _query, 100) == ESP_OK)
         {
             ESP_LOGD(TAG, "Query: %s", _query);
+
             if (httpd_query_key_value(_query, "delay", _delay, 10) == ESP_OK)
             {
-                #ifdef DEBUG_DETAIL_ON   
-                    ESP_LOGD(TAG, "Delay: %s", _delay);
-                #endif        
+#ifdef DEBUG_DETAIL_ON
+                ESP_LOGD(TAG, "Delay: %s", _delay);
+#endif
                 delay = atoi(_delay);
 
                 if (delay < 0)
+                {
                     delay = 0;
+                }
             }
         }
 
-        Camera.GetCameraParameter(req, quality, res);
+#ifdef DEBUG_DETAIL_ON
+        ESP_LOGD(TAG, "Size: %d, Quality: %d", res, quality);
+#endif
 
-        #ifdef DEBUG_DETAIL_ON   
-            ESP_LOGD(TAG, "Size: %d, Quality: %d", res, quality);
-        #endif
+        // wenn die Kameraeinstellungen durch Erstellen eines neuen Referenzbildes verändert wurden, müssen sie neu gesetzt werden
+        if (CFstatus.changedCameraSettings)
+        {
+            Camera.setSensorDatenFromCCstatus(); // CCstatus >>> Kamera
+            Camera.SetQualityZoomSize(CCstatus.ImageQuality, CCstatus.ImageFrameSize, CCstatus.ImageZoomEnabled, CCstatus.ImageZoomOffsetX, CCstatus.ImageZoomOffsetY, CCstatus.ImageZoomSize);
+            CFstatus.changedCameraSettings = false;
+        }
 
-        Camera.SetQualitySize(quality, res);
         Camera.LightOnOff(true);
         const TickType_t xDelay = delay / portTICK_PERIOD_MS;
-        vTaskDelay( xDelay );
+        vTaskDelay(xDelay);
 
         esp_err_t result;
-        result = Camera.CaptureToHTTP(req);  
+        result = Camera.CaptureToHTTP(req);
 
         Camera.LightOnOff(false);
 
-        #ifdef DEBUG_DETAIL_ON   
-            LogFile.WriteHeapInfo("handler_capture_with_light - Done");
-        #endif
+#ifdef DEBUG_DETAIL_ON
+        LogFile.WriteHeapInfo("handler_capture_with_light - Done");
+#endif
 
         return result;
     }
-        else 
+    else
     {
         httpd_resp_send_err(req, HTTPD_403_FORBIDDEN, "Camera not initialized: REST API /capture_with_flashlight not available!");
         return ESP_ERR_NOT_FOUND;
     }
 }
 
-
 esp_err_t handler_capture_save_to_file(httpd_req_t *req)
 {
-    #ifdef DEBUG_DETAIL_ON   
-        LogFile.WriteHeapInfo("handler_capture_save_to_file - Start");
-    #endif
+#ifdef DEBUG_DETAIL_ON
+    LogFile.WriteHeapInfo("handler_capture_save_to_file - Start");
+#endif
 
-    if (Camera.getCameraInitSuccessful()) 
+    if (Camera.getCameraInitSuccessful())
     {
         char _query[100];
         char _delay[10];
@@ -197,94 +198,102 @@ esp_err_t handler_capture_save_to_file(httpd_req_t *req)
         char filename[100];
         std::string fn = "/sdcard/";
 
-
-        int quality;
-        framesize_t res;    
-
         if (httpd_req_get_url_query_str(req, _query, 100) == ESP_OK)
         {
             ESP_LOGD(TAG, "Query: %s", _query);
+
             if (httpd_query_key_value(_query, "filename", filename, 100) == ESP_OK)
             {
                 fn.append(filename);
-                #ifdef DEBUG_DETAIL_ON   
-                    ESP_LOGD(TAG, "Filename: %s", fn.c_str());
-                #endif
+#ifdef DEBUG_DETAIL_ON
+                ESP_LOGD(TAG, "Filename: %s", fn.c_str());
+#endif
             }
             else
+            {
                 fn.append("noname.jpg");
+            }
 
             if (httpd_query_key_value(_query, "delay", _delay, 10) == ESP_OK)
             {
-                #ifdef DEBUG_DETAIL_ON   
-                    ESP_LOGD(TAG, "Delay: %s", _delay);
-                #endif
+#ifdef DEBUG_DETAIL_ON
+                ESP_LOGD(TAG, "Delay: %s", _delay);
+#endif
                 delay = atoi(_delay);
 
                 if (delay < 0)
+                {
                     delay = 0;
+                }
             }
         }
         else
+        {
             fn.append("noname.jpg");
+        }
 
-        Camera.GetCameraParameter(req, quality, res);
-        #ifdef DEBUG_DETAIL_ON   
-            ESP_LOGD(TAG, "Size: %d, Quality: %d", res, quality);
-        #endif
-        Camera.SetQualitySize(quality, res);
+#ifdef DEBUG_DETAIL_ON
+        ESP_LOGD(TAG, "Size: %d, Quality: %d", res, quality);
+#endif
+
+        // wenn die Kameraeinstellungen durch Erstellen eines neuen Referenzbildes verändert wurden, müssen sie neu gesetzt werden
+        if (CFstatus.changedCameraSettings)
+        {
+            Camera.setSensorDatenFromCCstatus(); // CCstatus >>> Kamera
+            Camera.SetQualityZoomSize(CCstatus.ImageQuality, CCstatus.ImageFrameSize, CCstatus.ImageZoomEnabled, CCstatus.ImageZoomOffsetX, CCstatus.ImageZoomOffsetY, CCstatus.ImageZoomSize);
+            CFstatus.changedCameraSettings = false;
+        }
 
         esp_err_t result;
-        result = Camera.CaptureToFile(fn, delay);  
+        result = Camera.CaptureToFile(fn, delay);
 
-        const char* resp_str = (const char*) fn.c_str();
+        const char *resp_str = (const char *)fn.c_str();
         httpd_resp_send(req, resp_str, strlen(resp_str));
 
-        #ifdef DEBUG_DETAIL_ON   
-            LogFile.WriteHeapInfo("handler_capture_save_to_file - Done");
-        #endif
+#ifdef DEBUG_DETAIL_ON
+        LogFile.WriteHeapInfo("handler_capture_save_to_file - Done");
+#endif
 
         return result;
     }
-    else 
+    else
     {
         httpd_resp_send_err(req, HTTPD_403_FORBIDDEN, "Camera not initialized: REST API /save not available!");
         return ESP_ERR_NOT_FOUND;
     }
 }
 
-
 void register_server_camera_uri(httpd_handle_t server)
 {
-#ifdef DEBUG_DETAIL_ON   
+#ifdef DEBUG_DETAIL_ON
     ESP_LOGI(TAG, "server_part_camera - Registering URI handlers");
 #endif
 
-    httpd_uri_t camuri = { };
-    camuri.method    = HTTP_GET;
+    httpd_uri_t camuri = {};
+    camuri.method = HTTP_GET;
 
-    camuri.uri       = "/lighton";
-    camuri.handler   = handler_lightOn;
-    camuri.user_ctx  = (void*) "Light On";    
+    camuri.uri = "/lighton";
+    camuri.handler = handler_lightOn;
+    camuri.user_ctx = (void *)"Light On";
     httpd_register_uri_handler(server, &camuri);
 
-    camuri.uri       = "/lightoff";
-    camuri.handler   = handler_lightOff;
-    camuri.user_ctx  = (void*) "Light Off"; 
-    httpd_register_uri_handler(server, &camuri);    
-
-    camuri.uri       = "/capture";
-    camuri.handler   = handler_capture;
-    camuri.user_ctx  = NULL; 
-    httpd_register_uri_handler(server, &camuri);      
-
-    camuri.uri       = "/capture_with_flashlight";
-    camuri.handler   = handler_capture_with_light;
-    camuri.user_ctx  = NULL; 
-    httpd_register_uri_handler(server, &camuri);  
-
-    camuri.uri       = "/save";
-    camuri.handler   = handler_capture_save_to_file;
-    camuri.user_ctx  = NULL; 
-    httpd_register_uri_handler(server, &camuri);    
+    camuri.uri = "/lightoff";
+    camuri.handler = handler_lightOff;
+    camuri.user_ctx = (void *)"Light Off";
+    httpd_register_uri_handler(server, &camuri);
+
+    camuri.uri = "/capture";
+    camuri.handler = handler_capture;
+    camuri.user_ctx = NULL;
+    httpd_register_uri_handler(server, &camuri);
+
+    camuri.uri = "/capture_with_flashlight";
+    camuri.handler = handler_capture_with_light;
+    camuri.user_ctx = NULL;
+    httpd_register_uri_handler(server, &camuri);
+
+    camuri.uri = "/save";
+    camuri.handler = handler_capture_save_to_file;
+    camuri.user_ctx = NULL;
+    httpd_register_uri_handler(server, &camuri);
 }

+ 0 - 1
code/components/jomjol_controlcamera/server_camera.h

@@ -10,7 +10,6 @@
 //#include "ClassControllCamera.h"
 
 void register_server_camera_uri(httpd_handle_t server);
-
 void PowerResetCamera();
 
 #endif

+ 1 - 1
code/components/jomjol_fileserver_ota/miniz/miniz.h

@@ -153,7 +153,7 @@
 /*#define MINIZ_NO_MALLOC */
 
 #ifdef MINIZ_NO_INFLATE_APIS
-#define MINIZ_NO_ARCHIVE_APIS
+//#define MINIZ_NO_ARCHIVE_APIS
 #endif
 
 #ifdef MINIZ_NO_DEFLATE_APIS

+ 1 - 1
code/components/jomjol_fileserver_ota/server_file.cpp

@@ -547,7 +547,7 @@ static esp_err_t download_get_handler(httpd_req_t *req)
                 /* Get value of expected key from query string */
                 if (httpd_query_key_value(buf, "readonly", param, sizeof(param)) == ESP_OK) {
                     ESP_LOGI(TAG, "Found URL query parameter => readonly=%s", param);
-                    readonly = param && strcmp(param,"true")==0;
+                    readonly = (strcmp(param,"true") == 0);
                 }
             }
         }

+ 402 - 393
code/components/jomjol_flowcontroll/ClassFlowAlignment.cpp

@@ -1,393 +1,402 @@
-#include "ClassFlowAlignment.h"
-#include "ClassFlowTakeImage.h"
-#include "ClassFlow.h"
-#include "MainFlowControl.h"
-
-#include "CRotateImage.h"
-#include "esp_log.h"
-
-
-#include "ClassLogFile.h"
-#include "psram.h"
-#include "../../include/defines.h"
-
-
-static const char *TAG = "ALIGN";
-
-// #define DEBUG_DETAIL_ON  
-
-
-void ClassFlowAlignment::SetInitialParameter(void)
-{
-    initalrotate = 0;
-    anz_ref = 0;
-    initialmirror = false;
-    use_antialiasing = false;
-    initialflip = false;
-    SaveAllFiles = false;
-    namerawimage =  "/sdcard/img_tmp/raw.jpg";
-    FileStoreRefAlignment = "/sdcard/config/align.txt";
-    ListFlowControll = NULL;
-    AlignAndCutImage = NULL;
-    ImageBasis = NULL;
-    ImageTMP = NULL;
-    #ifdef ALGROI_LOAD_FROM_MEM_AS_JPG 
-    AlgROI = (ImageData*)malloc_psram_heap(std::string(TAG) + "->AlgROI", sizeof(ImageData), MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM);
-    #endif
-    previousElement = NULL;
-    disabled = false;
-    SAD_criteria = 0.05;
-}
-
-
-ClassFlowAlignment::ClassFlowAlignment(std::vector<ClassFlow*>* lfc)
-{
-    SetInitialParameter();
-    ListFlowControll = lfc;
-
-    for (int i = 0; i < ListFlowControll->size(); ++i)
-    {
-        if (((*ListFlowControll)[i])->name().compare("ClassFlowTakeImage") == 0)
-        {
-            ImageBasis = ((ClassFlowTakeImage*) (*ListFlowControll)[i])->rawImage;
-        }
-    }
-
-    if (!ImageBasis)            // the function take pictures does not exist --> must be created first ONLY FOR TEST PURPOSES
-    {
-        ESP_LOGD(TAG, "CImageBasis had to be created");
-        ImageBasis = new CImageBasis("ImageBasis", namerawimage);
-    }
-}
-
-
-bool ClassFlowAlignment::ReadParameter(FILE* pfile, string& aktparamgraph)
-{
-    std::vector<string> splitted;
-    int suchex = 40;
-    int suchey = 40;
-    int alg_algo = 0; //default=0; 1 =HIGHACCURACY; 2= FAST; 3= OFF //add disable aligment algo |01.2023
-
-
-    aktparamgraph = trim(aktparamgraph);
-
-    if (aktparamgraph.size() == 0)
-        if (!this->GetNextParagraph(pfile, aktparamgraph))
-            return false;
-
-    if (aktparamgraph.compare("[Alignment]") != 0)       //Paragraph does not fit Alignment
-        return false;
-
-    while (this->getNextLine(pfile, &aktparamgraph) && !this->isNewParagraph(aktparamgraph))
-    {
-        splitted = ZerlegeZeile(aktparamgraph);
-        if ((toUpper(splitted[0]) == "FLIPIMAGESIZE") && (splitted.size() > 1))
-        {
-            if (toUpper(splitted[1]) == "TRUE")
-                initialflip = true;
-        }
-        if ((toUpper(splitted[0]) == "INITIALMIRROR") && (splitted.size() > 1))
-        {
-            if (toUpper(splitted[1]) == "TRUE")
-                initialmirror = true;
-        }
-        if (((toUpper(splitted[0]) == "INITALROTATE") || (toUpper(splitted[0]) == "INITIALROTATE")) && (splitted.size() > 1))
-        {
-            this->initalrotate = std::stod(splitted[1]);
-        }
-        if ((toUpper(splitted[0]) == "SEARCHFIELDX") && (splitted.size() > 1))
-        {
-            suchex = std::stod(splitted[1]);
-        }   
-        if ((toUpper(splitted[0]) == "SEARCHFIELDY") && (splitted.size() > 1))
-        {
-            suchey = std::stod(splitted[1]);
-        }   
-        if ((toUpper(splitted[0]) == "ANTIALIASING") && (splitted.size() > 1))
-        {
-            if (toUpper(splitted[1]) == "TRUE")
-                use_antialiasing = true;
-        }   
-        if ((splitted.size() == 3) && (anz_ref < 2))
-        {
-            References[anz_ref].image_file = FormatFileName("/sdcard" + splitted[0]);
-            References[anz_ref].target_x = std::stod(splitted[1]);
-            References[anz_ref].target_y = std::stod(splitted[2]);
-            anz_ref++;
-        }
-
-        if ((toUpper(splitted[0]) == "SAVEALLFILES") && (splitted.size() > 1))
-        {
-            if (toUpper(splitted[1]) == "TRUE")
-                SaveAllFiles = true;
-        }
-        if ((toUpper(splitted[0]) == "ALIGNMENTALGO") && (splitted.size() > 1))
-        {
-            #ifdef DEBUG_DETAIL_ON
-                std::string zw2 = "Alignment mode selected: " + splitted[1];
-                LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, zw2);
-            #endif
-            if (toUpper(splitted[1]) == "HIGHACCURACY")
-                alg_algo = 1;
-            if (toUpper(splitted[1]) == "FAST")
-                alg_algo = 2;
-            if (toUpper(splitted[1]) == "OFF") //no align algo if set to 3 = off => no draw ref //add disable aligment algo |01.2023
-                alg_algo = 3;
-        }
-    }
-
-    for (int i = 0; i < anz_ref; ++i)
-    {
-        References[i].search_x = suchex;
-        References[i].search_y = suchey;
-        References[i].fastalg_SAD_criteria = SAD_criteria;
-        References[i].alignment_algo = alg_algo;
-        #ifdef DEBUG_DETAIL_ON
-            std::string zw2 = "Alignment mode written: " + std::to_string(alg_algo);
-            LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, zw2);
-        #endif
-    }
-
-    //no align algo if set to 3 = off => no draw ref //add disable aligment algo |01.2023
-    if(References[0].alignment_algo != 3){
-        LoadReferenceAlignmentValues();
-    }
-    
-    return true;
-
-}
-
-
-string ClassFlowAlignment::getHTMLSingleStep(string host)
-{
-    string result;
-
-    result =          "<p>Rotated Image: </p> <p><img src=\"" + host + "/img_tmp/rot.jpg\"></p>\n";
-    result = result + "<p>Found Alignment: </p> <p><img src=\"" + host + "/img_tmp/rot_roi.jpg\"></p>\n";
-    result = result + "<p>Aligned Image: </p> <p><img src=\"" + host + "/img_tmp/alg.jpg\"></p>\n";
-    return result;
-}
-
-
-bool ClassFlowAlignment::doFlow(string time) 
-{
-    #ifdef ALGROI_LOAD_FROM_MEM_AS_JPG
-        if (!AlgROI)  // AlgROI needs to be allocated before ImageTMP to avoid heap fragmentation
-        {
-            AlgROI = (ImageData*)heap_caps_realloc(AlgROI, sizeof(ImageData), MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM);     
-            if (!AlgROI) 
-            {
-                LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Can't allocate AlgROI");
-                LogFile.WriteHeapInfo("ClassFlowAlignment-doFlow");
-            }
-        }
-
-        if (AlgROI)
-        {
-            ImageBasis->writeToMemoryAsJPG((ImageData*)AlgROI, 90);
-        }
-    #endif
-
-    if (!ImageTMP) 
-    {
-        ImageTMP = new CImageBasis("tmpImage", ImageBasis); // Make sure the name does not get change, it is relevant for the PSRAM allocation!
-        if (!ImageTMP) 
-        {
-            LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Can't allocate tmpImage -> Exec this round aborted!");
-            LogFile.WriteHeapInfo("ClassFlowAlignment-doFlow");
-            return false;
-        }
-    }
-
-    delete AlignAndCutImage;
-    AlignAndCutImage = new CAlignAndCutImage("AlignAndCutImage", ImageBasis, ImageTMP);
-    if (!AlignAndCutImage) 
-    {
-        LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Can't allocate AlignAndCutImage -> Exec this round aborted!");
-        LogFile.WriteHeapInfo("ClassFlowAlignment-doFlow");
-        return false;
-    }
-
-    CRotateImage rt("rawImage", AlignAndCutImage, ImageTMP, initialflip);
-    if (initialflip)
-    {
-        int _zw = ImageBasis->height;
-        ImageBasis->height = ImageBasis->width;
-        ImageBasis->width = _zw;
-
-        _zw = ImageTMP->width;
-        ImageTMP->width = ImageTMP->height;
-        ImageTMP->height = _zw;
-    }
-
-    if (initialmirror)
-    {
-        ESP_LOGD(TAG, "do mirror");
-        rt.Mirror();
-        
-        if (SaveAllFiles)
-            AlignAndCutImage->SaveToFile(FormatFileName("/sdcard/img_tmp/mirror.jpg"));
-    }
- 
-    if ((initalrotate != 0) || initialflip)
-    {
-        if (use_antialiasing)
-            rt.RotateAntiAliasing(initalrotate);
-        else
-            rt.Rotate(initalrotate);
-        
-        if (SaveAllFiles)
-            AlignAndCutImage->SaveToFile(FormatFileName("/sdcard/img_tmp/rot.jpg"));
-    }
-
-
-        //no align algo if set to 3 = off //add disable aligment algo |01.2023
-        if(References[0].alignment_algo != 3){
-            if (!AlignAndCutImage->Align(&References[0], &References[1])) 
-            {
-                SaveReferenceAlignmentValues();
-            }
-        }// no align
-
-
-    #ifdef ALGROI_LOAD_FROM_MEM_AS_JPG
-        if (AlgROI) {
-            //no align algo if set to 3 = off => no draw ref //add disable aligment algo |01.2023
-            if(References[0].alignment_algo != 3){
-                DrawRef(ImageTMP);
-            }
-            flowctrl.DigitalDrawROI(ImageTMP);
-            flowctrl.AnalogDrawROI(ImageTMP);
-            ImageTMP->writeToMemoryAsJPG((ImageData*)AlgROI, 90);
-        }
-    #endif
-    
-    if (SaveAllFiles)
-    {
-        AlignAndCutImage->SaveToFile(FormatFileName("/sdcard/img_tmp/alg.jpg"));
-        ImageTMP->SaveToFile(FormatFileName("/sdcard/img_tmp/alg_roi.jpg"));
-    }
-
-    // must be deleted to have memory space for loading tflite
-    delete ImageTMP;
-    ImageTMP = NULL;
-
-    //no align algo if set to 3 = off => no draw ref //add disable aligment algo |01.2023
-    if(References[0].alignment_algo != 3){
-        LoadReferenceAlignmentValues();
-    }
-
-    return true;
-}
-
-
-void ClassFlowAlignment::SaveReferenceAlignmentValues()
-{
-    FILE* pFile;
-    std::string zwtime, zwvalue;
-
-    pFile = fopen(FileStoreRefAlignment.c_str(), "w");
-
-    if (strlen(zwtime.c_str()) == 0)
-    {
-        time_t rawtime;
-        struct tm* timeinfo;
-        char buffer[80];
-
-        time(&rawtime);
-        timeinfo = localtime(&rawtime);
-
-        strftime(buffer, 80, "%Y-%m-%dT%H:%M:%S", timeinfo);
-        zwtime = std::string(buffer);
-    }
-
-    fputs(zwtime.c_str(), pFile);
-    fputs("\n", pFile);
-
-    zwvalue = std::to_string(References[0].fastalg_x) + "\t" + std::to_string(References[0].fastalg_y);
-    zwvalue = zwvalue + "\t" +std::to_string(References[0].fastalg_SAD)+ "\t" +std::to_string(References[0].fastalg_min); 
-    zwvalue = zwvalue + "\t" +std::to_string(References[0].fastalg_max)+ "\t" +std::to_string(References[0].fastalg_avg); 
-    fputs(zwvalue.c_str(), pFile);
-    fputs("\n", pFile);
-
-    zwvalue = std::to_string(References[1].fastalg_x) + "\t" + std::to_string(References[1].fastalg_y);
-    zwvalue = zwvalue + "\t" +std::to_string(References[1].fastalg_SAD)+ "\t" +std::to_string(References[1].fastalg_min); 
-    zwvalue = zwvalue + "\t" +std::to_string(References[1].fastalg_max)+ "\t" +std::to_string(References[1].fastalg_avg); 
-    fputs(zwvalue.c_str(), pFile);
-    fputs("\n", pFile);
-
-    fclose(pFile);
-}
-
-
-bool ClassFlowAlignment::LoadReferenceAlignmentValues(void)
-{
-    FILE* pFile;
-    char zw[1024];
-    string zwvalue;
-    std::vector<string> splitted;  
-
-
-    pFile = fopen(FileStoreRefAlignment.c_str(), "r");
-    if (pFile == NULL)
-        return false;
-
-    fgets(zw, 1024, pFile);
-    ESP_LOGD(TAG, "%s", zw);
-
-    fgets(zw, 1024, pFile);
-    splitted = ZerlegeZeile(std::string(zw), " \t");
-    if (splitted.size() < 6)
-    {
-        fclose(pFile);
-        return false;
-    }
-
-    References[0].fastalg_x = stoi(splitted[0]);
-    References[0].fastalg_y = stoi(splitted[1]);
-    References[0].fastalg_SAD = stof(splitted[2]);
-    References[0].fastalg_min = stoi(splitted[3]);
-    References[0].fastalg_max = stoi(splitted[4]);
-    References[0].fastalg_avg = stof(splitted[5]);
-
-    fgets(zw, 1024, pFile);
-    splitted = ZerlegeZeile(std::string(zw));
-    if (splitted.size() < 6)
-    {
-        fclose(pFile);
-        return false;
-    }
-
-    References[1].fastalg_x = stoi(splitted[0]);
-    References[1].fastalg_y = stoi(splitted[1]);
-    References[1].fastalg_SAD = stof(splitted[2]);
-    References[1].fastalg_min = stoi(splitted[3]);
-    References[1].fastalg_max = stoi(splitted[4]);
-    References[1].fastalg_avg = stof(splitted[5]);
-
-    fclose(pFile);
-
-
-    /*#ifdef DEBUG_DETAIL_ON
-        std::string _zw = "\tLoadReferences[0]\tx,y:\t" + std::to_string(References[0].fastalg_x) + "\t" + std::to_string(References[0].fastalg_x);
-        _zw = _zw + "\tSAD, min, max, avg:\t" + std::to_string(References[0].fastalg_SAD) + "\t" + std::to_string(References[0].fastalg_min);
-        _zw = _zw + "\t" + std::to_string(References[0].fastalg_max) + "\t" + std::to_string(References[0].fastalg_avg);
-        LogFile.WriteToDedicatedFile("/sdcard/alignment.txt", _zw);
-        _zw = "\tLoadReferences[1]\tx,y:\t" + std::to_string(References[1].fastalg_x) + "\t" + std::to_string(References[1].fastalg_x);
-        _zw = _zw + "\tSAD, min, max, avg:\t" + std::to_string(References[1].fastalg_SAD) + "\t" + std::to_string(References[1].fastalg_min);
-        _zw = _zw + "\t" + std::to_string(References[1].fastalg_max) + "\t" + std::to_string(References[1].fastalg_avg);
-        LogFile.WriteToDedicatedFile("/sdcard/alignment.txt", _zw);
-    #endif*/
-
-    return true;
-}
-
-
-void ClassFlowAlignment::DrawRef(CImageBasis *_zw)
-{
-    if (_zw->ImageOkay()) 
-    {
-        _zw->drawRect(References[0].target_x, References[0].target_y, References[0].width, References[0].height, 255, 0, 0, 2);
-        _zw->drawRect(References[1].target_x, References[1].target_y, References[1].width, References[1].height, 255, 0, 0, 2);
-    }
-}
+#include "ClassFlowAlignment.h"
+#include "ClassFlowTakeImage.h"
+#include "ClassFlow.h"
+#include "MainFlowControl.h"
+
+#include "CRotateImage.h"
+#include "esp_log.h"
+
+#include "ClassLogFile.h"
+#include "psram.h"
+#include "../../include/defines.h"
+
+static const char *TAG = "ALIGN";
+
+// #define DEBUG_DETAIL_ON
+
+void ClassFlowAlignment::SetInitialParameter(void)
+{
+    initialrotate = 0;
+    anz_ref = 0;
+    use_antialiasing = false;
+    initialflip = false;
+    SaveAllFiles = false;
+    namerawimage = "/sdcard/img_tmp/raw.jpg";
+    FileStoreRefAlignment = "/sdcard/config/align.txt";
+    ListFlowControll = NULL;
+    AlignAndCutImage = NULL;
+    ImageBasis = NULL;
+    ImageTMP = NULL;
+#ifdef ALGROI_LOAD_FROM_MEM_AS_JPG
+    AlgROI = (ImageData *)malloc_psram_heap(std::string(TAG) + "->AlgROI", sizeof(ImageData), MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM);
+#endif
+    previousElement = NULL;
+    disabled = false;
+    SAD_criteria = 0.05;
+}
+
+ClassFlowAlignment::ClassFlowAlignment(std::vector<ClassFlow *> *lfc)
+{
+    SetInitialParameter();
+    ListFlowControll = lfc;
+
+    for (int i = 0; i < ListFlowControll->size(); ++i)
+    {
+        if (((*ListFlowControll)[i])->name().compare("ClassFlowTakeImage") == 0)
+        {
+            ImageBasis = ((ClassFlowTakeImage *)(*ListFlowControll)[i])->rawImage;
+        }
+    }
+
+    if (!ImageBasis) // the function take pictures does not exist --> must be created first ONLY FOR TEST PURPOSES
+    {
+        ESP_LOGD(TAG, "CImageBasis had to be created");
+        ImageBasis = new CImageBasis("ImageBasis", namerawimage);
+    }
+}
+
+bool ClassFlowAlignment::ReadParameter(FILE *pfile, string &aktparamgraph)
+{
+    std::vector<string> splitted;
+    int suchex = 40;
+    int suchey = 40;
+    int alg_algo = 0; // default=0; 1 =HIGHACCURACY; 2= FAST; 3= OFF //add disable aligment algo |01.2023
+
+    aktparamgraph = trim(aktparamgraph);
+
+    if (aktparamgraph.size() == 0)
+    {
+        if (!this->GetNextParagraph(pfile, aktparamgraph))
+        {
+            return false;
+        }
+    }
+
+    if (aktparamgraph.compare("[Alignment]") != 0)
+    {
+        // Paragraph does not fit Alignment
+        return false;
+    }
+
+    while (this->getNextLine(pfile, &aktparamgraph) && !this->isNewParagraph(aktparamgraph))
+    {
+        splitted = ZerlegeZeile(aktparamgraph);
+
+        if ((toUpper(splitted[0]) == "FLIPIMAGESIZE") && (splitted.size() > 1))
+        {
+            if (toUpper(splitted[1]) == "TRUE")
+            {
+                initialflip = true;
+            }
+        }
+        else if (((toUpper(splitted[0]) == "initialrotate") || (toUpper(splitted[0]) == "INITIALROTATE")) && (splitted.size() > 1))
+        {
+            this->initialrotate = std::stod(splitted[1]);
+        }
+        else if ((toUpper(splitted[0]) == "SEARCHFIELDX") && (splitted.size() > 1))
+        {
+            suchex = std::stod(splitted[1]);
+        }
+        else if ((toUpper(splitted[0]) == "SEARCHFIELDY") && (splitted.size() > 1))
+        {
+            suchey = std::stod(splitted[1]);
+        }
+        else if ((toUpper(splitted[0]) == "ANTIALIASING") && (splitted.size() > 1))
+        {
+            if (toUpper(splitted[1]) == "TRUE")
+            {
+                use_antialiasing = true;
+            }
+        }
+        else if ((splitted.size() == 3) && (anz_ref < 2))
+        {
+            References[anz_ref].image_file = FormatFileName("/sdcard" + splitted[0]);
+            References[anz_ref].target_x = std::stod(splitted[1]);
+            References[anz_ref].target_y = std::stod(splitted[2]);
+            anz_ref++;
+        }
+
+        else if ((toUpper(splitted[0]) == "SAVEALLFILES") && (splitted.size() > 1))
+        {
+            if (toUpper(splitted[1]) == "TRUE")
+            {
+                SaveAllFiles = true;
+            }
+        }
+        else if ((toUpper(splitted[0]) == "ALIGNMENTALGO") && (splitted.size() > 1))
+        {
+#ifdef DEBUG_DETAIL_ON
+            std::string zw2 = "Alignment mode selected: " + splitted[1];
+            LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, zw2);
+#endif
+            if (toUpper(splitted[1]) == "HIGHACCURACY")
+            {
+                alg_algo = 1;
+            }
+            if (toUpper(splitted[1]) == "FAST")
+            {
+                alg_algo = 2;
+            }
+            if (toUpper(splitted[1]) == "OFF")
+            {
+                // no align algo if set to 3 = off => no draw ref //add disable aligment algo |01.2023
+                alg_algo = 3;
+            }
+        }
+    }
+
+    for (int i = 0; i < anz_ref; ++i)
+    {
+        References[i].search_x = suchex;
+        References[i].search_y = suchey;
+        References[i].fastalg_SAD_criteria = SAD_criteria;
+        References[i].alignment_algo = alg_algo;
+#ifdef DEBUG_DETAIL_ON
+        std::string zw2 = "Alignment mode written: " + std::to_string(alg_algo);
+        LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, zw2);
+#endif
+    }
+
+    // no align algo if set to 3 = off => no draw ref //add disable aligment algo |01.2023
+    if (References[0].alignment_algo != 3)
+    {
+        LoadReferenceAlignmentValues();
+    }
+
+    return true;
+}
+
+string ClassFlowAlignment::getHTMLSingleStep(string host)
+{
+    string result;
+
+    result = "<p>Rotated Image: </p> <p><img src=\"" + host + "/img_tmp/rot.jpg\"></p>\n";
+    result = result + "<p>Found Alignment: </p> <p><img src=\"" + host + "/img_tmp/rot_roi.jpg\"></p>\n";
+    result = result + "<p>Aligned Image: </p> <p><img src=\"" + host + "/img_tmp/alg.jpg\"></p>\n";
+    return result;
+}
+
+bool ClassFlowAlignment::doFlow(string time)
+{
+#ifdef ALGROI_LOAD_FROM_MEM_AS_JPG
+    if (!AlgROI) // AlgROI needs to be allocated before ImageTMP to avoid heap fragmentation
+    {
+        AlgROI = (ImageData *)heap_caps_realloc(AlgROI, sizeof(ImageData), MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM);
+
+        if (!AlgROI)
+        {
+            LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Can't allocate AlgROI");
+            LogFile.WriteHeapInfo("ClassFlowAlignment-doFlow");
+        }
+    }
+
+    if (AlgROI)
+    {
+        ImageBasis->writeToMemoryAsJPG((ImageData *)AlgROI, 90);
+    }
+#endif
+
+    if (!ImageTMP)
+    {
+        ImageTMP = new CImageBasis("tmpImage", ImageBasis); // Make sure the name does not get change, it is relevant for the PSRAM allocation!
+
+        if (!ImageTMP)
+        {
+            LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Can't allocate tmpImage -> Exec this round aborted!");
+            LogFile.WriteHeapInfo("ClassFlowAlignment-doFlow");
+            return false;
+        }
+    }
+
+    delete AlignAndCutImage;
+    AlignAndCutImage = new CAlignAndCutImage("AlignAndCutImage", ImageBasis, ImageTMP);
+
+    if (!AlignAndCutImage)
+    {
+        LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Can't allocate AlignAndCutImage -> Exec this round aborted!");
+        LogFile.WriteHeapInfo("ClassFlowAlignment-doFlow");
+        return false;
+    }
+
+    CRotateImage rt("rawImage", AlignAndCutImage, ImageTMP, initialflip);
+
+    if (initialflip)
+    {
+        int _zw = ImageBasis->height;
+        ImageBasis->height = ImageBasis->width;
+        ImageBasis->width = _zw;
+
+        _zw = ImageTMP->width;
+        ImageTMP->width = ImageTMP->height;
+        ImageTMP->height = _zw;
+    }
+
+    if ((initialrotate != 0) || initialflip)
+    {
+        if (use_antialiasing)
+        {
+            rt.RotateAntiAliasing(initialrotate);
+        }
+        else
+        {
+            rt.Rotate(initialrotate);
+        }
+
+        if (SaveAllFiles)
+        {
+            AlignAndCutImage->SaveToFile(FormatFileName("/sdcard/img_tmp/rot.jpg"));
+        }
+    }
+
+    // no align algo if set to 3 = off //add disable aligment algo |01.2023
+    if (References[0].alignment_algo != 3)
+    {
+        if (!AlignAndCutImage->Align(&References[0], &References[1]))
+        {
+            SaveReferenceAlignmentValues();
+        }
+    } // no align
+
+#ifdef ALGROI_LOAD_FROM_MEM_AS_JPG
+    if (AlgROI)
+    {
+        // no align algo if set to 3 = off => no draw ref //add disable aligment algo |01.2023
+        if (References[0].alignment_algo != 3)
+        {
+            DrawRef(ImageTMP);
+        }
+
+        flowctrl.DigitalDrawROI(ImageTMP);
+        flowctrl.AnalogDrawROI(ImageTMP);
+        ImageTMP->writeToMemoryAsJPG((ImageData *)AlgROI, 90);
+    }
+#endif
+
+    if (SaveAllFiles)
+    {
+        AlignAndCutImage->SaveToFile(FormatFileName("/sdcard/img_tmp/alg.jpg"));
+        ImageTMP->SaveToFile(FormatFileName("/sdcard/img_tmp/alg_roi.jpg"));
+    }
+
+    // must be deleted to have memory space for loading tflite
+    delete ImageTMP;
+    ImageTMP = NULL;
+
+    // no align algo if set to 3 = off => no draw ref //add disable aligment algo |01.2023
+    if (References[0].alignment_algo != 3)
+    {
+        LoadReferenceAlignmentValues();
+    }
+
+    return true;
+}
+
+void ClassFlowAlignment::SaveReferenceAlignmentValues()
+{
+    FILE *pFile;
+    std::string zwtime, zwvalue;
+
+    pFile = fopen(FileStoreRefAlignment.c_str(), "w");
+
+    if (strlen(zwtime.c_str()) == 0)
+    {
+        time_t rawtime;
+        struct tm *timeinfo;
+        char buffer[80];
+
+        time(&rawtime);
+        timeinfo = localtime(&rawtime);
+
+        strftime(buffer, 80, "%Y-%m-%dT%H:%M:%S", timeinfo);
+        zwtime = std::string(buffer);
+    }
+
+    fputs(zwtime.c_str(), pFile);
+    fputs("\n", pFile);
+
+    zwvalue = std::to_string(References[0].fastalg_x) + "\t" + std::to_string(References[0].fastalg_y);
+    zwvalue = zwvalue + "\t" + std::to_string(References[0].fastalg_SAD) + "\t" + std::to_string(References[0].fastalg_min);
+    zwvalue = zwvalue + "\t" + std::to_string(References[0].fastalg_max) + "\t" + std::to_string(References[0].fastalg_avg);
+    fputs(zwvalue.c_str(), pFile);
+    fputs("\n", pFile);
+
+    zwvalue = std::to_string(References[1].fastalg_x) + "\t" + std::to_string(References[1].fastalg_y);
+    zwvalue = zwvalue + "\t" + std::to_string(References[1].fastalg_SAD) + "\t" + std::to_string(References[1].fastalg_min);
+    zwvalue = zwvalue + "\t" + std::to_string(References[1].fastalg_max) + "\t" + std::to_string(References[1].fastalg_avg);
+    fputs(zwvalue.c_str(), pFile);
+    fputs("\n", pFile);
+
+    fclose(pFile);
+}
+
+bool ClassFlowAlignment::LoadReferenceAlignmentValues(void)
+{
+    FILE *pFile;
+    char zw[1024];
+    string zwvalue;
+    std::vector<string> splitted;
+
+    pFile = fopen(FileStoreRefAlignment.c_str(), "r");
+
+    if (pFile == NULL)
+        return false;
+
+    fgets(zw, 1024, pFile);
+    ESP_LOGD(TAG, "%s", zw);
+
+    fgets(zw, 1024, pFile);
+    splitted = ZerlegeZeile(std::string(zw), " \t");
+
+    if (splitted.size() < 6)
+    {
+        fclose(pFile);
+        return false;
+    }
+
+    References[0].fastalg_x = stoi(splitted[0]);
+    References[0].fastalg_y = stoi(splitted[1]);
+    References[0].fastalg_SAD = stof(splitted[2]);
+    References[0].fastalg_min = stoi(splitted[3]);
+    References[0].fastalg_max = stoi(splitted[4]);
+    References[0].fastalg_avg = stof(splitted[5]);
+
+    fgets(zw, 1024, pFile);
+    splitted = ZerlegeZeile(std::string(zw));
+
+    if (splitted.size() < 6)
+    {
+        fclose(pFile);
+        return false;
+    }
+
+    References[1].fastalg_x = stoi(splitted[0]);
+    References[1].fastalg_y = stoi(splitted[1]);
+    References[1].fastalg_SAD = stof(splitted[2]);
+    References[1].fastalg_min = stoi(splitted[3]);
+    References[1].fastalg_max = stoi(splitted[4]);
+    References[1].fastalg_avg = stof(splitted[5]);
+
+    fclose(pFile);
+
+    /*#ifdef DEBUG_DETAIL_ON
+        std::string _zw = "\tLoadReferences[0]\tx,y:\t" + std::to_string(References[0].fastalg_x) + "\t" + std::to_string(References[0].fastalg_x);
+        _zw = _zw + "\tSAD, min, max, avg:\t" + std::to_string(References[0].fastalg_SAD) + "\t" + std::to_string(References[0].fastalg_min);
+        _zw = _zw + "\t" + std::to_string(References[0].fastalg_max) + "\t" + std::to_string(References[0].fastalg_avg);
+        LogFile.WriteToDedicatedFile("/sdcard/alignment.txt", _zw);
+        _zw = "\tLoadReferences[1]\tx,y:\t" + std::to_string(References[1].fastalg_x) + "\t" + std::to_string(References[1].fastalg_x);
+        _zw = _zw + "\tSAD, min, max, avg:\t" + std::to_string(References[1].fastalg_SAD) + "\t" + std::to_string(References[1].fastalg_min);
+        _zw = _zw + "\t" + std::to_string(References[1].fastalg_max) + "\t" + std::to_string(References[1].fastalg_avg);
+        LogFile.WriteToDedicatedFile("/sdcard/alignment.txt", _zw);
+    #endif*/
+
+    return true;
+}
+
+void ClassFlowAlignment::DrawRef(CImageBasis *_zw)
+{
+    if (_zw->ImageOkay())
+    {
+        _zw->drawRect(References[0].target_x, References[0].target_y, References[0].width, References[0].height, 255, 0, 0, 2);
+        _zw->drawRect(References[1].target_x, References[1].target_y, References[1].width, References[1].height, 255, 0, 0, 2);
+    }
+}

+ 51 - 54
code/components/jomjol_flowcontroll/ClassFlowAlignment.h

@@ -1,54 +1,51 @@
-#pragma once
-
-#ifndef CLASSFLOWALIGNMENT_H
-#define CLASSFLOWALIGNMENT_H
-
-#include "ClassFlow.h"
-#include "Helper.h"
-#include "CAlignAndCutImage.h"
-#include "CFindTemplate.h"
-
-#include <string>
-
-using namespace std;
-
-class ClassFlowAlignment :
-    public ClassFlow
-{
-protected:
-    float initalrotate;
-    bool initialmirror;
-    bool initialflip;
-    bool use_antialiasing;
-    RefInfo References[2];
-    int anz_ref;
-    string namerawimage;
-    bool SaveAllFiles;
-    CAlignAndCutImage *AlignAndCutImage;
-    std::string FileStoreRefAlignment;
-    float SAD_criteria;
-
-    void SetInitialParameter(void);
-    bool LoadReferenceAlignmentValues(void);
-    void SaveReferenceAlignmentValues();
-
-public:
-    CImageBasis *ImageBasis, *ImageTMP;
-    #ifdef ALGROI_LOAD_FROM_MEM_AS_JPG 
-    ImageData *AlgROI;
-    #endif
-    
-    ClassFlowAlignment(std::vector<ClassFlow*>* lfc);
-
-    CAlignAndCutImage* GetAlignAndCutImage(){return AlignAndCutImage;};
-
-    void DrawRef(CImageBasis *_zw);
-
-    bool ReadParameter(FILE* pfile, string& aktparamgraph);
-    bool doFlow(string time);
-    string getHTMLSingleStep(string host);
-    string name(){return "ClassFlowAlignment";};
-};
-
-
-#endif //CLASSFLOWALIGNMENT_H
+#pragma once
+
+#ifndef CLASSFLOWALIGNMENT_H
+#define CLASSFLOWALIGNMENT_H
+
+#include "ClassFlow.h"
+#include "Helper.h"
+#include "CAlignAndCutImage.h"
+#include "CFindTemplate.h"
+
+#include <string>
+
+using namespace std;
+
+class ClassFlowAlignment : public ClassFlow
+{
+protected:
+    float initialrotate;
+    bool initialflip;
+    bool use_antialiasing;
+    RefInfo References[2];
+    int anz_ref;
+    string namerawimage;
+    bool SaveAllFiles;
+    CAlignAndCutImage *AlignAndCutImage;
+    std::string FileStoreRefAlignment;
+    float SAD_criteria;
+
+    void SetInitialParameter(void);
+    bool LoadReferenceAlignmentValues(void);
+    void SaveReferenceAlignmentValues();
+
+public:
+    CImageBasis *ImageBasis, *ImageTMP;
+#ifdef ALGROI_LOAD_FROM_MEM_AS_JPG
+    ImageData *AlgROI;
+#endif
+
+    ClassFlowAlignment(std::vector<ClassFlow *> *lfc);
+
+    CAlignAndCutImage *GetAlignAndCutImage() { return AlignAndCutImage; };
+
+    void DrawRef(CImageBasis *_zw);
+
+    bool ReadParameter(FILE *pfile, string &aktparamgraph);
+    bool doFlow(string time);
+    string getHTMLSingleStep(string host);
+    string name() { return "ClassFlowAlignment"; };
+};
+
+#endif // CLASSFLOWALIGNMENT_H

+ 245 - 252
code/components/jomjol_flowcontroll/ClassFlowCNNGeneral.cpp

@@ -20,8 +20,7 @@ static const char* TAG = "CNN";
 #endif
 
 
-ClassFlowCNNGeneral::ClassFlowCNNGeneral(ClassFlowAlignment *_flowalign, t_CNNType _cnntype) : ClassFlowImage(NULL, TAG)
-{
+ClassFlowCNNGeneral::ClassFlowCNNGeneral(ClassFlowAlignment *_flowalign, t_CNNType _cnntype) : ClassFlowImage(NULL, TAG) {
     string cnnmodelfile = "";
     modelxsize = 1;
     modelysize = 1;
@@ -38,16 +37,16 @@ ClassFlowCNNGeneral::ClassFlowCNNGeneral(ClassFlowAlignment *_flowalign, t_CNNTy
 }
 
 
-string ClassFlowCNNGeneral::getReadout(int _analog = 0, bool _extendedResolution, int prev, float _before_narrow_Analog, float analogDigitalTransitionStart)
-{
+string ClassFlowCNNGeneral::getReadout(int _analog = 0, bool _extendedResolution, int prev, float _before_narrow_Analog, float analogDigitalTransitionStart) {
     string result = "";    
 
-    if (GENERAL[_analog]->ROI.size() == 0)
+    if (GENERAL[_analog]->ROI.size() == 0) {
         return result;
+    }
+    
     LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "getReadout _analog=" + std::to_string(_analog) + ", _extendedResolution=" + std::to_string(_extendedResolution) + ", prev=" + std::to_string(prev));
  
-    if (CNNType == Analogue || CNNType == Analogue100)
-    {
+    if (CNNType == Analogue || CNNType == Analogue100) {
         float number = GENERAL[_analog]->ROI[GENERAL[_analog]->ROI.size() - 1]->result_float;
         int result_after_decimal_point = ((int) floor(number * 10) + 10) % 10;
         
@@ -55,37 +54,35 @@ string ClassFlowCNNGeneral::getReadout(int _analog = 0, bool _extendedResolution
 //        LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "getReadout(analog) number=" + std::to_string(number) + ", result_after_decimal_point=" + std::to_string(result_after_decimal_point) + ", prev=" + std::to_string(prev));
         result = std::to_string(prev);
 
-        if (_extendedResolution)
+        if (_extendedResolution) {
             result = result + std::to_string(result_after_decimal_point);
+        }
 
-        for (int i = GENERAL[_analog]->ROI.size() - 2; i >= 0; --i)
-        {
+        for (int i = GENERAL[_analog]->ROI.size() - 2; i >= 0; --i) {
             prev = PointerEvalAnalogNew(GENERAL[_analog]->ROI[i]->result_float, prev);
             result = std::to_string(prev) + result;
         }
         return result;
     }
 
-    if (CNNType == Digital)
-    {
-        for (int i = 0; i < GENERAL[_analog]->ROI.size(); ++i)
-        {
-            if (GENERAL[_analog]->ROI[i]->result_klasse >= 10)
+    if (CNNType == Digital) {
+        for (int i = 0; i < GENERAL[_analog]->ROI.size(); ++i) {
+            if (GENERAL[_analog]->ROI[i]->result_klasse >= 10) {
                 result = result + "N";
-            else
+            }
+            else {
                 result = result + std::to_string(GENERAL[_analog]->ROI[i]->result_klasse);
+            }
         }
         return result;
     }
 
-    if ((CNNType == DoubleHyprid10) || (CNNType == Digital100))
-    {
-
+    if ((CNNType == DoubleHyprid10) || (CNNType == Digital100)) {
         float number = GENERAL[_analog]->ROI[GENERAL[_analog]->ROI.size() - 1]->result_float;
-        if (number >= 0)       // NaN?
-        {
-            if (_extendedResolution)            // is only set if it is the first digit (no analogue before!)
-            {
+        // NaN?
+        if (number >= 0) {
+            // is only set if it is the first digit (no analogue before!)
+            if (_extendedResolution) {
                 int result_after_decimal_point = ((int) floor(number * 10)) % 10;
                 int result_before_decimal_point = ((int) floor(number)) % 10;
 
@@ -93,36 +90,32 @@ string ClassFlowCNNGeneral::getReadout(int _analog = 0, bool _extendedResolution
                 prev = result_before_decimal_point;
                 LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "getReadout(dig100-ext) result_before_decimal_point=" + std::to_string(result_before_decimal_point) + ", result_after_decimal_point=" + std::to_string(result_after_decimal_point) + ", prev=" + std::to_string(prev));
             }
-            else
-            {
-                if (_before_narrow_Analog >= 0)
+            else {
+                if (_before_narrow_Analog >= 0) {
                     prev = PointerEvalHybridNew(GENERAL[_analog]->ROI[GENERAL[_analog]->ROI.size() - 1]->result_float, _before_narrow_Analog, prev, true, analogDigitalTransitionStart);
-                else
+                }
+                else {
                     prev = PointerEvalHybridNew(GENERAL[_analog]->ROI[GENERAL[_analog]->ROI.size() - 1]->result_float, prev, prev);
+                }
                 result = std::to_string(prev);
                 LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "getReadout(dig100)  prev=" + std::to_string(prev));
-        
             }
         }
-        else
-        {
+        else {
             result = "N";
-            if (_extendedResolution && (CNNType != Digital))
+            if (_extendedResolution && (CNNType != Digital)) {
                 result = "NN";
+            }
         }
 
-        for (int i = GENERAL[_analog]->ROI.size() - 2; i >= 0; --i)
-        {
-            if (GENERAL[_analog]->ROI[i]->result_float >= 0)
-            {
+        for (int i = GENERAL[_analog]->ROI.size() - 2; i >= 0; --i) {
+            if (GENERAL[_analog]->ROI[i]->result_float >= 0) {
                 prev = PointerEvalHybridNew(GENERAL[_analog]->ROI[i]->result_float, GENERAL[_analog]->ROI[i+1]->result_float, prev);
                 LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "getReadout#PointerEvalHybridNew()= " + std::to_string(prev));
                 result = std::to_string(prev) + result;
                 LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "getReadout#result= " + result);
-                
             }
-            else
-            {
+            else {
                 prev = -1;
                 result = "N" + result;
                 LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "getReadout(result_float<0 /'N')  result_float=" + std::to_string(GENERAL[_analog]->ROI[i]->result_float));
@@ -134,15 +127,28 @@ string ClassFlowCNNGeneral::getReadout(int _analog = 0, bool _extendedResolution
     return result;
 }
 
-
-int ClassFlowCNNGeneral::PointerEvalHybridNew(float number, float number_of_predecessors, int eval_predecessors, bool Analog_Predecessors, float digitalAnalogTransitionStart)
-{
+/**
+ * @brief Determines the number of an ROI in connection with previous ROI results
+ * 
+ * @param number: is the current ROI as float value from recognition
+ * @param number_of_predecessors: is the last (lower) ROI as float from recognition
+ * @param eval_predecessors: is the evaluated number. Sometimes a much lower value can change higer values
+ *                          example: 9.8, 9.9, 0.1
+ *                          0.1 => 0 (eval_predecessors)
+ *                          The 0 makes a 9.9 to 0 (eval_predecessors)
+ *                          The 0 makes a 9.8 to 0 
+ * @param Analog_Predecessors false/true if the last ROI is an analog or digital ROI (default=false)
+ *                              runs in special handling because analog is much less precise
+ * @param digitalAnalogTransitionStart start of the transitionlogic begins on number_of_predecessor (default=9.2)
+ *
+ * @return int the determined number of the current ROI
+ */
+int ClassFlowCNNGeneral::PointerEvalHybridNew(float number, float number_of_predecessors, int eval_predecessors, bool Analog_Predecessors, float digitalAnalogTransitionStart) {
     int result;
     int result_after_decimal_point = ((int) floor(number * 10)) % 10;
     int result_before_decimal_point = ((int) floor(number) + 10) % 10;
 
-    if (eval_predecessors < 0)
-    {   
+    if (eval_predecessors < 0) {   
         // on first digit is no spezial logic for transition needed
         // we use the recognition as given. The result is the int value of the recognition
         // add precisition of 2 digits and round before trunc
@@ -153,8 +159,7 @@ int ClassFlowCNNGeneral::PointerEvalHybridNew(float number, float number_of_pred
         return result;
     }
 
-    if (Analog_Predecessors)
-    {
+    if (Analog_Predecessors) {
         result = PointerEvalAnalogToDigitNew(number, number_of_predecessors, eval_predecessors, digitalAnalogTransitionStart);
         LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "PointerEvalHybridNew - Analog predecessor, evaluation over PointerEvalAnalogNew = " + std::to_string(result) +
                                                     " number: " + std::to_string(number) + " number_of_predecessors = " + std::to_string(number_of_predecessors)+ " eval_predecessors = " + std::to_string(eval_predecessors) + " Digital_Uncertainty = " +  std::to_string(Digital_Uncertainty));
@@ -164,26 +169,31 @@ int ClassFlowCNNGeneral::PointerEvalHybridNew(float number, float number_of_pred
     if ((number_of_predecessors > Digital_Transition_Area_Predecessor ) && (number_of_predecessors < (10.0 - Digital_Transition_Area_Predecessor)))
     {
         // no digit change, because predecessor is far enough away (0+/-DigitalTransitionRangePredecessor) --> number is rounded
-        if ((result_after_decimal_point <= DigitalBand) || (result_after_decimal_point >= (10-DigitalBand)))     // Band around the digit --> Round off, as digit reaches inaccuracy in the frame
+        // Band around the digit --> Round off, as digit reaches inaccuracy in the frame
+        if ((result_after_decimal_point <= DigitalBand) || (result_after_decimal_point >= (10-DigitalBand))) {
             result = ((int) round(number) + 10) % 10;
-        else
+        }
+        else {
             result = ((int) trunc(number) + 10) % 10;
+        }
 
         LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "PointerEvalHybridNew - NO analogue predecessor, no change of digits, as pre-decimal point far enough away = " + std::to_string(result) +
                                                     " number: " + std::to_string(number) + " number_of_predecessors = " + std::to_string(number_of_predecessors)+ " eval_predecessors = " + std::to_string(eval_predecessors) + " Digital_Uncertainty = " +  std::to_string(Digital_Uncertainty));
         return result;
     }  
 
-    if (eval_predecessors <= 1)  // Zero crossing at the predecessor has taken place (! evaluation via Prev_value and not number!) --> round up here (2.8 --> 3, but also 3.1 --> 3)
-    {
+    // Zero crossing at the predecessor has taken place (! evaluation via Prev_value and not number!) --> round up here (2.8 --> 3, but also 3.1 --> 3)
+    if (eval_predecessors <= 1) {
         // We simply assume that the current digit after the zero crossing of the predecessor
         // has passed through at least half (x.5)
-        if (result_after_decimal_point > 5)
+        if (result_after_decimal_point > 5) {
             // The current digit does not yet have a zero crossing, but the predecessor does..
             result =  (result_before_decimal_point + 1) % 10;
-        else
+        }
+        else {
             // Act. digit and predecessor have zero crossing
             result =  result_before_decimal_point % 10;
+        }
         LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "PointerEvalHybridNew - NO analogue predecessor, zero crossing has taken placen = " + std::to_string(result) +
                                                     " number: " + std::to_string(number) + " number_of_predecessors = " + std::to_string(number_of_predecessors)+ " eval_predecessors = " + std::to_string(eval_predecessors) + " Digital_Uncertainty = " +  std::to_string(Digital_Uncertainty));
         return result;
@@ -199,10 +209,12 @@ int ClassFlowCNNGeneral::PointerEvalHybridNew(float number, float number_of_pred
         || result_after_decimal_point >= 4)
         // The current digit, like the previous digit, does not yet have a zero crossing. 
         result =  result_before_decimal_point % 10;
-    else
+    }
+    else {
         // current digit precedes the smaller digit (9.x). So already >=x.0 while the previous digit has not yet
         // has no zero crossing. Therefore, it is reduced by 1.
         result =  (result_before_decimal_point - 1 + 10) % 10;
+    }
 
     LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "PointerEvalHybridNew - O analogue predecessor, >= 9.5 --> no zero crossing yet = " + std::to_string(result) +
                                                 " number: " + std::to_string(number) + " number_of_predecessors = " + std::to_string(number_of_predecessors)+ " eval_predecessors = " + std::to_string(eval_predecessors) + " Digital_Uncertainty = " +  std::to_string(Digital_Uncertainty) + " result_after_decimal_point = " + std::to_string(result_after_decimal_point));
@@ -210,8 +222,7 @@ int ClassFlowCNNGeneral::PointerEvalHybridNew(float number, float number_of_pred
 }
 
 
-int ClassFlowCNNGeneral::PointerEvalAnalogToDigitNew(float number, float numeral_preceder,  int eval_predecessors, float analogDigitalTransitionStart)
-{
+int ClassFlowCNNGeneral::PointerEvalAnalogToDigitNew(float number, float numeral_preceder,  int eval_predecessors, float analogDigitalTransitionStart) {
     int result;
     int result_after_decimal_point = ((int) floor(number * 10)) % 10;
     int result_before_decimal_point = ((int) floor(number) + 10) % 10;
@@ -253,21 +264,17 @@ int ClassFlowCNNGeneral::PointerEvalAnalogToDigitNew(float number, float numeral
                                     " number: " + std::to_string(number) + 
                                     " numeral_preceder = " + std::to_string(numeral_preceder) + 
                                     " eerg after comma = " +  std::to_string(result_after_decimal_point));
-
     }
 
     return result;
-
 }
 
 
-int ClassFlowCNNGeneral::PointerEvalAnalogNew(float number, int numeral_preceder)
-{
+int ClassFlowCNNGeneral::PointerEvalAnalogNew(float number, int numeral_preceder) {
     float number_min, number_max;
     int result;
 
-    if (numeral_preceder == -1)
-    {
+    if (numeral_preceder == -1) {
         result = (int) floor(number);
         LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "PointerEvalAnalogNew - No predecessor - Result = " + std::to_string(result) +
                                                     " number: " + std::to_string(number) + " numeral_preceder = " + std::to_string(numeral_preceder) + " Analog_error = " +  std::to_string(Analog_error));
@@ -277,17 +284,14 @@ int ClassFlowCNNGeneral::PointerEvalAnalogNew(float number, int numeral_preceder
     number_min = number - Analog_error / 10.0;
     number_max = number + Analog_error / 10.0;
 
-    if ((int) floor(number_max) - (int) floor(number_min) != 0)
-    {
-        if (numeral_preceder <= Analog_error)
-        {
+    if ((int) floor(number_max) - (int) floor(number_min) != 0) {
+        if (numeral_preceder <= Analog_error) {
             result = ((int) floor(number_max) + 10) % 10;
             LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "PointerEvalAnalogNew - number ambiguous, correction upwards - result = " + std::to_string(result) +
                                                         " number: " + std::to_string(number) + " numeral_preceder = " + std::to_string(numeral_preceder) + " Analog_error = " +  std::to_string(Analog_error));
             return result;
         }
-        if (numeral_preceder >= 10 - Analog_error)
-        {
+        if (numeral_preceder >= 10 - Analog_error) {
             result = ((int) floor(number_min) + 10) % 10;
             LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "PointerEvalAnalogNew - number ambiguous, downward correction - result = " + std::to_string(result) +
                                                         " number: " + std::to_string(number) + " numeral_preceder = " + std::to_string(numeral_preceder) + " Analog_error = " +  std::to_string(Analog_error));
@@ -295,7 +299,6 @@ int ClassFlowCNNGeneral::PointerEvalAnalogNew(float number, int numeral_preceder
         }
     }
     
-
     result = ((int) floor(number) + 10) % 10;
     LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "PointerEvalAnalogNew - number unambiguous, no correction necessary - result = " + std::to_string(result) +
                                                 " number: " + std::to_string(number) + " numeral_preceder = " + std::to_string(numeral_preceder) + " Analog_error = " +  std::to_string(Analog_error));
@@ -304,25 +307,25 @@ int ClassFlowCNNGeneral::PointerEvalAnalogNew(float number, int numeral_preceder
 }
 
 
-bool ClassFlowCNNGeneral::ReadParameter(FILE* pfile, string& aktparamgraph)
-{
+bool ClassFlowCNNGeneral::ReadParameter(FILE* pfile, string& aktparamgraph) {
     std::vector<string> splitted;
 
     aktparamgraph = trim(aktparamgraph);
 
-    if (aktparamgraph.size() == 0)
-        if (!this->GetNextParagraph(pfile, aktparamgraph))
+    if (aktparamgraph.size() == 0) {
+        if (!this->GetNextParagraph(pfile, aktparamgraph)) {
             return false;
-
+        }
+    }
 
     if ((toUpper(aktparamgraph) != "[ANALOG]") && (toUpper(aktparamgraph) != ";[ANALOG]") 
         && (toUpper(aktparamgraph) != "[DIGIT]") && (toUpper(aktparamgraph) != ";[DIGIT]")
-        && (toUpper(aktparamgraph) != "[DIGITS]") && (toUpper(aktparamgraph) != ";[DIGITS]")
-        )       // Paragraph passt nicht
+        && (toUpper(aktparamgraph) != "[DIGITS]") && (toUpper(aktparamgraph) != ";[DIGITS]")) {      
+        // Paragraph passt nicht
         return false;
+    }
 
-    if (aktparamgraph[0] == ';')
-    {
+    if (aktparamgraph[0] == ';') {
         disabled = true;
         while (getNextLine(pfile, &aktparamgraph) && !isNewParagraph(aktparamgraph));
         ESP_LOGD(TAG, "[Analog/Digit] is disabled!");
@@ -330,36 +333,31 @@ bool ClassFlowCNNGeneral::ReadParameter(FILE* pfile, string& aktparamgraph)
     }
 
 
-    while (this->getNextLine(pfile, &aktparamgraph) && !this->isNewParagraph(aktparamgraph))
-    {
+    while (this->getNextLine(pfile, &aktparamgraph) && !this->isNewParagraph(aktparamgraph)) {
         splitted = ZerlegeZeile(aktparamgraph);
-        if ((toUpper(splitted[0]) == "ROIIMAGESLOCATION") && (splitted.size() > 1))
-        {
+        if ((toUpper(splitted[0]) == "ROIIMAGESLOCATION") && (splitted.size() > 1)) {
             this->imagesLocation = "/sdcard" + splitted[1];
             this->isLogImage = true;
         }
-        if ((toUpper(splitted[0]) == "LOGIMAGESELECT") && (splitted.size() > 1))
-        {
+        
+        if ((toUpper(splitted[0]) == "LOGIMAGESELECT") && (splitted.size() > 1)) {
             LogImageSelect = splitted[1];
             isLogImageSelect = true;            
         }
 
-        if ((toUpper(splitted[0]) == "ROIIMAGESRETENTION") && (splitted.size() > 1))
-        {
+        if ((toUpper(splitted[0]) == "ROIIMAGESRETENTION") && (splitted.size() > 1)) {
             this->imagesRetention = std::stoi(splitted[1]);
         }
 
-        if ((toUpper(splitted[0]) == "MODEL") && (splitted.size() > 1))
-        {
+        if ((toUpper(splitted[0]) == "MODEL") && (splitted.size() > 1)) {
             this->cnnmodelfile = splitted[1];
         }
         
-        if ((toUpper(splitted[0]) == "CNNGOODTHRESHOLD") && (splitted.size() > 1))
-        {
+        if ((toUpper(splitted[0]) == "CNNGOODTHRESHOLD") && (splitted.size() > 1)) {
             CNNGoodThreshold = std::stof(splitted[1]);
         }
-        if (splitted.size() >= 5)
-        {
+        
+        if (splitted.size() >= 5) {
             general* _analog = GetGENERAL(splitted[0], true);
             roi* neuroi = _analog->ROI[_analog->ROI.size()-1];
             neuroi->posx = std::stoi(splitted[1]);
@@ -367,19 +365,20 @@ bool ClassFlowCNNGeneral::ReadParameter(FILE* pfile, string& aktparamgraph)
             neuroi->deltax = std::stoi(splitted[3]);
             neuroi->deltay = std::stoi(splitted[4]);
             neuroi->CCW = false;
-            if (splitted.size() >= 6)
-            {
+            
+            if (splitted.size() >= 6) {
                 neuroi->CCW = toUpper(splitted[5]) == "TRUE";
             }
+            
             neuroi->result_float = -1;
             neuroi->image = NULL;
             neuroi->image_org = NULL;
         }
 
-        if ((toUpper(splitted[0]) == "SAVEALLFILES") && (splitted.size() > 1))
-        {
-            if (toUpper(splitted[1]) == "TRUE")
+        if ((toUpper(splitted[0]) == "SAVEALLFILES") && (splitted.size() > 1)) {
+            if (toUpper(splitted[1]) == "TRUE") {
                 SaveAllFiles = true;
+            }
         }
     }
 
@@ -390,55 +389,57 @@ bool ClassFlowCNNGeneral::ReadParameter(FILE* pfile, string& aktparamgraph)
     }
 
 
-    for (int _ana = 0; _ana < GENERAL.size(); ++_ana)
-        for (int i = 0; i < GENERAL[_ana]->ROI.size(); ++i)
-        {
+    for (int _ana = 0; _ana < GENERAL.size(); ++_ana) {
+        for (int i = 0; i < GENERAL[_ana]->ROI.size(); ++i) {
             GENERAL[_ana]->ROI[i]->image = new CImageBasis("ROI " + GENERAL[_ana]->ROI[i]->name, 
                     modelxsize, modelysize, modelchannel);
             GENERAL[_ana]->ROI[i]->image_org = new CImageBasis("ROI " + GENERAL[_ana]->ROI[i]->name + " original",
                     GENERAL[_ana]->ROI[i]->deltax, GENERAL[_ana]->ROI[i]->deltay, 3);
         }
+    }
 
     return true;
 }
 
 
-general* ClassFlowCNNGeneral::FindGENERAL(string _name_number)
-{
-    for (int i = 0; i < GENERAL.size(); ++i)
-        if (GENERAL[i]->name == _name_number)
+general* ClassFlowCNNGeneral::FindGENERAL(string _name_number) {
+    for (int i = 0; i < GENERAL.size(); ++i) {
+        if (GENERAL[i]->name == _name_number) {
             return GENERAL[i];
+        }
+    }
+    
     return NULL;
 }
 
 
-general* ClassFlowCNNGeneral::GetGENERAL(string _name, bool _create = true)
-{
+general* ClassFlowCNNGeneral::GetGENERAL(string _name, bool _create = true) {
     string _analog, _roi;
     int _pospunkt = _name.find_first_of(".");
 
-    if (_pospunkt > -1)
-    {
+    if (_pospunkt > -1) {
         _analog = _name.substr(0, _pospunkt);
         _roi = _name.substr(_pospunkt+1, _name.length() - _pospunkt - 1);
     }
-    else
-    {
+    else {
         _analog = "default";
         _roi = _name;
     }
 
     general *_ret = NULL;
 
-    for (int i = 0; i < GENERAL.size(); ++i)
-        if (GENERAL[i]->name == _analog)
+    for (int i = 0; i < GENERAL.size(); ++i) {
+        if (GENERAL[i]->name == _analog) {
             _ret = GENERAL[i];
+        }
+    }
 
-    if (!_create)         // not found and should not be created
+    // not found and should not be created
+    if (!_create) {        
         return _ret;
+    }
 
-    if (_ret == NULL)
-    {
+    if (_ret == NULL) {
         _ret = new general;
         _ret->name = _analog;
         GENERAL.push_back(_ret);
@@ -455,8 +456,7 @@ general* ClassFlowCNNGeneral::GetGENERAL(string _name, bool _create = true)
 }
 
 
-string ClassFlowCNNGeneral::getHTMLSingleStep(string host)
-{
+string ClassFlowCNNGeneral::getHTMLSingleStep(string host) {
     string result, zw;
     std::vector<HTMLInfo*> htmlinfo;
 
@@ -464,8 +464,8 @@ string ClassFlowCNNGeneral::getHTMLSingleStep(string host)
     result = result + "Analog Pointers: <p> ";
 
     htmlinfo = GetHTMLInfo();
-    for (int i = 0; i < htmlinfo.size(); ++i)
-    {
+    
+    for (int i = 0; i < htmlinfo.size(); ++i) {
         std::stringstream stream;
         stream << std::fixed << std::setprecision(1) << htmlinfo[i]->val;
         zw = stream.str();
@@ -473,15 +473,14 @@ string ClassFlowCNNGeneral::getHTMLSingleStep(string host)
         result = result + "<img src=\"" + host + "/img_tmp/" +  htmlinfo[i]->filename + "\"> " + zw;
         delete htmlinfo[i];
     }
+    
     htmlinfo.clear();         
 
     return result;
 }
 
 
-bool ClassFlowCNNGeneral::doFlow(string time)
-{
-
+bool ClassFlowCNNGeneral::doFlow(string time) {
 #ifdef HEAP_TRACING_CLASS_FLOW_CNN_GENERAL_DO_ALING_AND_CUT
     //register a buffer to record the memory trace
     ESP_ERROR_CHECK( heap_trace_init_standalone(trace_record, NUM_RECORDS) );
@@ -489,8 +488,9 @@ bool ClassFlowCNNGeneral::doFlow(string time)
     ESP_ERROR_CHECK( heap_trace_start(HEAP_TRACE_LEAKS) );
 #endif
 
-    if (disabled)
+    if (disabled) {
       return true;
+    }
 
     if (!doAlignAndCut(time)){
         return false;
@@ -511,79 +511,80 @@ bool ClassFlowCNNGeneral::doFlow(string time)
 }
 
 
-bool ClassFlowCNNGeneral::doAlignAndCut(string time)
-{
-    if (disabled)
+bool ClassFlowCNNGeneral::doAlignAndCut(string time) {
+    if (disabled) {
         return true;
+    }
 
     CAlignAndCutImage *caic = flowpostalignment->GetAlignAndCutImage();    
 
-    for (int _ana = 0; _ana < GENERAL.size(); ++_ana)
-        for (int i = 0; i < GENERAL[_ana]->ROI.size(); ++i)
-        {
+    for (int _ana = 0; _ana < GENERAL.size(); ++_ana) {
+        for (int i = 0; i < GENERAL[_ana]->ROI.size(); ++i) {
             ESP_LOGD(TAG, "General %d - Align&Cut", i);
             
             caic->CutAndSave(GENERAL[_ana]->ROI[i]->posx, GENERAL[_ana]->ROI[i]->posy, GENERAL[_ana]->ROI[i]->deltax, GENERAL[_ana]->ROI[i]->deltay, GENERAL[_ana]->ROI[i]->image_org);
-            if (SaveAllFiles)
-            {
-                if (GENERAL[_ana]->name == "default")
+            if (SaveAllFiles) {
+                if (GENERAL[_ana]->name == "default") {
                     GENERAL[_ana]->ROI[i]->image_org->SaveToFile(FormatFileName("/sdcard/img_tmp/" + GENERAL[_ana]->ROI[i]->name + ".jpg"));
-                else
+                }
+                else {
                     GENERAL[_ana]->ROI[i]->image_org->SaveToFile(FormatFileName("/sdcard/img_tmp/" + GENERAL[_ana]->name + "_" + GENERAL[_ana]->ROI[i]->name + ".jpg"));
+                }
             } 
 
             GENERAL[_ana]->ROI[i]->image_org->Resize(modelxsize, modelysize, GENERAL[_ana]->ROI[i]->image);
-            if (SaveAllFiles)
-            {
-                if (GENERAL[_ana]->name == "default")
+            if (SaveAllFiles) {
+                if (GENERAL[_ana]->name == "default") {
                     GENERAL[_ana]->ROI[i]->image->SaveToFile(FormatFileName("/sdcard/img_tmp/" + GENERAL[_ana]->ROI[i]->name + ".jpg"));
-                else
+                }
+                else {
                     GENERAL[_ana]->ROI[i]->image->SaveToFile(FormatFileName("/sdcard/img_tmp/" + GENERAL[_ana]->name + "_" + GENERAL[_ana]->ROI[i]->name + ".jpg"));
+                }
             } 
         }
+    }
 
     return true;
 } 
 
 
-void ClassFlowCNNGeneral::DrawROI(CImageBasis *_zw)
-{
-    if (_zw->ImageOkay()) 
-    { 
-        if (CNNType == Analogue || CNNType == Analogue100)
-        {
+void ClassFlowCNNGeneral::DrawROI(CImageBasis *_zw) {
+    if (_zw->ImageOkay()) { 
+        if (CNNType == Analogue || CNNType == Analogue100) {
             int r = 0;
             int g = 255;
             int b = 0;
 
-            for (int _ana = 0; _ana < GENERAL.size(); ++_ana)
-                for (int i = 0; i < GENERAL[_ana]->ROI.size(); ++i)
-                {
+            for (int _ana = 0; _ana < GENERAL.size(); ++_ana) {
+                for (int i = 0; i < GENERAL[_ana]->ROI.size(); ++i) {
                     _zw->drawRect(GENERAL[_ana]->ROI[i]->posx, GENERAL[_ana]->ROI[i]->posy, GENERAL[_ana]->ROI[i]->deltax, GENERAL[_ana]->ROI[i]->deltay, r, g, b, 1);
                     _zw->drawEllipse( (int) (GENERAL[_ana]->ROI[i]->posx + GENERAL[_ana]->ROI[i]->deltax/2), (int)  (GENERAL[_ana]->ROI[i]->posy + GENERAL[_ana]->ROI[i]->deltay/2), (int) (GENERAL[_ana]->ROI[i]->deltax/2), (int) (GENERAL[_ana]->ROI[i]->deltay/2), r, g, b, 2);
                     _zw->drawLine((int) (GENERAL[_ana]->ROI[i]->posx + GENERAL[_ana]->ROI[i]->deltax/2), (int) GENERAL[_ana]->ROI[i]->posy, (int) (GENERAL[_ana]->ROI[i]->posx + GENERAL[_ana]->ROI[i]->deltax/2), (int) (GENERAL[_ana]->ROI[i]->posy + GENERAL[_ana]->ROI[i]->deltay), r, g, b, 2);
                     _zw->drawLine((int) GENERAL[_ana]->ROI[i]->posx, (int) (GENERAL[_ana]->ROI[i]->posy + GENERAL[_ana]->ROI[i]->deltay/2), (int) GENERAL[_ana]->ROI[i]->posx + GENERAL[_ana]->ROI[i]->deltax, (int) (GENERAL[_ana]->ROI[i]->posy + GENERAL[_ana]->ROI[i]->deltay/2), r, g, b, 2);
                 }
+            }
         }
-        else
-        {
-            for (int _dig = 0; _dig < GENERAL.size(); ++_dig)
-                for (int i = 0; i < GENERAL[_dig]->ROI.size(); ++i)
+        else {
+            for (int _dig = 0; _dig < GENERAL.size(); ++_dig) {
+                for (int i = 0; i < GENERAL[_dig]->ROI.size(); ++i) {
                     _zw->drawRect(GENERAL[_dig]->ROI[i]->posx, GENERAL[_dig]->ROI[i]->posy, GENERAL[_dig]->ROI[i]->deltax, GENERAL[_dig]->ROI[i]->deltay, 0, 0, (255 - _dig*100), 2);
+                }
+            }
         }
     }
 } 
 
 
-bool ClassFlowCNNGeneral::getNetworkParameter()
-{
-    if (disabled)
+bool ClassFlowCNNGeneral::getNetworkParameter() {
+    if (disabled) {
         return true;
+    }
 
     CTfLiteClass *tflite = new CTfLiteClass;  
     string zwcnn = "/sdcard" + cnnmodelfile;
     zwcnn = FormatFileName(zwcnn);
     ESP_LOGD(TAG, "%s", zwcnn.c_str());
+    
     if (!tflite->LoadModel(zwcnn)) {
         LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Can't load tflite model " + cnnmodelfile + " -> Init aborted!");
         LogFile.WriteHeapInfo("getNetworkParameter-LoadModel");
@@ -598,16 +599,14 @@ bool ClassFlowCNNGeneral::getNetworkParameter()
         return false;
     }
 
-    if (CNNType == AutoDetect)
-    {
+    if (CNNType == AutoDetect) {
         tflite->GetInputDimension(false);
         modelxsize = tflite->ReadInputDimenstion(0);
         modelysize = tflite->ReadInputDimenstion(1);
         modelchannel = tflite->ReadInputDimenstion(2);
 
         int _anzoutputdimensions = tflite->GetAnzOutPut();
-        switch (_anzoutputdimensions) 
-        {
+        switch (_anzoutputdimensions) {
             case 2:
                 CNNType = Analogue;
                 ESP_LOGD(TAG, "TFlite-Type set to Analogue");
@@ -633,7 +632,8 @@ bool ClassFlowCNNGeneral::getNetworkParameter()
                 if (modelxsize==32 && modelysize == 32) {
                     CNNType = Analogue100;
                     ESP_LOGD(TAG, "TFlite-Type set to Analogue100");
-                } else {
+                } 
+                else {
                     CNNType = Digital100;
                     ESP_LOGD(TAG, "TFlite-Type set to Digital");
                 }
@@ -648,10 +648,10 @@ bool ClassFlowCNNGeneral::getNetworkParameter()
 }
 
 
-bool ClassFlowCNNGeneral::doNeuralNetwork(string time)
-{
-    if (disabled)
+bool ClassFlowCNNGeneral::doNeuralNetwork(string time) {
+    if (disabled) {
         return true;
+    }
 
     string logPath = CreateLogFolder(time);
 
@@ -674,11 +674,11 @@ bool ClassFlowCNNGeneral::doNeuralNetwork(string time)
         return false;
     }
 
-    for (int n = 0; n < GENERAL.size(); ++n) // For each NUMBER
-    {
+    // For each NUMBER
+    for (int n = 0; n < GENERAL.size(); ++n) {
         LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Processing Number '" + GENERAL[n]->name + "'");
-        for (int roi = 0; roi < GENERAL[n]->ROI.size(); ++roi) // For each ROI
-        {
+        // For each ROI
+        for (int roi = 0; roi < GENERAL[n]->ROI.size(); ++roi) {
             LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "ROI #" + std::to_string(roi) + " - TfLite");
             //ESP_LOGD(TAG, "General %d - TfLite", i);
 
@@ -697,14 +697,17 @@ bool ClassFlowCNNGeneral::doNeuralNetwork(string time)
                         f2 = tflite->GetOutputValue(1);
                         float result = fmod(atan2(f1, f2) / (M_PI * 2) + 2, 1);
                               
-                        if(GENERAL[n]->ROI[roi]->CCW)
+                        if(GENERAL[n]->ROI[roi]->CCW) {
                             GENERAL[n]->ROI[roi]->result_float = 10 - (result * 10);
-                        else
+                        }
+                        else {
                             GENERAL[n]->ROI[roi]->result_float = result * 10;
+                        }
                               
                         ESP_LOGD(TAG, "General result (Analog)%i - CCW: %d -  %f", roi, GENERAL[n]->ROI[roi]->CCW, GENERAL[n]->ROI[roi]->result_float);
-                        if (isLogImage)
+                        if (isLogImage) {
                             LogImage(logPath, GENERAL[n]->ROI[roi]->name, &GENERAL[n]->ROI[roi]->result_float, NULL, time, GENERAL[n]->ROI[roi]->image_org);
+                        }
                     } break;
 
                 case Digital:
@@ -714,22 +717,19 @@ bool ClassFlowCNNGeneral::doNeuralNetwork(string time)
                         GENERAL[n]->ROI[roi]->result_klasse = tflite->GetClassFromImageBasis(GENERAL[n]->ROI[roi]->image);
                         ESP_LOGD(TAG, "General result (Digit)%i: %d", roi, GENERAL[n]->ROI[roi]->result_klasse);
 
-                        if (isLogImage)
-                        {
+                        if (isLogImage) {
                             string _imagename = GENERAL[n]->name +  "_" + GENERAL[n]->ROI[roi]->name;
-                            if (isLogImageSelect)
-                            {
-                                if (LogImageSelect.find(GENERAL[n]->ROI[roi]->name) != std::string::npos)
+                            if (isLogImageSelect) {
+                                if (LogImageSelect.find(GENERAL[n]->ROI[roi]->name) != std::string::npos) {
                                     LogImage(logPath, _imagename, NULL, &GENERAL[n]->ROI[roi]->result_klasse, time, GENERAL[n]->ROI[roi]->image_org);
+                                }
                             }
-                            else
-                            {
+                            else {
                                 LogImage(logPath, _imagename, NULL, &GENERAL[n]->ROI[roi]->result_klasse, time, GENERAL[n]->ROI[roi]->image_org);
                             }
                         }
                     } break;
 
-
                 case DoubleHyprid10:
                     {
                     LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "CNN Type: DoubleHyprid10");
@@ -752,62 +752,56 @@ bool ClassFlowCNNGeneral::doNeuralNetwork(string time)
 
                         float result = _num;
 
-                        if (_valplus > _valminus)
-                        {
+                        if (_valplus > _valminus) {
                             result = result + _valplus / (_valplus + _val);
                             _fit = _val + _valplus;
                         }
-                        else
-                        {
+                        else {
                             result = result - _valminus / (_val + _valminus);
                             _fit = _val + _valminus;
-
                         }
-                        if (result >= 10)
+                        
+                        if (result >= 10) {
                             result = result - 10;
-                        if (result < 0)
+                        }
+                        
+                        if (result < 0) {
                             result = result + 10;
+                        }
 
                         string zw = "_num (p, m): " + to_string(_num) + " " + to_string(_numplus) + " " + to_string(_numminus);
                         zw = zw + " _val (p, m): " + to_string(_val) + " " + to_string(_valplus) + " " + to_string(_valminus);
                         zw = zw + " result: " + to_string(result) + " _fit: " + to_string(_fit);
                         LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, zw);
 
-
                         _result_save_file = result;
 
-                        if (_fit < CNNGoodThreshold)
-                        {
+                        if (_fit < CNNGoodThreshold) {
                             GENERAL[n]->ROI[roi]->isReject = true;
                             result = -1;
                             _result_save_file+= 100;     // In case fit is not sufficient, the result should still be saved with "-10x.y".
                             string zw = "Value Rejected due to Threshold (Fit: " + to_string(_fit) + ", Threshold: " + to_string(CNNGoodThreshold) + ")";
                             LogFile.WriteToFile(ESP_LOG_WARN, TAG, zw);
                         }
-                        else
-                        {
+                        else {
                             GENERAL[n]->ROI[roi]->isReject = false;
                         }
 
-
                         GENERAL[n]->ROI[roi]->result_float = result;
                         ESP_LOGD(TAG, "Result General(Analog)%i: %f", roi, GENERAL[n]->ROI[roi]->result_float);
 
-                        if (isLogImage)
-                        {
+                        if (isLogImage) {
                             string _imagename = GENERAL[n]->name +  "_" + GENERAL[n]->ROI[roi]->name;
-                            if (isLogImageSelect)
-                            {
-                                if (LogImageSelect.find(GENERAL[n]->ROI[roi]->name) != std::string::npos)
+                            if (isLogImageSelect) {
+                                if (LogImageSelect.find(GENERAL[n]->ROI[roi]->name) != std::string::npos) {
                                     LogImage(logPath, _imagename, &_result_save_file, NULL, time, GENERAL[n]->ROI[roi]->image_org);
+                                }
                             }
-                            else
-                            {
+                            else {
                                 LogImage(logPath, _imagename, &_result_save_file, NULL, time, GENERAL[n]->ROI[roi]->image_org);
                             }
                         }
-                    }
-                    break;
+                    } break;
                 case Digital100:
                 case Analogue100:
                     {
@@ -820,28 +814,27 @@ bool ClassFlowCNNGeneral::doNeuralNetwork(string time)
     
                         _num = tflite->GetOutClassification();
                         
-                        if(GENERAL[n]->ROI[roi]->CCW)
-                            GENERAL[n]->ROI[roi]->result_float = 10 - ((float)_num / 10.0);                              
-                        else
+                        if(GENERAL[n]->ROI[roi]->CCW) {
+                            GENERAL[n]->ROI[roi]->result_float = 10 - ((float)_num / 10.0);
+                        }
+                        else {
                             GENERAL[n]->ROI[roi]->result_float = (float)_num / 10.0;
+                        }
 
                         _result_save_file = GENERAL[n]->ROI[roi]->result_float;
-
                         
                         GENERAL[n]->ROI[roi]->isReject = false;
                         
                         ESP_LOGD(TAG, "Result General(Analog)%i - CCW: %d -  %f", roi, GENERAL[n]->ROI[roi]->CCW, GENERAL[n]->ROI[roi]->result_float);
 
-                        if (isLogImage)
-                        {
+                        if (isLogImage) {
                             string _imagename = GENERAL[n]->name +  "_" + GENERAL[n]->ROI[roi]->name;
-                            if (isLogImageSelect)
-                            {
-                                if (LogImageSelect.find(GENERAL[n]->ROI[roi]->name) != std::string::npos)
+                            if (isLogImageSelect) {
+                                if (LogImageSelect.find(GENERAL[n]->ROI[roi]->name) != std::string::npos) {
                                     LogImage(logPath, _imagename, &_result_save_file, NULL, time, GENERAL[n]->ROI[roi]->image_org);
+                                }
                             }
-                            else
-                            {
+                            else {
                                 LogImage(logPath, _imagename, &_result_save_file, NULL, time, GENERAL[n]->ROI[roi]->image_org);
                             }
                         }
@@ -860,93 +853,94 @@ bool ClassFlowCNNGeneral::doNeuralNetwork(string time)
 }
 
 
-bool ClassFlowCNNGeneral::isExtendedResolution(int _number)
-{
-    if (CNNType == Digital)
+bool ClassFlowCNNGeneral::isExtendedResolution(int _number) {
+    if (CNNType == Digital) {
         return false;
+    }
+    
     return true;
 }
 
 
-std::vector<HTMLInfo*> ClassFlowCNNGeneral::GetHTMLInfo()
-{
+std::vector<HTMLInfo*> ClassFlowCNNGeneral::GetHTMLInfo() {
     std::vector<HTMLInfo*> result;
 
-    for (int _ana = 0; _ana < GENERAL.size(); ++_ana)
-        for (int i = 0; i < GENERAL[_ana]->ROI.size(); ++i)
-        {
+    for (int _ana = 0; _ana < GENERAL.size(); ++_ana) {
+        for (int i = 0; i < GENERAL[_ana]->ROI.size(); ++i) {
             ESP_LOGD(TAG, "Image: %d", (int) GENERAL[_ana]->ROI[i]->image);
-            if (GENERAL[_ana]->ROI[i]->image)
-            {
-                if (GENERAL[_ana]->name == "default")
+            if (GENERAL[_ana]->ROI[i]->image) {
+                if (GENERAL[_ana]->name == "default") {
                     GENERAL[_ana]->ROI[i]->image->SaveToFile(FormatFileName("/sdcard/img_tmp/" + GENERAL[_ana]->ROI[i]->name + ".jpg"));
-                else
+                }
+                else {
                     GENERAL[_ana]->ROI[i]->image->SaveToFile(FormatFileName("/sdcard/img_tmp/" + GENERAL[_ana]->name + "_" + GENERAL[_ana]->ROI[i]->name + ".jpg"));
+                }
             }
 
             HTMLInfo *zw = new HTMLInfo;
-            if (GENERAL[_ana]->name == "default")
-            {
+            if (GENERAL[_ana]->name == "default") {
                 zw->filename = GENERAL[_ana]->ROI[i]->name + ".jpg";
                 zw->filename_org = GENERAL[_ana]->ROI[i]->name + ".jpg";
             }
-            else
-            {
+            else {
                 zw->filename = GENERAL[_ana]->name + "_" + GENERAL[_ana]->ROI[i]->name + ".jpg";
                 zw->filename_org = GENERAL[_ana]->name + "_" + GENERAL[_ana]->ROI[i]->name + ".jpg";
             }
 
-            if (CNNType == Digital)
+            if (CNNType == Digital) {
                 zw->val = GENERAL[_ana]->ROI[i]->result_klasse;
-            else
+            }
+            else {
                 zw->val = GENERAL[_ana]->ROI[i]->result_float;
+            }
+            
             zw->image = GENERAL[_ana]->ROI[i]->image;
             zw->image_org = GENERAL[_ana]->ROI[i]->image_org;
 
             result.push_back(zw);
         }
+    }
 
     return result;
 }
 
 
-int ClassFlowCNNGeneral::getNumberGENERAL()
-{
+int ClassFlowCNNGeneral::getNumberGENERAL() {
     return GENERAL.size();
 }
 
 
-string ClassFlowCNNGeneral::getNameGENERAL(int _analog)
-{
-    if (_analog < GENERAL.size())
+string ClassFlowCNNGeneral::getNameGENERAL(int _analog) {
+    if (_analog < GENERAL.size()) {
         return GENERAL[_analog]->name;
+    }
 
     return "GENERAL DOES NOT EXIST";
 }
 
 
-general* ClassFlowCNNGeneral::GetGENERAL(int _analog)
-{
-    if (_analog < GENERAL.size())
+general* ClassFlowCNNGeneral::GetGENERAL(int _analog) {
+    if (_analog < GENERAL.size()) {
         return GENERAL[_analog];
+    }
 
     return NULL;
 }
 
 
-void ClassFlowCNNGeneral::UpdateNameNumbers(std::vector<std::string> *_name_numbers)
-{
-    for (int _dig = 0; _dig < GENERAL.size(); _dig++)
-    {
+void ClassFlowCNNGeneral::UpdateNameNumbers(std::vector<std::string> *_name_numbers) {
+    for (int _dig = 0; _dig < GENERAL.size(); _dig++) {
         std::string _name = GENERAL[_dig]->name;
         bool found = false;
-        for (int i = 0; i < (*_name_numbers).size(); ++i)
-        {
-            if ((*_name_numbers)[i] == _name)
+        
+        for (int i = 0; i < (*_name_numbers).size(); ++i) {
+            if ((*_name_numbers)[i] == _name) {
                 found = true;
+            }
         }
-        if (!found)
+        if (!found) {
             (*_name_numbers).push_back(_name);
+        }
     }
 }
 
@@ -955,26 +949,25 @@ string ClassFlowCNNGeneral::getReadoutRawString(int _analog)
 {
     string rt = "";
 
-    if (_analog >= GENERAL.size() || GENERAL[_analog]==NULL || GENERAL[_analog]->ROI.size() == 0)
+    if (_analog >= GENERAL.size() || GENERAL[_analog]==NULL || GENERAL[_analog]->ROI.size() == 0) {
         return rt;
+    }
  
-    for (int i = 0; i < GENERAL[_analog]->ROI.size(); ++i)
-    {
-        if (CNNType == Analogue || CNNType == Analogue100)
-        {
+    for (int i = 0; i < GENERAL[_analog]->ROI.size(); ++i) {
+        if (CNNType == Analogue || CNNType == Analogue100) {
             rt = rt + "," + RundeOutput(GENERAL[_analog]->ROI[i]->result_float, 1);
         }
 
-        if (CNNType == Digital)
-        {
-            if (GENERAL[_analog]->ROI[i]->result_klasse == 10)
+        if (CNNType == Digital) {
+            if (GENERAL[_analog]->ROI[i]->result_klasse >= 10) {
                 rt = rt + ",N";
-            else
+            }
+            else {
                 rt = rt + "," + RundeOutput(GENERAL[_analog]->ROI[i]->result_klasse, 0);
+            }
         }
 
-        if ((CNNType == DoubleHyprid10) || (CNNType == Digital100))
-        {
+        if ((CNNType == DoubleHyprid10) || (CNNType == Digital100)) {
             rt = rt + "," + RundeOutput(GENERAL[_analog]->ROI[i]->result_float, 1);
         }
     }

+ 1 - 0
code/components/jomjol_flowcontroll/ClassFlowDefineTypes.h

@@ -34,6 +34,7 @@ struct NumberPost {
     bool AllowNegativeRates;
     bool checkDigitIncreaseConsistency;
     time_t lastvalue;
+    time_t timeStampTimeUTC;
     string timeStamp;
     double FlowRateAct; // m3 / min
     double PreValue; // last value that was read out well

+ 3 - 1
code/components/jomjol_flowcontroll/ClassFlowInfluxDB.cpp

@@ -137,6 +137,7 @@ bool ClassFlowInfluxDB::doFlow(string zwtime)
     std::string resultraw = "";
     std::string resultrate = "";
     std::string resulttimestamp = "";
+    long int timeutc;
     string zw = "";
     string namenumber = "";
 
@@ -152,6 +153,7 @@ bool ClassFlowInfluxDB::doFlow(string zwtime)
             resulterror = (*NUMBERS)[i]->ErrorMessageText;
             resultrate = (*NUMBERS)[i]->ReturnRateValue;
             resulttimestamp = (*NUMBERS)[i]->timeStamp;
+            timeutc = (*NUMBERS)[i]->timeStampTimeUTC;
 
             if ((*NUMBERS)[i]->FieldV1.length() > 0)
             {
@@ -167,7 +169,7 @@ bool ClassFlowInfluxDB::doFlow(string zwtime)
             }
 
             if (result.length() > 0)   
-                InfluxDBPublish(measurement, namenumber, result, resulttimestamp);
+                InfluxDBPublish(measurement, namenumber, result, timeutc);
         }
     }
    

+ 4 - 2
code/components/jomjol_flowcontroll/ClassFlowInfluxDBv2.cpp

@@ -196,6 +196,7 @@ bool ClassFlowInfluxDBv2::doFlow(string zwtime)
     std::string resultraw = "";
     std::string resultrate = "";
     std::string resulttimestamp = "";
+    long int resulttimeutc = 0;
     string zw = "";
     string namenumber = "";
 
@@ -212,6 +213,8 @@ bool ClassFlowInfluxDBv2::doFlow(string zwtime)
             resulterror = (*NUMBERS)[i]->ErrorMessageText;
             resultrate = (*NUMBERS)[i]->ReturnRateValue;
             resulttimestamp = (*NUMBERS)[i]->timeStamp;
+            resulttimeutc = (*NUMBERS)[i]->timeStampTimeUTC;
+
 
             if ((*NUMBERS)[i]->FieldV2.length() > 0)
             {
@@ -229,8 +232,7 @@ bool ClassFlowInfluxDBv2::doFlow(string zwtime)
             printf("vor sende Influx_DB_V2 - namenumber. %s, result: %s, timestampt: %s", namenumber.c_str(), result.c_str(), resulttimestamp.c_str());
 
             if (result.length() > 0)   
-                InfluxDB_V2_Publish(measurement, namenumber, result, resulttimestamp);
-//                InfluxDB_V2_Publish(namenumber, result, resulttimestamp);
+                InfluxDB_V2_Publish(measurement, namenumber, result, resulttimeutc);
         }
     }
    

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 280 - 303
code/components/jomjol_flowcontroll/ClassFlowPostProcessing.cpp


+ 440 - 101
code/components/jomjol_flowcontroll/ClassFlowTakeImage.cpp

@@ -1,9 +1,15 @@
+#include <iostream>
+#include <string>
+#include <vector>
+#include <regex>
+
 #include "ClassFlowTakeImage.h"
 #include "Helper.h"
 #include "ClassLogFile.h"
 
 #include "CImageBasis.h"
 #include "ClassControllCamera.h"
+#include "MainFlowControl.h"
 
 #include "esp_wifi.h"
 #include "esp_log.h"
@@ -12,14 +18,14 @@
 
 #include <time.h>
 
-// #define DEBUG_DETAIL_ON 
-
+// #define DEBUG_DETAIL_ON
 // #define WIFITURNOFF
 
-static const char* TAG = "TAKEIMAGE";
+static const char *TAG = "TAKEIMAGE";
 
-esp_err_t ClassFlowTakeImage::camera_capture(){
-    string nm =  namerawimage;
+esp_err_t ClassFlowTakeImage::camera_capture(void)
+{
+    string nm = namerawimage;
     Camera.CaptureToFile(nm);
     time(&TimeImageTaken);
     localtime(&TimeImageTaken);
@@ -30,149 +36,479 @@ esp_err_t ClassFlowTakeImage::camera_capture(){
 void ClassFlowTakeImage::takePictureWithFlash(int flash_duration)
 {
     // in case the image is flipped, it must be reset here //
-    rawImage->width = image_width;          
-    rawImage->height = image_height;
-    /////////////////////////////////////////////////////////////////////////////////////
+    rawImage->width = CCstatus.ImageWidth;
+    rawImage->height = CCstatus.ImageHeight;
+
     ESP_LOGD(TAG, "flash_duration: %d", flash_duration);
+
     Camera.CaptureToBasisImage(rawImage, flash_duration);
+
     time(&TimeImageTaken);
     localtime(&TimeImageTaken);
 
-    if (SaveAllFiles) rawImage->SaveToFile(namerawimage);
+    if (CCstatus.SaveAllFiles)
+    {
+        rawImage->SaveToFile(namerawimage);
+    }
 }
 
 void ClassFlowTakeImage::SetInitialParameter(void)
 {
-    waitbeforepicture = 5;
-    isImageSize = false;
-    ImageQuality = -1;    
     TimeImageTaken = 0;
-    ImageQuality = 5;
     rawImage = NULL;
-    ImageSize = FRAMESIZE_VGA;
-    SaveAllFiles = false;
     disabled = false;
-    FixedExposure = false;
     namerawimage = "/sdcard/img_tmp/raw.jpg";
-}     
-
-
-ClassFlowTakeImage::ClassFlowTakeImage(std::vector<ClassFlow*>* lfc) : ClassFlowImage(lfc, TAG)
-{
-    imagesLocation = "/log/source";
-    imagesRetention = 5;
-    SetInitialParameter();
 }
 
-
-bool ClassFlowTakeImage::ReadParameter(FILE* pfile, string& aktparamgraph)
+// auslesen der Kameraeinstellungen aus der config.ini
+// wird beim Start aufgerufen
+bool ClassFlowTakeImage::ReadParameter(FILE *pfile, string &aktparamgraph)
 {
+    Camera.getSensorDatenToCCstatus(); // Kamera >>> CCstatus
+
     std::vector<string> splitted;
 
     aktparamgraph = trim(aktparamgraph);
-    int _brightness = -100;
-    int _contrast = -100;
-    int _saturation = -100;
 
     if (aktparamgraph.size() == 0)
+    {
         if (!this->GetNextParagraph(pfile, aktparamgraph))
+        {
             return false;
+        }
+    }
 
-    if (aktparamgraph.compare("[TakeImage]") != 0)       // Paragraph does not fit TakeImage
+    if (aktparamgraph.compare("[TakeImage]") != 0)
+    {
+        // Paragraph does not fit TakeImage
         return false;
+    }
 
     while (this->getNextLine(pfile, &aktparamgraph) && !this->isNewParagraph(aktparamgraph))
     {
         splitted = ZerlegeZeile(aktparamgraph);
-        if ((toUpper(splitted[0]) ==  "RAWIMAGESLOCATION") && (splitted.size() > 1))
+
+        if ((toUpper(splitted[0]) == "RAWIMAGESLOCATION") && (splitted.size() > 1))
         {
             imagesLocation = "/sdcard" + splitted[1];
             isLogImage = true;
         }
-        if ((toUpper(splitted[0]) == "IMAGEQUALITY") && (splitted.size() > 1))
-            ImageQuality = std::stod(splitted[1]);
 
-        if ((toUpper(splitted[0]) == "IMAGESIZE") && (splitted.size() > 1))
+        else if ((toUpper(splitted[0]) == "RAWIMAGESRETENTION") && (splitted.size() > 1))
+        {
+            this->imagesRetention = std::stod(splitted[1]);
+        }
+
+        else if ((toUpper(splitted[0]) == "SAVEALLFILES") && (splitted.size() > 1))
+        {
+            if (toUpper(splitted[1]) == "TRUE")
+            {
+                CCstatus.SaveAllFiles = 1;
+            }
+            else
+            {
+                CCstatus.SaveAllFiles = 0;
+            }
+        }
+
+        else if ((toUpper(splitted[0]) == "WAITBEFORETAKINGPICTURE") && (splitted.size() > 1))
+        {
+            int _WaitBeforePicture = std::stoi(splitted[1]);
+            if (_WaitBeforePicture != 0)
+            {
+                CCstatus.WaitBeforePicture = _WaitBeforePicture;
+            }
+            else
+            {
+                CCstatus.WaitBeforePicture = 2;
+            }
+        }
+
+        else if ((toUpper(splitted[0]) == "CAMGAINCEILING") && (splitted.size() > 1))
+        {
+            std::string _ImageGainceiling = toUpper(splitted[1]);
+            if (_ImageGainceiling == "X4")
+            {
+                CCstatus.ImageGainceiling = GAINCEILING_4X;
+            }
+            else if (_ImageGainceiling == "X8")
+            {
+                CCstatus.ImageGainceiling = GAINCEILING_8X;
+            }
+            else if (_ImageGainceiling == "X16")
+            {
+                CCstatus.ImageGainceiling = GAINCEILING_16X;
+            }
+            else if (_ImageGainceiling == "X32")
+            {
+                CCstatus.ImageGainceiling = GAINCEILING_32X;
+            }
+            else if (_ImageGainceiling == "X64")
+            {
+                CCstatus.ImageGainceiling = GAINCEILING_64X;
+            }
+            else if (_ImageGainceiling == "X128")
+            {
+                CCstatus.ImageGainceiling = GAINCEILING_128X;
+            }
+            else
+            {
+                CCstatus.ImageGainceiling = GAINCEILING_2X;
+            }
+        }
+
+        else if ((toUpper(splitted[0]) == "CAMQUALITY") && (splitted.size() > 1))
+        {
+            int _ImageQuality = std::stoi(splitted[1]);
+            if ((_ImageQuality >= 0) && (_ImageQuality <= 63))
+            {
+                CCstatus.ImageQuality = _ImageQuality;
+            }
+        }
+
+        else if ((toUpper(splitted[0]) == "CAMBRIGHTNESS") && (splitted.size() > 1))
+        {
+            int _ImageBrightness = std::stoi(splitted[1]);
+            if ((_ImageBrightness >= -2) && (_ImageBrightness <= 2))
+            {
+                CCstatus.ImageBrightness = _ImageBrightness;
+            }
+        }
+
+        else if ((toUpper(splitted[0]) == "CAMCONTRAST") && (splitted.size() > 1))
+        {
+            int _ImageContrast = std::stoi(splitted[1]);
+            if ((_ImageContrast >= -2) && (_ImageContrast <= 2))
+            {
+                CCstatus.ImageContrast = _ImageContrast;
+            }
+        }
+
+        else if ((toUpper(splitted[0]) == "CAMSATURATION") && (splitted.size() > 1))
+        {
+            int _ImageSaturation = std::stoi(splitted[1]);
+            if ((_ImageSaturation >= -2) && (_ImageSaturation <= 2))
+            {
+                CCstatus.ImageSaturation = _ImageSaturation;
+            }
+        }
+
+        else if ((toUpper(splitted[0]) == "CAMSHARPNESS") && (splitted.size() > 1))
+        {
+            int _ImageSharpness = std::stoi(splitted[1]);
+            if ((_ImageSharpness >= -2) && (_ImageSharpness <= 2))
+            {
+                CCstatus.ImageSharpness = _ImageSharpness;
+            }
+        }
+
+        else if ((toUpper(splitted[0]) == "CAMAUTOSHARPNESS") && (splitted.size() > 1))
+        {
+            if (toUpper(splitted[1]) == "TRUE")
+            {
+                CCstatus.ImageAutoSharpness = 1;
+            }
+            else
+            {
+                CCstatus.ImageAutoSharpness = 0;
+            }
+        }
+
+        else if ((toUpper(splitted[0]) == "CAMSPECIALEFFECT") && (splitted.size() > 1))
+        {
+            std::string _ImageSpecialEffect = toUpper(splitted[1]);
+            if (_ImageSpecialEffect == "NEGATIVE")
+            {
+                CCstatus.ImageSpecialEffect = 1;
+            }
+            else if (_ImageSpecialEffect == "GRAYSCALE")
+            {
+                CCstatus.ImageSpecialEffect = 2;
+            }
+            else if (_ImageSpecialEffect == "RED")
+            {
+                CCstatus.ImageSpecialEffect = 3;
+            }
+            else if (_ImageSpecialEffect == "GREEN")
+            {
+                CCstatus.ImageSpecialEffect = 4;
+            }
+            else if (_ImageSpecialEffect == "BLUE")
+            {
+                CCstatus.ImageSpecialEffect = 5;
+            }
+            else if (_ImageSpecialEffect == "RETRO")
+            {
+                CCstatus.ImageSpecialEffect = 6;
+            }
+            else
+            {
+                CCstatus.ImageSpecialEffect = 0;
+            }
+        }
+
+        else if ((toUpper(splitted[0]) == "CAMWBMODE") && (splitted.size() > 1))
+        {
+            std::string _ImageWbMode = toUpper(splitted[1]);
+            if (_ImageWbMode == "SUNNY")
+            {
+                CCstatus.ImageWbMode = 1;
+            }
+            else if (_ImageWbMode == "CLOUDY")
+            {
+                CCstatus.ImageWbMode = 2;
+            }
+            else if (_ImageWbMode == "OFFICE")
+            {
+                CCstatus.ImageWbMode = 3;
+            }
+            else if (_ImageWbMode == "HOME")
+            {
+                CCstatus.ImageWbMode = 4;
+            }
+            else
+            {
+                CCstatus.ImageWbMode = 0;
+            }
+        }
+
+        else if ((toUpper(splitted[0]) == "CAMAWB") && (splitted.size() > 1))
         {
-            ImageSize = Camera.TextToFramesize(splitted[1].c_str());
-            isImageSize = true;
+            if (toUpper(splitted[1]) == "TRUE")
+            {
+                CCstatus.ImageAwb = 1;
+            }
+            else
+            {
+                CCstatus.ImageAwb = 0;
+            }
+        }
+
+        else if ((toUpper(splitted[0]) == "CAMAWBGAIN") && (splitted.size() > 1))
+        {
+            if (toUpper(splitted[1]) == "TRUE")
+            {
+                CCstatus.ImageAwbGain = 1;
+            }
+            else
+            {
+                CCstatus.ImageAwbGain = 0;
+            }
+        }
+
+        else if ((toUpper(splitted[0]) == "CAMAEC") && (splitted.size() > 1))
+        {
+            if (toUpper(splitted[1]) == "TRUE")
+            {
+                CCstatus.ImageAec = 1;
+            }
+            else
+            {
+                CCstatus.ImageAec = 0;
+            }
         }
 
-        if ((toUpper(splitted[0]) == "SAVEALLFILES") && (splitted.size() > 1))
+        else if ((toUpper(splitted[0]) == "CAMAEC2") && (splitted.size() > 1))
         {
             if (toUpper(splitted[1]) == "TRUE")
-                SaveAllFiles = true;
+            {
+                CCstatus.ImageAec2 = 1;
+            }
+            else
+            {
+                CCstatus.ImageAec2 = 0;
+            }
+        }
+
+        else if ((toUpper(splitted[0]) == "CAMAELEVEL") && (splitted.size() > 1))
+        {
+            int _ImageAeLevel = std::stoi(splitted[1]);
+            if ((_ImageAeLevel >= -2) && (_ImageAeLevel <= 2))
+            {
+                CCstatus.ImageAeLevel = _ImageAeLevel;
+            }
         }
-        
-        if ((toUpper(splitted[0]) == "WAITBEFORETAKINGPICTURE") && (splitted.size() > 1))
+
+        else if ((toUpper(splitted[0]) == "CAMAECVALUE") && (splitted.size() > 1))
         {
-            waitbeforepicture = stoi(splitted[1]);
+            int _ImageAecValue = std::stoi(splitted[1]);
+            if ((_ImageAecValue >= 0) && (_ImageAecValue <= 1200))
+            {
+                CCstatus.ImageAecValue = _ImageAecValue;
+            }
         }
 
-        if ((toUpper(splitted[0]) == "RAWIMAGESRETENTION") && (splitted.size() > 1))
+        else if ((toUpper(splitted[0]) == "CAMAGC") && (splitted.size() > 1))
         {
-            this->imagesRetention = std::stoi(splitted[1]);
+            if (toUpper(splitted[1]) == "TRUE")
+            {
+                CCstatus.ImageAgc = 1;
+            }
+            else
+            {
+                CCstatus.ImageAgc = 0;
+            }
         }
 
-        if ((toUpper(splitted[0]) == "BRIGHTNESS") && (splitted.size() > 1))
+        else if ((toUpper(splitted[0]) == "CAMAGCGAIN") && (splitted.size() > 1))
         {
-            _brightness = stoi(splitted[1]);
+            int _ImageAgcGain = std::stoi(splitted[1]);
+            if ((_ImageAgcGain >= 0) && (_ImageAgcGain <= 30))
+            {
+                CCstatus.ImageAgcGain = _ImageAgcGain;
+            }
         }
 
-        if ((toUpper(splitted[0]) == "CONTRAST") && (splitted.size() > 1))
+        else if ((toUpper(splitted[0]) == "CAMBPC") && (splitted.size() > 1))
         {
-            _contrast = stoi(splitted[1]);
+            if (toUpper(splitted[1]) == "TRUE")
+            {
+                CCstatus.ImageBpc = 1;
+            }
+            else
+            {
+                CCstatus.ImageBpc = 0;
+            }
         }
 
-        if ((toUpper(splitted[0]) == "SATURATION") && (splitted.size() > 1))
+        else if ((toUpper(splitted[0]) == "CAMWPC") && (splitted.size() > 1))
         {
-            _saturation = stoi(splitted[1]);
+            if (toUpper(splitted[1]) == "TRUE")
+            {
+                CCstatus.ImageWpc = 1;
+            }
+            else
+            {
+                CCstatus.ImageWpc = 0;
+            }
         }
 
-        if ((toUpper(splitted[0]) == "FIXEDEXPOSURE") && (splitted.size() > 1))
+        else if ((toUpper(splitted[0]) == "CAMRAWGMA") && (splitted.size() > 1))
         {
             if (toUpper(splitted[1]) == "TRUE")
-                FixedExposure = true;  
+            {
+                CCstatus.ImageRawGma = 1;
+            }
+            else
+            {
+                CCstatus.ImageRawGma = 0;
+            }
         }
 
-        if ((toUpper(splitted[0]) == "LEDINTENSITY") && (splitted.size() > 1))
+        else if ((toUpper(splitted[0]) == "CAMLENC") && (splitted.size() > 1))
         {
-            float ledintensity = stof(splitted[1]);
-            ledintensity = min((float) 100, ledintensity);
-            ledintensity = max((float) 0, ledintensity);
+            if (toUpper(splitted[1]) == "TRUE")
+            {
+                CCstatus.ImageLenc = 1;
+            }
+            else
+            {
+                CCstatus.ImageLenc = 0;
+            }
+        }
+
+        else if ((toUpper(splitted[0]) == "CAMHMIRROR") && (splitted.size() > 1))
+        {
+            if (toUpper(splitted[1]) == "TRUE")
+            {
+                CCstatus.ImageHmirror = 1;
+            }
+            else
+            {
+                CCstatus.ImageHmirror = 0;
+            }
+        }
+
+        else if ((toUpper(splitted[0]) == "CAMVFLIP") && (splitted.size() > 1))
+        {
+            if (toUpper(splitted[1]) == "TRUE")
+            {
+                CCstatus.ImageVflip = 1;
+            }
+            else
+            {
+                CCstatus.ImageVflip = 0;
+            }
+        }
+
+        else if ((toUpper(splitted[0]) == "CAMDCW") && (splitted.size() > 1))
+        {
+            if (toUpper(splitted[1]) == "TRUE")
+            {
+                CCstatus.ImageDcw = 1;
+            }
+            else
+            {
+                CCstatus.ImageDcw = 0;
+            }
+        }
+
+        else if ((toUpper(splitted[0]) == "CAMZOOM") && (splitted.size() > 1))
+        {
+            if (toUpper(splitted[1]) == "TRUE")
+            {
+                CCstatus.ImageZoomEnabled = 1;
+            }
+            else
+            {
+                CCstatus.ImageZoomEnabled = 0;
+            }
+        }
+
+        else if ((toUpper(splitted[0]) == "CAMZOOMOFFSETX") && (splitted.size() > 1))
+        {
+            CCstatus.ImageZoomOffsetX = std::stoi(splitted[1]);
+        }
+
+        else if ((toUpper(splitted[0]) == "CAMZOOMOFFSETY") && (splitted.size() > 1))
+        {
+            CCstatus.ImageZoomOffsetY = std::stoi(splitted[1]);
+        }
+
+        else if ((toUpper(splitted[0]) == "CAMZOOMSIZE") && (splitted.size() > 1))
+        {
+            int _ImageZoomSize = std::stoi(splitted[1]);
+            if (_ImageZoomSize >= 0)
+            {
+                CCstatus.ImageZoomSize = _ImageZoomSize;
+            }
+        }
+
+        else if ((toUpper(splitted[0]) == "LEDINTENSITY") && (splitted.size() > 1))
+        {
+            float ledintensity = std::stof(splitted[1]);
             Camera.SetLEDIntensity(ledintensity);
         }
 
-        if ((toUpper(splitted[0]) == "DEMO") && (splitted.size() > 1))
+        else if ((toUpper(splitted[0]) == "DEMO") && (splitted.size() > 1))
         {
             if (toUpper(splitted[1]) == "TRUE")
+            {
+                CCstatus.DemoMode = true;
                 Camera.useDemoMode();
+            }
+            else
+            {
+                CCstatus.DemoMode = false;
+            }
         }
     }
 
-    Camera.SetBrightnessContrastSaturation(_brightness, _contrast, _saturation);
-    Camera.SetQualitySize(ImageQuality, ImageSize);
+    Camera.setSensorDatenFromCCstatus(); // CCstatus >>> Kamera
+    Camera.SetQualityZoomSize(CCstatus.ImageQuality, CCstatus.ImageFrameSize, CCstatus.ImageZoomEnabled, CCstatus.ImageZoomOffsetX, CCstatus.ImageZoomOffsetY, CCstatus.ImageZoomSize);
 
-    image_width = Camera.image_width;
-    image_height = Camera.image_height;
     rawImage = new CImageBasis("rawImage");
-    rawImage->CreateEmptyImage(image_width, image_height, 3);
-
-    waitbeforepicture_store = waitbeforepicture;
-    if (FixedExposure && (waitbeforepicture > 0))
-    {
-//        ESP_LOGD(TAG, "Fixed Exposure enabled!");
-        int flash_duration = (int) (waitbeforepicture * 1000);
-        Camera.EnableAutoExposure(flash_duration);
-        waitbeforepicture = 0.2;
-//        flash_duration = (int) (waitbeforepicture * 1000);
-//        takePictureWithFlash(flash_duration);
-//        rawImage->SaveToFile("/sdcard/init2.jpg");
-    }
+    rawImage->CreateEmptyImage(CCstatus.ImageWidth, CCstatus.ImageHeight, 3);
 
     return true;
 }
 
+ClassFlowTakeImage::ClassFlowTakeImage(std::vector<ClassFlow *> *lfc) : ClassFlowImage(lfc, TAG)
+{
+    imagesLocation = "/log/source";
+    imagesRetention = 5;
+    SetInitialParameter();
+}
 
 string ClassFlowTakeImage::getHTMLSingleStep(string host)
 {
@@ -181,74 +517,78 @@ string ClassFlowTakeImage::getHTMLSingleStep(string host)
     return result;
 }
 
-
+// wird bei jeder Auswertrunde aufgerufen
 bool ClassFlowTakeImage::doFlow(string zwtime)
 {
     psram_init_shared_memory_for_take_image_step();
 
     string logPath = CreateLogFolder(zwtime);
 
-    int flash_duration = (int) (waitbeforepicture * 1000);
- 
-    #ifdef DEBUG_DETAIL_ON  
-        LogFile.WriteHeapInfo("ClassFlowTakeImage::doFlow - Before takePictureWithFlash");
-    #endif
+    int flash_duration = (int)(CCstatus.WaitBeforePicture * 1000);
 
+#ifdef DEBUG_DETAIL_ON
+    LogFile.WriteHeapInfo("ClassFlowTakeImage::doFlow - Before takePictureWithFlash");
+#endif
 
-    #ifdef WIFITURNOFF
-        esp_wifi_stop();        // to save power usage and 
-    #endif
+#ifdef WIFITURNOFF
+    esp_wifi_stop(); // to save power usage and
+#endif
 
-    takePictureWithFlash(flash_duration);
+    // wenn die Kameraeinstellungen durch Erstellen eines neuen Referenzbildes verändert wurden, müssen sie neu gesetzt werden
+    if (CFstatus.changedCameraSettings)
+    {
+        Camera.setSensorDatenFromCCstatus(); // CCstatus >>> Kamera
+        Camera.SetQualityZoomSize(CCstatus.ImageQuality, CCstatus.ImageFrameSize, CCstatus.ImageZoomEnabled, CCstatus.ImageZoomOffsetX, CCstatus.ImageZoomOffsetY, CCstatus.ImageZoomSize);
+        CFstatus.changedCameraSettings = false;
+    }
 
-    #ifdef WIFITURNOFF
-        esp_wifi_start();
-    #endif
+    takePictureWithFlash(flash_duration);
 
+#ifdef WIFITURNOFF
+    esp_wifi_start();
+#endif
 
-    #ifdef DEBUG_DETAIL_ON  
-        LogFile.WriteHeapInfo("ClassFlowTakeImage::doFlow - After takePictureWithFlash");
-    #endif
+#ifdef DEBUG_DETAIL_ON
+    LogFile.WriteHeapInfo("ClassFlowTakeImage::doFlow - After takePictureWithFlash");
+#endif
 
     LogImage(logPath, "raw", NULL, NULL, zwtime, rawImage);
 
     RemoveOldLogs();
 
-    #ifdef DEBUG_DETAIL_ON  
-        LogFile.WriteHeapInfo("ClassFlowTakeImage::doFlow - After RemoveOldLogs");
-    #endif
+#ifdef DEBUG_DETAIL_ON
+    LogFile.WriteHeapInfo("ClassFlowTakeImage::doFlow - After RemoveOldLogs");
+#endif
 
     psram_deinit_shared_memory_for_take_image_step();
 
     return true;
 }
 
-
 esp_err_t ClassFlowTakeImage::SendRawJPG(httpd_req_t *req)
 {
-    int flash_duration = (int) (waitbeforepicture * 1000);
+    int flash_duration = (int)(CCstatus.WaitBeforePicture * 1000);
     time(&TimeImageTaken);
     localtime(&TimeImageTaken);
 
     return Camera.CaptureToHTTP(req, flash_duration);
 }
 
-
-ImageData* ClassFlowTakeImage::SendRawImage()
+ImageData *ClassFlowTakeImage::SendRawImage(void)
 {
     CImageBasis *zw = new CImageBasis("SendRawImage", rawImage);
     ImageData *id;
-    int flash_duration = (int) (waitbeforepicture * 1000);
+    int flash_duration = (int)(CCstatus.WaitBeforePicture * 1000);
     Camera.CaptureToBasisImage(zw, flash_duration);
     time(&TimeImageTaken);
     localtime(&TimeImageTaken);
 
-    id = zw->writeToMemoryAsJPG();    
+    id = zw->writeToMemoryAsJPG();
     delete zw;
-    return id;  
+    return id;
 }
 
-time_t ClassFlowTakeImage::getTimeImageTaken()
+time_t ClassFlowTakeImage::getTimeImageTaken(void)
 {
     return TimeImageTaken;
 }
@@ -257,4 +597,3 @@ ClassFlowTakeImage::~ClassFlowTakeImage(void)
 {
     delete rawImage;
 }
-

+ 9 - 24
code/components/jomjol_flowcontroll/ClassFlowTakeImage.h

@@ -9,47 +9,32 @@
 
 #include <string>
 
-class ClassFlowTakeImage :
-    public ClassFlowImage
+class ClassFlowTakeImage : public ClassFlowImage
 {
 protected:
-    float waitbeforepicture;
-    float waitbeforepicture_store;
-    framesize_t ImageSize;
-    bool isImageSize;
-    int ImageQuality;
     time_t TimeImageTaken;
     string namerawimage;
-    int image_height, image_width;
-    bool SaveAllFiles;
-    bool FixedExposure;
 
-
-
-    void CopyFile(string input, string output);
-
-    esp_err_t camera_capture();
+    esp_err_t camera_capture(void);
     void takePictureWithFlash(int flash_duration);
 
-
-    void SetInitialParameter(void);       
+    void SetInitialParameter(void);
 
 public:
     CImageBasis *rawImage;
 
-    ClassFlowTakeImage(std::vector<ClassFlow*>* lfc);
+    ClassFlowTakeImage(std::vector<ClassFlow *> *lfc);
 
-    bool ReadParameter(FILE* pfile, string& aktparamgraph);
+    bool ReadParameter(FILE *pfile, string &aktparamgraph);
     bool doFlow(string time);
     string getHTMLSingleStep(string host);
-    time_t getTimeImageTaken();
-    string name(){return "ClassFlowTakeImage";};
+    time_t getTimeImageTaken(void);
+    string name() { return "ClassFlowTakeImage"; };
 
-    ImageData* SendRawImage();
+    ImageData *SendRawImage(void);
     esp_err_t SendRawJPG(httpd_req_t *req);
 
     ~ClassFlowTakeImage(void);
 };
 
-
-#endif //CLASSFFLOWTAKEIMAGE_H
+#endif // CLASSFFLOWTAKEIMAGE_H

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 801 - 336
code/components/jomjol_flowcontroll/MainFlowControl.cpp


+ 61 - 8
code/components/jomjol_flowcontroll/MainFlowControl.h

@@ -10,25 +10,78 @@
 #include "CImageBasis.h"
 #include "ClassFlowControll.h"
 
+typedef struct
+{
+    framesize_t ImageFrameSize = FRAMESIZE_VGA; // 0 - 10
+    gainceiling_t ImageGainceiling; // Image gain (GAINCEILING_x2, x4, x8, x16, x32, x64 or x128)
+
+    int ImageQuality;    // 0 - 63
+    int ImageBrightness; // (-2 to 2) - set brightness
+    int ImageContrast;   //-2 - 2
+    int ImageSaturation; //-2 - 2
+    int ImageSharpness;  //-2 - 2
+    bool ImageAutoSharpness;
+    int ImageSpecialEffect; // 0 - 6
+    int ImageWbMode;        // 0 to 4 - if awb_gain enabled (0 - Auto, 1 - Sunny, 2 - Cloudy, 3 - Office, 4 - Home)
+    int ImageAwb;           // white balance enable (0 or 1)
+    int ImageAwbGain;       // Auto White Balance enable (0 or 1)
+    int ImageAec;           // auto exposure off (1 or 0)
+    int ImageAec2;          // automatic exposure sensor  (0 or 1)
+    int ImageAeLevel;       // auto exposure levels (-2 to 2)
+    int ImageAecValue;      // set exposure manually  (0-1200)
+    int ImageAgc;           // auto gain off (1 or 0)
+    int ImageAgcGain;       // set gain manually (0 - 30)
+    int ImageBpc;           // black pixel correction
+    int ImageWpc;           // white pixel correction
+    int ImageRawGma;        // (1 or 0)
+    int ImageLenc;          // lens correction (1 or 0)
+    int ImageHmirror;       // (0 or 1) flip horizontally
+    int ImageVflip;         // Invert image (0 or 1)
+    int ImageDcw;           // downsize enable (1 or 0)
+
+    int ImageWidth;
+    int ImageHeight;
+
+    int ImageLedIntensity;
+
+    bool ImageZoomEnabled;
+    int ImageZoomMode;
+    int ImageZoomOffsetX;
+    int ImageZoomOffsetY;
+    int ImageZoomSize;
+
+    int WaitBeforePicture;
+    bool isImageSize;
+
+    bool CameraInitSuccessful;
+    bool changedCameraSettings;
+    bool DemoMode;
+    bool SaveAllFiles;
+} camera_flow_config_temp_t;
+
+extern camera_flow_config_temp_t CFstatus;
 extern ClassFlowControll flowctrl;
 
+esp_err_t setCCstatusToCFstatus(void); // CCstatus >>> CFstatus
+esp_err_t setCFstatusToCCstatus(void); // CFstatus >>> CCstatus
+esp_err_t setCFstatusToCam(void);      // CFstatus >>> Kamera
 
 void register_server_main_flow_task_uri(httpd_handle_t server);
 
-void CheckIsPlannedReboot();
-bool getIsPlannedReboot();
+void CheckIsPlannedReboot(void);
+bool getIsPlannedReboot(void);
 
-void InitializeFlowTask();
-void DeleteMainFlowTask();
-bool isSetupModusActive();
+void InitializeFlowTask(void);
+void DeleteMainFlowTask(void);
+bool isSetupModusActive(void);
 
-int getCountFlowRounds();
+int getCountFlowRounds(void);
 
 #ifdef ENABLE_MQTT
 esp_err_t MQTTCtrlFlowStart(std::string _topic);
-#endif //ENABLE_MQTT
+#endif // ENABLE_MQTT
 
 esp_err_t GetRawJPG(httpd_req_t *req);
 esp_err_t GetJPG(std::string _filename, httpd_req_t *req);
 
-#endif //MAINFLOWCONTROL_H
+#endif // MAINFLOWCONTROL_H

+ 1 - 1
code/components/jomjol_helper/CMakeLists.txt

@@ -2,6 +2,6 @@ FILE(GLOB_RECURSE app_sources ${CMAKE_CURRENT_SOURCE_DIR}/*.*)
 
 idf_component_register(SRCS ${app_sources}
                     INCLUDE_DIRS "."
-                    REQUIRES esp_timer esp-tflite-micro jomjol_logfile fatfs sdmmc)
+                    REQUIRES esp_timer esp-tflite-micro jomjol_logfile fatfs sdmmc vfs)
 
 

+ 624 - 414
code/components/jomjol_helper/Helper.cpp

@@ -1,4 +1,4 @@
-//#pragma warning(disable : 4996)
+// #pragma warning(disable : 4996)
 
 #include "freertos/FreeRTOS.h"
 #include "freertos/task.h"
@@ -14,7 +14,8 @@
 #include <math.h>
 
 #ifdef __cplusplus
-extern "C" {
+extern "C"
+{
 #endif
 #include <dirent.h>
 #ifdef __cplusplus
@@ -27,12 +28,12 @@ extern "C" {
 #include <esp_timer.h>
 #include "../../include/defines.h"
 
-
 #include "ClassLogFile.h"
 
 #include "esp_vfs_fat.h"
+#include "../sdmmc_common.h"
 
-static const char* TAG = "HELPER";
+static const char *TAG = "HELPER";
 
 using namespace std;
 
@@ -40,191 +41,207 @@ unsigned int systemStatus = 0;
 
 sdmmc_cid_t SDCardCid;
 sdmmc_csd_t SDCardCsd;
+bool SDCardIsMMC;
 
-
-// #define DEBUG_DETAIL_ON 
+// #define DEBUG_DETAIL_ON
 
 /////////////////////////////////////////////////////////////////////////////////////////////
-string getESPHeapInfo(){
+string getESPHeapInfo()
+{
 	string espInfoResultStr = "";
 	char aMsgBuf[80];
 
-	size_t aFreeHeapSize  = heap_caps_get_free_size(MALLOC_CAP_8BIT);
+	size_t aFreeHeapSize = heap_caps_get_free_size(MALLOC_CAP_8BIT);
 
-	size_t aFreeSPIHeapSize  = heap_caps_get_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM);
-	size_t aFreeInternalHeapSize  = heap_caps_get_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
+	size_t aFreeSPIHeapSize = heap_caps_get_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM);
+	size_t aFreeInternalHeapSize = heap_caps_get_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
 
 	size_t aHeapLargestFreeBlockSize = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM);
 	size_t aHeapIntLargestFreeBlockSize = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
 
-	size_t aMinFreeHeapSize =  heap_caps_get_minimum_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM);
-	size_t aMinFreeInternalHeapSize =  heap_caps_get_minimum_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
-
+	size_t aMinFreeHeapSize = heap_caps_get_minimum_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM);
+	size_t aMinFreeInternalHeapSize = heap_caps_get_minimum_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
 
-	sprintf(aMsgBuf,"Heap Total: %ld", (long) aFreeHeapSize);
+	sprintf(aMsgBuf, "Heap Total: %ld", (long)aFreeHeapSize);
 	espInfoResultStr += string(aMsgBuf);
 
-	sprintf(aMsgBuf," | SPI Free: %ld", (long) aFreeSPIHeapSize);
+	sprintf(aMsgBuf, " | SPI Free: %ld", (long)aFreeSPIHeapSize);
 	espInfoResultStr += string(aMsgBuf);
-	sprintf(aMsgBuf," | SPI Large Block:  %ld", (long) aHeapLargestFreeBlockSize);
+	sprintf(aMsgBuf, " | SPI Large Block:  %ld", (long)aHeapLargestFreeBlockSize);
 	espInfoResultStr += string(aMsgBuf);
-	sprintf(aMsgBuf," | SPI Min Free: %ld", (long) aMinFreeHeapSize);
+	sprintf(aMsgBuf, " | SPI Min Free: %ld", (long)aMinFreeHeapSize);
 	espInfoResultStr += string(aMsgBuf);
 
-	sprintf(aMsgBuf," | Int Free: %ld", (long) (aFreeInternalHeapSize));
+	sprintf(aMsgBuf, " | Int Free: %ld", (long)(aFreeInternalHeapSize));
 	espInfoResultStr += string(aMsgBuf);
-	sprintf(aMsgBuf," | Int Large Block:  %ld", (long) aHeapIntLargestFreeBlockSize);
+	sprintf(aMsgBuf, " | Int Large Block:  %ld", (long)aHeapIntLargestFreeBlockSize);
 	espInfoResultStr += string(aMsgBuf);
-	sprintf(aMsgBuf," | Int Min Free: %ld", (long) (aMinFreeInternalHeapSize));
+	sprintf(aMsgBuf, " | Int Min Free: %ld", (long)(aMinFreeInternalHeapSize));
 	espInfoResultStr += string(aMsgBuf);
-	
-	return 	espInfoResultStr;
-}
 
+	return espInfoResultStr;
+}
 
 size_t getESPHeapSize()
 {
-   return heap_caps_get_free_size(MALLOC_CAP_8BIT);
+	return heap_caps_get_free_size(MALLOC_CAP_8BIT);
 }
 
-
-size_t getInternalESPHeapSize() 
+size_t getInternalESPHeapSize()
 {
-	return heap_caps_get_free_size(MALLOC_CAP_8BIT| MALLOC_CAP_INTERNAL);
+	return heap_caps_get_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
 }
 
-
-string getSDCardPartitionSize(){
+string getSDCardPartitionSize()
+{
 	FATFS *fs;
-    uint32_t fre_clust, tot_sect;
+	uint32_t fre_clust, tot_sect;
 
-    /* Get volume information and free clusters of drive 0 */
-    f_getfree("0:", (DWORD *)&fre_clust, &fs);
-    tot_sect = ((fs->n_fatent - 2) * fs->csize) /1024 /(1024/SDCardCsd.sector_size);	//corrected by SD Card sector size (usually 512 bytes) and convert to MB
+	/* Get volume information and free clusters of drive 0 */
+	f_getfree("0:", (DWORD *)&fre_clust, &fs);
+	tot_sect = ((fs->n_fatent - 2) * fs->csize) / 1024 / (1024 / SDCardCsd.sector_size); // corrected by SD Card sector size (usually 512 bytes) and convert to MB
 
-	//ESP_LOGD(TAG, "%d MB total drive space (Sector size [bytes]: %d)", (int)tot_sect, (int)fs->ssize);
+	// ESP_LOGD(TAG, "%d MB total drive space (Sector size [bytes]: %d)", (int)tot_sect, (int)fs->ssize);
 
 	return std::to_string(tot_sect);
 }
 
-
-string getSDCardFreePartitionSpace(){
+string getSDCardFreePartitionSpace()
+{
 	FATFS *fs;
-    uint32_t fre_clust, fre_sect;
-  
-    /* Get volume information and free clusters of drive 0 */
-    f_getfree("0:", (DWORD *)&fre_clust, &fs);
-    fre_sect = (fre_clust * fs->csize) / 1024 /(1024/SDCardCsd.sector_size);	//corrected by SD Card sector size (usually 512 bytes) and convert to MB
+	uint32_t fre_clust, fre_sect;
+
+	/* Get volume information and free clusters of drive 0 */
+	f_getfree("0:", (DWORD *)&fre_clust, &fs);
+	fre_sect = (fre_clust * fs->csize) / 1024 / (1024 / SDCardCsd.sector_size); // corrected by SD Card sector size (usually 512 bytes) and convert to MB
 
-    //ESP_LOGD(TAG, "%d MB free drive space (Sector size [bytes]: %d)", (int)fre_sect, (int)fs->ssize);
+	// ESP_LOGD(TAG, "%d MB free drive space (Sector size [bytes]: %d)", (int)fre_sect, (int)fs->ssize);
 
 	return std::to_string(fre_sect);
 }
 
-
-string getSDCardPartitionAllocationSize(){
+string getSDCardPartitionAllocationSize()
+{
 	FATFS *fs;
-    uint32_t fre_clust, allocation_size;
-  
-    /* Get volume information and free clusters of drive 0 */
-    f_getfree("0:", (DWORD *)&fre_clust, &fs);
-    allocation_size = fs->ssize;
+	uint32_t fre_clust, allocation_size;
 
-    //ESP_LOGD(TAG, "SD Card Partition Allocation Size: %d bytes", allocation_size);
+	/* Get volume information and free clusters of drive 0 */
+	f_getfree("0:", (DWORD *)&fre_clust, &fs);
+	allocation_size = fs->ssize;
+
+	// ESP_LOGD(TAG, "SD Card Partition Allocation Size: %d bytes", allocation_size);
 
 	return std::to_string(allocation_size);
 }
 
-
-void SaveSDCardInfo(sdmmc_card_t* card) {
+void SaveSDCardInfo(sdmmc_card_t *card)
+{
 	SDCardCid = card->cid;
-    SDCardCsd = card->csd;
+	SDCardCsd = card->csd;
+	SDCardIsMMC = card->is_mmc;
 }
 
-
-string getSDCardManufacturer(){
+string getSDCardManufacturer()
+{
 	string SDCardManufacturer = SDCardParseManufacturerIDs(SDCardCid.mfg_id);
-	//ESP_LOGD(TAG, "SD Card Manufacturer: %s", SDCardManufacturer.c_str());
-	
+	// ESP_LOGD(TAG, "SD Card Manufacturer: %s", SDCardManufacturer.c_str());
+
 	return (SDCardManufacturer + " (ID: " + std::to_string(SDCardCid.mfg_id) + ")");
 }
 
-
-string getSDCardName(){
+string getSDCardName()
+{
 	char *SDCardName = SDCardCid.name;
-	//ESP_LOGD(TAG, "SD Card Name: %s", SDCardName); 
+	// ESP_LOGD(TAG, "SD Card Name: %s", SDCardName);
 
 	return std::string(SDCardName);
 }
 
-
-string getSDCardCapacity(){
-	int SDCardCapacity = SDCardCsd.capacity / (1024/SDCardCsd.sector_size) / 1024;  // total sectors * sector size  --> Byte to MB (1024*1024)
-	//ESP_LOGD(TAG, "SD Card Capacity: %s", std::to_string(SDCardCapacity).c_str()); 
+string getSDCardCapacity()
+{
+	int SDCardCapacity = SDCardCsd.capacity / (1024 / SDCardCsd.sector_size) / 1024; // total sectors * sector size  --> Byte to MB (1024*1024)
+	// ESP_LOGD(TAG, "SD Card Capacity: %s", std::to_string(SDCardCapacity).c_str());
 
 	return std::to_string(SDCardCapacity);
 }
 
-
-string getSDCardSectorSize(){
+string getSDCardSectorSize()
+{
 	int SDCardSectorSize = SDCardCsd.sector_size;
-	//ESP_LOGD(TAG, "SD Card Sector Size: %s bytes", std::to_string(SDCardSectorSize).c_str()); 
+	// ESP_LOGD(TAG, "SD Card Sector Size: %s bytes", std::to_string(SDCardSectorSize).c_str());
 
 	return std::to_string(SDCardSectorSize);
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////
 
-void memCopyGen(uint8_t* _source, uint8_t* _target, int _size)
+void memCopyGen(uint8_t *_source, uint8_t *_target, int _size)
 {
-    for (int i = 0; i < _size; ++i)
-        *(_target + i) = *(_source + i);
+	for (int i = 0; i < _size; ++i)
+	{
+		*(_target + i) = *(_source + i);
+	}
 }
 
-
 std::string FormatFileName(std::string input)
 {
 #ifdef ISWINDOWS_TRUE
-    input.erase(0, 1);
-    std::string os = "/";
-    std::string ns = "\\";
-    FindReplace(input, os, ns);
+	input.erase(0, 1);
+	std::string os = "/";
+	std::string ns = "\\";
+	FindReplace(input, os, ns);
 #endif
-    return input;
+	return input;
 }
 
+std::size_t file_size(const std::string &file_name)
+{
+	std::ifstream file(file_name.c_str(), std::ios::in | std::ios::binary);
 
-std::size_t file_size(const std::string& file_name) {
-    std::ifstream file(file_name.c_str(),std::ios::in | std::ios::binary);
-    if (!file) return 0;
-    file.seekg (0, std::ios::end);
-    return static_cast<std::size_t>(file.tellg());
+	if (!file)
+	{
+		return 0;
+	}
+
+	file.seekg(0, std::ios::end);
+	return static_cast<std::size_t>(file.tellg());
 }
 
+void FindReplace(std::string &line, std::string &oldString, std::string &newString)
+{
+	const size_t oldSize = oldString.length();
 
-void FindReplace(std::string& line, std::string& oldString, std::string& newString) {
-    const size_t oldSize = oldString.length();
+	// do nothing if line is shorter than the string to find
+	if (oldSize > line.length())
+	{
+		return;
+	}
 
-    // do nothing if line is shorter than the string to find
-    if (oldSize > line.length()) return;
+	const size_t newSize = newString.length();
 
-    const size_t newSize = newString.length();
-    for (size_t pos = 0; ; pos += newSize) {
-        // Locate the substring to replace
-        pos = line.find(oldString, pos);
-        if (pos == std::string::npos) return;
-        if (oldSize == newSize) {
-            // if they're same size, use std::string::replace
-            line.replace(pos, oldSize, newString);
-        }
-        else {
-            // if not same size, replace by erasing and inserting
-            line.erase(pos, oldSize);
-            line.insert(pos, newString);
-        }
-    }
-}
+	for (size_t pos = 0;; pos += newSize)
+	{
+		// Locate the substring to replace
+		pos = line.find(oldString, pos);
 
+		if (pos == std::string::npos)
+		{
+			return;
+		}
+
+		if (oldSize == newSize)
+		{
+			// if they're same size, use std::string::replace
+			line.replace(pos, oldSize, newString);
+		}
+		else
+		{
+			// if not same size, replace by erasing and inserting
+			line.erase(pos, oldSize);
+			line.insert(pos, newString);
+		}
+	}
+}
 
 /**
  * Create a folder and its parent folders as needed
@@ -236,56 +253,63 @@ bool MakeDir(std::string path)
 	LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Creating folder " + path + "...");
 
 	bool bSuccess = false;
-    int nRC = ::mkdir( path.c_str(), 0775 );
-    if( nRC == -1 )
-    {
-        switch( errno ) {
-            case ENOENT:
-                //parent didn't exist, try to create it
-				parent = path.substr(0, path.find_last_of('/'));
-        		LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Need to create parent folder first: " + parent);
-                if(MakeDir(parent)) {
-                    //Now, try to create again.
-                    bSuccess = 0 == ::mkdir( path.c_str(), 0775 );
-				}
-                else {
-        			LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to create parent folder: " + parent);
-                    bSuccess = false;
-				}
-                break;
+	int nRC = ::mkdir(path.c_str(), 0775);
+
+	if (nRC == -1)
+	{
+		switch (errno)
+		{
+		case ENOENT:
+			// parent didn't exist, try to create it
+			parent = path.substr(0, path.find_last_of('/'));
+			LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Need to create parent folder first: " + parent);
+
+			if (MakeDir(parent))
+			{
+				// Now, try to create again.
+				bSuccess = 0 == ::mkdir(path.c_str(), 0775);
+			}
+			else
+			{
+				LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to create parent folder: " + parent);
+				bSuccess = false;
+			}
+			break;
+
+		case EEXIST:
+			// Done!
+			bSuccess = true;
+			break;
 
-            case EEXIST:
-                //Done!
-                bSuccess = true;
-                break;
-				
-            default:
-				LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to create folder: " + path + " (errno: " + std::to_string(errno) + ")");
-                bSuccess = false;
-                break;
-        }
-    }
-    else {
-        bSuccess = true;
+		default:
+			LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to create folder: " + path + " (errno: " + std::to_string(errno) + ")");
+			bSuccess = false;
+			break;
+		}
+	}
+	else
+	{
+		bSuccess = true;
 	}
 
-    return bSuccess;
+	return bSuccess;
 }
 
-
 bool ctype_space(const char c, string adddelimiter)
 {
 	if (c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == 11)
 	{
 		return true;
 	}
+
 	if (adddelimiter.find(c) != string::npos)
+	{
 		return true;
+	}
 
 	return false;
 }
 
-
 string trim(string istring, string adddelimiter)
 {
 	bool trimmed = false;
@@ -312,76 +336,87 @@ string trim(string istring, string adddelimiter)
 	}
 }
 
-
 size_t findDelimiterPos(string input, string delimiter)
 {
 	size_t pos = std::string::npos;
-	size_t zw;
+	// size_t zw;
 	string akt_del;
 
 	for (int anz = 0; anz < delimiter.length(); ++anz)
 	{
 		akt_del = delimiter[anz];
-		if ((zw = input.find(akt_del)) != std::string::npos)
+		size_t zw = input.find(akt_del);
+
+		if (zw != std::string::npos)
 		{
-			if (pos != std::string::npos)
+			if ((pos != std::string::npos) && (zw < pos))
 			{
-				if (zw < pos)
-					pos = zw;
+				pos = zw;
 			}
 			else
+			{
 				pos = zw;
+			}
 		}
 	}
+
 	return pos;
 }
 
-
 bool RenameFile(string from, string to)
 {
-//	ESP_LOGI(logTag, "Deleting file: %s", fn.c_str());
+	//	ESP_LOGI(logTag, "Deleting file: %s", fn.c_str());
 	/* Delete file */
-	FILE* fpSourceFile = fopen(from.c_str(), "rb");
-	if (!fpSourceFile)	// Sourcefile existiert nicht sonst gibt es einen Fehler beim Kopierversuch!
+	FILE *fpSourceFile = fopen(from.c_str(), "rb");
+
+	// Sourcefile existiert nicht sonst gibt es einen Fehler beim Kopierversuch!
+	if (!fpSourceFile)
 	{
 		ESP_LOGE(TAG, "DeleteFile: File %s existiert nicht!", from.c_str());
 		return false;
 	}
+
 	fclose(fpSourceFile);
 
 	rename(from.c_str(), to.c_str());
+
 	return true;
 }
 
-
 bool FileExists(string filename)
 {
-	FILE* fpSourceFile = fopen(filename.c_str(), "rb");
-	if (!fpSourceFile)	// Sourcefile existiert nicht sonst gibt es einen Fehler beim Kopierversuch!
+	FILE *fpSourceFile = fopen(filename.c_str(), "rb");
+
+	// Sourcefile existiert nicht sonst gibt es einen Fehler beim Kopierversuch!
+	if (!fpSourceFile)
 	{
 		return false;
 	}
+
 	fclose(fpSourceFile);
-	return true;    
-}
 
+	return true;
+}
 
 bool DeleteFile(string fn)
 {
-//	ESP_LOGI(logTag, "Deleting file: %s", fn.c_str());
+	//	ESP_LOGI(logTag, "Deleting file: %s", fn.c_str());
 	/* Delete file */
-	FILE* fpSourceFile = fopen(fn.c_str(), "rb");
-	if (!fpSourceFile)	// Sourcefile existiert nicht sonst gibt es einen Fehler beim Kopierversuch!
+	FILE *fpSourceFile = fopen(fn.c_str(), "rb");
+
+	// Sourcefile existiert nicht sonst gibt es einen Fehler beim Kopierversuch!
+	if (!fpSourceFile)
 	{
 		ESP_LOGD(TAG, "DeleteFile: File %s existiert nicht!", fn.c_str());
 		return false;
 	}
+
 	fclose(fpSourceFile);
 
 	unlink(fn.c_str());
-	return true;    
-}
 
+	return true;
+}
 
 bool CopyFile(string input, string output)
 {
@@ -395,14 +430,16 @@ bool CopyFile(string input, string output)
 	}
 
 	char cTemp;
-	FILE* fpSourceFile = fopen(input.c_str(), "rb");
-	if (!fpSourceFile)	// Sourcefile existiert nicht sonst gibt es einen Fehler beim Kopierversuch!
+	FILE *fpSourceFile = fopen(input.c_str(), "rb");
+
+	// Sourcefile existiert nicht sonst gibt es einen Fehler beim Kopierversuch!
+	if (!fpSourceFile)
 	{
 		ESP_LOGD(TAG, "File %s existiert nicht!", input.c_str());
 		return false;
 	}
 
-	FILE* fpTargetFile = fopen(output.c_str(), "wb");
+	FILE *fpTargetFile = fopen(output.c_str(), "wb");
 
 	// Code Section
 
@@ -417,53 +454,60 @@ bool CopyFile(string input, string output)
 	fclose(fpSourceFile);
 	fclose(fpTargetFile);
 	ESP_LOGD(TAG, "File copied: %s to %s", input.c_str(), output.c_str());
+
 	return true;
 }
 
-
 string getFileFullFileName(string filename)
 {
 	size_t lastpos = filename.find_last_of('/');
 
 	if (lastpos == string::npos)
+	{
 		return "";
+	}
 
-//	ESP_LOGD(TAG, "Last position: %d", lastpos);
+	//	ESP_LOGD(TAG, "Last position: %d", lastpos);
 
 	string zw = filename.substr(lastpos + 1, filename.size() - lastpos);
 
 	return zw;
 }
 
-
 string getDirectory(string filename)
 {
 	size_t lastpos = filename.find('/');
 
 	if (lastpos == string::npos)
+	{
 		lastpos = filename.find('\\');
+	}
 
 	if (lastpos == string::npos)
+	{
 		return "";
+	}
 
-//	ESP_LOGD(TAG, "Directory: %d", lastpos);
+	//	ESP_LOGD(TAG, "Directory: %d", lastpos);
 
 	string zw = filename.substr(0, lastpos - 1);
 	return zw;
 }
 
-
 string getFileType(string filename)
 {
 	size_t lastpos = filename.rfind(".", filename.length());
 	size_t neu_pos;
+
 	while ((neu_pos = filename.find(".", lastpos + 1)) > -1)
 	{
 		lastpos = neu_pos;
 	}
 
 	if (lastpos == string::npos)
+	{
 		return "";
+	}
 
 	string zw = filename.substr(lastpos + 1, filename.size() - lastpos);
 	zw = toUpper(zw);
@@ -471,172 +515,213 @@ string getFileType(string filename)
 	return zw;
 }
 
-
 /* recursive mkdir */
-int mkdir_r(const char *dir, const mode_t mode) {
-    char tmp[FILE_PATH_MAX];
-    char *p = NULL;
-    struct stat sb;
-    size_t len;
-    
-    /* copy path */
-    len = strnlen (dir, FILE_PATH_MAX);
-    if (len == 0 || len == FILE_PATH_MAX) {
-        return -1;
-    }
-    memcpy (tmp, dir, len);
-    tmp[len] = '\0';
-
-    /* remove trailing slash */
-    if(tmp[len - 1] == '/') {
-        tmp[len - 1] = '\0';
-    }
-
-    /* check if path exists and is a directory */
-    if (stat (tmp, &sb) == 0) {
-        if (S_ISDIR (sb.st_mode)) {
-            return 0;
-        }
-    }
-    
-    /* recursive mkdir */
-    for(p = tmp + 1; *p; p++) {
-        if(*p == '/') {
-            *p = 0;
-            /* test path */
-            if (stat(tmp, &sb) != 0) {
-                /* path does not exist - create directory */
-                if (mkdir(tmp, mode) < 0) {
-                    return -1;
-                }
-            } else if (!S_ISDIR(sb.st_mode)) {
-                /* not a directory */
-                return -1;
-            }
-            *p = '/';
-        }
-    }
-    /* test path */
-    if (stat(tmp, &sb) != 0) {
-        /* path does not exist - create directory */
-        if (mkdir(tmp, mode) < 0) {
-            return -1;
-        }
-    } else if (!S_ISDIR(sb.st_mode)) {
-        /* not a directory */
-        return -1;
-    }
-    return 0;
-}
+int mkdir_r(const char *dir, const mode_t mode)
+{
+	char tmp[FILE_PATH_MAX];
+	char *p = NULL;
+	struct stat sb;
+	size_t len;
 
+	/* copy path */
+	len = strnlen(dir, FILE_PATH_MAX);
+
+	if (len == 0 || len == FILE_PATH_MAX)
+	{
+		return -1;
+	}
+
+	memcpy(tmp, dir, len);
+	tmp[len] = '\0';
+
+	/* remove trailing slash */
+	if (tmp[len - 1] == '/')
+	{
+		tmp[len - 1] = '\0';
+	}
+
+	/* check if path exists and is a directory */
+	if (stat(tmp, &sb) == 0)
+	{
+		if (S_ISDIR(sb.st_mode))
+		{
+			return 0;
+		}
+	}
+
+	/* recursive mkdir */
+	for (p = tmp + 1; *p; p++)
+	{
+		if (*p == '/')
+		{
+			*p = 0;
+
+			/* test path */
+			if (stat(tmp, &sb) != 0)
+			{
+				/* path does not exist - create directory */
+				if (mkdir(tmp, mode) < 0)
+				{
+					return -1;
+				}
+			}
+			else if (!S_ISDIR(sb.st_mode))
+			{
+				/* not a directory */
+				return -1;
+			}
+
+			*p = '/';
+		}
+	}
+
+	/* test path */
+	if (stat(tmp, &sb) != 0)
+	{
+		/* path does not exist - create directory */
+		if (mkdir(tmp, mode) < 0)
+		{
+			return -1;
+		}
+	}
+	else if (!S_ISDIR(sb.st_mode))
+	{
+		/* not a directory */
+		return -1;
+	}
+
+	return 0;
+}
 
 string toUpper(string in)
 {
 	for (int i = 0; i < in.length(); ++i)
+	{
 		in[i] = toupper(in[i]);
-	
+	}
+
 	return in;
 }
 
-
 string toLower(string in)
 {
 	for (int i = 0; i < in.length(); ++i)
+	{
 		in[i] = tolower(in[i]);
-	
+	}
+
 	return in;
 }
 
-
 // CPU Temp
 extern "C" uint8_t temprature_sens_read();
 float temperatureRead()
 {
-    return (temprature_sens_read() - 32) / 1.8;
+	return (temprature_sens_read() - 32) / 1.8;
 }
 
-
-time_t addDays(time_t startTime, int days) {
-	struct tm* tm = localtime(&startTime);
+time_t addDays(time_t startTime, int days)
+{
+	struct tm *tm = localtime(&startTime);
 	tm->tm_mday += days;
 	return mktime(tm);
 }
 
-
-int removeFolder(const char* folderPath, const char* logTag) {
-	//ESP_LOGD(logTag, "Delete content in path %s", folderPath);
+int removeFolder(const char *folderPath, const char *logTag)
+{
+	// ESP_LOGD(logTag, "Delete content in path %s", folderPath);
 
 	DIR *dir = opendir(folderPath);
-    if (!dir) {
-        ESP_LOGE(logTag, "Failed to stat dir: %s", folderPath);
-        return -1;
-    }
-
-    struct dirent *entry;
-    int deleted = 0;
-    while ((entry = readdir(dir)) != NULL) {
-        std::string path = string(folderPath) + "/" + entry->d_name;
-		if (entry->d_type == DT_REG) {
-			//ESP_LOGD(logTag, "Delete file %s", path.c_str());
-			if (unlink(path.c_str()) == 0) {
-				deleted ++;
-			} else {
+
+	if (!dir)
+	{
+		ESP_LOGE(logTag, "Failed to stat dir: %s", folderPath);
+		return -1;
+	}
+
+	struct dirent *entry;
+	int deleted = 0;
+
+	while ((entry = readdir(dir)) != NULL)
+	{
+		std::string path = string(folderPath) + "/" + entry->d_name;
+
+		if (entry->d_type == DT_REG)
+		{
+			// ESP_LOGD(logTag, "Delete file %s", path.c_str());
+			if (unlink(path.c_str()) == 0)
+			{
+				deleted++;
+			}
+			else
+			{
 				ESP_LOGE(logTag, "can't delete file: %s", path.c_str());
 			}
-        } else if (entry->d_type == DT_DIR) {
+		}
+		else if (entry->d_type == DT_DIR)
+		{
 			deleted += removeFolder(path.c_str(), logTag);
 		}
-    }
-    
-    closedir(dir);
-	if (rmdir(folderPath) != 0) {
+	}
+
+	closedir(dir);
+
+	if (rmdir(folderPath) != 0)
+	{
 		ESP_LOGE(logTag, "can't delete folder: %s", folderPath);
 	}
+
 	ESP_LOGD(logTag, "%d files in folder %s deleted.", deleted, folderPath);
 
 	return deleted;
 }
 
-
 std::vector<string> HelperZerlegeZeile(std::string input, std::string _delimiter = "")
 {
 	std::vector<string> Output;
 	std::string delimiter = " =,";
-    if (_delimiter.length() > 0){
-        delimiter = _delimiter;
-    }
+
+	if (_delimiter.length() > 0)
+	{
+		delimiter = _delimiter;
+	}
 
 	return ZerlegeZeile(input, delimiter);
 }
 
-
 std::vector<string> ZerlegeZeile(std::string input, std::string delimiter)
 {
 	std::vector<string> Output;
-	/* The input can have multiple formats: 
+	/* The input can have multiple formats:
 	 *  - key = value
-     *  - key = value1 value2 value3 ...
-     *  - key value1 value2 value3 ...
-	 *  
+	 *  - key = value1 value2 value3 ...
+	 *  - key value1 value2 value3 ...
+	 *
 	 * Examples:
 	 *  - ImageSize = VGA
-	 *  - IO0 = input disabled 10 false false 
+	 *  - IO0 = input disabled 10 false false
 	 *  - main.dig1 28 144 55 100 false
-	 * 
+	 *
 	 * This causes issues eg. if a password key has a whitespace or equal sign in its value.
 	 * As a workaround and to not break any legacy usage, we enforce to only use the
 	 * equal sign, if the key is "password"
-	*/
-	if ((input.find("password") != string::npos) || (input.find("Token") != string::npos)) { // Line contains a password, use the equal sign as the only delimiter and only split on first occurrence
+	 */
+	if ((input.find("password") != string::npos) || (input.find("Token") != string::npos))
+	{
+		// Line contains a password, use the equal sign as the only delimiter and only split on first occurrence
 		size_t pos = input.find("=");
 		Output.push_back(trim(input.substr(0, pos), ""));
-		Output.push_back(trim(input.substr(pos +1, string::npos), ""));
+		Output.push_back(trim(input.substr(pos + 1, string::npos), ""));
 	}
-	else { // Legacy Mode
-		input = trim(input, delimiter);							// sonst werden delimiter am Ende (z.B. == im Token) gelöscht)
+	else
+	{
+		// Legacy Mode
+		input = trim(input, delimiter); // sonst werden delimiter am Ende (z.B. == im Token) gelöscht)
 		size_t pos = findDelimiterPos(input, delimiter);
 		std::string token;
-		while (pos != std::string::npos) {
+
+		while (pos != std::string::npos)
+		{
 			token = input.substr(0, pos);
 			token = trim(token, delimiter);
 			Output.push_back(token);
@@ -644,37 +729,38 @@ std::vector<string> ZerlegeZeile(std::string input, std::string delimiter)
 			input = trim(input, delimiter);
 			pos = findDelimiterPos(input, delimiter);
 		}
+
 		Output.push_back(input);
 	}
 
 	return Output;
-
 }
 
+std::string ReplaceString(std::string subject, const std::string &search, const std::string &replace)
+{
+	size_t pos = 0;
 
-std::string ReplaceString(std::string subject, const std::string& search,
-                          const std::string& replace) {
-    size_t pos = 0;
-    while ((pos = subject.find(search, pos)) != std::string::npos) {
-         subject.replace(pos, search.length(), replace);
-         pos += replace.length();
-    }
-    return subject;
-}
+	while ((pos = subject.find(search, pos)) != std::string::npos)
+	{
+		subject.replace(pos, search.length(), replace);
+		pos += replace.length();
+	}
 
+	return subject;
+}
 
 /* Source: https://git.kernel.org/pub/scm/utils/mmc/mmc-utils.git/tree/lsmmc.c */
 /* SD Card Manufacturer Database */
-struct SDCard_Manufacturer_database {
+struct SDCard_Manufacturer_database
+{
 	string type;
 	int id;
 	string manufacturer;
 };
 
-
 /* Source: https://git.kernel.org/pub/scm/utils/mmc/mmc-utils.git/tree/lsmmc.c */
 /* SD Card Manufacturer Database */
-struct SDCard_Manufacturer_database database[] = {
+struct SDCard_Manufacturer_database sd_database[] = {
 	{
 		.type = "sd",
 		.id = 0x01,
@@ -779,220 +865,344 @@ struct SDCard_Manufacturer_database database[] = {
 		.type = "sd",
 		.id = 0x89,
 		.manufacturer = "Unknown",
-	}
+	},
 };
 
+struct SDCard_Manufacturer_database mmc_database[] = {
+	{
+		.type = "mmc",
+		.id = 0x00,
+		.manufacturer = "SanDisk",
+	},
+	{
+		.type = "mmc",
+		.id = 0x02,
+		.manufacturer = "Kingston/SanDisk",
+	},
+	{
+		.type = "mmc",
+		.id = 0x03,
+		.manufacturer = "Toshiba",
+	},
+	{
+		.type = "mmc",
+		.id = 0x05,
+		.manufacturer = "Unknown",
+	},
+	{
+		.type = "mmc",
+		.id = 0x06,
+		.manufacturer = "Unknown",
+	},
+	{
+		.type = "mmc",
+		.id = 0x11,
+		.manufacturer = "Toshiba",
+	},
+	{
+		.type = "mmc",
+		.id = 0x13,
+		.manufacturer = "Micron",
+	},
+	{
+		.type = "mmc",
+		.id = 0x15,
+		.manufacturer = "Samsung/SanDisk/LG",
+	},
+	{
+		.type = "mmc",
+		.id = 0x37,
+		.manufacturer = "KingMax",
+	},
+	{
+		.type = "mmc",
+		.id = 0x44,
+		.manufacturer = "ATP",
+	},
+	{
+		.type = "mmc",
+		.id = 0x45,
+		.manufacturer = "SanDisk Corporation",
+	},
+	{
+		.type = "mmc",
+		.id = 0x2c,
+		.manufacturer = "Kingston",
+	},
+	{
+		.type = "mmc",
+		.id = 0x70,
+		.manufacturer = "Kingston",
+	},
+	{
+		.type = "mmc",
+		.id = 0xfe,
+		.manufacturer = "Micron",
+	},
+};
 
 /* Parse SD Card Manufacturer Database */
-string SDCardParseManufacturerIDs(int id) 
+string SDCardParseManufacturerIDs(int id)
 {
-	unsigned int id_cnt = sizeof(database) / sizeof(struct SDCard_Manufacturer_database);
-	string ret_val = "";
+	if (SDCardIsMMC)
+	{
+		unsigned int id_cnt = sizeof(mmc_database) / sizeof(struct SDCard_Manufacturer_database);
+		string ret_val = "";
 
-	for (int i = 0; i < id_cnt; i++) {
-		if (database[i].id == id) {
-			return database[i].manufacturer;
+		for (int i = 0; i < id_cnt; i++)
+		{
+			if (mmc_database[i].id == id)
+			{
+				return mmc_database[i].manufacturer;
+			}
+			else
+			{
+				ret_val = "ID unknown (not in DB)";
+			}
 		}
-		else {
-			ret_val = "ID unknown (not in DB)";
+
+		return ret_val;
+	}
+
+	else
+	{
+		unsigned int id_cnt = sizeof(sd_database) / sizeof(struct SDCard_Manufacturer_database);
+		string ret_val = "";
+
+		for (int i = 0; i < id_cnt; i++)
+		{
+			if (sd_database[i].id == id)
+			{
+				return sd_database[i].manufacturer;
+			}
+			else
+			{
+				ret_val = "ID unknown (not in DB)";
+			}
 		}
+
+		return ret_val;
 	}
-	return ret_val;
 }
 
-
 string RundeOutput(double _in, int _anzNachkomma)
 {
-    std::stringstream stream;
-    int _zw = _in;    
-//    ESP_LOGD(TAG, "AnzNachkomma: %d", _anzNachkomma);
-
-    if (_anzNachkomma < 0) {
-        _anzNachkomma = 0;
-    }
-
-    if (_anzNachkomma > 0)
-    {
-        stream << std::fixed << std::setprecision(_anzNachkomma) << _in;
-        return stream.str();          
-    }
-    else
-    {
-        stream << _zw;
-    }
+	std::stringstream stream;
+	int _zw = _in;
+	//    ESP_LOGD(TAG, "AnzNachkomma: %d", _anzNachkomma);
 
+	if (_anzNachkomma > 0)
+	{
+		stream << std::fixed << std::setprecision(_anzNachkomma) << _in;
+	}
+	else
+	{
+		stream << _zw;
+	}
 
-    return stream.str();  
+	return stream.str();
 }
 
+string getMac(void)
+{
+	uint8_t macInt[6];
+	char macFormated[6 * 2 + 5 + 1]; // AA:BB:CC:DD:EE:FF
 
-string getMac(void) {
-    uint8_t macInt[6];
-    char macFormated[6*2 + 5 + 1]; // AA:BB:CC:DD:EE:FF
-
-    esp_read_mac(macInt, ESP_MAC_WIFI_STA);
-    sprintf(macFormated, "%02X:%02X:%02X:%02X:%02X:%02X", macInt[0], macInt[1], macInt[2], macInt[3], macInt[4], macInt[5]); 
+	esp_read_mac(macInt, ESP_MAC_WIFI_STA);
+	sprintf(macFormated, "%02X:%02X:%02X:%02X:%02X:%02X", macInt[0], macInt[1], macInt[2], macInt[3], macInt[4], macInt[5]);
 
-    return macFormated;
+	return macFormated;
 }
 
-
-void setSystemStatusFlag(SystemStatusFlag_t flag) {
+void setSystemStatusFlag(SystemStatusFlag_t flag)
+{
 	systemStatus = systemStatus | flag; // set bit
 
 	char buf[20];
 	snprintf(buf, sizeof(buf), "0x%08X", getSystemStatus());
-    LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "New System Status: " + std::string(buf));
+	LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "New System Status: " + std::string(buf));
 }
 
-
-void clearSystemStatusFlag(SystemStatusFlag_t flag) {
+void clearSystemStatusFlag(SystemStatusFlag_t flag)
+{
 	systemStatus = systemStatus | ~flag; // clear bit
 
 	char buf[20];
 	snprintf(buf, sizeof(buf), "0x%08X", getSystemStatus());
-    LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "New System Status: " + std::string(buf));
+	LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "New System Status: " + std::string(buf));
 }
 
-
-int getSystemStatus(void) {
-    return systemStatus;
+int getSystemStatus(void)
+{
+	return systemStatus;
 }
 
+bool isSetSystemStatusFlag(SystemStatusFlag_t flag)
+{
+	// ESP_LOGE(TAG, "Flag (0x%08X) is set (0x%08X): %d", flag, systemStatus , ((systemStatus & flag) == flag));
 
-bool isSetSystemStatusFlag(SystemStatusFlag_t flag) {
-	//ESP_LOGE(TAG, "Flag (0x%08X) is set (0x%08X): %d", flag, systemStatus , ((systemStatus & flag) == flag));
-
-	if ((systemStatus & flag) == flag) {
+	if ((systemStatus & flag) == flag)
+	{
 		return true;
 	}
-	else {
+	else
+	{
 		return false;
 	}
 }
 
-
-time_t getUpTime(void) {
-    return (uint32_t)(esp_timer_get_time()/1000/1000); // in seconds
+time_t getUpTime(void)
+{
+	return (uint32_t)(esp_timer_get_time() / 1000 / 1000); // in seconds
 }
 
-
-string getResetReason(void) {
+string getResetReason(void)
+{
 	std::string reasonText;
 
-	switch(esp_reset_reason()) {
-		case ESP_RST_POWERON: reasonText = "Power-on event (or reset button)"; break;    //!< Reset due to power-on event
-		case ESP_RST_EXT: reasonText = "External pin"; break;        //!< Reset by external pin (not applicable for ESP32)
-		case ESP_RST_SW: reasonText = "Via esp_restart"; break;         //!< Software reset via esp_restart
-		case ESP_RST_PANIC: reasonText = "Exception/panic"; break;      //!< Software reset due to exception/panic
-		case ESP_RST_INT_WDT: reasonText = "Interrupt watchdog"; break;    //!< Reset (software or hardware) due to interrupt watchdog
-		case ESP_RST_TASK_WDT: reasonText = "Task watchdog"; break;   //!< Reset due to task watchdog
-		case ESP_RST_WDT: reasonText = "Other watchdogs"; break;        //!< Reset due to other watchdogs
-		case ESP_RST_DEEPSLEEP: reasonText = "Exiting deep sleep mode"; break;  //!< Reset after exiting deep sleep mode
-		case ESP_RST_BROWNOUT: reasonText = "Brownout"; break;   //!< Brownout reset (software or hardware)
-		case ESP_RST_SDIO: reasonText = "SDIO"; break;       //!< Reset over SDIO
-
-		case ESP_RST_UNKNOWN:   //!< Reset reason can not be determined
-		default: 
-			reasonText = "Unknown";
+	switch (esp_reset_reason())
+	{
+	case ESP_RST_POWERON:
+		reasonText = "Power-on event (or reset button)";
+		break; //!< Reset due to power-on event
+	case ESP_RST_EXT:
+		reasonText = "External pin";
+		break; //!< Reset by external pin (not applicable for ESP32)
+	case ESP_RST_SW:
+		reasonText = "Via esp_restart";
+		break; //!< Software reset via esp_restart
+	case ESP_RST_PANIC:
+		reasonText = "Exception/panic";
+		break; //!< Software reset due to exception/panic
+	case ESP_RST_INT_WDT:
+		reasonText = "Interrupt watchdog";
+		break; //!< Reset (software or hardware) due to interrupt watchdog
+	case ESP_RST_TASK_WDT:
+		reasonText = "Task watchdog";
+		break; //!< Reset due to task watchdog
+	case ESP_RST_WDT:
+		reasonText = "Other watchdogs";
+		break; //!< Reset due to other watchdogs
+	case ESP_RST_DEEPSLEEP:
+		reasonText = "Exiting deep sleep mode";
+		break; //!< Reset after exiting deep sleep mode
+	case ESP_RST_BROWNOUT:
+		reasonText = "Brownout";
+		break; //!< Brownout reset (software or hardware)
+	case ESP_RST_SDIO:
+		reasonText = "SDIO";
+		break; //!< Reset over SDIO
+
+	case ESP_RST_UNKNOWN: //!< Reset reason can not be determined
+	default:
+		reasonText = "Unknown";
 	}
-    return reasonText;
-}
 
+	return reasonText;
+}
 
 /**
  * Returns the current uptime  formated ad xxf xxh xxm [xxs]
  */
-std::string getFormatedUptime(bool compact) {
+std::string getFormatedUptime(bool compact)
+{
 	char buf[20];
-	#pragma GCC diagnostic ignored "-Wformat-truncation"
+#pragma GCC diagnostic ignored "-Wformat-truncation"
 
-    int uptime = getUpTime(); // in seconds
+	int uptime = getUpTime(); // in seconds
 
-    int days = int(floor(uptime / (3600*24)));
-    int hours = int(floor((uptime - days * 3600*24) / (3600)));
-    int minutes = int(floor((uptime - days * 3600*24 - hours * 3600) / (60)));
-    int seconds = uptime - days * 3600*24 - hours * 3600 - minutes * 60;
-    
-	if (compact) {
+	int days = int(floor(uptime / (3600 * 24)));
+	int hours = int(floor((uptime - days * 3600 * 24) / (3600)));
+	int minutes = int(floor((uptime - days * 3600 * 24 - hours * 3600) / (60)));
+	int seconds = uptime - days * 3600 * 24 - hours * 3600 - minutes * 60;
+
+	if (compact)
+	{
 		snprintf(buf, sizeof(buf), "%dd%02dh%02dm%02ds", days, hours, minutes, seconds);
 	}
-	else {
+	else
+	{
 		snprintf(buf, sizeof(buf), "%3dd %02dh %02dm %02ds", days, hours, minutes, seconds);
 	}
 
 	return std::string(buf);
 }
 
-
-const char* get404(void) {
-    return 
-"<pre>\n\n\n\n"
-"        _\n"
-"    .__(.)< ( oh oh! This page does not exist! )\n"
-"    \\___)\n"
-"\n\n"
-"                You could try your <a href=index.html target=_parent>luck</a> here!</pre>\n"
-"<script>document.cookie = \"page=overview.html\"</script>"; // Make sure we load the overview page
+const char *get404(void)
+{
+	return "<pre>\n\n\n\n"
+		   "        _\n"
+		   "    .__(.)< ( oh oh! This page does not exist! )\n"
+		   "    \\___)\n"
+		   "\n\n"
+		   "                You could try your <a href=index.html target=_parent>luck</a> here!</pre>\n"
+		   "<script>document.cookie = \"page=overview.html\"</script>"; // Make sure we load the overview page
 }
 
-
-std::string UrlDecode(const std::string& value)
+std::string UrlDecode(const std::string &value)
 {
-    std::string result;
-    result.reserve(value.size());
+	std::string result;
+	result.reserve(value.size());
 
-    for (std::size_t i = 0; i < value.size(); ++i)
-    {
-        auto ch = value[i];
+	for (std::size_t i = 0; i < value.size(); ++i)
+	{
+		auto ch = value[i];
 
-        if (ch == '%' && (i + 2) < value.size())
-        {
-            auto hex = value.substr(i + 1, 2);
-            auto dec = static_cast<char>(std::strtol(hex.c_str(), nullptr, 16));
-            result.push_back(dec);
-            i += 2;
-        }
-        else if (ch == '+')
-        {
-            result.push_back(' ');
-        }
-        else
-        {
-            result.push_back(ch);
-        }
-    }
+		if (ch == '%' && (i + 2) < value.size())
+		{
+			auto hex = value.substr(i + 1, 2);
+			auto dec = static_cast<char>(std::strtol(hex.c_str(), nullptr, 16));
+			result.push_back(dec);
+			i += 2;
+		}
+		else if (ch == '+')
+		{
+			result.push_back(' ');
+		}
+		else
+		{
+			result.push_back(ch);
+		}
+	}
 
-    return result;
+	return result;
 }
 
-
-bool replaceString(std::string& s, std::string const& toReplace, std::string const& replaceWith) {
-    return replaceString(s, toReplace, replaceWith, true);
+bool replaceString(std::string &s, std::string const &toReplace, std::string const &replaceWith)
+{
+	return replaceString(s, toReplace, replaceWith, true);
 }
 
+bool replaceString(std::string &s, std::string const &toReplace, std::string const &replaceWith, bool logIt)
+{
+	std::size_t pos = s.find(toReplace);
 
-bool replaceString(std::string& s, std::string const& toReplace, std::string const& replaceWith, bool logIt) {
-    std::size_t pos = s.find(toReplace);
+	if (pos == std::string::npos)
+	{
+		// Not found
+		return false;
+	}
 
-    if (pos == std::string::npos) { // Not found
-        return false;
-    }
+	std::string old = s;
+	s.replace(pos, toReplace.length(), replaceWith);
 
-    std::string old = s;
-    s.replace(pos, toReplace.length(), replaceWith);
-    if (logIt) {
-        LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Migrated Configfile line '" + old + "' to '" + s + "'");
-    }
-    return true;
+	if (logIt)
+	{
+		LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Migrated Configfile line '" + old + "' to '" + s + "'");
+	}
+
+	return true;
 }
 
+bool isInString(std::string &s, std::string const &toFind)
+{
+	std::size_t pos = s.find(toFind);
 
-bool isInString(std::string& s, std::string const& toFind) {
-    std::size_t pos = s.find(toFind);
+	if (pos == std::string::npos)
+	{
+		// Not found
+		return false;
+	}
 
-    if (pos == std::string::npos) { // Not found
-        return false;
-    }
-    return true;
+	return true;
 }

+ 1 - 0
code/components/jomjol_helper/Helper.h

@@ -6,6 +6,7 @@
 #include <string>
 #include <fstream>
 #include <vector>
+
 #include "sdmmc_cmd.h"
 
 using namespace std;

+ 1 - 0
code/components/jomjol_helper/sdcard_check.cpp

@@ -3,6 +3,7 @@
 #include <stdio.h>
 #include <stdbool.h>
 #include <stdint.h>
+#include <unistd.h>
 #include <inttypes.h>
 #include <sys/stat.h>
 

+ 651 - 0
code/components/jomjol_helper/sdcard_init.c

@@ -0,0 +1,651 @@
+/*
+ * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "sdcard_init.h"
+
+#include "esp_log.h"
+#include "ffconf.h"
+#include "esp_compiler.h"
+#include "esp_vfs.h"
+#include "vfs_fat_internal.h"
+#include "diskio_impl.h"
+#include "diskio_sdmmc.h"
+#include "soc/soc_caps.h"
+#include "driver/sdmmc_defs.h"
+
+#if SOC_SDMMC_HOST_SUPPORTED
+	#include "driver/sdmmc_host.h"
+#endif
+
+static sdmmc_card_t* s_cards[FF_VOLUMES] = { NULL };
+static bool s_disk_status_check_en[FF_VOLUMES] = { };
+
+static const char* TAG = "sdcard_init";
+
+#define CHECK_EXECUTE_RESULT(err, str) do { \
+    if ((err) !=ESP_OK) { \
+        ESP_LOGE(TAG, str" (0x%x).", err); \
+        goto cleanup; \
+    } \
+    } while(0)
+
+typedef struct vfs_fat_sd_ctx_t {
+    BYTE pdrv;                                  //Drive number that is mounted
+    esp_vfs_fat_mount_config_t mount_config;    //Mount configuration
+    FATFS *fs;                                  //FAT structure pointer that is registered
+    sdmmc_card_t *card;                         //Card info
+    char *base_path;                            //Path where partition is registered
+} vfs_fat_sd_ctx_t;
+
+static vfs_fat_sd_ctx_t *s_ctx[FF_VOLUMES] = {};
+
+/**
+ * This `s_saved_ctx_id` is only used by `esp_vfs_fat_sdmmc_unmount`, which is deprecated.
+ * This variable together with `esp_vfs_fat_sdmmc_unmount` should be removed in next major version
+ */
+static uint32_t s_saved_ctx_id = FF_VOLUMES;
+
+
+static void call_host_deinit_mh(const sdmmc_host_t *host_config);
+static esp_err_t partition_card_mh(const esp_vfs_fat_mount_config_t *mount_config, const char *drv, sdmmc_card_t *card, BYTE pdrv);
+
+
+//Check if SD/MMC card is present
+static DSTATUS ff_sdmmc_card_available_mh(BYTE pdrv)
+{
+    sdmmc_card_t* card = s_cards[pdrv];
+    assert(card);
+    esp_err_t err = sdmmc_get_status(card);
+	
+    if (unlikely(err != ESP_OK)) {
+        ESP_LOGE(TAG, "Check status failed (0x%x)", err);
+        return STA_NOINIT;
+    }
+    return 0;
+}
+
+/**
+*   ff_sdmmc_status() and ff_sdmmc_initialize() return STA_NOINIT when sdmmc_get_status()
+*   fails. This error value is checked throughout the FATFS code.
+*   Both functions return 0 on success.
+*/
+DSTATUS ff_sdmmc_initialize_mh (BYTE pdrv)
+{
+    return ff_sdmmc_card_available_mh(pdrv);
+}
+
+DSTATUS ff_sdmmc_status_mh(BYTE pdrv)
+{
+    if (s_disk_status_check_en[pdrv]) {
+        return ff_sdmmc_card_available_mh(pdrv);
+    }
+    return 0;
+}
+
+DRESULT ff_sdmmc_read_mh (BYTE pdrv, BYTE* buff, DWORD sector, UINT count)
+{
+    sdmmc_card_t* card = s_cards[pdrv];
+    assert(card);
+    esp_err_t err = sdmmc_read_sectors(card, buff, sector, count);
+    if (unlikely(err != ESP_OK)) {
+        ESP_LOGE(TAG, "sdmmc_read_blocks failed (%d)", err);
+        return RES_ERROR;
+    }
+    return RES_OK;
+}
+
+DRESULT ff_sdmmc_write_mh (BYTE pdrv, const BYTE* buff, DWORD sector, UINT count)
+{
+    sdmmc_card_t* card = s_cards[pdrv];
+    assert(card);
+    esp_err_t err = sdmmc_write_sectors(card, buff, sector, count);
+    if (unlikely(err != ESP_OK)) {
+        ESP_LOGE(TAG, "sdmmc_write_blocks failed (%d)", err);
+        return RES_ERROR;
+    }
+    return RES_OK;
+}
+
+#if FF_USE_TRIM
+DRESULT ff_sdmmc_trim_mh (BYTE pdrv, DWORD start_sector, DWORD sector_count)
+{
+    sdmmc_card_t* card = s_cards[pdrv];
+    assert(card);
+    sdmmc_erase_arg_t arg;
+
+    arg = sdmmc_can_discard(card) == ESP_OK ? SDMMC_DISCARD_ARG : SDMMC_ERASE_ARG;
+    esp_err_t err = sdmmc_erase_sectors(card, start_sector, sector_count, arg);
+    if (unlikely(err != ESP_OK)) {
+        ESP_LOGE(TAG, "sdmmc_erase_sectors failed (%d)", err);
+        return RES_ERROR;
+    }
+    return RES_OK;
+}
+#endif //FF_USE_TRIM
+
+DRESULT ff_sdmmc_ioctl_mh (BYTE pdrv, BYTE cmd, void* buff)
+{
+    sdmmc_card_t* card = s_cards[pdrv];
+    assert(card);
+    switch(cmd) {
+        case CTRL_SYNC:
+            return RES_OK;
+        case GET_SECTOR_COUNT:
+            *((DWORD*) buff) = card->csd.capacity;
+            return RES_OK;
+        case GET_SECTOR_SIZE:
+            *((WORD*) buff) = card->csd.sector_size;
+            return RES_OK;
+        case GET_BLOCK_SIZE:
+            return RES_ERROR;
+#if FF_USE_TRIM
+        case CTRL_TRIM:
+            if (sdmmc_can_trim(card) != ESP_OK) {
+                return RES_PARERR;
+            }
+            return ff_sdmmc_trim_mh (pdrv, *((DWORD*)buff), //start_sector
+                    (*((DWORD*)buff + 1) - *((DWORD*)buff) + 1)); //sector_count
+#endif //FF_USE_TRIM
+    }
+    return RES_ERROR;
+}
+
+void ff_sdmmc_set_disk_status_check_mh(BYTE pdrv, bool enable)
+{
+    s_disk_status_check_en[pdrv] = enable;
+}
+
+void ff_diskio_register_sdmmc_mh(BYTE pdrv, sdmmc_card_t* card)
+{
+    static const ff_diskio_impl_t sdmmc_impl = {
+        .init = &ff_sdmmc_initialize_mh,
+        .status = &ff_sdmmc_status_mh,
+        .read = &ff_sdmmc_read_mh,
+        .write = &ff_sdmmc_write_mh,
+        .ioctl = &ff_sdmmc_ioctl_mh
+    };
+    s_cards[pdrv] = card;
+    s_disk_status_check_en[pdrv] = false;
+    ff_diskio_register(pdrv, &sdmmc_impl);
+}
+
+BYTE ff_diskio_get_pdrv_card_mh(const sdmmc_card_t* card)
+{
+    for (int i = 0; i < FF_VOLUMES; i++) {
+        if (card == s_cards[i]) {
+            return i;
+        }
+    }
+    return 0xff;
+}
+
+
+
+
+
+
+static bool s_get_context_id_by_card_mh(const sdmmc_card_t *card, uint32_t *out_id)
+{
+    vfs_fat_sd_ctx_t *p_ctx = NULL;
+    for (int i = 0; i < FF_VOLUMES; i++) {
+        p_ctx = s_ctx[i];
+        if (p_ctx) {
+            if (p_ctx->card == card) {
+                *out_id = i;
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
+static uint32_t s_get_unused_context_id_mh(void)
+{
+    for (uint32_t i = 0; i < FF_VOLUMES; i++) {
+        if (!s_ctx[i]) {
+            return i;
+        }
+    }
+    return FF_VOLUMES;
+}
+
+static esp_err_t mount_prepare_mem_mh(const char *base_path, BYTE *out_pdrv, char **out_dup_path, sdmmc_card_t** out_card)
+{
+    esp_err_t err = ESP_OK;
+    char* dup_path = NULL;
+    sdmmc_card_t* card = NULL;
+
+    // connect SDMMC driver to FATFS
+    BYTE pdrv = FF_DRV_NOT_USED;
+
+    if (ff_diskio_get_drive(&pdrv) != ESP_OK || pdrv == FF_DRV_NOT_USED) {
+        ESP_LOGD(TAG, "the maximum count of volumes is already mounted");
+        return ESP_ERR_NO_MEM;
+    }
+
+    // not using ff_memalloc here, as allocation in internal RAM is preferred
+    card = (sdmmc_card_t*)malloc(sizeof(sdmmc_card_t));
+
+    if (card == NULL) {
+        ESP_LOGD(TAG, "could not locate new sdmmc_card_t");
+        err = ESP_ERR_NO_MEM;
+        goto cleanup;
+    }
+
+    dup_path = strdup(base_path);
+
+    if(!dup_path){
+        ESP_LOGD(TAG, "could not copy base_path");
+        err = ESP_ERR_NO_MEM;
+        goto cleanup;
+    }
+
+    *out_card = card;
+    *out_pdrv = pdrv;
+    *out_dup_path = dup_path;
+    return ESP_OK;
+cleanup:
+    free(card);
+    free(dup_path);
+    return err;
+}
+
+static esp_err_t s_f_mount_mh(sdmmc_card_t *card, FATFS *fs, const char *drv, uint8_t pdrv, const esp_vfs_fat_mount_config_t *mount_config)
+{
+    esp_err_t err = ESP_OK;
+    FRESULT res = f_mount(fs, drv, 1);
+    if (res != FR_OK) {
+        err = ESP_FAIL;
+        ESP_LOGW(TAG, "failed to mount card (%d)", res);
+
+        bool need_mount_again = (res == FR_NO_FILESYSTEM || res == FR_INT_ERR) && mount_config->format_if_mount_failed;
+
+        if (!need_mount_again) {
+            return ESP_FAIL;
+        }
+
+        err = partition_card_mh(mount_config, drv, card, pdrv);
+
+        if (err != ESP_OK) {
+            return err;
+        }
+
+        ESP_LOGW(TAG, "mounting again");
+        res = f_mount(fs, drv, 0);
+
+        if (res != FR_OK) {
+            err = ESP_FAIL;
+            ESP_LOGD(TAG, "f_mount failed after formatting (%d)", res);
+            return err;
+        }
+    }
+
+    return ESP_OK;
+}
+
+static esp_err_t mount_to_vfs_fat_mh(const esp_vfs_fat_mount_config_t *mount_config, sdmmc_card_t *card, uint8_t pdrv, const char *base_path, FATFS **out_fs)
+{
+    FATFS *fs = NULL;
+    esp_err_t err;
+    ff_diskio_register_sdmmc_mh(pdrv, card);
+    ff_sdmmc_set_disk_status_check_mh(pdrv, mount_config->disk_status_check_enable);
+    ESP_LOGD(TAG, "using pdrv=%i", pdrv);
+    char drv[3] = {(char)('0' + pdrv), ':', 0};
+
+    // connect FATFS to VFS
+    err = esp_vfs_fat_register(base_path, drv, mount_config->max_files, &fs);
+    *out_fs = fs;
+
+    if (err == ESP_ERR_INVALID_STATE) {
+        // it's okay, already registered with VFS
+    } else if (err != ESP_OK) {
+        ESP_LOGD(TAG, "esp_vfs_fat_register failed 0x(%x)", err);
+        goto fail;
+    }
+
+    // Try to mount partition
+    err = s_f_mount_mh(card, fs, drv, pdrv, mount_config);
+
+    if (err != ESP_OK) {
+        goto fail;
+    }
+    return ESP_OK;
+
+fail:
+    if (fs) {
+        f_mount(NULL, drv, 0);
+    }
+    esp_vfs_fat_unregister_path(base_path);
+    ff_diskio_unregister(pdrv);
+    return err;
+}
+
+static esp_err_t partition_card_mh(const esp_vfs_fat_mount_config_t *mount_config, const char *drv, sdmmc_card_t *card, BYTE pdrv)
+{
+    FRESULT res = FR_OK;
+    esp_err_t err;
+    const size_t workbuf_size = 4096;
+    void* workbuf = NULL;
+    ESP_LOGW(TAG, "partitioning card");
+
+    workbuf = ff_memalloc(workbuf_size);
+
+    if (workbuf == NULL) {
+        return ESP_ERR_NO_MEM;
+    }
+
+    LBA_t plist[] = {100, 0, 0, 0};
+    res = f_fdisk(pdrv, plist, workbuf);
+
+    if (res != FR_OK) {
+        err = ESP_FAIL;
+        ESP_LOGD(TAG, "f_fdisk failed (%d)", res);
+        goto fail;
+    }
+
+    size_t alloc_unit_size = esp_vfs_fat_get_allocation_unit_size(card->csd.sector_size, mount_config->allocation_unit_size);
+
+    ESP_LOGW(TAG, "formatting card, allocation unit size=%d", alloc_unit_size);
+    const MKFS_PARM opt = {(BYTE)FM_ANY, 0, 0, 0, alloc_unit_size};
+    res = f_mkfs(drv, &opt, workbuf, workbuf_size);
+
+    if (res != FR_OK) {
+        err = ESP_FAIL;
+        ESP_LOGD(TAG, "f_mkfs failed (%d)", res);
+        goto fail;
+    }
+
+    free(workbuf);
+    return ESP_OK;
+fail:
+    free(workbuf);
+    return err;
+}
+
+#if SOC_SDMMC_HOST_SUPPORTED
+static esp_err_t init_sdmmc_host_mh(int slot, const void *slot_config, int *out_slot)
+{
+    *out_slot = slot;
+    return sdmmc_host_init_slot(slot, (const sdmmc_slot_config_t*) slot_config);
+}
+
+esp_err_t esp_vfs_fat_sdmmc_mount_mh(const char* base_path, const sdmmc_host_t* host_config, const void* slot_config, const esp_vfs_fat_mount_config_t* mount_config, sdmmc_card_t** out_card)
+{
+    esp_err_t err;
+    vfs_fat_sd_ctx_t *ctx = NULL;
+    uint32_t ctx_id = FF_VOLUMES;
+    FATFS *fs = NULL;
+    int card_handle = -1;   //uninitialized
+    sdmmc_card_t* card = NULL;
+    BYTE pdrv = FF_DRV_NOT_USED;
+    char* dup_path = NULL;
+    bool host_inited = false;
+
+    err = mount_prepare_mem_mh(base_path, &pdrv, &dup_path, &card);
+
+    if (err != ESP_OK) {
+        ESP_LOGE(TAG, "mount_prepare failed");
+        return err;
+    }
+
+    err = (*host_config->init)();
+    CHECK_EXECUTE_RESULT(err, "host init failed");
+    //deinit() needs to be called to revert the init
+    host_inited = true;
+    //If this failed (indicated by card_handle != -1), slot deinit needs to called()
+    //leave card_handle as is to indicate that (though slot deinit not implemented yet.
+    err = init_sdmmc_host_mh(host_config->slot, slot_config, &card_handle);
+    CHECK_EXECUTE_RESULT(err, "slot init failed");
+
+    // probe and initialize card
+    err = sdmmc_card_init(host_config, card);
+    CHECK_EXECUTE_RESULT(err, "sdmmc_card_init failed");
+
+    err = mount_to_vfs_fat_mh(mount_config, card, pdrv, dup_path, &fs);
+    CHECK_EXECUTE_RESULT(err, "mount_to_vfs failed");
+
+    if (out_card != NULL) {
+        *out_card = card;
+    }
+
+    //For deprecation backward compatibility
+    if (s_saved_ctx_id == FF_VOLUMES) {
+        s_saved_ctx_id = 0;
+    }
+
+    ctx = calloc(sizeof(vfs_fat_sd_ctx_t), 1);
+
+    if (!ctx) {
+        CHECK_EXECUTE_RESULT(ESP_ERR_NO_MEM, "no mem");
+    }
+
+    ctx->pdrv = pdrv;
+    memcpy(&ctx->mount_config, mount_config, sizeof(esp_vfs_fat_mount_config_t));
+    ctx->card = card;
+    ctx->base_path = dup_path;
+    ctx->fs = fs;
+    ctx_id = s_get_unused_context_id_mh();
+    assert(ctx_id != FF_VOLUMES);
+    s_ctx[ctx_id] = ctx;
+
+    return ESP_OK;
+cleanup:
+    if (host_inited) {
+        call_host_deinit_mh(host_config);
+    }
+
+    free(card);
+    free(dup_path);
+    return err;
+}
+#endif
+
+static esp_err_t init_sdspi_host_mh(int slot, const void *slot_config, int *out_slot)
+{
+    esp_err_t err = sdspi_host_init_device((const sdspi_device_config_t*)slot_config, out_slot);
+    if (err != ESP_OK) {
+        ESP_LOGE(TAG,
+"Failed to attach sdspi device onto an SPI bus (rc=0x%x), please initialize the \
+bus first and check the device parameters."
+            , err);
+    }
+    return err;
+}
+
+esp_err_t esp_vfs_fat_sdspi_mount_mh(const char* base_path, const sdmmc_host_t* host_config_input, const sdspi_device_config_t* slot_config, const esp_vfs_fat_mount_config_t* mount_config, sdmmc_card_t** out_card)
+{
+    const sdmmc_host_t* host_config = host_config_input;
+    esp_err_t err;
+    vfs_fat_sd_ctx_t *ctx = NULL;
+    uint32_t ctx_id = FF_VOLUMES;
+    FATFS *fs = NULL;
+    int card_handle = -1;   //uninitialized
+    bool host_inited = false;
+    BYTE pdrv = FF_DRV_NOT_USED;
+    sdmmc_card_t* card = NULL;
+    char* dup_path = NULL;
+
+    err = mount_prepare_mem_mh(base_path, &pdrv, &dup_path, &card);
+    if (err != ESP_OK) {
+        ESP_LOGE(TAG, "mount_prepare failed");
+        return err;
+    }
+
+    //the init() function is usually empty, doesn't require any deinit to revert it
+    err = (*host_config->init)();
+    CHECK_EXECUTE_RESULT(err, "host init failed");
+
+    err = init_sdspi_host_mh(host_config->slot, slot_config, &card_handle);
+    CHECK_EXECUTE_RESULT(err, "slot init failed");
+    //Set `host_inited` to true to indicate that host_config->deinit() needs
+    //to be called to revert `init_sdspi_host`
+    host_inited = true;
+
+    //The `slot` argument inside host_config should be replaced by the SD SPI handled returned
+    //above. But the input pointer is const, so create a new variable.
+
+    sdmmc_host_t new_config;
+
+    if (card_handle != host_config->slot) {
+        new_config = *host_config_input;
+        host_config = &new_config;
+        new_config.slot = card_handle;
+    }
+
+    // probe and initialize card
+    err = sdmmc_card_init(host_config, card);
+    CHECK_EXECUTE_RESULT(err, "sdmmc_card_init failed");
+
+    err = mount_to_vfs_fat_mh(mount_config, card, pdrv, dup_path, &fs);
+    CHECK_EXECUTE_RESULT(err, "mount_to_vfs failed");
+
+    if (out_card != NULL) {
+        *out_card = card;
+    }
+
+    //For deprecation backward compatibility
+    if (s_saved_ctx_id == FF_VOLUMES) {
+        s_saved_ctx_id = 0;
+    }
+
+    ctx = calloc(sizeof(vfs_fat_sd_ctx_t), 1);
+
+    if (!ctx) {
+        CHECK_EXECUTE_RESULT(ESP_ERR_NO_MEM, "no mem");
+    }
+
+    ctx->pdrv = pdrv;
+    memcpy(&ctx->mount_config, mount_config, sizeof(esp_vfs_fat_mount_config_t));
+    ctx->card = card;
+    ctx->base_path = dup_path;
+    ctx->fs = fs;
+    ctx_id = s_get_unused_context_id_mh();
+    assert(ctx_id != FF_VOLUMES);
+    s_ctx[ctx_id] = ctx;
+
+    return ESP_OK;
+
+cleanup:
+    if (host_inited) {
+        call_host_deinit_mh(host_config);
+    }
+
+    free(card);
+    free(dup_path);
+    return err;
+}
+
+static void call_host_deinit_mh(const sdmmc_host_t *host_config)
+{
+    if (host_config->flags & SDMMC_HOST_FLAG_DEINIT_ARG) {
+        host_config->deinit_p(host_config->slot);
+    } else {
+        host_config->deinit();
+    }
+}
+
+static esp_err_t unmount_card_core_mh(const char *base_path, sdmmc_card_t *card)
+{
+    BYTE pdrv = ff_diskio_get_pdrv_card_mh(card);
+
+    if (pdrv == 0xff) {
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    // unmount
+    char drv[3] = {(char)('0' + pdrv), ':', 0};
+    f_mount(0, drv, 0);
+    // release SD driver
+    ff_diskio_unregister(pdrv);
+
+    call_host_deinit_mh(&card->host);
+    free(card);
+
+    esp_err_t err = esp_vfs_fat_unregister_path(base_path);
+    return err;
+}
+
+esp_err_t esp_vfs_fat_sdmmc_unmount_mh(void)
+{
+    esp_err_t err = unmount_card_core_mh(s_ctx[s_saved_ctx_id]->base_path, s_ctx[s_saved_ctx_id]->card);
+    free(s_ctx[s_saved_ctx_id]);
+    s_ctx[s_saved_ctx_id] = NULL;
+    s_saved_ctx_id = FF_VOLUMES;
+    return err;
+}
+
+esp_err_t esp_vfs_fat_sdcard_unmount_mh(const char *base_path, sdmmc_card_t *card)
+{
+    uint32_t id = FF_VOLUMES;
+    bool found = s_get_context_id_by_card_mh(card, &id);
+
+    if (!found) {
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    free(s_ctx[id]);
+    s_ctx[id] = NULL;
+
+    esp_err_t err = unmount_card_core_mh(base_path, card);
+
+    return err;
+}
+
+esp_err_t esp_vfs_fat_sdcard_format_mh(const char *base_path, sdmmc_card_t *card)
+{
+    esp_err_t ret = ESP_OK;
+
+    if (!card) {
+        ESP_LOGE(TAG, "card not initialized");
+        return ESP_ERR_INVALID_STATE;
+    }
+
+    BYTE pdrv = ff_diskio_get_pdrv_card_mh(card);
+
+    if (pdrv == 0xff) {
+        ESP_LOGE(TAG, "card driver not registered");
+        return ESP_ERR_INVALID_STATE;
+    }
+
+    const size_t workbuf_size = 4096;
+    void *workbuf = ff_memalloc(workbuf_size);
+
+    if (workbuf == NULL) {
+        return ESP_ERR_NO_MEM;
+    }
+
+    //unmount
+    char drv[3] = {(char)('0' + pdrv), ':', 0};
+    f_mount(0, drv, 0);
+
+    //format
+    uint32_t id = FF_VOLUMES;
+    bool found = s_get_context_id_by_card_mh(card, &id);
+    assert(found);
+    size_t alloc_unit_size = esp_vfs_fat_get_allocation_unit_size(card->csd.sector_size, s_ctx[id]->mount_config.allocation_unit_size);
+    ESP_LOGI(TAG, "Formatting card, allocation unit size=%d", alloc_unit_size);
+    const MKFS_PARM opt = {(BYTE)FM_ANY, 0, 0, 0, alloc_unit_size};
+    FRESULT res = f_mkfs(drv, &opt, workbuf, workbuf_size);
+    free(workbuf);
+
+    if (res != FR_OK) {
+        ret = ESP_FAIL;
+        ESP_LOGD(TAG, "f_mkfs failed (%d)", res);
+    }
+
+    //mount back
+    esp_err_t err = s_f_mount_mh(card, s_ctx[id]->fs, drv, pdrv, &s_ctx[id]->mount_config);
+
+    if (err != ESP_OK) {
+        unmount_card_core_mh(base_path, card);
+        ESP_LOGE(TAG, "failed to format, resources recycled, please mount again");
+    }
+
+    return ret;
+}

+ 111 - 0
code/components/jomjol_helper/sdcard_init.h

@@ -0,0 +1,111 @@
+/*
+ * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#pragma once
+
+#include <stddef.h>
+#include "esp_err.h"
+#include "driver/gpio.h"
+#include "sdmmc_cmd.h"
+#include "driver/sdmmc_types.h"
+#include "driver/sdspi_host.h"
+#include "ff.h"
+#include "esp_vfs_fat.h"
+#include "wear_levelling.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief Convenience function to get FAT filesystem on SD card registered in VFS
+ *
+ * This is an all-in-one function which does the following:
+ * - initializes SDMMC driver or SPI driver with configuration in host_config
+ * - initializes SD card with configuration in slot_config
+ * - mounts FAT partition on SD card using FATFS library, with configuration in mount_config
+ * - registers FATFS library with VFS, with prefix given by base_prefix variable
+ *
+ * This function is intended to make example code more compact.
+ * For real world applications, developers should implement the logic of
+ * probing SD card, locating and mounting partition, and registering FATFS in VFS,
+ * with proper error checking and handling of exceptional conditions.
+ *
+ * @note Use this API to mount a card through SDSPI is deprecated. Please call
+ *       `esp_vfs_fat_sdspi_mount()` instead for that case.
+ *
+ * @param base_path     path where partition should be registered (e.g. "/sdcard")
+ * @param host_config   Pointer to structure describing SDMMC host. When using
+ *                      SDMMC peripheral, this structure can be initialized using
+ *                      SDMMC_HOST_DEFAULT() macro. When using SPI peripheral,
+ *                      this structure can be initialized using SDSPI_HOST_DEFAULT()
+ *                      macro.
+ * @param slot_config   Pointer to structure with slot configuration.
+ *                      For SDMMC peripheral, pass a pointer to sdmmc_slot_config_t
+ *                      structure initialized using SDMMC_SLOT_CONFIG_DEFAULT.
+ * @param mount_config  pointer to structure with extra parameters for mounting FATFS
+ * @param[out] out_card  if not NULL, pointer to the card information structure will be returned via this argument
+ * @return
+ *      - ESP_OK on success
+ *      - ESP_ERR_INVALID_STATE if esp_vfs_fat_sdmmc_mount was already called
+ *      - ESP_ERR_NO_MEM if memory can not be allocated
+ *      - ESP_FAIL if partition can not be mounted
+ *      - other error codes from SDMMC or SPI drivers, SDMMC protocol, or FATFS drivers
+ */
+esp_err_t esp_vfs_fat_sdmmc_mount_mh(const char* base_path, const sdmmc_host_t* host_config, const void* slot_config, const esp_vfs_fat_mount_config_t* mount_config, sdmmc_card_t** out_card);
+
+/**
+ * @brief Convenience function to get FAT filesystem on SD card registered in VFS
+ *
+ * This is an all-in-one function which does the following:
+ * - initializes an SPI Master device based on the SPI Master driver with configuration in
+ *   slot_config, and attach it to an initialized SPI bus.
+ * - initializes SD card with configuration in host_config_input
+ * - mounts FAT partition on SD card using FATFS library, with configuration in mount_config
+ * - registers FATFS library with VFS, with prefix given by base_prefix variable
+ *
+ * This function is intended to make example code more compact.
+ * For real world applications, developers should implement the logic of
+ * probing SD card, locating and mounting partition, and registering FATFS in VFS,
+ * with proper error checking and handling of exceptional conditions.
+ *
+ * @note This function try to attach the new SD SPI device to the bus specified in host_config.
+ *       Make sure the SPI bus specified in `host_config->slot` have been initialized by
+ *       `spi_bus_initialize()` before.
+ *
+ * @param base_path     path where partition should be registered (e.g. "/sdcard")
+ * @param host_config_input Pointer to structure describing SDMMC host. This structure can be
+ *                          initialized using SDSPI_HOST_DEFAULT() macro.
+ * @param slot_config   Pointer to structure with slot configuration.
+ *                      For SPI peripheral, pass a pointer to sdspi_device_config_t
+ *                      structure initialized using SDSPI_DEVICE_CONFIG_DEFAULT().
+ * @param mount_config  pointer to structure with extra parameters for mounting FATFS
+ * @param[out] out_card If not NULL, pointer to the card information structure will be returned via
+ *                      this argument. It is suggested to hold this handle and use it to unmount the card later if
+ *                      needed. Otherwise it's not suggested to use more than one card at the same time and unmount one
+ *                      of them in your application.
+ * @return
+ *      - ESP_OK on success
+ *      - ESP_ERR_INVALID_STATE if esp_vfs_fat_sdmmc_mount was already called
+ *      - ESP_ERR_NO_MEM if memory can not be allocated
+ *      - ESP_FAIL if partition can not be mounted
+ *      - other error codes from SDMMC or SPI drivers, SDMMC protocol, or FATFS drivers
+ */
+esp_err_t esp_vfs_fat_sdspi_mount_mh(const char* base_path, const sdmmc_host_t* host_config_input, const sdspi_device_config_t* slot_config, const esp_vfs_fat_mount_config_t* mount_config, sdmmc_card_t** out_card);
+
+#ifdef __cplusplus
+}
+#endif
+
+
+
+
+
+
+
+
+
+

+ 53 - 1
code/components/jomjol_image_proc/CImageBasis.cpp

@@ -443,7 +443,7 @@ void CImageBasis::LoadFromMemory(stbi_uc *_buffer, int len)
         //free_psram_heap(std::string(TAG) + "->rgb_image (LoadFromMemory)", rgb_image);
     }
 
-    rgb_image = stbi_load_from_memory(_buffer, len, &width, &height, &channels, 3);
+    rgb_image = stbi_load_from_memory(_buffer, len, &width, &height, &channels, STBI_rgb);
     bpp = channels;
     ESP_LOGD(TAG, "Image loaded from memory: %d, %d, %d", width, height, channels);
     
@@ -459,6 +459,44 @@ void CImageBasis::LoadFromMemory(stbi_uc *_buffer, int len)
 }
 
 
+void CImageBasis::crop_image(unsigned short cropLeft, unsigned short cropRight, unsigned short cropTop, unsigned short cropBottom)
+{
+    unsigned int maxTopIndex = cropTop * width * channels;
+    unsigned int minBottomIndex = ((width*height) - (cropBottom * width)) * channels;
+    unsigned short maxX = width - cropRight; // In pixels
+    unsigned short newWidth = width - cropLeft - cropRight;
+    unsigned short newHeight = height - cropTop - cropBottom;
+
+    unsigned int writeIndex = 0;
+    // Loop over all bytes
+    for (int i = 0; i < width * height * channels; i += channels) {
+        // Calculate current X, Y pixel position
+        int x = (i/channels) % width;
+
+        // Crop from the top
+        if (i < maxTopIndex) { continue; }
+
+        // Crop from the bottom
+        if (i > minBottomIndex) { continue; }
+
+        // Crop from the left
+        if (x <= cropLeft) { continue; }
+
+        // Crop from the right
+        if (x > maxX) { continue; }
+
+        // If we get here, keep the pixels
+        for (int c = 0; c < channels; c++) {
+            rgb_image[writeIndex++] = rgb_image[i+c];
+        }
+    }
+
+    // Set the new dimensions of the framebuffer for further use.
+    width = newWidth;
+    height = newHeight;
+}
+
+
 CImageBasis::CImageBasis(string _name, CImageBasis *_copyfrom) 
 {
     name = _name;
@@ -598,6 +636,20 @@ CImageBasis::CImageBasis(string _name, uint8_t* _rgb_image, int _channels, int _
 }
 
 
+void CImageBasis::Negative(void)
+{
+    RGBImageLock();
+
+    for (int i = 0; i < width * height * channels; i += channels) {
+        for (int c = 0; c < channels; c++) {
+            rgb_image[i+c] = 255 - rgb_image[i+c];
+        }
+    }
+
+    RGBImageRelease();
+}
+
+
 void CImageBasis::Contrast(float _contrast)  //input range [-100..100]
 {
     stbi_uc* p_source;

+ 2 - 0
code/components/jomjol_image_proc/CImageBasis.h

@@ -56,6 +56,7 @@ class CImageBasis
         void drawEllipse(int x1, int y1, int radx, int rady, int r, int g, int b, int thickness = 1);
 
         void setPixelColor(int x, int y, int r, int g, int b);
+        void Negative(void);
         void Contrast(float _contrast);
         bool ImageOkay();
         bool CopyFromMemory(uint8_t* _source, int _size);
@@ -74,6 +75,7 @@ class CImageBasis
 
         void Resize(int _new_dx, int _new_dy);        
         void Resize(int _new_dx, int _new_dy, CImageBasis *_target);        
+        void crop_image(unsigned short cropLeft, unsigned short cropRight, unsigned short cropTop, unsigned short cropBottom);
 
         void LoadFromMemory(stbi_uc *_buffer, int len);
 

+ 309 - 353
code/components/jomjol_image_proc/CRotateImage.cpp

@@ -1,353 +1,309 @@
-#include <string>
-#include "CRotateImage.h"
-#include "psram.h"
-
-static const char *TAG = "C ROTATE IMG";
-
-CRotateImage::CRotateImage(std::string _name, CImageBasis *_org, CImageBasis *_temp, bool _flip) : CImageBasis(_name)
-{
-    rgb_image = _org->rgb_image;
-    channels = _org->channels;
-    width = _org->width;
-    height = _org->height;
-    bpp = _org->bpp;
-    externalImage = true;   
-    ImageTMP = _temp;   
-    ImageOrg = _org; 
-    islocked = false;
-    doflip = _flip;
-}
-
-
-void CRotateImage::Mirror(){
-    int memsize = width * height * channels;
-    uint8_t* odata;
-    if (ImageTMP)
-    {
-        odata = ImageTMP->RGBImageLock();
-    }
-    else
-    {
-        odata = (unsigned char*)malloc_psram_heap(std::string(TAG) + "->odata", memsize, MALLOC_CAP_SPIRAM);
-    }
-
-
-    int x_source, y_source;
-    stbi_uc* p_target;
-    stbi_uc* p_source;
-
-    RGBImageLock();
-
-    for (int x = 0; x < width; ++x)
-        for (int y = 0; y < height; ++y)
-        {
-            p_target = odata + (channels * (y * width + x));
-
-            x_source = width - x;
-            y_source = y;
-
-            p_source = rgb_image + (channels * (y_source * width + x_source));
-            for (int _channels = 0; _channels < channels; ++_channels)
-                p_target[_channels] = p_source[_channels];
-        }
-
-    //    memcpy(rgb_image, odata, memsize);
-    memCopy(odata, rgb_image, memsize);
-    if (!ImageTMP)
-        free_psram_heap(std::string(TAG) + "->odata", odata);
-
-    if (ImageTMP)
-        ImageTMP->RGBImageRelease();
-
-    RGBImageRelease();
-}
-
-void CRotateImage::Rotate(float _angle, int _centerx, int _centery)
-{
-    int org_width, org_height;
-    float m[2][3];
-
-    float x_center = _centerx;
-    float y_center = _centery;
-    _angle = _angle / 180 * M_PI;
-
-    if (doflip)
-    {
-        org_width = width;
-        org_height = height;
-        height = org_width;
-        width = org_height;
-        x_center =  x_center - (org_width/2) + (org_height/2);
-        y_center =  y_center + (org_width/2) - (org_height/2);
-        if (ImageOrg)
-        {
-            ImageOrg->height = height;
-            ImageOrg->width = width;
-        }
-    }
-    else
-    {
-        org_width = width;
-        org_height = height;
-    }
-
-    m[0][0] = cos(_angle);
-    m[0][1] = sin(_angle);
-    m[0][2] = (1 - m[0][0]) * x_center - m[0][1] * y_center;
-
-    m[1][0] = -m[0][1];
-    m[1][1] = m[0][0];
-    m[1][2] = m[0][1] * x_center + (1 - m[0][0]) * y_center;
-
-    if (doflip)
-    {
-        m[0][2] = m[0][2] + (org_width/2) - (org_height/2);
-        m[1][2] = m[1][2] - (org_width/2) + (org_height/2);
-    }
-
-    int memsize = width * height * channels;
-    uint8_t* odata;
-    if (ImageTMP)
-    {
-        odata = ImageTMP->RGBImageLock();
-    }
-    else
-    {
-        odata = (unsigned char*)malloc_psram_heap(std::string(TAG) + "->odata", memsize, MALLOC_CAP_SPIRAM);
-    }
-    
-
-    int x_source, y_source;
-    stbi_uc* p_target;
-    stbi_uc* p_source;
-
-    RGBImageLock();
-
-    for (int x = 0; x < width; ++x)
-        for (int y = 0; y < height; ++y)
-        {
-            p_target = odata + (channels * (y * width + x));
-
-            x_source = int(m[0][0] * x + m[0][1] * y);
-            y_source = int(m[1][0] * x + m[1][1] * y);
-
-            x_source += int(m[0][2]);
-            y_source += int(m[1][2]);
-
-            if ((x_source >= 0) && (x_source < org_width) && (y_source >= 0) && (y_source < org_height))
-            {
-                p_source = rgb_image + (channels * (y_source * org_width + x_source));
-                for (int _channels = 0; _channels < channels; ++_channels)
-                    p_target[_channels] = p_source[_channels];
-            }
-            else
-            {
-                for (int _channels = 0; _channels < channels; ++_channels)
-                    p_target[_channels] = 255;
-            }
-        }
-
-    //    memcpy(rgb_image, odata, memsize);
-    memCopy(odata, rgb_image, memsize);
-
-    if (!ImageTMP)
-    {
-        free_psram_heap(std::string(TAG) + "->odata", odata);
-    }
-    if (ImageTMP)
-        ImageTMP->RGBImageRelease();
-
-    RGBImageRelease();
-}
-
-
-
-void CRotateImage::RotateAntiAliasing(float _angle, int _centerx, int _centery)
-{
-    int org_width, org_height;
-    float m[2][3];
-
-    float x_center = _centerx;
-    float y_center = _centery;
-    _angle = _angle / 180 * M_PI;
-
-    if (doflip)
-    {
-        org_width = width;
-        org_height = height;
-        height = org_width;
-        width = org_height;
-        x_center =  x_center - (org_width/2) + (org_height/2);
-        y_center =  y_center + (org_width/2) - (org_height/2);
-        if (ImageOrg)
-        {
-            ImageOrg->height = height;
-            ImageOrg->width = width;
-        }
-    }
-    else
-    {
-        org_width = width;
-        org_height = height;
-    }
-
-    m[0][0] = cos(_angle);
-    m[0][1] = sin(_angle);
-    m[0][2] = (1 - m[0][0]) * x_center - m[0][1] * y_center;
-
-    m[1][0] = -m[0][1];
-    m[1][1] = m[0][0];
-    m[1][2] = m[0][1] * x_center + (1 - m[0][0]) * y_center;
-
-    if (doflip)
-    {
-        m[0][2] = m[0][2] + (org_width/2) - (org_height/2);
-        m[1][2] = m[1][2] - (org_width/2) + (org_height/2);
-    }
-
-    int memsize = width * height * channels;
-    uint8_t* odata;
-    if (ImageTMP)
-    {
-        odata = ImageTMP->RGBImageLock();
-    }
-    else
-    {
-        odata = (unsigned char*)malloc_psram_heap(std::string(TAG) + "->odata", memsize, MALLOC_CAP_SPIRAM);
-    }
-    
-
-    int x_source_1, y_source_1, x_source_2, y_source_2;
-    float x_source, y_source;
-    float quad_ul, quad_ur, quad_ol, quad_or;
-    stbi_uc* p_target;
-    stbi_uc *p_source_ul, *p_source_ur, *p_source_ol, *p_source_or;
-
-    RGBImageLock();
-
-    for (int x = 0; x < width; ++x)
-        for (int y = 0; y < height; ++y)
-        {
-            p_target = odata + (channels * (y * width + x));
-
-            x_source = (m[0][0] * x + m[0][1] * y);
-            y_source = (m[1][0] * x + m[1][1] * y);
-
-            x_source += (m[0][2]);
-            y_source += (m[1][2]);
-
-            x_source_1 = (int)x_source;
-            x_source_2 = x_source_1 + 1;
-            y_source_1 = (int)y_source;
-            y_source_2 = y_source_1 + 1;
-
-            quad_ul = (x_source_2 - x_source) * (y_source_2 - y_source);
-            quad_ur = (1- (x_source_2 - x_source)) * (y_source_2 - y_source);
-            quad_or = (x_source_2 - x_source) * (1-(y_source_2 - y_source));
-            quad_ol = (1- (x_source_2 - x_source)) * (1-(y_source_2 - y_source));
-
-
-            if ((x_source_1 >= 0) && (x_source_2 < org_width) && (y_source_1 >= 0) && (y_source_2 < org_height))
-            {
-                p_source_ul = rgb_image + (channels * (y_source_1 * org_width + x_source_1));
-                p_source_ur = rgb_image + (channels * (y_source_1 * org_width + x_source_2));
-                p_source_or = rgb_image + (channels * (y_source_2 * org_width + x_source_1));
-                p_source_ol = rgb_image + (channels * (y_source_2 * org_width + x_source_2));
-                for (int _channels = 0; _channels < channels; ++_channels)
-                {
-                    p_target[_channels] = (int)((float)p_source_ul[_channels] * quad_ul
-                                                + (float)p_source_ur[_channels] * quad_ur
-                                                + (float)p_source_or[_channels] * quad_or
-                                                + (float)p_source_ol[_channels] * quad_ol);
-                }
-            }
-            else
-            {
-                for (int _channels = 0; _channels < channels; ++_channels)
-                    p_target[_channels] = 255;
-            }
-        }
-
-    //    memcpy(rgb_image, odata, memsize);
-    memCopy(odata, rgb_image, memsize);
-
-    if (!ImageTMP)
-    {
-        free_psram_heap(std::string(TAG) + "->odata", odata);
-    }
-    if (ImageTMP)
-        ImageTMP->RGBImageRelease();
-
-    RGBImageRelease();
-}
-
-
-void CRotateImage::Rotate(float _angle)
-{
-//    ESP_LOGD(TAG, "width %d, height %d", width, height);
-    Rotate(_angle, width / 2, height / 2);
-}
-
-void CRotateImage::RotateAntiAliasing(float _angle)
-{
-//    ESP_LOGD(TAG, "width %d, height %d", width, height);
-    RotateAntiAliasing(_angle, width / 2, height / 2);
-}
-
-void CRotateImage::Translate(int _dx, int _dy)
-{
-    int memsize = width * height * channels;
-    uint8_t* odata;
-    if (ImageTMP)
-    {
-        odata = ImageTMP->RGBImageLock();
-    }
-    else
-    {
-        odata = (unsigned char*)malloc_psram_heap(std::string(TAG) + "->odata", memsize, MALLOC_CAP_SPIRAM);
-    }
-
-
-
-    int x_source, y_source;
-    stbi_uc* p_target;
-    stbi_uc* p_source;
-
-    RGBImageLock();
-
-    for (int x = 0; x < width; ++x)
-        for (int y = 0; y < height; ++y)
-        {
-            p_target = odata + (channels * (y * width + x));
-
-            x_source = x - _dx;
-            y_source = y - _dy;
-
-            if ((x_source >= 0) && (x_source < width) && (y_source >= 0) && (y_source < height))
-            {
-                p_source = rgb_image + (channels * (y_source * width + x_source));
-                for (int _channels = 0; _channels < channels; ++_channels)
-                    p_target[_channels] = p_source[_channels];
-            }
-            else
-            {
-                for (int _channels = 0; _channels < channels; ++_channels)
-                    p_target[_channels] = 255;
-            }
-        }
-
-    //    memcpy(rgb_image, odata, memsize);
-    memCopy(odata, rgb_image, memsize);
-    if (!ImageTMP)
-    {
-        free_psram_heap(std::string(TAG) + "->odata", odata);
-    }
-
-    if (ImageTMP)
-    {
-        ImageTMP->RGBImageRelease();
-    }
-    RGBImageRelease();
-
-}
-
+#include <string>
+#include "CRotateImage.h"
+#include "psram.h"
+
+static const char *TAG = "C ROTATE IMG";
+
+CRotateImage::CRotateImage(std::string _name, CImageBasis *_org, CImageBasis *_temp, bool _flip) : CImageBasis(_name)
+{
+    rgb_image = _org->rgb_image;
+    channels = _org->channels;
+    width = _org->width;
+    height = _org->height;
+    bpp = _org->bpp;
+    externalImage = true;   
+    ImageTMP = _temp;   
+    ImageOrg = _org; 
+    islocked = false;
+    doflip = _flip;
+}
+
+void CRotateImage::Rotate(float _angle, int _centerx, int _centery)
+{
+    int org_width, org_height;
+    float m[2][3];
+
+    float x_center = _centerx;
+    float y_center = _centery;
+    _angle = _angle / 180 * M_PI;
+
+    if (doflip)
+    {
+        org_width = width;
+        org_height = height;
+        height = org_width;
+        width = org_height;
+        x_center =  x_center - (org_width/2) + (org_height/2);
+        y_center =  y_center + (org_width/2) - (org_height/2);
+        if (ImageOrg)
+        {
+            ImageOrg->height = height;
+            ImageOrg->width = width;
+        }
+    }
+    else
+    {
+        org_width = width;
+        org_height = height;
+    }
+
+    m[0][0] = cos(_angle);
+    m[0][1] = sin(_angle);
+    m[0][2] = (1 - m[0][0]) * x_center - m[0][1] * y_center;
+
+    m[1][0] = -m[0][1];
+    m[1][1] = m[0][0];
+    m[1][2] = m[0][1] * x_center + (1 - m[0][0]) * y_center;
+
+    if (doflip)
+    {
+        m[0][2] = m[0][2] + (org_width/2) - (org_height/2);
+        m[1][2] = m[1][2] - (org_width/2) + (org_height/2);
+    }
+
+    int memsize = width * height * channels;
+    uint8_t* odata;
+    if (ImageTMP)
+    {
+        odata = ImageTMP->RGBImageLock();
+    }
+    else
+    {
+        odata = (unsigned char*)malloc_psram_heap(std::string(TAG) + "->odata", memsize, MALLOC_CAP_SPIRAM);
+    }
+    
+
+    int x_source, y_source;
+    stbi_uc* p_target;
+    stbi_uc* p_source;
+
+    RGBImageLock();
+
+    for (int x = 0; x < width; ++x)
+        for (int y = 0; y < height; ++y)
+        {
+            p_target = odata + (channels * (y * width + x));
+
+            x_source = int(m[0][0] * x + m[0][1] * y);
+            y_source = int(m[1][0] * x + m[1][1] * y);
+
+            x_source += int(m[0][2]);
+            y_source += int(m[1][2]);
+
+            if ((x_source >= 0) && (x_source < org_width) && (y_source >= 0) && (y_source < org_height))
+            {
+                p_source = rgb_image + (channels * (y_source * org_width + x_source));
+                for (int _channels = 0; _channels < channels; ++_channels)
+                    p_target[_channels] = p_source[_channels];
+            }
+            else
+            {
+                for (int _channels = 0; _channels < channels; ++_channels)
+                    p_target[_channels] = 255;
+            }
+        }
+
+    //    memcpy(rgb_image, odata, memsize);
+    memCopy(odata, rgb_image, memsize);
+
+    if (!ImageTMP)
+    {
+        free_psram_heap(std::string(TAG) + "->odata", odata);
+    }
+    if (ImageTMP)
+        ImageTMP->RGBImageRelease();
+
+    RGBImageRelease();
+}
+
+
+
+void CRotateImage::RotateAntiAliasing(float _angle, int _centerx, int _centery)
+{
+    int org_width, org_height;
+    float m[2][3];
+
+    float x_center = _centerx;
+    float y_center = _centery;
+    _angle = _angle / 180 * M_PI;
+
+    if (doflip)
+    {
+        org_width = width;
+        org_height = height;
+        height = org_width;
+        width = org_height;
+        x_center =  x_center - (org_width/2) + (org_height/2);
+        y_center =  y_center + (org_width/2) - (org_height/2);
+        if (ImageOrg)
+        {
+            ImageOrg->height = height;
+            ImageOrg->width = width;
+        }
+    }
+    else
+    {
+        org_width = width;
+        org_height = height;
+    }
+
+    m[0][0] = cos(_angle);
+    m[0][1] = sin(_angle);
+    m[0][2] = (1 - m[0][0]) * x_center - m[0][1] * y_center;
+
+    m[1][0] = -m[0][1];
+    m[1][1] = m[0][0];
+    m[1][2] = m[0][1] * x_center + (1 - m[0][0]) * y_center;
+
+    if (doflip)
+    {
+        m[0][2] = m[0][2] + (org_width/2) - (org_height/2);
+        m[1][2] = m[1][2] - (org_width/2) + (org_height/2);
+    }
+
+    int memsize = width * height * channels;
+    uint8_t* odata;
+    if (ImageTMP)
+    {
+        odata = ImageTMP->RGBImageLock();
+    }
+    else
+    {
+        odata = (unsigned char*)malloc_psram_heap(std::string(TAG) + "->odata", memsize, MALLOC_CAP_SPIRAM);
+    }
+    
+
+    int x_source_1, y_source_1, x_source_2, y_source_2;
+    float x_source, y_source;
+    float quad_ul, quad_ur, quad_ol, quad_or;
+    stbi_uc* p_target;
+    stbi_uc *p_source_ul, *p_source_ur, *p_source_ol, *p_source_or;
+
+    RGBImageLock();
+
+    for (int x = 0; x < width; ++x)
+        for (int y = 0; y < height; ++y)
+        {
+            p_target = odata + (channels * (y * width + x));
+
+            x_source = (m[0][0] * x + m[0][1] * y);
+            y_source = (m[1][0] * x + m[1][1] * y);
+
+            x_source += (m[0][2]);
+            y_source += (m[1][2]);
+
+            x_source_1 = (int)x_source;
+            x_source_2 = x_source_1 + 1;
+            y_source_1 = (int)y_source;
+            y_source_2 = y_source_1 + 1;
+
+            quad_ul = (x_source_2 - x_source) * (y_source_2 - y_source);
+            quad_ur = (1- (x_source_2 - x_source)) * (y_source_2 - y_source);
+            quad_or = (x_source_2 - x_source) * (1-(y_source_2 - y_source));
+            quad_ol = (1- (x_source_2 - x_source)) * (1-(y_source_2 - y_source));
+
+
+            if ((x_source_1 >= 0) && (x_source_2 < org_width) && (y_source_1 >= 0) && (y_source_2 < org_height))
+            {
+                p_source_ul = rgb_image + (channels * (y_source_1 * org_width + x_source_1));
+                p_source_ur = rgb_image + (channels * (y_source_1 * org_width + x_source_2));
+                p_source_or = rgb_image + (channels * (y_source_2 * org_width + x_source_1));
+                p_source_ol = rgb_image + (channels * (y_source_2 * org_width + x_source_2));
+                for (int _channels = 0; _channels < channels; ++_channels)
+                {
+                    p_target[_channels] = (int)((float)p_source_ul[_channels] * quad_ul
+                                                + (float)p_source_ur[_channels] * quad_ur
+                                                + (float)p_source_or[_channels] * quad_or
+                                                + (float)p_source_ol[_channels] * quad_ol);
+                }
+            }
+            else
+            {
+                for (int _channels = 0; _channels < channels; ++_channels)
+                    p_target[_channels] = 255;
+            }
+        }
+
+    //    memcpy(rgb_image, odata, memsize);
+    memCopy(odata, rgb_image, memsize);
+
+    if (!ImageTMP)
+    {
+        free_psram_heap(std::string(TAG) + "->odata", odata);
+    }
+    if (ImageTMP)
+        ImageTMP->RGBImageRelease();
+
+    RGBImageRelease();
+}
+
+
+void CRotateImage::Rotate(float _angle)
+{
+//    ESP_LOGD(TAG, "width %d, height %d", width, height);
+    Rotate(_angle, width / 2, height / 2);
+}
+
+void CRotateImage::RotateAntiAliasing(float _angle)
+{
+//    ESP_LOGD(TAG, "width %d, height %d", width, height);
+    RotateAntiAliasing(_angle, width / 2, height / 2);
+}
+
+void CRotateImage::Translate(int _dx, int _dy)
+{
+    int memsize = width * height * channels;
+    uint8_t* odata;
+    if (ImageTMP)
+    {
+        odata = ImageTMP->RGBImageLock();
+    }
+    else
+    {
+        odata = (unsigned char*)malloc_psram_heap(std::string(TAG) + "->odata", memsize, MALLOC_CAP_SPIRAM);
+    }
+
+
+
+    int x_source, y_source;
+    stbi_uc* p_target;
+    stbi_uc* p_source;
+
+    RGBImageLock();
+
+    for (int x = 0; x < width; ++x)
+        for (int y = 0; y < height; ++y)
+        {
+            p_target = odata + (channels * (y * width + x));
+
+            x_source = x - _dx;
+            y_source = y - _dy;
+
+            if ((x_source >= 0) && (x_source < width) && (y_source >= 0) && (y_source < height))
+            {
+                p_source = rgb_image + (channels * (y_source * width + x_source));
+                for (int _channels = 0; _channels < channels; ++_channels)
+                    p_target[_channels] = p_source[_channels];
+            }
+            else
+            {
+                for (int _channels = 0; _channels < channels; ++_channels)
+                    p_target[_channels] = 255;
+            }
+        }
+
+    //    memcpy(rgb_image, odata, memsize);
+    memCopy(odata, rgb_image, memsize);
+    if (!ImageTMP)
+    {
+        free_psram_heap(std::string(TAG) + "->odata", odata);
+    }
+
+    if (ImageTMP)
+    {
+        ImageTMP->RGBImageRelease();
+    }
+    RGBImageRelease();
+
+}
+

+ 27 - 28
code/components/jomjol_image_proc/CRotateImage.h

@@ -1,29 +1,28 @@
-#pragma once
-
-#ifndef CROTATEIMAGE_H
-#define CROTATEIMAGE_H
-
-#include "CImageBasis.h"
-
-
-class CRotateImage: public CImageBasis
-{
-
-    public:
-        CImageBasis *ImageTMP, *ImageOrg;
-        bool doflip;
-        CRotateImage(std::string name, std::string _image, bool _flip = false) : CImageBasis(name, _image) {ImageTMP = NULL; ImageOrg = NULL; doflip = _flip;};
-        CRotateImage(std::string name, uint8_t* _rgb_image, int _channels, int _width, int _height, int _bpp, bool _flip = false) : CImageBasis(name, _rgb_image, _channels, _width, _height, _bpp) {ImageTMP = NULL;  ImageOrg = NULL; doflip = _flip;};
-        CRotateImage(std::string name, CImageBasis *_org, CImageBasis *_temp, bool _flip = false);
-
-        void Rotate(float _angle);
-        void RotateAntiAliasing(float _angle);
-       
-        void Rotate(float _angle, int _centerx, int _centery);
-        void RotateAntiAliasing(float _angle, int _centerx, int _centery);
-
-        void Translate(int _dx, int _dy);
-        void Mirror();
-};
-
+#pragma once
+
+#ifndef CROTATEIMAGE_H
+#define CROTATEIMAGE_H
+
+#include "CImageBasis.h"
+
+
+class CRotateImage: public CImageBasis
+{
+
+    public:
+        CImageBasis *ImageTMP, *ImageOrg;
+        bool doflip;
+        CRotateImage(std::string name, std::string _image, bool _flip = false) : CImageBasis(name, _image) {ImageTMP = NULL; ImageOrg = NULL; doflip = _flip;};
+        CRotateImage(std::string name, uint8_t* _rgb_image, int _channels, int _width, int _height, int _bpp, bool _flip = false) : CImageBasis(name, _rgb_image, _channels, _width, _height, _bpp) {ImageTMP = NULL;  ImageOrg = NULL; doflip = _flip;};
+        CRotateImage(std::string name, CImageBasis *_org, CImageBasis *_temp, bool _flip = false);
+
+        void Rotate(float _angle);
+        void RotateAntiAliasing(float _angle);
+       
+        void Rotate(float _angle, int _centerx, int _centery);
+        void RotateAntiAliasing(float _angle, int _centerx, int _centery);
+
+        void Translate(int _dx, int _dy);
+};
+
 #endif //CROTATEIMAGE_H

+ 13 - 28
code/components/jomjol_influxdb/interface_influxdb.cpp

@@ -5,6 +5,7 @@
 #include <time.h>
 #include "ClassLogFile.h"
 #include "esp_http_client.h"
+#include "time_sntp.h"
 #include "../../include/defines.h"
 
 
@@ -30,7 +31,7 @@ void InfluxDB_V2_Init(std::string _uri, std::string _bucket, std::string _org, s
     _influxDB_V2_Token = _token;
 }
 
-void InfluxDB_V2_Publish(std::string _measurement, std::string _key, std::string _content, std::string _timestamp) 
+void InfluxDB_V2_Publish(std::string _measurement, std::string _key, std::string _content, long int _timeUTC) 
 {
     char response_buffer[MAX_HTTP_OUTPUT_BUFFER] = {0};
     esp_http_client_config_t http_config = {
@@ -41,28 +42,20 @@ void InfluxDB_V2_Publish(std::string _measurement, std::string _key, std::string
        .user_data = response_buffer
     };
 
-    LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "InfluxDB_V2_Publish - Key: " + _key + ", Content: " + _content + ", Timestamp: " + _timestamp);
+    LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "InfluxDB_V2_Publish - Key: " + _key + ", Content: " + _content + ", timeUTC: " + std::to_string(_timeUTC));
 
     std::string payload;
     char nowTimestamp[21];
 
-    if (_timestamp.length() > 0)
+    if (_timeUTC > 0)
     {
-        struct tm tm;
-
-        time_t t;
-        time(&t);
-        localtime_r(&t, &tm); // Extract DST setting from actual time to consider it for timestamp evaluation
-
-        strptime(_timestamp.c_str(), PREVALUE_TIME_FORMAT_OUTPUT, &tm);
-        t = mktime(&tm);
-        LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Timestamp: " + _timestamp + ", Timestamp (UTC): " + std::to_string(t));
-
-        sprintf(nowTimestamp,"%ld000000000", (long) t);           // UTC
+        LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Timestamp (UTC): " + std::to_string(_timeUTC));
+        sprintf(nowTimestamp,"%ld000000000", _timeUTC);           // UTC
         payload = _measurement + " " + _key + "=" + _content + " " + nowTimestamp;
     }
     else
     {
+        LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "no timestamp given");
         payload = _measurement + " " + _key + "=" + _content;
     }
 
@@ -137,7 +130,7 @@ static esp_err_t http_event_handler(esp_http_client_event_t *evt)
     return ESP_OK;
 }
 
-void InfluxDBPublish(std::string _measurement, std::string _key, std::string _content, std::string _timestamp) {
+void InfluxDBPublish(std::string _measurement, std::string _key, std::string _content, long int _timeUTC) {
     char response_buffer[MAX_HTTP_OUTPUT_BUFFER] = {0};
     esp_http_client_config_t http_config = {
        .user_agent = "ESP32 Meter reader",
@@ -156,25 +149,17 @@ void InfluxDBPublish(std::string _measurement, std::string _key, std::string _co
     std::string payload;
     char nowTimestamp[21];
 
-    LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "InfluxDBPublish - Key: " + _key + ", Content: " + _content + ", Timestamp: " + _timestamp);
+    LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "InfluxDBPublish - Key: " + _key + ", Content: " + _content + ", timeUTC: " + std::to_string(_timeUTC));
 
-    if (_timestamp.length() > 0)
+    if (_timeUTC > 0)
     {
-        struct tm tm;
-
-        time_t t;
-        time(&t);
-        localtime_r(&t, &tm); // Extract DST setting from actual time to consider it for timestamp evaluation
-
-        strptime(_timestamp.c_str(), PREVALUE_TIME_FORMAT_OUTPUT, &tm);
-        t = mktime(&tm);
-        LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Timestamp: " + _timestamp + ", Timestamp (UTC): " + std::to_string(t));
-
-        sprintf(nowTimestamp,"%ld000000000", (long) t);           // UTC
+        LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Timestamp (UTC): " + std::to_string(_timeUTC));
+        sprintf(nowTimestamp,"%ld000000000", _timeUTC);           // UTC
         payload = _measurement + " " + _key + "=" + _content + " " + nowTimestamp;
     }
     else
     {
+        LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "no timestamp given");
         payload = _measurement + " " + _key + "=" + _content;
     }
 

+ 2 - 2
code/components/jomjol_influxdb/interface_influxdb.h

@@ -10,11 +10,11 @@
 
 // Interface to InfluxDB v1.x
 void InfluxDBInit(std::string _influxDBURI, std::string _database, std::string _user, std::string _password);
-void InfluxDBPublish(std::string _measurement, std::string _key, std::string _content, std::string _timestamp);
+void InfluxDBPublish(std::string _measurement, std::string _key, std::string _content, long int _timeUTC);
 
 // Interface to InfluxDB v2.x
 void InfluxDB_V2_Init(std::string _uri, std::string _bucket, std::string _org, std::string _token);
-void InfluxDB_V2_Publish(std::string _measurement, std::string _key, std::string _content, std::string _timestamp);
+void InfluxDB_V2_Publish(std::string _measurement, std::string _key, std::string _content, long int _timeUTC);
 
 
 

+ 3 - 0
code/components/jomjol_mqtt/server_mqtt.cpp

@@ -21,6 +21,7 @@ static const char *TAG = "MQTT SERVER";
 extern const char* libfive_git_version(void);
 extern const char* libfive_git_revision(void);
 extern const char* libfive_git_branch(void);
+extern std::string getFwVersion(void);
 
 std::vector<NumberPost*>* NUMBERS;
 bool HomeassistantDiscovery = false;
@@ -151,6 +152,7 @@ bool MQTThomeassistantDiscovery(int qos) {
     //                                                   Group | Field            | User Friendly Name | Icon                      | Unit | Device Class     | State Class  | Entity Category
     allSendsSuccessed |= sendHomeAssistantDiscoveryTopic("",     "uptime",          "Uptime",            "clock-time-eight-outline", "s",   "",                "",            "diagnostic", qos);
     allSendsSuccessed |= sendHomeAssistantDiscoveryTopic("",     "MAC",             "MAC Address",       "network-outline",          "",    "",                "",            "diagnostic", qos);
+    allSendsSuccessed |= sendHomeAssistantDiscoveryTopic("",     "fwVersion",       "Firmware Version",  "application-outline",      "",    "",                "",            "diagnostic", qos);
     allSendsSuccessed |= sendHomeAssistantDiscoveryTopic("",     "hostname",        "Hostname",          "network-outline",          "",    "",                "",            "diagnostic", qos);
     allSendsSuccessed |= sendHomeAssistantDiscoveryTopic("",     "freeMem",         "Free Memory",       "memory",                   "B",   "",                "measurement", "diagnostic", qos);
     allSendsSuccessed |= sendHomeAssistantDiscoveryTopic("",     "wifiRSSI",        "Wi-Fi RSSI",        "wifi",                     "dBm", "signal_strength", "",            "diagnostic", qos);
@@ -244,6 +246,7 @@ bool publishStaticData(int qos) {
 
 	int aFreeInternalHeapSizeBefore = heap_caps_get_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
 
+    allSendsSuccessed |= MQTTPublish(maintopic + "/" + "fwVersion", getFwVersion().c_str(), qos, retainFlag);
     allSendsSuccessed |= MQTTPublish(maintopic + "/" + "MAC", getMac(), qos, retainFlag);
     allSendsSuccessed |= MQTTPublish(maintopic + "/" + "IP", *getIPAddress(), qos, retainFlag);
     allSendsSuccessed |= MQTTPublish(maintopic + "/" + "hostname", wlan_config.hostname, qos, retainFlag);

+ 15 - 1
code/components/jomjol_tfliteclass/CTfLiteClass.cpp

@@ -332,4 +332,18 @@ CTfLiteClass::~CTfLiteClass()
   delete this->interpreter;
 
   psram_free_shared_tensor_arena_and_model_memory();
-}        
+}        
+
+#ifdef SUPRESS_TFLITE_ERRORS
+namespace tflite 
+{
+//tflite::ErrorReporter
+//  int OwnMicroErrorReporter::Report(const char* format, va_list args) 
+
+  int OwnMicroErrorReporter::Report(const char* format, va_list args) 
+  {
+    return 0;
+  }
+} 
+#endif
+ 

+ 1 - 1
code/components/jomjol_time_sntp/CMakeLists.txt

@@ -2,6 +2,6 @@ FILE(GLOB_RECURSE app_sources ${CMAKE_CURRENT_SOURCE_DIR}/*.*)
 
 idf_component_register(SRCS ${app_sources}
                     INCLUDE_DIRS "."
-                    REQUIRES esp-tflite-micro jomjol_logfile jomjol_configfile)
+                    REQUIRES esp_netif esp-tflite-micro jomjol_logfile jomjol_configfile)
 
 

+ 46 - 13
code/components/jomjol_time_sntp/time_sntp.cpp

@@ -10,7 +10,8 @@
 #include "esp_log.h"
 #include "esp_attr.h"
 #include "esp_sleep.h"
-#include "esp_sntp.h"
+#include "esp_netif_sntp.h"
+
 #include "../../include/defines.h"
 
 #include "ClassLogFile.h"
@@ -31,6 +32,9 @@ std::string getNtpStatusText(sntp_sync_status_t status);
 static void setTimeZone(std::string _tzstring);
 static std::string getServerName(void);
 
+int LocalTimeToUTCOffsetSeconds;
+
+
 
 std::string ConvertTimeToString(time_t _time, const char * frm)
 {
@@ -89,15 +93,51 @@ bool time_manual_reset_sync(void)
 }
 
 
+int getUTCOffsetSeconds(std::string &zeitzone)
+{
+    int offset = 0;
+    int vorzeichen = 1;
+    int minuten = 0;
+    int stunden = 0;
+    time_t now;
+    struct tm timeinfo;
+
+    time (&now);
+    localtime_r(&now, &timeinfo);
+    char buffer[80];
+    strftime(buffer, 80, "%z", &timeinfo);
+    zeitzone = std::string(buffer);
+
+    if (zeitzone.length() == 5)
+    {
+        if (zeitzone[0] == '-')
+            vorzeichen = -1; 
+
+        stunden = stoi(zeitzone.substr(1, 2));
+        minuten = stoi(zeitzone.substr(3, 2));
+
+        offset = ((stunden * 60) + minuten) * 60;
+    }
+    return offset;
+}
+
+
 void setTimeZone(std::string _tzstring)
 {
     setenv("TZ", _tzstring.c_str(), 1);
     tzset();    
+
     _tzstring = "Time zone set to " + _tzstring;
     LogFile.WriteToFile(ESP_LOG_INFO, TAG, _tzstring);
+
+    std::string zeitzone;
+    LocalTimeToUTCOffsetSeconds = getUTCOffsetSeconds(zeitzone);
+//    std::string zw = std::to_string(LocalTimeToUTCOffsetSeconds);
+    LogFile.WriteToFile(ESP_LOG_INFO, TAG, "time zone: " + zeitzone + " Delta to UTC: " + std::to_string(LocalTimeToUTCOffsetSeconds) + " seconds");
 }
 
 
+
 std::string getNtpStatusText(sntp_sync_status_t status) {
     if (status == SNTP_SYNC_STATUS_COMPLETED) {
         return "Synchronized";
@@ -235,21 +275,13 @@ bool setupTime() {
 
     if (useNtp) {
         LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Configuring NTP Client...");        
-        sntp_setoperatingmode(SNTP_OPMODE_POLL);
-        sntp_setservername(0, timeServer.c_str());
-        sntp_set_time_sync_notification_cb(time_sync_notification_cb);
-        setTimeZone(timeZone);
+        esp_sntp_config_t config = ESP_NETIF_SNTP_DEFAULT_CONFIG(timeServer.c_str());
+        config.sync_cb = time_sync_notification_cb;
+        esp_netif_sntp_init(&config);
 
-        sntp_init();
-/*        
-        if (!wait_for_timesync())
-        {
-            LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Timesync at startup failed.");        
-        }
-*/
+        setTimeZone(timeZone);
     }
 
-
     /* The RTC keeps the time after a restart (Except on Power On or Pin Reset) 
      * There should only be a minor correction through NTP */
 
@@ -258,6 +290,7 @@ bool setupTime() {
     localtime_r(&now, &timeinfo);
     strftime(strftime_buf, sizeof(strftime_buf), "%Y-%m-%d %H:%M:%S", &timeinfo);
 
+
     if (getTimeIsSet()) {
         LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Time is already set: " + std::string(strftime_buf));
     }

+ 2 - 0
code/components/jomjol_time_sntp/time_sntp.h

@@ -28,5 +28,7 @@ bool setupTime();
 
 bool time_manual_reset_sync(void);
 
+extern int LocalTimeToUTCOffsetSeconds;
+
 
 #endif //TIMESNTP_H

+ 2 - 2
code/components/jomjol_wlan/connect_wlan.cpp

@@ -378,7 +378,7 @@ void wifi_scan(void)
 	else {
     	if (esp_wifi_scan_get_ap_records(&max_number_of_ap_found, wifi_ap_records) != ESP_OK) { // Retrieve results (and free internal heap)
 			LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "wifi_scan: esp_wifi_scan_get_ap_records: Error retrieving datasets");
-			delete wifi_ap_records;
+			delete[] wifi_ap_records;
 			return;
 		}
 	}
@@ -401,7 +401,7 @@ void wifi_scan(void)
 			APWithBetterRSSI = true;
         }
 	}
-	delete wifi_ap_records;
+	delete[] wifi_ap_records;
 }
 
 

+ 3 - 3
code/dependencies.lock

@@ -1,3 +1,3 @@
-manifest_hash: 63f5c6c9f0bcebc7b9ca12d2aa8b26b2c5f5218d377dc4b2375d9b9ca1df7815
-target: esp32
-version: 1.0.0
+manifest_hash: 63f5c6c9f0bcebc7b9ca12d2aa8b26b2c5f5218d377dc4b2375d9b9ca1df7815
+target: esp32
+version: 1.0.0

+ 1 - 9
code/include/defines.h

@@ -66,6 +66,7 @@
     #define FLASH_GPIO GPIO_NUM_4               // PIN for flashlight LED
     #define USE_PWM_LEDFLASH                    // if __LEDGLOBAL is defined, a global variable is used for LED control, otherwise locally and each time a new
     #define CAM_LIVESTREAM_REFRESHRATE 500      // Camera livestream feature: Waiting time in milliseconds to refresh image
+    // #define GRAYSCALE_AS_DEFAULT
 
 
     //ClassControllCamera + ClassFlowTakeImage
@@ -167,15 +168,6 @@
     #define LWT_DISCONNECTED "connection lost"
 
 
-    //CTfLiteClass
-    #define TFLITE_MINIMAL_CHECK(x)                              \
-        if (!(x)) {                                                \
-            fprintf(stderr, "Error at %s:%d\n", __FILE__, __LINE__); \
-            exit(1);                                                 \
-        }
-    // #define SUPRESS_TFLITE_ERRORS // use, to avoid error messages from TFLITE
-
-
     // connect_wlan.cpp
     //******************************
     /* WIFI roaming functionalities 802.11k+v (uses ca. 6kB - 8kB internal RAM; if SCAN CACHE activated: + 1kB / beacon)

+ 28 - 31
code/main/main.cpp

@@ -3,12 +3,6 @@
 #include <vector>
 #include <regex>
 
-//#include "freertos/FreeRTOS.h"
-//#include "freertos/task.h"
-//#include "freertos/event_groups.h"
-
-//#include "driver/gpio.h"
-//#include "sdkconfig.h"
 #include "esp_psram.h"
 #include "esp_pm.h"
 
@@ -16,17 +10,13 @@
 
 #include "esp_chip_info.h"
 
-
-
 // SD-Card ////////////////////
-//#include "nvs_flash.h"
+#include "sdcard_init.h"
 #include "esp_vfs_fat.h"
-//#include "sdmmc_cmd.h"
+#include "ffconf.h"
 #include "driver/sdmmc_host.h"
-//#include "driver/sdmmc_defs.h"
 ///////////////////////////////
 
-
 #include "ClassLogFile.h"
 
 #include "connect_wlan.h"
@@ -38,7 +28,6 @@
 #include "server_ota.h"
 #include "time_sntp.h"
 #include "configFile.h"
-//#include "ClassControllCamera.h"
 #include "server_main.h"
 #include "server_camera.h"
 #ifdef ENABLE_MQTT
@@ -49,7 +38,6 @@
 #include "sdcard_check.h"
 
 #include "../../include/defines.h"
-//#include "server_GPIO.h"
 
 #ifdef ENABLE_SOFTAP
     #include "softAP.h"
@@ -101,6 +89,8 @@ bool setCpuFrequency(void);
 
 static const char *TAG = "MAIN";
 
+#define MOUNT_POINT "/sdcard"
+
 
 bool Init_NVS_SDCard()
 {
@@ -112,26 +102,30 @@ bool Init_NVS_SDCard()
 
     ESP_LOGD(TAG, "Using SDMMC peripheral");
     sdmmc_host_t host = SDMMC_HOST_DEFAULT();
+    host.max_freq_khz = SDMMC_FREQ_HIGHSPEED;
 
     // This initializes the slot without card detect (CD) and write protect (WP) signals.
     // Modify slot_config.gpio_cd and slot_config.gpio_wp if your board has these signals.
     sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
 
-    // To use 1-line SD mode, uncomment the following line:
-    #ifdef __SD_USE_ONE_LINE_MODE__
-        slot_config.width = 1;
-    #endif
-
-    // GPIOs 15, 2, 4, 12, 13 should have external 10k pull-ups.
-    // Internal pull-ups are not sufficient. However, enabling internal pull-ups
-    // does make a difference some boards, so we do that here.
-    gpio_set_pull_mode(GPIO_NUM_15, GPIO_PULLUP_ONLY);   // CMD, needed in 4- and 1- line modes
-    gpio_set_pull_mode(GPIO_NUM_2, GPIO_PULLUP_ONLY);    // D0, needed in 4- and 1-line modes
-    #ifndef __SD_USE_ONE_LINE_MODE__
-        gpio_set_pull_mode(GPIO_NUM_4, GPIO_PULLUP_ONLY);    // D1, needed in 4-line mode only
-        gpio_set_pull_mode(GPIO_NUM_12, GPIO_PULLUP_ONLY);   // D2, needed in 4-line mode only
-    #endif
-    gpio_set_pull_mode(GPIO_NUM_13, GPIO_PULLUP_ONLY);   // D3, needed in 4- and 1-line modes
+   // Set bus width to use:
+   #ifdef __SD_USE_ONE_LINE_MODE__
+      slot_config.width = 1;
+   #else
+      slot_config.width = 4;
+   #endif
+
+    // Enable internal pullups on enabled pins. The internal pullups
+    // are insufficient however, please make sure 10k external pullups are
+    // connected on the bus. This is for debug / example purpose only.
+    slot_config.flags |= SDMMC_SLOT_FLAG_INTERNAL_PULLUP;
+
+    // Der PullUp des GPIO13 wird durch slot_config.flags |= SDMMC_SLOT_FLAG_INTERNAL_PULLUP;
+    // nicht gesetzt, da er eigentlich nicht benötigt wird, 
+    // dies führt jedoch bei schlechten Kopien des AI_THINKER Boards
+    // zu Problemen mit der SD Initialisierung und eventuell sogar zur reboot-loops.
+    // Um diese Probleme zu kompensieren, wird der PullUp manuel gesetzt.
+    gpio_set_pull_mode(GPIO_NUM_13, GPIO_PULLUP_ONLY); // HS2_D3	
 
     // Options for mounting the filesystem.
     // If format_if_mount_failed is set to true, SD card will be partitioned and
@@ -139,15 +133,18 @@ bool Init_NVS_SDCard()
     esp_vfs_fat_sdmmc_mount_config_t mount_config = {
         .format_if_mount_failed = false,
         .max_files = 12,                         // previously -> 2022-09-21: 5, 2023-01-02: 7 
-        .allocation_unit_size = 16 * 1024
+        .allocation_unit_size = 0,               // 0 = auto
+        .disk_status_check_enable = 0
     };
 
     sdmmc_card_t* card;
+    const char mount_point[] = MOUNT_POINT;
+
     // Use settings defined above to initialize SD card and mount FAT filesystem.
     // Note: esp_vfs_fat_sdmmc_mount is an all-in-one convenience function.
     // Please check its source code and implement error recovery when developing
     // production applications.
-    ret = esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, &card);
+    ret = esp_vfs_fat_sdmmc_mount_mh(mount_point, &host, &slot_config, &mount_config, &card);
 
     if (ret != ESP_OK) {
         if (ret == ESP_FAIL) {

+ 22 - 0
code/main/server_main.cpp

@@ -17,6 +17,7 @@
 
 #include "MainFlowControl.h"
 #include "esp_log.h"
+#include "esp_chip_info.h"
 
 #include <stdio.h>
 
@@ -170,6 +171,27 @@ esp_err_t info_get_handler(httpd_req_t *req)
         httpd_resp_sendstr(req, zw.c_str());
         return ESP_OK;        
     }
+    else if (_task.compare("ChipCores") == 0)
+    {
+        esp_chip_info_t chipInfo;
+        esp_chip_info(&chipInfo);
+        httpd_resp_sendstr(req, to_string(chipInfo.cores).c_str());
+        return ESP_OK;        
+    }
+    else if (_task.compare("ChipRevision") == 0)
+    {
+        esp_chip_info_t chipInfo;
+        esp_chip_info(&chipInfo);
+        httpd_resp_sendstr(req, to_string(chipInfo.revision).c_str());
+        return ESP_OK;        
+    }
+    else if (_task.compare("ChipFeatures") == 0)
+    {
+        esp_chip_info_t chipInfo;
+        esp_chip_info(&chipInfo);
+        httpd_resp_sendstr(req, to_string(chipInfo.features).c_str());
+        return ESP_OK;      
+    }
     else
     {
         char formatted[256];

+ 1 - 1
code/platformio.ini

@@ -19,7 +19,7 @@
 
 [common:esp32-idf]
     extends = common:idf
-    platform = platformio/espressif32 @ 6.3.2
+    platform = platformio/espressif32 @ 6.5.0
     framework = espidf
     lib_deps = 
         ${common:idf.lib_deps}

+ 0 - 5
code/sdkconfig.defaults

@@ -109,11 +109,6 @@ CONFIG_ESP_INT_WDT_TIMEOUT_MS=300
 
 CONFIG_HTTPD_MAX_REQ_HDR_LEN=1024
 CONFIG_HTTPD_PURGE_BUF_LEN=16
-<<<<<<< Updated upstream
-=======
-CONFIG_HTTPD_WS_SUPPORT=y
-CONFIG_LWIP_MAX_SOCKETS=12
->>>>>>> Stashed changes
 
 CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM=16
 CONFIG_ESP32_WIFI_CACHE_TX_BUFFER_NUM=16

+ 11 - 1
code/test/components/jomjol-flowcontroll/test_flow_postrocess_helper.cpp

@@ -33,7 +33,17 @@ std::string process_doFlow(UnderTestPost* _underTestPost) {
     return _underTestPost->getReadout(0);
 }
 
-
+/**
+ * @brief setup flow like it runs after recognition.
+ * 
+ * @param analog the analog recognitions as array begins with the highest ROI
+ * @param digits the digital regocnitions as array begins with the highest ROI
+ * @param digType type of the model defaults do Digital100
+ * @param checkConsistency for Digital type only. Not relvant for newer models
+ * @param extendedResolution the lowest ROI will directly used (9.7 => 9.7) if false 9.7 => 9
+ * @param decimal_shift the decimal point offset. -3 corresponds to x.yyy
+ * @return std::string the value result
+ */
 std::string process_doFlow(std::vector<float> analog, std::vector<float> digits, t_CNNType digType, 
             bool checkConsistency, bool extendedResolution, int decimal_shift) {
     // setup the classundertest

+ 2 - 0
code/test/components/jomjol-flowcontroll/test_flow_pp_negative.cpp

@@ -110,6 +110,8 @@ void testNegative_Issues() {
         setPreValue(underTestPost, preValue_extended);
         std::string result = process_doFlow(underTestPost);
         TEST_ASSERT_EQUAL_STRING("Neg. Rate - Read:  - Raw: 22017.98 - Pre: 22018.09 ", underTestPost->getReadoutError().c_str());
+        // if negativ no result any more
+
         TEST_ASSERT_EQUAL_STRING("", result.c_str());
         delete underTestPost;
 

+ 20 - 11
code/test/test_suite_flowcontroll.cpp

@@ -138,10 +138,6 @@ void task_UnityTesting(void *pvParameter)
         RUN_TEST(test_doFlowPP3);
         printf("---------------------------------------------------------------------------\n");
         RUN_TEST(test_doFlowPP4);
-        printf("---------------------------------------------------------------------------\n");
-        RUN_TEST(test_doFlowPP_rainman110);
-        printf("---------------------------------------------------------------------------\n");
-        RUN_TEST(test_doFlowPP_rainman110_transition);
     UNITY_END();
 
     while(1);
@@ -153,11 +149,24 @@ void task_UnityTesting(void *pvParameter)
  */
 extern "C" void app_main()
 {
-    initGPIO();
-    Init_NVS_SDCard();
-    esp_log_level_set("*", ESP_LOG_DEBUG);        // set all components to DEBUG level
-
-    // Create dedicated testing task (heap size can be configured - large enough to handle a lot of testing cases)
-    // ********************************************
-    xTaskCreate(&task_UnityTesting, "task_UnityTesting", 12 * 1024, NULL, tskIDLE_PRIORITY+2, NULL);
+  initGPIO();
+  Init_NVS_SDCard();
+  esp_log_level_set("*", ESP_LOG_ERROR);        // set all components to ERROR level
+
+  UNITY_BEGIN();
+    RUN_TEST(testNegative_Issues);
+   RUN_TEST(testNegative);
+   
+    RUN_TEST(test_analogToDigit_Standard);
+    RUN_TEST(test_analogToDigit_Transition);
+    RUN_TEST(test_doFlowPP);
+    RUN_TEST(test_doFlowPP1);
+    RUN_TEST(test_doFlowPP2);
+    RUN_TEST(test_doFlowPP3);
+    RUN_TEST(test_doFlowPP4);
+
+    // getReadoutRawString test
+    RUN_TEST(test_getReadoutRawString);
+  
+  UNITY_END();
 }

+ 3 - 0
param-docs/.idea/.gitignore

@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml

+ 8 - 0
param-docs/.idea/generate-param-docs.iml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="PYTHON_MODULE" version="4">
+  <component name="NewModuleRootManager">
+    <content url="file://$MODULE_DIR$" />
+    <orderEntry type="jdk" jdkName="Python 3.10" jdkType="Python SDK" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>

+ 6 - 0
param-docs/.idea/inspectionProfiles/profiles_settings.xml

@@ -0,0 +1,6 @@
+<component name="InspectionProjectProfileManager">
+  <settings>
+    <option name="USE_PROJECT_PROFILE" value="false" />
+    <version value="1.0" />
+  </settings>
+</component>

+ 4 - 0
param-docs/.idea/misc.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10" project-jdk-type="Python SDK" />
+</project>

+ 8 - 0
param-docs/.idea/modules.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/generate-param-docs.iml" filepath="$PROJECT_DIR$/.idea/generate-param-docs.iml" />
+    </modules>
+  </component>
+</project>

+ 6 - 0
param-docs/.idea/vcs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
+  </component>
+</project>

+ 17 - 0
param-docs/README.md

@@ -0,0 +1,17 @@
+# Parameter Documentation
+Each parameter which is listed in the [configfile](https://github.com/jomjol/AI-on-the-edge-device/blob/rolling/sd-card/config/config.ini) has its own description page in the folder `parameter-pages` (grouped by the config sections).
+Those pages can be edited as needed.
+
+During a Github action build, those parameter pages will be used to generate the tooltips in the web interface. And they also are used to build the [Online Documentation](https://jomjol.github.io/AI-on-the-edge-device-docs/Parameters).
+
+If you create or rename a parameter, make sure to also update its description page!
+
+## Template Generator
+The script `generate-template-param-doc-pages.py` should be run whenever a new parameter gets added to the config file.
+It then checks if there is already a page for each of the parameters.
+ - If no page exists yet, a templated page gets generated.
+ - Existing pages do not get modified.
+
+If the parameter is listed in `expert-params.txt`, an **Expert warning** will be shown.
+
+If the parameter is listed in `hidden-in-ui.txt`, a **Note**  will be shown.

+ 46 - 0
param-docs/expert-params.txt

@@ -0,0 +1,46 @@
+WaitBeforeTakingPicture
+CamFrameSize
+CamGainceiling
+CamQuality
+CamAutoSharpness
+CamSharpness
+CamSpecialEffect
+CamWbMode
+CamAwb
+CamAwbGain
+CamAec
+CamAec2
+CamAeLevel
+CamAecValue
+CamAgc
+CamAgcGain
+CamBpc
+CamWpc
+CamRawGma
+CamLenc
+CamDcw
+CamZoom
+CamZoomSize
+CamZoomOffsetX
+CamZoomOffsetY
+demo
+SearchFieldX
+SearchFieldY
+AlignmentAlgo
+CNNGoodThreshold
+PreValueAgeStartup
+ErrorMessage
+CheckDigitIncreaseConsistency
+IO0
+IO1
+IO3
+IO4
+IO12
+IO13
+AutoStart
+Hostname
+RSSIThreshold
+TimeServer
+CACert
+ClientCert
+ClientKey

+ 95 - 0
param-docs/generate-template-param-doc-pages.py

@@ -0,0 +1,95 @@
+"""
+For each parameter which can be found in the config file,
+create a markdown file with a templated content if it does not exist yet.
+The files are grouped in sub folders representing the config sections.
+"""
+
+import os
+import configparser
+import urllib.request
+
+
+configFileUrl = "https://raw.githubusercontent.com/jomjol/AI-on-the-edge-device/rolling/sd-card/config/config.ini"
+
+parameterDocsFolder = "parameter-pages"
+parameterTemplateFile = "./templates/parameter.md"
+expertParameterListFile = "./expert-params.txt"
+hiddenInUiParameterListFile = "./hidden-in-ui.txt"
+
+
+# Fetch default config file from URL
+print("Fetching %r..." % configFileUrl)
+with urllib.request.urlopen(configFileUrl) as response:
+   content = response.read().decode("utf-8")
+
+lines = str(content).split("\n")
+
+for l in range(len(lines)):
+    lines[l] = lines[l].strip() + "\n"
+    if lines[l][0] == ";":
+        lines[l] = lines[l][1:] # Remove comment
+
+content = "".join(lines)
+
+# Fetch list of expert parameters
+with open(expertParameterListFile) as f:
+    expertParameters = f.read().splitlines()
+
+# Fetch list of parameters not available through the UI
+with open(hiddenInUiParameterListFile) as f:
+    hiddenInUiParameters = f.read().splitlines()
+
+
+config = configparser.ConfigParser(allow_no_value=True)
+config.optionxform = str # Make it case-insensitive
+config.read_string(content)
+
+#shutil.rmtree(parameterDocsFolder)
+if not os.path.exists(parameterDocsFolder):
+    os.mkdir(parameterDocsFolder)
+
+with open(parameterTemplateFile, 'r') as parameterTemplateFileHandle:
+    parameterTemplate = parameterTemplateFileHandle.read()
+
+
+print("For each section/parameter, check if there is already a documentation page in the folder %r..." % (os.getcwd() + "/" + parameterDocsFolder))
+for section in config:
+    if section != "DEFAULT":
+        #print(section)
+
+        subFolder = parameterDocsFolder + "/" + section
+
+        if not os.path.exists(subFolder):
+            os.mkdir(subFolder)
+
+        for parameter in config[section]:
+            if not " " in parameter: # Ignore parameters with whitespaces in them (special format, not part of editable config)
+                value = config[section][parameter]
+                #print("  %s = %s" % (parameter, value))
+
+                if "main." in parameter:
+                    parameter = parameter.replace("main.", "NUMBER.")
+
+                """
+                For each config line, create a markdown file
+                """
+                parameterDocFile = subFolder + '/' + parameter + ".md"
+
+                if not os.path.exists(parameterDocFile): # File does not exist yet, generate template
+                    print("%r does not exit yet, generating a templated file for it" % (os.getcwd() + "/" + parameterDocFile))
+                    with open(parameterDocFile, 'w') as paramFileHandle:
+                        content = parameterTemplate
+                        content = content.replace("$NAME", parameter)
+                        content = content.replace("$DEFAULT", value)
+
+                        if parameter in expertParameters:
+                            content = content.replace("$EXPERT_PARAMETER", "!!! Warning\n    This is an **Expert Parameter**! Only change it if you understand what it does!") # Note: Needs a 4 whitespace Intent!
+                        else:
+                            content = content.replace("$EXPERT_PARAMETER", "")
+
+                        if parameter in hiddenInUiParameters:
+                            content = content.replace("$HIDDEN_IN_UI", "!!! Note\n    This parameter is not accessible through the Web Interface Configuration Page!") # Note: Needs a 4 whitespace Intent!
+                        else:
+                            content = content.replace("$HIDDEN_IN_UI", "")
+
+                        paramFileHandle.write(content)

+ 4 - 0
param-docs/hidden-in-ui.txt

@@ -0,0 +1,4 @@
+InitialRotate
+MainTopicMQTT
+AutoAdjustSummertime
+SetupMode

+ 14 - 0
param-docs/parameter-pages/Alignment/AlignmentAlgo.md

@@ -0,0 +1,14 @@
+# Parameter `AlignmentAlgo`
+Default Value: `Default`
+
+!!! Warning
+    This is an **Expert Parameter**! Only change it if you understand what it does!
+
+Algorithm used for the alignment step.
+
+Available options:
+
+- `Default`: Use only red color channel
+- `HighAccuracy`: Use all 3 color channels (3x slower)
+- `Fast`: First time use `HighAccuracy`, then only check if the image is shifted
+- `Off`: Disable alignment algorithm

+ 11 - 0
param-docs/parameter-pages/Alignment/FlipImageSize.md

@@ -0,0 +1,11 @@
+# Parameter `FlipImageSize`
+Default Value: `false`
+    
+!!! Note
+    This parameter can also be set on the Reference Image configuration page!
+
+!!! Note
+    After changing this parameter you need to update your reference image and alignment markers!
+
+This parameter can be used to rotate the viewport together with the alignment rotation:
+![](img/flipImageSize.png) 

+ 10 - 0
param-docs/parameter-pages/Alignment/InitialMirror.md

@@ -0,0 +1,10 @@
+# Parameter `InitialMirror`
+Default Value: `false`
+    
+!!! Note
+    This parameter can also be set on the Reference Image configuration page!
+
+!!! Note
+    After changing this parameter you need to update your reference image and alignment markers!
+
+Option for initially mirroring the image on the original x-axis.

+ 12 - 0
param-docs/parameter-pages/Alignment/InitialRotate.md

@@ -0,0 +1,12 @@
+# Parameter `InitialRotate`
+Default Value: `0`
+
+Unit: Degrees
+
+!!! Note
+    This parameter can also be set on the Reference Image configuration page!
+
+!!! Note
+    After changing this parameter you need to update your reference image and alignment markers!
+
+Initial rotation of image before alignment in degree (`-360` .. `+360`)

+ 14 - 0
param-docs/parameter-pages/Alignment/SearchFieldX.md

@@ -0,0 +1,14 @@
+# Parameter `SearchFieldX`
+Default Value: `20`
+
+Unit: Pixels
+
+!!! Warning
+    This is an **Expert Parameter**! Only change it if you understand what it does!
+
+X-size (width) in which the reference is searched.
+
+!!! Note
+     Since the alignment is one of the steps using a lot of computation time, 
+     the search field should be as small as possible.
+     The calculation time goes quadratic with the search field size.

+ 14 - 0
param-docs/parameter-pages/Alignment/SearchFieldY.md

@@ -0,0 +1,14 @@
+# Parameter `SearchFieldY`
+Default Value: `20`
+
+Unit: Pixels
+
+!!! Warning
+    This is an **Expert Parameter**! Only change it if you understand what it does!
+
+Y-size (height) in which the reference is searched.
+
+!!! Note
+     Since the alignment is one of the steps using a lot of computation time, 
+     the search field should be as small as possible.
+     The calculation time goes quadratic with the search field size.

+ 10 - 0
param-docs/parameter-pages/Analog/CNNGoodThreshold.md

@@ -0,0 +1,10 @@
+# Parameter `CNNGoodThreshold`
+Default Value: `0.5`
+
+!!! Warning
+    This is an **Expert Parameter**! Only change it if you understand what it does!
+
+Threshold above which the classification should be to accept the value (only meaningful for digits).
+
+!!! Warning
+    This is only supported for the `ana-class100` models!

+ 5 - 0
param-docs/parameter-pages/Analog/ExtendedResolution.md

@@ -0,0 +1,5 @@
+# Parameter `ExtendedResolution`
+
+!!! Warning
+    This parameter is unused!
+    Use [`NUMBER.ExtendedResolution`](../Parameters/#PostProcessing-NUMBER.ExtendedResolution) instead!

+ 4 - 0
param-docs/parameter-pages/Analog/Model.md

@@ -0,0 +1,4 @@
+# Parameter `Model`
+Default Value: `/config/ana-cont_*.tflite` (See [/config/config.ini](https://github.com/jomjol/AI-on-the-edge-device/blob/master/sd-card/config/config.ini))
+
+Path to CNN model file for image recognition. See [here](../Choosing-the-Model) for details. 

+ 7 - 0
param-docs/parameter-pages/Analog/ROIImagesLocation.md

@@ -0,0 +1,7 @@
+# Parameter `ROIImagesLocation`
+Default Value: `/log/analog`
+
+Location to store separated analog images on the SD-Card.
+
+!!! Warning
+    A SD-Card has limited write cycles. Since the device does not do [Wear Leveling](https://en.wikipedia.org/wiki/Wear_leveling), this can wear out your SD-Card!

+ 6 - 0
param-docs/parameter-pages/Analog/ROIImagesRetention.md

@@ -0,0 +1,6 @@
+# Parameter `ROIImagesRetention`
+Default Value: `3`
+
+Unit: Days
+
+Days to keep the separated analog images (`0` = forever).

+ 12 - 0
param-docs/parameter-pages/AutoTimer/AutoStart.md

@@ -0,0 +1,12 @@
+# Parameter `AutoStart`
+Default Value: `true`
+
+!!! Warning
+    This is an **Expert Parameter**! Only change it if you understand what it does!
+
+Automatically start the Flow (Digitization Rounds) immediately after power up.
+
+!!! Note
+    Typically this is set to `true`.
+    The main reasons to set it to `false` is when you want to trigger it manually using the
+    [REST API](../REST-API) or [MQTT-API](../MQTT-API) or for debugging.

+ 7 - 0
param-docs/parameter-pages/AutoTimer/Interval.md

@@ -0,0 +1,7 @@
+# Parameter `Interval`
+Default Value: `5`
+
+Unit: Minutes
+
+Interval in which the Flow (Digitization Round) is run.
+If a round takes longer than this interval, the next round gets postponed until the current round completes.

+ 6 - 0
param-docs/parameter-pages/DataLogging/DataFilesRetention.md

@@ -0,0 +1,6 @@
+# Parameter `DataFilesRetention`
+Default Value: `3`
+
+Unit: Days
+
+Number of days to keep the data files (`0` = forever).

+ 8 - 0
param-docs/parameter-pages/DataLogging/DataLogActive.md

@@ -0,0 +1,8 @@
+# Parameter `DataLogActive`
+Default Value: `true`
+Activate data logging to the SD-Card.
+
+The files will be stored in `/log/data/data_YYYY-MM-DD.csv`. See [`Data Logging`](../data-logging) for details.
+
+!!! Warning
+    A SD-Card has limited write cycles. Since the device does not do [Wear Leveling](https://en.wikipedia.org/wiki/Wear_leveling), this can wear out your SD-Card!

+ 16 - 0
param-docs/parameter-pages/Debug/LogLevel.md

@@ -0,0 +1,16 @@
+# Parameter `LogLevel`
+Default Value: `1` (`ERROR`)
+Define the log level for the logging to the SD-Card.
+
+Available options:
+
+- `1`: `ERROR`
+- `2`: `WARNING`
+- `3`: `INFO`
+- `4`: `DEBUG`
+
+As higher the level, as more log messages get written to the SD-Card.
+
+!!! Warning
+    `DEBUG` or `INFO` might damage the SD-Card if enabled long term due to excessive writes to the SD-Card!
+    A SD-Card has limited write cycles. Since the device does not do [Wear Leveling](https://en.wikipedia.org/wiki/Wear_leveling), this can wear out your SD-Card!

+ 6 - 0
param-docs/parameter-pages/Debug/LogfilesRetention.md

@@ -0,0 +1,6 @@
+# Parameter `LogfilesRetention`
+Default Value: `3`
+
+Unit: Days
+
+Number of days to keep the log files (`0` = forever).

+ 10 - 0
param-docs/parameter-pages/Digits/CNNGoodThreshold.md

@@ -0,0 +1,10 @@
+# Parameter `CNNGoodThreshold`
+Default Value: `0.5`
+
+!!! Warning
+    This is an **Expert Parameter**! Only change it if you understand what it does!
+
+Threshold above which the classification should be to accept the value (only meaningful for digits).
+
+!!! Warning
+    This is only supported for the `dig-class100` models!

+ 4 - 0
param-docs/parameter-pages/Digits/Model.md

@@ -0,0 +1,4 @@
+# Parameter `Model`
+Default Value: `/config/dig-cont_*.tflite` (See [/config/config.ini](https://github.com/jomjol/AI-on-the-edge-device/blob/master/sd-card/config/config.ini))
+
+Path to CNN model file for image recognition. See [here](../Choosing-the-Model) for details. 

+ 7 - 0
param-docs/parameter-pages/Digits/ROIImagesLocation.md

@@ -0,0 +1,7 @@
+# Parameter `ROIImagesLocation`
+Default Value: `/log/digit`
+
+Location to store separated digit images on the SD-Card.
+
+!!! Warning
+    A SD-Card has limited write cycles. Since the device does not do [Wear Leveling](https://en.wikipedia.org/wiki/Wear_leveling), this can wear out your SD-Card!

+ 6 - 0
param-docs/parameter-pages/Digits/ROIImagesRetention.md

@@ -0,0 +1,6 @@
+# Parameter `ROIImagesRetention`
+Default Value: `3`
+
+Unit: Days
+
+Days to keep the separated digit images (`0` = forever).

+ 21 - 0
param-docs/parameter-pages/GPIO/IO0.md

@@ -0,0 +1,21 @@
+# Parameter `IO0`
+Default Value: `input disabled 10 false false`
+
+!!! Warning
+    This is an **Expert Parameter**! Only change it if you understand what it does!
+
+This parameter can be used to configure the GPIO `IO0` pin.
+
+!!! Warning
+    This pin is only usable with restrictions!
+    It must be disabled when the camera is used.
+    Additionally, it is used to activate Bootloader mode and must therefore be HIGH after a reset!
+
+Parameters:
+
+- `GPIO 0 state`: One of `input`, `input pullup`, `input pulldown` or `output`.
+- `GPIO 0 use interrupt`: Enable interrupt trigger
+- `GPIO 0 PWM duty resolution`: LEDC PWM duty resolution in bit
+- `GPIO 0 enable MQTT`: Enable MQTT publishing/subscribing
+- `GPIO 0 enable HTTP`: Enable HTTP write/read
+- `GPIO 0 name`: MQTT topic name (empty = `GPIO0`). Allowed characters: `a-z, A-Z, 0-9, _, -`.

+ 19 - 0
param-docs/parameter-pages/GPIO/IO1.md

@@ -0,0 +1,19 @@
+# Parameter `IO1`
+Default Value: `input disabled 10 false false`
+
+!!! Warning
+    This is an **Expert Parameter**! Only change it if you understand what it does!
+
+This parameter can be used to configure the GPIO `IO1` pin.
+
+!!! Warning
+    This pin is by default used for the serial communication as TX pin (USB logging)!
+
+Parameters:
+
+- `GPIO 1 state`: One of `input`, `input pullup`, `input pulldown` or `output`.
+- `GPIO 1 use interrupt`: Enable interrupt trigger
+- `GPIO 1 PWM duty resolution`: LEDC PWM duty resolution in bit
+- `GPIO 1 enable MQTT`: Enable MQTT publishing/subscribing
+- `GPIO 1 enable HTTP`: Enable HTTP write/read
+- `GPIO 1 name`: MQTT topic name (empty = `GPIO1`). Allowed characters: `a-z, A-Z, 0-9, _, -`.

+ 19 - 0
param-docs/parameter-pages/GPIO/IO12.md

@@ -0,0 +1,19 @@
+# Parameter `IO12`
+Default Value: `input-pullup disabled 10 false false`
+
+!!! Warning
+    This is an **Expert Parameter**! Only change it if you understand what it does!
+
+This parameter can be used to configure the GPIO `IO12` pin.
+
+!!! Note
+    This pin is usable without known restrictions!
+
+Parameters:
+
+- `GPIO 12 state`: One of `external-flash-ws281x`, `input`, `input pullup`, `input pulldown` or `output`.
+- `GPIO 12 use interrupt`: Enable interrupt trigger
+- `GPIO 12 PWM duty resolution`: LEDC PWM duty resolution in bit
+- `GPIO 12 enable MQTT`: Enable MQTT publishing/subscribing
+- `GPIO 12 enable HTTP`: Enable HTTP write/read
+- `GPIO 12 name`: MQTT topic name (empty = `GPIO12`). Allowed characters: `a-z, A-Z, 0-9, _, -`.

+ 19 - 0
param-docs/parameter-pages/GPIO/IO13.md

@@ -0,0 +1,19 @@
+# Parameter `IO13`
+Default Value: `input-pullup disabled 10 false false`
+
+!!! Warning
+    This is an **Expert Parameter**! Only change it if you understand what it does!
+
+This parameter can be used to configure the GPIO `IO13` pin.
+
+!!! Note
+    This pin is usable without known restrictions!
+
+Parameters:
+
+- `GPIO 13 state`: One of `input`, `input pullup`, `input pulldown` or `output`.
+- `GPIO 13 use interrupt`: Enable interrupt trigger
+- `GPIO 13 PWM duty resolution`: LEDC PWM duty resolution in bit
+- `GPIO 13 enable MQTT`: Enable MQTT publishing/subscribing
+- `GPIO 13 enable HTTP`: Enable HTTP write/read
+- `GPIO 13 name`: MQTT topic name (empty = `GPIO13`). Allowed characters: `a-z, A-Z, 0-9, _, -`.

+ 19 - 0
param-docs/parameter-pages/GPIO/IO3.md

@@ -0,0 +1,19 @@
+# Parameter `IO3`
+Default Value: `input disabled 10 false false`
+
+!!! Warning
+    This is an **Expert Parameter**! Only change it if you understand what it does!
+
+This parameter can be used to configure the GPIO `IO3` pin.
+
+!!! Warning
+    This pin is by default used for the serial communication as RX pin (USB logging)!
+
+Parameters:
+
+- `GPIO 3 state`: One of `input`, `input pullup`, `input pulldown` or `output`.
+- `GPIO 3 use interrupt`: Enable interrupt trigger
+- `GPIO 3 PWM duty resolution`: LEDC PWM duty resolution in bit
+- `GPIO 3 enable MQTT`: Enable MQTT publishing/subscribing
+- `GPIO 3 enable HTTP`: Enable HTTP write/read
+- `GPIO 3 name`: MQTT topic name (empty = `GPIO3`). Allowed characters: `a-z, A-Z, 0-9, _, -`.

+ 20 - 0
param-docs/parameter-pages/GPIO/IO4.md

@@ -0,0 +1,20 @@
+# Parameter `IO4`
+Default Value: `built-in-led disabled 10 false false`
+
+!!! Warning
+    This is an **Expert Parameter**! Only change it if you understand what it does!
+
+This parameter can be used to configure the GPIO `IO4` pin.
+
+!!! Warning
+    This pin is only usable with restrictions!
+    By default, it is used for build-in flash light (onboard LED).
+
+Parameters:
+
+- `GPIO 4 state`: One of `built-in-led`, `input`, `input pullup`, `input pulldown` or `output`.
+- `GPIO 4 use interrupt`: Enable interrupt trigger
+- `GPIO 4 PWM duty resolution`: LEDC PWM duty resolution in bit
+- `GPIO 4 enable MQTT`: Enable MQTT publishing/subscribing
+- `GPIO 4 enable HTTP`: Enable HTTP write/read
+- `GPIO 4 name`: MQTT topic name (empty = `GPIO4`). Allowed characters: `a-z, A-Z, 0-9, _, -`.

+ 5 - 0
param-docs/parameter-pages/GPIO/LEDColor.md

@@ -0,0 +1,5 @@
+# Parameter `LEDColor`
+Default Value: `150 150 150`
+
+Color of the attached LEDs to GPIO12 in **R**ed, **G**reen **B**lue from `0` (full off) .. `255` (full on) 
+(See `IO12` parameter).

+ 4 - 0
param-docs/parameter-pages/GPIO/LEDNumbers.md

@@ -0,0 +1,4 @@
+# Parameter `LEDNumbers`
+Default Value: `2`
+
+Number of LEDs on the external LED-stripe attached to GPIO12 (See `IO12` parameter).

+ 3 - 0
param-docs/parameter-pages/GPIO/LEDType.md

@@ -0,0 +1,3 @@
+# Parameter `LEDType`
+Default Value: `WS2812`
+Type of the `WS2812x` which is connected to GPIO12 (See `IO12` parameter).

+ 8 - 0
param-docs/parameter-pages/GPIO/MainTopicMQTT.md

@@ -0,0 +1,8 @@
+# Parameter `MainTopicMQTT`
+Default Value: `wasserzaehler/GPIO`
+
+!!! Note
+    This parameter is not accessible through the Web Interface Configuration Page!
+
+The GPIO Interface is prepared to report it's status and status changes as a MQTT topic. With this parameter you configure the MQTT main topic, under which the status is published.
+As this parameter is still experimental it can only be set manually in the `config.ini` itself and has not been tested in detail so far.

+ 7 - 0
param-docs/parameter-pages/InfluxDB/Database.md

@@ -0,0 +1,7 @@
+# Parameter `Database`
+Default Value: `''`
+
+Name of the InfluxDB v1 Database into which to publish the values.
+
+!!! Note
+    See section `InfluxDBv2` for InfluxDB v2 support! 

+ 4 - 0
param-docs/parameter-pages/InfluxDB/NUMBER.Field.md

@@ -0,0 +1,4 @@
+# Parameter `<NUMBER>.Field`
+Default Value: `undefined`
+
+Dedicated definition of the field for InfluxDB use for saving in the Influx database (e.g.: "watermeter/value").

+ 7 - 0
param-docs/parameter-pages/InfluxDB/NUMBER.Measurement.md

@@ -0,0 +1,7 @@
+# Parameter `Measurement`
+Default Value: `undefined`
+
+Name of the InfluxDB v1 Measurement to use to publish the value.
+
+!!! Note
+    See section `InfluxDBv2` for InfluxDB v2 support! 

+ 7 - 0
param-docs/parameter-pages/InfluxDB/Uri.md

@@ -0,0 +1,7 @@
+# Parameter `Uri`
+Default Value: `undefined`
+
+URI of the HTTP interface to InfluxDB v1, without trailing slash, e.g. `http://192.168.1.1:8086`.
+
+!!! Note
+    See section `InfluxDBv2` for InfluxDB v2 support! 

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio