mirror of
https://github.com/jomjol/AI-on-the-edge-device.git
synced 2025-12-08 04:26:58 +03:00
Compare commits
83 Commits
v16.0.0-RC
...
16.0.0-RC7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf96d49bd0 | ||
|
|
94a53b38b8 | ||
|
|
00ac2130c2 | ||
|
|
cd1165e547 | ||
|
|
c587ca3224 | ||
|
|
76f45a5927 | ||
|
|
063c4827d0 | ||
|
|
c6b8823417 | ||
|
|
bb5e693077 | ||
|
|
424df641cc | ||
|
|
8ddbda16bf | ||
|
|
8f5bf209d9 | ||
|
|
ec639d4236 | ||
|
|
5304981733 | ||
|
|
7a9955477a | ||
|
|
04fe25e875 | ||
|
|
02dcd584bf | ||
|
|
68a262ef22 | ||
|
|
f2b6b4f819 | ||
|
|
6b672ff9a5 | ||
|
|
26770d877e | ||
|
|
e60c12b25d | ||
|
|
b10336b59c | ||
|
|
dadb004e85 | ||
|
|
e2f3e3d05b | ||
|
|
8c5a6528d9 | ||
|
|
69024adac7 | ||
|
|
9f1b7c9ef7 | ||
|
|
15fd7a6a33 | ||
|
|
9c3fbb4aff | ||
|
|
eb4c9276d5 | ||
|
|
842229ea98 | ||
|
|
b83717aece | ||
|
|
b01e42b893 | ||
|
|
902f1bc2a8 | ||
|
|
5c51990b40 | ||
|
|
55cbbf02cb | ||
|
|
e37817e3e2 | ||
|
|
a78ca53d60 | ||
|
|
20a0d8530a | ||
|
|
af1e3257be | ||
|
|
93c21d30a1 | ||
|
|
7111a7fdcd | ||
|
|
bd541ede60 | ||
|
|
52aab2f8a6 | ||
|
|
a1aee5b346 | ||
|
|
71f31dc841 | ||
|
|
f6a363a871 | ||
|
|
7de18753d9 | ||
|
|
fea0c1b859 | ||
|
|
603fcfef33 | ||
|
|
be3312e912 | ||
|
|
ff657ebb5c | ||
|
|
42d4916cb8 | ||
|
|
a73cd97629 | ||
|
|
636d816727 | ||
|
|
c6f6341c87 | ||
|
|
ad886898a8 | ||
|
|
f16cf1406c | ||
|
|
5dfd9a7db0 | ||
|
|
89dc941d40 | ||
|
|
3f3c845e9f | ||
|
|
1e60d839b7 | ||
|
|
6e474441f6 | ||
|
|
bf2a2f553f | ||
|
|
21ab5cb203 | ||
|
|
1af1796ee0 | ||
|
|
095cf984c4 | ||
|
|
b72d809e59 | ||
|
|
c6a593ba0d | ||
|
|
c41a0a476d | ||
|
|
c68463359b | ||
|
|
a1c2145e77 | ||
|
|
2986c6122d | ||
|
|
a348a51f14 | ||
|
|
6da894142e | ||
|
|
4846a52d45 | ||
|
|
4d74d0c522 | ||
|
|
53e818186a | ||
|
|
26ca15e18a | ||
|
|
f4dccc1d52 | ||
|
|
a07c2cf46d | ||
|
|
2f39d9bdd4 |
20
.github/workflows/build.yaml
vendored
20
.github/workflows/build.yaml
vendored
@@ -86,7 +86,16 @@ jobs:
|
||||
cp -r ./sd-card/html/* ./html/
|
||||
|
||||
echo "Replacing variables..."
|
||||
cd html; find . -type f -exec sed -i 's/$COMMIT_HASH/${{ steps.vars.outputs.sha_short }}/g' {} \;
|
||||
cd html
|
||||
find . -type f -exec sed -i 's/$COMMIT_HASH/${{ steps.vars.outputs.sha_short }}/g' {} \;
|
||||
echo "compressing all html files..."
|
||||
find . -name "*.html" -type f -exec gzip {} \;
|
||||
find . -name "*.css" -type f -exec gzip {} \;
|
||||
find . -name "*.js" -type f -exec gzip {} \;
|
||||
find . -name "*.jpg" -type f -exec gzip {} \;
|
||||
find . -name "*.png" -type f -exec gzip {} \;
|
||||
find . -name "*.svg" -type f -exec gzip {} \;
|
||||
find . -name "*.map" -type f -exec gzip {} \;
|
||||
|
||||
- name: Prepare Demo mode files
|
||||
run: |
|
||||
@@ -414,11 +423,12 @@ jobs:
|
||||
|
||||
- name: Get version of last release
|
||||
id: last_release
|
||||
uses: mindojo/get-latest-release@0b8ef1434d7468d6bffcc8263baff5c777f72321
|
||||
uses: joutvhu/get-release@v1
|
||||
with:
|
||||
myToken: ${{ github.token }}
|
||||
exclude_types: "draft|prerelease"
|
||||
view_top: 1
|
||||
latest: true
|
||||
prerelease: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Add binary to Web Installer and update manifest
|
||||
run: |
|
||||
|
||||
@@ -29,14 +29,15 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
|
||||
- name: Get version of last release
|
||||
id: last_release
|
||||
uses: mindojo/get-latest-release@0b8ef1434d7468d6bffcc8263baff5c777f72321
|
||||
uses: joutvhu/get-release@v1
|
||||
with:
|
||||
myToken: ${{ github.token }}
|
||||
exclude_types: "draft|prerelease"
|
||||
view_top: 1
|
||||
latest: true
|
||||
prerelease: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Add binary to Web Installer and update manifest
|
||||
run: |
|
||||
@@ -50,13 +51,13 @@ jobs:
|
||||
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@v5
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v1
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: 'docs'
|
||||
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v1
|
||||
uses: actions/deploy-pages@v4
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -10,3 +10,6 @@
|
||||
[submodule "code/components/stb"]
|
||||
path = code/components/stb
|
||||
url = https://github.com/nothings/stb.git
|
||||
[submodule "code/components/esp-protocols"]
|
||||
path = code/components/esp-protocols
|
||||
url = https://github.com/espressif/esp-protocols.git
|
||||
|
||||
114
Changelog.md
114
Changelog.md
@@ -1,6 +1,52 @@
|
||||
## [16.0.0-RC7] - 2024-03-01
|
||||
|
||||
For a full list of changes see [Full list of changes](https://github.com/jomjol/AI-on-the-edge-device/compare/v15.7.0...v16.0.0-RC7)
|
||||
|
||||
#### Known issues
|
||||
Please check the [issues](https://github.com/jomjol/AI-on-the-edge-device/issues) and
|
||||
[discussions](https://github.com/jomjol/AI-on-the-edge-device/discussions) before reporting a new issue.
|
||||
|
||||
|
||||
#### Homeassistant compatibility
|
||||
:warning: Please check your Homeassistant instance to make sure it is handled correctly!
|
||||
|
||||
#### Core Changes
|
||||
Only changes since RC5 are listed (Skipping RC6):
|
||||
- [#3436](https://github.com/jomjol/AI-on-the-edge-device/pull/3436) Added basic authentification of the Web Interface and the REST API, see https://jomjol.github.io/AI-on-the-edge-device-docs/Password-Protection
|
||||
|
||||
- [#3454](https://github.com/jomjol/AI-on-the-edge-device/pull/3454) Add thermometer
|
||||
|
||||
- [#3521](https://github.com/jomjol/AI-on-the-edge-device/pull/3521) openmetrics endpoint extension
|
||||
|
||||
- [#3537](https://github.com/jomjol/AI-on-the-edge-device/pull/3537) Added data export (CSV files) functionality
|
||||
|
||||
- [#3499](https://github.com/jomjol/AI-on-the-edge-device/pull/3499) Added and activated espressif mDNS service
|
||||
|
||||
- [#3547](https://github.com/jomjol/AI-on-the-edge-device/pull/3547) Enhanced `IgnoreLeadingNaN`. Marked it in the UI as a per-sequence parameter
|
||||
|
||||
- [#3580](https://github.com/jomjol/AI-on-the-edge-device/pull/3580) Update Homeassistant discovery to comply with Homeassistant `2025.2.4`
|
||||
|
||||
- [#3538](https://github.com/jomjol/AI-on-the-edge-device/pull/3538), [#3568](https://github.com/jomjol/AI-on-the-edge-device/pull/3568) Compress all Web UI files with gzip
|
||||
|
||||
- [#3590](https://github.com/jomjol/AI-on-the-edge-device/pull/3590) Validate uploaded OTA update file before installing it
|
||||
|
||||
- [#3583](https://github.com/jomjol/AI-on-the-edge-device/pull/3583) Remove HTML directory on update
|
||||
|
||||
- [#3590](https://github.com/jomjol/AI-on-the-edge-device/pull/3590) Consolidated the `reboot` and `save` buttons -> The `reboot` button ois not part of the notification
|
||||
corrected enable/disable texts on the configuration page to make it more intuitive
|
||||
|
||||
- [#3520](https://github.com/jomjol/AI-on-the-edge-device/pull/3520) Rewrite InfluxDB Interface
|
||||
|
||||
- [#3500](https://github.com/jomjol/AI-on-the-edge-device/pull/3500) Update smart-LED driver
|
||||
|
||||
#### Bug Fixes
|
||||
Only changes since RC5 are listed:
|
||||
- [#3450](https://github.com/jomjol/AI-on-the-edge-device/pull/3450) fix crash due to empty CAM parameters in migration
|
||||
- [#3446](https://github.com/jomjol/AI-on-the-edge-device/pull/3446) fix for incorrect decimal shift
|
||||
|
||||
## [16.0.0-RC5] - 2024-12-05
|
||||
|
||||
For a full list of changes see [Full list of changes](https://github.com/jomjol/AI-on-the-edge-device/compare/v15.7.0...v16.0.0)
|
||||
For a full list of changes see [Full list of changes](https://github.com/jomjol/AI-on-the-edge-device/compare/v15.7.0...v16.0.0-RC5)
|
||||
|
||||
#### Known issues
|
||||
Please check the [issues](https://github.com/jomjol/AI-on-the-edge-device/issues) and
|
||||
@@ -8,30 +54,30 @@ Please check the [issues](https://github.com/jomjol/AI-on-the-edge-device/issues
|
||||
|
||||
#### Core Changes
|
||||
Only changes since RC4 are listed:
|
||||
- Removed `Autostart` parameter and make the flow to be always enabled (#3423)
|
||||
- Enable `Flow start` menu entry in UI (#3423)
|
||||
- Updated the Homeassistant Discovery topics (#3332):
|
||||
- [#3423](https://github.com/jomjol/AI-on-the-edge-device/pull/3423) Removed `Autostart` parameter and make the flow to be always enabled
|
||||
- [#3423](https://github.com/jomjol/AI-on-the-edge-device/pull/3423) Enable `Flow start` menu entry in UI
|
||||
- [#3332](https://github.com/jomjol/AI-on-the-edge-device/pull/3332) Updated the Homeassistant Discovery topics :
|
||||
- `raw` has now set the `State Class` to `measurement`. Before it was always set to `""`.
|
||||
- `value` has now only set the `State Class` to `total_increasing` if the parameter `Allow Negative Rates` is **not** set. Else it uses `measurement` since the rate could also be negative. Before it was always set to `total_increasing`.
|
||||
- The `rate_per_time_unit` topic of an **Energy** meter needs a `Device Class`=`power`. For `gas` and `water` it should be `volume_flow_rate`. Before it was always set to `""`.
|
||||
- Added button for `flow start` (#3415)
|
||||
- Added support for Domoticz MQTT integration (#3359)
|
||||
- [#3415](https://github.com/jomjol/AI-on-the-edge-device/pull/3415) Added button for `flow start`
|
||||
- [#3359](https://github.com/jomjol/AI-on-the-edge-device/pull/3359) Added support for Domoticz MQTT integration
|
||||
- Added Date and time to overview page
|
||||
- Updated submodules and models
|
||||
|
||||
**:warning: Please check your Homeassistant instance to make sure it is handled correctly!**
|
||||
|
||||
#### Bug Fixes
|
||||
Only changes since RC3 are listed:
|
||||
- Added fix for ledintensity (#3418)
|
||||
- Added fix for OV2640 brightness contrast saturation (#3417)
|
||||
- Added fix for 'AnalogToDigitTransitionStart' always using 9.2 regardless of the configured value (#3393)
|
||||
- Addef fix for HA menu entry (#3342)
|
||||
Only changes since RC4 are listed:
|
||||
- [#3418](https://github.com/jomjol/AI-on-the-edge-device/pull/3418) Added fix for ledintensity
|
||||
- [#3417](https://github.com/jomjol/AI-on-the-edge-device/pull/3417) Added fix for OV2640 brightness contrast saturation
|
||||
- [#3393](https://github.com/jomjol/AI-on-the-edge-device/pull/3393) Added fix for 'AnalogToDigitTransitionStart' always using 9.2 regardless of the configured value
|
||||
- [#3342](https://github.com/jomjol/AI-on-the-edge-device/pull/3342) Added fix for HA menu entry
|
||||
|
||||
|
||||
## [16.0.0-RC4] - 2024-10-06
|
||||
|
||||
For a full list of changes see [Full list of changes](https://github.com/jomjol/AI-on-the-edge-device/compare/v15.7.0...v16.0.0-RC1)
|
||||
For a full list of changes see [Full list of changes](https://github.com/jomjol/AI-on-the-edge-device/compare/v15.7.0...v16.0.0-RC4)
|
||||
|
||||
#### Known issues
|
||||
Please check the [issues](https://github.com/jomjol/AI-on-the-edge-device/issues) and
|
||||
@@ -39,18 +85,18 @@ Please check the [issues](https://github.com/jomjol/AI-on-the-edge-device/issues
|
||||
|
||||
#### Core Changes
|
||||
Only changes since RC3 are listed:
|
||||
- Update esp32-camera submodule to `v2.0.13` (#3316)
|
||||
- Added contributor list (#3317)
|
||||
- Added files for demo mode (#3315)
|
||||
- [#3316](https://github.com/jomjol/AI-on-the-edge-device/pull/3316) Update esp32-camera submodule to `v2.0.13`
|
||||
- [#3317](https://github.com/jomjol/AI-on-the-edge-device/pull/3317) Added contributor list
|
||||
- [#3315](https://github.com/jomjol/AI-on-the-edge-device/pull/3315) Added files for demo mode
|
||||
|
||||
#### Bug Fixes
|
||||
Only changes since RC2 are listed:
|
||||
- Added delay in InitCam (#3313) to fix `Camera not detected` issues
|
||||
- [#3313](https://github.com/jomjol/AI-on-the-edge-device/pull/3313) Added delay in InitCam to fix `Camera not detected` issues
|
||||
|
||||
|
||||
## [16.0.0-RC3] - 2024-10-05
|
||||
|
||||
For a full list of changes see [Full list of changes](https://github.com/jomjol/AI-on-the-edge-device/compare/v15.7.0...v16.0.0-RC1)
|
||||
For a full list of changes see [Full list of changes](https://github.com/jomjol/AI-on-the-edge-device/compare/v15.7.0...v16.0.0-RC3)
|
||||
|
||||
#### Known issues
|
||||
Please check the [issues](https://github.com/jomjol/AI-on-the-edge-device/issues) and
|
||||
@@ -62,12 +108,12 @@ Only changes since RC2 are listed:
|
||||
|
||||
#### Bug Fixes
|
||||
Only changes since RC2 are listed:
|
||||
- Re-did revertion of TFlite submodule update as certain modules crash with it (#3269) (change was lost)
|
||||
- [#3269](https://github.com/jomjol/AI-on-the-edge-device/pull/3269) Re-did revertion of TFlite submodule update as certain modules crash with it (change was lost)
|
||||
|
||||
|
||||
## [16.0.0-RC2] - 2024-10-04
|
||||
|
||||
For a full list of changes see [Full list of changes](https://github.com/jomjol/AI-on-the-edge-device/compare/v15.7.0...v16.0.0-RC1)
|
||||
For a full list of changes see [Full list of changes](https://github.com/jomjol/AI-on-the-edge-device/compare/v15.7.0...v16.0.0-RC2)
|
||||
|
||||
#### Known issues
|
||||
Please check the [issues](https://github.com/jomjol/AI-on-the-edge-device/issues) and
|
||||
@@ -76,13 +122,13 @@ Please check the [issues](https://github.com/jomjol/AI-on-the-edge-device/issues
|
||||
#### Core Changes
|
||||
Only changes since RC1 are listed:
|
||||
- Updated parameter documentation pages
|
||||
- Rename/remove unused parameters (#3291)
|
||||
- Migrate-cam-parameters (#3288)
|
||||
- [#3291](https://github.com/jomjol/AI-on-the-edge-device/pull/3291) Rename/remove unused parameters
|
||||
- [#3288](https://github.com/jomjol/AI-on-the-edge-device/pull/3288) Migrate-cam-parameters
|
||||
|
||||
#### Bug Fixes
|
||||
Only changes since RC1 are listed:
|
||||
- Reverted TFlite submodule update as certain modules crash with it (#3269)
|
||||
- Changed the webhook UploadImg to false (#3279)
|
||||
- [#3269](https://github.com/jomjol/AI-on-the-edge-device/pull/3269) Reverted TFlite submodule update as certain modules crash with it
|
||||
- [#3279](https://github.com/jomjol/AI-on-the-edge-device/pull/3279) Changed the webhook UploadImg to false
|
||||
- Changed default value from boolean to numeric value in parameter camDenoise documentation
|
||||
- Updated config page
|
||||
|
||||
@@ -96,24 +142,24 @@ Please check the [issues](https://github.com/jomjol/AI-on-the-edge-device/issues
|
||||
|
||||
#### Core Changes
|
||||
Those are just the major changes:
|
||||
- Add support for OV5640 camera (#3063)
|
||||
- [#3063](https://github.com/jomjol/AI-on-the-edge-device/pull/3063) Add support for OV5640 camera
|
||||
- New tflite-Models
|
||||
- Homeassistant service discovery: derive node_id when using nested topics (#3088)
|
||||
- Added Prometheus/OpenMetrics exporter (#3081)
|
||||
- Added Webhook (#3148, #3163, #3174)
|
||||
- Add rate threshold parameter (#3195)
|
||||
- Added a Delay between the WiFi reconnections (#3068)
|
||||
- [#3088](https://github.com/jomjol/AI-on-the-edge-device/pull/3088) Homeassistant service discovery: derive node_id when using nested topics
|
||||
- [#3081](https://github.com/jomjol/AI-on-the-edge-device/pull/3081) Added Prometheus/OpenMetrics exporter
|
||||
- [#3148](https://github.com/jomjol/AI-on-the-edge-device/pull/3148), [#3163](https://github.com/jomjol/AI-on-the-edge-device/pull/3163), [#3174](https://github.com/jomjol/AI-on-the-edge-device/pull/3148), [#3163](https://github.com/jomjol/AI-on-the-edge-device/pull/3163), [#3174](https://github.com/jomjol/AI-on-the-edge-device/pull/3174) Added Webhook
|
||||
- [#3195](https://github.com/jomjol/AI-on-the-edge-device/pull/3195) Add rate threshold parameter
|
||||
- [#3068](https://github.com/jomjol/AI-on-the-edge-device/pull/3068) Added a Delay between the WiFi reconnections
|
||||
- Web UI improvements
|
||||
- Various minor changes
|
||||
- Update platformIO to 6.9.0 (Contains ESP IDF 5.3.1)
|
||||
|
||||
#### Bug Fixes
|
||||
Those are just the major changes:
|
||||
- Handle crash on corrupted model (#3220)
|
||||
- Bugfix for boot loop (#3175)
|
||||
- Bugfix for time stamp (#3180)
|
||||
- Handle empty prevalue.ini gracefully (#3162)
|
||||
- Added note about only TLS 1.2 is supported (#3213)
|
||||
- [#3220](https://github.com/jomjol/AI-on-the-edge-device/pull/3220) Handle crash on corrupted model
|
||||
- [#3175](https://github.com/jomjol/AI-on-the-edge-device/pull/3175) Bugfix for boot loop
|
||||
- [#3180](https://github.com/jomjol/AI-on-the-edge-device/pull/3180) Bugfix for time stamp
|
||||
- [#3162](https://github.com/jomjol/AI-on-the-edge-device/pull/3162) Handle empty prevalue.ini gracefully
|
||||
- [#3213](https://github.com/jomjol/AI-on-the-edge-device/pull/3213) Added note about only TLS 1.2 is supported
|
||||
|
||||
## [15.7.0] - 2024-02-17
|
||||
|
||||
|
||||
68
Licence.md
Normal file
68
Licence.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# **Dual Use License for AI-on-the-Edge Device**
|
||||
|
||||
Version: 1.0
|
||||
Date: 2025-01-05 (5th January 2025)
|
||||
|
||||
## **Preamble**
|
||||
|
||||
This license allows individuals to use, modify, and share AI-on-the-Edge freely for private, non-commercial purposes. Any commercial use requires a separate licensing agreement with the rights holder.
|
||||
|
||||
------
|
||||
|
||||
## **1. Grant of License**
|
||||
|
||||
### 1.1 **Private Use**
|
||||
|
||||
The licensor grants the licensee a free, non-exclusive, worldwide license to use, modify, and distribute the software for private, non-commercial purposes.
|
||||
|
||||
### 1.2 **Commercial Use**
|
||||
|
||||
The use of the software or any derivative works in any commercial context (including, but not limited to, selling, renting, providing as a service, or integrating into commercial products) is prohibited without a separate commercial license.
|
||||
|
||||
------
|
||||
|
||||
## **2. Obligation to Private Derivatives**
|
||||
|
||||
In modified private versions of the software, the unchanged license as well as the reference to the original source and authors must always be stated (https://github.com/jomjol/AI-on-the-edge-device).
|
||||
|
||||
Modified versions of the software must be clearly marked as such and must not imply they are provided by the original licensor.
|
||||
|
||||
------
|
||||
|
||||
## **3. Commercial Licensing**
|
||||
|
||||
Companies, organizations, or individuals wishing to use the software for commercial purposes must obtain a separate commercial license. Please contact mueller.josef(@)gmail.com for further details.
|
||||
|
||||
------
|
||||
|
||||
## **4. Terms of Cooperation**
|
||||
|
||||
By contributing to the AI-on-the-Edge software, this license is considered accepted. This applies to, but is not limited to, code, error corrections, extensions, artwork, documentation, and new features. Any contribution, including libraries and sources, must comply with the terms of this license.
|
||||
|
||||
The contributor agrees that the added code and functionality may also be used in commercial versions without compensation to the contributor.
|
||||
|
||||
------
|
||||
|
||||
## **5. Disclaimer of Liability**
|
||||
|
||||
### 5.1 **General Disclaimer**
|
||||
|
||||
The software is provided "as is", without any express or implied warranties. The licensor is not liable for any damages resulting from the use of the software.
|
||||
|
||||
### 5.2 **No Usage in Safety or Security Environments**
|
||||
|
||||
The image processing uses neural networks, among other algorithms, whose results can produce incorrect or unexpected outcomes due to their functionality and the underlying training data. Therefore, this system must not be used or offered for safety-relevant systems or systems with high reliability requirements.
|
||||
|
||||
------
|
||||
|
||||
## **6. General Provisions**
|
||||
|
||||
### 6.1 **Severability Clause**
|
||||
|
||||
If any provision of this license is deemed invalid, the remaining provisions shall remain in full force and effect.
|
||||
|
||||
------
|
||||
|
||||
## **Acceptance**
|
||||
|
||||
By using this software, the licensee agrees to the terms of this license.
|
||||
82
README.md
82
README.md
@@ -250,13 +250,6 @@ There are some ideas and feature requests which are not currently being pursued
|
||||
<sub><b>Frank Haverland</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/Slider0007">
|
||||
<img src="https://avatars.githubusercontent.com/u/115730895?v=4" width="100;" alt="Slider0007"/>
|
||||
<br />
|
||||
<sub><b>Slider0007</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/SybexX">
|
||||
<img src="https://avatars.githubusercontent.com/u/587201?v=4" width="100;" alt="SybexX"/>
|
||||
@@ -264,6 +257,13 @@ There are some ideas and feature requests which are not currently being pursued
|
||||
<sub><b>michael</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/Slider0007">
|
||||
<img src="https://avatars.githubusercontent.com/u/115730895?v=4" width="100;" alt="Slider0007"/>
|
||||
<br />
|
||||
<sub><b>Slider0007</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/nliaudat">
|
||||
<img src="https://avatars.githubusercontent.com/u/6782613?v=4" width="100;" alt="nliaudat"/>
|
||||
@@ -368,6 +368,13 @@ There are some ideas and feature requests which are not currently being pursued
|
||||
<sub><b>parhedberg</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/fsck-block">
|
||||
<img src="https://avatars.githubusercontent.com/u/58307481?v=4" width="100;" alt="fsck-block"/>
|
||||
<br />
|
||||
<sub><b>fsck-block</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/slovdahl">
|
||||
<img src="https://avatars.githubusercontent.com/u/1417619?v=4" width="100;" alt="slovdahl"/>
|
||||
@@ -389,13 +396,6 @@ There are some ideas and feature requests which are not currently being pursued
|
||||
<sub><b>LordGuilly</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/bilalmirza74">
|
||||
<img src="https://avatars.githubusercontent.com/u/84387676?v=4" width="100;" alt="bilalmirza74"/>
|
||||
<br />
|
||||
<sub><b>Bilal Mirza</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/muggenhor">
|
||||
<img src="https://avatars.githubusercontent.com/u/484066?v=4" width="100;" alt="muggenhor"/>
|
||||
@@ -406,10 +406,17 @@ There are some ideas and feature requests which are not currently being pursued
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://github.com/ppisljar">
|
||||
<img src="https://avatars.githubusercontent.com/u/13629809?v=4" width="100;" alt="ppisljar"/>
|
||||
<a href="https://github.com/bilalmirza74">
|
||||
<img src="https://avatars.githubusercontent.com/u/84387676?v=4" width="100;" alt="bilalmirza74"/>
|
||||
<br />
|
||||
<sub><b>Peter Pisljar</b></sub>
|
||||
<sub><b>Bilal Mirza</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/AngryApostrophe">
|
||||
<img src="https://avatars.githubusercontent.com/u/89547888?v=4" width="100;" alt="AngryApostrophe"/>
|
||||
<br />
|
||||
<sub><b>AngryApostrophe</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
@@ -426,6 +433,13 @@ There are some ideas and feature requests which are not currently being pursued
|
||||
<sub><b>Ranjana761</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/SURYANSH-RAI">
|
||||
<img src="https://avatars.githubusercontent.com/u/79277130?v=4" width="100;" alt="SURYANSH-RAI"/>
|
||||
<br />
|
||||
<sub><b>SURYANSH RAI</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/SkylightXD">
|
||||
<img src="https://avatars.githubusercontent.com/u/16561545?v=4" width="100;" alt="SkylightXD"/>
|
||||
@@ -433,6 +447,8 @@ There are some ideas and feature requests which are not currently being pursued
|
||||
<sub><b>SkylightXD</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://github.com/ottk3">
|
||||
<img src="https://avatars.githubusercontent.com/u/5236802?v=4" width="100;" alt="ottk3"/>
|
||||
@@ -447,8 +463,6 @@ There are some ideas and feature requests which are not currently being pursued
|
||||
<sub><b>Tobias Bieniek</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://github.com/tkopczuk">
|
||||
<img src="https://avatars.githubusercontent.com/u/101632?v=4" width="100;" alt="tkopczuk"/>
|
||||
@@ -477,6 +491,8 @@ There are some ideas and feature requests which are not currently being pursued
|
||||
<sub><b>flox_x</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://github.com/gneluka">
|
||||
<img src="https://avatars.githubusercontent.com/u/32097881?v=4" width="100;" alt="gneluka"/>
|
||||
@@ -491,8 +507,6 @@ There are some ideas and feature requests which are not currently being pursued
|
||||
<sub><b>kalwados</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://github.com/kub3let">
|
||||
<img src="https://avatars.githubusercontent.com/u/95883234?v=4" width="100;" alt="kub3let"/>
|
||||
@@ -521,13 +535,8 @@ There are some ideas and feature requests which are not currently being pursued
|
||||
<sub><b>smartboart</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/AngryApostrophe">
|
||||
<img src="https://avatars.githubusercontent.com/u/89547888?v=4" width="100;" alt="AngryApostrophe"/>
|
||||
<br />
|
||||
<sub><b>AngryApostrophe</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://github.com/wetneb">
|
||||
<img src="https://avatars.githubusercontent.com/u/309908?v=4" width="100;" alt="wetneb"/>
|
||||
@@ -535,8 +544,6 @@ There are some ideas and feature requests which are not currently being pursued
|
||||
<sub><b>Antonin Delpeuch</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://github.com/adarazs">
|
||||
<img src="https://avatars.githubusercontent.com/u/6269603?v=4" width="100;" alt="adarazs"/>
|
||||
@@ -572,6 +579,8 @@ There are some ideas and feature requests which are not currently being pursued
|
||||
<sub><b>Dave</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://github.com/FarukhS52">
|
||||
<img src="https://avatars.githubusercontent.com/u/129654632?v=4" width="100;" alt="FarukhS52"/>
|
||||
@@ -579,8 +588,6 @@ There are some ideas and feature requests which are not currently being pursued
|
||||
<sub><b>Farookh Zaheer Siddiqui</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://github.com/hex7c0">
|
||||
<img src="https://avatars.githubusercontent.com/u/4419146?v=4" width="100;" alt="hex7c0"/>
|
||||
@@ -616,6 +623,8 @@ There are some ideas and feature requests which are not currently being pursued
|
||||
<sub><b>Joerg Rosenkranz</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://github.com/Innovatorcloudy">
|
||||
<img src="https://avatars.githubusercontent.com/u/183274513?v=4" width="100;" alt="Innovatorcloudy"/>
|
||||
@@ -623,8 +632,6 @@ There are some ideas and feature requests which are not currently being pursued
|
||||
<sub><b>KrishCode</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://github.com/myxor">
|
||||
<img src="https://avatars.githubusercontent.com/u/1397377?v=4" width="100;" alt="myxor"/>
|
||||
@@ -652,6 +659,13 @@ There are some ideas and feature requests which are not currently being pursued
|
||||
<br />
|
||||
<sub><b>Michael Geissler</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/ppisljar">
|
||||
<img src="https://avatars.githubusercontent.com/u/13629809?v=4" width="100;" alt="ppisljar"/>
|
||||
<br />
|
||||
<sub><b>Peter Pisljar</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tbody>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
cmake_minimum_required(VERSION 3.16.0)
|
||||
|
||||
list(APPEND EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common components/esp-tflite-micro)
|
||||
list(APPEND EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common components/esp-tflite-micro components/esp-protocols/components/mdns)
|
||||
|
||||
ADD_CUSTOM_COMMAND(
|
||||
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/version.cpp
|
||||
|
||||
1
code/components/esp-protocols
Submodule
1
code/components/esp-protocols
Submodule
Submodule code/components/esp-protocols added at 9b74256b51
@@ -1,27 +1,53 @@
|
||||
/********************************************************************************
|
||||
* https://github.com/RoboticsBrno/SmartLeds
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2017 RoboticsBrno (RobotikaBrno)
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*******************************************************************************/
|
||||
|
||||
#include "Color.h"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
|
||||
namespace {
|
||||
|
||||
// Int -> fixed point
|
||||
int up( int x ) { return x * 255; }
|
||||
int up(int x) { return x * 255; }
|
||||
|
||||
} // namespace
|
||||
|
||||
int iRgbSqrt( int num ) {
|
||||
int iRgbSqrt(int num) {
|
||||
// https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Binary_numeral_system_.28base_2.29
|
||||
assert( "sqrt input should be non-negative" && num >= 0 );
|
||||
assert( "sqrt input should no exceed 16 bits" && num <= 0xFFFF );
|
||||
assert("sqrt input should be non-negative" && num >= 0);
|
||||
assert("sqrt input should no exceed 16 bits" && num <= 0xFFFF);
|
||||
int res = 0;
|
||||
int bit = 1 << 16;
|
||||
while ( bit > num )
|
||||
while (bit > num)
|
||||
bit >>= 2;
|
||||
while ( bit != 0 ) {
|
||||
if ( num >= res + bit ) {
|
||||
while (bit != 0) {
|
||||
if (num >= res + bit) {
|
||||
num -= res + bit;
|
||||
res = ( res >> 1 ) + bit;
|
||||
res = (res >> 1) + bit;
|
||||
} else
|
||||
res >>= 1;
|
||||
bit >>= 2;
|
||||
@@ -29,104 +55,133 @@ int iRgbSqrt( int num ) {
|
||||
return res;
|
||||
}
|
||||
|
||||
Rgb::Rgb( Hsv y ) {
|
||||
Rgb::Rgb(const Hsv& y) {
|
||||
// https://stackoverflow.com/questions/24152553/hsv-to-rgb-and-back-without-floating-point-math-in-python
|
||||
// greyscale
|
||||
if( y.s == 0 ) {
|
||||
if (y.s == 0) {
|
||||
r = g = b = y.v;
|
||||
return;
|
||||
}
|
||||
|
||||
const int region = y.h / 43;
|
||||
const int remainder = ( y.h - ( region * 43 ) ) * 6;
|
||||
const int remainder = (y.h - (region * 43)) * 6;
|
||||
|
||||
const int p = ( y.v * ( 255 - y.s ) ) >> 8;
|
||||
const int q = ( y.v * ( 255 - ( ( y.s * remainder ) >> 8 ) ) ) >> 8;
|
||||
const int t = ( y.v * ( 255 - ( ( y.s * (255 -remainder ) ) >> 8 ) ) ) >> 8;
|
||||
const int p = (y.v * (255 - y.s)) >> 8;
|
||||
const int q = (y.v * (255 - ((y.s * remainder) >> 8))) >> 8;
|
||||
const int t = (y.v * (255 - ((y.s * (255 - remainder)) >> 8))) >> 8;
|
||||
|
||||
switch( region ) {
|
||||
case 0: r = y.v; g = t; b = p; break;
|
||||
case 1: r = q; g = y.v; b = p; break;
|
||||
case 2: r = p; g = y.v; b = t; break;
|
||||
case 3: r = p; g = q; b = y.v; break;
|
||||
case 4: r = t; g = p; b = y.v; break;
|
||||
case 5: r = y.v; g = p; b = q; break;
|
||||
default: __builtin_trap();
|
||||
switch (region) {
|
||||
case 0:
|
||||
r = y.v;
|
||||
g = t;
|
||||
b = p;
|
||||
break;
|
||||
case 1:
|
||||
r = q;
|
||||
g = y.v;
|
||||
b = p;
|
||||
break;
|
||||
case 2:
|
||||
r = p;
|
||||
g = y.v;
|
||||
b = t;
|
||||
break;
|
||||
case 3:
|
||||
r = p;
|
||||
g = q;
|
||||
b = y.v;
|
||||
break;
|
||||
case 4:
|
||||
r = t;
|
||||
g = p;
|
||||
b = y.v;
|
||||
break;
|
||||
case 5:
|
||||
r = y.v;
|
||||
g = p;
|
||||
b = q;
|
||||
break;
|
||||
default:
|
||||
__builtin_trap();
|
||||
}
|
||||
|
||||
a = y.a;
|
||||
}
|
||||
|
||||
Rgb& Rgb::operator=( Hsv hsv ) {
|
||||
Rgb r{ hsv };
|
||||
swap( r );
|
||||
Rgb& Rgb::operator=(const Hsv& hsv) {
|
||||
Rgb r { hsv };
|
||||
swap(r);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Rgb Rgb::operator+( Rgb in ) const {
|
||||
Rgb Rgb::operator+(const Rgb& in) const {
|
||||
auto copy = *this;
|
||||
copy += in;
|
||||
return copy;
|
||||
}
|
||||
|
||||
Rgb& Rgb::operator+=( Rgb in ) {
|
||||
Rgb& Rgb::operator+=(const Rgb& in) {
|
||||
unsigned int red = r + in.r;
|
||||
r = ( red < 255 ) ? red : 255;
|
||||
r = (red < 255) ? red : 255;
|
||||
unsigned int green = g + in.g;
|
||||
g = ( green < 255 ) ? green : 255;
|
||||
g = (green < 255) ? green : 255;
|
||||
unsigned int blue = b + in.b;
|
||||
b = ( blue < 255 ) ? blue : 255;
|
||||
b = (blue < 255) ? blue : 255;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Rgb& Rgb::blend( Rgb in ) {
|
||||
unsigned int inAlpha = in.a * ( 255 - a );
|
||||
Rgb Rgb::operator-(const Rgb& in) const {
|
||||
auto copy = *this;
|
||||
copy -= in;
|
||||
return copy;
|
||||
}
|
||||
|
||||
Rgb& Rgb::operator-=(const Rgb& in) {
|
||||
r = (in.r > r) ? 0 : r - in.r;
|
||||
g = (in.g > g) ? 0 : g - in.g;
|
||||
b = (in.b > b) ? 0 : b - in.b;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Rgb& Rgb::blend(const Rgb& in) {
|
||||
unsigned int inAlpha = in.a * (255 - a);
|
||||
unsigned int alpha = a + inAlpha;
|
||||
r = iRgbSqrt( ( ( r * r * a ) + ( in.r * in.r * inAlpha ) ) / alpha );
|
||||
g = iRgbSqrt( ( ( g * g * a ) + ( in.g * in.g * inAlpha ) ) / alpha );
|
||||
b = iRgbSqrt( ( ( b * b * a ) + ( in.b * in.b * inAlpha ) ) / alpha );
|
||||
r = iRgbSqrt(((r * r * a) + (in.r * in.r * inAlpha)) / alpha);
|
||||
g = iRgbSqrt(((g * g * a) + (in.g * in.g * inAlpha)) / alpha);
|
||||
b = iRgbSqrt(((b * b * a) + (in.b * in.b * inAlpha)) / alpha);
|
||||
a = alpha;
|
||||
return *this;
|
||||
}
|
||||
|
||||
uint8_t IRAM_ATTR Rgb::getGrb( int idx ) {
|
||||
switch ( idx ) {
|
||||
case 0: return g;
|
||||
case 1: return r;
|
||||
case 2: return b;
|
||||
}
|
||||
__builtin_unreachable();
|
||||
}
|
||||
|
||||
Hsv::Hsv( Rgb r ) {
|
||||
int min = std::min( r.r, std::min( r.g, r.b ) );
|
||||
int max = std::max( r.r, std::max( r.g, r.b ) );
|
||||
Hsv::Hsv(const Rgb& r) {
|
||||
int min = std::min(r.r, std::min(r.g, r.b));
|
||||
int max = std::max(r.r, std::max(r.g, r.b));
|
||||
int chroma = max - min;
|
||||
|
||||
v = max;
|
||||
if ( chroma == 0 ) {
|
||||
if (chroma == 0) {
|
||||
h = s = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
s = up( chroma ) / max;
|
||||
s = up(chroma) / max;
|
||||
int hh;
|
||||
if ( max == r.r )
|
||||
hh = ( up( int( r.g ) - int( r.b ) ) ) / chroma / 6;
|
||||
else if ( max == r.g )
|
||||
hh = 255 / 3 + ( up( int( r.b ) - int( r.r ) ) ) / chroma / 6;
|
||||
if (max == r.r)
|
||||
hh = (up(int(r.g) - int(r.b))) / chroma / 6;
|
||||
else if (max == r.g)
|
||||
hh = 255 / 3 + (up(int(r.b) - int(r.r))) / chroma / 6;
|
||||
else
|
||||
hh = 2 * 255 / 3 + ( up( int( r.r ) - int( r.g ) ) ) / chroma / 6;
|
||||
hh = 2 * 255 / 3 + (up(int(r.r) - int(r.g))) / chroma / 6;
|
||||
|
||||
if ( hh < 0 )
|
||||
if (hh < 0)
|
||||
hh += 255;
|
||||
h = hh;
|
||||
|
||||
a = r.a;
|
||||
}
|
||||
|
||||
Hsv& Hsv::operator=( Rgb rgb ) {
|
||||
Hsv h{ rgb };
|
||||
swap( h );
|
||||
Hsv& Hsv::operator=(const Rgb& rgb) {
|
||||
Hsv h { rgb };
|
||||
swap(h);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -1,51 +1,90 @@
|
||||
/********************************************************************************
|
||||
* https://github.com/RoboticsBrno/SmartLeds
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2017 RoboticsBrno (RobotikaBrno)
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef COLOR_H
|
||||
#define COLOR_H
|
||||
|
||||
#include <cstdint>
|
||||
#include "esp_attr.h"
|
||||
#include <cstdint>
|
||||
union Hsv;
|
||||
|
||||
union Rgb {
|
||||
struct __attribute__ ((packed)) {
|
||||
uint8_t r, g, b, a;
|
||||
struct __attribute__((packed)) {
|
||||
uint8_t g, r, b, a;
|
||||
};
|
||||
uint32_t value;
|
||||
|
||||
Rgb( uint8_t r = 0, uint8_t g = 0, uint8_t b = 0, uint8_t a = 255 ) : r( r ), g( g ), b( b ), a( a ) {}
|
||||
Rgb( Hsv c );
|
||||
Rgb& operator=( Rgb rgb ) { swap( rgb ); return *this; }
|
||||
Rgb& operator=( Hsv hsv );
|
||||
Rgb operator+( Rgb in ) const;
|
||||
Rgb& operator+=( Rgb in );
|
||||
bool operator==( Rgb in ) const { return in.value == value; }
|
||||
Rgb& blend( Rgb in );
|
||||
void swap( Rgb& o ) { value = o.value; }
|
||||
Rgb(uint8_t r = 0, uint8_t g = 0, uint8_t b = 0, uint8_t a = 255)
|
||||
: g(g)
|
||||
, r(r)
|
||||
, b(b)
|
||||
, a(a) {}
|
||||
Rgb(const Hsv& c);
|
||||
Rgb(const Rgb&) = default;
|
||||
Rgb& operator=(const Rgb& rgb) {
|
||||
swap(rgb);
|
||||
return *this;
|
||||
}
|
||||
Rgb& operator=(const Hsv& hsv);
|
||||
Rgb operator+(const Rgb& in) const;
|
||||
Rgb& operator+=(const Rgb& in);
|
||||
Rgb operator-(const Rgb& in) const;
|
||||
Rgb& operator-=(const Rgb& in);
|
||||
bool operator==(const Rgb& in) const { return in.value == value; }
|
||||
Rgb& blend(const Rgb& in);
|
||||
void swap(const Rgb& o) { value = o.value; }
|
||||
void linearize() {
|
||||
r = channelGamma(r);
|
||||
g = channelGamma(g);
|
||||
b = channelGamma(b);
|
||||
}
|
||||
|
||||
uint8_t IRAM_ATTR getGrb( int idx );
|
||||
|
||||
void stretchChannels( uint8_t maxR, uint8_t maxG, uint8_t maxB ) {
|
||||
r = stretch( r, maxR );
|
||||
g = stretch( g, maxG );
|
||||
b = stretch( b, maxB );
|
||||
inline uint8_t IRAM_ATTR getGrb(int idx) {
|
||||
switch (idx) {
|
||||
case 0:
|
||||
return g;
|
||||
case 1:
|
||||
return r;
|
||||
case 2:
|
||||
return b;
|
||||
}
|
||||
__builtin_unreachable();
|
||||
}
|
||||
|
||||
void stretchChannelsEvenly( uint8_t max ) {
|
||||
stretchChannels( max, max, max );
|
||||
void stretchChannels(uint8_t maxR, uint8_t maxG, uint8_t maxB) {
|
||||
r = stretch(r, maxR);
|
||||
g = stretch(g, maxG);
|
||||
b = stretch(b, maxB);
|
||||
}
|
||||
|
||||
void stretchChannelsEvenly(uint8_t max) { stretchChannels(max, max, max); }
|
||||
|
||||
private:
|
||||
uint8_t stretch( int value, uint8_t max ) {
|
||||
return ( value * max ) >> 8;
|
||||
}
|
||||
uint8_t stretch(int value, uint8_t max) { return (value * max) >> 8; }
|
||||
|
||||
uint8_t channelGamma( int channel ) {
|
||||
uint8_t channelGamma(int channel) {
|
||||
/* The optimal gamma correction is x^2.8. However, this is expensive to
|
||||
* compute. Therefore, we use x^3 for gamma correction. Also, we add a
|
||||
* bias as the WS2812 LEDs do not turn on for values less than 4. */
|
||||
@@ -53,22 +92,27 @@ private:
|
||||
return channel;
|
||||
channel = channel * channel * channel * 251;
|
||||
channel >>= 24;
|
||||
return static_cast< uint8_t >( 4 + channel );
|
||||
return static_cast<uint8_t>(4 + channel);
|
||||
}
|
||||
};
|
||||
|
||||
union Hsv {
|
||||
struct __attribute__ ((packed)) {
|
||||
struct __attribute__((packed)) {
|
||||
uint8_t h, s, v, a;
|
||||
};
|
||||
uint32_t value;
|
||||
|
||||
Hsv( uint8_t h, uint8_t s = 0, uint8_t v = 0, uint8_t a = 255 ) : h( h ), s( s ), v( v ), a( a ) {}
|
||||
Hsv( Rgb r );
|
||||
Hsv& operator=( Hsv h ) { swap( h ); return *this; }
|
||||
Hsv& operator=( Rgb rgb );
|
||||
bool operator==( Hsv in ) const { return in.value == value; }
|
||||
void swap( Hsv& o ) { value = o.value; }
|
||||
Hsv(uint8_t h, uint8_t s = 0, uint8_t v = 0, uint8_t a = 255)
|
||||
: h(h)
|
||||
, s(s)
|
||||
, v(v)
|
||||
, a(a) {}
|
||||
Hsv(const Rgb& r);
|
||||
Hsv& operator=(const Hsv& h) {
|
||||
swap(h);
|
||||
return *this;
|
||||
}
|
||||
Hsv& operator=(const Rgb& rgb);
|
||||
bool operator==(const Hsv& in) const { return in.value == value; }
|
||||
void swap(const Hsv& o) { value = o.value; }
|
||||
};
|
||||
|
||||
#endif //COLOR_H
|
||||
|
||||
60
code/components/jomjol_controlGPIO/RmtDriver.h
Normal file
60
code/components/jomjol_controlGPIO/RmtDriver.h
Normal file
@@ -0,0 +1,60 @@
|
||||
/********************************************************************************
|
||||
* https://github.com/RoboticsBrno/SmartLeds
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2017 RoboticsBrno (RobotikaBrno)
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <esp_system.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#if defined(ESP_IDF_VERSION)
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||
#define SMARTLEDS_NEW_RMT_DRIVER 1
|
||||
#else
|
||||
#define SMARTLEDS_NEW_RMT_DRIVER 0
|
||||
#endif
|
||||
#else
|
||||
#define SMARTLEDS_NEW_RMT_DRIVER 0
|
||||
#endif
|
||||
|
||||
namespace detail {
|
||||
|
||||
struct TimingParams {
|
||||
uint32_t T0H;
|
||||
uint32_t T1H;
|
||||
uint32_t T0L;
|
||||
uint32_t T1L;
|
||||
uint32_t TRS;
|
||||
};
|
||||
|
||||
using LedType = TimingParams;
|
||||
|
||||
} // namespace detail
|
||||
|
||||
#if SMARTLEDS_NEW_RMT_DRIVER
|
||||
#include "RmtDriver5.h"
|
||||
#else
|
||||
#include "RmtDriver4.h"
|
||||
#endif
|
||||
143
code/components/jomjol_controlGPIO/RmtDriver4.cpp
Normal file
143
code/components/jomjol_controlGPIO/RmtDriver4.cpp
Normal file
@@ -0,0 +1,143 @@
|
||||
/********************************************************************************
|
||||
* https://github.com/RoboticsBrno/SmartLeds
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2017 RoboticsBrno (RobotikaBrno)
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*******************************************************************************/
|
||||
|
||||
#include "RmtDriver4.h"
|
||||
|
||||
#if !SMARTLEDS_NEW_RMT_DRIVER
|
||||
#include "SmartLeds.h"
|
||||
|
||||
namespace detail {
|
||||
|
||||
// 8 still seems to work, but timings become marginal
|
||||
static const int DIVIDER = 4;
|
||||
// minimum time of a single RMT duration based on clock ns
|
||||
static const double RMT_DURATION_NS = 12.5;
|
||||
|
||||
RmtDriver::RmtDriver(const LedType& timing, int count, int pin, int channel_num, SemaphoreHandle_t finishedFlag)
|
||||
: _timing(timing)
|
||||
, _count(count)
|
||||
, _pin((gpio_num_t)pin)
|
||||
, _finishedFlag(finishedFlag)
|
||||
, _channel((rmt_channel_t)channel_num) {
|
||||
_bitToRmt[0].level0 = 1;
|
||||
_bitToRmt[0].level1 = 0;
|
||||
_bitToRmt[0].duration0 = _timing.T0H / (RMT_DURATION_NS * DIVIDER);
|
||||
_bitToRmt[0].duration1 = _timing.T0L / (RMT_DURATION_NS * DIVIDER);
|
||||
|
||||
_bitToRmt[1].level0 = 1;
|
||||
_bitToRmt[1].level1 = 0;
|
||||
_bitToRmt[1].duration0 = _timing.T1H / (RMT_DURATION_NS * DIVIDER);
|
||||
_bitToRmt[1].duration1 = _timing.T1L / (RMT_DURATION_NS * DIVIDER);
|
||||
}
|
||||
|
||||
esp_err_t RmtDriver::init() {
|
||||
rmt_config_t config = RMT_DEFAULT_CONFIG_TX(_pin, _channel);
|
||||
config.rmt_mode = RMT_MODE_TX;
|
||||
config.clk_div = DIVIDER;
|
||||
config.mem_block_num = 1;
|
||||
|
||||
return rmt_config(&config);
|
||||
}
|
||||
|
||||
esp_err_t RmtDriver::registerIsr(bool isFirstRegisteredChannel) {
|
||||
auto err = rmt_driver_install(_channel, 0,
|
||||
#if defined(CONFIG_RMT_ISR_IRAM_SAFE)
|
||||
ESP_INTR_FLAG_IRAM
|
||||
#else
|
||||
0
|
||||
#endif
|
||||
);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (isFirstRegisteredChannel) {
|
||||
rmt_register_tx_end_callback(txEndCallback, NULL);
|
||||
}
|
||||
|
||||
err = rmt_translator_init(_channel, translateSample);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
return rmt_translator_set_context(_channel, this);
|
||||
}
|
||||
|
||||
esp_err_t RmtDriver::unregisterIsr() { return rmt_driver_uninstall(_channel); }
|
||||
|
||||
void IRAM_ATTR RmtDriver::txEndCallback(rmt_channel_t channel, void* arg) {
|
||||
xSemaphoreGiveFromISR(SmartLed::ledForChannel(channel)->_finishedFlag, nullptr);
|
||||
}
|
||||
|
||||
void IRAM_ATTR RmtDriver::translateSample(const void* src, rmt_item32_t* dest, size_t src_size,
|
||||
size_t wanted_rmt_items_num, size_t* out_consumed_src_bytes, size_t* out_used_rmt_items) {
|
||||
RmtDriver* self;
|
||||
ESP_ERROR_CHECK(rmt_translator_get_context(out_used_rmt_items, (void**)&self));
|
||||
|
||||
const auto& _bitToRmt = self->_bitToRmt;
|
||||
const auto src_offset = self->_translatorSourceOffset;
|
||||
|
||||
auto* src_components = (const uint8_t*)src;
|
||||
size_t consumed_src_bytes = 0;
|
||||
size_t used_rmt_items = 0;
|
||||
|
||||
while (consumed_src_bytes < src_size && used_rmt_items + 7 < wanted_rmt_items_num) {
|
||||
uint8_t val = *src_components;
|
||||
|
||||
// each bit, from highest to lowest
|
||||
for (uint8_t j = 0; j != 8; j++, val <<= 1) {
|
||||
dest->val = _bitToRmt[val >> 7].val;
|
||||
++dest;
|
||||
}
|
||||
|
||||
used_rmt_items += 8;
|
||||
++src_components;
|
||||
++consumed_src_bytes;
|
||||
|
||||
// skip alpha byte
|
||||
if (((src_offset + consumed_src_bytes) % 4) == 3) {
|
||||
++src_components;
|
||||
++consumed_src_bytes;
|
||||
|
||||
// TRST delay after last pixel in strip
|
||||
if (consumed_src_bytes == src_size) {
|
||||
(dest - 1)->duration1 = self->_timing.TRS / (detail::RMT_DURATION_NS * detail::DIVIDER);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self->_translatorSourceOffset = src_offset + consumed_src_bytes;
|
||||
*out_consumed_src_bytes = consumed_src_bytes;
|
||||
*out_used_rmt_items = used_rmt_items;
|
||||
}
|
||||
|
||||
esp_err_t RmtDriver::transmit(const Rgb* buffer) {
|
||||
static_assert(sizeof(Rgb) == 4); // The translator code above assumes RGB is 4 bytes
|
||||
|
||||
_translatorSourceOffset = 0;
|
||||
return rmt_write_sample(_channel, (const uint8_t*)buffer, _count * 4, false);
|
||||
}
|
||||
};
|
||||
#endif // !SMARTLEDS_NEW_RMT_DRIVER
|
||||
68
code/components/jomjol_controlGPIO/RmtDriver4.h
Normal file
68
code/components/jomjol_controlGPIO/RmtDriver4.h
Normal file
@@ -0,0 +1,68 @@
|
||||
/********************************************************************************
|
||||
* https://github.com/RoboticsBrno/SmartLeds
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2017 RoboticsBrno (RobotikaBrno)
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "RmtDriver.h"
|
||||
|
||||
#if !SMARTLEDS_NEW_RMT_DRIVER
|
||||
#include "Color.h"
|
||||
#include <driver/rmt.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
|
||||
namespace detail {
|
||||
|
||||
constexpr const int CHANNEL_COUNT = RMT_CHANNEL_MAX;
|
||||
|
||||
class RmtDriver {
|
||||
public:
|
||||
RmtDriver(const LedType& timing, int count, int pin, int channel_num, SemaphoreHandle_t finishedFlag);
|
||||
RmtDriver(const RmtDriver&) = delete;
|
||||
|
||||
esp_err_t init();
|
||||
esp_err_t registerIsr(bool isFirstRegisteredChannel);
|
||||
esp_err_t unregisterIsr();
|
||||
esp_err_t transmit(const Rgb* buffer);
|
||||
|
||||
private:
|
||||
static void IRAM_ATTR txEndCallback(rmt_channel_t channel, void* arg);
|
||||
|
||||
static void IRAM_ATTR translateSample(const void* src, rmt_item32_t* dest, size_t src_size,
|
||||
size_t wanted_rmt_items_num, size_t* out_consumed_src_bytes, size_t* out_used_rmt_items);
|
||||
|
||||
const LedType& _timing;
|
||||
int _count;
|
||||
gpio_num_t _pin;
|
||||
SemaphoreHandle_t _finishedFlag;
|
||||
|
||||
rmt_channel_t _channel;
|
||||
rmt_item32_t _bitToRmt[2];
|
||||
size_t _translatorSourceOffset;
|
||||
};
|
||||
|
||||
};
|
||||
#endif // !SMARTLEDS_NEW_RMT_DRIVER
|
||||
202
code/components/jomjol_controlGPIO/RmtDriver5.cpp
Normal file
202
code/components/jomjol_controlGPIO/RmtDriver5.cpp
Normal file
@@ -0,0 +1,202 @@
|
||||
/********************************************************************************
|
||||
* https://github.com/RoboticsBrno/SmartLeds
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2017 RoboticsBrno (RobotikaBrno)
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*******************************************************************************/
|
||||
|
||||
#include "RmtDriver5.h"
|
||||
|
||||
#if SMARTLEDS_NEW_RMT_DRIVER
|
||||
#include <cstddef>
|
||||
|
||||
#include "SmartLeds.h"
|
||||
|
||||
namespace detail {
|
||||
|
||||
static constexpr const uint32_t RMT_RESOLUTION_HZ = 20 * 1000 * 1000; // 20 MHz
|
||||
static constexpr const uint32_t RMT_NS_PER_TICK = 1000000000LLU / RMT_RESOLUTION_HZ;
|
||||
|
||||
static RmtEncoderWrapper* IRAM_ATTR encSelf(rmt_encoder_t* encoder) {
|
||||
return (RmtEncoderWrapper*)(((intptr_t)encoder) - offsetof(RmtEncoderWrapper, base));
|
||||
}
|
||||
|
||||
static size_t IRAM_ATTR encEncode(rmt_encoder_t* encoder, rmt_channel_handle_t tx_channel, const void* primary_data,
|
||||
size_t data_size, rmt_encode_state_t* ret_state) {
|
||||
auto* self = encSelf(encoder);
|
||||
|
||||
// Delay after last pixel
|
||||
if ((self->last_state & RMT_ENCODING_COMPLETE) && self->frame_idx == data_size) {
|
||||
*ret_state = (rmt_encode_state_t)0;
|
||||
return self->copy_encoder->encode(
|
||||
self->copy_encoder, tx_channel, (const void*)&self->reset_code, sizeof(self->reset_code), ret_state);
|
||||
}
|
||||
|
||||
if (self->last_state & RMT_ENCODING_COMPLETE) {
|
||||
Rgb* pixel = ((Rgb*)primary_data) + self->frame_idx;
|
||||
self->buffer_len = sizeof(self->buffer);
|
||||
for (size_t i = 0; i < sizeof(self->buffer); ++i) {
|
||||
self->buffer[i] = pixel->getGrb(self->component_idx);
|
||||
if (++self->component_idx == 3) {
|
||||
self->component_idx = 0;
|
||||
if (++self->frame_idx == data_size) {
|
||||
self->buffer_len = i + 1;
|
||||
break;
|
||||
}
|
||||
++pixel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self->last_state = (rmt_encode_state_t)0;
|
||||
auto encoded_symbols = self->bytes_encoder->encode(
|
||||
self->bytes_encoder, tx_channel, (const void*)&self->buffer, self->buffer_len, &self->last_state);
|
||||
if (self->last_state & RMT_ENCODING_MEM_FULL) {
|
||||
*ret_state = RMT_ENCODING_MEM_FULL;
|
||||
} else {
|
||||
*ret_state = (rmt_encode_state_t)0;
|
||||
}
|
||||
|
||||
return encoded_symbols;
|
||||
}
|
||||
|
||||
static esp_err_t encReset(rmt_encoder_t* encoder) {
|
||||
auto* self = encSelf(encoder);
|
||||
rmt_encoder_reset(self->bytes_encoder);
|
||||
rmt_encoder_reset(self->copy_encoder);
|
||||
self->last_state = RMT_ENCODING_COMPLETE;
|
||||
self->frame_idx = 0;
|
||||
self->component_idx = 0;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t encDelete(rmt_encoder_t* encoder) {
|
||||
auto* self = encSelf(encoder);
|
||||
rmt_del_encoder(self->bytes_encoder);
|
||||
rmt_del_encoder(self->copy_encoder);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
RmtDriver::RmtDriver(const LedType& timing, int count, int pin, int channel_num, SemaphoreHandle_t finishedFlag)
|
||||
: _timing(timing)
|
||||
, _count(count)
|
||||
, _pin(pin)
|
||||
, _finishedFlag(finishedFlag)
|
||||
, _channel(nullptr)
|
||||
, _encoder {} {}
|
||||
|
||||
esp_err_t RmtDriver::init() {
|
||||
_encoder.base.encode = encEncode;
|
||||
_encoder.base.reset = encReset;
|
||||
_encoder.base.del = encDelete;
|
||||
|
||||
_encoder.reset_code.duration0 = _timing.TRS / RMT_NS_PER_TICK;
|
||||
|
||||
rmt_bytes_encoder_config_t bytes_cfg = {
|
||||
.bit0 = {
|
||||
.duration0 = uint16_t(_timing.T0H / RMT_NS_PER_TICK),
|
||||
.level0 = 1,
|
||||
.duration1 = uint16_t(_timing.T0L / RMT_NS_PER_TICK),
|
||||
.level1 = 0,
|
||||
},
|
||||
.bit1 = {
|
||||
.duration0 = uint16_t(_timing.T1H / RMT_NS_PER_TICK),
|
||||
.level0 = 1,
|
||||
.duration1 = uint16_t(_timing.T1L / RMT_NS_PER_TICK),
|
||||
.level1 = 0,
|
||||
},
|
||||
.flags = {
|
||||
.msb_first = 1,
|
||||
},
|
||||
};
|
||||
|
||||
auto err = rmt_new_bytes_encoder(&bytes_cfg, &_encoder.bytes_encoder);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
rmt_copy_encoder_config_t copy_cfg = {};
|
||||
err = rmt_new_copy_encoder(©_cfg, &_encoder.copy_encoder);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// The config must be in registerIsr, because rmt_new_tx_channel
|
||||
// registers the ISR
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t RmtDriver::registerIsr(bool isFirstRegisteredChannel) {
|
||||
rmt_tx_channel_config_t conf = {
|
||||
.gpio_num = (gpio_num_t)_pin,
|
||||
.clk_src = RMT_CLK_SRC_DEFAULT, //.clk_src = RMT_CLK_SRC_APB,
|
||||
.resolution_hz = RMT_RESOLUTION_HZ,
|
||||
.mem_block_symbols = SOC_RMT_MEM_WORDS_PER_CHANNEL,
|
||||
.trans_queue_depth = 1,
|
||||
.flags = {},
|
||||
};
|
||||
|
||||
auto err = rmt_new_tx_channel(&conf, &_channel);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
rmt_tx_event_callbacks_t callbacks_cfg = {};
|
||||
callbacks_cfg.on_trans_done = txDoneCallback;
|
||||
|
||||
err = rmt_tx_register_event_callbacks(_channel, &callbacks_cfg, this);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
return rmt_enable(_channel);
|
||||
}
|
||||
|
||||
esp_err_t RmtDriver::unregisterIsr() {
|
||||
auto err = rmt_del_encoder(&_encoder.base);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
err = rmt_disable(_channel);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
return rmt_del_channel(_channel);
|
||||
}
|
||||
|
||||
bool IRAM_ATTR RmtDriver::txDoneCallback(
|
||||
rmt_channel_handle_t tx_chan, const rmt_tx_done_event_data_t* edata, void* user_ctx) {
|
||||
auto* self = (RmtDriver*)user_ctx;
|
||||
auto taskWoken = pdTRUE;
|
||||
xSemaphoreGiveFromISR(self->_finishedFlag, &taskWoken);
|
||||
return taskWoken == pdTRUE;
|
||||
}
|
||||
|
||||
esp_err_t RmtDriver::transmit(const Rgb* buffer) {
|
||||
rmt_encoder_reset(&_encoder.base);
|
||||
rmt_transmit_config_t cfg = {};
|
||||
return rmt_transmit(_channel, &_encoder.base, buffer, _count, &cfg);
|
||||
}
|
||||
};
|
||||
#endif // !SMARTLEDS_NEW_RMT_DRIVER
|
||||
91
code/components/jomjol_controlGPIO/RmtDriver5.h
Normal file
91
code/components/jomjol_controlGPIO/RmtDriver5.h
Normal file
@@ -0,0 +1,91 @@
|
||||
/********************************************************************************
|
||||
* https://github.com/RoboticsBrno/SmartLeds
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2017 RoboticsBrno (RobotikaBrno)
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "RmtDriver.h"
|
||||
|
||||
#if SMARTLEDS_NEW_RMT_DRIVER
|
||||
#include <driver/rmt_tx.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
#include <type_traits>
|
||||
|
||||
#include "Color.h"
|
||||
|
||||
#if !defined(CONFIG_RMT_ISR_IRAM_SAFE) && !defined(SMARTLEDS_DISABLE_IRAM_WARNING)
|
||||
#warning "Please enable CONFIG_RMT_ISR_IRAM_SAFE IDF option." \
|
||||
"without it, the IDF driver is not able to supply data fast enough."
|
||||
#endif
|
||||
|
||||
namespace detail {
|
||||
|
||||
constexpr const int CHANNEL_COUNT = SOC_RMT_GROUPS * SOC_RMT_CHANNELS_PER_GROUP;
|
||||
|
||||
class RmtDriver;
|
||||
|
||||
// This is ridiculous
|
||||
struct RmtEncoderWrapper {
|
||||
struct rmt_encoder_t base;
|
||||
struct rmt_encoder_t* bytes_encoder;
|
||||
struct rmt_encoder_t* copy_encoder;
|
||||
RmtDriver* driver;
|
||||
rmt_symbol_word_t reset_code;
|
||||
|
||||
uint8_t buffer[SOC_RMT_MEM_WORDS_PER_CHANNEL / 8];
|
||||
rmt_encode_state_t last_state;
|
||||
size_t frame_idx;
|
||||
uint8_t component_idx;
|
||||
uint8_t buffer_len;
|
||||
};
|
||||
|
||||
static_assert(std::is_standard_layout<RmtEncoderWrapper>::value == true);
|
||||
|
||||
class RmtDriver {
|
||||
public:
|
||||
RmtDriver(const LedType& timing, int count, int pin, int channel_num, SemaphoreHandle_t finishedFlag);
|
||||
RmtDriver(const RmtDriver&) = delete;
|
||||
|
||||
esp_err_t init();
|
||||
esp_err_t registerIsr(bool isFirstRegisteredChannel);
|
||||
esp_err_t unregisterIsr();
|
||||
esp_err_t transmit(const Rgb* buffer);
|
||||
|
||||
private:
|
||||
static bool IRAM_ATTR txDoneCallback(
|
||||
rmt_channel_handle_t tx_chan, const rmt_tx_done_event_data_t* edata, void* user_ctx);
|
||||
|
||||
const LedType& _timing;
|
||||
int _count;
|
||||
int _pin;
|
||||
SemaphoreHandle_t _finishedFlag;
|
||||
|
||||
rmt_channel_handle_t _channel;
|
||||
RmtEncoderWrapper _encoder;
|
||||
};
|
||||
|
||||
};
|
||||
#endif // !SMARTLEDS_NEW_RMT_DRIVER
|
||||
@@ -1,90 +1,35 @@
|
||||
/********************************************************************************
|
||||
* https://github.com/RoboticsBrno/SmartLeds
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2017 RoboticsBrno (RobotikaBrno)
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*******************************************************************************/
|
||||
|
||||
#include "SmartLeds.h"
|
||||
|
||||
|
||||
/* PlatformIO 6 (ESP IDF 5) does no longer allow access to RMTMEM,
|
||||
see https://docs.espressif.com/projects/esp-idf/en/latest/esp32/migration-guides/release-5.x/5.0/peripherals.html?highlight=rmtmem#id5
|
||||
As a dirty workaround, we copy the needed structures from rmt_struct.h
|
||||
In the long run, this should be replaced! */
|
||||
typedef struct rmt_item32_s {
|
||||
union {
|
||||
struct {
|
||||
uint32_t duration0 :15;
|
||||
uint32_t level0 :1;
|
||||
uint32_t duration1 :15;
|
||||
uint32_t level1 :1;
|
||||
};
|
||||
uint32_t val;
|
||||
};
|
||||
} rmt_item32_t;
|
||||
|
||||
//Allow access to RMT memory using RMTMEM.chan[0].data32[8]
|
||||
typedef volatile struct rmt_mem_s {
|
||||
struct {
|
||||
rmt_item32_t data32[64];
|
||||
} chan[8];
|
||||
} rmt_mem_t;
|
||||
extern rmt_mem_t RMTMEM;
|
||||
|
||||
|
||||
|
||||
IsrCore SmartLed::_interruptCore = CoreCurrent;
|
||||
intr_handle_t SmartLed::_interruptHandle = NULL;
|
||||
|
||||
SmartLed*& IRAM_ATTR SmartLed::ledForChannel( int channel ) {
|
||||
static SmartLed* table[8] = { nullptr };
|
||||
assert( channel < 8 );
|
||||
return table[ channel ];
|
||||
}
|
||||
|
||||
void IRAM_ATTR SmartLed::interruptHandler(void*) {
|
||||
for (int channel = 0; channel != 8; channel++) {
|
||||
auto self = ledForChannel( channel );
|
||||
|
||||
if ( RMT.int_st.val & (1 << (24 + channel ) ) ) { // tx_thr_event
|
||||
if ( self )
|
||||
self->copyRmtHalfBlock();
|
||||
RMT.int_clr.val |= 1 << ( 24 + channel );
|
||||
} else if ( RMT.int_st.val & ( 1 << (3 * channel ) ) ) { // tx_end
|
||||
if ( self )
|
||||
xSemaphoreGiveFromISR( self->_finishedFlag, nullptr );
|
||||
RMT.int_clr.val |= 1 << ( 3 * channel );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IRAM_ATTR SmartLed::copyRmtHalfBlock() {
|
||||
int offset = detail::MAX_PULSES * _halfIdx;
|
||||
_halfIdx = !_halfIdx;
|
||||
int len = 3 - _componentPosition + 3 * ( _count - 1 );
|
||||
len = std::min( len, detail::MAX_PULSES / 8 );
|
||||
|
||||
if ( !len ) {
|
||||
for ( int i = 0; i < detail::MAX_PULSES; i++) {
|
||||
RMTMEM.chan[ _channel].data32[i + offset ].val = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int i;
|
||||
for ( i = 0; i != len && _pixelPosition != _count; i++ ) {
|
||||
uint8_t val = _buffer[ _pixelPosition ].getGrb( _componentPosition );
|
||||
for ( int j = 0; j != 8; j++, val <<= 1 ) {
|
||||
int bit = val >> 7;
|
||||
int idx = i * 8 + offset + j;
|
||||
RMTMEM.chan[ _channel ].data32[ idx ].val = _bitToRmt[ bit & 0x01 ].value;
|
||||
}
|
||||
if ( _pixelPosition == _count - 1 && _componentPosition == 2 ) {
|
||||
RMTMEM.chan[ _channel ].data32[ i * 8 + offset + 7 ].duration1 =
|
||||
_timing.TRS / ( detail::RMT_DURATION_NS * detail::DIVIDER );
|
||||
}
|
||||
|
||||
_componentPosition++;
|
||||
if ( _componentPosition == 3 ) {
|
||||
_componentPosition = 0;
|
||||
_pixelPosition++;
|
||||
}
|
||||
}
|
||||
|
||||
for ( i *= 8; i != detail::MAX_PULSES; i++ ) {
|
||||
RMTMEM.chan[ _channel ].data32[ i + offset ].val = 0;
|
||||
}
|
||||
SmartLed*& IRAM_ATTR SmartLed::ledForChannel(int channel) {
|
||||
static SmartLed* table[detail::CHANNEL_COUNT] = {};
|
||||
assert(channel < detail::CHANNEL_COUNT);
|
||||
return table[channel];
|
||||
}
|
||||
|
||||
@@ -1,7 +1,30 @@
|
||||
#pragma once
|
||||
/********************************************************************************
|
||||
* https://github.com/RoboticsBrno/SmartLeds
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2017 RoboticsBrno (RobotikaBrno)
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*******************************************************************************/
|
||||
|
||||
#ifndef SMARTLEDS_H
|
||||
#define SMARTLEDS_H
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
* A C++ driver for the WS2812 LEDs using the RMT peripheral on the ESP32.
|
||||
@@ -31,305 +54,196 @@
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <memory>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
#include "esp_idf_version.h"
|
||||
#if (ESP_IDF_VERSION_MAJOR >= 5)
|
||||
#include "soc/periph_defs.h"
|
||||
#include "esp_private/periph_ctrl.h"
|
||||
#include "soc/gpio_sig_map.h"
|
||||
#include "soc/gpio_periph.h"
|
||||
#include "soc/io_mux_reg.h"
|
||||
#include "esp_rom_gpio.h"
|
||||
#define gpio_pad_select_gpio esp_rom_gpio_pad_select_gpio
|
||||
#define gpio_matrix_in(a,b,c) esp_rom_gpio_connect_in_signal(a,b,c)
|
||||
#define gpio_matrix_out(a,b,c,d) esp_rom_gpio_connect_out_signal(a,b,c,d)
|
||||
#define ets_delay_us(a) esp_rom_delay_us(a)
|
||||
#endif
|
||||
|
||||
#if defined ( ARDUINO )
|
||||
extern "C" { // ...someone forgot to put in the includes...
|
||||
#include "esp32-hal.h"
|
||||
#include "esp_intr_alloc.h"
|
||||
#include "esp_ipc.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/periph_ctrl.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "soc/rmt_struct.h"
|
||||
#include <driver/spi_master.h>
|
||||
#include "esp_idf_version.h"
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL( 4, 0, 0 )
|
||||
#include "soc/dport_reg.h"
|
||||
#endif
|
||||
}
|
||||
#elif defined ( ESP_PLATFORM )
|
||||
extern "C" { // ...someone forgot to put in the includes...
|
||||
#include <esp_intr_alloc.h>
|
||||
#include <esp_ipc.h>
|
||||
#include <driver/gpio.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
#include <soc/dport_reg.h>
|
||||
#include <soc/gpio_sig_map.h>
|
||||
#include <soc/rmt_struct.h>
|
||||
#include <driver/spi_master.h>
|
||||
}
|
||||
#include <stdio.h>
|
||||
#endif
|
||||
|
||||
#if (ESP_IDF_VERSION_MAJOR >= 4) && (ESP_IDF_VERSION_MINOR > 1)
|
||||
#include "hal/gpio_ll.h"
|
||||
#else
|
||||
#include "soc/gpio_periph.h"
|
||||
#define esp_rom_delay_us ets_delay_us
|
||||
static inline int gpio_ll_get_level(gpio_dev_t *hw, int gpio_num)
|
||||
{
|
||||
if (gpio_num < 32) {
|
||||
return (hw->in >> gpio_num) & 0x1;
|
||||
} else {
|
||||
return (hw->in1.data >> (gpio_num - 32)) & 0x1;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0))
|
||||
#if !(configENABLE_BACKWARD_COMPATIBILITY == 1)
|
||||
#define xSemaphoreHandle SemaphoreHandle_t
|
||||
#endif
|
||||
#endif
|
||||
#include <driver/gpio.h>
|
||||
#include <driver/spi_master.h>
|
||||
#include <esp_intr_alloc.h>
|
||||
#include <esp_ipc.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
|
||||
#include "Color.h"
|
||||
|
||||
namespace detail {
|
||||
|
||||
struct TimingParams {
|
||||
uint32_t T0H;
|
||||
uint32_t T1H;
|
||||
uint32_t T0L;
|
||||
uint32_t T1L;
|
||||
uint32_t TRS;
|
||||
};
|
||||
|
||||
union RmtPulsePair {
|
||||
struct {
|
||||
int duration0:15;
|
||||
int level0:1;
|
||||
int duration1:15;
|
||||
int level1:1;
|
||||
};
|
||||
uint32_t value;
|
||||
};
|
||||
|
||||
static const int DIVIDER = 4; // 8 still seems to work, but timings become marginal
|
||||
static const int MAX_PULSES = 32; // A channel has a 64 "pulse" buffer - we use half per pass
|
||||
static const double RMT_DURATION_NS = 12.5; // minimum time of a single RMT duration based on clock ns
|
||||
|
||||
} // namespace detail
|
||||
#include "RmtDriver.h"
|
||||
|
||||
using LedType = detail::TimingParams;
|
||||
|
||||
static const LedType LED_WS2812 = { 350, 700, 800, 600, 50000 };
|
||||
static const LedType LED_WS2812B = { 400, 850, 850, 400, 50100 };
|
||||
static const LedType LED_SK6812 = { 300, 600, 900, 600, 80000 };
|
||||
static const LedType LED_WS2813 = { 350, 800, 350, 350, 300000 };
|
||||
// Times are in nanoseconds,
|
||||
// The RMT driver runs at 20MHz, so minimal representable time is 50 nanoseconds
|
||||
static const LedType LED_WS2812 = { 350, 700, 800, 600, 50000 };
|
||||
// longer reset time because https://blog.adafruit.com/2017/05/03/psa-the-ws2812b-rgb-led-has-been-revised-will-require-code-tweak/
|
||||
static const LedType LED_WS2812B = { 400, 800, 850, 450, 300000 }; // universal
|
||||
static const LedType LED_WS2812B_NEWVARIANT = { 200, 750, 750, 200, 300000 };
|
||||
static const LedType LED_WS2812B_OLDVARIANT = { 400, 800, 850, 450, 50000 };
|
||||
// This is timing from datasheet, but does not seem to actually work - try LED_WS2812B
|
||||
static const LedType LED_WS2812C = { 250, 550, 550, 250, 280000 };
|
||||
static const LedType LED_SK6812 = { 300, 600, 900, 600, 80000 };
|
||||
static const LedType LED_WS2813 = { 350, 800, 350, 350, 300000 };
|
||||
|
||||
// Single buffer == can't touch the Rgbs between show() and wait()
|
||||
enum BufferType { SingleBuffer = 0, DoubleBuffer };
|
||||
|
||||
enum IsrCore { CoreFirst = 0, CoreSecond = 1, CoreCurrent = 2};
|
||||
enum IsrCore { CoreFirst = 0, CoreSecond = 1, CoreCurrent = 2 };
|
||||
|
||||
class SmartLed {
|
||||
public:
|
||||
friend class detail::RmtDriver;
|
||||
|
||||
// The RMT interrupt must not run on the same core as WiFi interrupts, otherwise SmartLeds
|
||||
// can't fill the RMT buffer fast enough, resulting in rendering artifacts.
|
||||
// Usually, that means you have to set isrCore == CoreSecond.
|
||||
//
|
||||
// If you use anything other than CoreCurrent, the FreeRTOS scheduler MUST be already running,
|
||||
// so you can't use it if you define SmartLed as global variable.
|
||||
SmartLed( const LedType& type, int count, int pin, int channel = 0, BufferType doubleBuffer = SingleBuffer, IsrCore isrCore = CoreCurrent)
|
||||
: _timing( type ),
|
||||
_channel( channel ),
|
||||
_count( count ),
|
||||
_firstBuffer( new Rgb[ count ] ),
|
||||
_secondBuffer( doubleBuffer ? new Rgb[ count ] : nullptr ),
|
||||
_finishedFlag( xSemaphoreCreateBinary() )
|
||||
{
|
||||
assert( channel >= 0 && channel < 8 );
|
||||
assert( ledForChannel( channel ) == nullptr );
|
||||
//
|
||||
// Does nothing on chips that only have one core.
|
||||
SmartLed(const LedType& type, int count, int pin, int channel = 0, BufferType doubleBuffer = DoubleBuffer,
|
||||
IsrCore isrCore = CoreCurrent)
|
||||
: _finishedFlag(xSemaphoreCreateBinary())
|
||||
, _driver(type, count, pin, channel, _finishedFlag)
|
||||
, _channel(channel)
|
||||
, _count(count)
|
||||
, _firstBuffer(new Rgb[count])
|
||||
, _secondBuffer(doubleBuffer ? new Rgb[count] : nullptr) {
|
||||
assert(channel >= 0 && channel < detail::CHANNEL_COUNT);
|
||||
assert(ledForChannel(channel) == nullptr);
|
||||
|
||||
xSemaphoreGive( _finishedFlag );
|
||||
xSemaphoreGive(_finishedFlag);
|
||||
|
||||
DPORT_SET_PERI_REG_MASK( DPORT_PERIP_CLK_EN_REG, DPORT_RMT_CLK_EN );
|
||||
DPORT_CLEAR_PERI_REG_MASK( DPORT_PERIP_RST_EN_REG, DPORT_RMT_RST );
|
||||
_driver.init();
|
||||
|
||||
PIN_FUNC_SELECT( GPIO_PIN_MUX_REG[ pin ], 2 );
|
||||
gpio_set_direction( static_cast< gpio_num_t >( pin ), GPIO_MODE_OUTPUT );
|
||||
gpio_matrix_out( static_cast< gpio_num_t >( pin ), RMT_SIG_OUT0_IDX + _channel, 0, 0 );
|
||||
initChannel( _channel );
|
||||
|
||||
RMT.tx_lim_ch[ _channel ].limit = detail::MAX_PULSES;
|
||||
RMT.int_ena.val |= 1 << ( 24 + _channel );
|
||||
RMT.int_ena.val |= 1 << ( 3 * _channel );
|
||||
|
||||
_bitToRmt[ 0 ].level0 = 1;
|
||||
_bitToRmt[ 0 ].level1 = 0;
|
||||
_bitToRmt[ 0 ].duration0 = _timing.T0H / ( detail::RMT_DURATION_NS * detail::DIVIDER );
|
||||
_bitToRmt[ 0 ].duration1 = _timing.T0L / ( detail::RMT_DURATION_NS * detail::DIVIDER );
|
||||
|
||||
_bitToRmt[ 1 ].level0 = 1;
|
||||
_bitToRmt[ 1 ].level1 = 0;
|
||||
_bitToRmt[ 1 ].duration0 = _timing.T1H / ( detail::RMT_DURATION_NS * detail::DIVIDER );
|
||||
_bitToRmt[ 1 ].duration1 = _timing.T1L / ( detail::RMT_DURATION_NS * detail::DIVIDER );
|
||||
|
||||
if ( !anyAlive() ) {
|
||||
#if !defined(SOC_CPU_CORES_NUM) || SOC_CPU_CORES_NUM > 1
|
||||
if (!anyAlive() && isrCore != CoreCurrent) {
|
||||
_interruptCore = isrCore;
|
||||
if(isrCore != CoreCurrent) {
|
||||
ESP_ERROR_CHECK(esp_ipc_call_blocking(isrCore, registerInterrupt, NULL));
|
||||
} else {
|
||||
registerInterrupt(NULL);
|
||||
}
|
||||
ESP_ERROR_CHECK(esp_ipc_call_blocking(isrCore, registerInterrupt, (void*)this));
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
registerInterrupt((void*)this);
|
||||
}
|
||||
|
||||
ledForChannel( channel ) = this;
|
||||
ledForChannel(channel) = this;
|
||||
}
|
||||
|
||||
~SmartLed() {
|
||||
ledForChannel( _channel ) = nullptr;
|
||||
if ( !anyAlive() ) {
|
||||
if(_interruptCore != CoreCurrent) {
|
||||
ESP_ERROR_CHECK(esp_ipc_call_blocking(_interruptCore, unregisterInterrupt, NULL));
|
||||
} else {
|
||||
unregisterInterrupt(NULL);
|
||||
}
|
||||
ledForChannel(_channel) = nullptr;
|
||||
#if !defined(SOC_CPU_CORES_NUM) || SOC_CPU_CORES_NUM > 1
|
||||
if (!anyAlive() && _interruptCore != CoreCurrent) {
|
||||
ESP_ERROR_CHECK(esp_ipc_call_blocking(_interruptCore, unregisterInterrupt, (void*)this));
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
unregisterInterrupt((void*)this);
|
||||
}
|
||||
vSemaphoreDelete( _finishedFlag );
|
||||
vSemaphoreDelete(_finishedFlag);
|
||||
}
|
||||
|
||||
Rgb& operator[]( int idx ) {
|
||||
return _firstBuffer[ idx ];
|
||||
}
|
||||
Rgb& operator[](int idx) { return _firstBuffer[idx]; }
|
||||
|
||||
const Rgb& operator[]( int idx ) const {
|
||||
return _firstBuffer[ idx ];
|
||||
}
|
||||
const Rgb& operator[](int idx) const { return _firstBuffer[idx]; }
|
||||
|
||||
void show() {
|
||||
_buffer = _firstBuffer.get();
|
||||
startTransmission();
|
||||
esp_err_t show() {
|
||||
esp_err_t err = startTransmission();
|
||||
swapBuffers();
|
||||
return err;
|
||||
}
|
||||
|
||||
bool wait( TickType_t timeout = portMAX_DELAY ) {
|
||||
if( xSemaphoreTake( _finishedFlag, timeout ) == pdTRUE ) {
|
||||
xSemaphoreGive( _finishedFlag );
|
||||
bool wait(TickType_t timeout = portMAX_DELAY) {
|
||||
if (xSemaphoreTake(_finishedFlag, timeout) == pdTRUE) {
|
||||
xSemaphoreGive(_finishedFlag);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int size() const {
|
||||
return _count;
|
||||
}
|
||||
int size() const { return _count; }
|
||||
int channel() const { return _channel; }
|
||||
|
||||
Rgb *begin() { return _firstBuffer.get(); }
|
||||
const Rgb *begin() const { return _firstBuffer.get(); }
|
||||
const Rgb *cbegin() const { return _firstBuffer.get(); }
|
||||
Rgb* begin() { return _firstBuffer.get(); }
|
||||
const Rgb* begin() const { return _firstBuffer.get(); }
|
||||
const Rgb* cbegin() const { return _firstBuffer.get(); }
|
||||
|
||||
Rgb *end() { return _firstBuffer.get() + _count; }
|
||||
const Rgb *end() const { return _firstBuffer.get() + _count; }
|
||||
const Rgb *cend() const { return _firstBuffer.get() + _count; }
|
||||
Rgb* end() { return _firstBuffer.get() + _count; }
|
||||
const Rgb* end() const { return _firstBuffer.get() + _count; }
|
||||
const Rgb* cend() const { return _firstBuffer.get() + _count; }
|
||||
|
||||
private:
|
||||
static intr_handle_t _interruptHandle;
|
||||
static IsrCore _interruptCore;
|
||||
|
||||
static void initChannel( int channel ) {
|
||||
RMT.apb_conf.fifo_mask = 1; //enable memory access, instead of FIFO mode.
|
||||
RMT.apb_conf.mem_tx_wrap_en = 1; //wrap around when hitting end of buffer
|
||||
RMT.conf_ch[ channel ].conf0.div_cnt = detail::DIVIDER;
|
||||
RMT.conf_ch[ channel ].conf0.mem_size = 1;
|
||||
RMT.conf_ch[ channel ].conf0.carrier_en = 0;
|
||||
RMT.conf_ch[ channel ].conf0.carrier_out_lv = 1;
|
||||
RMT.conf_ch[ channel ].conf0.mem_pd = 0;
|
||||
|
||||
RMT.conf_ch[ channel ].conf1.rx_en = 0;
|
||||
RMT.conf_ch[ channel ].conf1.mem_owner = 0;
|
||||
RMT.conf_ch[ channel ].conf1.tx_conti_mode = 0; //loop back mode.
|
||||
RMT.conf_ch[ channel ].conf1.ref_always_on = 1; // use apb clock: 80M
|
||||
RMT.conf_ch[ channel ].conf1.idle_out_en = 1;
|
||||
RMT.conf_ch[ channel ].conf1.idle_out_lv = 0;
|
||||
static void registerInterrupt(void* selfVoid) {
|
||||
auto* self = (SmartLed*)selfVoid;
|
||||
ESP_ERROR_CHECK(self->_driver.registerIsr(!anyAlive()));
|
||||
}
|
||||
|
||||
static void registerInterrupt(void *) {
|
||||
ESP_ERROR_CHECK(esp_intr_alloc( ETS_RMT_INTR_SOURCE, 0, interruptHandler, nullptr, &_interruptHandle));
|
||||
static void unregisterInterrupt(void* selfVoid) {
|
||||
auto* self = (SmartLed*)selfVoid;
|
||||
ESP_ERROR_CHECK(self->_driver.unregisterIsr());
|
||||
}
|
||||
|
||||
static void unregisterInterrupt(void*) {
|
||||
esp_intr_free( _interruptHandle );
|
||||
}
|
||||
|
||||
static SmartLed*& IRAM_ATTR ledForChannel( int channel );
|
||||
static void IRAM_ATTR interruptHandler( void* );
|
||||
|
||||
void IRAM_ATTR copyRmtHalfBlock();
|
||||
|
||||
void swapBuffers() {
|
||||
if ( _secondBuffer )
|
||||
_firstBuffer.swap( _secondBuffer );
|
||||
}
|
||||
|
||||
void startTransmission() {
|
||||
// Invalid use of the library
|
||||
if( xSemaphoreTake( _finishedFlag, 0 ) != pdTRUE )
|
||||
abort();
|
||||
|
||||
_pixelPosition = _componentPosition = _halfIdx = 0;
|
||||
copyRmtHalfBlock();
|
||||
if ( _pixelPosition < _count )
|
||||
copyRmtHalfBlock();
|
||||
|
||||
RMT.conf_ch[ _channel ].conf1.mem_rd_rst = 1;
|
||||
RMT.conf_ch[ _channel ].conf1.tx_start = 1;
|
||||
}
|
||||
static SmartLed*& IRAM_ATTR ledForChannel(int channel);
|
||||
|
||||
static bool anyAlive() {
|
||||
for ( int i = 0; i != 8; i++ )
|
||||
if ( ledForChannel( i ) != nullptr ) return true;
|
||||
for (int i = 0; i != detail::CHANNEL_COUNT; i++)
|
||||
if (ledForChannel(i) != nullptr)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
const LedType& _timing;
|
||||
void swapBuffers() {
|
||||
if (_secondBuffer)
|
||||
_firstBuffer.swap(_secondBuffer);
|
||||
}
|
||||
|
||||
esp_err_t startTransmission() {
|
||||
// Invalid use of the library, you must wait() fir previous frame to get processed first
|
||||
if (xSemaphoreTake(_finishedFlag, 0) != pdTRUE)
|
||||
abort();
|
||||
|
||||
auto err = _driver.transmit(_firstBuffer.get());
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
SemaphoreHandle_t _finishedFlag;
|
||||
detail::RmtDriver _driver;
|
||||
int _channel;
|
||||
detail::RmtPulsePair _bitToRmt[ 2 ];
|
||||
int _count;
|
||||
std::unique_ptr< Rgb[] > _firstBuffer;
|
||||
std::unique_ptr< Rgb[] > _secondBuffer;
|
||||
Rgb *_buffer;
|
||||
|
||||
xSemaphoreHandle _finishedFlag;
|
||||
|
||||
int _pixelPosition;
|
||||
int _componentPosition;
|
||||
int _halfIdx;
|
||||
std::unique_ptr<Rgb[]> _firstBuffer;
|
||||
std::unique_ptr<Rgb[]> _secondBuffer;
|
||||
};
|
||||
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
#define _SMARTLEDS_SPI_HOST SPI2_HOST
|
||||
#define _SMARTLEDS_SPI_DMA_CHAN SPI_DMA_CH_AUTO
|
||||
#else
|
||||
#define _SMARTLEDS_SPI_HOST HSPI_HOST
|
||||
#define _SMARTLEDS_SPI_DMA_CHAN 1
|
||||
#endif
|
||||
|
||||
class Apa102 {
|
||||
public:
|
||||
struct ApaRgb {
|
||||
ApaRgb( uint8_t r = 0, uint8_t g = 0, uint32_t b = 0, uint32_t v = 0xFF )
|
||||
: v( 0xE0 | v ), b( b ), g( g ), r( r )
|
||||
{}
|
||||
ApaRgb(uint8_t r = 0, uint8_t g = 0, uint32_t b = 0, uint32_t v = 0xFF)
|
||||
: v(0xE0 | v)
|
||||
, b(b)
|
||||
, g(g)
|
||||
, r(r) {}
|
||||
|
||||
ApaRgb& operator=( const Rgb& o ) {
|
||||
ApaRgb& operator=(const Rgb& o) {
|
||||
r = o.r;
|
||||
g = o.g;
|
||||
b = o.b;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ApaRgb& operator=( const Hsv& o ) {
|
||||
*this = Rgb{ o };
|
||||
ApaRgb& operator=(const Hsv& o) {
|
||||
*this = Rgb { o };
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -339,14 +253,14 @@ public:
|
||||
static const int FINAL_FRAME_SIZE = 4;
|
||||
static const int TRANS_COUNT = 2 + 8;
|
||||
|
||||
Apa102( int count, int clkpin, int datapin, BufferType doubleBuffer = SingleBuffer )
|
||||
: _count( count ),
|
||||
_firstBuffer( new ApaRgb[ count ] ),
|
||||
_secondBuffer( doubleBuffer ? new ApaRgb[ count ] : nullptr ),
|
||||
_initFrame( 0 )
|
||||
{
|
||||
Apa102(int count, int clkpin, int datapin, BufferType doubleBuffer = SingleBuffer, int clock_speed_hz = 1000000)
|
||||
: _count(count)
|
||||
, _firstBuffer(new ApaRgb[count])
|
||||
, _secondBuffer(doubleBuffer ? new ApaRgb[count] : nullptr)
|
||||
, _transCount(0)
|
||||
, _initFrame(0) {
|
||||
spi_bus_config_t buscfg;
|
||||
memset( &buscfg, 0, sizeof( buscfg ) );
|
||||
memset(&buscfg, 0, sizeof(buscfg));
|
||||
buscfg.mosi_io_num = datapin;
|
||||
buscfg.miso_io_num = -1;
|
||||
buscfg.sclk_io_num = clkpin;
|
||||
@@ -355,33 +269,29 @@ public:
|
||||
buscfg.max_transfer_sz = 65535;
|
||||
|
||||
spi_device_interface_config_t devcfg;
|
||||
memset( &devcfg, 0, sizeof( devcfg ) );
|
||||
devcfg.clock_speed_hz = 1000000;
|
||||
memset(&devcfg, 0, sizeof(devcfg));
|
||||
devcfg.clock_speed_hz = clock_speed_hz;
|
||||
devcfg.mode = 0;
|
||||
devcfg.spics_io_num = -1;
|
||||
devcfg.queue_size = TRANS_COUNT;
|
||||
devcfg.pre_cb = nullptr;
|
||||
|
||||
auto ret = spi_bus_initialize( HSPI_HOST, &buscfg, 1 );
|
||||
assert( ret == ESP_OK );
|
||||
auto ret = spi_bus_initialize(_SMARTLEDS_SPI_HOST, &buscfg, _SMARTLEDS_SPI_DMA_CHAN);
|
||||
assert(ret == ESP_OK);
|
||||
|
||||
ret = spi_bus_add_device( HSPI_HOST, &devcfg, &_spi );
|
||||
assert( ret == ESP_OK );
|
||||
ret = spi_bus_add_device(_SMARTLEDS_SPI_HOST, &devcfg, &_spi);
|
||||
assert(ret == ESP_OK);
|
||||
|
||||
std::fill_n( _finalFrame, FINAL_FRAME_SIZE, 0xFFFFFFFF );
|
||||
std::fill_n(_finalFrame, FINAL_FRAME_SIZE, 0xFFFFFFFF);
|
||||
}
|
||||
|
||||
~Apa102() {
|
||||
// ToDo
|
||||
}
|
||||
|
||||
ApaRgb& operator[]( int idx ) {
|
||||
return _firstBuffer[ idx ];
|
||||
}
|
||||
ApaRgb& operator[](int idx) { return _firstBuffer[idx]; }
|
||||
|
||||
const ApaRgb& operator[]( int idx ) const {
|
||||
return _firstBuffer[ idx ];
|
||||
}
|
||||
const ApaRgb& operator[](int idx) const { return _firstBuffer[idx]; }
|
||||
|
||||
void show() {
|
||||
_buffer = _firstBuffer.get();
|
||||
@@ -390,93 +300,95 @@ public:
|
||||
}
|
||||
|
||||
void wait() {
|
||||
for ( int i = 0; i != _transCount; i++ ) {
|
||||
spi_transaction_t *t;
|
||||
spi_device_get_trans_result( _spi, &t, portMAX_DELAY );
|
||||
for (int i = 0; i != _transCount; i++) {
|
||||
spi_transaction_t* t;
|
||||
spi_device_get_trans_result(_spi, &t, portMAX_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void swapBuffers() {
|
||||
if ( _secondBuffer )
|
||||
_firstBuffer.swap( _secondBuffer );
|
||||
if (_secondBuffer)
|
||||
_firstBuffer.swap(_secondBuffer);
|
||||
}
|
||||
|
||||
void startTransmission() {
|
||||
for ( int i = 0; i != TRANS_COUNT; i++ ) {
|
||||
_transactions[ i ].cmd = 0;
|
||||
_transactions[ i ].addr = 0;
|
||||
_transactions[ i ].flags = 0;
|
||||
_transactions[ i ].rxlength = 0;
|
||||
_transactions[ i ].rx_buffer = nullptr;
|
||||
for (int i = 0; i != TRANS_COUNT; i++) {
|
||||
_transactions[i].cmd = 0;
|
||||
_transactions[i].addr = 0;
|
||||
_transactions[i].flags = 0;
|
||||
_transactions[i].rxlength = 0;
|
||||
_transactions[i].rx_buffer = nullptr;
|
||||
}
|
||||
// Init frame
|
||||
_transactions[ 0 ].length = 32;
|
||||
_transactions[ 0 ].tx_buffer = &_initFrame;
|
||||
spi_device_queue_trans( _spi, _transactions + 0, portMAX_DELAY );
|
||||
_transactions[0].length = 32;
|
||||
_transactions[0].tx_buffer = &_initFrame;
|
||||
spi_device_queue_trans(_spi, _transactions + 0, portMAX_DELAY);
|
||||
// Data
|
||||
_transactions[ 1 ].length = 32 * _count;
|
||||
_transactions[ 1 ].tx_buffer = _buffer;
|
||||
spi_device_queue_trans( _spi, _transactions + 1, portMAX_DELAY );
|
||||
_transactions[1].length = 32 * _count;
|
||||
_transactions[1].tx_buffer = _buffer;
|
||||
spi_device_queue_trans(_spi, _transactions + 1, portMAX_DELAY);
|
||||
_transCount = 2;
|
||||
// End frame
|
||||
for ( int i = 0; i != 1 + _count / 32 / FINAL_FRAME_SIZE; i++ ) {
|
||||
_transactions[ 2 + i ].length = 32 * FINAL_FRAME_SIZE;
|
||||
_transactions[ 2 + i ].tx_buffer = _finalFrame;
|
||||
spi_device_queue_trans( _spi, _transactions + 2 + i, portMAX_DELAY );
|
||||
for (int i = 0; i != 1 + _count / 32 / FINAL_FRAME_SIZE; i++) {
|
||||
_transactions[2 + i].length = 32 * FINAL_FRAME_SIZE;
|
||||
_transactions[2 + i].tx_buffer = _finalFrame;
|
||||
spi_device_queue_trans(_spi, _transactions + 2 + i, portMAX_DELAY);
|
||||
_transCount++;
|
||||
}
|
||||
}
|
||||
|
||||
spi_device_handle_t _spi;
|
||||
int _count;
|
||||
std::unique_ptr< ApaRgb[] > _firstBuffer, _secondBuffer;
|
||||
ApaRgb *_buffer;
|
||||
std::unique_ptr<ApaRgb[]> _firstBuffer, _secondBuffer;
|
||||
ApaRgb* _buffer;
|
||||
|
||||
spi_transaction_t _transactions[ TRANS_COUNT ];
|
||||
spi_transaction_t _transactions[TRANS_COUNT];
|
||||
int _transCount;
|
||||
|
||||
uint32_t _initFrame;
|
||||
uint32_t _finalFrame[ FINAL_FRAME_SIZE ];
|
||||
uint32_t _finalFrame[FINAL_FRAME_SIZE];
|
||||
};
|
||||
|
||||
class LDP8806 {
|
||||
public:
|
||||
struct LDP8806_GRB {
|
||||
|
||||
LDP8806_GRB( uint8_t g_7bit = 0, uint8_t r_7bit = 0, uint32_t b_7bit = 0 )
|
||||
: g( g_7bit ), r( r_7bit ), b( b_7bit )
|
||||
{
|
||||
}
|
||||
LDP8806_GRB(uint8_t g_7bit = 0, uint8_t r_7bit = 0, uint32_t b_7bit = 0)
|
||||
: g(g_7bit)
|
||||
, r(r_7bit)
|
||||
, b(b_7bit) {}
|
||||
|
||||
LDP8806_GRB& operator=( const Rgb& o ) {
|
||||
LDP8806_GRB& operator=(const Rgb& o) {
|
||||
//Convert 8->7bit colour
|
||||
r = ( o.r * 127 / 256 ) | 0x80;
|
||||
g = ( o.g * 127 / 256 ) | 0x80;
|
||||
b = ( o.b * 127 / 256 ) | 0x80;
|
||||
r = (o.r * 127 / 256) | 0x80;
|
||||
g = (o.g * 127 / 256) | 0x80;
|
||||
b = (o.b * 127 / 256) | 0x80;
|
||||
return *this;
|
||||
}
|
||||
|
||||
LDP8806_GRB& operator=( const Hsv& o ) {
|
||||
*this = Rgb{ o };
|
||||
LDP8806_GRB& operator=(const Hsv& o) {
|
||||
*this = Rgb { o };
|
||||
return *this;
|
||||
}
|
||||
|
||||
uint8_t g, r, b;
|
||||
};
|
||||
|
||||
static const int LED_FRAME_SIZE_BYTES = sizeof( LDP8806_GRB );
|
||||
static const int LED_FRAME_SIZE_BYTES = sizeof(LDP8806_GRB);
|
||||
static const int LATCH_FRAME_SIZE_BYTES = 3;
|
||||
static const int TRANS_COUNT_MAX = 20;//Arbitrary, supports up to 600 LED
|
||||
static const int TRANS_COUNT_MAX = 20; //Arbitrary, supports up to 600 LED
|
||||
|
||||
LDP8806( int count, int clkpin, int datapin, BufferType doubleBuffer = SingleBuffer, uint32_t clock_speed_hz = 2000000 )
|
||||
: _count( count ),
|
||||
_firstBuffer( new LDP8806_GRB[ count ] ),
|
||||
_secondBuffer( doubleBuffer ? new LDP8806_GRB[ count ] : nullptr ),
|
||||
// one 'latch'/start-of-data mark frame for every 32 leds
|
||||
_latchFrames( ( count + 31 ) / 32 )
|
||||
{
|
||||
LDP8806(
|
||||
int count, int clkpin, int datapin, BufferType doubleBuffer = SingleBuffer, uint32_t clock_speed_hz = 2000000)
|
||||
: _count(count)
|
||||
, _firstBuffer(new LDP8806_GRB[count])
|
||||
, _secondBuffer(doubleBuffer ? new LDP8806_GRB[count] : nullptr)
|
||||
,
|
||||
// one 'latch'/start-of-data mark frame for every 32 leds
|
||||
_latchFrames((count + 31) / 32) {
|
||||
spi_bus_config_t buscfg;
|
||||
memset( &buscfg, 0, sizeof( buscfg ) );
|
||||
memset(&buscfg, 0, sizeof(buscfg));
|
||||
buscfg.mosi_io_num = datapin;
|
||||
buscfg.miso_io_num = -1;
|
||||
buscfg.sclk_io_num = clkpin;
|
||||
@@ -485,33 +397,29 @@ public:
|
||||
buscfg.max_transfer_sz = 65535;
|
||||
|
||||
spi_device_interface_config_t devcfg;
|
||||
memset( &devcfg, 0, sizeof( devcfg ) );
|
||||
memset(&devcfg, 0, sizeof(devcfg));
|
||||
devcfg.clock_speed_hz = clock_speed_hz;
|
||||
devcfg.mode = 0;
|
||||
devcfg.spics_io_num = -1;
|
||||
devcfg.queue_size = TRANS_COUNT_MAX;
|
||||
devcfg.pre_cb = nullptr;
|
||||
|
||||
auto ret = spi_bus_initialize( HSPI_HOST, &buscfg, 1 );
|
||||
assert( ret == ESP_OK );
|
||||
auto ret = spi_bus_initialize(_SMARTLEDS_SPI_HOST, &buscfg, _SMARTLEDS_SPI_DMA_CHAN);
|
||||
assert(ret == ESP_OK);
|
||||
|
||||
ret = spi_bus_add_device( HSPI_HOST, &devcfg, &_spi );
|
||||
assert( ret == ESP_OK );
|
||||
ret = spi_bus_add_device(_SMARTLEDS_SPI_HOST, &devcfg, &_spi);
|
||||
assert(ret == ESP_OK);
|
||||
|
||||
std::fill_n( _latchBuffer, LATCH_FRAME_SIZE_BYTES, 0x0 );
|
||||
std::fill_n(_latchBuffer, LATCH_FRAME_SIZE_BYTES, 0x0);
|
||||
}
|
||||
|
||||
~LDP8806() {
|
||||
// noop
|
||||
}
|
||||
|
||||
LDP8806_GRB& operator[]( int idx ) {
|
||||
return _firstBuffer[ idx ];
|
||||
}
|
||||
LDP8806_GRB& operator[](int idx) { return _firstBuffer[idx]; }
|
||||
|
||||
const LDP8806_GRB& operator[]( int idx ) const {
|
||||
return _firstBuffer[ idx ];
|
||||
}
|
||||
const LDP8806_GRB& operator[](int idx) const { return _firstBuffer[idx]; }
|
||||
|
||||
void show() {
|
||||
_buffer = _firstBuffer.get();
|
||||
@@ -520,51 +428,50 @@ public:
|
||||
}
|
||||
|
||||
void wait() {
|
||||
while ( _transCount-- ) {
|
||||
spi_transaction_t *t;
|
||||
spi_device_get_trans_result( _spi, &t, portMAX_DELAY );
|
||||
while (_transCount--) {
|
||||
spi_transaction_t* t;
|
||||
spi_device_get_trans_result(_spi, &t, portMAX_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void swapBuffers() {
|
||||
if ( _secondBuffer )
|
||||
_firstBuffer.swap( _secondBuffer );
|
||||
if (_secondBuffer)
|
||||
_firstBuffer.swap(_secondBuffer);
|
||||
}
|
||||
|
||||
void startTransmission() {
|
||||
_transCount = 0;
|
||||
for ( int i = 0; i != TRANS_COUNT_MAX; i++ ) {
|
||||
_transactions[ i ].cmd = 0;
|
||||
_transactions[ i ].addr = 0;
|
||||
_transactions[ i ].flags = 0;
|
||||
_transactions[ i ].rxlength = 0;
|
||||
_transactions[ i ].rx_buffer = nullptr;
|
||||
for (int i = 0; i != TRANS_COUNT_MAX; i++) {
|
||||
_transactions[i].cmd = 0;
|
||||
_transactions[i].addr = 0;
|
||||
_transactions[i].flags = 0;
|
||||
_transactions[i].rxlength = 0;
|
||||
_transactions[i].rx_buffer = nullptr;
|
||||
}
|
||||
// LED Data
|
||||
_transactions[ 0 ].length = ( LED_FRAME_SIZE_BYTES * 8 ) * _count;
|
||||
_transactions[ 0 ].tx_buffer = _buffer;
|
||||
spi_device_queue_trans( _spi, _transactions + _transCount, portMAX_DELAY );
|
||||
_transactions[0].length = (LED_FRAME_SIZE_BYTES * 8) * _count;
|
||||
_transactions[0].tx_buffer = _buffer;
|
||||
spi_device_queue_trans(_spi, _transactions + _transCount, portMAX_DELAY);
|
||||
_transCount++;
|
||||
|
||||
// 'latch'/start-of-data marker frames
|
||||
for ( int i = 0; i < _latchFrames; i++ ) {
|
||||
_transactions[ _transCount ].length = ( LATCH_FRAME_SIZE_BYTES * 8 );
|
||||
_transactions[ _transCount ].tx_buffer = _latchBuffer;
|
||||
spi_device_queue_trans( _spi, _transactions + _transCount, portMAX_DELAY );
|
||||
for (int i = 0; i < _latchFrames; i++) {
|
||||
_transactions[_transCount].length = (LATCH_FRAME_SIZE_BYTES * 8);
|
||||
_transactions[_transCount].tx_buffer = _latchBuffer;
|
||||
spi_device_queue_trans(_spi, _transactions + _transCount, portMAX_DELAY);
|
||||
_transCount++;
|
||||
}
|
||||
}
|
||||
|
||||
spi_device_handle_t _spi;
|
||||
int _count;
|
||||
std::unique_ptr< LDP8806_GRB[] > _firstBuffer, _secondBuffer;
|
||||
LDP8806_GRB *_buffer;
|
||||
std::unique_ptr<LDP8806_GRB[]> _firstBuffer, _secondBuffer;
|
||||
LDP8806_GRB* _buffer;
|
||||
|
||||
spi_transaction_t _transactions[ TRANS_COUNT_MAX ];
|
||||
spi_transaction_t _transactions[TRANS_COUNT_MAX];
|
||||
int _transCount;
|
||||
|
||||
int _latchFrames;
|
||||
uint8_t _latchBuffer[ LATCH_FRAME_SIZE_BYTES ];
|
||||
uint8_t _latchBuffer[LATCH_FRAME_SIZE_BYTES];
|
||||
};
|
||||
|
||||
#endif //SMARTLEDS_H
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "server_mqtt.h"
|
||||
#endif //ENABLE_MQTT
|
||||
|
||||
#include "basic_auth.h"
|
||||
|
||||
static const char *TAG = "GPIO";
|
||||
QueueHandle_t gpio_queue_handle = NULL;
|
||||
@@ -458,7 +459,7 @@ void GpioHandler::registerGpioUri()
|
||||
httpd_uri_t camuri = { };
|
||||
camuri.method = HTTP_GET;
|
||||
camuri.uri = "/GPIO";
|
||||
camuri.handler = callHandleHttpRequest;
|
||||
camuri.handler = APPLY_BASIC_AUTH_FILTER(callHandleHttpRequest);
|
||||
camuri.user_ctx = (void*)this;
|
||||
httpd_register_uri_handler(_httpServer, &camuri);
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ static camera_config_t camera_config = {
|
||||
.pixel_format = PIXFORMAT_JPEG, // YUV422,GRAYSCALE,RGB565,JPEG
|
||||
.frame_size = FRAMESIZE_VGA, // QQVGA-UXGA Do not use sizes above QVGA when not JPEG
|
||||
// .frame_size = FRAMESIZE_UXGA, //QQVGA-UXGA Do not use sizes above QVGA when not JPEG
|
||||
.jpeg_quality = 6, // 0-63 lower number means higher quality
|
||||
.jpeg_quality = 12, // 0-63 lower number means higher quality
|
||||
.fb_count = 1, // if more than one, i2s runs in continuous mode. Use only with JPEG
|
||||
.fb_location = CAMERA_FB_IN_PSRAM, /*!< The location where the frame buffer will be allocated */
|
||||
.grab_mode = CAMERA_GRAB_LATEST, // only from new esp32cam version
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
#include "ClassLogFile.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "basic_auth.h"
|
||||
|
||||
#include "../../include/defines.h"
|
||||
|
||||
static const char *TAG = "server_cam";
|
||||
@@ -280,27 +282,27 @@ void register_server_camera_uri(httpd_handle_t server)
|
||||
camuri.method = HTTP_GET;
|
||||
|
||||
camuri.uri = "/lighton";
|
||||
camuri.handler = handler_lightOn;
|
||||
camuri.handler = APPLY_BASIC_AUTH_FILTER(handler_lightOn);
|
||||
camuri.user_ctx = (void *)"Light On";
|
||||
httpd_register_uri_handler(server, &camuri);
|
||||
|
||||
camuri.uri = "/lightoff";
|
||||
camuri.handler = handler_lightOff;
|
||||
camuri.handler = APPLY_BASIC_AUTH_FILTER(handler_lightOff);
|
||||
camuri.user_ctx = (void *)"Light Off";
|
||||
httpd_register_uri_handler(server, &camuri);
|
||||
|
||||
camuri.uri = "/capture";
|
||||
camuri.handler = handler_capture;
|
||||
camuri.handler = APPLY_BASIC_AUTH_FILTER(handler_capture);
|
||||
camuri.user_ctx = NULL;
|
||||
httpd_register_uri_handler(server, &camuri);
|
||||
|
||||
camuri.uri = "/capture_with_flashlight";
|
||||
camuri.handler = handler_capture_with_light;
|
||||
camuri.handler = APPLY_BASIC_AUTH_FILTER(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.handler = APPLY_BASIC_AUTH_FILTER(handler_capture_save_to_file);
|
||||
camuri.user_ctx = NULL;
|
||||
httpd_register_uri_handler(server, &camuri);
|
||||
}
|
||||
|
||||
226
code/components/jomjol_fileserver_ota/md5.cpp
Normal file
226
code/components/jomjol_fileserver_ota/md5.cpp
Normal file
@@ -0,0 +1,226 @@
|
||||
/* Src: https://github.com/Zunawe/md5-c, commit: f3529b6
|
||||
* License: Unlicense */
|
||||
/*
|
||||
* Derived from the RSA Data Security, Inc. MD5 Message-Digest Algorithm
|
||||
* and modified slightly to be functionally identical but condensed into control structures.
|
||||
*/
|
||||
|
||||
#include "md5.h"
|
||||
|
||||
/*
|
||||
* Constants defined by the MD5 algorithm
|
||||
*/
|
||||
#define A 0x67452301
|
||||
#define B 0xefcdab89
|
||||
#define C 0x98badcfe
|
||||
#define D 0x10325476
|
||||
|
||||
static uint32_t S[] = {7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
|
||||
5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
|
||||
4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
|
||||
6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21};
|
||||
|
||||
static uint32_t K[] = {0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee,
|
||||
0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
|
||||
0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
|
||||
0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
|
||||
0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa,
|
||||
0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
|
||||
0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,
|
||||
0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
|
||||
0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
|
||||
0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
|
||||
0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05,
|
||||
0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
|
||||
0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039,
|
||||
0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
|
||||
0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
|
||||
0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391};
|
||||
|
||||
/*
|
||||
* Padding used to make the size (in bits) of the input congruent to 448 mod 512
|
||||
*/
|
||||
static uint8_t PADDING[] = {0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
/*
|
||||
* Bit-manipulation functions defined by the MD5 algorithm
|
||||
*/
|
||||
#define F(X, Y, Z) ((X & Y) | (~X & Z))
|
||||
#define G(X, Y, Z) ((X & Z) | (Y & ~Z))
|
||||
#define H(X, Y, Z) (X ^ Y ^ Z)
|
||||
#define I(X, Y, Z) (Y ^ (X | ~Z))
|
||||
|
||||
/*
|
||||
* Rotates a 32-bit word left by n bits
|
||||
*/
|
||||
uint32_t rotateLeft(uint32_t x, uint32_t n){
|
||||
return (x << n) | (x >> (32 - n));
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Initialize a context
|
||||
*/
|
||||
void md5Init(MD5Context *ctx){
|
||||
ctx->size = (uint64_t)0;
|
||||
|
||||
ctx->buffer[0] = (uint32_t)A;
|
||||
ctx->buffer[1] = (uint32_t)B;
|
||||
ctx->buffer[2] = (uint32_t)C;
|
||||
ctx->buffer[3] = (uint32_t)D;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add some amount of input to the context
|
||||
*
|
||||
* If the input fills out a block of 512 bits, apply the algorithm (md5Step)
|
||||
* and save the result in the buffer. Also updates the overall size.
|
||||
*/
|
||||
void md5Update(MD5Context *ctx, uint8_t *input_buffer, size_t input_len){
|
||||
uint32_t input[16];
|
||||
unsigned int offset = ctx->size % 64;
|
||||
ctx->size += (uint64_t)input_len;
|
||||
|
||||
// Copy each byte in input_buffer into the next space in our context input
|
||||
for(unsigned int i = 0; i < input_len; ++i){
|
||||
ctx->input[offset++] = (uint8_t)*(input_buffer + i);
|
||||
|
||||
// If we've filled our context input, copy it into our local array input
|
||||
// then reset the offset to 0 and fill in a new buffer.
|
||||
// Every time we fill out a chunk, we run it through the algorithm
|
||||
// to enable some back and forth between cpu and i/o
|
||||
if(offset % 64 == 0){
|
||||
for(unsigned int j = 0; j < 16; ++j){
|
||||
// Convert to little-endian
|
||||
// The local variable `input` our 512-bit chunk separated into 32-bit words
|
||||
// we can use in calculations
|
||||
input[j] = (uint32_t)(ctx->input[(j * 4) + 3]) << 24 |
|
||||
(uint32_t)(ctx->input[(j * 4) + 2]) << 16 |
|
||||
(uint32_t)(ctx->input[(j * 4) + 1]) << 8 |
|
||||
(uint32_t)(ctx->input[(j * 4)]);
|
||||
}
|
||||
md5Step(ctx->buffer, input);
|
||||
offset = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Pad the current input to get to 448 bytes, append the size in bits to the very end,
|
||||
* and save the result of the final iteration into digest.
|
||||
*/
|
||||
void md5Finalize(MD5Context *ctx){
|
||||
uint32_t input[16];
|
||||
unsigned int offset = ctx->size % 64;
|
||||
unsigned int padding_length = offset < 56 ? 56 - offset : (56 + 64) - offset;
|
||||
|
||||
// Fill in the padding and undo the changes to size that resulted from the update
|
||||
md5Update(ctx, PADDING, padding_length);
|
||||
ctx->size -= (uint64_t)padding_length;
|
||||
|
||||
// Do a final update (internal to this function)
|
||||
// Last two 32-bit words are the two halves of the size (converted from bytes to bits)
|
||||
for(unsigned int j = 0; j < 14; ++j){
|
||||
input[j] = (uint32_t)(ctx->input[(j * 4) + 3]) << 24 |
|
||||
(uint32_t)(ctx->input[(j * 4) + 2]) << 16 |
|
||||
(uint32_t)(ctx->input[(j * 4) + 1]) << 8 |
|
||||
(uint32_t)(ctx->input[(j * 4)]);
|
||||
}
|
||||
input[14] = (uint32_t)(ctx->size * 8);
|
||||
input[15] = (uint32_t)((ctx->size * 8) >> 32);
|
||||
|
||||
md5Step(ctx->buffer, input);
|
||||
|
||||
// Move the result into digest (convert from little-endian)
|
||||
for(unsigned int i = 0; i < 4; ++i){
|
||||
ctx->digest[(i * 4) + 0] = (uint8_t)((ctx->buffer[i] & 0x000000FF));
|
||||
ctx->digest[(i * 4) + 1] = (uint8_t)((ctx->buffer[i] & 0x0000FF00) >> 8);
|
||||
ctx->digest[(i * 4) + 2] = (uint8_t)((ctx->buffer[i] & 0x00FF0000) >> 16);
|
||||
ctx->digest[(i * 4) + 3] = (uint8_t)((ctx->buffer[i] & 0xFF000000) >> 24);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Step on 512 bits of input with the main MD5 algorithm.
|
||||
*/
|
||||
void md5Step(uint32_t *buffer, uint32_t *input){
|
||||
uint32_t AA = buffer[0];
|
||||
uint32_t BB = buffer[1];
|
||||
uint32_t CC = buffer[2];
|
||||
uint32_t DD = buffer[3];
|
||||
|
||||
uint32_t E;
|
||||
|
||||
unsigned int j;
|
||||
|
||||
for(unsigned int i = 0; i < 64; ++i){
|
||||
switch(i / 16){
|
||||
case 0:
|
||||
E = F(BB, CC, DD);
|
||||
j = i;
|
||||
break;
|
||||
case 1:
|
||||
E = G(BB, CC, DD);
|
||||
j = ((i * 5) + 1) % 16;
|
||||
break;
|
||||
case 2:
|
||||
E = H(BB, CC, DD);
|
||||
j = ((i * 3) + 5) % 16;
|
||||
break;
|
||||
default:
|
||||
E = I(BB, CC, DD);
|
||||
j = (i * 7) % 16;
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t temp = DD;
|
||||
DD = CC;
|
||||
CC = BB;
|
||||
BB = BB + rotateLeft(AA + E + K[i] + input[j], S[i]);
|
||||
AA = temp;
|
||||
}
|
||||
|
||||
buffer[0] += AA;
|
||||
buffer[1] += BB;
|
||||
buffer[2] += CC;
|
||||
buffer[3] += DD;
|
||||
}
|
||||
|
||||
/*
|
||||
* Functions that run the algorithm on the provided input and put the digest into result.
|
||||
* result should be able to store 16 bytes.
|
||||
*/
|
||||
void md5String(char *input, uint8_t *result){
|
||||
MD5Context ctx;
|
||||
md5Init(&ctx);
|
||||
md5Update(&ctx, (uint8_t *)input, strlen(input));
|
||||
md5Finalize(&ctx);
|
||||
|
||||
memcpy(result, ctx.digest, 16);
|
||||
}
|
||||
|
||||
|
||||
void md5File(FILE *file, uint8_t *result){
|
||||
void *input_buffer = malloc(1024);
|
||||
size_t input_size = 0;
|
||||
|
||||
MD5Context ctx;
|
||||
md5Init(&ctx);
|
||||
|
||||
while((input_size = fread(input_buffer, 1, 1024, file)) > 0){
|
||||
md5Update(&ctx, (uint8_t *)input_buffer, input_size);
|
||||
}
|
||||
|
||||
md5Finalize(&ctx);
|
||||
|
||||
free(input_buffer);
|
||||
|
||||
memcpy(result, ctx.digest, 16);
|
||||
}
|
||||
28
code/components/jomjol_fileserver_ota/md5.h
Normal file
28
code/components/jomjol_fileserver_ota/md5.h
Normal file
@@ -0,0 +1,28 @@
|
||||
/* Src: https://github.com/Zunawe/md5-c, commit: f3529b6
|
||||
* License: Unlicense */
|
||||
#pragma once
|
||||
|
||||
#ifndef MD5_H
|
||||
#define MD5_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef struct{
|
||||
uint64_t size; // Size of input in bytes
|
||||
uint32_t buffer[4]; // Current accumulation of hash
|
||||
uint8_t input[64]; // Input to be used in the next step
|
||||
uint8_t digest[16]; // Result of algorithm
|
||||
}MD5Context;
|
||||
|
||||
void md5Init(MD5Context *ctx);
|
||||
void md5Update(MD5Context *ctx, uint8_t *input, size_t input_len);
|
||||
void md5Finalize(MD5Context *ctx);
|
||||
void md5Step(uint32_t *buffer, uint32_t *input);
|
||||
|
||||
void md5String(char *input, uint8_t *result);
|
||||
void md5File(FILE *file, uint8_t *result);
|
||||
|
||||
#endif // MD5_H
|
||||
@@ -6,11 +6,8 @@
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
|
||||
#include "server_file.h"
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
@@ -39,6 +36,7 @@ extern "C" {
|
||||
#include "MainFlowControl.h"
|
||||
|
||||
#include "server_help.h"
|
||||
#include "md5.h"
|
||||
#ifdef ENABLE_MQTT
|
||||
#include "interface_mqtt.h"
|
||||
#endif //ENABLE_MQTT
|
||||
@@ -46,6 +44,7 @@ extern "C" {
|
||||
|
||||
#include "Helper.h"
|
||||
#include "miniz.h"
|
||||
#include "basic_auth.h"
|
||||
|
||||
static const char *TAG = "OTA FILE";
|
||||
|
||||
@@ -57,20 +56,17 @@ struct file_server_data {
|
||||
char scratch[SERVER_FILER_SCRATCH_BUFSIZE];
|
||||
};
|
||||
|
||||
|
||||
#include <iostream>
|
||||
#include <sys/types.h>
|
||||
#include <dirent.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
string SUFFIX_ZW = "_0xge";
|
||||
|
||||
string SUFFIX_ZW = "_tmp";
|
||||
|
||||
static esp_err_t send_logfile(httpd_req_t *req, bool send_full_file);
|
||||
static esp_err_t send_datafile(httpd_req_t *req, bool send_full_file);
|
||||
|
||||
|
||||
esp_err_t get_numbers_file_handler(httpd_req_t *req)
|
||||
{
|
||||
std::string ret = flowctrl.getNumbersName();
|
||||
@@ -86,7 +82,6 @@ esp_err_t get_numbers_file_handler(httpd_req_t *req)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
|
||||
esp_err_t get_data_file_handler(httpd_req_t *req)
|
||||
{
|
||||
struct dirent *entry;
|
||||
@@ -130,7 +125,6 @@ esp_err_t get_data_file_handler(httpd_req_t *req)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
|
||||
esp_err_t get_tflite_file_handler(httpd_req_t *req)
|
||||
{
|
||||
struct dirent *entry;
|
||||
@@ -174,12 +168,11 @@ esp_err_t get_tflite_file_handler(httpd_req_t *req)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
|
||||
/* Send HTTP response with a run-time generated html consisting of
|
||||
* a list of all files and folders under the requested path.
|
||||
* In case of SPIFFS this returns empty list when path is any
|
||||
* string other than '/', since SPIFFS doesn't support directories */
|
||||
static esp_err_t http_resp_dir_html(httpd_req_t *req, const char *dirpath, const char* uripath, bool readonly)
|
||||
static esp_err_t http_resp_dir_html(httpd_req_t *req, const char *dirpath, const char *uripath, bool readonly)
|
||||
{
|
||||
char entrypath[FILE_PATH_MAX];
|
||||
char entrysize[16];
|
||||
@@ -191,82 +184,85 @@ static esp_err_t http_resp_dir_html(httpd_req_t *req, const char *dirpath, const
|
||||
char dirpath_corrected[FILE_PATH_MAX];
|
||||
strcpy(dirpath_corrected, dirpath);
|
||||
|
||||
file_server_data * server_data = (file_server_data *) req->user_ctx;
|
||||
if ((strlen(dirpath_corrected)-1) > strlen(server_data->base_path)) // if dirpath is not mountpoint, the last "\" needs to be removed
|
||||
dirpath_corrected[strlen(dirpath_corrected)-1] = '\0';
|
||||
file_server_data *server_data = (file_server_data *)req->user_ctx;
|
||||
|
||||
DIR *dir = opendir(dirpath_corrected);
|
||||
if ((strlen(dirpath_corrected) - 1) > strlen(server_data->base_path)) {
|
||||
// if dirpath is not mountpoint, the last "\" needs to be removed
|
||||
dirpath_corrected[strlen(dirpath_corrected) - 1] = '\0';
|
||||
}
|
||||
|
||||
DIR *pdir = opendir(dirpath_corrected);
|
||||
|
||||
const size_t dirpath_len = strlen(dirpath);
|
||||
ESP_LOGD(TAG, "Dirpath: <%s>, Pathlength: %d", dirpath, dirpath_len);
|
||||
|
||||
/* Retrieve the base path of file storage to construct the full path */
|
||||
// Retrieve the base path of file storage to construct the full path
|
||||
strlcpy(entrypath, dirpath, sizeof(entrypath));
|
||||
ESP_LOGD(TAG, "entrypath: <%s>", entrypath);
|
||||
|
||||
if (!dir) {
|
||||
if (!pdir) {
|
||||
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to stat dir: " + std::string(dirpath) + "!");
|
||||
/* Respond with 404 Not Found */
|
||||
// Respond with 404 Not Found
|
||||
httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, get404());
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
|
||||
|
||||
/* Send HTML file header */
|
||||
httpd_resp_sendstr_chunk(req, "<!DOCTYPE html><html><body>");
|
||||
// Send HTML file header
|
||||
httpd_resp_sendstr_chunk(req, "<!DOCTYPE html><html lang=\"en\" xml:lang=\"en\"><head>");
|
||||
httpd_resp_sendstr_chunk(req, "<link href=\"/file_server.css\" rel=\"stylesheet\">");
|
||||
httpd_resp_sendstr_chunk(req, "<link href=\"/firework.css\" rel=\"stylesheet\">");
|
||||
httpd_resp_sendstr_chunk(req, "<script type=\"text/javascript\" src=\"/jquery-3.6.0.min.js\"></script>");
|
||||
httpd_resp_sendstr_chunk(req, "<script type=\"text/javascript\" src=\"/firework.js\"></script></head>");
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
if (!readonly) {
|
||||
FILE *fd = fopen("/sdcard/html/file_server.html", "r");
|
||||
char *chunk = ((struct file_server_data *)req->user_ctx)->scratch;
|
||||
size_t chunksize;
|
||||
do {
|
||||
chunksize = fread(chunk, 1, SERVER_FILER_SCRATCH_BUFSIZE, fd);
|
||||
// ESP_LOGD(TAG, "Chunksize %d", chunksize);
|
||||
if (chunksize > 0){
|
||||
if (httpd_resp_send_chunk(req, chunk, chunksize) != ESP_OK) {
|
||||
fclose(fd);
|
||||
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "File sending failed!");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
}
|
||||
} while (chunksize != 0);
|
||||
fclose(fd);
|
||||
// ESP_LOGI(TAG, "File sending complete");
|
||||
}
|
||||
///////////////////////////////
|
||||
httpd_resp_sendstr_chunk(req, "<body>");
|
||||
|
||||
httpd_resp_sendstr_chunk(req, "<table class=\"fixed\" border=\"0\" width=100% style=\"font-family: arial\">");
|
||||
httpd_resp_sendstr_chunk(req, "<tr><td style=\"vertical-align: top;width: 300px;\"><h2>Fileserver</h2></td>"
|
||||
"<td rowspan=\"2\"><table border=\"0\" style=\"width:100%\"><tr><td style=\"width:80px\">"
|
||||
"<label for=\"newfile\">Source</label></td><td colspan=\"2\">"
|
||||
"<input id=\"newfile\" type=\"file\" onchange=\"setpath()\" style=\"width:100%;\"></td></tr>"
|
||||
"<tr><td><label for=\"filepath\">Destination</label></td><td>"
|
||||
"<input id=\"filepath\" type=\"text\" style=\"width:94%;\"></td><td>"
|
||||
"<button id=\"upload\" type=\"button\" class=\"button\" onclick=\"upload()\">Upload</button></td></tr>"
|
||||
"</table></td></tr><tr></tr><tr><td colspan=\"2\">"
|
||||
"<button style=\"font-size:16px; padding: 5px 10px\" id=\"dirup\" type=\"button\" onclick=\"dirup()\""
|
||||
"disabled>🡹 Directory up</button><span style=\"padding-left:15px\" id=\"currentpath\">"
|
||||
"</span></td></tr>");
|
||||
httpd_resp_sendstr_chunk(req, "</table>");
|
||||
|
||||
httpd_resp_sendstr_chunk(req, "<script type=\"text/javascript\" src=\"/file_server.js\"></script>");
|
||||
httpd_resp_sendstr_chunk(req, "<script type=\"text/javascript\">initFileServer();</script>");
|
||||
|
||||
std::string _zw = std::string(dirpath);
|
||||
_zw = _zw.substr(8, _zw.length() - 8);
|
||||
_zw = "/delete/" + _zw + "?task=deldircontent";
|
||||
_zw = "/delete/" + _zw + "?task=deldircontent";
|
||||
|
||||
// Send file-list table definition and column labels
|
||||
httpd_resp_sendstr_chunk(req, "<table id=\"files_table\">"
|
||||
"<col width=\"800px\"><col width=\"300px\"><col width=\"300px\"><col width=\"100px\">"
|
||||
"<thead><tr><th>Name</th><th>Type</th><th>Size</th>");
|
||||
|
||||
/* Send file-list table definition and column labels */
|
||||
httpd_resp_sendstr_chunk(req,
|
||||
"<table id=\"files_table\">"
|
||||
"<col width=\"800px\" /><col width=\"300px\" /><col width=\"300px\" /><col width=\"100px\" />"
|
||||
"<thead><tr><th>Name</th><th>Type</th><th>Size</th>");
|
||||
if (!readonly) {
|
||||
httpd_resp_sendstr_chunk(req, "<th>"
|
||||
"<form method=\"post\" action=\"");
|
||||
httpd_resp_sendstr_chunk(req, "<th><form method=\"post\" action=\"");
|
||||
httpd_resp_sendstr_chunk(req, _zw.c_str());
|
||||
httpd_resp_sendstr_chunk(req,
|
||||
"\"><button type=\"submit\">DELETE ALL!</button></form>"
|
||||
"</th></tr>");
|
||||
httpd_resp_sendstr_chunk(req, "\"><button type=\"submit\">DELETE ALL!</button></form></th></tr>");
|
||||
}
|
||||
|
||||
httpd_resp_sendstr_chunk(req, "</thead><tbody>\n");
|
||||
|
||||
/* Iterate over all files / folders and fetch their names and sizes */
|
||||
while ((entry = readdir(dir)) != NULL) {
|
||||
if (strcmp("wlan.ini", entry->d_name) != 0 ) // wlan.ini soll nicht angezeigt werden!
|
||||
{
|
||||
// Iterate over all files / folders and fetch their names and sizes
|
||||
while ((entry = readdir(pdir)) != NULL) {
|
||||
// wlan.ini soll nicht angezeigt werden!
|
||||
if (strcmp("wlan.ini", entry->d_name) != 0) {
|
||||
entrytype = (entry->d_type == DT_DIR ? "directory" : "file");
|
||||
|
||||
strlcpy(entrypath + dirpath_len, entry->d_name, sizeof(entrypath) - dirpath_len);
|
||||
ESP_LOGD(TAG, "Entrypath: %s", entrypath);
|
||||
|
||||
if (stat(entrypath, &entry_stat) == -1) {
|
||||
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to stat " + string(entrytype) + ": " + string(entry->d_name));
|
||||
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to stat " + std::string(entrytype) + ": " + std::string(entry->d_name));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -282,22 +278,25 @@ static esp_err_t http_resp_dir_html(httpd_req_t *req, const char *dirpath, const
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Found %s: %s (%s bytes)", entrytype, entry->d_name, entrysize);
|
||||
ESP_LOGD(TAG, "Found %s: %s (%s bytes)", entrytype, entry->d_name, entrysize);
|
||||
|
||||
/* Send chunk of HTML file containing table entries with file name and size */
|
||||
// Send chunk of HTML file containing table entries with file name and size
|
||||
httpd_resp_sendstr_chunk(req, "<tr><td><a href=\"");
|
||||
httpd_resp_sendstr_chunk(req, "/fileserver");
|
||||
httpd_resp_sendstr_chunk(req, uripath);
|
||||
httpd_resp_sendstr_chunk(req, entry->d_name);
|
||||
|
||||
if (entry->d_type == DT_DIR) {
|
||||
httpd_resp_sendstr_chunk(req, "/");
|
||||
}
|
||||
|
||||
httpd_resp_sendstr_chunk(req, "\">");
|
||||
httpd_resp_sendstr_chunk(req, entry->d_name);
|
||||
httpd_resp_sendstr_chunk(req, "</a></td><td>");
|
||||
httpd_resp_sendstr_chunk(req, entrytype);
|
||||
httpd_resp_sendstr_chunk(req, "</td><td>");
|
||||
httpd_resp_sendstr_chunk(req, entrysize);
|
||||
|
||||
if (!readonly) {
|
||||
httpd_resp_sendstr_chunk(req, "</td><td>");
|
||||
httpd_resp_sendstr_chunk(req, "<form method=\"post\" action=\"/delete");
|
||||
@@ -305,31 +304,28 @@ static esp_err_t http_resp_dir_html(httpd_req_t *req, const char *dirpath, const
|
||||
httpd_resp_sendstr_chunk(req, entry->d_name);
|
||||
httpd_resp_sendstr_chunk(req, "\"><button type=\"submit\">Delete</button></form>");
|
||||
}
|
||||
|
||||
httpd_resp_sendstr_chunk(req, "</td></tr>\n");
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
|
||||
/* Finish the file list table */
|
||||
closedir(pdir);
|
||||
|
||||
// Finish the file list table
|
||||
httpd_resp_sendstr_chunk(req, "</tbody></table>");
|
||||
|
||||
/* Send remaining chunk of HTML file to complete it */
|
||||
// Send remaining chunk of HTML file to complete it
|
||||
httpd_resp_sendstr_chunk(req, "</body></html>");
|
||||
|
||||
/* Send empty chunk to signal HTTP response completion */
|
||||
// Send empty chunk to signal HTTP response completion
|
||||
httpd_resp_sendstr_chunk(req, NULL);
|
||||
return ESP_OK;
|
||||
}
|
||||
/*
|
||||
#define IS_FILE_EXT(filename, ext) \
|
||||
(strcasecmp(&filename[strlen(filename) - sizeof(ext) + 1], ext) == 0)
|
||||
*/
|
||||
|
||||
static esp_err_t logfileact_get_full_handler(httpd_req_t *req) {
|
||||
return send_logfile(req, true);
|
||||
}
|
||||
|
||||
|
||||
static esp_err_t logfileact_get_last_part_handler(httpd_req_t *req) {
|
||||
return send_logfile(req, false);
|
||||
}
|
||||
@@ -338,7 +334,6 @@ static esp_err_t datafileact_get_full_handler(httpd_req_t *req) {
|
||||
return send_datafile(req, true);
|
||||
}
|
||||
|
||||
|
||||
static esp_err_t datafileact_get_last_part_handler(httpd_req_t *req) {
|
||||
return send_datafile(req, false);
|
||||
}
|
||||
@@ -423,7 +418,6 @@ static esp_err_t send_datafile(httpd_req_t *req, bool send_full_file)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
|
||||
static esp_err_t send_logfile(httpd_req_t *req, bool send_full_file)
|
||||
{
|
||||
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "log_get_last_part_handler");
|
||||
@@ -509,7 +503,6 @@ static esp_err_t send_logfile(httpd_req_t *req, bool send_full_file)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
|
||||
/* Handler to download a file kept on the server */
|
||||
static esp_err_t download_get_handler(httpd_req_t *req)
|
||||
{
|
||||
@@ -527,7 +520,6 @@ static esp_err_t download_get_handler(httpd_req_t *req)
|
||||
// filename = get_path_from_uri(filepath, ((struct file_server_data *)req->user_ctx)->base_path,
|
||||
// req->uri, sizeof(filepath));
|
||||
|
||||
|
||||
if (!filename) {
|
||||
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Filename is too long");
|
||||
/* Respond with 414 Error */
|
||||
@@ -619,6 +611,8 @@ static esp_err_t upload_post_handler(httpd_req_t *req)
|
||||
FILE *fd = NULL;
|
||||
struct stat file_stat;
|
||||
|
||||
ESP_LOGI(TAG, "uri: %s", req->uri);
|
||||
|
||||
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
|
||||
|
||||
/* Skip leading "/upload" from URI to get filename */
|
||||
@@ -720,45 +714,77 @@ static esp_err_t upload_post_handler(httpd_req_t *req)
|
||||
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "File saved: " + string(filename));
|
||||
ESP_LOGI(TAG, "File reception completed");
|
||||
|
||||
std::string directory = std::string(filepath);
|
||||
size_t zw = directory.find("/");
|
||||
size_t found = zw;
|
||||
while (zw != std::string::npos)
|
||||
{
|
||||
zw = directory.find("/", found+1);
|
||||
if (zw != std::string::npos)
|
||||
found = zw;
|
||||
}
|
||||
string s = req->uri;
|
||||
if (isInString(s, "?md5")) {
|
||||
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Calculate and return MD5 sum...");
|
||||
|
||||
fd = fopen(filepath, "r");
|
||||
if (!fd) {
|
||||
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to open file for reading: " + string(filepath));
|
||||
/* Respond with 500 Internal Server Error */
|
||||
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to open file for reading");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
int start_fn = strlen(((struct file_server_data *)req->user_ctx)->base_path);
|
||||
ESP_LOGD(TAG, "Directory: %s, start_fn: %d, found: %d", directory.c_str(), start_fn, found);
|
||||
directory = directory.substr(start_fn, found - start_fn + 1);
|
||||
directory = "/fileserver" + directory;
|
||||
// ESP_LOGD(TAG, "Directory danach 2: %s", directory.c_str());
|
||||
uint8_t result[16];
|
||||
string md5hex = "";
|
||||
string response = "{\"md5\":";
|
||||
char hex[3];
|
||||
|
||||
/* Redirect onto root to see the updated file list */
|
||||
if (strcmp(filename, "/config/config.ini") == 0 ||
|
||||
strcmp(filename, "/config/ref0.jpg") == 0 ||
|
||||
strcmp(filename, "/config/ref0_org.jpg") == 0 ||
|
||||
strcmp(filename, "/config/ref1.jpg") == 0 ||
|
||||
strcmp(filename, "/config/ref1_org.jpg") == 0 ||
|
||||
strcmp(filename, "/config/reference.jpg") == 0 ||
|
||||
strcmp(filename, "/img_tmp/ref0.jpg") == 0 ||
|
||||
strcmp(filename, "/img_tmp/ref0_org.jpg") == 0 ||
|
||||
strcmp(filename, "/img_tmp/ref1.jpg") == 0 ||
|
||||
strcmp(filename, "/img_tmp/ref1_org.jpg") == 0 ||
|
||||
strcmp(filename, "/img_tmp/reference.jpg") == 0 )
|
||||
{
|
||||
httpd_resp_set_status(req, HTTPD_200); // Avoid reloading of folder content
|
||||
md5File(fd, result);
|
||||
fclose(fd);
|
||||
|
||||
for (int i = 0; i < sizeof(result); i++) {
|
||||
snprintf(hex, sizeof(hex), "%02x", result[i]);
|
||||
md5hex.append(hex);
|
||||
}
|
||||
|
||||
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "MD5 of " + string(filepath) + ": " + md5hex);
|
||||
response.append("\"" + md5hex + "\"");
|
||||
response.append("}");
|
||||
|
||||
httpd_resp_sendstr(req, response.c_str());
|
||||
}
|
||||
else {
|
||||
httpd_resp_set_status(req, "303 See Other"); // Reload folder content after upload
|
||||
else { // Return file server page
|
||||
std::string directory = std::string(filepath);
|
||||
size_t zw = directory.find("/");
|
||||
size_t found = zw;
|
||||
while (zw != std::string::npos)
|
||||
{
|
||||
zw = directory.find("/", found+1);
|
||||
if (zw != std::string::npos)
|
||||
found = zw;
|
||||
}
|
||||
|
||||
int start_fn = strlen(((struct file_server_data *)req->user_ctx)->base_path);
|
||||
ESP_LOGD(TAG, "Directory: %s, start_fn: %d, found: %d", directory.c_str(), start_fn, found);
|
||||
directory = directory.substr(start_fn, found - start_fn + 1);
|
||||
directory = "/fileserver" + directory;
|
||||
// ESP_LOGD(TAG, "Directory danach 2: %s", directory.c_str());
|
||||
|
||||
/* Redirect onto root to see the updated file list */
|
||||
if (strcmp(filename, "/config/config.ini") == 0 ||
|
||||
strcmp(filename, "/config/ref0.jpg") == 0 ||
|
||||
strcmp(filename, "/config/ref0_org.jpg") == 0 ||
|
||||
strcmp(filename, "/config/ref1.jpg") == 0 ||
|
||||
strcmp(filename, "/config/ref1_org.jpg") == 0 ||
|
||||
strcmp(filename, "/config/reference.jpg") == 0 ||
|
||||
strcmp(filename, "/img_tmp/ref0.jpg") == 0 ||
|
||||
strcmp(filename, "/img_tmp/ref0_org.jpg") == 0 ||
|
||||
strcmp(filename, "/img_tmp/ref1.jpg") == 0 ||
|
||||
strcmp(filename, "/img_tmp/ref1_org.jpg") == 0 ||
|
||||
strcmp(filename, "/img_tmp/reference.jpg") == 0 )
|
||||
{
|
||||
httpd_resp_set_status(req, HTTPD_200); // Avoid reloading of folder content
|
||||
}
|
||||
else {
|
||||
httpd_resp_set_status(req, "303 See Other"); // Reload folder content after upload
|
||||
}
|
||||
|
||||
httpd_resp_set_hdr(req, "Location", directory.c_str());
|
||||
httpd_resp_sendstr(req, "File uploaded successfully");
|
||||
}
|
||||
|
||||
httpd_resp_set_hdr(req, "Location", directory.c_str());
|
||||
httpd_resp_sendstr(req, "File uploaded successfully");
|
||||
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
@@ -769,7 +795,6 @@ static esp_err_t delete_post_handler(httpd_req_t *req)
|
||||
char filepath[FILE_PATH_MAX];
|
||||
struct stat file_stat;
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
char _query[200];
|
||||
char _valuechar[30];
|
||||
@@ -892,13 +917,11 @@ static esp_err_t delete_post_handler(httpd_req_t *req)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
httpd_resp_set_hdr(req, "Location", directory.c_str());
|
||||
httpd_resp_sendstr(req, "File successfully deleted");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
|
||||
void delete_all_in_directory(std::string _directory)
|
||||
{
|
||||
struct dirent *entry;
|
||||
@@ -924,7 +947,7 @@ void delete_all_in_directory(std::string _directory)
|
||||
closedir(dir);
|
||||
}
|
||||
|
||||
std::string unzip_new(std::string _in_zip_file, std::string _target_zip, std::string _target_bin, std::string _main, bool _initial_setup)
|
||||
std::string unzip_new(std::string _in_zip_file, std::string _html_tmp, std::string _html_final, std::string _target_bin, std::string _main, bool _initial_setup)
|
||||
{
|
||||
int i, sort_iter;
|
||||
mz_bool status;
|
||||
@@ -1006,10 +1029,15 @@ std::string unzip_new(std::string _in_zip_file, std::string _target_zip, std::st
|
||||
}
|
||||
else
|
||||
{
|
||||
zw = _target_zip + zw;
|
||||
zw = _html_tmp + zw;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// files in the html folder shall be redirected to the temporary html folder
|
||||
if (zw.find(_html_final) == 0) {
|
||||
FindReplace(zw, _html_final, _html_tmp);
|
||||
}
|
||||
|
||||
string filename_zw = zw + SUFFIX_ZW;
|
||||
|
||||
@@ -1136,8 +1164,6 @@ void unzip(std::string _in_zip_file, std::string _target_directory){
|
||||
ESP_LOGD(TAG, "Success.");
|
||||
}
|
||||
|
||||
|
||||
|
||||
void register_server_file_uri(httpd_handle_t server, const char *base_path)
|
||||
{
|
||||
static struct file_server_data *server_data = NULL;
|
||||
@@ -1163,8 +1189,6 @@ void register_server_file_uri(httpd_handle_t server, const char *base_path)
|
||||
strlcpy(server_data->base_path, base_path,
|
||||
sizeof(server_data->base_path));
|
||||
|
||||
|
||||
|
||||
/* URI handler for getting uploaded files */
|
||||
// char zw[sizeof(serverprefix)+1];
|
||||
// strcpy(zw, serverprefix);
|
||||
@@ -1174,25 +1198,23 @@ void register_server_file_uri(httpd_handle_t server, const char *base_path)
|
||||
httpd_uri_t file_download = {
|
||||
.uri = "/fileserver*", // Match all URIs of type /path/to/file
|
||||
.method = HTTP_GET,
|
||||
.handler = download_get_handler,
|
||||
.handler = APPLY_BASIC_AUTH_FILTER(download_get_handler),
|
||||
.user_ctx = server_data // Pass server data as context
|
||||
};
|
||||
httpd_register_uri_handler(server, &file_download);
|
||||
|
||||
|
||||
httpd_uri_t file_datafileact = {
|
||||
.uri = "/datafileact", // Match all URIs of type /path/to/file
|
||||
.method = HTTP_GET,
|
||||
.handler = datafileact_get_full_handler,
|
||||
.handler = APPLY_BASIC_AUTH_FILTER(datafileact_get_full_handler),
|
||||
.user_ctx = server_data // Pass server data as context
|
||||
};
|
||||
httpd_register_uri_handler(server, &file_datafileact);
|
||||
|
||||
|
||||
httpd_uri_t file_datafile_last_part_handle = {
|
||||
.uri = "/data", // Match all URIs of type /path/to/file
|
||||
.method = HTTP_GET,
|
||||
.handler = datafileact_get_last_part_handler,
|
||||
.handler = APPLY_BASIC_AUTH_FILTER(datafileact_get_last_part_handler),
|
||||
.user_ctx = server_data // Pass server data as context
|
||||
};
|
||||
httpd_register_uri_handler(server, &file_datafile_last_part_handle);
|
||||
@@ -1200,26 +1222,24 @@ void register_server_file_uri(httpd_handle_t server, const char *base_path)
|
||||
httpd_uri_t file_logfileact = {
|
||||
.uri = "/logfileact", // Match all URIs of type /path/to/file
|
||||
.method = HTTP_GET,
|
||||
.handler = logfileact_get_full_handler,
|
||||
.handler = APPLY_BASIC_AUTH_FILTER(logfileact_get_full_handler),
|
||||
.user_ctx = server_data // Pass server data as context
|
||||
};
|
||||
httpd_register_uri_handler(server, &file_logfileact);
|
||||
|
||||
|
||||
httpd_uri_t file_logfile_last_part_handle = {
|
||||
.uri = "/log", // Match all URIs of type /path/to/file
|
||||
.method = HTTP_GET,
|
||||
.handler = logfileact_get_last_part_handler,
|
||||
.handler = APPLY_BASIC_AUTH_FILTER(logfileact_get_last_part_handler),
|
||||
.user_ctx = server_data // Pass server data as context
|
||||
};
|
||||
httpd_register_uri_handler(server, &file_logfile_last_part_handle);
|
||||
|
||||
|
||||
/* URI handler for uploading files to server */
|
||||
httpd_uri_t file_upload = {
|
||||
.uri = "/upload/*", // Match all URIs of type /upload/path/to/file
|
||||
.method = HTTP_POST,
|
||||
.handler = upload_post_handler,
|
||||
.handler = APPLY_BASIC_AUTH_FILTER(upload_post_handler),
|
||||
.user_ctx = server_data // Pass server data as context
|
||||
};
|
||||
httpd_register_uri_handler(server, &file_upload);
|
||||
@@ -1228,9 +1248,8 @@ void register_server_file_uri(httpd_handle_t server, const char *base_path)
|
||||
httpd_uri_t file_delete = {
|
||||
.uri = "/delete/*", // Match all URIs of type /delete/path/to/file
|
||||
.method = HTTP_POST,
|
||||
.handler = delete_post_handler,
|
||||
.handler = APPLY_BASIC_AUTH_FILTER(delete_post_handler),
|
||||
.user_ctx = server_data // Pass server data as context
|
||||
};
|
||||
httpd_register_uri_handler(server, &file_delete);
|
||||
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
void register_server_file_uri(httpd_handle_t server, const char *base_path);
|
||||
|
||||
void unzip(std::string _in_zip_file, std::string _target_directory);
|
||||
std::string unzip_new(std::string _in_zip_file, std::string _target_zip, std::string _target_bin, std::string _main = "/sdcard/", bool _initial_setup = false);
|
||||
std::string unzip_new(std::string _in_zip_file, std::string _html_tmp, std::string _html_final, std::string _target_bin, std::string _main = "/sdcard/", bool _initial_setup = false);
|
||||
|
||||
|
||||
void delete_all_in_directory(std::string _directory);
|
||||
|
||||
@@ -16,77 +16,101 @@ extern "C" {
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "Helper.h"
|
||||
|
||||
#include "esp_http_server.h"
|
||||
|
||||
#include "../../include/defines.h"
|
||||
|
||||
|
||||
static const char *TAG = "SERVER HELP";
|
||||
|
||||
char scratch[SERVER_HELPER_SCRATCH_BUFSIZE];
|
||||
|
||||
|
||||
bool endsWith(std::string const &str, std::string const &suffix) {
|
||||
bool endsWith(std::string const &str, std::string const &suffix)
|
||||
{
|
||||
if (str.length() < suffix.length()) {
|
||||
return false;
|
||||
}
|
||||
return str.compare(str.length() - suffix.length(), suffix.length(), suffix) == 0;
|
||||
}
|
||||
|
||||
|
||||
esp_err_t send_file(httpd_req_t *req, std::string filename)
|
||||
{
|
||||
std::string _filename_old = filename;
|
||||
struct stat file_stat;
|
||||
bool _gz_file_exists = false;
|
||||
|
||||
ESP_LOGD(TAG, "old filename: %s", filename.c_str());
|
||||
std::string _filename_temp = std::string(filename) + ".gz";
|
||||
|
||||
// Checks whether the file is available as .gz
|
||||
if (stat(_filename_temp.c_str(), &file_stat) == 0) {
|
||||
filename = _filename_temp;
|
||||
|
||||
ESP_LOGD(TAG, "new filename: %s", filename.c_str());
|
||||
_gz_file_exists = true;
|
||||
}
|
||||
|
||||
FILE *fd = fopen(filename.c_str(), "r");
|
||||
if (!fd) {
|
||||
if (!fd) {
|
||||
ESP_LOGE(TAG, "Failed to read file: %s", filename.c_str());
|
||||
|
||||
/* Respond with 404 Error */
|
||||
httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, get404());
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Sending file: %s ...", filename.c_str());
|
||||
// httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
|
||||
|
||||
/* For all files with the following file extention tell
|
||||
the webbrowser to cache them for 24h */
|
||||
/* For all files with the following file extention tell the webbrowser to cache them for 12h */
|
||||
if (endsWith(filename, ".html") ||
|
||||
endsWith(filename, ".htm") ||
|
||||
endsWith(filename, ".xml") ||
|
||||
endsWith(filename, ".css") ||
|
||||
endsWith(filename, ".js") ||
|
||||
endsWith(filename, ".map") ||
|
||||
endsWith(filename, ".jpg") ||
|
||||
endsWith(filename, ".jpeg") ||
|
||||
endsWith(filename, ".ico") ||
|
||||
endsWith(filename, ".png")) {
|
||||
|
||||
if (filename == "/sdcard/html/setup.html") {
|
||||
endsWith(filename, ".png") ||
|
||||
endsWith(filename, ".gif") ||
|
||||
// endsWith(filename, ".zip") ||
|
||||
endsWith(filename, ".gz")) {
|
||||
if (filename == "/sdcard/html/setup.html") {
|
||||
httpd_resp_set_hdr(req, "Clear-Site-Data", "\"*\"");
|
||||
set_content_type_from_file(req, filename.c_str());
|
||||
}
|
||||
else if (_gz_file_exists) {
|
||||
httpd_resp_set_hdr(req, "Cache-Control", "max-age=43200");
|
||||
httpd_resp_set_hdr(req, "Content-Encoding", "gzip");
|
||||
set_content_type_from_file(req, _filename_old.c_str());
|
||||
}
|
||||
else {
|
||||
httpd_resp_set_hdr(req, "Cache-Control", "max-age=86400");
|
||||
httpd_resp_set_hdr(req, "Cache-Control", "max-age=43200");
|
||||
set_content_type_from_file(req, filename.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
set_content_type_from_file(req, filename.c_str());
|
||||
else {
|
||||
set_content_type_from_file(req, filename.c_str());
|
||||
}
|
||||
|
||||
/* Retrieve the pointer to scratch buffer for temporary storage */
|
||||
char *chunk = scratch;
|
||||
size_t chunksize;
|
||||
do {
|
||||
|
||||
do {
|
||||
/* Read file in chunks into the scratch buffer */
|
||||
chunksize = fread(chunk, 1, SERVER_HELPER_SCRATCH_BUFSIZE, fd);
|
||||
|
||||
/* Send the buffer contents as HTTP response chunk */
|
||||
if (httpd_resp_send_chunk(req, chunk, chunksize) != ESP_OK) {
|
||||
if (httpd_resp_send_chunk(req, chunk, chunksize) != ESP_OK) {
|
||||
fclose(fd);
|
||||
ESP_LOGE(TAG, "File sending failed!");
|
||||
|
||||
/* Abort sending file */
|
||||
httpd_resp_sendstr_chunk(req, NULL);
|
||||
|
||||
/* Respond with 500 Internal Server Error */
|
||||
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to send file");
|
||||
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
@@ -95,13 +119,11 @@ esp_err_t send_file(httpd_req_t *req, std::string filename)
|
||||
|
||||
/* Close file after sending complete */
|
||||
fclose(fd);
|
||||
ESP_LOGD(TAG, "File sending complete");
|
||||
ESP_LOGD(TAG, "File sending complete");
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* Copies the full path into destination buffer and returns
|
||||
* pointer to path (skipping the preceding base path) */
|
||||
const char* get_path_from_uri(char *dest, const char *base_path, const char *uri, size_t destsize)
|
||||
@@ -131,25 +153,49 @@ const char* get_path_from_uri(char *dest, const char *base_path, const char *uri
|
||||
return dest + base_pathlen;
|
||||
}
|
||||
|
||||
|
||||
/* Set HTTP response content type according to file extension */
|
||||
esp_err_t set_content_type_from_file(httpd_req_t *req, const char *filename)
|
||||
{
|
||||
if (IS_FILE_EXT(filename, ".pdf")) {
|
||||
return httpd_resp_set_type(req, "application/pdf");
|
||||
} else if (IS_FILE_EXT(filename, ".html")) {
|
||||
return httpd_resp_set_type(req, "application/x-pdf");
|
||||
}
|
||||
else if (IS_FILE_EXT(filename, ".htm")) {
|
||||
return httpd_resp_set_type(req, "text/html");
|
||||
} else if (IS_FILE_EXT(filename, ".jpeg")) {
|
||||
}
|
||||
else if (IS_FILE_EXT(filename, ".html")) {
|
||||
return httpd_resp_set_type(req, "text/html");
|
||||
}
|
||||
else if (IS_FILE_EXT(filename, ".jpeg")) {
|
||||
return httpd_resp_set_type(req, "image/jpeg");
|
||||
} else if (IS_FILE_EXT(filename, ".jpg")) {
|
||||
}
|
||||
else if (IS_FILE_EXT(filename, ".jpg")) {
|
||||
return httpd_resp_set_type(req, "image/jpeg");
|
||||
} else if (IS_FILE_EXT(filename, ".ico")) {
|
||||
}
|
||||
else if (IS_FILE_EXT(filename, ".gif")) {
|
||||
return httpd_resp_set_type(req, "image/gif");
|
||||
}
|
||||
else if (IS_FILE_EXT(filename, ".png")) {
|
||||
return httpd_resp_set_type(req, "image/png");
|
||||
}
|
||||
else if (IS_FILE_EXT(filename, ".ico")) {
|
||||
return httpd_resp_set_type(req, "image/x-icon");
|
||||
} else if (IS_FILE_EXT(filename, ".js")) {
|
||||
return httpd_resp_set_type(req, "text/javascript");
|
||||
} else if (IS_FILE_EXT(filename, ".css")) {
|
||||
}
|
||||
else if (IS_FILE_EXT(filename, ".js")) {
|
||||
return httpd_resp_set_type(req, "application/javascript");
|
||||
}
|
||||
else if (IS_FILE_EXT(filename, ".css")) {
|
||||
return httpd_resp_set_type(req, "text/css");
|
||||
}
|
||||
else if (IS_FILE_EXT(filename, ".xml")) {
|
||||
return httpd_resp_set_type(req, "text/xml");
|
||||
}
|
||||
else if (IS_FILE_EXT(filename, ".zip")) {
|
||||
return httpd_resp_set_type(req, "application/x-zip");
|
||||
}
|
||||
else if (IS_FILE_EXT(filename, ".gz")) {
|
||||
return httpd_resp_set_type(req, "application/x-gzip");
|
||||
}
|
||||
|
||||
/* This is a limited set only */
|
||||
/* For any other type always set as plain text */
|
||||
return httpd_resp_set_type(req, "text/plain");
|
||||
|
||||
@@ -42,6 +42,7 @@ https://docs.espressif.com/projects/esp-idf/en/latest/esp32/migration-guides/rel
|
||||
|
||||
#include "Helper.h"
|
||||
#include "statusled.h"
|
||||
#include "basic_auth.h"
|
||||
#include "../../include/defines.h"
|
||||
|
||||
/*an ota data write buffer ready to write to the flash*/
|
||||
@@ -75,20 +76,35 @@ void task_do_Update_ZIP(void *pvParameter)
|
||||
|
||||
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "File: " + _file_name_update + " Filetype: " + filetype);
|
||||
|
||||
|
||||
if (filetype == "ZIP")
|
||||
{
|
||||
std::string in, out, outbin, zw, retfirmware;
|
||||
std::string in, outHtml, outHtmlTmp, outHtmlOld, outbin, zw, retfirmware;
|
||||
|
||||
out = "/sdcard/html";
|
||||
outHtml = "/sdcard/html";
|
||||
outHtmlTmp = "/sdcard/html_tmp";
|
||||
outHtmlOld = "/sdcard/html_old";
|
||||
outbin = "/sdcard/firmware";
|
||||
|
||||
retfirmware = unzip_new(_file_name_update, out+"/", outbin+"/", "/sdcard/", initial_setup);
|
||||
/* Remove the old and tmp html folder in case they still exist */
|
||||
removeFolder(outHtmlTmp.c_str(), TAG);
|
||||
removeFolder(outHtmlOld.c_str(), TAG);
|
||||
|
||||
/* Extract the ZIP file. The content of the html folder gets extracted to the temporar folder html-temp. */
|
||||
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Extracting ZIP file " + _file_name_update + "...");
|
||||
retfirmware = unzip_new(_file_name_update, outHtmlTmp+"/", outHtml+"/", outbin+"/", "/sdcard/", initial_setup);
|
||||
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Files unzipped.");
|
||||
|
||||
/* ZIP file got extracted, replace the old html folder with the new one */
|
||||
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Renaming folder " + outHtml + " to " + outHtmlOld + "...");
|
||||
RenameFolder(outHtml, outHtmlOld);
|
||||
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Renaming folder " + outHtmlTmp + " to " + outHtml + "...");
|
||||
RenameFolder(outHtmlTmp, outHtml);
|
||||
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Deleting folder " + outHtmlOld + "...");
|
||||
removeFolder(outHtmlOld.c_str(), TAG);
|
||||
|
||||
if (retfirmware.length() > 0)
|
||||
{
|
||||
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Found firmware.bin");
|
||||
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Found firmware.bin");
|
||||
ota_update_task(retfirmware);
|
||||
}
|
||||
|
||||
@@ -433,7 +449,6 @@ esp_err_t handler_ota_update(httpd_req_t *req)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
|
||||
if ((filetype == "TFLITE") || (filetype == "TFL"))
|
||||
{
|
||||
std::string out = "/sdcard/config/" + getFileFullFileName(fn);
|
||||
@@ -690,13 +705,13 @@ void register_server_ota_sdcard_uri(httpd_handle_t server)
|
||||
httpd_uri_t camuri = { };
|
||||
camuri.method = HTTP_GET;
|
||||
camuri.uri = "/ota";
|
||||
camuri.handler = handler_ota_update;
|
||||
camuri.handler = APPLY_BASIC_AUTH_FILTER(handler_ota_update);
|
||||
camuri.user_ctx = (void*) "Do OTA";
|
||||
httpd_register_uri_handler(server, &camuri);
|
||||
|
||||
camuri.method = HTTP_GET;
|
||||
camuri.uri = "/reboot";
|
||||
camuri.handler = handler_reboot;
|
||||
camuri.handler = APPLY_BASIC_AUTH_FILTER(handler_reboot);
|
||||
camuri.user_ctx = (void*) "Reboot";
|
||||
httpd_register_uri_handler(server, &camuri);
|
||||
|
||||
|
||||
@@ -65,11 +65,11 @@ string ClassFlowCNNGeneral::getReadout(int _analog = 0, bool _extendedResolution
|
||||
|
||||
if (CNNType == Digit) {
|
||||
for (int i = 0; i < GENERAL[_analog]->ROI.size(); ++i) {
|
||||
if (GENERAL[_analog]->ROI[i]->result_klasse >= 10) {
|
||||
result = result + "N";
|
||||
if ((GENERAL[_analog]->ROI[i]->result_klasse >= 0) && (GENERAL[_analog]->ROI[i]->result_klasse < 10)) {
|
||||
result = result + std::to_string(GENERAL[_analog]->ROI[i]->result_klasse);
|
||||
}
|
||||
else {
|
||||
result = result + std::to_string(GENERAL[_analog]->ROI[i]->result_klasse);
|
||||
result = result + "N";
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@@ -78,7 +78,7 @@ string ClassFlowCNNGeneral::getReadout(int _analog = 0, bool _extendedResolution
|
||||
if ((CNNType == DoubleHyprid10) || (CNNType == Digit100)) {
|
||||
float number = GENERAL[_analog]->ROI[GENERAL[_analog]->ROI.size() - 1]->result_float;
|
||||
// NaN?
|
||||
if (number >= 0) {
|
||||
if ((number >= 0) && (number < 10)) {
|
||||
// is only set if it is the first digit (no analogue before!)
|
||||
if (_extendedResolution) {
|
||||
int result_after_decimal_point = ((int) floor(number * 10)) % 10;
|
||||
@@ -95,8 +95,15 @@ string ClassFlowCNNGeneral::getReadout(int _analog = 0, bool _extendedResolution
|
||||
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));
|
||||
|
||||
// is necessary because a number greater than 9.994999 returns a 10! (for further details see check in PointerEvalHybridNew)
|
||||
if ((prev >= 0) && (prev < 10)) {
|
||||
result = std::to_string(prev);
|
||||
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "getReadout(dig100) prev=" + std::to_string(prev));
|
||||
}
|
||||
else {
|
||||
result = "N";
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -107,7 +114,7 @@ string ClassFlowCNNGeneral::getReadout(int _analog = 0, bool _extendedResolution
|
||||
}
|
||||
|
||||
for (int i = GENERAL[_analog]->ROI.size() - 2; i >= 0; --i) {
|
||||
if (GENERAL[_analog]->ROI[i]->result_float >= 0) {
|
||||
if ((GENERAL[_analog]->ROI[i]->result_float >= 0) && (GENERAL[_analog]->ROI[i]->result_float < 10)) {
|
||||
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;
|
||||
@@ -117,7 +124,6 @@ string ClassFlowCNNGeneral::getReadout(int _analog = 0, bool _extendedResolution
|
||||
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));
|
||||
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@@ -150,6 +156,9 @@ int ClassFlowCNNGeneral::PointerEvalHybridNew(float number, float number_of_pred
|
||||
// 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
|
||||
// a number greater than 9.994999 is returned as 10, this leads to an error during the decimal shift because the NUMBERS[j]->ReturnRawValue is one digit longer.
|
||||
// To avoid this, an additional test must be carried out, see "if ((CNNType == DoubleHyprid10) || (CNNType == Digit100))" check in getReadout()
|
||||
// Another alternative would be "result = (int) ((int) trunc(round((number+10 % 10)*1000))) / 1000;", which could, however, lead to other errors?
|
||||
result = (int) ((int) trunc(round((number+10 % 10)*100)) ) / 100;
|
||||
|
||||
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "PointerEvalHybridNew - No predecessor - Result = " + std::to_string(result) +
|
||||
|
||||
@@ -26,52 +26,59 @@ extern "C" {
|
||||
|
||||
#include "server_help.h"
|
||||
#include "MainFlowControl.h"
|
||||
#include "basic_auth.h"
|
||||
#include "../../include/defines.h"
|
||||
|
||||
static const char* TAG = "FLOWCTRL";
|
||||
|
||||
//#define DEBUG_DETAIL_ON
|
||||
|
||||
std::string ClassFlowControll::doSingleStep(std::string _stepname, std::string _host){
|
||||
std::string ClassFlowControll::doSingleStep(std::string _stepname, std::string _host)
|
||||
{
|
||||
std::string _classname = "";
|
||||
std::string result = "";
|
||||
|
||||
ESP_LOGD(TAG, "Step %s start", _stepname.c_str());
|
||||
|
||||
if ((_stepname.compare("[TakeImage]") == 0) || (_stepname.compare(";[TakeImage]") == 0)){
|
||||
if ((_stepname.compare("[TakeImage]") == 0) || (_stepname.compare(";[TakeImage]") == 0)) {
|
||||
_classname = "ClassFlowTakeImage";
|
||||
}
|
||||
if ((_stepname.compare("[Alignment]") == 0) || (_stepname.compare(";[Alignment]") == 0)){
|
||||
|
||||
if ((_stepname.compare("[Alignment]") == 0) || (_stepname.compare(";[Alignment]") == 0)) {
|
||||
_classname = "ClassFlowAlignment";
|
||||
}
|
||||
|
||||
if ((_stepname.compare(0, 7, "[Digits") == 0) || (_stepname.compare(0, 8, ";[Digits") == 0)) {
|
||||
_classname = "ClassFlowCNNGeneral";
|
||||
}
|
||||
if ((_stepname.compare("[Analog]") == 0) || (_stepname.compare(";[Analog]") == 0)){
|
||||
|
||||
if ((_stepname.compare("[Analog]") == 0) || (_stepname.compare(";[Analog]") == 0)) {
|
||||
_classname = "ClassFlowCNNGeneral";
|
||||
}
|
||||
|
||||
#ifdef ENABLE_MQTT
|
||||
if ((_stepname.compare("[MQTT]") == 0) || (_stepname.compare(";[MQTT]") == 0)){
|
||||
_classname = "ClassFlowMQTT";
|
||||
}
|
||||
if ((_stepname.compare("[MQTT]") == 0) || (_stepname.compare(";[MQTT]") == 0)) {
|
||||
_classname = "ClassFlowMQTT";
|
||||
}
|
||||
#endif //ENABLE_MQTT
|
||||
|
||||
#ifdef ENABLE_INFLUXDB
|
||||
if ((_stepname.compare("[InfluxDB]") == 0) || (_stepname.compare(";[InfluxDB]") == 0)){
|
||||
_classname = "ClassFlowInfluxDB";
|
||||
}
|
||||
if ((_stepname.compare("[InfluxDBv2]") == 0) || (_stepname.compare(";[InfluxDBv2]") == 0)){
|
||||
_classname = "ClassFlowInfluxDBv2";
|
||||
}
|
||||
if ((_stepname.compare("[InfluxDB]") == 0) || (_stepname.compare(";[InfluxDB]") == 0)) {
|
||||
_classname = "ClassFlowInfluxDB";
|
||||
}
|
||||
if ((_stepname.compare("[InfluxDBv2]") == 0) || (_stepname.compare(";[InfluxDBv2]") == 0)) {
|
||||
_classname = "ClassFlowInfluxDBv2";
|
||||
}
|
||||
#endif //ENABLE_INFLUXDB
|
||||
|
||||
#ifdef ENABLE_WEBHOOK
|
||||
if ((_stepname.compare("[Webhook]") == 0) || (_stepname.compare(";[Webhook]") == 0)){
|
||||
_classname = "ClassFlowWebhook";
|
||||
}
|
||||
if ((_stepname.compare("[Webhook]") == 0) || (_stepname.compare(";[Webhook]") == 0)) {
|
||||
_classname = "ClassFlowWebhook";
|
||||
}
|
||||
#endif //ENABLE_WEBHOOK
|
||||
|
||||
for (int i = 0; i < FlowControll.size(); ++i)
|
||||
if (FlowControll[i]->name().compare(_classname) == 0){
|
||||
for (int i = 0; i < FlowControll.size(); ++i) {
|
||||
if (FlowControll[i]->name().compare(_classname) == 0) {
|
||||
if (!(FlowControll[i]->name().compare("ClassFlowTakeImage") == 0)) {
|
||||
// if it is a TakeImage, the image does not need to be included, this happens automatically with the html query.
|
||||
FlowControll[i]->doFlow("");
|
||||
@@ -79,6 +86,7 @@ std::string ClassFlowControll::doSingleStep(std::string _stepname, std::string _
|
||||
|
||||
result = FlowControll[i]->getHTMLSingleStep(_host);
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Step %s end", _stepname.c_str());
|
||||
|
||||
@@ -114,11 +122,13 @@ std::string ClassFlowControll::TranslateAktstatus(std::string _input)
|
||||
return ("Sending InfluxDBv2");
|
||||
}
|
||||
#endif //ENABLE_INFLUXDB
|
||||
|
||||
#ifdef ENABLE_WEBHOOK
|
||||
if (_input.compare("ClassFlowWebhook") == 0) {
|
||||
return ("Sending Webhook");
|
||||
}
|
||||
if (_input.compare("ClassFlowWebhook") == 0) {
|
||||
return ("Sending Webhook");
|
||||
}
|
||||
#endif //ENABLE_WEBHOOK
|
||||
|
||||
if (_input.compare("ClassFlowPostProcessing") == 0) {
|
||||
return ("Post-Processing");
|
||||
}
|
||||
@@ -223,7 +233,6 @@ void ClassFlowControll::setAutoStartInterval(long &_interval)
|
||||
ClassFlow* ClassFlowControll::CreateClassFlow(std::string _type)
|
||||
{
|
||||
ClassFlow* cfc = NULL;
|
||||
|
||||
_type = trim(_type);
|
||||
|
||||
if (toUpper(_type).compare("[TAKEIMAGE]") == 0) {
|
||||
@@ -247,23 +256,25 @@ ClassFlow* ClassFlowControll::CreateClassFlow(std::string _type)
|
||||
}
|
||||
|
||||
#ifdef ENABLE_MQTT
|
||||
if (toUpper(_type).compare("[MQTT]") == 0) {
|
||||
cfc = new ClassFlowMQTT(&FlowControll);
|
||||
}
|
||||
if (toUpper(_type).compare("[MQTT]") == 0) {
|
||||
cfc = new ClassFlowMQTT(&FlowControll);
|
||||
}
|
||||
#endif //ENABLE_MQTT
|
||||
|
||||
#ifdef ENABLE_INFLUXDB
|
||||
if (toUpper(_type).compare("[INFLUXDB]") == 0) {
|
||||
cfc = new ClassFlowInfluxDB(&FlowControll);
|
||||
}
|
||||
if (toUpper(_type).compare("[INFLUXDB]") == 0) {
|
||||
cfc = new ClassFlowInfluxDB(&FlowControll);
|
||||
}
|
||||
|
||||
if (toUpper(_type).compare("[INFLUXDBV2]") == 0) {
|
||||
cfc = new ClassFlowInfluxDBv2(&FlowControll);
|
||||
}
|
||||
#endif //ENABLE_INFLUXDB
|
||||
if (toUpper(_type).compare("[INFLUXDBV2]") == 0) {
|
||||
cfc = new ClassFlowInfluxDBv2(&FlowControll);
|
||||
}
|
||||
#endif //ENABLE_INFLUXDB
|
||||
|
||||
#ifdef ENABLE_WEBHOOK
|
||||
if (toUpper(_type).compare("[WEBHOOK]") == 0)
|
||||
cfc = new ClassFlowWebhook(&FlowControll);
|
||||
if (toUpper(_type).compare("[WEBHOOK]") == 0) {
|
||||
cfc = new ClassFlowWebhook(&FlowControll);
|
||||
}
|
||||
#endif //ENABLE_WEBHOOK
|
||||
|
||||
if (toUpper(_type).compare("[POSTPROCESSING]") == 0) {
|
||||
@@ -410,7 +421,7 @@ bool ClassFlowControll::doFlow(string time)
|
||||
LogFile.WriteHeapInfo(zw);
|
||||
#endif
|
||||
|
||||
if (!FlowControll[i]->doFlow(time)){
|
||||
if (!FlowControll[i]->doFlow(time)) {
|
||||
repeat++;
|
||||
LogFile.WriteToFile(ESP_LOG_WARN, TAG, "Fehler im vorheriger Schritt - wird zum " + to_string(repeat) + ". Mal wiederholt");
|
||||
if (i) { i -= 1; } // vPrevious step must be repeated (probably take pictures)
|
||||
@@ -460,14 +471,14 @@ string ClassFlowControll::getReadoutAll(int _type)
|
||||
if (flowpostprocessing->PreValueUse) {
|
||||
if ((*numbers)[i]->PreValueOkay) {
|
||||
out = out + (*numbers)[i]->ReturnPreValue;
|
||||
}
|
||||
}
|
||||
else {
|
||||
out = out + "PreValue too old";
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
out = out + "PreValue deactivated";
|
||||
}
|
||||
}
|
||||
break;
|
||||
case READOUT_TYPE_RAWVALUE:
|
||||
out = out + (*numbers)[i]->ReturnRawValue;
|
||||
@@ -479,9 +490,9 @@ string ClassFlowControll::getReadoutAll(int _type)
|
||||
|
||||
if (i < (*numbers).size()-1) {
|
||||
out = out + "\r\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
// ESP_LOGD(TAG, "OUT: %s", out.c_str());
|
||||
// ESP_LOGD(TAG, "OUT: %s", out.c_str());
|
||||
}
|
||||
|
||||
return out;
|
||||
@@ -511,7 +522,7 @@ bool ClassFlowControll::UpdatePrevalue(std::string _newvalue, std::string _numbe
|
||||
char* p;
|
||||
|
||||
_newvalue = trim(_newvalue);
|
||||
//ESP_LOGD(TAG, "Input UpdatePreValue: %s", _newvalue.c_str());
|
||||
// ESP_LOGD(TAG, "Input UpdatePreValue: %s", _newvalue.c_str());
|
||||
|
||||
if (_newvalue.substr(0,8).compare("0.000000") == 0 || _newvalue.compare("0.0") == 0 || _newvalue.compare("0") == 0) {
|
||||
newvalueAsDouble = 0; // preset to value = 0
|
||||
@@ -527,10 +538,10 @@ bool ClassFlowControll::UpdatePrevalue(std::string _newvalue, std::string _numbe
|
||||
if (flowpostprocessing) {
|
||||
if (flowpostprocessing->SetPreValue(newvalueAsDouble, _numbers, _extern)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "UpdatePrevalue: ERROR - Class Post-Processing not initialized");
|
||||
@@ -541,13 +552,12 @@ bool ClassFlowControll::UpdatePrevalue(std::string _newvalue, std::string _numbe
|
||||
bool ClassFlowControll::ReadParameter(FILE* pfile, string& aktparamgraph)
|
||||
{
|
||||
std::vector<string> splitted;
|
||||
|
||||
aktparamgraph = trim(aktparamgraph);
|
||||
|
||||
if (aktparamgraph.size() == 0) {
|
||||
if (!this->GetNextParagraph(pfile, aktparamgraph)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((toUpper(aktparamgraph).compare("[AUTOTIMER]") != 0) && (toUpper(aktparamgraph).compare("[DEBUG]") != 0) &&
|
||||
@@ -560,8 +570,7 @@ bool ClassFlowControll::ReadParameter(FILE* pfile, string& aktparamgraph)
|
||||
splitted = ZerlegeZeile(aktparamgraph, " =");
|
||||
|
||||
if ((toUpper(splitted[0]) == "INTERVAL") && (splitted.size() > 1)) {
|
||||
if (isStringNumeric(splitted[1]))
|
||||
{
|
||||
if (isStringNumeric(splitted[1])) {
|
||||
AutoInterval = std::stof(splitted[1]);
|
||||
}
|
||||
}
|
||||
@@ -571,8 +580,7 @@ bool ClassFlowControll::ReadParameter(FILE* pfile, string& aktparamgraph)
|
||||
}
|
||||
|
||||
if ((toUpper(splitted[0]) == "DATAFILESRETENTION") && (splitted.size() > 1)) {
|
||||
if (isStringNumeric(splitted[1]))
|
||||
{
|
||||
if (isStringNumeric(splitted[1])) {
|
||||
LogFile.SetDataLogRetention(std::stoi(splitted[1]));
|
||||
}
|
||||
}
|
||||
@@ -595,12 +603,11 @@ bool ClassFlowControll::ReadParameter(FILE* pfile, string& aktparamgraph)
|
||||
/* If system reboot was not triggered by user and reboot was caused by execption -> keep log level to DEBUG */
|
||||
if (!getIsPlannedReboot() && (esp_reset_reason() == ESP_RST_PANIC)) {
|
||||
LogFile.setLogLevel(ESP_LOG_DEBUG);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((toUpper(splitted[0]) == "LOGFILESRETENTION") && (splitted.size() > 1)) {
|
||||
if (isStringNumeric(splitted[1]))
|
||||
{
|
||||
if (isStringNumeric(splitted[1])) {
|
||||
LogFile.SetLogFileRetention(std::stoi(splitted[1]));
|
||||
}
|
||||
}
|
||||
@@ -653,17 +660,17 @@ int ClassFlowControll::CleanTempFolder() {
|
||||
|
||||
while ((entry = readdir(dir)) != NULL) {
|
||||
std::string path = string(folderPath) + "/" + entry->d_name;
|
||||
if (entry->d_type == DT_REG) {
|
||||
if (unlink(path.c_str()) == 0) {
|
||||
deleted ++;
|
||||
}
|
||||
else {
|
||||
ESP_LOGE(TAG, "can't delete file: %s", path.c_str());
|
||||
}
|
||||
if (entry->d_type == DT_REG) {
|
||||
if (unlink(path.c_str()) == 0) {
|
||||
deleted ++;
|
||||
}
|
||||
else {
|
||||
ESP_LOGE(TAG, "can't delete file: %s", path.c_str());
|
||||
}
|
||||
}
|
||||
else if (entry->d_type == DT_DIR) {
|
||||
deleted += removeFolder(path.c_str(), TAG);
|
||||
}
|
||||
else if (entry->d_type == DT_DIR) {
|
||||
deleted += removeFolder(path.c_str(), TAG);
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
@@ -701,6 +708,9 @@ esp_err_t ClassFlowControll::GetJPGStream(std::string _fn, httpd_req_t *req)
|
||||
else if (_fn == "alg_roi.jpg") {
|
||||
#ifdef ALGROI_LOAD_FROM_MEM_AS_JPG // no CImageBasis needed to create alg_roi.jpg (ca. 790kB less RAM)
|
||||
if (aktstatus.find("Initialization (delayed)") != -1) {
|
||||
std::string filename = "/sdcard/html/Flowstate_initialization_delayed.jpg";
|
||||
result = send_file(req, filename);
|
||||
/*
|
||||
FILE* file = fopen("/sdcard/html/Flowstate_initialization_delayed.jpg", "rb");
|
||||
|
||||
if (!file) {
|
||||
@@ -709,8 +719,8 @@ esp_err_t ClassFlowControll::GetJPGStream(std::string _fn, httpd_req_t *req)
|
||||
}
|
||||
|
||||
fseek(file, 0, SEEK_END);
|
||||
long fileSize = ftell(file); /* how long is the file ? */
|
||||
fseek(file, 0, SEEK_SET); /* reset */
|
||||
long fileSize = ftell(file); // how long is the file ?
|
||||
fseek(file, 0, SEEK_SET); // reset
|
||||
|
||||
unsigned char* fileBuffer = (unsigned char*) malloc(fileSize);
|
||||
|
||||
@@ -726,8 +736,12 @@ esp_err_t ClassFlowControll::GetJPGStream(std::string _fn, httpd_req_t *req)
|
||||
httpd_resp_set_type(req, "image/jpeg");
|
||||
result = httpd_resp_send(req, (const char *)fileBuffer, fileSize);
|
||||
free(fileBuffer);
|
||||
*/
|
||||
}
|
||||
else if (aktstatus.find("Initialization") != -1) {
|
||||
std::string filename = "/sdcard/html/Flowstate_initialization.jpg";
|
||||
result = send_file(req, filename);
|
||||
/*
|
||||
FILE* file = fopen("/sdcard/html/Flowstate_initialization.jpg", "rb");
|
||||
|
||||
if (!file) {
|
||||
@@ -736,8 +750,8 @@ esp_err_t ClassFlowControll::GetJPGStream(std::string _fn, httpd_req_t *req)
|
||||
}
|
||||
|
||||
fseek(file, 0, SEEK_END);
|
||||
long fileSize = ftell(file); /* how long is the file ? */
|
||||
fseek(file, 0, SEEK_SET); /* reset */
|
||||
long fileSize = ftell(file); // how long is the file ?
|
||||
fseek(file, 0, SEEK_SET); // reset
|
||||
|
||||
unsigned char* fileBuffer = (unsigned char*) malloc(fileSize);
|
||||
|
||||
@@ -753,9 +767,13 @@ esp_err_t ClassFlowControll::GetJPGStream(std::string _fn, httpd_req_t *req)
|
||||
httpd_resp_set_type(req, "image/jpeg");
|
||||
result = httpd_resp_send(req, (const char *)fileBuffer, fileSize);
|
||||
free(fileBuffer);
|
||||
*/
|
||||
}
|
||||
else if (aktstatus.find("Take Image") != -1) {
|
||||
if (flowalignment && flowalignment->AlgROI) {
|
||||
std::string filename = "/sdcard/html/Flowstate_take_image.jpg";
|
||||
result = send_file(req, filename);
|
||||
/*
|
||||
FILE* file = fopen("/sdcard/html/Flowstate_take_image.jpg", "rb");
|
||||
|
||||
if (!file) {
|
||||
@@ -764,8 +782,8 @@ esp_err_t ClassFlowControll::GetJPGStream(std::string _fn, httpd_req_t *req)
|
||||
}
|
||||
|
||||
fseek(file, 0, SEEK_END);
|
||||
flowalignment->AlgROI->size = ftell(file); /* how long is the file ? */
|
||||
fseek(file, 0, SEEK_SET); /* reset */
|
||||
flowalignment->AlgROI->size = ftell(file); // how long is the file ?
|
||||
fseek(file, 0, SEEK_SET); // reset
|
||||
|
||||
if (flowalignment->AlgROI->size > MAX_JPG_SIZE) {
|
||||
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "File /sdcard/html/Flowstate_take_image.jpg (" + std::to_string(flowalignment->AlgROI->size) +
|
||||
@@ -779,6 +797,7 @@ esp_err_t ClassFlowControll::GetJPGStream(std::string _fn, httpd_req_t *req)
|
||||
|
||||
httpd_resp_set_type(req, "image/jpeg");
|
||||
result = httpd_resp_send(req, (const char *)flowalignment->AlgROI->data, flowalignment->AlgROI->size);
|
||||
*/
|
||||
}
|
||||
else {
|
||||
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "ClassFlowControll::GetJPGStream: alg_roi.jpg cannot be served -> alg.jpg is going to be served!");
|
||||
@@ -817,9 +836,9 @@ esp_err_t ClassFlowControll::GetJPGStream(std::string _fn, httpd_req_t *req)
|
||||
_send = new CImageBasis("alg_roi", flowalignment->ImageBasis);
|
||||
|
||||
if (_send->ImageOkay()) {
|
||||
if (flowalignment) flowalignment->DrawRef(_send);
|
||||
if (flowdigit) flowdigit->DrawROI(_send);
|
||||
if (flowanalog) flowanalog->DrawROI(_send);
|
||||
if (flowalignment) { flowalignment->DrawRef(_send); }
|
||||
if (flowdigit) { flowdigit->DrawROI(_send); }
|
||||
if (flowanalog) { flowanalog->DrawROI(_send); }
|
||||
_sendDelete = true; // delete temporary _send element after sending
|
||||
}
|
||||
else {
|
||||
@@ -841,62 +860,60 @@ esp_err_t ClassFlowControll::GetJPGStream(std::string _fn, httpd_req_t *req)
|
||||
htmlinfo = GetAllDigit();
|
||||
ESP_LOGD(TAG, "After getClassFlowControll::GetAllDigit");
|
||||
|
||||
for (int i = 0; i < htmlinfo.size(); ++i)
|
||||
{
|
||||
if (_fn == htmlinfo[i]->filename)
|
||||
{
|
||||
if (htmlinfo[i]->image)
|
||||
for (int i = 0; i < htmlinfo.size(); ++i) {
|
||||
if (_fn == htmlinfo[i]->filename) {
|
||||
if (htmlinfo[i]->image) {
|
||||
_send = htmlinfo[i]->image;
|
||||
}
|
||||
}
|
||||
|
||||
if (_fn == htmlinfo[i]->filename_org)
|
||||
{
|
||||
if (htmlinfo[i]->image_org)
|
||||
if (_fn == htmlinfo[i]->filename_org) {
|
||||
if (htmlinfo[i]->image_org) {
|
||||
_send = htmlinfo[i]->image_org;
|
||||
}
|
||||
}
|
||||
delete htmlinfo[i];
|
||||
}
|
||||
htmlinfo.clear();
|
||||
|
||||
if (!_send)
|
||||
{
|
||||
htmlinfo = GetAllAnalog();
|
||||
ESP_LOGD(TAG, "After getClassFlowControll::GetAllAnalog");
|
||||
if (!_send) {
|
||||
htmlinfo = GetAllAnalog();
|
||||
ESP_LOGD(TAG, "After getClassFlowControll::GetAllAnalog");
|
||||
|
||||
for (int i = 0; i < htmlinfo.size(); ++i)
|
||||
{
|
||||
if (_fn == htmlinfo[i]->filename)
|
||||
{
|
||||
if (htmlinfo[i]->image)
|
||||
_send = htmlinfo[i]->image;
|
||||
}
|
||||
for (int i = 0; i < htmlinfo.size(); ++i) {
|
||||
if (_fn == htmlinfo[i]->filename) {
|
||||
if (htmlinfo[i]->image) {
|
||||
_send = htmlinfo[i]->image;
|
||||
}
|
||||
}
|
||||
|
||||
if (_fn == htmlinfo[i]->filename_org)
|
||||
{
|
||||
if (htmlinfo[i]->image_org)
|
||||
_send = htmlinfo[i]->image_org;
|
||||
}
|
||||
delete htmlinfo[i];
|
||||
}
|
||||
htmlinfo.clear();
|
||||
}
|
||||
if (_fn == htmlinfo[i]->filename_org) {
|
||||
if (htmlinfo[i]->image_org) {
|
||||
_send = htmlinfo[i]->image_org;
|
||||
}
|
||||
}
|
||||
delete htmlinfo[i];
|
||||
}
|
||||
htmlinfo.clear();
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG_DETAIL_ON
|
||||
LogFile.WriteHeapInfo("ClassFlowControll::GetJPGStream - before send");
|
||||
#endif
|
||||
|
||||
if (_send)
|
||||
{
|
||||
if (_send) {
|
||||
ESP_LOGD(TAG, "Sending file: %s ...", _fn.c_str());
|
||||
set_content_type_from_file(req, _fn.c_str());
|
||||
result = _send->SendJPGtoHTTP(req);
|
||||
|
||||
/* Respond with an empty chunk to signal HTTP response completion */
|
||||
httpd_resp_send_chunk(req, NULL, 0);
|
||||
ESP_LOGD(TAG, "File sending complete");
|
||||
|
||||
if (_sendDelete)
|
||||
if (_sendDelete) {
|
||||
delete _send;
|
||||
}
|
||||
|
||||
_send = NULL;
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ enum t_RateType {
|
||||
RateChange // time difference is considered and a normalized rate is used for comparison with NumberPost.maxRate
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Holds all properties and settings of a sequence. A sequence is a set of digit and/or analog ROIs that are combined to
|
||||
* provide one meter reading (value).
|
||||
@@ -45,6 +44,7 @@ struct NumberPost {
|
||||
int ChangeRateThreshold; // threshold parameter for negative rate detection
|
||||
bool PreValueOkay; // previousValueValid; indicates that the reading of the previous round has no errors
|
||||
bool AllowNegativeRates; // allowNegativeRate; defines if the consistency checks allow negative rates between consecutive meter readings.
|
||||
bool IgnoreLeadingNaN;
|
||||
bool checkDigitIncreaseConsistency; // extendedConsistencyCheck; performs an additional consistency check to avoid wrong readings
|
||||
time_t timeStampLastValue; // Timestamp for the last read value; is used for the log
|
||||
time_t timeStampLastPreValue; // Timestamp for the last PreValue set; is used for useMaxRateValue
|
||||
@@ -66,7 +66,7 @@ struct NumberPost {
|
||||
float AnalogToDigitTransitionStart; // AnalogToDigitTransitionStartValue; FIXME: need a better description; When is the digit > x.1, i.e. when does it start to tilt?
|
||||
int Nachkomma; // decimalPlaces; usually defined by the number of analog ROIs; affected by DecimalShift
|
||||
|
||||
string DomoticzIdx; // Domoticz counter Idx
|
||||
string DomoticzIdx; // Domoticz counter Idx
|
||||
|
||||
string FieldV1; // influxdbFieldName_v1; Name of the Field in InfluxDBv1
|
||||
string MeasurementV1; // influxdbMeasurementName_v1; Name of the Measurement in InfluxDBv1
|
||||
@@ -83,4 +83,3 @@ struct NumberPost {
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -117,7 +117,12 @@ bool ClassFlowInfluxDB::ReadParameter(FILE* pfile, string& aktparamgraph)
|
||||
{
|
||||
// ESP_LOGD(TAG, "Init InfluxDB with uri: %s, measurement: %s, user: %s, password: %s", uri.c_str(), measurement.c_str(), user.c_str(), password.c_str());
|
||||
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Init InfluxDB with uri: " + uri + ", user: " + user + ", password: " + password);
|
||||
InfluxDBInit(uri, database, user, password);
|
||||
|
||||
/////////////////////// NEW //////////////////////////
|
||||
// InfluxDBInit(uri, database, user, password);
|
||||
influxDB.InfluxDBInitV1(uri, database, user, password);
|
||||
/////////////////////// NEW //////////////////////////
|
||||
|
||||
InfluxDBenable = true;
|
||||
} else {
|
||||
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "InfluxDB init skipped as we are missing some parameters");
|
||||
@@ -169,7 +174,12 @@ bool ClassFlowInfluxDB::doFlow(string zwtime)
|
||||
}
|
||||
|
||||
if (result.length() > 0)
|
||||
InfluxDBPublish(measurement, namenumber, result, timeutc);
|
||||
//////////////////////// NEW //////////////////////////
|
||||
// InfluxDBPublish(measurement, namenumber, result, timeutc);
|
||||
influxDB.InfluxDBPublish(measurement, namenumber, result, timeutc);
|
||||
//////////////////////// NEW //////////////////////////
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "ClassFlow.h"
|
||||
|
||||
#include "ClassFlowPostProcessing.h"
|
||||
#include "interface_influxdb.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
@@ -21,6 +22,8 @@ protected:
|
||||
std::string user, password;
|
||||
bool InfluxDBenable;
|
||||
|
||||
InfluxDB influxDB;
|
||||
|
||||
void SetInitialParameter(void);
|
||||
|
||||
void handleFieldname(string _decsep, string _value);
|
||||
|
||||
@@ -123,7 +123,14 @@ bool ClassFlowInfluxDBv2::ReadParameter(FILE* pfile, string& aktparamgraph)
|
||||
{
|
||||
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Init InfluxDB with uri: " + uri + ", org: " + dborg + ", token: *****");
|
||||
// printf("vor V2 Init\n");
|
||||
InfluxDB_V2_Init(uri, bucket, dborg, dbtoken);
|
||||
|
||||
|
||||
////////////////////////////////////////// NEW ////////////////////////////////////////////
|
||||
// InfluxDB_V2_Init(uri, bucket, dborg, dbtoken);
|
||||
// InfluxDB_V2_Init(uri, bucket, dborg, dbtoken);
|
||||
influxdb.InfluxDBInitV2(uri, bucket, dborg, dbtoken);
|
||||
////////////////////////////////////////// NEW ////////////////////////////////////////////
|
||||
|
||||
// printf("nach V2 Init\n");
|
||||
InfluxDBenable = true;
|
||||
} else {
|
||||
@@ -232,7 +239,8 @@ 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, resulttimeutc);
|
||||
influxdb.InfluxDBPublish(measurement, namenumber, result, resulttimeutc);
|
||||
// InfluxDB_V2_Publish(measurement, namenumber, result, resulttimeutc);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
|
||||
#include "ClassFlowPostProcessing.h"
|
||||
|
||||
#include "interface_influxdb.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
class ClassFlowInfluxDBv2 :
|
||||
@@ -21,6 +23,8 @@ protected:
|
||||
ClassFlowPostProcessing* flowpostprocessing;
|
||||
bool InfluxDBenable;
|
||||
|
||||
InfluxDB influxdb;
|
||||
|
||||
void SetInitialParameter(void);
|
||||
|
||||
void handleFieldname(string _decsep, string _value);
|
||||
|
||||
@@ -40,6 +40,7 @@ void ClassFlowMQTT::SetInitialParameter(void)
|
||||
caCertFilename = "";
|
||||
clientCertFilename = "";
|
||||
clientKeyFilename = "";
|
||||
validateServerCert = true;
|
||||
clientname = wlan_config.hostname;
|
||||
|
||||
OldValue = "";
|
||||
@@ -109,15 +110,19 @@ bool ClassFlowMQTT::ReadParameter(FILE* pfile, string& aktparamgraph)
|
||||
std::string _param = GetParameterName(splitted[0]);
|
||||
if ((toUpper(_param) == "CACERT") && (splitted.size() > 1))
|
||||
{
|
||||
this->caCertFilename = splitted[1];
|
||||
this->caCertFilename = "/sdcard" + splitted[1];
|
||||
}
|
||||
if ((toUpper(_param) == "VALIDATESERVERCERT") && (splitted.size() > 1))
|
||||
{
|
||||
validateServerCert = alphanumericToBoolean(splitted[1]);
|
||||
}
|
||||
if ((toUpper(_param) == "CLIENTCERT") && (splitted.size() > 1))
|
||||
{
|
||||
this->clientCertFilename = splitted[1];
|
||||
this->clientCertFilename = "/sdcard" + splitted[1];
|
||||
}
|
||||
if ((toUpper(_param) == "CLIENTKEY") && (splitted.size() > 1))
|
||||
{
|
||||
this->clientKeyFilename = splitted[1];
|
||||
this->clientKeyFilename = "/sdcard" + splitted[1];
|
||||
}
|
||||
if ((toUpper(_param) == "USER") && (splitted.size() > 1))
|
||||
{
|
||||
@@ -133,10 +138,8 @@ bool ClassFlowMQTT::ReadParameter(FILE* pfile, string& aktparamgraph)
|
||||
}
|
||||
if ((toUpper(_param) == "RETAINMESSAGES") && (splitted.size() > 1))
|
||||
{
|
||||
if (toUpper(splitted[1]) == "TRUE") {
|
||||
SetRetainFlag = true;
|
||||
setMqtt_Server_Retain(SetRetainFlag);
|
||||
}
|
||||
SetRetainFlag = alphanumericToBoolean(splitted[1]);
|
||||
setMqtt_Server_Retain(SetRetainFlag);
|
||||
}
|
||||
if ((toUpper(_param) == "HOMEASSISTANTDISCOVERY") && (splitted.size() > 1))
|
||||
{
|
||||
@@ -153,7 +156,7 @@ bool ClassFlowMQTT::ReadParameter(FILE* pfile, string& aktparamgraph)
|
||||
mqttServer_setMeterType("water", "L", "h", "L/h");
|
||||
}
|
||||
else if (toUpper(splitted[1]) == "WATER_FT3") {
|
||||
mqttServer_setMeterType("water", "ft³", "m", "ft³/m"); // m = Minutes
|
||||
mqttServer_setMeterType("water", "ft³", "min", "ft³/min"); // min = Minutes
|
||||
}
|
||||
else if (toUpper(splitted[1]) == "WATER_GAL") {
|
||||
mqttServer_setMeterType("water", "gal", "h", "gal/h");
|
||||
@@ -162,7 +165,7 @@ bool ClassFlowMQTT::ReadParameter(FILE* pfile, string& aktparamgraph)
|
||||
mqttServer_setMeterType("gas", "m³", "h", "m³/h");
|
||||
}
|
||||
else if (toUpper(splitted[1]) == "GAS_FT3") {
|
||||
mqttServer_setMeterType("gas", "ft³", "m", "ft³/m"); // m = Minutes
|
||||
mqttServer_setMeterType("gas", "ft³", "min", "ft³/min"); // min = Minutes
|
||||
}
|
||||
else if (toUpper(splitted[1]) == "ENERGY_WH") {
|
||||
mqttServer_setMeterType("energy", "Wh", "h", "W");
|
||||
@@ -176,6 +179,15 @@ bool ClassFlowMQTT::ReadParameter(FILE* pfile, string& aktparamgraph)
|
||||
else if (toUpper(splitted[1]) == "ENERGY_GJ") {
|
||||
mqttServer_setMeterType("energy", "GJ", "h", "GJ/h");
|
||||
}
|
||||
else if (toUpper(splitted[1]) == "TEMPERATURE_C") {
|
||||
mqttServer_setMeterType("temperature", "°C", "min", "°C/min"); // min = Minutes
|
||||
}
|
||||
else if (toUpper(splitted[1]) == "TEMPERATURE_F") {
|
||||
mqttServer_setMeterType("temperature", "°F", "min", "°F/min"); // min = Minutes
|
||||
}
|
||||
else if (toUpper(splitted[1]) == "TEMPERATURE_K") {
|
||||
mqttServer_setMeterType("temperature", "K", "min", "K/m"); // min = Minutes
|
||||
}
|
||||
}
|
||||
|
||||
if ((toUpper(_param) == "CLIENTID") && (splitted.size() > 1))
|
||||
@@ -225,7 +237,7 @@ bool ClassFlowMQTT::Start(float AutoInterval)
|
||||
mqttServer_setParameter(flowpostprocessing->GetNumbers(), keepAlive, roundInterval);
|
||||
|
||||
bool MQTTConfigCheck = MQTT_Configure(uri, clientname, user, password, maintopic, domoticzintopic, LWT_TOPIC, LWT_CONNECTED,
|
||||
LWT_DISCONNECTED, caCertFilename, clientCertFilename, clientKeyFilename,
|
||||
LWT_DISCONNECTED, caCertFilename, validateServerCert, clientCertFilename, clientKeyFilename,
|
||||
keepAlive, SetRetainFlag, (void *)&GotConnected);
|
||||
|
||||
if (!MQTTConfigCheck) {
|
||||
@@ -367,4 +379,4 @@ void ClassFlowMQTT::handleIdx(string _decsep, string _value)
|
||||
}
|
||||
}
|
||||
|
||||
#endif //ENABLE_MQTT
|
||||
#endif //ENABLE_MQTT
|
||||
|
||||
@@ -19,7 +19,8 @@ protected:
|
||||
std::string OldValue;
|
||||
ClassFlowPostProcessing* flowpostprocessing;
|
||||
std::string user, password;
|
||||
std::string caCertFilename, clientCertFilename, clientKeyFilename;
|
||||
std::string caCertFilename, clientCertFilename, clientKeyFilename;
|
||||
bool validateServerCert;
|
||||
bool SetRetainFlag;
|
||||
int keepAlive; // Seconds
|
||||
float roundInterval; // Minutes
|
||||
|
||||
@@ -320,7 +320,6 @@ ClassFlowPostProcessing::ClassFlowPostProcessing(std::vector<ClassFlow*>* lfc, C
|
||||
ListFlowControll = lfc;
|
||||
flowTakeImage = NULL;
|
||||
UpdatePreValueINI = false;
|
||||
IgnoreLeadingNaN = false;
|
||||
flowAnalog = _analog;
|
||||
flowDigit = _digit;
|
||||
|
||||
@@ -431,6 +430,27 @@ void ClassFlowPostProcessing::handleAllowNegativeRate(string _decsep, string _va
|
||||
}
|
||||
}
|
||||
|
||||
void ClassFlowPostProcessing::handleIgnoreLeadingNaN(string _decsep, string _value) {
|
||||
string _digit, _decpos;
|
||||
int _pospunkt = _decsep.find_first_of(".");
|
||||
|
||||
if (_pospunkt > -1) {
|
||||
_digit = _decsep.substr(0, _pospunkt);
|
||||
}
|
||||
else {
|
||||
_digit = "default";
|
||||
}
|
||||
|
||||
for (int j = 0; j < NUMBERS.size(); ++j) {
|
||||
bool _zwdc = alphanumericToBoolean(_value);
|
||||
|
||||
// Set to default first (if nothing else is set)
|
||||
if ((_digit == "default") || (NUMBERS[j]->name == _digit)) {
|
||||
NUMBERS[j]->IgnoreLeadingNaN = _zwdc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ClassFlowPostProcessing::handleMaxRateType(string _decsep, string _value) {
|
||||
string _digit, _decpos;
|
||||
int _pospunkt = _decsep.find_first_of(".");
|
||||
@@ -509,7 +529,7 @@ void ClassFlowPostProcessing::handleChangeRateThreshold(string _decsep, string _
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
|
||||
void ClassFlowPostProcessing::handlecheckDigitIncreaseConsistency(std::string _decsep, std::string _value)
|
||||
{
|
||||
std::string _digit;
|
||||
@@ -532,7 +552,7 @@ void ClassFlowPostProcessing::handlecheckDigitIncreaseConsistency(std::string _d
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
bool ClassFlowPostProcessing::ReadParameter(FILE* pfile, string& aktparamgraph) {
|
||||
std::vector<string> splitted;
|
||||
int _n;
|
||||
@@ -585,12 +605,7 @@ bool ClassFlowPostProcessing::ReadParameter(FILE* pfile, string& aktparamgraph)
|
||||
}
|
||||
|
||||
if ((toUpper(_param) == "CHECKDIGITINCREASECONSISTENCY") && (splitted.size() > 1)) {
|
||||
// handlecheckDigitIncreaseConsistency(splitted[0], splitted[1]);
|
||||
if (alphanumericToBoolean(splitted[1])) {
|
||||
for (_n = 0; _n < NUMBERS.size(); ++_n) {
|
||||
NUMBERS[_n]->checkDigitIncreaseConsistency = true;
|
||||
}
|
||||
}
|
||||
handlecheckDigitIncreaseConsistency(splitted[0], splitted[1]);
|
||||
}
|
||||
|
||||
if ((toUpper(_param) == "ALLOWNEGATIVERATES") && (splitted.size() > 1)) {
|
||||
@@ -602,7 +617,7 @@ bool ClassFlowPostProcessing::ReadParameter(FILE* pfile, string& aktparamgraph)
|
||||
}
|
||||
|
||||
if ((toUpper(_param) == "IGNORELEADINGNAN") && (splitted.size() > 1)) {
|
||||
IgnoreLeadingNaN = alphanumericToBoolean(splitted[1]);
|
||||
handleIgnoreLeadingNaN(splitted[0], splitted[1]);
|
||||
}
|
||||
|
||||
if ((toUpper(_param) == "PREVALUEAGESTARTUP") && (splitted.size() > 1)) {
|
||||
@@ -670,6 +685,7 @@ void ClassFlowPostProcessing::InitNUMBERS() {
|
||||
_number->FlowRateAct = 0; // m3 / min
|
||||
_number->PreValueOkay = false;
|
||||
_number->AllowNegativeRates = false;
|
||||
_number->IgnoreLeadingNaN = false;
|
||||
_number->MaxRateValue = 0.1;
|
||||
_number->MaxRateType = AbsoluteChange;
|
||||
_number->useMaxRateValue = false;
|
||||
@@ -821,7 +837,7 @@ bool ClassFlowPostProcessing::doFlow(string zwtime) {
|
||||
ESP_LOGD(TAG, "After ShiftDecimal: ReturnRaw %s", NUMBERS[j]->ReturnRawValue.c_str());
|
||||
#endif
|
||||
|
||||
if (IgnoreLeadingNaN) {
|
||||
if (NUMBERS[j]->IgnoreLeadingNaN) {
|
||||
while ((NUMBERS[j]->ReturnRawValue.length() > 1) && (NUMBERS[j]->ReturnRawValue[0] == 'N')) {
|
||||
NUMBERS[j]->ReturnRawValue.erase(0, 1);
|
||||
}
|
||||
@@ -868,12 +884,7 @@ bool ClassFlowPostProcessing::doFlow(string zwtime) {
|
||||
|
||||
if (NUMBERS[j]->checkDigitIncreaseConsistency) {
|
||||
if (flowDigit) {
|
||||
if (flowDigit->getCNNType() != Digit) {
|
||||
ESP_LOGD(TAG, "checkDigitIncreaseConsistency = true - ignored due to wrong CNN-Type (not Digit Classification)");
|
||||
}
|
||||
else {
|
||||
NUMBERS[j]->Value = checkDigitConsistency(NUMBERS[j]->Value, NUMBERS[j]->DecimalShift, NUMBERS[j]->analog_roi != NULL, NUMBERS[j]->PreValue);
|
||||
}
|
||||
NUMBERS[j]->Value = checkDigitConsistency(NUMBERS[j]->Value, NUMBERS[j]->DecimalShift, NUMBERS[j]->analog_roi != NULL, NUMBERS[j]->PreValue);
|
||||
}
|
||||
else {
|
||||
#ifdef SERIAL_DEBUG
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
|
||||
class ClassFlowPostProcessing :
|
||||
public ClassFlow
|
||||
{
|
||||
@@ -19,8 +18,7 @@ protected:
|
||||
|
||||
int PreValueAgeStartup;
|
||||
bool ErrorMessage;
|
||||
bool IgnoreLeadingNaN; // SPECIAL CASE for User Gustl ???
|
||||
|
||||
|
||||
ClassFlowCNNGeneral* flowAnalog;
|
||||
ClassFlowCNNGeneral* flowDigit;
|
||||
|
||||
@@ -35,15 +33,16 @@ protected:
|
||||
float checkDigitConsistency(double input, int _decilamshift, bool _isanalog, double _preValue);
|
||||
|
||||
void InitNUMBERS();
|
||||
|
||||
void handleDecimalSeparator(string _decsep, string _value);
|
||||
void handleMaxRateValue(string _decsep, string _value);
|
||||
void handleDecimalExtendedResolution(string _decsep, string _value);
|
||||
void handleMaxRateType(string _decsep, string _value);
|
||||
void handleAnalogToDigitTransitionStart(string _decsep, string _value);
|
||||
void handleAllowNegativeRate(string _decsep, string _value);
|
||||
void handleIgnoreLeadingNaN(string _decsep, string _value);
|
||||
void handleChangeRateThreshold(string _decsep, string _value);
|
||||
|
||||
std::string GetStringReadouts(general);
|
||||
void handlecheckDigitIncreaseConsistency(std::string _decsep, std::string _value);
|
||||
|
||||
void WriteDataLog(int _index);
|
||||
|
||||
@@ -75,5 +74,4 @@ public:
|
||||
string name(){return "ClassFlowPostProcessing";};
|
||||
};
|
||||
|
||||
|
||||
#endif //CLASSFFLOWPOSTPROCESSING_H
|
||||
|
||||
@@ -133,25 +133,25 @@ bool ClassFlowTakeImage::ReadParameter(FILE *pfile, string &aktparamgraph)
|
||||
switch (_ImageGainceiling_)
|
||||
{
|
||||
case 1:
|
||||
CFstatus.ImageGainceiling = GAINCEILING_4X;
|
||||
CCstatus.ImageGainceiling = GAINCEILING_4X;
|
||||
break;
|
||||
case 2:
|
||||
CFstatus.ImageGainceiling = GAINCEILING_8X;
|
||||
CCstatus.ImageGainceiling = GAINCEILING_8X;
|
||||
break;
|
||||
case 3:
|
||||
CFstatus.ImageGainceiling = GAINCEILING_16X;
|
||||
CCstatus.ImageGainceiling = GAINCEILING_16X;
|
||||
break;
|
||||
case 4:
|
||||
CFstatus.ImageGainceiling = GAINCEILING_32X;
|
||||
CCstatus.ImageGainceiling = GAINCEILING_32X;
|
||||
break;
|
||||
case 5:
|
||||
CFstatus.ImageGainceiling = GAINCEILING_64X;
|
||||
CCstatus.ImageGainceiling = GAINCEILING_64X;
|
||||
break;
|
||||
case 6:
|
||||
CFstatus.ImageGainceiling = GAINCEILING_128X;
|
||||
CCstatus.ImageGainceiling = GAINCEILING_128X;
|
||||
break;
|
||||
default:
|
||||
CFstatus.ImageGainceiling = GAINCEILING_2X;
|
||||
CCstatus.ImageGainceiling = GAINCEILING_2X;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -251,7 +251,7 @@ bool ClassFlowTakeImage::ReadParameter(FILE *pfile, string &aktparamgraph)
|
||||
if (isStringNumeric(_ImageSpecialEffect))
|
||||
{
|
||||
int _ImageSpecialEffect_ = std::stoi(_ImageSpecialEffect);
|
||||
CFstatus.ImageSpecialEffect = clipInt(_ImageSpecialEffect_, 6, 0);
|
||||
CCstatus.ImageSpecialEffect = clipInt(_ImageSpecialEffect_, 6, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -293,7 +293,7 @@ bool ClassFlowTakeImage::ReadParameter(FILE *pfile, string &aktparamgraph)
|
||||
if (isStringNumeric(_ImageWbMode))
|
||||
{
|
||||
int _ImageWbMode_ = std::stoi(_ImageWbMode);
|
||||
CFstatus.ImageWbMode = clipInt(_ImageWbMode_, 4, 0);
|
||||
CCstatus.ImageWbMode = clipInt(_ImageWbMode_, 4, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "read_wlanini.h"
|
||||
#include "connect_wlan.h"
|
||||
#include "psram.h"
|
||||
#include "basic_auth.h"
|
||||
|
||||
// support IDF 5.x
|
||||
#ifndef portTICK_RATE_MS
|
||||
@@ -1280,7 +1281,7 @@ esp_err_t handler_editflow(httpd_req_t *req)
|
||||
int _ImageDenoiseLevel = std::stoi(_valuechar);
|
||||
if (CCstatus.CamSensor_id == OV2640_PID)
|
||||
{
|
||||
CCstatus.ImageDenoiseLevel = 0;
|
||||
CFstatus.ImageDenoiseLevel = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1782,108 +1783,108 @@ void register_server_main_flow_task_uri(httpd_handle_t server)
|
||||
camuri.method = HTTP_GET;
|
||||
|
||||
camuri.uri = "/doinit";
|
||||
camuri.handler = handler_init;
|
||||
camuri.handler = APPLY_BASIC_AUTH_FILTER(handler_init);
|
||||
camuri.user_ctx = (void *)"Light On";
|
||||
httpd_register_uri_handler(server, &camuri);
|
||||
|
||||
// Legacy API => New: "/setPreValue"
|
||||
camuri.uri = "/setPreValue.html";
|
||||
camuri.handler = handler_prevalue;
|
||||
camuri.handler = APPLY_BASIC_AUTH_FILTER(handler_prevalue);
|
||||
camuri.user_ctx = (void *)"Prevalue";
|
||||
httpd_register_uri_handler(server, &camuri);
|
||||
|
||||
camuri.uri = "/setPreValue";
|
||||
camuri.handler = handler_prevalue;
|
||||
camuri.handler = APPLY_BASIC_AUTH_FILTER(handler_prevalue);
|
||||
camuri.user_ctx = (void *)"Prevalue";
|
||||
httpd_register_uri_handler(server, &camuri);
|
||||
|
||||
camuri.uri = "/flow_start";
|
||||
camuri.handler = handler_flow_start;
|
||||
camuri.handler = APPLY_BASIC_AUTH_FILTER(handler_flow_start);
|
||||
camuri.user_ctx = (void *)"Flow Start";
|
||||
httpd_register_uri_handler(server, &camuri);
|
||||
|
||||
camuri.uri = "/statusflow.html";
|
||||
camuri.handler = handler_statusflow;
|
||||
camuri.handler = APPLY_BASIC_AUTH_FILTER(handler_statusflow);
|
||||
camuri.user_ctx = (void *)"Light Off";
|
||||
httpd_register_uri_handler(server, &camuri);
|
||||
|
||||
camuri.uri = "/statusflow";
|
||||
camuri.handler = handler_statusflow;
|
||||
camuri.handler = APPLY_BASIC_AUTH_FILTER(handler_statusflow);
|
||||
camuri.user_ctx = (void *)"Light Off";
|
||||
httpd_register_uri_handler(server, &camuri);
|
||||
|
||||
// Legacy API => New: "/cpu_temperature"
|
||||
camuri.uri = "/cputemp.html";
|
||||
camuri.handler = handler_cputemp;
|
||||
camuri.handler = APPLY_BASIC_AUTH_FILTER(handler_cputemp);
|
||||
camuri.user_ctx = (void *)"Light Off";
|
||||
httpd_register_uri_handler(server, &camuri);
|
||||
|
||||
camuri.uri = "/cpu_temperature";
|
||||
camuri.handler = handler_cputemp;
|
||||
camuri.handler = APPLY_BASIC_AUTH_FILTER(handler_cputemp);
|
||||
camuri.user_ctx = (void *)"Light Off";
|
||||
httpd_register_uri_handler(server, &camuri);
|
||||
|
||||
// Legacy API => New: "/rssi"
|
||||
camuri.uri = "/rssi.html";
|
||||
camuri.handler = handler_rssi;
|
||||
camuri.handler = APPLY_BASIC_AUTH_FILTER(handler_rssi);
|
||||
camuri.user_ctx = (void *)"Light Off";
|
||||
httpd_register_uri_handler(server, &camuri);
|
||||
|
||||
camuri.uri = "/rssi";
|
||||
camuri.handler = handler_rssi;
|
||||
camuri.handler = APPLY_BASIC_AUTH_FILTER(handler_rssi);
|
||||
camuri.user_ctx = (void *)"Light Off";
|
||||
httpd_register_uri_handler(server, &camuri);
|
||||
|
||||
camuri.uri = "/date";
|
||||
camuri.handler = handler_current_date;
|
||||
camuri.handler = APPLY_BASIC_AUTH_FILTER(handler_current_date);
|
||||
camuri.user_ctx = (void *)"Light Off";
|
||||
httpd_register_uri_handler(server, &camuri);
|
||||
|
||||
camuri.uri = "/uptime";
|
||||
camuri.handler = handler_uptime;
|
||||
camuri.handler = APPLY_BASIC_AUTH_FILTER(handler_uptime);
|
||||
camuri.user_ctx = (void *)"Light Off";
|
||||
httpd_register_uri_handler(server, &camuri);
|
||||
|
||||
camuri.uri = "/editflow";
|
||||
camuri.handler = handler_editflow;
|
||||
camuri.handler = APPLY_BASIC_AUTH_FILTER(handler_editflow);
|
||||
camuri.user_ctx = (void *)"EditFlow";
|
||||
httpd_register_uri_handler(server, &camuri);
|
||||
|
||||
// Legacy API => New: "/value"
|
||||
camuri.uri = "/value.html";
|
||||
camuri.handler = handler_wasserzaehler;
|
||||
camuri.handler = APPLY_BASIC_AUTH_FILTER(handler_wasserzaehler);
|
||||
camuri.user_ctx = (void *)"Value";
|
||||
httpd_register_uri_handler(server, &camuri);
|
||||
|
||||
camuri.uri = "/value";
|
||||
camuri.handler = handler_wasserzaehler;
|
||||
camuri.handler = APPLY_BASIC_AUTH_FILTER(handler_wasserzaehler);
|
||||
camuri.user_ctx = (void *)"Value";
|
||||
httpd_register_uri_handler(server, &camuri);
|
||||
|
||||
// Legacy API => New: "/value"
|
||||
camuri.uri = "/wasserzaehler.html";
|
||||
camuri.handler = handler_wasserzaehler;
|
||||
camuri.handler = APPLY_BASIC_AUTH_FILTER(handler_wasserzaehler);
|
||||
camuri.user_ctx = (void *)"Wasserzaehler";
|
||||
httpd_register_uri_handler(server, &camuri);
|
||||
|
||||
camuri.uri = "/json";
|
||||
camuri.handler = handler_json;
|
||||
camuri.handler = APPLY_BASIC_AUTH_FILTER(handler_json);
|
||||
camuri.user_ctx = (void *)"JSON";
|
||||
httpd_register_uri_handler(server, &camuri);
|
||||
|
||||
camuri.uri = "/heap";
|
||||
camuri.handler = handler_get_heap;
|
||||
camuri.handler = APPLY_BASIC_AUTH_FILTER(handler_get_heap);
|
||||
camuri.user_ctx = (void *)"Heap";
|
||||
httpd_register_uri_handler(server, &camuri);
|
||||
|
||||
camuri.uri = "/stream";
|
||||
camuri.handler = handler_stream;
|
||||
camuri.handler = APPLY_BASIC_AUTH_FILTER(handler_stream);
|
||||
camuri.user_ctx = (void *)"stream";
|
||||
httpd_register_uri_handler(server, &camuri);
|
||||
|
||||
/** will handle metrics requests */
|
||||
camuri.uri = "/metrics";
|
||||
camuri.handler = handler_openmetrics;
|
||||
camuri.handler = APPLY_BASIC_AUTH_FILTER(handler_openmetrics);
|
||||
camuri.user_ctx = (void *)"metrics";
|
||||
httpd_register_uri_handler(server, &camuri);
|
||||
|
||||
|
||||
@@ -365,19 +365,35 @@ size_t findDelimiterPos(string input, string delimiter)
|
||||
|
||||
bool RenameFile(string from, string to)
|
||||
{
|
||||
// ESP_LOGI(logTag, "Deleting file: %s", fn.c_str());
|
||||
/* Delete file */
|
||||
// ESP_LOGI(logTag, "Renaming File: %s", from.c_str());
|
||||
FILE *fpSourceFile = fopen(from.c_str(), "rb");
|
||||
|
||||
// Sourcefile existiert nicht sonst gibt es einen Fehler beim Kopierversuch!
|
||||
// Sourcefile does not exist otherwise there is a mistake when renaming!
|
||||
if (!fpSourceFile)
|
||||
{
|
||||
ESP_LOGE(TAG, "DeleteFile: File %s existiert nicht!", from.c_str());
|
||||
ESP_LOGE(TAG, "RenameFile: File %s does not exist!", from.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
fclose(fpSourceFile);
|
||||
rename(from.c_str(), to.c_str());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RenameFolder(string from, string to)
|
||||
{
|
||||
// ESP_LOGI(logTag, "Renaming Folder: %s", from.c_str());
|
||||
DIR *fpSourceFolder = opendir(from.c_str());
|
||||
|
||||
// Sourcefolder does not exist otherwise there is a mistake when renaming!
|
||||
if (!fpSourceFolder)
|
||||
{
|
||||
ESP_LOGE(TAG, "RenameFolder: Folder %s does not exist!", from.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
closedir(fpSourceFolder);
|
||||
rename(from.c_str(), to.c_str());
|
||||
|
||||
return true;
|
||||
@@ -387,7 +403,7 @@ bool FileExists(string filename)
|
||||
{
|
||||
FILE *fpSourceFile = fopen(filename.c_str(), "rb");
|
||||
|
||||
// Sourcefile existiert nicht sonst gibt es einen Fehler beim Kopierversuch!
|
||||
// Sourcefile does not exist
|
||||
if (!fpSourceFile)
|
||||
{
|
||||
return false;
|
||||
@@ -398,22 +414,36 @@ bool FileExists(string filename)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeleteFile(string fn)
|
||||
bool FolderExists(string foldername)
|
||||
{
|
||||
// ESP_LOGI(logTag, "Deleting file: %s", fn.c_str());
|
||||
/* Delete file */
|
||||
FILE *fpSourceFile = fopen(fn.c_str(), "rb");
|
||||
DIR *fpSourceFolder = opendir(foldername.c_str());
|
||||
|
||||
// Sourcefile existiert nicht sonst gibt es einen Fehler beim Kopierversuch!
|
||||
// Sourcefolder does not exist
|
||||
if (!fpSourceFolder)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
closedir(fpSourceFolder);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeleteFile(string filename)
|
||||
{
|
||||
// ESP_LOGI(logTag, "Deleting file: %s", filename.c_str());
|
||||
/* Delete file */
|
||||
FILE *fpSourceFile = fopen(filename.c_str(), "rb");
|
||||
|
||||
// Sourcefile does not exist otherwise there is a mistake in copying!
|
||||
if (!fpSourceFile)
|
||||
{
|
||||
ESP_LOGD(TAG, "DeleteFile: File %s existiert nicht!", fn.c_str());
|
||||
ESP_LOGD(TAG, "DeleteFile: File %s existiert nicht!", filename.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
fclose(fpSourceFile);
|
||||
|
||||
unlink(fn.c_str());
|
||||
unlink(filename.c_str());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -16,10 +16,12 @@ std::size_t file_size(const std::string& file_name);
|
||||
void FindReplace(std::string& line, std::string& oldString, std::string& newString);
|
||||
|
||||
bool CopyFile(string input, string output);
|
||||
bool DeleteFile(string fn);
|
||||
bool DeleteFile(string filename);
|
||||
bool RenameFile(string from, string to);
|
||||
bool RenameFolder(string from, string to);
|
||||
bool MakeDir(std::string _what);
|
||||
bool FileExists(string filename);
|
||||
bool FolderExists(string foldername);
|
||||
|
||||
string RundeOutput(double _in, int _anzNachkomma);
|
||||
|
||||
@@ -40,6 +42,7 @@ string toUpper(string in);
|
||||
|
||||
float temperatureRead();
|
||||
|
||||
std::string intToHexString(int _valueInt);
|
||||
time_t addDays(time_t startTime, int days);
|
||||
|
||||
void memCopyGen(uint8_t* _source, uint8_t* _target, int _size);
|
||||
|
||||
@@ -69,7 +69,6 @@ int SDCardCheckRW(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
bool SDCardCheckFolderFilePresence()
|
||||
{
|
||||
struct stat sb;
|
||||
@@ -125,25 +124,25 @@ bool SDCardCheckFolderFilePresence()
|
||||
}
|
||||
|
||||
/* check if file exists: index.html */
|
||||
if (stat("/sdcard/html/index.html", &sb) != 0) {
|
||||
if ((stat("/sdcard/html/index.html", &sb) != 0) && (stat("/sdcard/html/index.html.gz", &sb) != 0)) {
|
||||
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Folder/file check: File /html/index.html not found");
|
||||
bRetval = false;
|
||||
}
|
||||
|
||||
/* check if file exists: ota.html */
|
||||
if (stat("/sdcard/html/ota_page.html", &sb) != 0) {
|
||||
if ((stat("/sdcard/html/ota_page.html", &sb) != 0) && (stat("/sdcard/html/ota_page.html.gz", &sb) != 0)) {
|
||||
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Folder/file check: File /html/ota.html not found");
|
||||
bRetval = false;
|
||||
}
|
||||
|
||||
/* check if file exists: log.html */
|
||||
if (stat("/sdcard/html/log.html", &sb) != 0) {
|
||||
if ((stat("/sdcard/html/log.html", &sb) != 0) && (stat("/sdcard/html/log.html.gz", &sb) != 0)) {
|
||||
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Folder/file check: File /html/log.html not found");
|
||||
bRetval = false;
|
||||
}
|
||||
|
||||
/* check if file exists: common.js */
|
||||
if (stat("/sdcard/html/common.js", &sb) != 0) {
|
||||
if ((stat("/sdcard/html/common.js", &sb) != 0) && (stat("/sdcard/html/common.js.gz", &sb) != 0)) {
|
||||
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Folder/file check: File /html/common.js not found");
|
||||
bRetval = false;
|
||||
}
|
||||
@@ -154,8 +153,9 @@ bool SDCardCheckFolderFilePresence()
|
||||
bRetval = false;
|
||||
}
|
||||
|
||||
if (bRetval)
|
||||
if (bRetval) {
|
||||
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Folder/file presence check successful");
|
||||
}
|
||||
|
||||
return bRetval;
|
||||
}
|
||||
|
||||
@@ -8,95 +8,35 @@
|
||||
#include "time_sntp.h"
|
||||
#include "../../include/defines.h"
|
||||
|
||||
|
||||
static const char *TAG = "INFLUXDB";
|
||||
|
||||
std::string _influxDBURI;
|
||||
std::string _influxDBDatabase;
|
||||
std::string _influxDBUser;
|
||||
std::string _influxDBPassword;
|
||||
|
||||
std::string _influxDB_V2_URI;
|
||||
std::string _influxDB_V2_Bucket;
|
||||
std::string _influxDB_V2_Token;
|
||||
std::string _influxDB_V2_Org;
|
||||
|
||||
static esp_err_t http_event_handler(esp_http_client_event_t *evt);
|
||||
|
||||
void InfluxDB_V2_Init(std::string _uri, std::string _bucket, std::string _org, std::string _token)
|
||||
{
|
||||
_influxDB_V2_URI = _uri;
|
||||
_influxDB_V2_Bucket = _bucket;
|
||||
_influxDB_V2_Org = _org;
|
||||
_influxDB_V2_Token = _token;
|
||||
}
|
||||
|
||||
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 = {
|
||||
.user_agent = "ESP32 Meter reader",
|
||||
.method = HTTP_METHOD_POST,
|
||||
.event_handler = http_event_handler,
|
||||
.buffer_size = MAX_HTTP_OUTPUT_BUFFER,
|
||||
.user_data = response_buffer
|
||||
};
|
||||
|
||||
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 (_timeUTC > 0)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
payload.shrink_to_fit();
|
||||
|
||||
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "sending line to influxdb:" + payload);
|
||||
|
||||
std::string apiURI = _influxDB_V2_URI + "/api/v2/write?org=" + _influxDB_V2_Org + "&bucket=" + _influxDB_V2_Bucket;
|
||||
apiURI.shrink_to_fit();
|
||||
http_config.url = apiURI.c_str();
|
||||
ESP_LOGI(TAG, "http_config: %s", http_config.url); // Add mark on log to see when it restarted
|
||||
|
||||
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "API URI: " + apiURI);
|
||||
|
||||
esp_http_client_handle_t http_client = esp_http_client_init(&http_config);
|
||||
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "client is initialized");
|
||||
|
||||
esp_http_client_set_header(http_client, "Content-Type", "text/plain");
|
||||
std::string _zw = "Token " + _influxDB_V2_Token;
|
||||
// LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Tokenheader: %s\n", _zw.c_str());
|
||||
esp_http_client_set_header(http_client, "Authorization", _zw.c_str());
|
||||
|
||||
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "header is set");
|
||||
|
||||
ESP_ERROR_CHECK(esp_http_client_set_post_field(http_client, payload.c_str(), payload.length()));
|
||||
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "post payload is set");
|
||||
|
||||
esp_err_t err = ESP_ERROR_CHECK_WITHOUT_ABORT(esp_http_client_perform(http_client));
|
||||
|
||||
if( err == ESP_OK ) {
|
||||
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "HTTP request was performed");
|
||||
int status_code = esp_http_client_get_status_code(http_client);
|
||||
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "HTTP status code" + std::to_string(status_code));
|
||||
} else {
|
||||
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "HTTP request failed");
|
||||
}
|
||||
esp_http_client_cleanup(http_client);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Buffer to store the HTTP response.
|
||||
*
|
||||
* This character array is used to store the output of an HTTP response.
|
||||
* The size of the buffer is defined by the constant MAX_HTTP_OUTPUT_BUFFER.
|
||||
*/
|
||||
char response_buffer[MAX_HTTP_OUTPUT_BUFFER] = {0};
|
||||
|
||||
|
||||
/**
|
||||
* @brief HTTP event handler callback function.
|
||||
*
|
||||
* This function handles various HTTP client events and logs appropriate messages.
|
||||
*
|
||||
* @param evt Pointer to the HTTP client event structure.
|
||||
* @return esp_err_t ESP_OK on success.
|
||||
*
|
||||
* Event types handled:
|
||||
* - HTTP_EVENT_ERROR: Logs an error message when an HTTP error is encountered.
|
||||
* - HTTP_EVENT_ON_CONNECTED: Logs a message when the HTTP client successfully connects.
|
||||
* - HTTP_EVENT_HEADERS_SENT: Logs a message when all request headers are sent.
|
||||
* - HTTP_EVENT_ON_HEADER: Logs the received header key and value.
|
||||
* - HTTP_EVENT_ON_DATA: Logs the length of the received data.
|
||||
* - HTTP_EVENT_ON_FINISH: Logs a message when the HTTP client finishes the request.
|
||||
* - HTTP_EVENT_DISCONNECTED: Logs a message when the HTTP client disconnects.
|
||||
* - HTTP_EVENT_REDIRECT: Logs a message when an HTTP redirect occurs.
|
||||
*/
|
||||
static esp_err_t http_event_handler(esp_http_client_event_t *evt)
|
||||
{
|
||||
switch(evt->event_id)
|
||||
@@ -130,25 +70,123 @@ 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, long int _timeUTC) {
|
||||
char response_buffer[MAX_HTTP_OUTPUT_BUFFER] = {0};
|
||||
esp_http_client_config_t http_config = {
|
||||
.user_agent = "ESP32 Meter reader",
|
||||
.method = HTTP_METHOD_POST,
|
||||
.event_handler = http_event_handler,
|
||||
.buffer_size = MAX_HTTP_OUTPUT_BUFFER,
|
||||
.user_data = response_buffer
|
||||
};
|
||||
|
||||
if (_influxDBUser.length() && _influxDBPassword.length()){
|
||||
http_config.username = _influxDBUser.c_str();
|
||||
http_config.password = _influxDBPassword.c_str();
|
||||
http_config.auth_type = HTTP_AUTH_TYPE_BASIC;
|
||||
|
||||
/**
|
||||
* @brief Initializes the InfluxDB connection with version 1 settings.
|
||||
*
|
||||
* This function sets up the connection parameters for InfluxDB version 1.
|
||||
*
|
||||
* @param _influxDBURI The URI of the InfluxDB server.
|
||||
* @param _database The name of the database to connect to.
|
||||
* @param _user The username for authentication.
|
||||
* @param _password The password for authentication.
|
||||
*/
|
||||
void InfluxDB::InfluxDBInitV1(std::string _influxDBURI, std::string _database, std::string _user, std::string _password) {
|
||||
version = INFLUXDB_V1;
|
||||
influxDBURI = _influxDBURI;
|
||||
database = _database;
|
||||
user = _user;
|
||||
password = _password;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initializes the InfluxDB client with version 2 settings.
|
||||
*
|
||||
* This function sets up the InfluxDB client to use InfluxDB version 2 by
|
||||
* configuring the URI, bucket, organization, and token.
|
||||
*
|
||||
* @param _influxDBURI The URI of the InfluxDB server.
|
||||
* @param _bucket The bucket name to store data in.
|
||||
* @param _org The organization name associated with the bucket.
|
||||
* @param _token The authentication token for accessing the InfluxDB server.
|
||||
*/
|
||||
void InfluxDB::InfluxDBInitV2(std::string _influxDBURI, std::string _bucket, std::string _org, std::string _token) {
|
||||
version = INFLUXDB_V2;
|
||||
influxDBURI = _influxDBURI;
|
||||
bucket = _bucket;
|
||||
org = _org;
|
||||
token = _token;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Establishes an HTTP connection to the InfluxDB server.
|
||||
*
|
||||
* This function configures and initializes an HTTP client to connect to the InfluxDB server.
|
||||
* It sets up the necessary parameters such as the URL, event handler, buffer size, and user data.
|
||||
* Depending on the InfluxDB version, it also configures the authentication type and credentials.
|
||||
*
|
||||
* @note This function destroys any existing HTTP client before initializing a new one.
|
||||
*
|
||||
* @param None
|
||||
* @return None
|
||||
*/
|
||||
void InfluxDB::connectHTTP() {
|
||||
esp_http_client_config_t config = {};
|
||||
|
||||
config.url = influxDBURI.c_str();
|
||||
config.event_handler = http_event_handler;
|
||||
config.buffer_size = MAX_HTTP_OUTPUT_BUFFER;
|
||||
config.user_data = response_buffer;
|
||||
|
||||
|
||||
switch (version) {
|
||||
case INFLUXDB_V1:
|
||||
config.auth_type = HTTP_AUTH_TYPE_BASIC;
|
||||
config.username = user.c_str();
|
||||
config.password = password.c_str();
|
||||
break;
|
||||
case INFLUXDB_V2:
|
||||
break;
|
||||
}
|
||||
|
||||
InfluxDBdestroy();
|
||||
httpClient = esp_http_client_init(&config);
|
||||
if (!httpClient) {
|
||||
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to initialize HTTP client");
|
||||
} else {
|
||||
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "HTTP client initialized successfully");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Destroys the InfluxDB instance by cleaning up the HTTP client.
|
||||
*
|
||||
* This function checks if the HTTP client is initialized. If it is, it cleans up the HTTP client
|
||||
* and logs the cleanup action. The HTTP client pointer is then set to NULL.
|
||||
*/
|
||||
void InfluxDB::InfluxDBdestroy() {
|
||||
if (httpClient) {
|
||||
esp_http_client_cleanup(httpClient);
|
||||
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "HTTP client cleaned up");
|
||||
httpClient = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Publishes data to an InfluxDB instance.
|
||||
*
|
||||
* This function sends a measurement, key, and content to an InfluxDB server.
|
||||
* It supports both InfluxDB v1 and v2 APIs.
|
||||
*
|
||||
* @param _measurement The measurement name to publish.
|
||||
* @param _key The key associated with the measurement.
|
||||
* @param _content The content or value to publish.
|
||||
* @param _timeUTC The timestamp in UTC. If greater than 0, it will be included in the payload.
|
||||
*
|
||||
* The function logs the process and handles HTTP communication with the InfluxDB server.
|
||||
* It constructs the appropriate API URI based on the InfluxDB version and sends the data
|
||||
* using an HTTP POST request.
|
||||
*/
|
||||
void InfluxDB::InfluxDBPublish(std::string _measurement, std::string _key, std::string _content, long int _timeUTC) {
|
||||
std::string apiURI;
|
||||
std::string payload;
|
||||
char nowTimestamp[21];
|
||||
|
||||
connectHTTP();
|
||||
|
||||
|
||||
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "InfluxDBPublish - Key: " + _key + ", Content: " + _content + ", timeUTC: " + std::to_string(_timeUTC));
|
||||
|
||||
if (_timeUTC > 0)
|
||||
@@ -164,50 +202,47 @@ void InfluxDBPublish(std::string _measurement, std::string _key, std::string _co
|
||||
}
|
||||
|
||||
payload.shrink_to_fit();
|
||||
|
||||
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "sending line to influxdb:" + payload);
|
||||
|
||||
esp_err_t err;
|
||||
|
||||
// use the default retention policy of the bucket
|
||||
std::string apiURI = _influxDBURI + "/write?db=" + _influxDBDatabase;
|
||||
// std::string apiURI = _influxDBURI + "/api/v2/write?bucket=" + _influxDBDatabase + "/";
|
||||
switch (version) {
|
||||
case INFLUXDB_V1:
|
||||
apiURI = influxDBURI + "/write?db=" + database;
|
||||
apiURI.shrink_to_fit();
|
||||
|
||||
apiURI.shrink_to_fit();
|
||||
http_config.url = apiURI.c_str();
|
||||
esp_http_client_set_url(httpClient, apiURI.c_str());
|
||||
esp_http_client_set_method(httpClient, HTTP_METHOD_POST);
|
||||
esp_http_client_set_header(httpClient, "Content-Type", "text/plain");
|
||||
esp_http_client_set_post_field(httpClient, payload.c_str(), payload.length());
|
||||
|
||||
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "API URI: " + apiURI);
|
||||
err = esp_http_client_perform(httpClient);
|
||||
if (err == ESP_OK) {
|
||||
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Data published successfully: " + payload);
|
||||
} else {
|
||||
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to publish data: " + std::string(esp_err_to_name(err)));
|
||||
}
|
||||
break;
|
||||
|
||||
esp_http_client_handle_t http_client = esp_http_client_init(&http_config);
|
||||
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "client is initialized");
|
||||
case INFLUXDB_V2:
|
||||
apiURI = influxDBURI + "/api/v2/write?org=" + org + "&bucket=" + bucket;
|
||||
apiURI.shrink_to_fit();
|
||||
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "apiURI: " + apiURI);
|
||||
|
||||
esp_http_client_set_header(http_client, "Content-Type", "text/plain");
|
||||
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "header is set");
|
||||
|
||||
ESP_ERROR_CHECK(esp_http_client_set_post_field(http_client, payload.c_str(), payload.length()));
|
||||
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "post payload is set");
|
||||
|
||||
esp_err_t err = ESP_ERROR_CHECK_WITHOUT_ABORT(esp_http_client_perform(http_client));
|
||||
|
||||
if( err == ESP_OK ) {
|
||||
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "HTTP request was performed");
|
||||
int status_code = esp_http_client_get_status_code(http_client);
|
||||
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "HTTP status code" + std::to_string(status_code));
|
||||
} else {
|
||||
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "HTTP request failed");
|
||||
esp_http_client_set_url(httpClient, apiURI.c_str());
|
||||
esp_http_client_set_method(httpClient, HTTP_METHOD_POST);
|
||||
esp_http_client_set_header(httpClient, "Content-Type", "text/plain");
|
||||
std::string _zw = "Token " + token;
|
||||
esp_http_client_set_header(httpClient, "Authorization", _zw.c_str());
|
||||
esp_http_client_set_post_field(httpClient, payload.c_str(), payload.length());
|
||||
err = ESP_ERROR_CHECK_WITHOUT_ABORT(esp_http_client_perform(httpClient));
|
||||
if (err == ESP_OK) {
|
||||
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Data published successfully: " + payload);
|
||||
} else {
|
||||
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Failed to publish data: " + std::string(esp_err_to_name(err)));
|
||||
}
|
||||
break;
|
||||
}
|
||||
esp_http_client_cleanup(http_client);
|
||||
}
|
||||
|
||||
|
||||
void InfluxDBInit(std::string _uri, std::string _database, std::string _user, std::string _password){
|
||||
_influxDBURI = _uri;
|
||||
_influxDBDatabase = _database;
|
||||
_influxDBUser = _user;
|
||||
_influxDBPassword = _password;
|
||||
|
||||
}
|
||||
|
||||
void InfluxDBdestroy() {
|
||||
}
|
||||
|
||||
#endif //ENABLE_INFLUXDB
|
||||
|
||||
@@ -8,17 +8,106 @@
|
||||
#include <map>
|
||||
#include <functional>
|
||||
|
||||
// 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, 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, long int _timeUTC);
|
||||
#include <string>
|
||||
#include "esp_http_client.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
|
||||
enum InfluxDBVersion {
|
||||
INFLUXDB_V1,
|
||||
INFLUXDB_V2
|
||||
};
|
||||
|
||||
/**
|
||||
* @class InfluxDB
|
||||
* @brief A class to handle connections and data publishing to InfluxDB servers.
|
||||
*
|
||||
* This class supports both InfluxDB v1.x and v2.x versions. It provides methods to initialize
|
||||
* the connection parameters, publish data, and destroy the connection.
|
||||
*
|
||||
* @private
|
||||
* @var std::string influxDBURI
|
||||
* URI for the InfluxDB server.
|
||||
*
|
||||
* @var std::string database
|
||||
* Database name for InfluxDB v1.x.
|
||||
*
|
||||
* @var std::string user
|
||||
* Username for InfluxDB v1.x.
|
||||
*
|
||||
* @var std::string password
|
||||
* Password for InfluxDB v1.x.
|
||||
*
|
||||
* @var std::string bucket
|
||||
* Bucket name for InfluxDB v2.x.
|
||||
*
|
||||
* @var std::string org
|
||||
* Organization name for InfluxDB v2.x.
|
||||
*
|
||||
* @var std::string token
|
||||
* Token for InfluxDB v2.x.
|
||||
*
|
||||
* @var InfluxDBVersion version
|
||||
* Version of the InfluxDB server (v1.x or v2.x).
|
||||
*
|
||||
* @var esp_http_client_handle_t httpClient
|
||||
* HTTP client handle for making requests to the InfluxDB server.
|
||||
*
|
||||
* @var void connectHTTP()
|
||||
* Establishes an HTTP connection to the InfluxDB server.
|
||||
*
|
||||
* @public
|
||||
* @fn void InfluxDBInitV1(std::string _influxDBURI, std::string _database, std::string _user, std::string _password)
|
||||
* Initializes the connection parameters for InfluxDB v1.x.
|
||||
*
|
||||
* @fn void InfluxDBInitV2(std::string _influxDBURI, std::string _bucket, std::string _org, std::string _token)
|
||||
* Initializes the connection parameters for InfluxDB v2.x.
|
||||
*
|
||||
* @fn void InfluxDBdestroy()
|
||||
* Destroys the InfluxDB connection.
|
||||
*
|
||||
* @fn void InfluxDBPublish(std::string _measurement, std::string _key, std::string _content, long int _timeUTC)
|
||||
* Publishes data to the InfluxDB server.
|
||||
*
|
||||
* @param _measurement The measurement name.
|
||||
* @param _key The key for the data point.
|
||||
* @param _content The content or value of the data point.
|
||||
* @param _timeUTC The timestamp in UTC for the data point.
|
||||
*/
|
||||
|
||||
class InfluxDB {
|
||||
private:
|
||||
// Information for InfluxDB v1.x
|
||||
std::string influxDBURI = "";
|
||||
// Information for InfluxDB v1.x
|
||||
std::string database = "";
|
||||
std::string user = "";
|
||||
std::string password = "";
|
||||
|
||||
// Information for InfluxDB v2.x
|
||||
std::string bucket = "";
|
||||
std::string org = "";
|
||||
std::string token = "";
|
||||
|
||||
InfluxDBVersion version;
|
||||
|
||||
esp_http_client_handle_t httpClient = NULL;
|
||||
|
||||
void connectHTTP();
|
||||
|
||||
public:
|
||||
// Initialize the InfluxDB connection parameters
|
||||
void InfluxDBInitV1(std::string _influxDBURI, std::string _database, std::string _user, std::string _password);
|
||||
void InfluxDBInitV2(std::string _influxDBURI, std::string _bucket, std::string _org, std::string _token);
|
||||
|
||||
// Destroy the InfluxDB connection
|
||||
void InfluxDBdestroy();
|
||||
// Publish data to the InfluxDB server
|
||||
void InfluxDBPublish(std::string _measurement, std::string _key, std::string _content, long int _timeUTC);
|
||||
};
|
||||
|
||||
|
||||
void InfluxDBdestroy();
|
||||
|
||||
#endif //INTERFACE_INFLUXDB_H
|
||||
#endif //ENABLE_INFLUXDB
|
||||
@@ -16,7 +16,6 @@
|
||||
#include "esp_timer.h"
|
||||
#endif
|
||||
|
||||
|
||||
static const char *TAG = "MQTT IF";
|
||||
|
||||
std::map<std::string, std::function<void()>>* connectFunktionMap = NULL;
|
||||
@@ -36,11 +35,11 @@ bool mqtt_connected = false;
|
||||
esp_mqtt_client_handle_t client = NULL;
|
||||
std::string uri, client_id, lwt_topic, lwt_connected, lwt_disconnected, user, password, maintopic, domoticz_in_topic;
|
||||
std::string caCert, clientCert, clientKey;
|
||||
bool validateServerCert = true;
|
||||
int keepalive;
|
||||
bool SetRetainFlag;
|
||||
void (*callbackOnConnected)(std::string, bool) = NULL;
|
||||
|
||||
|
||||
bool MQTTPublish(std::string _key, std::string _content, int qos, bool retained_flag)
|
||||
{
|
||||
if (!mqtt_enabled) { // MQTT sevice not started / configured (MQTT_Init not called before)
|
||||
@@ -95,7 +94,6 @@ bool MQTTPublish(std::string _key, std::string _content, int qos, bool retained_
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static esp_err_t mqtt_event_handler_cb(esp_mqtt_event_handle_t event) {
|
||||
std::string topic = "";
|
||||
switch (event->event_id) {
|
||||
@@ -197,16 +195,14 @@ static esp_err_t mqtt_event_handler_cb(esp_mqtt_event_handle_t event) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
|
||||
static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) {
|
||||
ESP_LOGD(TAG, "Event dispatched from event loop base=%s, event_id=%d", base, (int)event_id);
|
||||
mqtt_event_handler_cb((esp_mqtt_event_handle_t) event_data);
|
||||
}
|
||||
|
||||
|
||||
bool MQTT_Configure(std::string _mqttURI, std::string _clientid, std::string _user, std::string _password,
|
||||
std::string _maintopic, std::string _domoticz_in_topic, std::string _lwt, std::string _lwt_connected, std::string _lwt_disconnected,
|
||||
std::string _cacertfilename, std::string _clientcertfilename, std::string _clientkeyfilename,
|
||||
std::string _cacertfilename, bool _validateServerCert, std::string _clientcertfilename, std::string _clientkeyfilename,
|
||||
int _keepalive, bool _SetRetainFlag, void *_callbackOnConnected) {
|
||||
if ((_mqttURI.length() == 0) || (_maintopic.length() == 0) || (_clientid.length() == 0))
|
||||
{
|
||||
@@ -225,25 +221,45 @@ bool MQTT_Configure(std::string _mqttURI, std::string _clientid, std::string _us
|
||||
domoticz_in_topic = _domoticz_in_topic;
|
||||
callbackOnConnected = ( void (*)(std::string, bool) )(_callbackOnConnected);
|
||||
|
||||
if (_clientcertfilename.length() && _clientkeyfilename.length()){
|
||||
if (_clientcertfilename.length() && _clientkeyfilename.length()) {
|
||||
std::ifstream cert_ifs(_clientcertfilename);
|
||||
std::string cert_content((std::istreambuf_iterator<char>(cert_ifs)), (std::istreambuf_iterator<char>()));
|
||||
clientCert = cert_content;
|
||||
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "using clientCert: " + _clientcertfilename);
|
||||
if (cert_ifs.is_open()) {
|
||||
std::string cert_content((std::istreambuf_iterator<char>(cert_ifs)), (std::istreambuf_iterator<char>()));
|
||||
clientCert = cert_content;
|
||||
cert_ifs.close();
|
||||
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "using clientCert: " + _clientcertfilename);
|
||||
}
|
||||
else {
|
||||
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "could not open clientCert: " + _clientcertfilename);
|
||||
}
|
||||
|
||||
std::ifstream key_ifs(_clientkeyfilename);
|
||||
std::string key_content((std::istreambuf_iterator<char>(key_ifs)), (std::istreambuf_iterator<char>()));
|
||||
clientKey = key_content;
|
||||
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "using clientKey: " + _clientkeyfilename);
|
||||
if (key_ifs.is_open()) {
|
||||
std::string key_content((std::istreambuf_iterator<char>(key_ifs)), (std::istreambuf_iterator<char>()));
|
||||
clientKey = key_content;
|
||||
key_ifs.close();
|
||||
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "using clientKey: " + _clientkeyfilename);
|
||||
}
|
||||
else {
|
||||
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "could not open clientKey: " + _clientkeyfilename);
|
||||
}
|
||||
}
|
||||
|
||||
if (_cacertfilename.length() ){
|
||||
std::ifstream ifs(_cacertfilename);
|
||||
std::string content((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>()));
|
||||
caCert = content;
|
||||
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "using caCert: " + _cacertfilename);
|
||||
if (_cacertfilename.length()) {
|
||||
std::ifstream ca_ifs(_cacertfilename);
|
||||
if (ca_ifs.is_open()) {
|
||||
std::string content((std::istreambuf_iterator<char>(ca_ifs)), (std::istreambuf_iterator<char>()));
|
||||
caCert = content;
|
||||
ca_ifs.close();
|
||||
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "using caCert: " + _cacertfilename);
|
||||
}
|
||||
else {
|
||||
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "could not open caCert: " + _cacertfilename);
|
||||
}
|
||||
}
|
||||
|
||||
validateServerCert = _validateServerCert;
|
||||
|
||||
if (_user.length() && _password.length()){
|
||||
user = _user;
|
||||
password = _password;
|
||||
@@ -261,7 +277,6 @@ bool MQTT_Configure(std::string _mqttURI, std::string _clientid, std::string _us
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
int MQTT_Init() {
|
||||
if (mqtt_initialized) {
|
||||
return 0;
|
||||
@@ -295,18 +310,21 @@ int MQTT_Init() {
|
||||
mqtt_cfg.session.last_will.msg = lwt_disconnected.c_str();
|
||||
mqtt_cfg.session.last_will.msg_len = (int)(lwt_disconnected.length());
|
||||
mqtt_cfg.session.keepalive = keepalive;
|
||||
mqtt_cfg.buffer.size = 1536; // size of MQTT send/receive buffer (Default: 1024)
|
||||
mqtt_cfg.buffer.size = 2048; // size of MQTT send/receive buffer
|
||||
|
||||
if (caCert.length()){
|
||||
if (caCert.length()) {
|
||||
mqtt_cfg.broker.verification.certificate = caCert.c_str();
|
||||
mqtt_cfg.broker.verification.certificate_len = caCert.length() + 1;
|
||||
mqtt_cfg.broker.verification.skip_cert_common_name_check = true;
|
||||
|
||||
// Skip any validation of server certificate CN field, this reduces the
|
||||
// security of TLS and makes the *MQTT* client susceptible to MITM attacks
|
||||
mqtt_cfg.broker.verification.skip_cert_common_name_check = !validateServerCert;
|
||||
}
|
||||
|
||||
if (clientCert.length() && clientKey.length()){
|
||||
if (clientCert.length() && clientKey.length()) {
|
||||
mqtt_cfg.credentials.authentication.certificate = clientCert.c_str();
|
||||
mqtt_cfg.credentials.authentication.certificate_len = clientCert.length() + 1;
|
||||
|
||||
|
||||
mqtt_cfg.credentials.authentication.key = clientKey.c_str();
|
||||
mqtt_cfg.credentials.authentication.key_len = clientKey.length() + 1;
|
||||
}
|
||||
@@ -356,7 +374,6 @@ int MQTT_Init() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
void MQTTdestroy_client(bool _disable = false) {
|
||||
if (client) {
|
||||
if (mqtt_connected) {
|
||||
@@ -374,17 +391,14 @@ void MQTTdestroy_client(bool _disable = false) {
|
||||
mqtt_configOK = false;
|
||||
}
|
||||
|
||||
|
||||
bool getMQTTisEnabled() {
|
||||
return mqtt_enabled;
|
||||
}
|
||||
|
||||
|
||||
bool getMQTTisConnected() {
|
||||
return mqtt_connected;
|
||||
}
|
||||
|
||||
|
||||
bool mqtt_handler_flow_start(std::string _topic, char* _data, int _data_len)
|
||||
{
|
||||
ESP_LOGD(TAG, "Handler called: topic %s, data %.*s", _topic.c_str(), _data_len, _data);
|
||||
@@ -393,7 +407,6 @@ bool mqtt_handler_flow_start(std::string _topic, char* _data, int _data_len)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
|
||||
bool mqtt_handler_set_prevalue(std::string _topic, char* _data, int _data_len)
|
||||
{
|
||||
//ESP_LOGD(TAG, "Handler called: topic %s, data %.*s", _topic.c_str(), _data_len, _data);
|
||||
@@ -429,7 +442,6 @@ bool mqtt_handler_set_prevalue(std::string _topic, char* _data, int _data_len)
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
|
||||
void MQTTconnected(){
|
||||
if (mqtt_connected) {
|
||||
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Connected to broker");
|
||||
@@ -464,7 +476,6 @@ void MQTTconnected(){
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void MQTTregisterConnectFunction(std::string name, std::function<void()> func){
|
||||
ESP_LOGD(TAG, "MQTTregisteronnectFunction %s\r\n", name.c_str());
|
||||
if (connectFunktionMap == NULL) {
|
||||
@@ -483,7 +494,6 @@ void MQTTregisterConnectFunction(std::string name, std::function<void()> func){
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void MQTTunregisterConnectFunction(std::string name){
|
||||
ESP_LOGD(TAG, "unregisterConnnectFunction %s\r\n", name.c_str());
|
||||
if ((connectFunktionMap != NULL) && (connectFunktionMap->find(name) != connectFunktionMap->end())) {
|
||||
@@ -491,7 +501,6 @@ void MQTTunregisterConnectFunction(std::string name){
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void MQTTregisterSubscribeFunction(std::string topic, std::function<bool(std::string, char*, int)> func){
|
||||
ESP_LOGD(TAG, "registerSubscribeFunction %s", topic.c_str());
|
||||
if (subscribeFunktionMap == NULL) {
|
||||
@@ -506,7 +515,6 @@ void MQTTregisterSubscribeFunction(std::string topic, std::function<bool(std::st
|
||||
(*subscribeFunktionMap)[topic] = func;
|
||||
}
|
||||
|
||||
|
||||
void MQTTdestroySubscribeFunction(){
|
||||
if (subscribeFunktionMap != NULL) {
|
||||
if (mqtt_connected) {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
bool MQTT_Configure(std::string _mqttURI, std::string _clientid, std::string _user, std::string _password,
|
||||
std::string _maintopic, std::string _domoticz_in_topic, std::string _lwt, std::string _lwt_connected, std::string _lwt_disconnected,
|
||||
std::string _cacertfilename, std::string _clientcertfilename, std::string _clientkeyfilename,
|
||||
std::string _cacertfilename, bool _validateServerCert, std::string _clientcertfilename, std::string _clientkeyfilename,
|
||||
int _keepalive, bool SetRetainFlag, void *callbackOnConnected);
|
||||
int MQTT_Init();
|
||||
void MQTTdestroy_client(bool _disable);
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "interface_mqtt.h"
|
||||
#include "time_sntp.h"
|
||||
#include "../../include/defines.h"
|
||||
#include "basic_auth.h"
|
||||
|
||||
|
||||
|
||||
@@ -194,9 +195,12 @@ bool MQTThomeassistantDiscovery(int qos) {
|
||||
|
||||
/* If "Allow neg. rate" is true, use "measurement" instead of "total_increasing" for the State Class, see https://github.com/jomjol/AI-on-the-edge-device/issues/3331 */
|
||||
std::string value_state_class = "total_increasing";
|
||||
if ((*NUMBERS)[i]->AllowNegativeRates) {
|
||||
if (meterType == "temperature") {
|
||||
value_state_class = "measurement";
|
||||
}
|
||||
else if ((*NUMBERS)[i]->AllowNegativeRates) {
|
||||
value_state_class = "total";
|
||||
}
|
||||
|
||||
/* Energy meters need a different Device Class, see https://github.com/jomjol/AI-on-the-edge-device/issues/3333 */
|
||||
std::string rate_device_class = "volume_flow_rate";
|
||||
@@ -204,17 +208,17 @@ bool MQTThomeassistantDiscovery(int qos) {
|
||||
rate_device_class = "power";
|
||||
}
|
||||
|
||||
// Group | Field | User Friendly Name | Icon | Unit | Device Class | State Class | Entity Category
|
||||
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic(group, "value", "Value", "gauge", valueUnit, meterType, value_state_class, "", qos); // State Class = "total_increasing" if <NUMBERS>.AllowNegativeRates = false, else use "measurement"
|
||||
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic(group, "raw", "Raw Value", "raw", valueUnit, meterType, "measurement", "diagnostic", qos);
|
||||
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic(group, "error", "Error", "alert-circle-outline", "", "", "", "diagnostic", qos);
|
||||
// Group | Field | User Friendly Name | Icon | Unit | Device Class | State Class | Entity Category | QoS
|
||||
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic(group, "value", "Value", "gauge", valueUnit, meterType, value_state_class, "", qos); // State Class = "total_increasing" if <NUMBERS>.AllowNegativeRates = false, "measurement" in case of a thermometer, else use "total".
|
||||
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic(group, "raw", "Raw Value", "raw", valueUnit, meterType, value_state_class, "diagnostic", qos);
|
||||
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic(group, "error", "Error", "alert-circle-outline", "", "", "", "diagnostic", qos);
|
||||
/* Not announcing "rate" as it is better to use rate_per_time_unit resp. rate_per_digitization_round */
|
||||
// allSendsSuccessed |= sendHomeAssistantDiscoveryTopic(group, "rate", "Rate (Unit/Minute)", "swap-vertical", "", "", "", ""); // Legacy, always Unit per Minute
|
||||
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic(group, "rate_per_time_unit", "Rate (" + rateUnit + ")", "swap-vertical", rateUnit, rate_device_class, "measurement", "", qos);
|
||||
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic(group, "rate_per_digitization_round", "Change since last Digitization round", "arrow-expand-vertical", valueUnit, "", "measurement", "", qos); // correctly the Unit is Unit/Interval!
|
||||
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic(group, "timestamp", "Timestamp", "clock-time-eight-outline", "", "timestamp", "", "diagnostic", qos);
|
||||
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic(group, "json", "JSON", "code-json", "", "", "", "diagnostic", qos);
|
||||
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic(group, "problem", "Problem", "alert-outline", "", "problem", "", "", qos); // Special binary sensor which is based on error topic
|
||||
// allSendsSuccessed |= sendHomeAssistantDiscoveryTopic(group, "rate", "Rate (Unit/Minute)", "swap-vertical", "", "", "", "", qos); // Legacy, always Unit per Minute
|
||||
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic(group, "rate_per_time_unit", "Rate (" + rateUnit + ")", "swap-vertical", rateUnit, rate_device_class, "measurement", "", qos);
|
||||
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic(group, "rate_per_digitization_round","Change since last Digitization round", "arrow-expand-vertical", valueUnit, "", "measurement", "", qos); // correctly the Unit is Unit/Interval!
|
||||
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic(group, "timestamp", "Timestamp", "clock-time-eight-outline", "", "timestamp", "", "diagnostic", qos);
|
||||
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic(group, "json", "JSON", "code-json", "", "", "", "diagnostic", qos);
|
||||
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic(group, "problem", "Problem", "alert-outline", "", "problem", "", "", qos); // Special binary sensor which is based on error topic
|
||||
}
|
||||
|
||||
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Successfully published all Homeassistant Discovery MQTT topics");
|
||||
@@ -347,7 +351,7 @@ void register_server_mqtt_uri(httpd_handle_t server) {
|
||||
uri.method = HTTP_GET;
|
||||
|
||||
uri.uri = "/mqtt_publish_discovery";
|
||||
uri.handler = scheduleSendingDiscovery_and_static_Topics;
|
||||
uri.handler = APPLY_BASIC_AUTH_FILTER(scheduleSendingDiscovery_and_static_Topics);
|
||||
uri.user_ctx = (void*) "";
|
||||
httpd_register_uri_handler(server, &uri);
|
||||
}
|
||||
|
||||
107
code/components/jomjol_wlan/basic_auth.cpp
Normal file
107
code/components/jomjol_wlan/basic_auth.cpp
Normal file
@@ -0,0 +1,107 @@
|
||||
#include "basic_auth.h"
|
||||
#include "read_wlanini.h"
|
||||
#include <esp_tls_crypto.h>
|
||||
#include <esp_log.h>
|
||||
|
||||
|
||||
#define HTTPD_401 "401 UNAUTHORIZED"
|
||||
|
||||
static const char *TAG = "HTTPAUTH";
|
||||
|
||||
typedef struct {
|
||||
const char *username;
|
||||
const char *password;
|
||||
} basic_auth_info_t;
|
||||
|
||||
basic_auth_info_t basic_auth_info = { NULL, NULL };
|
||||
|
||||
void init_basic_auth() {
|
||||
if (!wlan_config.http_username.empty() && !wlan_config.http_password.empty()) {
|
||||
basic_auth_info.username = wlan_config.http_username.c_str();
|
||||
basic_auth_info.password = wlan_config.http_password.c_str();
|
||||
}
|
||||
}
|
||||
|
||||
static char *http_auth_basic(const char *username, const char *password)
|
||||
{
|
||||
int out;
|
||||
char *user_info = NULL;
|
||||
char *digest = NULL;
|
||||
size_t n = 0;
|
||||
asprintf(&user_info, "%s:%s", username, password);
|
||||
if (!user_info) {
|
||||
ESP_LOGE(TAG, "No enough memory for user information");
|
||||
return NULL;
|
||||
}
|
||||
esp_crypto_base64_encode(NULL, 0, &n, (const unsigned char *)user_info, strlen(user_info));
|
||||
|
||||
/* 6: The length of the "Basic " string
|
||||
* n: Number of bytes for a base64 encode format
|
||||
* 1: Number of bytes for a reserved which be used to fill zero
|
||||
*/
|
||||
digest = static_cast<char*>(calloc(1, 6 + n + 1));
|
||||
if (digest) {
|
||||
strcpy(digest, "Basic ");
|
||||
esp_crypto_base64_encode((unsigned char *)digest + 6, n, (size_t *)&out, (const unsigned char *)user_info, strlen(user_info));
|
||||
}
|
||||
free(user_info);
|
||||
return digest;
|
||||
}
|
||||
|
||||
esp_err_t basic_auth_request_filter(httpd_req_t *req, esp_err_t original_handler(httpd_req_t *))
|
||||
{
|
||||
char *buf = NULL;
|
||||
size_t buf_len = 0;
|
||||
esp_err_t ret = ESP_OK;
|
||||
|
||||
char unauthorized[] = "You are not authorized to use this website!";
|
||||
|
||||
if (basic_auth_info.username == NULL || basic_auth_info.password == NULL) {
|
||||
ret = original_handler(req);
|
||||
} else {
|
||||
buf_len = httpd_req_get_hdr_value_len(req, "Authorization") + 1;
|
||||
if (buf_len > 1) {
|
||||
buf = static_cast<char*>(calloc(1, buf_len));
|
||||
if (!buf) {
|
||||
ESP_LOGE(TAG, "No enough memory for basic authorization");
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
if (httpd_req_get_hdr_value_str(req, "Authorization", buf, buf_len) == ESP_OK) {
|
||||
ESP_LOGI(TAG, "Found header => Authorization: %s", buf);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "No auth value received");
|
||||
}
|
||||
|
||||
char *auth_credentials = http_auth_basic(basic_auth_info.username, basic_auth_info.password);
|
||||
if (!auth_credentials) {
|
||||
ESP_LOGE(TAG, "No enough memory for basic authorization credentials");
|
||||
free(buf);
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
if (strncmp(auth_credentials, buf, buf_len)) {
|
||||
ESP_LOGE(TAG, "Not authenticated");
|
||||
httpd_resp_set_status(req, HTTPD_401);
|
||||
httpd_resp_set_type(req, HTTPD_TYPE_TEXT);
|
||||
httpd_resp_set_hdr(req, "Connection", "keep-alive");
|
||||
httpd_resp_set_hdr(req, "WWW-Authenticate", "Basic realm=\"AIOTED\"");
|
||||
httpd_resp_send(req, unauthorized, strlen(unauthorized));
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Authenticated calling http handler now!");
|
||||
ret=original_handler(req);
|
||||
}
|
||||
free(auth_credentials);
|
||||
free(buf);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "No auth header received");
|
||||
httpd_resp_set_status(req, HTTPD_401);
|
||||
httpd_resp_set_type(req, HTTPD_TYPE_TEXT);
|
||||
httpd_resp_set_hdr(req, "Connection", "keep-alive");
|
||||
httpd_resp_set_hdr(req, "WWW-Authenticate", "Basic realm=\"AIOTED\"");
|
||||
httpd_resp_send(req, unauthorized, strlen(unauthorized));
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
8
code/components/jomjol_wlan/basic_auth.h
Normal file
8
code/components/jomjol_wlan/basic_auth.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <esp_http_server.h>
|
||||
|
||||
void init_basic_auth();
|
||||
esp_err_t basic_auth_request_filter(httpd_req_t *req, esp_err_t original_handler(httpd_req_t *));
|
||||
|
||||
#define APPLY_BASIC_AUTH_FILTER(method) [](httpd_req_t *req){ return basic_auth_request_filter(req, method); }
|
||||
@@ -49,6 +49,9 @@
|
||||
#define ets_delay_us(a) esp_rom_delay_us(a)
|
||||
#endif
|
||||
|
||||
#include "../esp-protocols/components/mdns/include/mdns.h"
|
||||
|
||||
|
||||
static const char *TAG = "WIFI";
|
||||
|
||||
static bool APWithBetterRSSI = false;
|
||||
@@ -657,6 +660,14 @@ esp_err_t wifi_init_sta(void)
|
||||
else {
|
||||
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Set hostname to: " + wlan_config.hostname);
|
||||
}
|
||||
//initialize mDNS service
|
||||
retval = mdns_init();
|
||||
if (retval != ESP_OK) {
|
||||
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "mdns_init failed! Error: " + std::to_string(retval));
|
||||
} else {
|
||||
//set mdns hostname
|
||||
mdns_hostname_set(wlan_config.hostname.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Init successful");
|
||||
|
||||
@@ -145,6 +145,29 @@ int LoadWlanFromFile(std::string fn)
|
||||
wlan_config.dns = tmp;
|
||||
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "DNS: " + wlan_config.dns);
|
||||
}
|
||||
|
||||
else if ((splitted.size() > 1) && (toUpper(splitted[0]) == "HTTP_USERNAME")){
|
||||
tmp = splitted[1];
|
||||
if ((tmp[0] == '"') && (tmp[tmp.length()-1] == '"')){
|
||||
tmp = tmp.substr(1, tmp.length()-2);
|
||||
}
|
||||
wlan_config.http_username = tmp;
|
||||
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "HTTP_USERNAME: " + wlan_config.http_username);
|
||||
}
|
||||
|
||||
else if ((splitted.size() > 1) && (toUpper(splitted[0]) == "HTTP_PASSWORD")){
|
||||
tmp = splitted[1];
|
||||
if ((tmp[0] == '"') && (tmp[tmp.length()-1] == '"')){
|
||||
tmp = tmp.substr(1, tmp.length()-2);
|
||||
}
|
||||
wlan_config.http_password = tmp;
|
||||
#ifndef __HIDE_PASSWORD
|
||||
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "HTTP_PASSWORD: " + wlan_config.http_password);
|
||||
#else
|
||||
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "HTTP_PASSWORD: XXXXXXXX");
|
||||
#endif
|
||||
}
|
||||
|
||||
#if (defined WLAN_USE_ROAMING_BY_SCANNING || (defined WLAN_USE_MESH_ROAMING && defined WLAN_USE_MESH_ROAMING_ACTIVATE_CLIENT_TRIGGERED_QUERIES))
|
||||
else if ((splitted.size() > 1) && (toUpper(splitted[0]) == "RSSITHRESHOLD")){
|
||||
tmp = trim(splitted[1]);
|
||||
|
||||
@@ -13,6 +13,8 @@ struct wlan_config {
|
||||
std::string gateway = "";
|
||||
std::string netmask = "";
|
||||
std::string dns = "";
|
||||
std::string http_username = "";
|
||||
std::string http_password = "";
|
||||
int rssi_threshold = 0; // Default: 0 -> ROAMING disabled
|
||||
};
|
||||
extern struct wlan_config wlan_config;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#include "openmetrics.h"
|
||||
#include "functional"
|
||||
#include "esp_log.h"
|
||||
|
||||
/**
|
||||
* create a singe metric from the given input
|
||||
@@ -10,10 +12,66 @@ std::string createMetric(const std::string &metricName, const std::string &help,
|
||||
metricName + " " + value + "\n";
|
||||
}
|
||||
|
||||
typedef struct sequence_metric {
|
||||
const char *name;
|
||||
const char *help;
|
||||
const char *type;
|
||||
std::function<std::string(NumberPost *number)> valueFunc;
|
||||
} sequence_metric_t;
|
||||
|
||||
|
||||
sequence_metric_t sequenceMetrics[4] = {
|
||||
{ "flow_value", "current value of meter readout", "gauge", [](NumberPost *number)-> std::string {return number->ReturnValue;} },
|
||||
{ "flow_raw_value", "current raw value of meter readout", "gauge", [](NumberPost *number)-> std::string {return number->ReturnRawValue;} },
|
||||
{ "flow_pre_value", "previous value of meter readout", "gauge", [](NumberPost *number)-> std::string {return number->ReturnPreValue;} },
|
||||
{ "flow_error", "Error message text != 'no error'", "gauge", [](NumberPost *number)-> std::string {return number->ErrorMessageText.compare("no error") == 0 ? "0" : "1";} },
|
||||
};
|
||||
|
||||
std::string createSequenceMetrics(std::string prefix, const std::vector<NumberPost *> &numbers)
|
||||
{
|
||||
std::string result;
|
||||
for (int i = 0; i<sizeof(sequenceMetrics)/sizeof(sequence_metric_t);i++)
|
||||
{
|
||||
std::string res;
|
||||
for (const auto &number : numbers)
|
||||
{
|
||||
std::string value = sequenceMetrics[i].valueFunc(number);
|
||||
if (value.find("N") != std::string::npos) {
|
||||
value = "NaN";
|
||||
}
|
||||
ESP_LOGD("METRICS", "metric=%s, name=%s, value = %s ",sequenceMetrics[i].name,number->name.c_str(), value.c_str());
|
||||
|
||||
// only valid data is reported (https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#missing-data)
|
||||
if (value.length() > 0)
|
||||
{
|
||||
auto label = number->name;
|
||||
// except newline, double quote, and backslash (https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#abnf)
|
||||
// to keep it simple, these characters are just removed from the label
|
||||
replaceAll(label, "\\", "");
|
||||
replaceAll(label, "\"", "");
|
||||
replaceAll(label, "\n", "");
|
||||
|
||||
res += prefix + "_" + sequenceMetrics[i].name + "{sequence=\"" + label + "\"} " + value + "\n";
|
||||
}
|
||||
}
|
||||
// prepend metadata if a valid metric was created
|
||||
if (res.length() > 0)
|
||||
{
|
||||
res = "# HELP " + prefix + "_" + sequenceMetrics[i].name + " " + sequenceMetrics[i].help + "\n"
|
||||
+ "# TYPE " + prefix + "_" + sequenceMetrics[i].name + " " + sequenceMetrics[i].type + "\n"
|
||||
+ res;
|
||||
}
|
||||
result += res;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the MetricFamily from all available sequences
|
||||
* @returns the string containing the text wire format of the MetricFamily
|
||||
**/
|
||||
/*
|
||||
std::string createSequenceMetrics(std::string prefix, const std::vector<NumberPost *> &numbers)
|
||||
{
|
||||
std::string res;
|
||||
@@ -41,3 +99,4 @@ std::string createSequenceMetrics(std::string prefix, const std::vector<NumberPo
|
||||
}
|
||||
return res;
|
||||
}
|
||||
*/
|
||||
@@ -1,16 +1,15 @@
|
||||
dependencies:
|
||||
espressif/esp-nn:
|
||||
component_hash: b32869798bdb40dec6bc99caca48cd65d42f8a9f506b9ab9c598a076f891ede9
|
||||
component_hash: f6f2851ce82137a66e4265071c9b852bbe0130b882a18dea9f03faea7bf1295a
|
||||
source:
|
||||
pre_release: true
|
||||
service_url: https://api.components.espressif.com/
|
||||
type: service
|
||||
version: 1.0.2
|
||||
version: 1.1.0
|
||||
idf:
|
||||
component_hash: null
|
||||
source:
|
||||
type: idf
|
||||
version: 5.3.1
|
||||
manifest_hash: 6995555b9b41e897235448c868ca92c0c3401fd2ff90df084be9bb8629958f2c
|
||||
manifest_hash: f88c9e5c2d75a9d5d6968fc67a90ef0cd7146dd6a3905a79c4dfcfc3b4fe6731
|
||||
target: esp32
|
||||
version: 1.0.0
|
||||
|
||||
@@ -33,6 +33,8 @@
|
||||
#include "configFile.h"
|
||||
#include "server_main.h"
|
||||
#include "server_camera.h"
|
||||
#include "basic_auth.h"
|
||||
|
||||
#ifdef ENABLE_MQTT
|
||||
#include "server_mqtt.h"
|
||||
#endif //ENABLE_MQTT
|
||||
@@ -77,10 +79,10 @@
|
||||
static heap_trace_record_t trace_record[NUM_RECORDS]; // This buffer must be in internal RAM
|
||||
#endif
|
||||
|
||||
extern const char* GIT_TAG;
|
||||
extern const char* GIT_REV;
|
||||
extern const char* GIT_BRANCH;
|
||||
extern const char* BUILD_TIME;
|
||||
extern const char *GIT_TAG;
|
||||
extern const char *GIT_REV;
|
||||
extern const char *GIT_BRANCH;
|
||||
extern const char *BUILD_TIME;
|
||||
|
||||
extern std::string getFwVersion(void);
|
||||
extern std::string getHTMLversion(void);
|
||||
@@ -107,27 +109,51 @@ bool Init_NVS_SDCard()
|
||||
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
|
||||
host.max_freq_khz = SDMMC_FREQ_HIGHSPEED;
|
||||
|
||||
// For SoCs where the SD power can be supplied both via an internal or external (e.g. on-board LDO) power supply.
|
||||
// When using specific IO pins (which can be used for ultra high-speed SDMMC) to connect to the SD card
|
||||
// and the internal LDO power supply, we need to initialize the power supply first.
|
||||
#if SD_PWR_CTRL_LDO_INTERNAL_IO
|
||||
sd_pwr_ctrl_ldo_config_t ldo_config = {
|
||||
.ldo_chan_id = CONFIG_EXAMPLE_SD_PWR_CTRL_LDO_IO_ID,
|
||||
};
|
||||
sd_pwr_ctrl_handle_t pwr_ctrl_handle = NULL;
|
||||
|
||||
ret = sd_pwr_ctrl_new_on_chip_ldo(&ldo_config, &pwr_ctrl_handle);
|
||||
if (ret != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to create a new on-chip LDO power control driver");
|
||||
return false;
|
||||
}
|
||||
host.pwr_ctrl_handle = pwr_ctrl_handle;
|
||||
#endif
|
||||
|
||||
// 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.
|
||||
#ifdef CONFIG_SOC_SDMMC_USE_GPIO_MATRIX
|
||||
sdmmc_slot_config_t slot_config = {
|
||||
.cd = SDMMC_SLOT_NO_CD,
|
||||
.wp = SDMMC_SLOT_NO_WP,
|
||||
};
|
||||
#else
|
||||
sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
|
||||
#endif
|
||||
|
||||
// Set bus width to use:
|
||||
#ifdef __SD_USE_ONE_LINE_MODE__
|
||||
slot_config.width = 1;
|
||||
#ifdef SOC_SDMMC_USE_GPIO_MATRIX
|
||||
#ifdef CONFIG_SOC_SDMMC_USE_GPIO_MATRIX
|
||||
slot_config.clk = GPIO_SDCARD_CLK;
|
||||
slot_config.cmd = GPIO_SDCARD_CMD;
|
||||
slot_config.d0 = GPIO_SDCARD_D0;
|
||||
#endif
|
||||
|
||||
#else
|
||||
#endif // end CONFIG_SOC_SDMMC_USE_GPIO_MATRIX
|
||||
#else // else __SD_USE_ONE_LINE_MODE__
|
||||
slot_config.width = 4;
|
||||
#ifdef SOC_SDMMC_USE_GPIO_MATRIX
|
||||
#ifdef CONFIG_SOC_SDMMC_USE_GPIO_MATRIX
|
||||
slot_config.d1 = GPIO_SDCARD_D1;
|
||||
slot_config.d2 = GPIO_SDCARD_D2;
|
||||
slot_config.d3 = GPIO_SDCARD_D3;
|
||||
#endif
|
||||
#endif
|
||||
#endif // end CONFIG_SOC_SDMMC_USE_GPIO_MATRIX
|
||||
#endif // end __SD_USE_ONE_LINE_MODE__
|
||||
|
||||
// Enable internal pullups on enabled pins. The internal pullups
|
||||
// are insufficient however, please make sure 10k external pullups are
|
||||
@@ -148,7 +174,7 @@ bool Init_NVS_SDCard()
|
||||
.format_if_mount_failed = false,
|
||||
.max_files = 12, // previously -> 2022-09-21: 5, 2023-01-02: 7
|
||||
.allocation_unit_size = 0, // 0 = auto
|
||||
.disk_status_check_enable = 0
|
||||
.disk_status_check_enable = 0,
|
||||
};
|
||||
|
||||
sdmmc_card_t* card;
|
||||
@@ -228,6 +254,35 @@ extern "C" void app_main(void)
|
||||
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "==================== Start ======================");
|
||||
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "=================================================");
|
||||
|
||||
// SD card: basic R/W check
|
||||
// ********************************************
|
||||
int iSDCardStatus = SDCardCheckRW();
|
||||
if (iSDCardStatus < 0) {
|
||||
if (iSDCardStatus <= -1 && iSDCardStatus >= -2) { // write error
|
||||
StatusLED(SDCARD_CHECK, 1, true);
|
||||
}
|
||||
else if (iSDCardStatus <= -3 && iSDCardStatus >= -5) { // read error
|
||||
StatusLED(SDCARD_CHECK, 2, true);
|
||||
}
|
||||
else if (iSDCardStatus == -6) { // delete error
|
||||
StatusLED(SDCARD_CHECK, 3, true);
|
||||
}
|
||||
setSystemStatusFlag(SYSTEM_STATUS_SDCARD_CHECK_BAD); // reduced web interface going to be loaded
|
||||
}
|
||||
|
||||
// SD card: Create further mandatory directories (if not already existing)
|
||||
// Correct creation of these folders will be checked with function "SDCardCheckFolderFilePresence"
|
||||
// ********************************************
|
||||
MakeDir("/sdcard/firmware"); // mandatory for OTA firmware update
|
||||
MakeDir("/sdcard/img_tmp"); // mandatory for setting up alignment marks
|
||||
MakeDir("/sdcard/demo"); // mandatory for demo mode
|
||||
MakeDir("/sdcard/config/certs"); // mandatory for mqtt certificates
|
||||
|
||||
// Check for updates
|
||||
// ********************************************
|
||||
CheckOTAUpdate();
|
||||
CheckUpdate();
|
||||
|
||||
// Init external PSRAM
|
||||
// ********************************************
|
||||
esp_err_t PSRAMStatus = esp_psram_init();
|
||||
@@ -265,7 +320,7 @@ extern "C" void app_main(void)
|
||||
setSystemStatusFlag(SYSTEM_STATUS_HEAP_TOO_SMALL);
|
||||
StatusLED(PSRAM_INIT, 3, true);
|
||||
}
|
||||
else { // OK
|
||||
else { // PSRAM OK
|
||||
// Init camera
|
||||
// ********************************************
|
||||
PowerResetCamera();
|
||||
@@ -296,10 +351,12 @@ extern "C" void app_main(void)
|
||||
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Camera init failed (" + std::string(camStatusHex) +
|
||||
")! Check camera module and/or proper electrical connection");
|
||||
setSystemStatusFlag(SYSTEM_STATUS_CAM_BAD);
|
||||
Camera.LightOnOff(false); // make sure flashlight is off
|
||||
StatusLED(CAM_INIT, 1, true);
|
||||
}
|
||||
}
|
||||
else { // ESP_OK -> Camera init OK --> continue to perform camera framebuffer check
|
||||
|
||||
if (camStatus == ESP_OK) { // ESP_OK -> Camera init OK --> continue to perform camera framebuffer check
|
||||
// Camera framebuffer check
|
||||
// ********************************************
|
||||
if (!Camera.testCamera()) {
|
||||
@@ -324,22 +381,6 @@ extern "C" void app_main(void)
|
||||
}
|
||||
}
|
||||
|
||||
// SD card: basic R/W check
|
||||
// ********************************************
|
||||
int iSDCardStatus = SDCardCheckRW();
|
||||
if (iSDCardStatus < 0) {
|
||||
if (iSDCardStatus <= -1 && iSDCardStatus >= -2) { // write error
|
||||
StatusLED(SDCARD_CHECK, 1, true);
|
||||
}
|
||||
else if (iSDCardStatus <= -3 && iSDCardStatus >= -5) { // read error
|
||||
StatusLED(SDCARD_CHECK, 2, true);
|
||||
}
|
||||
else if (iSDCardStatus == -6) { // delete error
|
||||
StatusLED(SDCARD_CHECK, 3, true);
|
||||
}
|
||||
setSystemStatusFlag(SYSTEM_STATUS_SDCARD_CHECK_BAD); // reduced web interface going to be loaded
|
||||
}
|
||||
|
||||
// Migrate parameter in config.ini to new naming (firmware 15.0 and newer)
|
||||
// ********************************************
|
||||
migrateConfiguration();
|
||||
@@ -352,18 +393,6 @@ extern "C" void app_main(void)
|
||||
// ********************************************
|
||||
setCpuFrequency();
|
||||
|
||||
// SD card: Create further mandatory directories (if not already existing)
|
||||
// Correct creation of these folders will be checked with function "SDCardCheckFolderFilePresence"
|
||||
// ********************************************
|
||||
MakeDir("/sdcard/firmware"); // mandatory for OTA firmware update
|
||||
MakeDir("/sdcard/img_tmp"); // mandatory for setting up alignment marks
|
||||
MakeDir("/sdcard/demo"); // mandatory for demo mode
|
||||
|
||||
// Check for updates
|
||||
// ********************************************
|
||||
CheckOTAUpdate();
|
||||
CheckUpdate();
|
||||
|
||||
// Start SoftAP for initial remote setup
|
||||
// Note: Start AP if no wlan.ini and/or config.ini available, e.g. SD card empty; function does not exit anymore until reboot
|
||||
// ********************************************
|
||||
@@ -429,6 +458,8 @@ extern "C" void app_main(void)
|
||||
StatusLED(WLAN_INIT, 3, true);
|
||||
return;
|
||||
}
|
||||
|
||||
init_basic_auth();
|
||||
}
|
||||
else if (iWLANStatus == -1) { // wlan.ini not available, potentially empty or content not readable
|
||||
StatusLED(WLAN_INIT, 1, true);
|
||||
@@ -600,27 +631,57 @@ void migrateConfiguration(void) {
|
||||
}
|
||||
else if ((isInString(configLines[i], "Zoom")) && (!isInString(configLines[i], "CamZoom")) && (!isInString(configLines[i], "ZoomMode")) && (!isInString(configLines[i], "ZoomOffsetX")) && (!isInString(configLines[i], "ZoomOffsetY"))) {
|
||||
CamZoom_lines = i;
|
||||
CamZoom_value = alphanumericToBoolean(splitted[1]);
|
||||
if (splitted.size() < 2) {
|
||||
CamZoom_value = false;
|
||||
}
|
||||
else {
|
||||
ESP_LOGE(TAG, "splitted[1]: %s", splitted[1].c_str());
|
||||
CamZoom_value = alphanumericToBoolean(splitted[1]);
|
||||
}
|
||||
CamZoom_found = true;
|
||||
}
|
||||
else if ((isInString(configLines[i], "ZoomMode")) && (!isInString(configLines[i], "CamZoom"))) {
|
||||
CamZoomSize_lines = i;
|
||||
if (isStringNumeric(splitted[1])) {
|
||||
CamZoomSize_value = std::stof(splitted[1]);
|
||||
if (splitted.size() < 2) {
|
||||
CamZoomSize_value = 0;
|
||||
}
|
||||
else {
|
||||
if (isStringNumeric(splitted[1])) {
|
||||
CamZoomSize_value = std::stof(splitted[1]);
|
||||
}
|
||||
else {
|
||||
CamZoomSize_value = 0;
|
||||
}
|
||||
}
|
||||
CamZoom_found = true;
|
||||
}
|
||||
else if ((isInString(configLines[i], "ZoomOffsetX")) && (!isInString(configLines[i], "CamZoom")) && (!isInString(configLines[i], "ZoomOffsetY"))) {
|
||||
CamZoomOffsetX_lines = i;
|
||||
if (isStringNumeric(splitted[1])) {
|
||||
CamZoomOffsetX_value = std::stof(splitted[1]);
|
||||
if (splitted.size() < 2) {
|
||||
CamZoomOffsetX_value = 0;
|
||||
}
|
||||
else {
|
||||
if (isStringNumeric(splitted[1])) {
|
||||
CamZoomOffsetX_value = std::stof(splitted[1]);
|
||||
}
|
||||
else {
|
||||
CamZoomOffsetX_value = 0;
|
||||
}
|
||||
}
|
||||
CamZoom_found = true;
|
||||
}
|
||||
else if ((isInString(configLines[i], "ZoomOffsetY")) && (!isInString(configLines[i], "CamZoom")) && (!isInString(configLines[i], "ZoomOffsetX"))) {
|
||||
CamZoomOffsetY_lines = i;
|
||||
if (isStringNumeric(splitted[1])) {
|
||||
CamZoomOffsetY_value = std::stof(splitted[1]);
|
||||
if (splitted.size() < 2) {
|
||||
CamZoomOffsetY_value = 0;
|
||||
}
|
||||
else {
|
||||
if (isStringNumeric(splitted[1])) {
|
||||
CamZoomOffsetY_value = std::stof(splitted[1]);
|
||||
}
|
||||
else {
|
||||
CamZoomOffsetY_value = 0;
|
||||
}
|
||||
}
|
||||
CamZoom_found = true;
|
||||
}
|
||||
@@ -896,11 +957,8 @@ bool setCpuFrequency(void) {
|
||||
/* Load config from config file */
|
||||
while ((!configFile.GetNextParagraph(line, disabledLine, eof) ||
|
||||
(line.compare("[System]") != 0)) && !eof) {}
|
||||
if (eof) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (disabledLine) {
|
||||
if (eof || disabledLine) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -909,7 +967,12 @@ bool setCpuFrequency(void) {
|
||||
splitted = ZerlegeZeile(line);
|
||||
|
||||
if (toUpper(splitted[0]) == "CPUFREQUENCY") {
|
||||
cpuFrequency = splitted[1];
|
||||
if (splitted.size() < 2) {
|
||||
cpuFrequency = "160";
|
||||
}
|
||||
else {
|
||||
cpuFrequency = splitted[1];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
#include "MainFlowControl.h"
|
||||
#include "esp_log.h"
|
||||
#include "basic_auth.h"
|
||||
#include "esp_chip_info.h"
|
||||
|
||||
#include <stdio.h>
|
||||
@@ -408,7 +409,7 @@ void register_server_main_uri(httpd_handle_t server, const char *base_path)
|
||||
httpd_uri_t info_get_handle = {
|
||||
.uri = "/info", // Match all URIs of type /path/to/file
|
||||
.method = HTTP_GET,
|
||||
.handler = info_get_handler,
|
||||
.handler = APPLY_BASIC_AUTH_FILTER(info_get_handler),
|
||||
.user_ctx = (void*) base_path // Pass server data as context
|
||||
};
|
||||
httpd_register_uri_handler(server, &info_get_handle);
|
||||
@@ -416,7 +417,7 @@ void register_server_main_uri(httpd_handle_t server, const char *base_path)
|
||||
httpd_uri_t sysinfo_handle = {
|
||||
.uri = "/sysinfo", // Match all URIs of type /path/to/file
|
||||
.method = HTTP_GET,
|
||||
.handler = sysinfo_handler,
|
||||
.handler = APPLY_BASIC_AUTH_FILTER(sysinfo_handler),
|
||||
.user_ctx = (void*) base_path // Pass server data as context
|
||||
};
|
||||
httpd_register_uri_handler(server, &sysinfo_handle);
|
||||
@@ -424,7 +425,7 @@ void register_server_main_uri(httpd_handle_t server, const char *base_path)
|
||||
httpd_uri_t starttime_tmp_handle = {
|
||||
.uri = "/starttime", // Match all URIs of type /path/to/file
|
||||
.method = HTTP_GET,
|
||||
.handler = starttime_get_handler,
|
||||
.handler = APPLY_BASIC_AUTH_FILTER(starttime_get_handler),
|
||||
.user_ctx = NULL // Pass server data as context
|
||||
};
|
||||
httpd_register_uri_handler(server, &starttime_tmp_handle);
|
||||
@@ -432,7 +433,7 @@ void register_server_main_uri(httpd_handle_t server, const char *base_path)
|
||||
httpd_uri_t img_tmp_handle = {
|
||||
.uri = "/img_tmp/*", // Match all URIs of type /path/to/file
|
||||
.method = HTTP_GET,
|
||||
.handler = img_tmp_virtual_handler,
|
||||
.handler = APPLY_BASIC_AUTH_FILTER(img_tmp_virtual_handler),
|
||||
.user_ctx = (void*) base_path // Pass server data as context
|
||||
};
|
||||
httpd_register_uri_handler(server, &img_tmp_handle);
|
||||
@@ -440,7 +441,7 @@ void register_server_main_uri(httpd_handle_t server, const char *base_path)
|
||||
httpd_uri_t main_rest_handle = {
|
||||
.uri = "/*", // Match all URIs of type /path/to/file
|
||||
.method = HTTP_GET,
|
||||
.handler = hello_main_handler,
|
||||
.handler = APPLY_BASIC_AUTH_FILTER(hello_main_handler),
|
||||
.user_ctx = (void*) base_path // Pass server data as context
|
||||
};
|
||||
httpd_register_uri_handler(server, &main_rest_handle);
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include "Helper.h"
|
||||
#include "statusled.h"
|
||||
#include "server_ota.h"
|
||||
#include "basic_auth.h"
|
||||
|
||||
#include "lwip/err.h"
|
||||
#include "lwip/sys.h"
|
||||
@@ -468,7 +469,7 @@ httpd_handle_t start_webserverAP(void)
|
||||
httpd_uri_t reboot_handle = {
|
||||
.uri = "/reboot", // Match all URIs of type /path/to/file
|
||||
.method = HTTP_GET,
|
||||
.handler = reboot_handlerAP,
|
||||
.handler = APPLY_BASIC_AUTH_FILTER(reboot_handlerAP),
|
||||
.user_ctx = NULL // Pass server data as context
|
||||
};
|
||||
httpd_register_uri_handler(server, &reboot_handle);
|
||||
@@ -476,7 +477,7 @@ httpd_handle_t start_webserverAP(void)
|
||||
httpd_uri_t config_ini_handle = {
|
||||
.uri = "/config", // Match all URIs of type /path/to/file
|
||||
.method = HTTP_GET,
|
||||
.handler = config_ini_handler,
|
||||
.handler = APPLY_BASIC_AUTH_FILTER(config_ini_handler),
|
||||
.user_ctx = NULL // Pass server data as context
|
||||
};
|
||||
httpd_register_uri_handler(server, &config_ini_handle);
|
||||
@@ -485,7 +486,7 @@ httpd_handle_t start_webserverAP(void)
|
||||
httpd_uri_t file_uploadAP = {
|
||||
.uri = "/upload/*", // Match all URIs of type /upload/path/to/file
|
||||
.method = HTTP_POST,
|
||||
.handler = upload_post_handlerAP,
|
||||
.handler = APPLY_BASIC_AUTH_FILTER(upload_post_handlerAP),
|
||||
.user_ctx = NULL // Pass server data as context
|
||||
};
|
||||
httpd_register_uri_handler(server, &file_uploadAP);
|
||||
@@ -493,7 +494,7 @@ httpd_handle_t start_webserverAP(void)
|
||||
httpd_uri_t test_uri = {
|
||||
.uri = "*",
|
||||
.method = HTTP_GET,
|
||||
.handler = test_handler,
|
||||
.handler = APPLY_BASIC_AUTH_FILTER(test_handler),
|
||||
.user_ctx = NULL
|
||||
};
|
||||
httpd_register_uri_handler(server, &test_uri);
|
||||
|
||||
@@ -57,8 +57,15 @@ build_flags =
|
||||
${common:esp32-idf.build_flags}
|
||||
${flags:runtime.build_flags}
|
||||
; ### Sofware options : (can be set in defines.h)
|
||||
-D BOARD_ESP32CAM_AITHINKER
|
||||
-D ENABLE_MQTT
|
||||
-D BOARD_ESP32CAM_AITHINKER
|
||||
-D ENABLE_MQTT
|
||||
;-D MQTT_PROTOCOL_311
|
||||
-D MQTT_ENABLE_SSL
|
||||
;-D MQTT_ENABLE_WS
|
||||
;-D MQTT_ENABLE_WSS
|
||||
-D MQTT_SUPPORTED_FEATURE_SKIP_CRT_CMN_NAME_CHECK
|
||||
;-D MQTT_SUPPORTED_FEATURE_CRT_CMN_NAME
|
||||
;-D MQTT_SUPPORTED_FEATURE_CLIENT_KEY_PASSWORD
|
||||
-D ENABLE_INFLUXDB
|
||||
-D ENABLE_WEBHOOK
|
||||
-D ENABLE_SOFTAP
|
||||
|
||||
@@ -127,7 +127,31 @@ CONFIG_MQTT_USE_CUSTOM_CONFIG=y
|
||||
#CONFIG_MQTT_OUTBOX_EXPIRED_TIMEOUT_MS=5000
|
||||
#CONFIG_MQTT_CUSTOM_OUTBOX=y # -> Use custom outbox in components/jomjol_mqtt/mqtt_outbox.h/cpp. If USE_PSRAM is enabled in there, it will save 10 kBytes of internal RAM. How ever it also leads to memory fragmentation, see https://github.com/jomjol/AI-on-the-edge-device/issues/2200
|
||||
|
||||
CONFIG_FREERTOS_TASK_FUNCTION_WRAPPER=n
|
||||
#
|
||||
# mbedTLS
|
||||
#
|
||||
CONFIG_MBEDTLS_HAVE_TIME=y
|
||||
CONFIG_MBEDTLS_HAVE_TIME_DATE=y
|
||||
|
||||
#
|
||||
# ESP-Driver:LEDC Configurations
|
||||
#
|
||||
CONFIG_LEDC_CTRL_FUNC_IN_IRAM=y
|
||||
# end of ESP-Driver:LEDC Configurations
|
||||
|
||||
#
|
||||
# Legacy RMT Driver Configurations
|
||||
#
|
||||
CONFIG_RMT_SUPPRESS_DEPRECATE_WARN=y
|
||||
# end of Legacy RMT Driver Configurations
|
||||
|
||||
#
|
||||
# ESP-Driver:RMT Configurations
|
||||
#
|
||||
CONFIG_RMT_ISR_IRAM_SAFE=y
|
||||
CONFIG_RMT_RECV_FUNC_IN_IRAM=y
|
||||
# CONFIG_RMT_ENABLE_DEBUG_LOG is not set
|
||||
# end of ESP-Driver:RMT Configurations
|
||||
|
||||
CONFIG_CAMERA_CORE0=n
|
||||
CONFIG_CAMERA_CORE1=y
|
||||
@@ -156,6 +180,7 @@ CONFIG_SYSTEM_EVENT_TASK_STACK_SIZE=4864
|
||||
#CONFIG_FREERTOS_USE_TRACE_FACILITY=1
|
||||
#CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=y
|
||||
#CONFIG_FREERTOS_VTASKLIST_INCLUDE_COREID=y
|
||||
CONFIG_FREERTOS_TASK_FUNCTION_WRAPPER=n
|
||||
|
||||
#force disable HIMEM as not used in default config, can be enabled with [env:esp32cam-dev-himem]
|
||||
#free 256kb of internal memory :
|
||||
|
||||
@@ -37,23 +37,58 @@ void test_createSequenceMetrics()
|
||||
NumberPost *number_1 = new NumberPost;
|
||||
number_1->name = "main";
|
||||
number_1->ReturnValue = "123.456";
|
||||
number_1->ReturnRawValue = "N23.456";
|
||||
number_1->ReturnPreValue = "986.543";
|
||||
number_1->ErrorMessageText = "";
|
||||
NUMBERS.push_back(number_1);
|
||||
|
||||
const std::string metricNamePrefix = "ai_on_the_edge_device";
|
||||
const std::string metricName = metricNamePrefix + "_flow_value";
|
||||
const std::string metricName1 = metricNamePrefix + "_flow_value";
|
||||
const std::string metricName2 = metricNamePrefix + "_flow_raw_value";
|
||||
const std::string metricName3 = metricNamePrefix + "_flow_pre_value";
|
||||
const std::string metricName4 = metricNamePrefix + "_flow_error";
|
||||
|
||||
std::string expected1 ;
|
||||
expected1 = "# HELP " + metricName1 + " current value of meter readout\n# TYPE " + metricName1 + " gauge\n" +
|
||||
metricName1 + "{sequence=\"" + number_1->name + "\"} " + number_1->ReturnValue + "\n";
|
||||
|
||||
expected1 += "# HELP " + metricName2 + " current raw value of meter readout\n# TYPE " + metricName2 + " gauge\n" +
|
||||
metricName2 + "{sequence=\"" + number_1->name + "\"} " + "NaN" + "\n";
|
||||
|
||||
expected1 += "# HELP " + metricName3 + " previous value of meter readout\n# TYPE " + metricName3 + " gauge\n" +
|
||||
metricName3 + "{sequence=\"" + number_1->name + "\"} " + number_1->ReturnPreValue + "\n";
|
||||
|
||||
expected1 += "# HELP " + metricName4 + " Error message text != 'no error'\n# TYPE " + metricName4 + " gauge\n" +
|
||||
metricName4 + "{sequence=\"" + number_1->name + "\"} " + "1" + "\n";
|
||||
|
||||
std::string expected1 = "# HELP " + metricName + " current value of meter readout\n# TYPE " + metricName + " gauge\n" +
|
||||
metricName + "{sequence=\"" + number_1->name + "\"} " + number_1->ReturnValue + "\n";
|
||||
TEST_ASSERT_EQUAL_STRING(expected1.c_str(), createSequenceMetrics(metricNamePrefix, NUMBERS).c_str());
|
||||
|
||||
NumberPost *number_2 = new NumberPost;
|
||||
number_2->name = "secondary";
|
||||
number_2->ReturnValue = "1.0";
|
||||
number_2->ReturnRawValue = "01.000";
|
||||
number_2->ReturnPreValue = "0.987";
|
||||
number_2->ErrorMessageText = "no error";
|
||||
NUMBERS.push_back(number_2);
|
||||
|
||||
std::string expected2 = "# HELP " + metricName + " current value of meter readout\n# TYPE " + metricName + " gauge\n" +
|
||||
metricName + "{sequence=\"" + number_1->name + "\"} " + number_1->ReturnValue + "\n" +
|
||||
metricName + "{sequence=\"" + number_2->name + "\"} " + number_2->ReturnValue + "\n";
|
||||
std::string expected2 ;
|
||||
expected2 = "# HELP " + metricName1 + " current value of meter readout\n# TYPE " + metricName1 + " gauge\n" +
|
||||
metricName1 + "{sequence=\"" + number_1->name + "\"} " + number_1->ReturnValue + "\n" +
|
||||
metricName1 + "{sequence=\"" + number_2->name + "\"} " + number_2->ReturnValue + "\n";
|
||||
|
||||
expected2 += "# HELP " + metricName2 + " current raw value of meter readout\n# TYPE " + metricName2 + " gauge\n" +
|
||||
metricName2 + "{sequence=\"" + number_1->name + "\"} " + "NaN" + "\n" +
|
||||
metricName2 + "{sequence=\"" + number_2->name + "\"} " + number_2->ReturnRawValue + "\n";
|
||||
|
||||
expected2 += "# HELP " + metricName3 + " previous value of meter readout\n# TYPE " + metricName3 + " gauge\n" +
|
||||
metricName3 + "{sequence=\"" + number_1->name + "\"} " + number_1->ReturnPreValue + "\n" +
|
||||
metricName3 + "{sequence=\"" + number_2->name + "\"} " + number_2->ReturnPreValue + "\n";
|
||||
|
||||
expected2 += "# HELP " + metricName4 + " Error message text != 'no error'\n# TYPE " + metricName4 + " gauge\n" +
|
||||
metricName4 + "{sequence=\"" + number_1->name + "\"} " + "1" + "\n" +
|
||||
metricName4 + "{sequence=\"" + number_2->name + "\"} " + "0" + "\n";
|
||||
|
||||
|
||||
TEST_ASSERT_EQUAL_STRING(expected2.c_str(), createSequenceMetrics(metricNamePrefix, NUMBERS).c_str());
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,14 @@
|
||||
<li>The SD card can be setup automatically after the firmware got installed. See the <a href=https://jomjol.github.io/AI-on-the-edge-device-docs/Installation/#remote-setup-using-the-built-in-access-point target=_blank>documentation</a> for details. For this to work, the SD card must be FAT formated (which is the default on a new SD card).
|
||||
Alternatively the SD card still can be setup manually, see the <a href=https://jomjol.github.io/AI-on-the-edge-device-docs/Installation/#3-sd-card target=_blank>documentation</a> for details!</li>
|
||||
</ul>
|
||||
|
||||
<h2>Licence:</h2>
|
||||
<ul>
|
||||
<li>This project is published under an individual license: <a href="https://github.com/jomjol/AI-on-the-edge-device?tab=License-1-ov-file#readme" target="_blank">License</a></li>
|
||||
<li>By using this web installer, you agree to this license. In particular, a separate agreement with the authors is required for commercial use of AI-on-the-Edge.</li>
|
||||
</ul>
|
||||
|
||||
|
||||
<hr>
|
||||
|
||||
<p><esp-web-install-button manifest="manifest.json"></esp-web-install-button></p>
|
||||
|
||||
@@ -31,7 +31,6 @@ AlignmentAlgo
|
||||
CNNGoodThreshold
|
||||
PreValueAgeStartup
|
||||
ErrorMessage
|
||||
CheckDigitIncreaseConsistency
|
||||
IO0
|
||||
IO1
|
||||
IO3
|
||||
@@ -43,5 +42,6 @@ Hostname
|
||||
RSSIThreshold
|
||||
TimeServer
|
||||
CACert
|
||||
ValidateServerCert
|
||||
ClientCert
|
||||
ClientKey
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
Default Value: `true`
|
||||
|
||||
!!! Warning
|
||||
This parameter is no longer available. The flow is now always enabled. If you want it to be disabled, set an interval which is high enough (eg. 1440 = 24h).
|
||||
This parameter is no longer available. The flow is now always enabled. If you want it to be disabled, set an interval which is high enough (eg. 1440 = 24h).
|
||||
|
||||
@@ -7,5 +7,7 @@ Interval in which the Flow (Digitization Round) is run.
|
||||
It will run immediately on startup and then the next time after the given interval.
|
||||
If a round takes longer than this interval, the next round gets postponed until the current round completes.
|
||||
|
||||
If the flow gets started by a MQTT message or the REST API call, the interval automatically gets reset.
|
||||
|
||||
!!! Note
|
||||
If you want the flow to be disabled, set an interval which is high enough (eg. 1440 = 24h).
|
||||
If you want the flow to be disabled, set an interval which is high enough (eg. 1440 = 24h).
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
# Parameter `<NUMBER>.Field`
|
||||
# Parameter `Field`
|
||||
Default Value: `undefined`
|
||||
|
||||
Dedicated definition of the field for InfluxDB use for saving in the Influx database (e.g.: "watermeter/value").
|
||||
|
||||
!!! Note
|
||||
This parameter must be prefixed with `<NUMBER>` followed by a dot (eg. `main.Field`). `<NUMBER>` is the name of the number sequence defined in the ROI's.
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
# Parameter `<NUMBER>.Field`
|
||||
# Parameter `Field`
|
||||
Default Value: `undefined`
|
||||
|
||||
Field for InfluxDB v2 to use for saving.
|
||||
|
||||
!!! Note
|
||||
This parameter must be prefixed with `<NUMBER>` followed by a dot (eg. `main.Field`). `<NUMBER>` is the name of the number sequence defined in the ROI's.
|
||||
|
||||
@@ -1,21 +1,24 @@
|
||||
# Parameter `CACert`
|
||||
Default Value: `""`
|
||||
|
||||
Example: `/config/certs/RootCA.pem`.
|
||||
Example: `/config/certs/RootCA.crt`.
|
||||
|
||||
!!! Warning
|
||||
This is an **Expert Parameter**! Only change it if you understand what it does!
|
||||
|
||||
Path to the CA certificate file.
|
||||
|
||||
This is part of the configuration to enable TLS for MQTT.
|
||||
This is part of the configuration to enable TLS 1.2 for MQTT.<br>
|
||||
|
||||
The CA Certificate is used by the client to validate the broker is who it claims to be.
|
||||
It allows the client to authenticate the server, which is the first part of the MTLS handshake.
|
||||
|
||||
Usually there is a common RootCA certificate for the MQTT broker
|
||||
Usually there is a common RootCA certificate for the MQTT broker.
|
||||
More information is available [here](https://jomjol.github.io/AI-on-the-edge-device-docs/MQTT-API/#mqtt-tls).
|
||||
|
||||
For more information on how to create your own certificate, see: [mosquitto.org](https://mosquitto.org/man/mosquitto-tls-7.html) or [emqx.com](https://www.emqx.com/en/blog/emqx-server-ssl-tls-secure-connection-configuration-guide).
|
||||
|
||||
!!! Note
|
||||
This also means that you might have to change the protocol and port in [uri](https://jomjol.github.io/AI-on-the-edge-device-docs/Parameters/#parameter-uri) to `mqtts://example.com:8883`!
|
||||
|
||||
!!! Note
|
||||
Only TLS 1.2 is supported!
|
||||
Only Certificates up to 4096 Bit are supported!
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
# Parameter `ClientCert`
|
||||
Default Value: `""`
|
||||
|
||||
Example: `/config/certs/client.pem.crt`.
|
||||
Example: `/config/certs/client.crt`.
|
||||
|
||||
!!! Warning
|
||||
This is an **Expert Parameter**! Only change it if you understand what it does!
|
||||
|
||||
Path to the Client Certificate file.
|
||||
|
||||
This is part of the configuration to enable TLS for MQTT.
|
||||
This is part of the configuration to enable TLS 1.2 for MQTT.<br>
|
||||
|
||||
The Client Certificate is used by the client to prove its identity to the server, in conjunction with the Client Key.
|
||||
It is the second part of the MTLS handshake.
|
||||
|
||||
Usually there is a one pair of Client Certificate/Key for each client that connects to the MQTT broker
|
||||
Usually there is a one pair of Client Certificate/Key for each client that connects to the MQTT broker.
|
||||
More information is available [here](https://jomjol.github.io/AI-on-the-edge-device-docs/MQTT-API/#mqtt-tls).
|
||||
|
||||
For more information on how to create your own certificate, see: [mosquitto.org](https://mosquitto.org/man/mosquitto-tls-7.html) or [emqx.com](https://www.emqx.com/en/blog/emqx-server-ssl-tls-secure-connection-configuration-guide).
|
||||
|
||||
!!! Note
|
||||
If set, `ClientKey` must be set too
|
||||
If set, `ClientKey` must be set too.
|
||||
This also means that you might have to change the protocol and port in [uri](https://jomjol.github.io/AI-on-the-edge-device-docs/Parameters/#parameter-uri) to `mqtts://example.com:8883`!
|
||||
|
||||
!!! Note
|
||||
Only TLS 1.2 is supported!
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
# Parameter `ClientKey`
|
||||
Default Value: `""`
|
||||
|
||||
Example: `/config/certs/client.pem.key`.
|
||||
Example: `/config/certs/client.key`.
|
||||
|
||||
!!! Warning
|
||||
This is an **Expert Parameter**! Only change it if you understand what it does!
|
||||
|
||||
Path to the Client Key file.
|
||||
|
||||
This is part of the configuration to enable TLS for MQTT.
|
||||
This is part of the configuration to enable TLS 1.2 for MQTT.<br>
|
||||
|
||||
The Client Key is used by the client to prove its identity to the server, in conjunction with the Client Certificate.
|
||||
It is the second part of the MTLS handshake.
|
||||
|
||||
Usually there is a one pair of Client Certificate/Key for each client that connects to the MQTT broker
|
||||
|
||||
!!! Note
|
||||
If set, `ClientCert` must be set too
|
||||
This also means that you might have to change the protocol and port in [uri](https://jomjol.github.io/AI-on-the-edge-device-docs/Parameters/#parameter-uri) to `mqtts://example.com:8883`!
|
||||
For more information on how to create your own certificate, see: [mosquitto.org](https://mosquitto.org/man/mosquitto-tls-7.html) or [emqx.com](https://www.emqx.com/en/blog/emqx-server-ssl-tls-secure-connection-configuration-guide).
|
||||
|
||||
!!! Note
|
||||
Only TLS 1.2 is supported!
|
||||
If set, `ClientCert` must be set too.
|
||||
This also means that you might have to change the protocol and port in [uri](https://jomjol.github.io/AI-on-the-edge-device-docs/Parameters/#parameter-uri) to `mqtts://example.com:8883`!
|
||||
|
||||
@@ -3,9 +3,26 @@ Default Value: `other`
|
||||
|
||||
Select the Meter Type so the sensors have the right units in Homeassistant.
|
||||
|
||||
!!! Note
|
||||
For `Watermeter` you need to have Homeassistant 2022.11 or newer!
|
||||
|
||||
Please also make sure that the selected Meter Type matches the dimension of the value provided by the meter!
|
||||
Eg. if your meter provides `m³`, you need to also set it to `m³`.
|
||||
Alternatively you can set the parameter `DecimalShift` to `3` so the value is converted to `liters`!
|
||||
|
||||
List of supported options:
|
||||
|
||||
- `other`
|
||||
- `water_m3` (uses `m^3/min` as rate)
|
||||
- `water_l` (uses `l/h` as rate, not officially supported by Homeassistant!)
|
||||
- `water_gal` (uses `gal/h` as rate, not officially supported by Homeassistant!)
|
||||
- `water_ft3` (uses `ft^3/min` as rate)
|
||||
- `gas_m3` (uses `m^3/min` as rate)
|
||||
- `gas_ft3` (uses `ft^3/min` as rate)
|
||||
- `energy_wh` (uses `W` as rate)
|
||||
- `energy_kwh` (uses `KW` as rate)
|
||||
- `energy_mwh` (uses `MW` as rate)
|
||||
- `energy_gj` (uses `GJ/h` as rate, not officially supported by Homeassistant!)
|
||||
- `temperature_c` (uses `+C/min` as rate)
|
||||
- `temperature_f` (uses `°F/min` as rate)
|
||||
- `temperature_k` (uses `K/min` as rate)
|
||||
|
||||
!!! Note
|
||||
Not all options are supported by Homeassistant, see `SensorDeviceClass.VOLUME_FLOW_RATE` in [https://developers.home-assistant.io/docs/core/entity/sensor/#available-device-classes](https://developers.home-assistant.io/docs/core/entity/sensor/#available-device-classes)!
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
# Parameter `<NUMBER>.DomoticzIDX`
|
||||
# Parameter `DomoticzIDX`
|
||||
Default Value: `0`
|
||||
|
||||
The Idx number for the counter device. Can be obtained from the devices setup page on the Domoticz system.
|
||||
|
||||
!!! Note
|
||||
This parameter must be prefixed with `<NUMBER>` followed by a dot (eg. `main.DomoticzIDX`). `<NUMBER>` is the name of the number sequence defined in the ROI's.
|
||||
|
||||
@@ -2,3 +2,6 @@
|
||||
Default Value: `true`
|
||||
|
||||
Enable or disable the [Retain Flag](https://www.hivemq.com/blog/mqtt-essentials-part-8-retained-messages/) for all MQTT entries.
|
||||
|
||||
!!! Warning
|
||||
Disabling (set to `false`) this does not clear the last retained value on the MQTT broker! This must be done manually, see [How to Delete Retained Messages in MQTT?](https://www.hivemq.com/blog/mqtt-essentials-part-8-retained-messages/#heading-how-to-delete-retained-messages-in-mqtt) and this [discussion](https://github.com/jomjol/AI-on-the-edge-device/discussions/3534#discussioncomment-12199543)!
|
||||
|
||||
20
param-docs/parameter-pages/MQTT/ValidateServerCert.md
Normal file
20
param-docs/parameter-pages/MQTT/ValidateServerCert.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# Parameter `ValidateServerCert`
|
||||
|
||||
Default Value: `true`
|
||||
|
||||
!!! Warning
|
||||
This is an **Expert Parameter**! Only change it if you understand what it does!
|
||||
|
||||
Enable or disable the validation of the server certificate CN field.<br>
|
||||
|
||||
If `enabled (true)`, the certificate sent by the server is validated using the configured [Root CA Certificate file](https://jomjol.github.io/AI-on-the-edge-device-docs/Parameters/#parameter-cacert).<br>
|
||||
The server name in [uri](https://jomjol.github.io/AI-on-the-edge-device-docs/Parameters/#parameter-uri) is compared with the CN field of the server certificate.<br>
|
||||
A connection is only established if they agree. It ensures the origin of the server.
|
||||
|
||||
If `disabled (false)`, the ESP32 skipped any validation of server certificate CN field.<br>
|
||||
This reduces the security of TLS and makes the *MQTT* client susceptible to MITM attacks.
|
||||
|
||||
!!! Note
|
||||
This also means that you might have to change the protocol and port in to `mqtts://example.com:8883`!
|
||||
|
||||
If you use public brokers, is recommended to set this parameter to "enabled (true)".
|
||||
@@ -1,7 +1,10 @@
|
||||
# Parameter `<NUMBERS>.AllowNegativeRates`
|
||||
# Parameter `AllowNegativeRates`
|
||||
Default Value: `false`
|
||||
|
||||
Allow a meter to count backwards (decreasing values).
|
||||
|
||||
!!! Note
|
||||
This is unusual (it means there is a negative rate) and not wanted in most cases!
|
||||
|
||||
!!! Note
|
||||
This parameter must be prefixed with `<NUMBER>` followed by a dot (eg. `main.AllowNegativeRates`). `<NUMBER>` is the name of the number sequence defined in the ROI's.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Parameter `<NUMBER>.AnalogToDigitTransitionStart`
|
||||
# Parameter `AnalogToDigitTransitionStart`
|
||||
Default Value: `9.2`
|
||||
|
||||
This can be used if you have wrong values, but the recognition of the individual ROIs are correct.
|
||||
@@ -7,3 +7,6 @@ Set it here. Only used on combination of digits and analog pointers.
|
||||
See [here](../Watermeter-specific-analog---digit-transition) for details.
|
||||
|
||||
Range: `6.0` .. `9.9`.
|
||||
|
||||
!!! Note
|
||||
This parameter must be prefixed with `<NUMBER>` followed by a dot (eg. `main.AnalogToDigitTransitionStart`). `<NUMBER>` is the name of the number sequence defined in the ROI's.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Parameter `<NUMBER>.ChangeRateThreshold`
|
||||
# Parameter `ChangeRateThreshold`
|
||||
Default Value: `2`
|
||||
|
||||
Range: `1` .. `9`.
|
||||
@@ -9,17 +9,20 @@ This parameter is intended to compensate for small reading fluctuations that occ
|
||||
It is only applied to the last digit of the read value (See example below).
|
||||
If the read value is within PreValue +/- Threshold, no further calculation is carried out and the Value/Prevalue remains at the old value.
|
||||
|
||||
Example:
|
||||
!!! Note
|
||||
This parameter must be prefixed with `<NUMBER>` followed by a dot (eg. `main.ChangeRateThreshold`). `<NUMBER>` is the name of the number sequence defined in the ROI's.
|
||||
|
||||
Smallest ROI provides value for 0.000x
|
||||
ChangeRateThreshold = 2
|
||||
## Example
|
||||
|
||||
- Smallest ROI provides value for `0.000'x` (Eg. a water meter with 4 pointers behind the decimal point)
|
||||
- ChangeRateThreshold = 2
|
||||
|
||||
Extended Resolution disabled:
|
||||
PreValue: 123.456'7 >>> Threshold = +/- 0.000'2
|
||||
Comparative value >>> max = 123.456'9 and min = 123.456'5
|
||||
#### With `Extended Resolution` **disabled**
|
||||
PreValue: `123.456'7` -> Threshold = `+/-0.000'2`.<br>
|
||||
All changes between `123.456'5` and `123.456'9` get ignored
|
||||
|
||||
Extended Resolution enabled:
|
||||
PreValue: 123.456'78 >>> Threshold = +/- 0.000'02
|
||||
Comparative value >>> max = 123.456'80 and min = 123.456'76
|
||||
#### With `Extended Resolution` **enabled**
|
||||
PreValue: `123.456'78` -> Threshold = `+/-0.000'02`.<br>
|
||||
All changes between `123.456'76` and `123.456'80` get ignored.
|
||||
|
||||

|
||||
|
||||
@@ -6,3 +6,6 @@ Default Value: `false`
|
||||
|
||||
An additional consistency check.
|
||||
It especially improves the zero crossing check between digits.
|
||||
|
||||
!!! Note
|
||||
This parameter must be prefixed with `<NUMBER>` followed by a dot (eg. `main.CheckDigitIncreaseConsistency`). `<NUMBER>` is the name of the number sequence defined in the ROI's.
|
||||
@@ -1,5 +1,8 @@
|
||||
# Parameter `<NUMBER>.DecimalShift`
|
||||
# Parameter `DecimalShift`
|
||||
Default Value: `0`
|
||||
|
||||
Shift the decimal separator (positiv or negativ).
|
||||
Eg. to move from `m³` to `liter` (`1 m³` equals `1000 liters`), you need to set it to `+3`.
|
||||
|
||||
!!! Note
|
||||
This parameter must be prefixed with `<NUMBER>` followed by a dot (eg. `main.DecimalShift`). `<NUMBER>` is the name of the number sequence defined in the ROI's.
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
# Parameter `<NUMBER>.ExtendedResolution`
|
||||
# Parameter `ExtendedResolution`
|
||||
Default Value: `false`
|
||||
|
||||
Use the decimal place of the last analog counter for increased accuracy.
|
||||
|
||||
!!! Note
|
||||
This parameter is only supported on the `*-class*` and `*-const` models! See [Choosing-the-Model](../Choosing-the-Model) for details.
|
||||
|
||||
!!! Note
|
||||
This parameter must be prefixed with `<NUMBER>` followed by a dot (eg. `main.ExtendedResolution`). `<NUMBER>` is the name of the number sequence defined in the ROI's.
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
# Parameter `<NUMBER>.IgnoreLeadingNaN`
|
||||
# Parameter `IgnoreLeadingNaN`
|
||||
Default Value: `true`
|
||||
|
||||
Leading `N`'s will be deleted before further processing.
|
||||
This is only relevant for models which use `N`!
|
||||
See [here](../Choosing-the-Model) for details.
|
||||
|
||||
!!! Note
|
||||
This parameter must be prefixed with `<NUMBER>` followed by a dot (eg. `main.IgnoreLeadingNaN`). `<NUMBER>` is the name of the number sequence defined in the ROI's.
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
# Parameter `<NUMBER>.MaxRateType`
|
||||
# Parameter `MaxRateType`
|
||||
Default Value: `AbsoluteChange`
|
||||
|
||||
Defines if the **Change Rate** is calculated as the difference between the last two readings (`AbsoluteChange` = difference) or
|
||||
as the difference normalized to the interval (`RateChange` = difference per minute).
|
||||
|
||||
!!! Note
|
||||
This parameter must be prefixed with `<NUMBER>` followed by a dot (eg. `main.MaxRateType`). `<NUMBER>` is the name of the number sequence defined in the ROI's.
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
# Parameter `<NUMBER>.MaxRateValue`
|
||||
# Parameter `MaxRateValue`
|
||||
Default Value: `0,05`
|
||||
|
||||
Maximum allowed change between two readings, if exceeded the last reading will be rejected. Depending on the settings of `<NUMBER>.MaxRateType` the `MaxRateValue` is either treated as the difference between the two measurements (`AbsoluteChange` = difference) not taking the set time interval into account or as the difference normalized to the interval (`RateChange` = difference per minute).
|
||||
|
||||
If negative rate is disallowed and no maximum rate value is set, one false high reading will lead to a period of missing measurements until the measurement reaches the previous false high reading. E.g. if the counter is at `600,00` and it's read incorrectly as` 610,00`, all measurements will be skipped until the counter reaches `610,00`. Setting the MaxRateValue to `0,05` leads to a rejection of all readings with a difference `> 0,05`, in this case `610,00`. The rejection also applies to correct readings with a difference `> 0,05`!
|
||||
|
||||
!!! Note
|
||||
This parameter must be prefixed with `<NUMBER>` followed by a dot (eg. `main.MaxRateValue`). `<NUMBER>` is the name of the number sequence defined in the ROI's.
|
||||
|
||||
BIN
sd-card/config/ana-cont_1400_s2_q.tflite
Normal file
BIN
sd-card/config/ana-cont_1400_s2_q.tflite
Normal file
Binary file not shown.
@@ -87,6 +87,7 @@ HomeassistantDiscovery = false
|
||||
;CACert = /config/certs/RootCA.pem
|
||||
;ClientCert = /config/certs/client.pem.crt
|
||||
;ClientKey = /config/certs/client.pem.key
|
||||
;ValidateServerCert = true
|
||||
;DomoticzTopicIn = domoticz/in
|
||||
;main.DomoticzIDX = 0
|
||||
|
||||
|
||||
BIN
sd-card/config/dig-class11_1900_s2_q.tflite
Normal file
BIN
sd-card/config/dig-class11_1900_s2_q.tflite
Normal file
Binary file not shown.
BIN
sd-card/config/dig-class11_1910_s2_q.tflite
Normal file
BIN
sd-card/config/dig-class11_1910_s2_q.tflite
Normal file
Binary file not shown.
BIN
sd-card/config/dig-cont_0800_s3_q.tflite
Normal file
BIN
sd-card/config/dig-cont_0800_s3_q.tflite
Normal file
Binary file not shown.
BIN
sd-card/config/dig-cont_0810_s3_q.tflite
Normal file
BIN
sd-card/config/dig-cont_0810_s3_q.tflite
Normal file
Binary file not shown.
@@ -1,222 +1,222 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" xml:lang="en">
|
||||
<head>
|
||||
<title>Backup/Restore Configuration</title>
|
||||
<meta charset="UTF-8" />
|
||||
|
||||
<style>
|
||||
h1 {font-size: 2em;}
|
||||
h2 {font-size: 1.5em; margin-block-start: 0.0em; margin-block-end: 0.2em;}
|
||||
h3 {font-size: 1.2em;}
|
||||
p {font-size: 1em;}
|
||||
|
||||
input[type=number] {
|
||||
width: 138px;
|
||||
padding: 10px 5px;
|
||||
display: inline-block;
|
||||
border: 1px solid #ccc;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.button {
|
||||
padding: 5px 10px;
|
||||
width: 205px;
|
||||
font-size: 16px;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
|
||||
<body style="font-family: arial; padding: 0px 10px;">
|
||||
<h2>Backup Configuration</h2>
|
||||
<p>With the following action the <a href="/fileserver/config/" target="_self">config</a> folder on the SD-card gets zipped and provided as a download.</p>
|
||||
|
||||
<button class="button" id="startBackup" type="button" onclick="startBackup()">Create Backup</button>
|
||||
<p id=progress></p>
|
||||
<hr>
|
||||
<h2>Restore Configuration</h2>
|
||||
<p>Use the <a href="/fileserver/config/" target="_self">File Server</a> to upload individual files.</p>
|
||||
</body>
|
||||
|
||||
|
||||
<script type="text/javascript" src="common.js?v=$COMMIT_HASH"></script>
|
||||
<script type="text/javascript" src="jszip.min.js?v=$COMMIT_HASH"></script>
|
||||
<script type="text/javascript" src="FileSaver.min.js?v=$COMMIT_HASH"></script>
|
||||
<script>
|
||||
|
||||
function startBackup() {
|
||||
document.getElementById("progress").innerHTML = "Creating backup...<br>\n";
|
||||
|
||||
// Get hostname
|
||||
try {
|
||||
var xhttp = new XMLHttpRequest();
|
||||
xhttp.open("GET", getDomainname() + "/info?type=Hostname", false);
|
||||
xhttp.send();
|
||||
hostname = xhttp.responseText;
|
||||
}
|
||||
catch(err) {
|
||||
setStatus("<span style=\"color: red\">Failed to fetch hostname: " + err.message + "!</span>");
|
||||
return;
|
||||
}
|
||||
|
||||
// get date/time
|
||||
var dateTime = new Date().toJSON().slice(0,10) + "_" + new Date().toJSON().slice(11,19).replaceAll(":", "-");
|
||||
|
||||
zipFilename = hostname + "_" + dateTime + ".zip";
|
||||
console.log(zipFilename);
|
||||
|
||||
// Get files list
|
||||
setStatus("Fetching File List...");
|
||||
try {
|
||||
var xhttp = new XMLHttpRequest();
|
||||
xhttp.open("GET", getDomainname() + "/fileserver/config/", false);
|
||||
xhttp.send();
|
||||
|
||||
var parser = new DOMParser();
|
||||
var content = parser.parseFromString(xhttp.responseText, 'text/html'); }
|
||||
catch(err) {
|
||||
setStatus("Failed to fetch files list: " + err.message);
|
||||
return;
|
||||
}
|
||||
|
||||
const list = content.querySelectorAll("a");
|
||||
|
||||
var urls = [];
|
||||
|
||||
for (a of list) {
|
||||
url = a.getAttribute("href");
|
||||
urls.push(getDomainname() + url);
|
||||
}
|
||||
|
||||
// Pack as zip and download
|
||||
try {
|
||||
backup(urls, zipFilename);
|
||||
}
|
||||
catch(err) {
|
||||
setStatus("<span style=\"color: red\">Failed to zip files: " + err.message + "!</span>");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function fetchFiles(urls, filesData, index, retry, zipFilename) {
|
||||
url = urls[index];
|
||||
|
||||
// console.log(url + " started (" + index + "/" + urls.length + ")");
|
||||
if (retry == 0) {
|
||||
setStatus(" - " + getFilenameFromUrl(urls[index]) + " (" + (index+1) + "/" + urls.length + ")...");
|
||||
}
|
||||
else {
|
||||
setStatus("<span style=\"color: gray\"> Retrying (" + retry + ")...</span>");
|
||||
}
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', url, true);
|
||||
xhr.responseType = "blob";
|
||||
|
||||
if (retry == 0) { // Short timeout on first retry
|
||||
xhr.timeout = 2000; // time in milliseconds
|
||||
}
|
||||
else if (retry == 1) { // longer timeout
|
||||
xhr.timeout = 5000; // time in milliseconds
|
||||
}
|
||||
else if (retry == 2) { // longer timeout
|
||||
xhr.timeout = 10000; // time in milliseconds
|
||||
}
|
||||
else if (retry == 3) { // longer timeout
|
||||
xhr.timeout = 20000; // time in milliseconds
|
||||
}
|
||||
else { // very long timeout
|
||||
xhr.timeout = 30000; // time in milliseconds
|
||||
}
|
||||
|
||||
xhr.onload = () => { // Request finished
|
||||
//console.log(url + " done");
|
||||
|
||||
filesData[index] = xhr.response;
|
||||
|
||||
if (index == urls.length - 1) {
|
||||
setStatus("Fetched all files");
|
||||
generateZipFile(urls, filesData, zipFilename);
|
||||
return;
|
||||
}
|
||||
else { // Next file
|
||||
fetchFiles(urls, filesData, index+1, 0, zipFilename);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onprogress = (e) => { // XMLHttpRequest progress ... extend timeout
|
||||
xhr.timeout = xhr.timeout + 500;
|
||||
};
|
||||
|
||||
xhr.onerror = (e) => { // XMLHttpRequest error loading
|
||||
console.log("Error on fetching " + url + "!");
|
||||
if (retry > 5) {
|
||||
setStatus("<span style=\"color: red\">Backup failed, please restart the device and try again!</span>");
|
||||
}
|
||||
else {
|
||||
fetchFiles(urls, filesData, index, retry+1, zipFilename);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.ontimeout = (e) => { // XMLHttpRequest timed out
|
||||
console.log("Timeout on fetching " + url + "!");
|
||||
if (retry > 5) {
|
||||
setStatus("<span style=\"color: red\">Backup failed, please restart the device and try again!</span>");
|
||||
}
|
||||
else {
|
||||
fetchFiles(urls, filesData, index, retry+1, zipFilename);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.send(null);
|
||||
}
|
||||
|
||||
|
||||
function generateZipFile(urls, filesData, zipFilename) {
|
||||
setStatus("Creating Zip File...");
|
||||
|
||||
var zip = new JSZip();
|
||||
|
||||
for (var i = 0; i < urls.length; i++) {
|
||||
zip.file(getFilenameFromUrl(urls[i]), filesData[i]);
|
||||
}
|
||||
|
||||
zip.generateAsync({type:"blob"})
|
||||
.then(function(content) {
|
||||
saveAs(content, zipFilename);
|
||||
});
|
||||
|
||||
setStatus("Backup completed");
|
||||
}
|
||||
|
||||
|
||||
const backup = (urls, zipFilename) => {
|
||||
if(!urls) return;
|
||||
|
||||
/* Testing */
|
||||
/*len = urls.length;
|
||||
for (i = 0; i < len - 3; i++) {
|
||||
urls.pop();
|
||||
}*/
|
||||
|
||||
console.log(urls);
|
||||
|
||||
urlIndex = 0;
|
||||
setStatus("Fetching files...");
|
||||
fetchFiles(urls, [], 0, 0, zipFilename);
|
||||
};
|
||||
|
||||
|
||||
function setStatus(status) {
|
||||
console.log(status);
|
||||
document.getElementById("progress").innerHTML += status + "<br>\n";
|
||||
}
|
||||
|
||||
function getFilenameFromUrl(url) {
|
||||
return filename = url.substring(url.lastIndexOf('/')+1);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
</html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" xml:lang="en">
|
||||
<head>
|
||||
<title>Backup/Restore Configuration</title>
|
||||
<meta charset="UTF-8" />
|
||||
|
||||
<style>
|
||||
h1 {font-size: 2em;}
|
||||
h2 {font-size: 1.5em; margin-block-start: 0.0em; margin-block-end: 0.2em;}
|
||||
h3 {font-size: 1.2em;}
|
||||
p {font-size: 1em;}
|
||||
|
||||
input[type=number] {
|
||||
width: 138px;
|
||||
padding: 10px 5px;
|
||||
display: inline-block;
|
||||
border: 1px solid #ccc;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.button {
|
||||
padding: 5px 10px;
|
||||
width: 205px;
|
||||
font-size: 16px;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
|
||||
<body style="font-family: arial; padding: 0px 10px;">
|
||||
<h2>Backup Configuration</h2>
|
||||
<p>With the following action the <a href="/fileserver/config/" target="_self">config</a> folder on the SD-card gets zipped and provided as a download.</p>
|
||||
|
||||
<button class="button" id="startBackup" type="button" onclick="startBackup()">Create Backup</button>
|
||||
<p id=progress></p>
|
||||
<hr>
|
||||
<h2>Restore Configuration</h2>
|
||||
<p>Use the <a href="/fileserver/config/" target="_self">File Server</a> to upload individual files.</p>
|
||||
</body>
|
||||
|
||||
|
||||
<script type="text/javascript" src="common.js?v=$COMMIT_HASH"></script>
|
||||
<script type="text/javascript" src="jszip.min.js?v=$COMMIT_HASH"></script>
|
||||
<script type="text/javascript" src="FileSaver.min.js?v=$COMMIT_HASH"></script>
|
||||
<script>
|
||||
|
||||
function startBackup() {
|
||||
document.getElementById("progress").innerHTML = "Creating backup...<br>\n";
|
||||
|
||||
// Get hostname
|
||||
try {
|
||||
var xhttp = new XMLHttpRequest();
|
||||
xhttp.open("GET", getDomainname() + "/info?type=Hostname", false);
|
||||
xhttp.send();
|
||||
hostname = xhttp.responseText;
|
||||
}
|
||||
catch(err) {
|
||||
setStatus("<span style=\"color: red\">Failed to fetch hostname: " + err.message + "!</span>");
|
||||
return;
|
||||
}
|
||||
|
||||
// get date/time
|
||||
var dateTime = new Date().toJSON().slice(0,10) + "_" + new Date().toJSON().slice(11,19).replaceAll(":", "-");
|
||||
|
||||
zipFilename = hostname + "_" + dateTime + ".zip";
|
||||
console.log(zipFilename);
|
||||
|
||||
// Get files list
|
||||
setStatus("Fetching File List...");
|
||||
try {
|
||||
var xhttp = new XMLHttpRequest();
|
||||
xhttp.open("GET", getDomainname() + "/fileserver/config/", false);
|
||||
xhttp.send();
|
||||
|
||||
var parser = new DOMParser();
|
||||
var content = parser.parseFromString(xhttp.responseText, 'text/html'); }
|
||||
catch(err) {
|
||||
setStatus("Failed to fetch files list: " + err.message);
|
||||
return;
|
||||
}
|
||||
|
||||
const list = content.querySelectorAll("a");
|
||||
|
||||
var urls = [];
|
||||
|
||||
for (a of list) {
|
||||
url = a.getAttribute("href");
|
||||
urls.push(getDomainname() + url);
|
||||
}
|
||||
|
||||
// Pack as zip and download
|
||||
try {
|
||||
backup(urls, zipFilename);
|
||||
}
|
||||
catch(err) {
|
||||
setStatus("<span style=\"color: red\">Failed to zip files: " + err.message + "!</span>");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function fetchFiles(urls, filesData, index, retry, zipFilename) {
|
||||
url = urls[index];
|
||||
|
||||
// console.log(url + " started (" + index + "/" + urls.length + ")");
|
||||
if (retry == 0) {
|
||||
setStatus(" - " + getFilenameFromUrl(urls[index]) + " (" + (index+1) + "/" + urls.length + ")...");
|
||||
}
|
||||
else {
|
||||
setStatus("<span style=\"color: gray\"> Retrying (" + retry + ")...</span>");
|
||||
}
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', url, true);
|
||||
xhr.responseType = "blob";
|
||||
|
||||
if (retry == 0) { // Short timeout on first retry
|
||||
xhr.timeout = 2000; // time in milliseconds
|
||||
}
|
||||
else if (retry == 1) { // longer timeout
|
||||
xhr.timeout = 5000; // time in milliseconds
|
||||
}
|
||||
else if (retry == 2) { // longer timeout
|
||||
xhr.timeout = 10000; // time in milliseconds
|
||||
}
|
||||
else if (retry == 3) { // longer timeout
|
||||
xhr.timeout = 20000; // time in milliseconds
|
||||
}
|
||||
else { // very long timeout
|
||||
xhr.timeout = 30000; // time in milliseconds
|
||||
}
|
||||
|
||||
xhr.onload = () => { // Request finished
|
||||
//console.log(url + " done");
|
||||
|
||||
filesData[index] = xhr.response;
|
||||
|
||||
if (index == urls.length - 1) {
|
||||
setStatus("Fetched all files");
|
||||
generateZipFile(urls, filesData, zipFilename);
|
||||
return;
|
||||
}
|
||||
else { // Next file
|
||||
fetchFiles(urls, filesData, index+1, 0, zipFilename);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onprogress = (e) => { // XMLHttpRequest progress ... extend timeout
|
||||
xhr.timeout = xhr.timeout + 500;
|
||||
};
|
||||
|
||||
xhr.onerror = (e) => { // XMLHttpRequest error loading
|
||||
console.log("Error on fetching " + url + "!");
|
||||
if (retry > 5) {
|
||||
setStatus("<span style=\"color: red\">Backup failed, please restart the device and try again!</span>");
|
||||
}
|
||||
else {
|
||||
fetchFiles(urls, filesData, index, retry+1, zipFilename);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.ontimeout = (e) => { // XMLHttpRequest timed out
|
||||
console.log("Timeout on fetching " + url + "!");
|
||||
if (retry > 5) {
|
||||
setStatus("<span style=\"color: red\">Backup failed, please restart the device and try again!</span>");
|
||||
}
|
||||
else {
|
||||
fetchFiles(urls, filesData, index, retry+1, zipFilename);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.send(null);
|
||||
}
|
||||
|
||||
|
||||
function generateZipFile(urls, filesData, zipFilename) {
|
||||
setStatus("Creating Zip File...");
|
||||
|
||||
var zip = new JSZip();
|
||||
|
||||
for (var i = 0; i < urls.length; i++) {
|
||||
zip.file(getFilenameFromUrl(urls[i]), filesData[i]);
|
||||
}
|
||||
|
||||
zip.generateAsync({type:"blob"})
|
||||
.then(function(content) {
|
||||
saveAs(content, zipFilename);
|
||||
});
|
||||
|
||||
setStatus("Backup completed");
|
||||
}
|
||||
|
||||
|
||||
const backup = (urls, zipFilename) => {
|
||||
if(!urls) return;
|
||||
|
||||
/* Testing */
|
||||
/*len = urls.length;
|
||||
for (i = 0; i < len - 3; i++) {
|
||||
urls.pop();
|
||||
}*/
|
||||
|
||||
console.log(urls);
|
||||
|
||||
urlIndex = 0;
|
||||
setStatus("Fetching files...");
|
||||
fetchFiles(urls, [], 0, 0, zipFilename);
|
||||
};
|
||||
|
||||
|
||||
function setStatus(status) {
|
||||
console.log(status);
|
||||
document.getElementById("progress").innerHTML += status + "<br>\n";
|
||||
}
|
||||
|
||||
function getFilenameFromUrl(url) {
|
||||
return filename = url.substring(url.lastIndexOf('/')+1);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
</html>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
|
||||
/* The UI can also be run locally, but you have to set the IP of your devide accordingly.
|
||||
/* The UI can also be run locally, but you have to set the IP of your device accordingly.
|
||||
* And you also might have to disable CORS in your webbrowser!
|
||||
* Eg using https://chromewebstore.google.com/detail/allow-cors-access-control/lhobafahddgcelffkeicbaginigeejlf?utm_source=ext_app_menu on chrome
|
||||
* Keep empty to disable using it. Enabling it will break access through a forwared port, see
|
||||
* https://github.com/jomjol/AI-on-the-edge-device/issues/2681 */
|
||||
var domainname_for_testing = "";
|
||||
|
||||
207
sd-card/html/data_export.html
Normal file
207
sd-card/html/data_export.html
Normal file
@@ -0,0 +1,207 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" xml:lang="en">
|
||||
<head>
|
||||
<title>Data Export (CSV)</title>
|
||||
<meta charset="UTF-8"/>
|
||||
|
||||
<style>
|
||||
h1 {font-size: 2em;}
|
||||
h2 {font-size: 1.5em; margin-block-start: 0.0em; margin-block-end: 0.2em;}
|
||||
h3 {font-size: 1.2em;}
|
||||
p {font-size: 1em;}
|
||||
|
||||
input[type=number] {
|
||||
width: 138px;
|
||||
padding: 10px 5px;
|
||||
display: inline-block;
|
||||
border: 1px solid #ccc;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.button {
|
||||
padding: 5px 10px;
|
||||
width: 300px;
|
||||
font-size: 16px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="font-family: arial; padding: 0px 10px;">
|
||||
<h2>Data Export(CSV)</h2>
|
||||
<p>With the following action the <a href="/fileserver/log/data/" target="_self">data</a> folder on the SD-card gets zipped and provided as a download.</p>
|
||||
<button class="button" id="startExportData" type="button" onclick="startExportData()">Export Data</button>
|
||||
<p></p>
|
||||
<hr>
|
||||
<p id=progress></p>
|
||||
|
||||
<script type="text/javascript" src="common.js?v=$COMMIT_HASH"></script>
|
||||
<script type="text/javascript" src="jszip.min.js?v=$COMMIT_HASH"></script>
|
||||
<script type="text/javascript" src="FileSaver.min.js?v=$COMMIT_HASH"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
function startExportData() {
|
||||
document.getElementById("progress").innerHTML = "Creating Export Data...<br>\n";
|
||||
|
||||
// Get hostname
|
||||
try {
|
||||
var xhttp = new XMLHttpRequest();
|
||||
xhttp.open("GET", getDomainname() + "/info?type=Hostname", false);
|
||||
xhttp.send();
|
||||
hostname = xhttp.responseText;
|
||||
}
|
||||
catch(err) {
|
||||
setStatus("<span style=\"color: red\">Failed to fetch hostname: " + err.message + "!</span>");
|
||||
return;
|
||||
}
|
||||
|
||||
// get date/time
|
||||
var dateTime = new Date().toJSON().slice(0,10) + "_" + new Date().toJSON().slice(11,19).replaceAll(":", "-");
|
||||
|
||||
zipFilename = hostname + "_" + dateTime + "_csv" + ".zip";
|
||||
console.log(zipFilename);
|
||||
|
||||
// Get files list
|
||||
setStatus("Fetching File List...");
|
||||
try {
|
||||
var xhttp = new XMLHttpRequest();
|
||||
xhttp.open("GET", getDomainname() + "/fileserver/log/data/", false);
|
||||
xhttp.send();
|
||||
|
||||
var parser = new DOMParser();
|
||||
var content = parser.parseFromString(xhttp.responseText, 'text/html');
|
||||
}
|
||||
catch(err) {
|
||||
setStatus("Failed to fetch files list: " + err.message);
|
||||
return;
|
||||
}
|
||||
|
||||
const list = content.querySelectorAll("a");
|
||||
|
||||
var urls = [];
|
||||
|
||||
for (a of list) {
|
||||
url = a.getAttribute("href");
|
||||
urls.push(getDomainname() + url);
|
||||
}
|
||||
|
||||
// Pack as zip and download
|
||||
try {
|
||||
startExportZip(urls, zipFilename);
|
||||
}
|
||||
catch(err) {
|
||||
setStatus("<span style=\"color: red\">Failed to zip files: " + err.message + "!</span>");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function fetchFiles(urls, filesData, index, retry, zipFilename) {
|
||||
url = urls[index];
|
||||
|
||||
if (retry == 0) {
|
||||
setStatus(" - " + getFilenameFromUrl(urls[index]) + " (" + (index+1) + "/" + urls.length + ")...");
|
||||
}
|
||||
else {
|
||||
setStatus("<span style=\"color: gray\"> Retrying (" + retry + ")...</span>");
|
||||
}
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', url, true);
|
||||
xhr.responseType = "blob";
|
||||
|
||||
if (retry == 0) { // Short timeout on first retry
|
||||
xhr.timeout = 2000; // time in milliseconds
|
||||
}
|
||||
else if (retry == 1) { // longer timeout
|
||||
xhr.timeout = 5000;
|
||||
}
|
||||
else if (retry == 2) { // longer timeout
|
||||
xhr.timeout = 10000;
|
||||
}
|
||||
else if (retry == 3) { // longer timeout
|
||||
xhr.timeout = 20000;
|
||||
}
|
||||
else { // very long timeout
|
||||
xhr.timeout = 30000;
|
||||
}
|
||||
|
||||
xhr.onload = () => { // Request finished
|
||||
//console.log(url + " done");
|
||||
|
||||
filesData[index] = xhr.response;
|
||||
|
||||
if (index == urls.length - 1) {
|
||||
setStatus("Fetched all files");
|
||||
generateZipFile(urls, filesData, zipFilename);
|
||||
return;
|
||||
}
|
||||
else { // Next file
|
||||
fetchFiles(urls, filesData, index+1, 0, zipFilename);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onprogress = (e) => { // XMLHttpRequest progress ... extend timeout
|
||||
xhr.timeout = xhr.timeout + 500;
|
||||
};
|
||||
|
||||
xhr.onerror = (e) => { // XMLHttpRequest error loading
|
||||
console.log("Error on fetching " + url + "!");
|
||||
if (retry > 5) {
|
||||
setStatus("<span style=\"color: red\">Data Export failed, please restart the device and try again!</span>");
|
||||
}
|
||||
else {
|
||||
fetchFiles(urls, filesData, index, retry+1, zipFilename);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.ontimeout = (e) => { // XMLHttpRequest timed out
|
||||
console.log("Timeout on fetching " + url + "!");
|
||||
if (retry > 5) {
|
||||
setStatus("<span style=\"color: red\">Data Export failed, please restart the device and try again!</span>");
|
||||
}
|
||||
else {
|
||||
fetchFiles(urls, filesData, index, retry+1, zipFilename);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.send(null);
|
||||
}
|
||||
|
||||
function generateZipFile(urls, filesData, zipFilename) {
|
||||
setStatus("Creating Zip File...");
|
||||
|
||||
var zip = new JSZip();
|
||||
|
||||
for (var i = 0; i < urls.length; i++) {
|
||||
zip.file(getFilenameFromUrl(urls[i]), filesData[i]);
|
||||
}
|
||||
|
||||
zip.generateAsync({type:"blob"})
|
||||
.then(function(content) {
|
||||
saveAs(content, zipFilename);
|
||||
});
|
||||
|
||||
setStatus("Data Export completed");
|
||||
}
|
||||
|
||||
const startExportZip = (urls, zipFilename) => {
|
||||
if(!urls) { return; }
|
||||
|
||||
console.log(urls);
|
||||
|
||||
urlIndex = 0;
|
||||
setStatus("Fetching files...");
|
||||
fetchFiles(urls, [], 0, 0, zipFilename);
|
||||
};
|
||||
|
||||
function setStatus(status) {
|
||||
console.log(status);
|
||||
document.getElementById("progress").innerHTML += status + "<br>\n";
|
||||
}
|
||||
|
||||
function getFilenameFromUrl(url) {
|
||||
return filename = url.substring(url.lastIndexOf('/')+1);
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -79,6 +79,18 @@
|
||||
transform: translate(-50%,-50%);
|
||||
-ms-transform: translate(-50%,-50%);
|
||||
}
|
||||
|
||||
#reboot_button {
|
||||
float: none;
|
||||
background-color: #f44336;
|
||||
color: white;
|
||||
padding: 5px;
|
||||
border-radius:
|
||||
5px; font-weight: bold;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
|
||||
<link href="firework.css?v=$COMMIT_HASH" rel="stylesheet">
|
||||
@@ -188,13 +200,11 @@
|
||||
param;
|
||||
|
||||
function doReboot() {
|
||||
if (confirm("Are you sure you want to reboot? Did you save your changes?")) {
|
||||
var stringota = domainname + "/reboot";
|
||||
window.location = stringota;
|
||||
window.location.href = stringota;
|
||||
window.location.assign(stringota);
|
||||
window.location.replace(stringota);
|
||||
}
|
||||
var stringota = domainname + "/reboot";
|
||||
window.location = stringota;
|
||||
window.location.href = stringota;
|
||||
window.location.assign(stringota);
|
||||
window.location.replace(stringota);
|
||||
}
|
||||
|
||||
function ChangeSelection(){
|
||||
@@ -215,50 +225,45 @@
|
||||
document.getElementById("overlay").style.display = "block";
|
||||
document.getElementById("overlaytext").innerHTML = "Save Alignment Marker...";
|
||||
|
||||
if (confirm("Are you sure you want to save the new alignment marker configuration?")) {
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
async function task() {
|
||||
while (true) {
|
||||
WriteConfigININew();
|
||||
|
||||
if (neueref1 == 1 && neueref2 == 1) {
|
||||
UpdateConfigReferences(domainname);
|
||||
}
|
||||
else if (neueref1 == 1) {
|
||||
var anzneueref = 1;
|
||||
UpdateConfigReference(anzneueref, domainname);
|
||||
}
|
||||
else if (neueref2 == 1) {
|
||||
var anzneueref = 2;
|
||||
UpdateConfigReference(anzneueref, domainname);
|
||||
}
|
||||
|
||||
SaveConfigToServer(domainname);
|
||||
|
||||
document.getElementById("updatemarker").disabled = false;
|
||||
// document.getElementById("savemarker").disabled = true;
|
||||
// document.getElementById("enhancecontrast").disabled = true;
|
||||
|
||||
EnDisableItem(false, "savemarker", true);
|
||||
EnDisableItem(false, "enhancecontrast", true);
|
||||
|
||||
document.getElementById("overlay").style.display = "none";
|
||||
firework.launch('Alignment marker saved. They will get applied after next reboot', 'success', 5000);
|
||||
return;
|
||||
async function task() {
|
||||
while (true) {
|
||||
WriteConfigININew();
|
||||
|
||||
if (neueref1 == 1 && neueref2 == 1) {
|
||||
UpdateConfigReferences(domainname);
|
||||
}
|
||||
else if (neueref1 == 1) {
|
||||
var anzneueref = 1;
|
||||
UpdateConfigReference(anzneueref, domainname);
|
||||
}
|
||||
else if (neueref2 == 1) {
|
||||
var anzneueref = 2;
|
||||
UpdateConfigReference(anzneueref, domainname);
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(function () {
|
||||
// Delay so the overlay gets shown
|
||||
task();
|
||||
}, 1);
|
||||
}
|
||||
else {
|
||||
document.getElementById("overlay").style.display = "none";
|
||||
SaveConfigToServer(domainname);
|
||||
|
||||
document.getElementById("updatemarker").disabled = false;
|
||||
// document.getElementById("savemarker").disabled = true;
|
||||
// document.getElementById("enhancecontrast").disabled = true;
|
||||
|
||||
EnDisableItem(false, "savemarker", true);
|
||||
EnDisableItem(false, "enhancecontrast", true);
|
||||
|
||||
document.getElementById("overlay").style.display = "none";
|
||||
firework.launch('Alignment marker saved. They will get applied after the next reboot!<br><br>\n<a id="reboot_button" onclick="doReboot()">reboot now</a>', 'success', 5000);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(function () {
|
||||
// Delay so the overlay gets shown
|
||||
task();
|
||||
}, 1);
|
||||
}
|
||||
|
||||
function EnhanceContrast() {
|
||||
|
||||
@@ -5,6 +5,20 @@
|
||||
<meta charset="UTF-8" />
|
||||
<title>Analog ROI</title>
|
||||
|
||||
<style>
|
||||
#reboot_button {
|
||||
float: none;
|
||||
background-color: #f44336;
|
||||
color: white;
|
||||
padding: 5px;
|
||||
border-radius:
|
||||
5px; font-weight: bold;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
|
||||
<link href="edit_style.css" rel="stylesheet">
|
||||
<link href="firework.css?v=$COMMIT_HASH" rel="stylesheet">
|
||||
|
||||
@@ -185,13 +199,11 @@ The following settings are only used for easier setup, they are <b>not</b> persi
|
||||
domainname = getDomainname();
|
||||
|
||||
function doReboot() {
|
||||
if (confirm("Are you sure you want to reboot? Did you save your changes?")) {
|
||||
var stringota = getDomainname() + "/reboot";
|
||||
window.location = stringota;
|
||||
window.location.href = stringota;
|
||||
window.location.assign(stringota);
|
||||
window.location.replace(stringota);
|
||||
}
|
||||
var stringota = getDomainname() + "/reboot";
|
||||
window.location = stringota;
|
||||
window.location.href = stringota;
|
||||
window.location.assign(stringota);
|
||||
window.location.replace(stringota);
|
||||
}
|
||||
|
||||
function EnDisableAnalog() {
|
||||
@@ -331,16 +343,14 @@ The following settings are only used for easier setup, they are <b>not</b> persi
|
||||
}
|
||||
|
||||
function SaveToConfig() {
|
||||
if (confirm("Are you sure you want to save the new analog ROI configuration?")) {
|
||||
//_zwcat = getConfigCategory();
|
||||
cofcat["Analog"]["enabled"] = document.getElementById("Category_Analog_enabled").checked;
|
||||
WriteConfigININew();
|
||||
SaveConfigToServer(domainname);
|
||||
UpdateROIs();
|
||||
document.getElementById("saveroi").disabled = true;
|
||||
//_zwcat = getConfigCategory();
|
||||
cofcat["Analog"]["enabled"] = document.getElementById("Category_Analog_enabled").checked;
|
||||
WriteConfigININew();
|
||||
SaveConfigToServer(domainname);
|
||||
UpdateROIs();
|
||||
document.getElementById("saveroi").disabled = true;
|
||||
|
||||
firework.launch('Configuration saved. It will get applied after next reboot', 'success', 5000);
|
||||
}
|
||||
firework.launch('Configuration saved. It will get applied after the next reboot!<br><br>\n<a id="reboot_button" onclick="doReboot()">reboot now</a>', 'success', 5000);
|
||||
}
|
||||
|
||||
function ShowMultiplier() {
|
||||
|
||||
@@ -20,6 +20,18 @@
|
||||
textarea {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
#reboot_button {
|
||||
float: none;
|
||||
background-color: #f44336;
|
||||
color: white;
|
||||
padding: 5px;
|
||||
border-radius:
|
||||
5px; font-weight: bold;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
|
||||
<link href="firework.css?v=$COMMIT_HASH" rel="stylesheet">
|
||||
@@ -38,15 +50,8 @@
|
||||
</td>
|
||||
</table>
|
||||
|
||||
<table>
|
||||
<td>
|
||||
<button class="button" onclick="saveTextAsFile()">Save Config</button>
|
||||
</td>
|
||||
<td>
|
||||
<button class="button" id="reboot" type="button" onclick="doReboot()">Reboot to activate changes</button>
|
||||
</td>
|
||||
</table>
|
||||
|
||||
<hr>
|
||||
<button class="button" onclick="saveTextAsFile()">Save Config</button>
|
||||
|
||||
<script type="text/javascript" src="readconfigparam.js?v=$COMMIT_HASH"></script>
|
||||
<script type="text/javascript" src="readconfigcommon.js?v=$COMMIT_HASH"></script>
|
||||
@@ -64,23 +69,19 @@
|
||||
|
||||
function saveTextAsFile()
|
||||
{
|
||||
if (confirm("Are you sure you want to save the configuration?")) {
|
||||
FileDeleteOnServer("/config/config.ini", domainname);
|
||||
var textToSave = document.getElementById("inputTextToSave").value;
|
||||
FileSendContent(textToSave, "/config/config.ini", domainname);
|
||||
FileDeleteOnServer("/config/config.ini", domainname);
|
||||
var textToSave = document.getElementById("inputTextToSave").value;
|
||||
FileSendContent(textToSave, "/config/config.ini", domainname);
|
||||
|
||||
firework.launch('Configuration saved. It will get applied after next reboot', 'success', 5000);
|
||||
}
|
||||
firework.launch('Configuration saved. It will get applied after the next reboot!<br><br>\n<a id="reboot_button" onclick="doReboot()">reboot now</a>', 'success', 5000);
|
||||
}
|
||||
|
||||
function doReboot() {
|
||||
if (confirm("Are you sure you want to reboot?")) {
|
||||
var stringota = "/reboot";
|
||||
window.location = stringota;
|
||||
window.location.href = stringota;
|
||||
window.location.assign(stringota);
|
||||
window.location.replace(stringota);
|
||||
}
|
||||
var stringota = "/reboot";
|
||||
window.location = stringota;
|
||||
window.location.href = stringota;
|
||||
window.location.assign(stringota);
|
||||
window.location.replace(stringota);
|
||||
}
|
||||
|
||||
LoadConfigNeu();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,19 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Digit ROI</title>
|
||||
<style>
|
||||
#reboot_button {
|
||||
float: none;
|
||||
background-color: #f44336;
|
||||
color: white;
|
||||
padding: 5px;
|
||||
border-radius:
|
||||
5px; font-weight: bold;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
|
||||
<link href="edit_style.css" rel="stylesheet">
|
||||
<link href="firework.css?v=$COMMIT_HASH" rel="stylesheet">
|
||||
@@ -204,13 +217,11 @@
|
||||
domainname = getDomainname();
|
||||
|
||||
function doReboot() {
|
||||
if (confirm("Are you sure you want to reboot? Did you save your changes?")) {
|
||||
var stringota = getDomainname() + "/reboot";
|
||||
window.location = stringota;
|
||||
window.location.href = stringota;
|
||||
window.location.assign(stringota);
|
||||
window.location.replace(stringota);
|
||||
}
|
||||
var stringota = getDomainname() + "/reboot";
|
||||
window.location = stringota;
|
||||
window.location.href = stringota;
|
||||
window.location.assign(stringota);
|
||||
window.location.replace(stringota);
|
||||
}
|
||||
|
||||
function EnDisableDigits() {
|
||||
@@ -359,16 +370,14 @@
|
||||
}
|
||||
|
||||
function SaveToConfig() {
|
||||
if (confirm("Are you sure you want to save the new digit ROI configuration?")) {
|
||||
// _zwcat = getConfigCategory();
|
||||
cofcat["Digits"]["enabled"] = document.getElementById("Category_Digits_enabled").checked;
|
||||
WriteConfigININew();
|
||||
SaveConfigToServer(domainname);
|
||||
UpdateROIs();
|
||||
document.getElementById("saveroi").disabled = true;
|
||||
// _zwcat = getConfigCategory();
|
||||
cofcat["Digits"]["enabled"] = document.getElementById("Category_Digits_enabled").checked;
|
||||
WriteConfigININew();
|
||||
SaveConfigToServer(domainname);
|
||||
UpdateROIs();
|
||||
document.getElementById("saveroi").disabled = true;
|
||||
|
||||
firework.launch('Configuration saved. It will get applied after next reboot', 'success', 5000);
|
||||
}
|
||||
firework.launch('Configuration saved. It will get applied after the next reboot!<br><br>\n<a id="reboot_button" onclick="doReboot()">reboot now</a>', 'success', 5000);
|
||||
}
|
||||
|
||||
function ShowMultiplier() {
|
||||
|
||||
@@ -174,8 +174,8 @@
|
||||
</td>
|
||||
<td>
|
||||
<select id="TakeImage_CamZoom_value1" onchange="cameraParameterChanged()">
|
||||
<option value="true">enable (true)</option>
|
||||
<option value="false" selected>disable (false)</option>
|
||||
<option value="true">enabled (true)</option>
|
||||
<option value="false" selected>disabled (false)</option>
|
||||
</select>
|
||||
</td>
|
||||
<td>$TOOLTIP_TakeImage_CamZoom</td>
|
||||
@@ -232,8 +232,8 @@
|
||||
</td>
|
||||
<td>
|
||||
<select id="TakeImage_CamAec2_value1" onchange="cameraParameterChanged()">
|
||||
<option value="true">enable (true)</option>
|
||||
<option value="false" selected>disable (false)</option>
|
||||
<option value="true">enabled (true)</option>
|
||||
<option value="false" selected>disabled (false)</option>
|
||||
</select>
|
||||
</td>
|
||||
<td>$TOOLTIP_TakeImage_CamAec2</td>
|
||||
@@ -245,8 +245,8 @@
|
||||
</td>
|
||||
<td>
|
||||
<select id="TakeImage_CamHmirror_value1" onchange="cameraParameterChanged()">
|
||||
<option value="true">enable (true)</option>
|
||||
<option value="false" selected>disable (false)</option>
|
||||
<option value="true">enabled (true)</option>
|
||||
<option value="false" selected>disabled (false)</option>
|
||||
</select>
|
||||
</td>
|
||||
<td>$TOOLTIP_TakeImage_CamHmirror</td>
|
||||
@@ -267,8 +267,8 @@
|
||||
</td>
|
||||
<td>
|
||||
<select id="TakeImage_CamVflip_value1" onchange="cameraParameterChanged()">
|
||||
<option value="true">enable (true)</option>
|
||||
<option value="false" selected>disable (false)</option>
|
||||
<option value="true">enabled (true)</option>
|
||||
<option value="false" selected>disabled (false)</option>
|
||||
</select>
|
||||
</td>
|
||||
<td>$TOOLTIP_TakeImage_CamVflip</td>
|
||||
@@ -332,8 +332,8 @@
|
||||
</td>
|
||||
<td>
|
||||
<select id="TakeImage_CamAec_value1" onchange="cameraParameterChanged()">
|
||||
<option value="true">enable (true)</option>
|
||||
<option value="false" selected>disable (false)</option>
|
||||
<option value="true">enabled (true)</option>
|
||||
<option value="false" selected>disabled (false)</option>
|
||||
</select>
|
||||
</td>
|
||||
<td>$TOOLTIP_TakeImage_CamAec</td>
|
||||
@@ -355,8 +355,8 @@
|
||||
</td>
|
||||
<td>
|
||||
<select id="TakeImage_CamAutoSharpness_value1" onchange="cameraParameterChanged()">
|
||||
<option value="true">enable (true)</option>
|
||||
<option value="false" selected>disable (false)</option>
|
||||
<option value="true">enabled (true)</option>
|
||||
<option value="false" selected>disabled (false)</option>
|
||||
</select>
|
||||
</td>
|
||||
<td>$TOOLTIP_TakeImage_CamAutoSharpness</td>
|
||||
@@ -395,22 +395,12 @@
|
||||
|
||||
<script type="text/javascript">
|
||||
var canvas = document.getElementById('canvas'),
|
||||
domainname = getDomainname(),
|
||||
context = canvas.getContext('2d'),
|
||||
imageObj = new Image(),
|
||||
isActReference = false,
|
||||
param,
|
||||
category;
|
||||
|
||||
function doReboot() {
|
||||
if (confirm("Are you sure you want to reboot? Did you save the config?")) {
|
||||
var stringota = domainname + "/reboot";
|
||||
window.location = stringota;
|
||||
window.location.href = stringota;
|
||||
window.location.assign(stringota);
|
||||
window.location.replace(stringota);
|
||||
}
|
||||
}
|
||||
domainname = getDomainname(),
|
||||
context = canvas.getContext('2d'),
|
||||
imageObj = new Image(),
|
||||
isActReference = false,
|
||||
param,
|
||||
category;
|
||||
|
||||
function cameraParameterChanged() {
|
||||
document.getElementById("savereferenceimage").disabled = true;
|
||||
@@ -738,7 +728,7 @@
|
||||
|
||||
if (xhttp.responseText == "CamSettingsSet") {
|
||||
document.getElementById("overlay").style.display = "none";
|
||||
firework.launch('Cam Settings saved', 'success', 2000);
|
||||
firework.launch('Cam Settings saved', 'success', 5000);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
|
||||
50
sd-card/html/file_server.css
Normal file
50
sd-card/html/file_server.css
Normal file
@@ -0,0 +1,50 @@
|
||||
h1 {font-size: 2em;}
|
||||
h2 {font-size: 1.5em; margin-block-start: 0.0em; margin-block-end: 0.2em;}
|
||||
h3 {font-size: 1.2em;}
|
||||
p {font-size: 1em;}
|
||||
|
||||
#files_table {
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#files_table td, #files_table th {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
#files_table tr:nth-child(even){
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
||||
#files_table tr:hover {
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
#files_table th {
|
||||
padding-top: 12px;
|
||||
padding-bottom: 12px;
|
||||
text-align: left;
|
||||
background-color:lightgrey;
|
||||
color: black;
|
||||
}
|
||||
|
||||
input[type=file] {
|
||||
padding: 5px 0px;
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
input[type=text] {
|
||||
padding: 5px 10px;
|
||||
display: inline-block;
|
||||
border: 1px solid #ccc;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.button {
|
||||
padding: 4px 10px;
|
||||
width: 100px;
|
||||
font-size: 16px;
|
||||
}
|
||||
@@ -1,206 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" xml:lang="en">
|
||||
|
||||
<head>
|
||||
<link href="/firework.css?v=$COMMIT_HASH" rel="stylesheet">
|
||||
<script type="text/javascript" src="/jquery-3.6.0.min.js?v=$COMMIT_HASH"></script>
|
||||
<script type="text/javascript" src="/firework.js?v=$COMMIT_HASH"></script>
|
||||
|
||||
<style>
|
||||
h1 {font-size: 2em;}
|
||||
h2 {font-size: 1.5em; margin-block-start: 0.0em; margin-block-end: 0.2em;}
|
||||
h3 {font-size: 1.2em;}
|
||||
p {font-size: 1em;}
|
||||
|
||||
#files_table {
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#files_table td, #files_table th {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
#files_table tr:nth-child(even){
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
||||
#files_table tr:hover {
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
#files_table th {
|
||||
padding-top: 12px;
|
||||
padding-bottom: 12px;
|
||||
text-align: left;
|
||||
background-color:lightgrey;
|
||||
color: black;
|
||||
}
|
||||
|
||||
input[type=file] {
|
||||
padding: 5px 0px;
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
input[type=text] {
|
||||
padding: 5px 10px;
|
||||
display: inline-block;
|
||||
border: 1px solid #ccc;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.button {
|
||||
padding: 4px 10px;
|
||||
width: 100px;
|
||||
font-size: 16px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
</body>
|
||||
<table class="fixed" border="0" width=100% style="font-family: arial">
|
||||
<tr>
|
||||
<td style="vertical-align: top;width: 300px;">
|
||||
<h2>Fileserver</h2>
|
||||
</td>
|
||||
<td rowspan="2">
|
||||
<table border="0" style="width:100%">
|
||||
<tr>
|
||||
<td style="width:80px">
|
||||
<label for="newfile">Source</label>
|
||||
</td>
|
||||
<td colspan="2">
|
||||
<input id="newfile" type="file" onchange="setpath()" style="width:100%;">
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<label for="filepath">Destination</label>
|
||||
</td>
|
||||
<td>
|
||||
<input id="filepath" type="text" style="width:94%;">
|
||||
</td>
|
||||
<td>
|
||||
<button id="upload" type="button" class="button" onclick="upload()">Upload</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr></tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<button style="font-size:16px; padding: 5px 10px" id="dirup" type="button" onclick="dirup()" disabled>🡹 Directory up</button>
|
||||
<span style="padding-left:15px" id="currentpath"></span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<script type="text/javascript" src="/common.js?v=$COMMIT_HASH"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
function setpath() {
|
||||
var fileserverpraefix = "/fileserver";
|
||||
var anz_zeichen_fileserver = fileserverpraefix.length;
|
||||
var default_path = window.location.pathname.substring(anz_zeichen_fileserver) + document.getElementById("newfile").files[0].name;
|
||||
document.getElementById("filepath").value = default_path;
|
||||
}
|
||||
|
||||
function dirup() {
|
||||
var str = window.location.href;
|
||||
str = str.substring(0, str.length-1);
|
||||
var zw = str.indexOf("/");
|
||||
var found = zw;
|
||||
while (zw >= 0)
|
||||
{
|
||||
zw = str.indexOf("/", found+1);
|
||||
if (zw >= 0)
|
||||
found = zw;
|
||||
}
|
||||
var res = str.substring(0, found+1);
|
||||
|
||||
window.location.href = res;
|
||||
}
|
||||
|
||||
|
||||
function upload() {
|
||||
var filePath = document.getElementById("filepath").value;
|
||||
var upload_path = "/upload/" + filePath;
|
||||
var fileInput = document.getElementById("newfile").files;
|
||||
|
||||
/* Max size of an individual file. Make sure this
|
||||
* value is same as that set in file_server.c */
|
||||
var MAX_FILE_SIZE = 8000*1024;
|
||||
var MAX_FILE_SIZE_STR = "8000KB";
|
||||
|
||||
if (fileInput.length == 0) {
|
||||
firework.launch('No file selected!', 'danger', 30000);
|
||||
} else if (filePath.length == 0) {
|
||||
firework.launch('File path on server is not set!', 'danger', 30000);
|
||||
} else if (filePath.length > 100) {
|
||||
firework.launch('Filename is to long! Max 100 characters.', 'danger', 30000);
|
||||
} else if (filePath.indexOf(' ') >= 0) {
|
||||
firework.launch('File path on server cannot have spaces!', 'danger', 30000);
|
||||
} else if (filePath[filePath.length-1] == '/') {
|
||||
firework.launch('File name not specified after path!', 'danger', 30000);
|
||||
} else if (fileInput[0].size > MAX_FILE_SIZE) {
|
||||
firework.launch("File size must be less than " + MAX_FILE_SIZE_STR + "!", 'danger', 30000);
|
||||
} else {
|
||||
document.getElementById("newfile").disabled = true;
|
||||
document.getElementById("filepath").disabled = true;
|
||||
document.getElementById("upload").disabled = true;
|
||||
|
||||
var file = fileInput[0];
|
||||
var xhttp = new XMLHttpRequest();
|
||||
xhttp.onreadystatechange = function() {
|
||||
if (xhttp.readyState == 4) {
|
||||
if (xhttp.status == 200) {
|
||||
document.open();
|
||||
document.write(xhttp.responseText);
|
||||
document.close();
|
||||
firework.launch('File upload completed', 'success', 5000);
|
||||
} else if (xhttp.status == 0) {
|
||||
firework.launch('Server closed the connection abruptly!', 'danger', 30000);
|
||||
UpdatePage(false);
|
||||
} else {
|
||||
firework.launch('An error occured: ' + xhttp.responseText, 'danger', 30000);
|
||||
UpdatePage(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
xhttp.open("POST", upload_path, true);
|
||||
xhttp.send(file);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function checkAtRootLevel(res) {
|
||||
if (getPath() == "/fileserver/") { // Already at root level
|
||||
document.getElementById("dirup").disabled = true;
|
||||
console.log("Already on sd-card root level!");
|
||||
return true;
|
||||
}
|
||||
|
||||
document.getElementById("dirup").disabled = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
function getPath() {
|
||||
return window.location.pathname.replace(/\/+$/, '') + "/"
|
||||
}
|
||||
|
||||
checkAtRootLevel();
|
||||
|
||||
console.log("Current path: " + getPath().replace("/fileserver", ""));
|
||||
document.getElementById("currentpath").innerHTML = "Current path: <b>" + getPath().replace("/fileserver", "") + "</b>";
|
||||
|
||||
document.cookie = "page=" + getPath() + "; path=/";
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
93
sd-card/html/file_server.js
Normal file
93
sd-card/html/file_server.js
Normal file
@@ -0,0 +1,93 @@
|
||||
function setpath() {
|
||||
var fileserverpraefix = "/fileserver";
|
||||
var anz_zeichen_fileserver = fileserverpraefix.length;
|
||||
var default_path = window.location.pathname.substring(anz_zeichen_fileserver) + document.getElementById("newfile").files[0].name;
|
||||
document.getElementById("filepath").value = default_path;
|
||||
}
|
||||
|
||||
function dirup() {
|
||||
var str = window.location.href;
|
||||
str = str.substring(0, str.length-1);
|
||||
var zw = str.indexOf("/");
|
||||
var found = zw;
|
||||
while (zw >= 0) {
|
||||
zw = str.indexOf("/", found+1);
|
||||
if (zw >= 0) {
|
||||
found = zw;
|
||||
}
|
||||
}
|
||||
var res = str.substring(0, found+1);
|
||||
window.location.href = res;
|
||||
}
|
||||
|
||||
function upload() {
|
||||
var filePath = document.getElementById("filepath").value;
|
||||
var upload_path = "/upload/" + filePath;
|
||||
var fileInput = document.getElementById("newfile").files;
|
||||
|
||||
// Max size of an individual file. Make sure this value is same as that set in file_server.c
|
||||
var MAX_FILE_SIZE = 8000*1024;
|
||||
var MAX_FILE_SIZE_STR = "8000KB";
|
||||
|
||||
if (fileInput.length == 0) {
|
||||
firework.launch('No file selected!', 'danger', 30000);
|
||||
} else if (filePath.length == 0) {
|
||||
firework.launch('File path on server is not set!', 'danger', 30000);
|
||||
} else if (filePath.length > 100) {
|
||||
firework.launch('Filename is to long! Max 100 characters.', 'danger', 30000);
|
||||
} else if (filePath.indexOf(' ') >= 0) {
|
||||
firework.launch('File path on server cannot have spaces!', 'danger', 30000);
|
||||
} else if (filePath[filePath.length-1] == '/') {
|
||||
firework.launch('File name not specified after path!', 'danger', 30000);
|
||||
} else if (fileInput[0].size > MAX_FILE_SIZE) {
|
||||
firework.launch("File size must be less than " + MAX_FILE_SIZE_STR + "!", 'danger', 30000);
|
||||
} else {
|
||||
document.getElementById("newfile").disabled = true;
|
||||
document.getElementById("filepath").disabled = true;
|
||||
document.getElementById("upload").disabled = true;
|
||||
|
||||
var file = fileInput[0];
|
||||
var xhttp = new XMLHttpRequest();
|
||||
xhttp.onreadystatechange = function() {
|
||||
if (xhttp.readyState == 4) {
|
||||
if (xhttp.status == 200) {
|
||||
document.open();
|
||||
document.write(xhttp.responseText);
|
||||
document.close();
|
||||
firework.launch('File upload completed', 'success', 5000);
|
||||
} else if (xhttp.status == 0) {
|
||||
firework.launch('Server closed the connection abruptly!', 'danger', 30000);
|
||||
UpdatePage(false);
|
||||
} else {
|
||||
firework.launch('An error occured: ' + xhttp.responseText, 'danger', 30000);
|
||||
UpdatePage(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
xhttp.open("POST", upload_path, true);
|
||||
xhttp.send(file);
|
||||
}
|
||||
}
|
||||
|
||||
function checkAtRootLevel(res) {
|
||||
if (getPath() == "/fileserver/") {
|
||||
// Already at root level
|
||||
document.getElementById("dirup").disabled = true;
|
||||
console.log("Already on sd-card root level!");
|
||||
return true;
|
||||
}
|
||||
|
||||
document.getElementById("dirup").disabled = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
function getPath() {
|
||||
return window.location.pathname.replace(/\/+$/, '') + "/"
|
||||
}
|
||||
|
||||
function initFileServer() {
|
||||
checkAtRootLevel();
|
||||
console.log("Current path: " + getPath().replace("/fileserver", ""));
|
||||
document.getElementById("currentpath").innerHTML = "Current path: <b>" + getPath().replace("/fileserver", "") + "</b>";
|
||||
document.cookie = "page=" + getPath() + "; path=/";
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user