Compare commits

...

68 Commits

Author SHA1 Message Date
CaCO3
1ceb47fbc7 . 2025-02-25 00:22:47 +01:00
CaCO3
a413d0228e . 2025-02-25 00:10:29 +01:00
CaCO3
e22eaa2736 . 2025-02-25 00:01:17 +01:00
CaCO3
154d159595 . 2025-02-24 23:58:27 +01:00
CaCO3
23a5458a2b . 2025-02-24 23:53:57 +01:00
CaCO3
89097fa5a2 . 2025-02-24 23:46:38 +01:00
CaCO3
6952db0298 swap HTML folders after extracting 2025-02-24 23:26:55 +01:00
CaCO3
550889c373 rename html folder 2025-02-24 23:10:03 +01:00
CaCO3
1200ee1289 Merge branch 'fix-gz' of https://github.com/jomjol/AI-on-the-edge-device into fix-gz 2025-02-24 21:57:09 +01:00
CaCO3
fceccb90b0 delete HTML directory on an update 2025-02-24 21:56:49 +01:00
CaCO3
8f5bf209d9 Gz html files fix missing file server.html (#3582)
* Add files via upload

* Delete sd-card/html/file_server.html

* Update server_file.cpp

* Update server_file.cpp

---------

Co-authored-by: SybexX <Heinrich-Tuning@web.de>
2025-02-24 21:55:16 +01:00
CaCO3
ec639d4236 revert unwanted removal of Autotimer title in config page
Revert removal of title. It got removed in 708fd68cf5 but should have stayed
2025-02-24 21:39:31 +01:00
CaCO3
d68c95d626 delete HTML directory on an update 2025-02-24 21:30:14 +01:00
CaCO3
5304981733 Fix thermometer config (#3578)
* fix thermometer config

* update note

---------

Co-authored-by: CaCO3 <caco@ruinelli.ch>
2025-02-23 21:12:20 +01:00
SybexX
7a9955477a add data export (CSV files) (#3537)
* Update backup.html

* Add files via upload

* Update data_export.html

---------

Co-authored-by: CaCO3 <caco3@ruinelli.ch>
2025-02-23 17:38:32 +01:00
SybexX
04fe25e875 Update timezones.html
fix missing searchicon.png
2025-02-20 19:23:33 +01:00
SybexX
02dcd584bf Add files via upload 2025-02-20 19:19:59 +01:00
SybexX
68a262ef22 Update ClassFlowControll.cpp
fix .gz in GetJPGStream(std::string _fn, httpd_req_t *req)
2025-02-18 00:54:00 +01:00
SybexX
f2b6b4f819 Update sdcard_check.cpp
fix .gz in SDCardCheckFolderFilePresence()
2025-02-17 22:23:55 +01:00
CaCO3
6b672ff9a5 compress all HTML files (#3568)
* compress all HTML files

* remove gz files, they get zipped in the build pipeline

* Update build.yaml

* removed flipImageSize.png, it is not used anywhere anymore

* Update build.yaml
2025-02-16 19:11:09 +01:00
SybexX
26770d877e Update main.cpp
If the camera could only be initialized on the second attempt, "Camera Framebuffer Check" and "Print camera infos" was skipped.
2025-02-16 12:01:30 +01:00
jomjol
e60c12b25d Create Licence.md 2025-02-16 10:56:52 +01:00
CaCO3
b10336b59c Add thermometer (#3454)
* add thermometer

* .

---------

Co-authored-by: CaCO3 <caco@ruinelli.ch>
2025-02-16 10:34:10 +01:00
SybexX
dadb004e85 add .gz (#3538)
* Update server_help.cpp

* Add files via upload

* Add files via upload

* Update server_help.cpp
2025-02-16 10:28:06 +01:00
CaCO3
e2f3e3d05b corrected enable/disable texts to make it more intuitive (#3565) 2025-02-16 09:59:52 +01:00
CaCO3
8c5a6528d9 Update RetainMessages.md 2025-02-15 22:43:14 +01:00
CaCO3
69024adac7 updated param doc 2025-02-15 22:11:27 +01:00
CaCO3
9f1b7c9ef7 updated param doc 2025-02-15 21:55:57 +01:00
CaCO3
15fd7a6a33 Update webinstaller job (#3563)
* Update manual-update-webinstaller.yaml

* Update build.yaml
2025-02-15 00:28:30 +01:00
SybexX
9c3fbb4aff Update main.cpp 2025-02-14 21:41:28 +01:00
SybexX
eb4c9276d5 Merge pull request #3455 from jomjol/mqtt-add-ValidateServerCert-Parameter
MQTT add validate server cert parameter
2025-02-14 21:28:29 +01:00
SybexX
842229ea98 Merge branch 'main' into mqtt-add-ValidateServerCert-Parameter 2025-02-14 21:20:37 +01:00
jomjol
b83717aece Update manual-update-webinstaller.yaml 2025-02-14 20:21:43 +01:00
jomjol
b01e42b893 Revert to old version 2025-02-14 20:19:37 +01:00
jomjol
902f1bc2a8 Revert "Update manual-update-webinstaller.yaml"
This reverts commit 71f31dc841.
2025-02-14 20:13:51 +01:00
jomjol
5c51990b40 Merge branch 'main' of https://github.com/jomjol/AI-on-the-edge-device 2025-02-14 20:12:18 +01:00
jomjol
55cbbf02cb Revert "Update manual-update-webinstaller.yaml"
This reverts commit 20a0d8530a.
2025-02-14 20:11:39 +01:00
jomjol
e37817e3e2 Update manual-update-webinstaller.yaml 2025-02-14 19:57:53 +01:00
jomjol
a78ca53d60 Update manual-update-webinstaller.yaml 2025-02-14 19:55:10 +01:00
jomjol
20a0d8530a Update manual-update-webinstaller.yaml 2025-02-14 19:47:27 +01:00
jomjol
af1e3257be Update manual-update-webinstaller.yaml 2025-02-14 19:45:03 +01:00
jomjol
93c21d30a1 Update manual-update-webinstaller.yaml 2025-02-14 19:39:11 +01:00
jomjol
7111a7fdcd Update manual-update-webinstaller.yaml 2025-02-14 19:37:57 +01:00
jomjol
bd541ede60 Update manual-update-webinstaller.yaml 2025-02-14 19:25:58 +01:00
jomjol
52aab2f8a6 Update manual-update-webinstaller.yaml 2025-02-14 19:24:08 +01:00
jomjol
a1aee5b346 Update manual-update-webinstaller.yaml 2025-02-14 19:19:24 +01:00
jomjol
71f31dc841 Update manual-update-webinstaller.yaml 2025-02-14 19:18:03 +01:00
jomjol
f6a363a871 Update WebInstaller 2025-02-14 18:35:54 +01:00
SybexX
7de18753d9 Update smartled driver (#3500)
* update-SmartLeds

* Update sdkconfig.defaults
2025-02-14 18:07:55 +01:00
michael
fea0c1b859 TOOLTIPs_revised 2025-02-02 22:14:58 +01:00
SybexX
603fcfef33 fix 2025-01-31 11:49:46 +01:00
SybexX
be3312e912 fix 2025-01-31 11:49:34 +01:00
jomjol
ff657ebb5c Documentation of intreace_influxdb 2025-01-26 16:58:28 +01:00
jomjol
42d4916cb8 Rewrite InfluxDB Interfache (#3520)
* Replace influxdb interface

* Implement InfluxDBV2

* Update interface_influxdb.cpp

* Cleanup new InfluxDB interface
2025-01-25 23:01:32 +01:00
SybexX
a73cd97629 Update sdkconfig.defaults 2025-01-25 17:17:56 +01:00
SybexX
636d816727 Update main.cpp 2025-01-25 00:16:23 +01:00
SybexX
c6f6341c87 Update sdkconfig.defaults 2025-01-24 06:47:13 +01:00
SybexX
ad886898a8 Update ClassFlowMQTT.cpp 2025-01-24 06:43:52 +01:00
SybexX
f16cf1406c Update interface_mqtt.cpp 2025-01-24 06:41:38 +01:00
SybexX
5dfd9a7db0 Update interface_mqtt.cpp 2025-01-21 05:50:34 +01:00
SybexX
89dc941d40 Update platformio.ini 2025-01-21 05:36:50 +01:00
SybexX
3f3c845e9f Update sdkconfig.defaults 2025-01-21 05:34:22 +01:00
fsck-block
1e60d839b7 added and activated espressif mDNS service (#3494)
* added and activated espressif mDNS service

* moved esp-protocols to code/components
2025-01-17 23:16:55 +01:00
SybexX
c41a0a476d Update sdkconfig.defaults 2024-12-28 13:21:53 +01:00
michael
6da894142e test3 2024-12-24 14:07:59 +01:00
michael
4846a52d45 test2 2024-12-24 13:14:19 +01:00
michael
4d74d0c522 test1 2024-12-24 12:14:53 +01:00
michael
53e818186a test 2024-12-24 04:27:12 +01:00
72 changed files with 2565 additions and 1503 deletions

View File

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

View File

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

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

View File

@@ -1,6 +1,6 @@
# **Dual Use License for AI-on-the-Edge Device**
Version: 1.0 - Draft Version
Version: 1.0
Date: 2025-01-05 (5th January 2025)
## **Preamble**

View File

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

View File

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

View File

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

View 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

View 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

View 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

View 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(&copy_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

View 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

View File

@@ -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];
}

View File

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

View File

@@ -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>
@@ -58,20 +55,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();
@@ -87,7 +81,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;
@@ -131,7 +124,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;
@@ -175,12 +167,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];
@@ -192,82 +183,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>&#129145; 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;
}
@@ -283,22 +277,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");
@@ -306,31 +303,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);
}
@@ -339,7 +333,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);
}
@@ -424,7 +417,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");
@@ -510,7 +502,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)
{
@@ -528,7 +519,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 */
@@ -759,7 +749,6 @@ static esp_err_t upload_post_handler(httpd_req_t *req)
httpd_resp_set_hdr(req, "Location", directory.c_str());
httpd_resp_sendstr(req, "File uploaded successfully");
return ESP_OK;
}
@@ -770,7 +759,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];
@@ -893,13 +881,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;
@@ -925,7 +911,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;
@@ -1007,10 +993,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;
@@ -1137,8 +1128,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;
@@ -1164,8 +1153,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);
@@ -1180,7 +1167,6 @@ void register_server_file_uri(httpd_handle_t server, const char *base_path)
};
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,
@@ -1189,7 +1175,6 @@ void register_server_file_uri(httpd_handle_t server, const char *base_path)
};
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,
@@ -1206,7 +1191,6 @@ void register_server_file_uri(httpd_handle_t server, const char *base_path)
};
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,
@@ -1215,7 +1199,6 @@ void register_server_file_uri(httpd_handle_t server, const char *base_path)
};
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
@@ -1233,5 +1216,4 @@ void register_server_file_uri(httpd_handle_t server, const char *base_path)
.user_ctx = server_data // Pass server data as context
};
httpd_register_uri_handler(server, &file_delete);
}

View File

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

View File

@@ -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");

View File

@@ -76,17 +76,32 @@ 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 + "...");
::rename(outHtml.c_str(), outHtmlOld.c_str());
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Renaming folder " + outHtmlTmp + " to " + outHtml + "...");
::rename(outHtmlTmp.c_str(), outHtml.c_str());
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");
@@ -434,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);

View File

@@ -33,46 +33,52 @@ 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("");
@@ -80,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());
@@ -115,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");
}
@@ -224,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) {
@@ -248,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) {
@@ -411,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)
@@ -461,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;
@@ -480,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;
@@ -512,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
@@ -528,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");
@@ -542,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) &&
@@ -561,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]);
}
}
@@ -572,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]));
}
}
@@ -596,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]));
}
}
@@ -654,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);
@@ -702,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) {
@@ -710,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);
@@ -727,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) {
@@ -737,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);
@@ -754,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) {
@@ -765,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) +
@@ -780,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!");
@@ -818,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 {
@@ -842,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;
}

View File

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

View File

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

View File

@@ -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);
}
}

View File

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

View File

@@ -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))
{
@@ -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", "m", "°C/m"); // m = Minutes
}
else if (toUpper(splitted[1]) == "TEMPERATURE_F") {
mqttServer_setMeterType("temperature", "°F", "m", "°F/m"); // m = Minutes
}
else if (toUpper(splitted[1]) == "TEMPERATURE_K") {
mqttServer_setMeterType("temperature", "K", "m", "K/m"); // m = 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

View File

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

View File

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

View File

@@ -1281,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
{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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");

View File

@@ -10,6 +10,6 @@ dependencies:
source:
type: idf
version: 5.3.1
manifest_hash: 7350b157da8e1eb3cf21d0ea99443ec18c94cb2e0b22af07e20f286a9d15ec7a
manifest_hash: f88c9e5c2d75a9d5d6968fc67a90ef0cd7146dd6a3905a79c4dfcfc3b4fe6731
target: esp32
version: 1.0.0

View File

@@ -122,7 +122,7 @@ bool Init_NVS_SDCard()
if (ret != ESP_OK)
{
ESP_LOGE(TAG, "Failed to create a new on-chip LDO power control driver");
return ret;
return false;
}
host.pwr_ctrl_handle = pwr_ctrl_handle;
#endif
@@ -174,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;
@@ -291,7 +291,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();
@@ -322,10 +322,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()) {
@@ -384,6 +386,7 @@ extern "C" void app_main(void)
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
// ********************************************
@@ -954,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;
}
@@ -968,7 +968,7 @@ bool setCpuFrequency(void) {
if (toUpper(splitted[0]) == "CPUFREQUENCY") {
if (splitted.size() < 2) {
cpuFrequency = 160;
cpuFrequency = "160";
}
else {
cpuFrequency = splitted[1];

View File

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

View File

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

View File

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

View File

@@ -43,5 +43,6 @@ Hostname
RSSIThreshold
TimeServer
CACert
ValidateServerCert
ClientCert
ClientKey

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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)".

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
# Parameter `<NUMBER>.ChangeRateThreshold`
# Parameter `ChangeRateThreshold`
Default Value: `2`
Range: `1` .. `9`.
@@ -9,6 +9,9 @@ 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.
!!! 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.
## Example
- Smallest ROI provides value for `0.000'x` (Eg. a water meter with 4 pointers behind the decimal point)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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("&nbsp;- " + getFilenameFromUrl(urls[index]) + " (" + (index+1) + "/" + urls.length + ")...");
}
else {
setStatus("<span style=\"color: gray\">&nbsp;&nbsp;&nbsp;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("&nbsp;- " + getFilenameFromUrl(urls[index]) + " (" + (index+1) + "/" + urls.length + ")...");
}
else {
setStatus("<span style=\"color: gray\">&nbsp;&nbsp;&nbsp;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>

View 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("&nbsp;- " + getFilenameFromUrl(urls[index]) + " (" + (index+1) + "/" + urls.length + ")...");
}
else {
setStatus("<span style=\"color: gray\">&nbsp;&nbsp;&nbsp;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>

View File

@@ -363,8 +363,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>
@@ -421,8 +421,8 @@
</td>
<td>
<select id="TakeImage_CamAwb_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_CamAwb</td>
@@ -434,8 +434,8 @@
</td>
<td>
<select id="TakeImage_CamAwbGain_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_CamAwbGain</td>
@@ -447,8 +447,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>
@@ -460,8 +460,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>
@@ -495,8 +495,8 @@
</td>
<td>
<select id="TakeImage_CamAgc_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_CamAgc</td>
@@ -520,8 +520,8 @@
</td>
<td>
<select id="TakeImage_CamBpc_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_CamBpc</td>
@@ -533,8 +533,8 @@
</td>
<td>
<select id="TakeImage_CamWpc_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_CamWpc</td>
@@ -546,8 +546,8 @@
</td>
<td>
<select id="TakeImage_CamRawGma_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_CamRawGma</td>
@@ -559,8 +559,8 @@
</td>
<td>
<select id="TakeImage_CamLenc_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_CamLenc</td>
@@ -572,8 +572,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>
@@ -585,8 +585,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>
@@ -598,8 +598,8 @@
</td>
<td>
<select id="TakeImage_CamDcw_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_CamDcw</td>
@@ -622,8 +622,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>
@@ -681,8 +681,8 @@
</td>
<td>
<select id="TakeImage_Demo_value1">
<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_Demo</td>
@@ -850,8 +850,8 @@
</td>
<td>
<select id="PostProcessing_PreValueUse_value1">
<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_PostProcessing_PreValueUse</td>
@@ -875,8 +875,8 @@
</td>
<td>
<select id="PostProcessing_ErrorMessage_value1">
<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_PostProcessing_ErrorMessage</td>
@@ -888,8 +888,8 @@
</td>
<td>
<select id="PostProcessing_CheckDigitIncreaseConsistency_value1">
<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_PostProcessing_CheckDigitIncreaseConsistency</td>
@@ -897,7 +897,7 @@
<tr style="margin-top:12px">
<td class="indent1" style="padding-top:25px" colspan="3">
<b>Parameter per number sequence:</b>
<b>The following parameters are configurable individually for each number sequence:</b>
<select
style="font-weight: bold; margin-left:17px" id="Numbers_value1" onchange="numberChanged()">
</select>
@@ -910,8 +910,8 @@
</td>
<td>
<select id="PostProcessing_AllowNegativeRates_value1">
<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_PostProcessing_NUMBER.AllowNegativeRates</td>
@@ -988,8 +988,8 @@
</td>
<td>
<select id="PostProcessing_ExtendedResolution_value1">
<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_PostProcessing_NUMBER.ExtendedResolution</td>
@@ -1001,8 +1001,8 @@
</td>
<td>
<select id="PostProcessing_IgnoreLeadingNaN_value1">
<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_PostProcessing_NUMBER.IgnoreLeadingNaN</td>
@@ -1014,8 +1014,8 @@
</td>
<td>
<select id="PostProcessing_IgnoreAllNaN_value1">
<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_PostProcessing_NUMBER.IgnoreAllNaN</td>
@@ -1119,6 +1119,20 @@
</td>
<td>$TOOLTIP_MQTT_ClientKey</td>
</tr>
<tr class="MQTTItem expert" unused_id="exMqtt">
<td class="indent1">
<input type="checkbox" id="MQTT_ValidateServerCert_enabled" value="1" onclick = 'InvertEnableItem("MQTT", "ValidateServerCert")' unchecked >
<label for=MQTT_ValidateServerCert_enabled><class id="MQTT_ValidateServerCert_text" style="color:black;">Validate ServerCert</class></label>
</td>
<td>
<select id="MQTT_ValidateServerCert_value1">
<option value="true" selected>enabled (true)</option>
<option value="false">disabled (false)</option>
</select>
</td>
<td>$TOOLTIP_MQTT_ValidateServerCert</td>
</tr>
<tr class="MQTTItem">
<td class="indent1">
@@ -1126,8 +1140,8 @@
</td>
<td>
<select id="MQTT_RetainMessages_value1">
<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_MQTT_RetainMessages</td>
@@ -1148,8 +1162,8 @@
</td>
<td>
<select id="MQTT_HomeassistantDiscovery_value1">
<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_MQTT_HomeassistantDiscovery</td>
@@ -1166,13 +1180,16 @@
<option value="water_m3">Watermeter (Value: m³, Rate: m³/h)</option>
<option value="water_l">Watermeter (Value: l, Rate: l/h)</option>
<option value="water_gal">Watermeter (Value: gal, Rate: gal/h)</option>
<option value="water_ft3">Watermeter (Value: ft³, Rate: ft³/m)</option>
<option value="water_ft3">Watermeter (Value: ft³, Rate: ft³/min)</option>
<option value="gas_m3">Gasmeter (Value: m³, Rate: m³/h)</option>
<option value="gas_ft3">Gasmeter (Value: ft³, Rate: ft³/m)</option>
<option value="gas_ft3">Gasmeter (Value: ft³, Rate: ft³/min)</option>
<option value="energy_wh">Energymeter (Value: Wh, Rate: W)</option>
<option value="energy_kwh">Energymeter (Value: kWh, Rate: kW)</option>
<option value="energy_mwh">Energymeter (Value: MWh, Rate: MW)</option>
<option value="energy_gj">Energymeter (Value: GJ, Rate: GJ/h)</option>
<option value="temperature_c">Thermometer (Value: °C, Rate: °C/min)</option>
<option value="temperature_f">Thermometer (Value: °F, Rate: °F/min)</option>
<option value="temperature_k">Thermometer (Value: K, Rate: K/min)</option>
</select>
</td>
<td>$TOOLTIP_MQTT_MeterType</td>
@@ -1191,7 +1208,7 @@
<tr class="MQTTItem" style="margin-top:12px">
<td class="indent1" style="padding-top:25px" colspan="3">
<b>Parameter per number sequence:</b>
<b>The following parameters are configurable individually for each number sequence:</b>
<select
style="font-weight: bold; margin-left:17px" id="NumbersMQTTIdx_value1" onchange="numberMQTTIdxChanged()">
</select>
@@ -1920,19 +1937,19 @@
<!------------- Autotimer ------------------>
<!--
<tr style="border-bottom: 2px solid lightgray;">
<td colspan="3" style="padding-left: 0px; padding-bottom: 3px;"><h4>Auto Timer</h4></td>
</tr>
<!--
<tr class="expert" unused_id="ex13">
<td class="indent1">
<class id="AutoTimer_AutoStart_text" style="color:black;">Automatic Round Start</class>
</td>
<td>
<select id="AutoTimer_AutoStart_value1">
<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_AutoTimer_AutoStart</td>
@@ -1962,8 +1979,8 @@
</td>
<td>
<select id="DataLogging_DataLogActive_value1">
<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_DataLogging_DataLogActive</td>
@@ -2085,8 +2102,8 @@
</td>
<td>
<select id="System_Tooltip_value1" onchange="UpdateTooltipModus()">
<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></td>
@@ -2353,6 +2370,7 @@ function UpdateInput() {
WriteParameter(param, category, "MQTT", "CACert", true);
WriteParameter(param, category, "MQTT", "ClientCert", true);
WriteParameter(param, category, "MQTT", "ClientKey", true);
WriteParameter(param, category, "MQTT", "ValidateServerCert", true);
WriteParameter(param, category, "MQTT", "DomoticzTopicIn", true);
WriteParameter(param, category, "InfluxDB", "Uri", true);
@@ -2521,6 +2539,7 @@ function ReadParameterAll() {
ReadParameter(param, "MQTT", "CACert", true);
ReadParameter(param, "MQTT", "ClientCert", true);
ReadParameter(param, "MQTT", "ClientKey", true);
ReadParameter(param, "MQTT", "ValidateServerCert", true);
ReadParameter(param, "MQTT", "DomoticzTopicIn", true);
ReadParameter(param, "InfluxDB", "Uri", true);

View File

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

View 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;
}

View File

@@ -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>&#129145; 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>

View 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=/";
}

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 167 KiB

View File

@@ -142,6 +142,7 @@
<li><a href="#" onclick="loadPage('graph.html?v=$COMMIT_HASH');">Data Graph</a></li>
<li><a href="#" onclick="loadPage('data.html?v=$COMMIT_HASH');">Data Table</a></li>
<li><a href="#" onclick="loadPage(getDomainname() + '/fileserver/log/data/');">Data Files</a></li>
<li><a href="#" onclick="loadPage('data_export.html?v=$COMMIT_HASH');">Data Export</a></li>
</ul>
</li>

26
sd-card/html/ipInput.min.css vendored Normal file
View File

@@ -0,0 +1,26 @@
.ip-input-container {
display:inline-block;
font-size:0;
border:1px solid #ccc;
padding:1px 0;
}
.ip-input-container .ip-input-item {
border:none;
outline:0;
margin:0;
width:40px;
text-align:center;
vertical-align:bottom;
font-size:16px;
}
.ip-input-container .ip-input-dot {
display:inline-block;
width:2px;
height:2px;
margin-bottom:1px;
background-color:#333;
border-radius:50%;
vertical-align:bottom;
}

1
sd-card/html/ipInput.min.js vendored Normal file
View File

@@ -0,0 +1 @@
!function(t,i,e){function n(t,i,e){if(t.setSelectionRange)t.setSelectionRange(i,e);else if(t.createTextRange){var n=t.createTextRange();n.moveEnd("character",e),n.moveStart("character",i),n.select()}}function l(t,i,e){var n=i?/\d+/.test(i.replace(".","")):"",l=i?i.split("."):"";this.ipArr=4==l.length&&n?l:"",this.el=t,e&&this._init()}l.prototype={_init:function(){this.el.html('<div class="ip-input-container"><input type="text" class="ip-input-item" value="'+(this.ipArr?this.ipArr[0]:"")+'"/><i class="ip-input-dot"></i><input type="text" class="ip-input-item" value="'+(this.ipArr?this.ipArr[1]:"")+'"/><i class="ip-input-dot"></i><input type="text" class="ip-input-item" value="'+(this.ipArr?this.ipArr[2]:"")+'"/><i class="ip-input-dot"></i><input type="text" class="ip-input-item" value="'+(this.ipArr?this.ipArr[3]:"")+'"/></div>'),this._initEvent()},_initEvent:function(){var i;this.el.on("focus","input",function(){i=t(this).val()}).on("input","input",function(e){var l=t(this),r=l.val();if("."!=r||""==i){var a=r.charAt(r.length-1);if(3==(r=r.replace(/[^\d]/g,"")).length&&"."!=a){var s=l.nextAll("input").eq(0);s[0]&&(s.focus(),n(s[0],0,s.val().length))}parseInt(r)>255&&(r=r.substr(0,r.length-1)),l.val(r)}else l.val(i)}).on("keyup","input",function(e){var l=t(this),r=e.keyCode;if(190==r&&l.val().trim().length>0){var a=l.nextAll("input").eq(0);a[0]&&(a.focus(),n(a[0],0,a.val().length))}if(8==r){if(0==l.val().trim().length&&""==i){var s=l.prevAll("input").eq(0);if(s[0]){s.focus();var u=s.val();s.val(u.slice(0,u.length-1)),n(s[0],s.val().length,s.val().length)}}i=""==l.val()?"":i.slice(0,i.length-1)}8!=r&&190!=r&&(i=l.val())})},getIp:function(){for(var t=this.el.find("input"),i=[],e=0,n=t.length;e<n;e++)if(i[e]=t.eq(e).val(),""==i[e]||null==i[e])return void console.warn("please input the correct IP address");return i.join(".")},setIp:function(t){var i=t?/\d+/.test(t.replace(".","")):"",e=t?t.split("."):"";if(4==e.length&&i)for(var n=this.el.find("input"),l=0,r=n.length;l<r;l++)n.eq(l).val(e[l])},validate:function(){for(var t=this.el.find("input"),i=0,e=t.length;i<e;i++){var n=t.eq(i).val();if(""==n||null==n)return!1}return!0}},t.fn.ipInput=function(t){return new l(this,t,!0)},t.fn.validateIp=function(){for(var t=this.find("input"),i=0,e=t.length;i<e;i++){var n=t.eq(i).val();if(""==n||null==n)return!1}return!0},t.fn.getIp=function(){for(var t=this.find("input"),i=[],e=0,n=t.length;e<n;e++)if(i[e]=t.eq(e).val(),""==i[e]||null==i[e])return void console.warn("please input the correct IP address");return i.join(".")}}(jQuery,window,document);

View File

@@ -205,6 +205,7 @@ function ParseConfig() {
ParamAddValue(param, catname, "CACert");
ParamAddValue(param, catname, "ClientCert");
ParamAddValue(param, catname, "ClientKey");
ParamAddValue(param, catname, "ValidateServerCert");
var catname = "InfluxDB";
category[catname] = new Object();

BIN
sd-card/html/searchicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 638 B

View File

@@ -8,7 +8,7 @@
}
#myInput {
background-image: url('/css/searchicon.png');
background-image: url('searchicon.png');
background-position: 10px 10px;
background-repeat: no-repeat;
width: 100%;
@@ -539,4 +539,4 @@
</body>
</html>