Compare commits

...

31 Commits

Author SHA1 Message Date
allexoK
247f7ee8e2 Esp32s3 test (#3836)
* Added ethernet functionality

* change smart leds to GPIO47 for now

* Make etherenet code specific for the esp32-s3 board

* Add 'sleep if idle' option for sleeping between rounds and change IO47 to IO12 for LED

* minor fix, remove space

* minor fix, add space

* remove space
2025-08-26 20:38:28 +02:00
allexoK
149bbdc553 Esp32s3 test (#3734)
* Added ethernet functionality

* change smart leds to GPIO47 for now

* Make etherenet code specific for the esp32-s3 board
2025-05-14 20:13:01 +02:00
jomjol
58c7bce8b5 Merge branch 'main' into esp32s3-test 2025-03-19 19:41:44 +01:00
CaCO3
64bf79b288 Update config.ini in demo folder 2025-03-18 22:36:53 +01:00
jomjol
13e9fa2cc4 First Running Version 2025-03-17 22:08:03 +01:00
jomjol
de48d0b008 Update defines.h 2025-03-17 19:40:57 +01:00
CaCO3
61085d3861 webinstaller: remove broken email link (#3639) 2025-03-16 18:32:27 +01:00
CaCO3
8494f36069 Rename webinstaller folder and add readme's (#3637) 2025-03-16 18:25:41 +01:00
CaCO3
3e67aeec0d Add note to sd card folder (#3635) to make people aware that the HTML folder only contains templates
* Create Readme.md

* Update Readme.md

* Update build.yaml

* Update Readme.md
2025-03-16 18:24:16 +01:00
CaCO3
e85e92762e Fix webinstaller-imgs 2025-03-15 22:56:33 +01:00
CaCO3
eb9bf3c7c1 Update index.html (#3636) 2025-03-15 22:52:53 +01:00
jomjol
9c604c9cbc Correct PSRAM 2025-03-15 17:00:47 +01:00
CaCO3
f542d842cf Update changelog (consolidate all RC changes into one) (#3614)
* Update changelog (consolidate all RC changes into one)

* Update Changelog.md

---------

Co-authored-by: CaCO3 <caco@ruinelli.ch>
Co-authored-by: jomjol <mueller.josef@gmail.com>
2025-03-15 14:24:06 +01:00
jomjol
636a17117b Update board type 2025-03-14 21:30:21 +01:00
SybexX
5bbc2f3da5 Update ClassFlowPostProcessing.cpp
fix for: ChangeRateThreshold could not be deactivated
2025-03-02 13:26:07 +01:00
SybexX
2831478e02 Update edit_config_template.html
fix for: ChangeRateThreshold could not be deactivated
2025-03-02 13:25:49 +01:00
CaCO3
1e0cdfaba1 Remove unused config template page and empty, unused folder in zip files (#3599)
* Update build.yaml

* Update build.yaml

* remove empty folder in sd-card.zip in manual package
2025-03-02 08:31:22 +01:00
SybexX
8d4b28b481 Update Helper.cpp 2025-03-02 01:32:49 +01:00
jomjol
9a82e0ac92 Initial Modifikations 2025-03-01 20:08:25 +01:00
SybexX
bfebcd5d15 Update config.ini 2025-03-01 18:21:40 +01:00
CaCO3
cf96d49bd0 Release preparations (#3598)
* Update Changelog.md

* Update Changelog.md

* Update Changelog.md

* Update Changelog.md

* Update Changelog.md

* Update Changelog.md

* Update Changelog.md

* Update Changelog.md

* Update Changelog.md
2025-03-01 00:34:39 +01:00
CaCO3
94a53b38b8 Update Homeassistant discovery (#3580)
* use "total" for Homeassistant discovery topic "raw" if AllowNegativeRates is activ (same as for "value")

* update webUI

* .

* .

* .

* formating

* use state class "measurement" in case of a thermometer

* Update edit_config_template.html

---------

Co-authored-by: CaCO3 <caco@ruinelli.ch>
2025-03-01 00:28:38 +01:00
CaCO3
00ac2130c2 Calculate and validate MD5 on upload (#3590)
* added md5 library

* added MD5 calculation of uploaded file. And return JSON string instead of fileserver

* .

* .

* .

* .

* .

* .

* .

* .

* .

* Add fallback for older firmware

---------

Co-authored-by: CaCO3 <caco@ruinelli.ch>
2025-03-01 00:25:48 +01:00
SybexX
cd1165e547 IgnoreLeadingNaN fix (#3547)
* test1

* test2

* Update edit_config_template.html

* fix

* Update NUMBER.CheckDigitIncreaseConsistency.md

---------

Co-authored-by: CaCO3 <caco3@ruinelli.ch>
2025-03-01 00:09:11 +01:00
jomjol
c587ca3224 Update Changelog.md 2025-02-28 20:46:08 +01:00
CaCO3
76f45a5927 Remove html directory on update (#3584)
* delete HTML directory on an update

* delete HTML directory on an update

* rename html folder

* swap HTML folders after extracting

* .

* .

* .

* .

* .

* .

* move SD card check, SD card directories setup and update to before the PSRAM init.
The update should be as early as possible to allow updates even if the PSRAM or cam fails.

* .

* .

* Update Helper.cpp

* Update Helper.h

* .

---------

Co-authored-by: CaCO3 <caco@ruinelli.ch>
Co-authored-by: SybexX <Heinrich-Tuning@web.de>
2025-02-26 23:56:49 +01:00
jomjol
063c4827d0 Merge branch 'main' of https://github.com/jomjol/AI-on-the-edge-device 2025-02-26 22:00:27 +01:00
jomjol
c6b8823417 Update Changelog.md 2025-02-26 22:00:20 +01:00
github-actions[bot]
bb5e693077 docs(contributor): contrib-readme-action has updated readme 2025-02-25 18:31:30 +00:00
fsck-block
424df641cc openmetrics endpoint extension (#3521)
* added pre-value and raw-value to openmetrics endpoint

* added flow_error to openmentrics endpoint
2025-02-24 23:07:29 +01:00
CaCO3
8ddbda16bf consolidated reboot and save buttons (#3581)
* config page: consolidated reboot and save button

* various pages: consolidated reboot and save button

---------

Co-authored-by: CaCO3 <caco@ruinelli.ch>
2025-02-24 23:05:32 +01:00
62 changed files with 10904 additions and 2667 deletions

View File

@@ -84,7 +84,8 @@ jobs:
cd ../..
cp -r ./sd-card/html/* ./html/
rm -f ./html/edit_config_template.html # Remove the config page template, it is no longer needed
echo "Replacing variables..."
cd html
find . -type f -exec sed -i 's/$COMMIT_HASH/${{ steps.vars.outputs.sha_short }}/g' {} \;
@@ -285,8 +286,9 @@ jobs:
rm -rf ./sd-card/html
rm -rf ./sd-card/demo
cp -r ./html ./sd-card/ # Overwrite the Web UI with the preprocessed files
rm -f ./sd-card/Readme.md
cp -r ./demo ./sd-card/
cd sd-card; zip -r ../manual_setup/sd-card.zip *; cd ..
cd sd-card; rm -rf html/param-tooltips; zip -r ../manual_setup/sd-card.zip *; cd ..
cd ./manual_setup
- name: Upload manual_setup.zip artifact (Firmware + Bootloader + Partitions + Web UI)
@@ -433,13 +435,13 @@ jobs:
- name: Add binary to Web Installer and update manifest
run: |
echo "Updating Web installer to use firmware from ${{ steps.last_release.outputs.tag_name }}..."
rm -f docs/binary/firmware.bin
rm -f webinstaller/binary/firmware.bin
wget ${{ github.server_url }}/${{ github.repository }}/releases/download/${{ steps.last_release.outputs.tag_name }}/AI-on-the-edge-device__update__${{ steps.last_release.outputs.tag_name }}.zip
unzip AI-on-the-edge-device__update__${{ steps.last_release.outputs.tag_name }}.zip
cp -f firmware.bin docs/binary/firmware.bin
cp -f firmware.bin webinstaller/binary/firmware.bin
echo "Updating index and manifest file..."
sed -i 's/$VERSION/${{ steps.last_release.outputs.tag_name }}/g' docs/index.html
sed -i 's/$VERSION/${{ steps.last_release.outputs.tag_name }}/g' docs/manifest.json
sed -i 's/$VERSION/${{ steps.last_release.outputs.tag_name }}/g' webinstaller/index.html
sed -i 's/$VERSION/${{ steps.last_release.outputs.tag_name }}/g' webinstaller/manifest.json
- name: Setup Pages
uses: actions/configure-pages@v4
@@ -447,7 +449,7 @@ jobs:
- name: Upload artifact
uses: actions/upload-pages-artifact@v2
with:
path: 'docs'
path: 'webinstaller'
- name: Deploy to GitHub Pages
id: deployment

View File

@@ -1,4 +1,4 @@
# This updates the Web Installer with the files from the docs folder and the binary of the latest release
# This updates the Web Installer with the files from the webinstaller folder and the binary of the latest release
# it only gets run on:
# - Manually triggered
# Make sure to also update the lower part of build.yml!
@@ -11,7 +11,7 @@ on:
# branches:
# - rolling
# paths:
# - docs # The path filter somehow does not work, so lets run it on every change to rolling
# - webinstaller # The path filter somehow does not work, so lets run it on every change to rolling
jobs:
manually-update-web-installer:
@@ -42,13 +42,13 @@ jobs:
- name: Add binary to Web Installer and update manifest
run: |
echo "Updating Web installer to use firmware from ${{ steps.last_release.outputs.tag_name }}..."
rm -f docs/binary/firmware.bin
wget https://github.com/jomjol/AI-on-the-edge-device/releases/download/${{ steps.last_release.outputs.tag_name }}/AI-on-the-edge-device__update__${{ steps.last_release.outputs.tag_name }}.zip
rm -f webinstaller/binary/firmware.bin
wget ${{ github.server_url }}/${{ github.repository }}/releases/download/${{ steps.last_release.outputs.tag_name }}/AI-on-the-edge-device__update__${{ steps.last_release.outputs.tag_name }}.zip
unzip AI-on-the-edge-device__update__${{ steps.last_release.outputs.tag_name }}.zip
cp -f firmware.bin docs/binary/firmware.bin
cp -f firmware.bin webinstaller/binary/firmware.bin
echo "Updating index and manifest file..."
sed -i 's/$VERSION/${{ steps.last_release.outputs.tag_name }}/g' docs/index.html
sed -i 's/$VERSION/${{ steps.last_release.outputs.tag_name }}/g' docs/manifest.json
sed -i 's/$VERSION/${{ steps.last_release.outputs.tag_name }}/g' webinstaller/index.html
sed -i 's/$VERSION/${{ steps.last_release.outputs.tag_name }}/g' webinstaller/manifest.json
- name: Setup Pages
uses: actions/configure-pages@v5
@@ -56,7 +56,7 @@ jobs:
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: 'docs'
path: 'webinstaller'
- name: Deploy to GitHub Pages
id: deployment

File diff suppressed because it is too large Load Diff

View File

@@ -250,13 +250,6 @@ There are some ideas and feature requests which are not currently being pursued
<sub><b>Frank Haverland</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/Slider0007">
<img src="https://avatars.githubusercontent.com/u/115730895?v=4" width="100;" alt="Slider0007"/>
<br />
<sub><b>Slider0007</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/SybexX">
<img src="https://avatars.githubusercontent.com/u/587201?v=4" width="100;" alt="SybexX"/>
@@ -264,6 +257,13 @@ There are some ideas and feature requests which are not currently being pursued
<sub><b>michael</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/Slider0007">
<img src="https://avatars.githubusercontent.com/u/115730895?v=4" width="100;" alt="Slider0007"/>
<br />
<sub><b>Slider0007</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/nliaudat">
<img src="https://avatars.githubusercontent.com/u/6782613?v=4" width="100;" alt="nliaudat"/>
@@ -368,6 +368,13 @@ There are some ideas and feature requests which are not currently being pursued
<sub><b>parhedberg</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/fsck-block">
<img src="https://avatars.githubusercontent.com/u/58307481?v=4" width="100;" alt="fsck-block"/>
<br />
<sub><b>fsck-block</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/slovdahl">
<img src="https://avatars.githubusercontent.com/u/1417619?v=4" width="100;" alt="slovdahl"/>
@@ -389,13 +396,6 @@ There are some ideas and feature requests which are not currently being pursued
<sub><b>LordGuilly</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/bilalmirza74">
<img src="https://avatars.githubusercontent.com/u/84387676?v=4" width="100;" alt="bilalmirza74"/>
<br />
<sub><b>Bilal Mirza</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/muggenhor">
<img src="https://avatars.githubusercontent.com/u/484066?v=4" width="100;" alt="muggenhor"/>
@@ -406,10 +406,17 @@ There are some ideas and feature requests which are not currently being pursued
</tr>
<tr>
<td align="center">
<a href="https://github.com/ppisljar">
<img src="https://avatars.githubusercontent.com/u/13629809?v=4" width="100;" alt="ppisljar"/>
<a href="https://github.com/bilalmirza74">
<img src="https://avatars.githubusercontent.com/u/84387676?v=4" width="100;" alt="bilalmirza74"/>
<br />
<sub><b>Peter Pisljar</b></sub>
<sub><b>Bilal Mirza</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/AngryApostrophe">
<img src="https://avatars.githubusercontent.com/u/89547888?v=4" width="100;" alt="AngryApostrophe"/>
<br />
<sub><b>AngryApostrophe</b></sub>
</a>
</td>
<td align="center">
@@ -426,6 +433,13 @@ There are some ideas and feature requests which are not currently being pursued
<sub><b>Ranjana761</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/SURYANSH-RAI">
<img src="https://avatars.githubusercontent.com/u/79277130?v=4" width="100;" alt="SURYANSH-RAI"/>
<br />
<sub><b>SURYANSH RAI</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/SkylightXD">
<img src="https://avatars.githubusercontent.com/u/16561545?v=4" width="100;" alt="SkylightXD"/>
@@ -433,6 +447,8 @@ There are some ideas and feature requests which are not currently being pursued
<sub><b>SkylightXD</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/ottk3">
<img src="https://avatars.githubusercontent.com/u/5236802?v=4" width="100;" alt="ottk3"/>
@@ -447,8 +463,6 @@ There are some ideas and feature requests which are not currently being pursued
<sub><b>Tobias Bieniek</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/tkopczuk">
<img src="https://avatars.githubusercontent.com/u/101632?v=4" width="100;" alt="tkopczuk"/>
@@ -477,6 +491,8 @@ There are some ideas and feature requests which are not currently being pursued
<sub><b>flox_x</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/gneluka">
<img src="https://avatars.githubusercontent.com/u/32097881?v=4" width="100;" alt="gneluka"/>
@@ -491,8 +507,6 @@ There are some ideas and feature requests which are not currently being pursued
<sub><b>kalwados</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/kub3let">
<img src="https://avatars.githubusercontent.com/u/95883234?v=4" width="100;" alt="kub3let"/>
@@ -521,13 +535,8 @@ There are some ideas and feature requests which are not currently being pursued
<sub><b>smartboart</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/AngryApostrophe">
<img src="https://avatars.githubusercontent.com/u/89547888?v=4" width="100;" alt="AngryApostrophe"/>
<br />
<sub><b>AngryApostrophe</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/wetneb">
<img src="https://avatars.githubusercontent.com/u/309908?v=4" width="100;" alt="wetneb"/>
@@ -535,8 +544,6 @@ There are some ideas and feature requests which are not currently being pursued
<sub><b>Antonin Delpeuch</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/adarazs">
<img src="https://avatars.githubusercontent.com/u/6269603?v=4" width="100;" alt="adarazs"/>
@@ -572,6 +579,8 @@ There are some ideas and feature requests which are not currently being pursued
<sub><b>Dave</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/FarukhS52">
<img src="https://avatars.githubusercontent.com/u/129654632?v=4" width="100;" alt="FarukhS52"/>
@@ -579,8 +588,6 @@ There are some ideas and feature requests which are not currently being pursued
<sub><b>Farookh Zaheer Siddiqui</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/hex7c0">
<img src="https://avatars.githubusercontent.com/u/4419146?v=4" width="100;" alt="hex7c0"/>
@@ -616,6 +623,8 @@ There are some ideas and feature requests which are not currently being pursued
<sub><b>Joerg Rosenkranz</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/Innovatorcloudy">
<img src="https://avatars.githubusercontent.com/u/183274513?v=4" width="100;" alt="Innovatorcloudy"/>
@@ -623,8 +632,6 @@ There are some ideas and feature requests which are not currently being pursued
<sub><b>KrishCode</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/myxor">
<img src="https://avatars.githubusercontent.com/u/1397377?v=4" width="100;" alt="myxor"/>
@@ -652,6 +659,13 @@ There are some ideas and feature requests which are not currently being pursued
<br />
<sub><b>Michael Geissler</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/ppisljar">
<img src="https://avatars.githubusercontent.com/u/13629809?v=4" width="100;" alt="ppisljar"/>
<br />
<sub><b>Peter Pisljar</b></sub>
</a>
</td>
</tr>
<tbody>

View File

@@ -0,0 +1,7 @@
FILE(GLOB_RECURSE app_sources ${CMAKE_CURRENT_SOURCE_DIR}/*.*)
idf_component_register(SRCS ${app_sources}
INCLUDE_DIRS "."
REQUIRES esp_eth nvs_flash wpa_supplicant jomjol_wlan jomjol_helper jomjol_mqtt esp_netif)

View File

@@ -0,0 +1,210 @@
#if defined(BOARD_ESP32_S3_ALEKSEI)
#include "connect_lan.h"
#include <string.h>
#include <stdlib.h>
#include <fstream>
#include <vector>
#include <sstream>
#include <iostream>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "driver/gpio.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_wnm.h"
#include "esp_rrm.h"
#include "esp_mbo.h"
#include "esp_mac.h"
#include "esp_netif.h"
#include <netdb.h>
#include "esp_log.h"
#include "nvs_flash.h"
#include "lwip/err.h"
#include "lwip/sys.h"
#ifdef ENABLE_MQTT
#include "interface_mqtt.h"
#endif //ENABLE_MQTT
#include "ClassLogFile.h"
#include "read_lanini.h"
#include "Helper.h"
#include "statusled.h"
#include "../../include/defines.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
#include "../esp-protocols/components/mdns/include/mdns.h"
#include <driver/spi_master.h>
#include <esp_eth.h>
#include <esp_netif.h>
static const char *TAG = "LAN";
extern bool WIFIConnected;
static int LanReconnectCnt = 0;
std::string* getLanIPAddress()
{
return &wlan_config.ipaddress;
}
static void eth_event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data)
{
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START)
{
WIFIConnected = false;
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Ethernet Started");
}
else if (event_base == ETH_EVENT && event_id == ETHERNET_EVENT_DISCONNECTED)
{
LogFile.WriteToFile(ESP_LOG_WARN, TAG, "Ethernet Link Down");
// Optionally, try to reconnect or handle fallback LED:
StatusLED(WLAN_CONN, 1, false);
LanReconnectCnt++;
WIFIConnected = false;
}
else if (event_base == ETH_EVENT && event_id == ETHERNET_EVENT_CONNECTED)
{
uint8_t mac_addr[6] = {0};
esp_eth_handle_t eth_handle = *(esp_eth_handle_t *)event_data;
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Ethernet Link Up");
esp_eth_ioctl(eth_handle, ETH_CMD_G_MAC_ADDR, mac_addr);
ESP_LOGI(TAG, "Ethernet HW Addr %02x:%02x:%02x:%02x:%02x:%02x",
mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
}
else if (event_base == ETH_EVENT && event_id == ETHERNET_EVENT_STOP) {
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Ethernet Stopped");
WIFIConnected = false;
}
}
static void got_ip_event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
WIFIConnected = true;
LanReconnectCnt = 0;
ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
wlan_config.ipaddress = std::string(ip4addr_ntoa((const ip4_addr*) &event->ip_info.ip));
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Assigned IP: " + wlan_config.ipaddress);
#ifdef ENABLE_MQTT
if (getMQTTisEnabled()) {
vTaskDelay(5000 / portTICK_PERIOD_MS);
MQTT_Init(); // Init when WIFI is getting connected
}
#endif //ENABLE_MQTT
// LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Assigned IP: " + WIFIConnected);
// LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Assigned IP: " + getWIFIisConnected());
}
esp_eth_handle_t eth_handle = NULL;
esp_netif_t *eth_netif = NULL;
int lan_init(void)
{
esp_err_t retval = esp_netif_init();
if (retval != ESP_OK) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "esp_netif_init: Error: " + std::to_string(retval));
return retval;
}
int retVal = esp_event_loop_create_default();
if (retVal != ESP_OK && retVal != ESP_ERR_INVALID_STATE) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "esp_event_loop_create_default, Error");
return retVal;
}
gpio_set_direction(ETH_ENABLE, GPIO_MODE_OUTPUT);
gpio_set_level(ETH_ENABLE, 1);
gpio_set_direction(ETH_INT, GPIO_MODE_INPUT);
gpio_set_pull_mode(ETH_INT, GPIO_PULLUP_ONLY);
esp_log_level_set("netif", ESP_LOG_DEBUG);
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "SPI init");
// 1) SPI bus init
spi_bus_config_t buscfg = { 0 };
buscfg.mosi_io_num = ETH_MOSI;
buscfg.miso_io_num = ETH_MISO;
buscfg.sclk_io_num = ETH_CLK;
ESP_ERROR_CHECK(spi_bus_initialize(W5500_SPI_HOST, &buscfg, SPI_DMA_CH_AUTO));
// 2) Prepare a `spi_device_interface_config_t` but DO NOT call spi_bus_add_device manually
spi_device_interface_config_t devcfg = {
.mode = 0, // SPI mode 0
.clock_speed_hz = 40 * 1000 * 1000, // 20MHz
.spics_io_num = ETH_CS,
.queue_size = 30,
// the rest zero-initialized
};
eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(W5500_SPI_HOST, &devcfg);
w5500_config.int_gpio_num = ETH_INT;
// 4) Standard MAC/PHY config
eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG();
esp_eth_mac_t *mac = esp_eth_mac_new_w5500(&w5500_config, &mac_config);
eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();
phy_config.phy_addr = 1; // typical W5500
esp_eth_phy_t *phy = esp_eth_phy_new_w5500(&phy_config);
esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(mac, phy);
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Driver install");
ESP_ERROR_CHECK(esp_eth_driver_install(&eth_config, &eth_handle));
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Driver installed");
uint8_t base_mac_addr[6];
esp_err_t ret = esp_efuse_mac_get_default(base_mac_addr);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to get efuse base MAC, error=0x%x", ret);
}
uint8_t local_mac[6];
esp_derive_local_mac(local_mac, base_mac_addr);
ret = esp_eth_ioctl(eth_handle, ETH_CMD_S_MAC_ADDR, local_mac);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to set W5500 MAC, error=0x%x", ret);
}
// 5) Attach netif + start
esp_netif_config_t netif_cfg = ESP_NETIF_DEFAULT_ETH();
eth_netif = esp_netif_new(&netif_cfg);
// Register event handlers
ESP_ERROR_CHECK(esp_event_handler_register(
ETH_EVENT, ESP_EVENT_ANY_ID,
&eth_event_handler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(
IP_EVENT, IP_EVENT_ETH_GOT_IP,
&got_ip_event_handler, NULL));
esp_netif_attach(eth_netif, esp_eth_new_netif_glue(eth_handle));
esp_eth_start(eth_handle);
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "W5500 Ethernet init done");
return ESP_OK;
}
#endif

View File

@@ -0,0 +1,20 @@
#if defined(BOARD_ESP32_S3_ALEKSEI)
#pragma once
#ifndef CONNECT_LAN_H
#define CONNECT_LAN_H
#include <string>
// #include "connect_wlan.h"
// int wifi_init_sta(void);
std::string* getLanIPAddress();
// int get_WIFI_RSSI();
std::string* getLanHostname();
bool getLanIsConnected();
void LanDestroy();
int lan_init();
#endif //CONNECT_WLAN_H
#endif

View File

@@ -0,0 +1,248 @@
#if defined(BOARD_ESP32_S3_ALEKSEI)
#include "read_lanini.h"
#include "Helper.h"
#include "connect_lan.h"
#include <fstream>
#include <string>
#include <vector>
#include <sstream>
#include <iostream>
#include <string.h>
#include "esp_log.h"
#include "ClassLogFile.h"
#include "../../include/defines.h"
static const char *TAG = "LANINI";
std::vector<string> ZerlegeZeileLAN(std::string input, std::string _delimiter = "")
{
std::vector<string> Output;
std::string delimiter = " =,";
if (_delimiter.length() > 0){
delimiter = _delimiter;
}
input = trim(input, delimiter);
size_t pos = findDelimiterPos(input, delimiter);
std::string token;
if (pos != std::string::npos) // splitted only up to first equal sign !!! Special case for LAN.ini
{
token = input.substr(0, pos);
token = trim(token, delimiter);
Output.push_back(token);
input.erase(0, pos + 1);
input = trim(input, delimiter);
}
Output.push_back(input);
return Output;
}
int LoadLanFromFile(std::string fn)
{
std::string line = "";
std::string tmp = "";
std::vector<string> splitted;
fn = FormatFileName(fn);
FILE* pFile = fopen(fn.c_str(), "r");
if (pFile == NULL) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Unable to open file (read). Device init aborted!");
return -1;
}
ESP_LOGD(TAG, "LoadLanFromFile: lan.ini opened");
char zw[256];
if (fgets(zw, sizeof(zw), pFile) == NULL) {
line = "";
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "file opened, but empty or content not readable. Device init aborted!");
fclose(pFile);
return -1;
}
else {
line = std::string(zw);
}
while ((line.size() > 0) || !(feof(pFile)))
{
//ESP_LOGD(TAG, "line: %s", line.c_str());
if (line[0] != ';') { // Skip lines which starts with ';'
splitted = ZerlegeZeileLAN(line, "=");
splitted[0] = trim(splitted[0], " ");
if ((splitted.size() > 1) && (toUpper(splitted[0]) == "HOSTNAME")){
tmp = trim(splitted[1]);
if ((tmp[0] == '"') && (tmp[tmp.length()-1] == '"')){
tmp = tmp.substr(1, tmp.length()-2);
}
wlan_config.hostname = tmp;
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Hostname: " + wlan_config.hostname);
}
else if ((splitted.size() > 1) && (toUpper(splitted[0]) == "IP")){
tmp = splitted[1];
if ((tmp[0] == '"') && (tmp[tmp.length()-1] == '"')){
tmp = tmp.substr(1, tmp.length()-2);
}
wlan_config.ipaddress = tmp;
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "IP-Address: " + wlan_config.ipaddress);
}
else if ((splitted.size() > 1) && (toUpper(splitted[0]) == "GATEWAY")){
tmp = splitted[1];
if ((tmp[0] == '"') && (tmp[tmp.length()-1] == '"')){
tmp = tmp.substr(1, tmp.length()-2);
}
wlan_config.gateway = tmp;
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Gateway: " + wlan_config.gateway);
}
else if ((splitted.size() > 1) && (toUpper(splitted[0]) == "NETMASK")){
tmp = splitted[1];
if ((tmp[0] == '"') && (tmp[tmp.length()-1] == '"')){
tmp = tmp.substr(1, tmp.length()-2);
}
wlan_config.netmask = tmp;
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Netmask: " + wlan_config.netmask);
}
else if ((splitted.size() > 1) && (toUpper(splitted[0]) == "DNS")){
tmp = splitted[1];
if ((tmp[0] == '"') && (tmp[tmp.length()-1] == '"')){
tmp = tmp.substr(1, tmp.length()-2);
}
wlan_config.dns = tmp;
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "DNS: " + wlan_config.dns);
}
else if ((splitted.size() > 1) && (toUpper(splitted[0]) == "HTTP_USERNAME")){
tmp = splitted[1];
if ((tmp[0] == '"') && (tmp[tmp.length()-1] == '"')){
tmp = tmp.substr(1, tmp.length()-2);
}
wlan_config.http_username = tmp;
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "HTTP_USERNAME: " + wlan_config.http_username);
}
else if ((splitted.size() > 1) && (toUpper(splitted[0]) == "HTTP_PASSWORD")){
tmp = splitted[1];
if ((tmp[0] == '"') && (tmp[tmp.length()-1] == '"')){
tmp = tmp.substr(1, tmp.length()-2);
}
wlan_config.http_password = tmp;
#ifndef __HIDE_PASSWORD
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "HTTP_PASSWORD: " + wlan_config.http_password);
#else
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "HTTP_PASSWORD: XXXXXXXX");
#endif
}
}
/* read next line */
if (fgets(zw, sizeof(zw), pFile) == NULL) {
line = "";
}
else {
line = std::string(zw);
}
}
fclose(pFile);
return 0;
}
bool ChangeLanHostName(std::string fn, std::string _newhostname)
{
if (_newhostname == wlan_config.hostname)
return false;
std::string line = "";
std::vector<string> splitted;
std::vector<string> neuesfile;
bool found = false;
FILE* pFile = NULL;
fn = FormatFileName(fn);
pFile = fopen(fn.c_str(), "r");
if (pFile == NULL) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "ChangeHostName: Unable to open file lan.ini (read)");
return false;
}
ESP_LOGD(TAG, "ChangeHostName: lan.ini opened");
char zw[256];
if (fgets(zw, sizeof(zw), pFile) == NULL) {
line = "";
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "ChangeHostName: File opened, but empty or content not readable");
return false;
}
else {
line = std::string(zw);
}
while ((line.size() > 0) || !(feof(pFile)))
{
//ESP_LOGD(TAG, "ChangeHostName: line: %s", line.c_str());
splitted = ZerlegeZeileLAN(line, "=");
splitted[0] = trim(splitted[0], " ");
if ((splitted.size() > 1) && ((toUpper(splitted[0]) == "HOSTNAME") || (toUpper(splitted[0]) == ";HOSTNAME"))){
line = "hostname = \"" + _newhostname + "\"\n";
found = true;
}
neuesfile.push_back(line);
if (fgets(zw, sizeof(zw), pFile) == NULL)
{
line = "";
}
else
{
line = std::string(zw);
}
}
if (!found)
{
line = "\n;++++++++++++++++++++++++++++++++++\n";
line += "; Hostname: Name of device in network\n";
line += "; This parameter can be configured via WebUI configuration\n";
line += "; Default: \"watermeter\", if nothing is configured\n\n";
line = "hostname = \"" + _newhostname + "\"\n";
neuesfile.push_back(line);
}
fclose(pFile);
pFile = fopen(fn.c_str(), "w+");
if (pFile == NULL) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "ChangeHostName: Unable to open file wlan.ini (write)");
return false;
}
for (int i = 0; i < neuesfile.size(); ++i)
{
//ESP_LOGD(TAG, "%s", neuesfile[i].c_str());
fputs(neuesfile[i].c_str(), pFile);
}
fclose(pFile);
ESP_LOGD(TAG, "ChangeLanHostName done");
return true;
}
#endif

View File

@@ -0,0 +1,19 @@
#if defined(BOARD_ESP32_S3_ALEKSEI)
#pragma once
#ifndef READ_LANINI_H
#define READ_LANINI_H
#include <string>
#include "read_wlanini.h"
extern struct wlan_config wlan_config;
int LoadLanFromFile(std::string fn);
bool ChangeLanHostName(std::string fn, std::string _newhostname);
#endif //READ_WLANINI_H
#endif

View File

@@ -244,6 +244,10 @@ bool CCamera::getCameraInitSuccessful(void)
esp_err_t CCamera::setSensorDatenFromCCstatus(void)
{
#if defined(BOARD_ESP32_S3_ALEKSEI)
esp_camera_deinit();
ESP_ERROR_CHECK( esp_camera_init(&camera_config) );
#endif
sensor_t *s = esp_camera_sensor_get();
if (s != NULL)
@@ -1011,7 +1015,7 @@ esp_err_t CCamera::CaptureToStream(httpd_req_t *req, bool FlashlightOn)
vTaskDelay(xDelay);
}
}
// httpd_resp_send_chunk(req, NULL, 0); //
LEDOnOff(false); // Status-LED off
LightOnOff(false); // Flash-LED off

View File

@@ -0,0 +1,226 @@
/* Src: https://github.com/Zunawe/md5-c, commit: f3529b6
* License: Unlicense */
/*
* Derived from the RSA Data Security, Inc. MD5 Message-Digest Algorithm
* and modified slightly to be functionally identical but condensed into control structures.
*/
#include "md5.h"
/*
* Constants defined by the MD5 algorithm
*/
#define A 0x67452301
#define B 0xefcdab89
#define C 0x98badcfe
#define D 0x10325476
static uint32_t S[] = {7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21};
static uint32_t K[] = {0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee,
0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa,
0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,
0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05,
0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039,
0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391};
/*
* Padding used to make the size (in bits) of the input congruent to 448 mod 512
*/
static uint8_t PADDING[] = {0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
/*
* Bit-manipulation functions defined by the MD5 algorithm
*/
#define F(X, Y, Z) ((X & Y) | (~X & Z))
#define G(X, Y, Z) ((X & Z) | (Y & ~Z))
#define H(X, Y, Z) (X ^ Y ^ Z)
#define I(X, Y, Z) (Y ^ (X | ~Z))
/*
* Rotates a 32-bit word left by n bits
*/
uint32_t rotateLeft(uint32_t x, uint32_t n){
return (x << n) | (x >> (32 - n));
}
/*
* Initialize a context
*/
void md5Init(MD5Context *ctx){
ctx->size = (uint64_t)0;
ctx->buffer[0] = (uint32_t)A;
ctx->buffer[1] = (uint32_t)B;
ctx->buffer[2] = (uint32_t)C;
ctx->buffer[3] = (uint32_t)D;
}
/*
* Add some amount of input to the context
*
* If the input fills out a block of 512 bits, apply the algorithm (md5Step)
* and save the result in the buffer. Also updates the overall size.
*/
void md5Update(MD5Context *ctx, uint8_t *input_buffer, size_t input_len){
uint32_t input[16];
unsigned int offset = ctx->size % 64;
ctx->size += (uint64_t)input_len;
// Copy each byte in input_buffer into the next space in our context input
for(unsigned int i = 0; i < input_len; ++i){
ctx->input[offset++] = (uint8_t)*(input_buffer + i);
// If we've filled our context input, copy it into our local array input
// then reset the offset to 0 and fill in a new buffer.
// Every time we fill out a chunk, we run it through the algorithm
// to enable some back and forth between cpu and i/o
if(offset % 64 == 0){
for(unsigned int j = 0; j < 16; ++j){
// Convert to little-endian
// The local variable `input` our 512-bit chunk separated into 32-bit words
// we can use in calculations
input[j] = (uint32_t)(ctx->input[(j * 4) + 3]) << 24 |
(uint32_t)(ctx->input[(j * 4) + 2]) << 16 |
(uint32_t)(ctx->input[(j * 4) + 1]) << 8 |
(uint32_t)(ctx->input[(j * 4)]);
}
md5Step(ctx->buffer, input);
offset = 0;
}
}
}
/*
* Pad the current input to get to 448 bytes, append the size in bits to the very end,
* and save the result of the final iteration into digest.
*/
void md5Finalize(MD5Context *ctx){
uint32_t input[16];
unsigned int offset = ctx->size % 64;
unsigned int padding_length = offset < 56 ? 56 - offset : (56 + 64) - offset;
// Fill in the padding and undo the changes to size that resulted from the update
md5Update(ctx, PADDING, padding_length);
ctx->size -= (uint64_t)padding_length;
// Do a final update (internal to this function)
// Last two 32-bit words are the two halves of the size (converted from bytes to bits)
for(unsigned int j = 0; j < 14; ++j){
input[j] = (uint32_t)(ctx->input[(j * 4) + 3]) << 24 |
(uint32_t)(ctx->input[(j * 4) + 2]) << 16 |
(uint32_t)(ctx->input[(j * 4) + 1]) << 8 |
(uint32_t)(ctx->input[(j * 4)]);
}
input[14] = (uint32_t)(ctx->size * 8);
input[15] = (uint32_t)((ctx->size * 8) >> 32);
md5Step(ctx->buffer, input);
// Move the result into digest (convert from little-endian)
for(unsigned int i = 0; i < 4; ++i){
ctx->digest[(i * 4) + 0] = (uint8_t)((ctx->buffer[i] & 0x000000FF));
ctx->digest[(i * 4) + 1] = (uint8_t)((ctx->buffer[i] & 0x0000FF00) >> 8);
ctx->digest[(i * 4) + 2] = (uint8_t)((ctx->buffer[i] & 0x00FF0000) >> 16);
ctx->digest[(i * 4) + 3] = (uint8_t)((ctx->buffer[i] & 0xFF000000) >> 24);
}
}
/*
* Step on 512 bits of input with the main MD5 algorithm.
*/
void md5Step(uint32_t *buffer, uint32_t *input){
uint32_t AA = buffer[0];
uint32_t BB = buffer[1];
uint32_t CC = buffer[2];
uint32_t DD = buffer[3];
uint32_t E;
unsigned int j;
for(unsigned int i = 0; i < 64; ++i){
switch(i / 16){
case 0:
E = F(BB, CC, DD);
j = i;
break;
case 1:
E = G(BB, CC, DD);
j = ((i * 5) + 1) % 16;
break;
case 2:
E = H(BB, CC, DD);
j = ((i * 3) + 5) % 16;
break;
default:
E = I(BB, CC, DD);
j = (i * 7) % 16;
break;
}
uint32_t temp = DD;
DD = CC;
CC = BB;
BB = BB + rotateLeft(AA + E + K[i] + input[j], S[i]);
AA = temp;
}
buffer[0] += AA;
buffer[1] += BB;
buffer[2] += CC;
buffer[3] += DD;
}
/*
* Functions that run the algorithm on the provided input and put the digest into result.
* result should be able to store 16 bytes.
*/
void md5String(char *input, uint8_t *result){
MD5Context ctx;
md5Init(&ctx);
md5Update(&ctx, (uint8_t *)input, strlen(input));
md5Finalize(&ctx);
memcpy(result, ctx.digest, 16);
}
void md5File(FILE *file, uint8_t *result){
void *input_buffer = malloc(1024);
size_t input_size = 0;
MD5Context ctx;
md5Init(&ctx);
while((input_size = fread(input_buffer, 1, 1024, file)) > 0){
md5Update(&ctx, (uint8_t *)input_buffer, input_size);
}
md5Finalize(&ctx);
free(input_buffer);
memcpy(result, ctx.digest, 16);
}

View File

@@ -0,0 +1,28 @@
/* Src: https://github.com/Zunawe/md5-c, commit: f3529b6
* License: Unlicense */
#pragma once
#ifndef MD5_H
#define MD5_H
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
typedef struct{
uint64_t size; // Size of input in bytes
uint32_t buffer[4]; // Current accumulation of hash
uint8_t input[64]; // Input to be used in the next step
uint8_t digest[16]; // Result of algorithm
}MD5Context;
void md5Init(MD5Context *ctx);
void md5Update(MD5Context *ctx, uint8_t *input, size_t input_len);
void md5Finalize(MD5Context *ctx);
void md5Step(uint32_t *buffer, uint32_t *input);
void md5String(char *input, uint8_t *result);
void md5File(FILE *file, uint8_t *result);
#endif // MD5_H

View File

@@ -36,6 +36,7 @@ extern "C" {
#include "MainFlowControl.h"
#include "server_help.h"
#include "md5.h"
#ifdef ENABLE_MQTT
#include "interface_mqtt.h"
#endif //ENABLE_MQTT
@@ -61,7 +62,7 @@ struct file_server_data {
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);
@@ -610,6 +611,8 @@ static esp_err_t upload_post_handler(httpd_req_t *req)
FILE *fd = NULL;
struct stat file_stat;
ESP_LOGI(TAG, "uri: %s", req->uri);
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
/* Skip leading "/upload" from URI to get filename */
@@ -711,43 +714,76 @@ static esp_err_t upload_post_handler(httpd_req_t *req)
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "File saved: " + string(filename));
ESP_LOGI(TAG, "File reception completed");
std::string directory = std::string(filepath);
size_t zw = directory.find("/");
size_t found = zw;
while (zw != std::string::npos)
{
zw = directory.find("/", found+1);
if (zw != std::string::npos)
found = zw;
}
string s = req->uri;
if (isInString(s, "?md5")) {
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Calculate and return MD5 sum...");
fd = fopen(filepath, "r");
if (!fd) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to open file for reading: " + string(filepath));
/* Respond with 500 Internal Server Error */
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to open file for reading");
return ESP_FAIL;
}
int start_fn = strlen(((struct file_server_data *)req->user_ctx)->base_path);
ESP_LOGD(TAG, "Directory: %s, start_fn: %d, found: %d", directory.c_str(), start_fn, found);
directory = directory.substr(start_fn, found - start_fn + 1);
directory = "/fileserver" + directory;
// ESP_LOGD(TAG, "Directory danach 2: %s", directory.c_str());
uint8_t result[16];
string md5hex = "";
string response = "{\"md5\":";
char hex[3];
/* Redirect onto root to see the updated file list */
if (strcmp(filename, "/config/config.ini") == 0 ||
strcmp(filename, "/config/ref0.jpg") == 0 ||
strcmp(filename, "/config/ref0_org.jpg") == 0 ||
strcmp(filename, "/config/ref1.jpg") == 0 ||
strcmp(filename, "/config/ref1_org.jpg") == 0 ||
strcmp(filename, "/config/reference.jpg") == 0 ||
strcmp(filename, "/img_tmp/ref0.jpg") == 0 ||
strcmp(filename, "/img_tmp/ref0_org.jpg") == 0 ||
strcmp(filename, "/img_tmp/ref1.jpg") == 0 ||
strcmp(filename, "/img_tmp/ref1_org.jpg") == 0 ||
strcmp(filename, "/img_tmp/reference.jpg") == 0 )
{
httpd_resp_set_status(req, HTTPD_200); // Avoid reloading of folder content
}
else {
httpd_resp_set_status(req, "303 See Other"); // Reload folder content after upload
md5File(fd, result);
fclose(fd);
for (int i = 0; i < sizeof(result); i++) {
snprintf(hex, sizeof(hex), "%02x", result[i]);
md5hex.append(hex);
}
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "MD5 of " + string(filepath) + ": " + md5hex);
response.append("\"" + md5hex + "\"");
response.append("}");
httpd_resp_sendstr(req, response.c_str());
}
else { // Return file server page
std::string directory = std::string(filepath);
size_t zw = directory.find("/");
size_t found = zw;
while (zw != std::string::npos)
{
zw = directory.find("/", found+1);
if (zw != std::string::npos)
found = zw;
}
httpd_resp_set_hdr(req, "Location", directory.c_str());
httpd_resp_sendstr(req, "File uploaded successfully");
int start_fn = strlen(((struct file_server_data *)req->user_ctx)->base_path);
ESP_LOGD(TAG, "Directory: %s, start_fn: %d, found: %d", directory.c_str(), start_fn, found);
directory = directory.substr(start_fn, found - start_fn + 1);
directory = "/fileserver" + directory;
// ESP_LOGD(TAG, "Directory danach 2: %s", directory.c_str());
/* Redirect onto root to see the updated file list */
if (strcmp(filename, "/config/config.ini") == 0 ||
strcmp(filename, "/config/ref0.jpg") == 0 ||
strcmp(filename, "/config/ref0_org.jpg") == 0 ||
strcmp(filename, "/config/ref1.jpg") == 0 ||
strcmp(filename, "/config/ref1_org.jpg") == 0 ||
strcmp(filename, "/config/reference.jpg") == 0 ||
strcmp(filename, "/img_tmp/ref0.jpg") == 0 ||
strcmp(filename, "/img_tmp/ref0_org.jpg") == 0 ||
strcmp(filename, "/img_tmp/ref1.jpg") == 0 ||
strcmp(filename, "/img_tmp/ref1_org.jpg") == 0 ||
strcmp(filename, "/img_tmp/reference.jpg") == 0 )
{
httpd_resp_set_status(req, HTTPD_200); // Avoid reloading of folder content
}
else {
httpd_resp_set_status(req, "303 See Other"); // Reload folder content after upload
}
httpd_resp_set_hdr(req, "Location", directory.c_str());
httpd_resp_sendstr(req, "File uploaded successfully");
}
return ESP_OK;
}
@@ -911,7 +947,7 @@ void delete_all_in_directory(std::string _directory)
closedir(dir);
}
std::string unzip_new(std::string _in_zip_file, std::string _target_zip, std::string _target_bin, std::string _main, bool _initial_setup)
std::string unzip_new(std::string _in_zip_file, std::string _html_tmp, std::string _html_final, std::string _target_bin, std::string _main, bool _initial_setup)
{
int i, sort_iter;
mz_bool status;
@@ -993,10 +1029,15 @@ std::string unzip_new(std::string _in_zip_file, std::string _target_zip, std::st
}
else
{
zw = _target_zip + zw;
zw = _html_tmp + zw;
}
}
// files in the html folder shall be redirected to the temporary html folder
if (zw.find(_html_final) == 0) {
FindReplace(zw, _html_final, _html_tmp);
}
string filename_zw = zw + SUFFIX_ZW;

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

@@ -76,20 +76,35 @@ void task_do_Update_ZIP(void *pvParameter)
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "File: " + _file_name_update + " Filetype: " + filetype);
if (filetype == "ZIP")
{
std::string in, out, outbin, zw, retfirmware;
std::string in, outHtml, outHtmlTmp, outHtmlOld, outbin, zw, retfirmware;
out = "/sdcard/html";
outHtml = "/sdcard/html";
outHtmlTmp = "/sdcard/html_tmp";
outHtmlOld = "/sdcard/html_old";
outbin = "/sdcard/firmware";
retfirmware = unzip_new(_file_name_update, out+"/", outbin+"/", "/sdcard/", initial_setup);
/* Remove the old and tmp html folder in case they still exist */
removeFolder(outHtmlTmp.c_str(), TAG);
removeFolder(outHtmlOld.c_str(), TAG);
/* Extract the ZIP file. The content of the html folder gets extracted to the temporar folder html-temp. */
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Extracting ZIP file " + _file_name_update + "...");
retfirmware = unzip_new(_file_name_update, outHtmlTmp+"/", outHtml+"/", outbin+"/", "/sdcard/", initial_setup);
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Files unzipped.");
/* ZIP file got extracted, replace the old html folder with the new one */
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Renaming folder " + outHtml + " to " + outHtmlOld + "...");
RenameFolder(outHtml, outHtmlOld);
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Renaming folder " + outHtmlTmp + " to " + outHtml + "...");
RenameFolder(outHtmlTmp, outHtml);
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Deleting folder " + outHtmlOld + "...");
removeFolder(outHtmlOld.c_str(), TAG);
if (retfirmware.length() > 0)
{
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Found firmware.bin");
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Found firmware.bin");
ota_update_task(retfirmware);
}
@@ -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

@@ -230,6 +230,10 @@ void ClassFlowControll::setAutoStartInterval(long &_interval)
_interval = AutoInterval * 60 * 1000; // AutoInterval: minutes -> ms
}
void ClassFlowControll::setSleepWhileIdle(bool& _sleepwhileidle){
_sleepwhileidle = SleepWhileIdle;
}
ClassFlow* ClassFlowControll::CreateClassFlow(std::string _type)
{
ClassFlow* cfc = NULL;
@@ -575,6 +579,10 @@ bool ClassFlowControll::ReadParameter(FILE* pfile, string& aktparamgraph)
}
}
if ((toUpper(splitted[0]) == "SLEEPWHILEIDLE") && (splitted.size() > 1)) {
SleepWhileIdle = alphanumericToBoolean(splitted[1]);
}
if ((toUpper(splitted[0]) == "DATALOGACTIVE") && (splitted.size() > 1)) {
LogFile.SetDataLogToSD(alphanumericToBoolean(splitted[1]));
}

View File

@@ -37,6 +37,7 @@ protected:
bool AutoStart;
float AutoInterval;
bool SleepWhileIdle;
void SetInitialParameter(void);
std::string aktstatusWithTime;
std::string aktstatus;
@@ -72,6 +73,7 @@ public:
bool getIsAutoStart();
void setAutoStartInterval(long &_interval);
void setSleepWhileIdle(bool& _sleepwhileidle);
std::string* getActStatusWithTime();
std::string* getActStatus();

View File

@@ -31,7 +31,6 @@ enum t_RateType {
RateChange // time difference is considered and a normalized rate is used for comparison with NumberPost.maxRate
};
/**
* Holds all properties and settings of a sequence. A sequence is a set of digit and/or analog ROIs that are combined to
* provide one meter reading (value).
@@ -45,6 +44,7 @@ struct NumberPost {
int ChangeRateThreshold; // threshold parameter for negative rate detection
bool PreValueOkay; // previousValueValid; indicates that the reading of the previous round has no errors
bool AllowNegativeRates; // allowNegativeRate; defines if the consistency checks allow negative rates between consecutive meter readings.
bool IgnoreLeadingNaN;
bool checkDigitIncreaseConsistency; // extendedConsistencyCheck; performs an additional consistency check to avoid wrong readings
time_t timeStampLastValue; // Timestamp for the last read value; is used for the log
time_t timeStampLastPreValue; // Timestamp for the last PreValue set; is used for useMaxRateValue
@@ -66,7 +66,7 @@ struct NumberPost {
float AnalogToDigitTransitionStart; // AnalogToDigitTransitionStartValue; FIXME: need a better description; When is the digit > x.1, i.e. when does it start to tilt?
int Nachkomma; // decimalPlaces; usually defined by the number of analog ROIs; affected by DecimalShift
string DomoticzIdx; // Domoticz counter Idx
string DomoticzIdx; // Domoticz counter Idx
string FieldV1; // influxdbFieldName_v1; Name of the Field in InfluxDBv1
string MeasurementV1; // influxdbMeasurementName_v1; Name of the Measurement in InfluxDBv1
@@ -83,4 +83,3 @@ struct NumberPost {
};
#endif

View File

@@ -156,7 +156,7 @@ bool ClassFlowMQTT::ReadParameter(FILE* pfile, string& aktparamgraph)
mqttServer_setMeterType("water", "L", "h", "L/h");
}
else if (toUpper(splitted[1]) == "WATER_FT3") {
mqttServer_setMeterType("water", "ft³", "m", "ft³/m"); // m = Minutes
mqttServer_setMeterType("water", "ft³", "min", "ft³/min"); // min = Minutes
}
else if (toUpper(splitted[1]) == "WATER_GAL") {
mqttServer_setMeterType("water", "gal", "h", "gal/h");
@@ -165,7 +165,7 @@ bool ClassFlowMQTT::ReadParameter(FILE* pfile, string& aktparamgraph)
mqttServer_setMeterType("gas", "", "h", "m³/h");
}
else if (toUpper(splitted[1]) == "GAS_FT3") {
mqttServer_setMeterType("gas", "ft³", "m", "ft³/m"); // m = Minutes
mqttServer_setMeterType("gas", "ft³", "min", "ft³/min"); // min = Minutes
}
else if (toUpper(splitted[1]) == "ENERGY_WH") {
mqttServer_setMeterType("energy", "Wh", "h", "W");
@@ -180,13 +180,13 @@ bool ClassFlowMQTT::ReadParameter(FILE* pfile, string& aktparamgraph)
mqttServer_setMeterType("energy", "GJ", "h", "GJ/h");
}
else if (toUpper(splitted[1]) == "TEMPERATURE_C") {
mqttServer_setMeterType("temperature", "°C", "m", "°C/m"); // m = Minutes
mqttServer_setMeterType("temperature", "°C", "min", "°C/min"); // min = Minutes
}
else if (toUpper(splitted[1]) == "TEMPERATURE_F") {
mqttServer_setMeterType("temperature", "°F", "m", "°F/m"); // m = Minutes
mqttServer_setMeterType("temperature", "°F", "min", "°F/min"); // min = Minutes
}
else if (toUpper(splitted[1]) == "TEMPERATURE_K") {
mqttServer_setMeterType("temperature", "K", "m", "K/m"); // m = Minutes
mqttServer_setMeterType("temperature", "K", "min", "K/m"); // min = Minutes
}
}

View File

@@ -320,7 +320,6 @@ ClassFlowPostProcessing::ClassFlowPostProcessing(std::vector<ClassFlow*>* lfc, C
ListFlowControll = lfc;
flowTakeImage = NULL;
UpdatePreValueINI = false;
IgnoreLeadingNaN = false;
flowAnalog = _analog;
flowDigit = _digit;
@@ -431,6 +430,27 @@ void ClassFlowPostProcessing::handleAllowNegativeRate(string _decsep, string _va
}
}
void ClassFlowPostProcessing::handleIgnoreLeadingNaN(string _decsep, string _value) {
string _digit, _decpos;
int _pospunkt = _decsep.find_first_of(".");
if (_pospunkt > -1) {
_digit = _decsep.substr(0, _pospunkt);
}
else {
_digit = "default";
}
for (int j = 0; j < NUMBERS.size(); ++j) {
bool _zwdc = alphanumericToBoolean(_value);
// Set to default first (if nothing else is set)
if ((_digit == "default") || (NUMBERS[j]->name == _digit)) {
NUMBERS[j]->IgnoreLeadingNaN = _zwdc;
}
}
}
void ClassFlowPostProcessing::handleMaxRateType(string _decsep, string _value) {
string _digit, _decpos;
int _pospunkt = _decsep.find_first_of(".");
@@ -509,7 +529,7 @@ void ClassFlowPostProcessing::handleChangeRateThreshold(string _decsep, string _
}
}
}
/*
void ClassFlowPostProcessing::handlecheckDigitIncreaseConsistency(std::string _decsep, std::string _value)
{
std::string _digit;
@@ -532,7 +552,7 @@ void ClassFlowPostProcessing::handlecheckDigitIncreaseConsistency(std::string _d
}
}
}
*/
bool ClassFlowPostProcessing::ReadParameter(FILE* pfile, string& aktparamgraph) {
std::vector<string> splitted;
int _n;
@@ -585,12 +605,7 @@ bool ClassFlowPostProcessing::ReadParameter(FILE* pfile, string& aktparamgraph)
}
if ((toUpper(_param) == "CHECKDIGITINCREASECONSISTENCY") && (splitted.size() > 1)) {
// handlecheckDigitIncreaseConsistency(splitted[0], splitted[1]);
if (alphanumericToBoolean(splitted[1])) {
for (_n = 0; _n < NUMBERS.size(); ++_n) {
NUMBERS[_n]->checkDigitIncreaseConsistency = true;
}
}
handlecheckDigitIncreaseConsistency(splitted[0], splitted[1]);
}
if ((toUpper(_param) == "ALLOWNEGATIVERATES") && (splitted.size() > 1)) {
@@ -602,7 +617,7 @@ bool ClassFlowPostProcessing::ReadParameter(FILE* pfile, string& aktparamgraph)
}
if ((toUpper(_param) == "IGNORELEADINGNAN") && (splitted.size() > 1)) {
IgnoreLeadingNaN = alphanumericToBoolean(splitted[1]);
handleIgnoreLeadingNaN(splitted[0], splitted[1]);
}
if ((toUpper(_param) == "PREVALUEAGESTARTUP") && (splitted.size() > 1)) {
@@ -670,6 +685,7 @@ void ClassFlowPostProcessing::InitNUMBERS() {
_number->FlowRateAct = 0; // m3 / min
_number->PreValueOkay = false;
_number->AllowNegativeRates = false;
_number->IgnoreLeadingNaN = false;
_number->MaxRateValue = 0.1;
_number->MaxRateType = AbsoluteChange;
_number->useMaxRateValue = false;
@@ -821,7 +837,7 @@ bool ClassFlowPostProcessing::doFlow(string zwtime) {
ESP_LOGD(TAG, "After ShiftDecimal: ReturnRaw %s", NUMBERS[j]->ReturnRawValue.c_str());
#endif
if (IgnoreLeadingNaN) {
if (NUMBERS[j]->IgnoreLeadingNaN) {
while ((NUMBERS[j]->ReturnRawValue.length() > 1) && (NUMBERS[j]->ReturnRawValue[0] == 'N')) {
NUMBERS[j]->ReturnRawValue.erase(0, 1);
}
@@ -868,12 +884,7 @@ bool ClassFlowPostProcessing::doFlow(string zwtime) {
if (NUMBERS[j]->checkDigitIncreaseConsistency) {
if (flowDigit) {
if (flowDigit->getCNNType() != Digit) {
ESP_LOGD(TAG, "checkDigitIncreaseConsistency = true - ignored due to wrong CNN-Type (not Digit Classification)");
}
else {
NUMBERS[j]->Value = checkDigitConsistency(NUMBERS[j]->Value, NUMBERS[j]->DecimalShift, NUMBERS[j]->analog_roi != NULL, NUMBERS[j]->PreValue);
}
NUMBERS[j]->Value = checkDigitConsistency(NUMBERS[j]->Value, NUMBERS[j]->DecimalShift, NUMBERS[j]->analog_roi != NULL, NUMBERS[j]->PreValue);
}
else {
#ifdef SERIAL_DEBUG
@@ -887,7 +898,7 @@ bool ClassFlowPostProcessing::doFlow(string zwtime) {
#endif
if (PreValueUse && NUMBERS[j]->PreValueOkay) {
if (NUMBERS[j]->Nachkomma > 0) {
if ((NUMBERS[j]->Nachkomma > 0) && (NUMBERS[j]->ChangeRateThreshold > 0)) {
double _difference1 = (NUMBERS[j]->PreValue - (NUMBERS[j]->ChangeRateThreshold / pow(10, NUMBERS[j]->Nachkomma)));
double _difference2 = (NUMBERS[j]->PreValue + (NUMBERS[j]->ChangeRateThreshold / pow(10, NUMBERS[j]->Nachkomma)));

View File

@@ -10,7 +10,6 @@
#include <string>
class ClassFlowPostProcessing :
public ClassFlow
{
@@ -19,8 +18,7 @@ protected:
int PreValueAgeStartup;
bool ErrorMessage;
bool IgnoreLeadingNaN; // SPECIAL CASE for User Gustl ???
ClassFlowCNNGeneral* flowAnalog;
ClassFlowCNNGeneral* flowDigit;
@@ -35,15 +33,16 @@ protected:
float checkDigitConsistency(double input, int _decilamshift, bool _isanalog, double _preValue);
void InitNUMBERS();
void handleDecimalSeparator(string _decsep, string _value);
void handleMaxRateValue(string _decsep, string _value);
void handleDecimalExtendedResolution(string _decsep, string _value);
void handleMaxRateType(string _decsep, string _value);
void handleAnalogToDigitTransitionStart(string _decsep, string _value);
void handleAllowNegativeRate(string _decsep, string _value);
void handleIgnoreLeadingNaN(string _decsep, string _value);
void handleChangeRateThreshold(string _decsep, string _value);
std::string GetStringReadouts(general);
void handlecheckDigitIncreaseConsistency(std::string _decsep, std::string _value);
void WriteDataLog(int _index);
@@ -75,5 +74,4 @@ public:
string name(){return "ClassFlowPostProcessing";};
};
#endif //CLASSFFLOWPOSTPROCESSING_H

View File

@@ -43,6 +43,7 @@ bool bTaskAutoFlowCreated = false;
bool flowisrunning = false;
long auto_interval = 0;
bool sleep_while_idle = false;
bool autostartIsEnabled = false;
int countRounds = 0;
@@ -1659,6 +1660,7 @@ void task_autodoFlow(void *pvParameter)
doInit();
flowctrl.setAutoStartInterval(auto_interval);
flowctrl.setSleepWhileIdle(sleep_while_idle);
autostartIsEnabled = flowctrl.getIsAutoStart();
if (isSetupModusActive())
@@ -1739,9 +1741,20 @@ void task_autodoFlow(void *pvParameter)
if (auto_interval > fr_delta_ms)
{
const TickType_t xDelay = (auto_interval - fr_delta_ms) / portTICK_PERIOD_MS;
ESP_LOGD(TAG, "Autoflow: sleep for: %ldms", (long)xDelay);
vTaskDelay(xDelay);
if(sleep_while_idle){
vTaskDelay(10000 / portTICK_PERIOD_MS);//A little more time so the user can finish config
fr_delta_ms = (esp_timer_get_time() - fr_start) / 1000;
if (auto_interval > fr_delta_ms){
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Deep sleep for " + std::to_string(auto_interval - fr_delta_ms));
esp_sleep_enable_timer_wakeup((auto_interval - fr_delta_ms) * 1000); // Time in microseconds
esp_deep_sleep_start();
}
}else{
const TickType_t xDelay = (auto_interval - fr_delta_ms) / portTICK_PERIOD_MS;
ESP_LOGD(TAG, "Autoflow: sleep for: %ldms", (long)xDelay);
vTaskDelay(xDelay);
}
}
}

View File

@@ -365,19 +365,35 @@ size_t findDelimiterPos(string input, string delimiter)
bool RenameFile(string from, string to)
{
// ESP_LOGI(logTag, "Deleting file: %s", fn.c_str());
/* Delete file */
// ESP_LOGI(logTag, "Renaming File: %s", from.c_str());
FILE *fpSourceFile = fopen(from.c_str(), "rb");
// Sourcefile existiert nicht sonst gibt es einen Fehler beim Kopierversuch!
// Sourcefile does not exist otherwise there is a mistake when renaming!
if (!fpSourceFile)
{
ESP_LOGE(TAG, "DeleteFile: File %s existiert nicht!", from.c_str());
ESP_LOGE(TAG, "RenameFile: File %s does not exist!", from.c_str());
return false;
}
fclose(fpSourceFile);
rename(from.c_str(), to.c_str());
return true;
}
bool RenameFolder(string from, string to)
{
// ESP_LOGI(logTag, "Renaming Folder: %s", from.c_str());
DIR *fpSourceFolder = opendir(from.c_str());
// Sourcefolder does not exist otherwise there is a mistake when renaming!
if (!fpSourceFolder)
{
ESP_LOGE(TAG, "RenameFolder: Folder %s does not exist!", from.c_str());
return false;
}
closedir(fpSourceFolder);
rename(from.c_str(), to.c_str());
return true;
@@ -387,7 +403,7 @@ bool FileExists(string filename)
{
FILE *fpSourceFile = fopen(filename.c_str(), "rb");
// Sourcefile existiert nicht sonst gibt es einen Fehler beim Kopierversuch!
// Sourcefile does not exist
if (!fpSourceFile)
{
return false;
@@ -398,22 +414,36 @@ bool FileExists(string filename)
return true;
}
bool DeleteFile(string fn)
bool FolderExists(string foldername)
{
// ESP_LOGI(logTag, "Deleting file: %s", fn.c_str());
/* Delete file */
FILE *fpSourceFile = fopen(fn.c_str(), "rb");
DIR *fpSourceFolder = opendir(foldername.c_str());
// Sourcefile existiert nicht sonst gibt es einen Fehler beim Kopierversuch!
// Sourcefolder does not exist
if (!fpSourceFolder)
{
return false;
}
closedir(fpSourceFolder);
return true;
}
bool DeleteFile(string filename)
{
// ESP_LOGI(logTag, "Deleting file: %s", filename.c_str());
/* Delete file */
FILE *fpSourceFile = fopen(filename.c_str(), "rb");
// Sourcefile does not exist otherwise there is a mistake in copying!
if (!fpSourceFile)
{
ESP_LOGD(TAG, "DeleteFile: File %s existiert nicht!", fn.c_str());
ESP_LOGD(TAG, "DeleteFile: File %s existiert nicht!", filename.c_str());
return false;
}
fclose(fpSourceFile);
unlink(fn.c_str());
unlink(filename.c_str());
return true;
}
@@ -614,11 +644,19 @@ string toLower(string in)
}
// CPU Temp
#if defined(BOARD_ESP32_S3_ALEKSEI) // ESP32s3 hat die Funktion nicht
float temperatureRead()
{
return 0.0;
}
#else
extern "C" uint8_t temprature_sens_read();
float temperatureRead()
{
return (temprature_sens_read() - 32) / 1.8;
}
#endif
time_t addDays(time_t startTime, int days)
{

View File

@@ -16,10 +16,12 @@ std::size_t file_size(const std::string& file_name);
void FindReplace(std::string& line, std::string& oldString, std::string& newString);
bool CopyFile(string input, string output);
bool DeleteFile(string fn);
bool DeleteFile(string filename);
bool RenameFile(string from, string to);
bool RenameFolder(string from, string to);
bool MakeDir(std::string _what);
bool FileExists(string filename);
bool FolderExists(string foldername);
string RundeOutput(double _in, int _anzNachkomma);

View File

@@ -112,8 +112,8 @@ bool SDCardCheckFolderFilePresence()
}
/* check if file exists: wlan.ini */
if (stat("/sdcard/wlan.ini", &sb) != 0) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Folder/file check: File /wlan.ini not found");
if (stat("/sdcard/wlan.ini", &sb) != 0 and stat("/sdcard/lan.ini", &sb) != 0) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Folder/file check: File /wlan.ini and /lan.ini not found");
bRetval = false;
}

View File

@@ -195,9 +195,12 @@ bool MQTThomeassistantDiscovery(int qos) {
/* If "Allow neg. rate" is true, use "measurement" instead of "total_increasing" for the State Class, see https://github.com/jomjol/AI-on-the-edge-device/issues/3331 */
std::string value_state_class = "total_increasing";
if ((*NUMBERS)[i]->AllowNegativeRates) {
if (meterType == "temperature") {
value_state_class = "measurement";
}
else if ((*NUMBERS)[i]->AllowNegativeRates) {
value_state_class = "total";
}
/* Energy meters need a different Device Class, see https://github.com/jomjol/AI-on-the-edge-device/issues/3333 */
std::string rate_device_class = "volume_flow_rate";
@@ -205,17 +208,17 @@ bool MQTThomeassistantDiscovery(int qos) {
rate_device_class = "power";
}
// Group | Field | User Friendly Name | Icon | Unit | Device Class | State Class | Entity Category
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic(group, "value", "Value", "gauge", valueUnit, meterType, value_state_class, "", qos); // State Class = "total_increasing" if <NUMBERS>.AllowNegativeRates = false, else use "measurement"
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic(group, "raw", "Raw Value", "raw", valueUnit, meterType, "measurement", "diagnostic", qos);
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic(group, "error", "Error", "alert-circle-outline", "", "", "", "diagnostic", qos);
// Group | Field | User Friendly Name | Icon | Unit | Device Class | State Class | Entity Category | QoS
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic(group, "value", "Value", "gauge", valueUnit, meterType, value_state_class, "", qos); // State Class = "total_increasing" if <NUMBERS>.AllowNegativeRates = false, "measurement" in case of a thermometer, else use "total".
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic(group, "raw", "Raw Value", "raw", valueUnit, meterType, value_state_class, "diagnostic", qos);
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic(group, "error", "Error", "alert-circle-outline", "", "", "", "diagnostic", qos);
/* Not announcing "rate" as it is better to use rate_per_time_unit resp. rate_per_digitization_round */
// allSendsSuccessed |= sendHomeAssistantDiscoveryTopic(group, "rate", "Rate (Unit/Minute)", "swap-vertical", "", "", "", ""); // Legacy, always Unit per Minute
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic(group, "rate_per_time_unit", "Rate (" + rateUnit + ")", "swap-vertical", rateUnit, rate_device_class, "measurement", "", qos);
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic(group, "rate_per_digitization_round", "Change since last Digitization round", "arrow-expand-vertical", valueUnit, "", "measurement", "", qos); // correctly the Unit is Unit/Interval!
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic(group, "timestamp", "Timestamp", "clock-time-eight-outline", "", "timestamp", "", "diagnostic", qos);
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic(group, "json", "JSON", "code-json", "", "", "", "diagnostic", qos);
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic(group, "problem", "Problem", "alert-outline", "", "problem", "", "", qos); // Special binary sensor which is based on error topic
// allSendsSuccessed |= sendHomeAssistantDiscoveryTopic(group, "rate", "Rate (Unit/Minute)", "swap-vertical", "", "", "", "", qos); // Legacy, always Unit per Minute
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic(group, "rate_per_time_unit", "Rate (" + rateUnit + ")", "swap-vertical", rateUnit, rate_device_class, "measurement", "", qos);
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic(group, "rate_per_digitization_round","Change since last Digitization round", "arrow-expand-vertical", valueUnit, "", "measurement", "", qos); // correctly the Unit is Unit/Interval!
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic(group, "timestamp", "Timestamp", "clock-time-eight-outline", "", "timestamp", "", "diagnostic", qos);
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic(group, "json", "JSON", "code-json", "", "", "", "diagnostic", qos);
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic(group, "problem", "Problem", "alert-outline", "", "problem", "", "", qos); // Special binary sensor which is based on error topic
}
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Successfully published all Homeassistant Discovery MQTT topics");

View File

@@ -55,7 +55,7 @@
static const char *TAG = "WIFI";
static bool APWithBetterRSSI = false;
static bool WIFIConnected = false;
bool WIFIConnected = false;
static int WIFIReconnectCnt = 0;
esp_netif_t *my_sta;

View File

@@ -14,6 +14,8 @@ std::string* getHostname();
bool getWIFIisConnected();
void WIFIDestroy();
extern bool WIFIConnected;
#if (defined WLAN_USE_MESH_ROAMING && defined WLAN_USE_MESH_ROAMING_ACTIVATE_CLIENT_TRIGGERED_QUERIES)
void wifiRoamingQuery(void);
#endif

View File

@@ -1,4 +1,6 @@
#include "openmetrics.h"
#include "functional"
#include "esp_log.h"
/**
* create a singe metric from the given input
@@ -10,10 +12,66 @@ std::string createMetric(const std::string &metricName, const std::string &help,
metricName + " " + value + "\n";
}
typedef struct sequence_metric {
const char *name;
const char *help;
const char *type;
std::function<std::string(NumberPost *number)> valueFunc;
} sequence_metric_t;
sequence_metric_t sequenceMetrics[4] = {
{ "flow_value", "current value of meter readout", "gauge", [](NumberPost *number)-> std::string {return number->ReturnValue;} },
{ "flow_raw_value", "current raw value of meter readout", "gauge", [](NumberPost *number)-> std::string {return number->ReturnRawValue;} },
{ "flow_pre_value", "previous value of meter readout", "gauge", [](NumberPost *number)-> std::string {return number->ReturnPreValue;} },
{ "flow_error", "Error message text != 'no error'", "gauge", [](NumberPost *number)-> std::string {return number->ErrorMessageText.compare("no error") == 0 ? "0" : "1";} },
};
std::string createSequenceMetrics(std::string prefix, const std::vector<NumberPost *> &numbers)
{
std::string result;
for (int i = 0; i<sizeof(sequenceMetrics)/sizeof(sequence_metric_t);i++)
{
std::string res;
for (const auto &number : numbers)
{
std::string value = sequenceMetrics[i].valueFunc(number);
if (value.find("N") != std::string::npos) {
value = "NaN";
}
ESP_LOGD("METRICS", "metric=%s, name=%s, value = %s ",sequenceMetrics[i].name,number->name.c_str(), value.c_str());
// only valid data is reported (https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#missing-data)
if (value.length() > 0)
{
auto label = number->name;
// except newline, double quote, and backslash (https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#abnf)
// to keep it simple, these characters are just removed from the label
replaceAll(label, "\\", "");
replaceAll(label, "\"", "");
replaceAll(label, "\n", "");
res += prefix + "_" + sequenceMetrics[i].name + "{sequence=\"" + label + "\"} " + value + "\n";
}
}
// prepend metadata if a valid metric was created
if (res.length() > 0)
{
res = "# HELP " + prefix + "_" + sequenceMetrics[i].name + " " + sequenceMetrics[i].help + "\n"
+ "# TYPE " + prefix + "_" + sequenceMetrics[i].name + " " + sequenceMetrics[i].type + "\n"
+ res;
}
result += res;
}
return result;
}
/**
* Generate the MetricFamily from all available sequences
* @returns the string containing the text wire format of the MetricFamily
**/
/*
std::string createSequenceMetrics(std::string prefix, const std::vector<NumberPost *> &numbers)
{
std::string res;
@@ -41,3 +99,4 @@ std::string createSequenceMetrics(std::string prefix, const std::vector<NumberPo
}
return res;
}
*/

View File

@@ -11,5 +11,5 @@ dependencies:
type: idf
version: 5.3.1
manifest_hash: f88c9e5c2d75a9d5d6968fc67a90ef0cd7146dd6a3905a79c4dfcfc3b4fe6731
target: esp32
target: esp32s3
version: 1.0.0

View File

@@ -79,6 +79,8 @@
//ClassFlowControll + Main + SoftAP
#define WLAN_CONFIG_FILE "/sdcard/wlan.ini"
#define LAN_CONFIG_FILE "/sdcard/lan.ini"
//main
#define __SD_USE_ONE_LINE_MODE__
@@ -93,8 +95,8 @@
#define LOGFILE_LAST_PART_BYTES 80 * 1024 // 80 kBytes // Size of partial log file to return
#define SERVER_FILER_SCRATCH_BUFSIZE 4096
#define SERVER_HELPER_SCRATCH_BUFSIZE 4096
#define SERVER_FILER_SCRATCH_BUFSIZE 1024
#define SERVER_HELPER_SCRATCH_BUFSIZE 1024
#define SERVER_OTA_SCRATCH_BUFSIZE 1024
@@ -171,7 +173,7 @@
//#define WLAN_USE_MESH_ROAMING_ACTIVATE_CLIENT_TRIGGERED_QUERIES // Client can send query to AP requesting to roam (if RSSI lower than RSSI threshold)
/* WIFI roaming only client triggered by scanning the channels after each round (only if RSSI < RSSIThreshold) and trigger a disconnect to switch AP */
#define WLAN_USE_ROAMING_BY_SCANNING
// #define WLAN_USE_ROAMING_BY_SCANNING
//ClassFlowCNNGeneral
@@ -308,6 +310,63 @@
#define FLASH_GPIO GPIO_NUM_4 // PIN for flashlight LED
#define USE_PWM_LEDFLASH // if __LEDGLOBAL is defined, a global variable is used for LED control, otherwise locally and each time a new
#elif defined(BOARD_ESP32_S3_ALEKSEI) // Sonderversion für Aleksei mit ESP32s3 und Ethernet (PoE)
// HIGH=ENABLE
// ETH_EN activates power for the Ethernet
// PER_EN activates power for camera,leds,and SDcard, Battery measurement voltage divider
#define ETH_ENABLE GPIO_NUM_45
#define PER_ENABLE GPIO_NUM_46
// Ethernet (operated with SPI peripheral)
#define W5500_SPI_HOST SPI2_HOST
#define ETH_MOSI GPIO_NUM_1
#define ETH_MISO GPIO_NUM_14
#define ETH_CLK GPIO_NUM_21
#define ETH_CS GPIO_NUM_39
#define ETH_INT GPIO_NUM_38
// SD card (operated with SDMMC peripheral)
//-------------------------------------------------
#define GPIO_SDCARD_CLK GPIO_NUM_40
#define GPIO_SDCARD_CMD GPIO_NUM_42
#define GPIO_SDCARD_D0 GPIO_NUM_41
#ifndef __SD_USE_ONE_LINE_MODE__
#define GPIO_SDCARD_D1 GPIO_NUM_4
#define GPIO_SDCARD_D2 GPIO_NUM_12
#define GPIO_SDCARD_D3 GPIO_NUM_13
#else
#define GPIO_SDCARD_D1 GPIO_NUM_NC
#define GPIO_SDCARD_D2 GPIO_NUM_NC
#define GPIO_SDCARD_D3 GPIO_NUM_13
#endif
#define CAM_PIN_PWDN GPIO_NUM_NC
#define CAM_PIN_RESET GPIO_NUM_NC //software reset will be performed
#define CAM_PIN_XCLK GPIO_NUM_15
#define CAM_PIN_SIOD GPIO_NUM_4
#define CAM_PIN_SIOC GPIO_NUM_5
#define CAM_PIN_D7 GPIO_NUM_16
#define CAM_PIN_D6 GPIO_NUM_17
#define CAM_PIN_D5 GPIO_NUM_18
#define CAM_PIN_D4 GPIO_NUM_47
#define CAM_PIN_D3 GPIO_NUM_10
#define CAM_PIN_D2 GPIO_NUM_8
#define CAM_PIN_D1 GPIO_NUM_9
#define CAM_PIN_D0 GPIO_NUM_11
#define CAM_PIN_VSYNC GPIO_NUM_6
#define CAM_PIN_HREF GPIO_NUM_7
#define CAM_PIN_PCLK GPIO_NUM_13
//Statusled + ClassControllCamera
#define BLINK_GPIO GPIO_NUM_48 // PIN for red board LED
//ClassControllCamera
#define FLASH_GPIO GPIO_NUM_48 // PIN for flashlight LED
#define USE_PWM_LEDFLASH // if __LEDGLOBAL is defined, a global variable is used for LED control, otherwise locally and each time a new
#else
#error "Board not selected"
#endif //Board PIN Map

View File

@@ -25,6 +25,9 @@
#include "connect_wlan.h"
#include "read_wlanini.h"
#include "connect_lan.h"
#include "read_lanini.h"
#include "server_main.h"
#include "MainFlowControl.h"
#include "server_file.h"
@@ -213,13 +216,28 @@ bool Init_NVS_SDCard()
extern "C" void app_main(void)
{
TickType_t xDelay;
#if defined (BOARD_ESP32_S3_ALEKSEI)
// gpio_pad_select_gpio(ETH_EN);
// gpio_set_direction(ETH_EN, GPIO_MODE_OUTPUT);
// gpio_set_level(ETH_EN, 0);
// PER_ENABLE activates power for camera,leds,and SDcard, Battery measurement voltage divider
gpio_pad_select_gpio(PER_ENABLE);
gpio_set_direction(PER_ENABLE, GPIO_MODE_OUTPUT);
gpio_set_level(PER_ENABLE, 1);
xDelay = 1000 / portTICK_PERIOD_MS;
ESP_LOGI(TAG, "BOARD_ESP32_S3_ALEKSEI - Switch on Power for camera, ... : sleep for: %ldms", (long) xDelay * CONFIG_FREERTOS_HZ/portTICK_PERIOD_MS);
vTaskDelay( xDelay );
#endif //ETH_EN &&
//#ifdef CONFIG_HEAP_TRACING_STANDALONE
#if defined HEAP_TRACING_MAIN_WIFI || defined HEAP_TRACING_MAIN_START
//register a buffer to record the memory trace
ESP_ERROR_CHECK( heap_trace_init_standalone(trace_record, NUM_RECORDS) );
#endif
TickType_t xDelay;
#ifdef DISABLE_BROWNOUT_DETECTOR
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector
@@ -254,6 +272,35 @@ extern "C" void app_main(void)
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "==================== Start ======================");
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "=================================================");
// SD card: basic R/W check
// ********************************************
int iSDCardStatus = SDCardCheckRW();
if (iSDCardStatus < 0) {
if (iSDCardStatus <= -1 && iSDCardStatus >= -2) { // write error
StatusLED(SDCARD_CHECK, 1, true);
}
else if (iSDCardStatus <= -3 && iSDCardStatus >= -5) { // read error
StatusLED(SDCARD_CHECK, 2, true);
}
else if (iSDCardStatus == -6) { // delete error
StatusLED(SDCARD_CHECK, 3, true);
}
setSystemStatusFlag(SYSTEM_STATUS_SDCARD_CHECK_BAD); // reduced web interface going to be loaded
}
// SD card: Create further mandatory directories (if not already existing)
// Correct creation of these folders will be checked with function "SDCardCheckFolderFilePresence"
// ********************************************
MakeDir("/sdcard/firmware"); // mandatory for OTA firmware update
MakeDir("/sdcard/img_tmp"); // mandatory for setting up alignment marks
MakeDir("/sdcard/demo"); // mandatory for demo mode
MakeDir("/sdcard/config/certs"); // mandatory for mqtt certificates
// Check for updates
// ********************************************
CheckOTAUpdate();
CheckUpdate();
// Init external PSRAM
// ********************************************
esp_err_t PSRAMStatus = esp_psram_init();
@@ -352,22 +399,6 @@ extern "C" void app_main(void)
}
}
// SD card: basic R/W check
// ********************************************
int iSDCardStatus = SDCardCheckRW();
if (iSDCardStatus < 0) {
if (iSDCardStatus <= -1 && iSDCardStatus >= -2) { // write error
StatusLED(SDCARD_CHECK, 1, true);
}
else if (iSDCardStatus <= -3 && iSDCardStatus >= -5) { // read error
StatusLED(SDCARD_CHECK, 2, true);
}
else if (iSDCardStatus == -6) { // delete error
StatusLED(SDCARD_CHECK, 3, true);
}
setSystemStatusFlag(SYSTEM_STATUS_SDCARD_CHECK_BAD); // reduced web interface going to be loaded
}
// Migrate parameter in config.ini to new naming (firmware 15.0 and newer)
// ********************************************
migrateConfiguration();
@@ -380,19 +411,6 @@ extern "C" void app_main(void)
// ********************************************
setCpuFrequency();
// SD card: Create further mandatory directories (if not already existing)
// Correct creation of these folders will be checked with function "SDCardCheckFolderFilePresence"
// ********************************************
MakeDir("/sdcard/firmware"); // mandatory for OTA firmware update
MakeDir("/sdcard/img_tmp"); // mandatory for setting up alignment marks
MakeDir("/sdcard/demo"); // mandatory for demo mode
MakeDir("/sdcard/config/certs"); // mandatory for mqtt certificates
// Check for updates
// ********************************************
CheckOTAUpdate();
CheckUpdate();
// Start SoftAP for initial remote setup
// Note: Start AP if no wlan.ini and/or config.ini available, e.g. SD card empty; function does not exit anymore until reboot
// ********************************************
@@ -448,26 +466,52 @@ extern "C" void app_main(void)
ESP_ERROR_CHECK( heap_trace_start(HEAP_TRACE_LEAKS) );
#endif
// Read WLAN parameter and start WIFI
// ********************************************
int iWLANStatus = LoadWlanFromFile(WLAN_CONFIG_FILE);
if (iWLANStatus == 0) {
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "WLAN config loaded, init WIFI...");
if (wifi_init_sta() != ESP_OK) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "WIFI init failed. Device init aborted!");
bool lanEnabled = false;
#if defined(BOARD_ESP32_S3_ALEKSEI)
int iLanStatus = LoadLanFromFile(LAN_CONFIG_FILE);
if (iLanStatus == 0) {
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "LAN config loaded, init Lan...");
if (lan_init() != ESP_OK) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "LAN init failed. Device init aborted!");
StatusLED(WLAN_INIT, 3, true);
return;
}
else {
lanEnabled = true;
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "LAN init completed successfully");
}
}
else if(iLanStatus == -1) { // lan.ini not available, potentially empty or content not readable
LogFile.WriteToFile(ESP_LOG_WARN, TAG, "lan.ini not found, proceeding to wlan.ini");
}
else {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Lan init failed. Unknown error!");
return; // No way to continue without reading the lan.ini
}
#endif
if(!lanEnabled){
init_basic_auth();
}
else if (iWLANStatus == -1) { // wlan.ini not available, potentially empty or content not readable
StatusLED(WLAN_INIT, 1, true);
return; // No way to continue without reading the wlan.ini
}
else if (iWLANStatus == -2) { // SSID or password not configured
StatusLED(WLAN_INIT, 2, true);
return; // No way to continue with empty SSID or password!
// Read WLAN parameter and start WIFI
// ********************************************
int iWLANStatus = LoadWlanFromFile(WLAN_CONFIG_FILE);
if (iWLANStatus == 0) {
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "WLAN config loaded, init WIFI...");
if (wifi_init_sta() != ESP_OK) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "WIFI init failed. Device init aborted!");
StatusLED(WLAN_INIT, 3, true);
return;
}
init_basic_auth();
}
else if (iWLANStatus == -1) { // wlan.ini not available, potentially empty or content not readable
StatusLED(WLAN_INIT, 1, true);
return; // No way to continue without reading the wlan.ini
}
else if (iWLANStatus == -2) { // SSID or password not configured
StatusLED(WLAN_INIT, 2, true);
return; // No way to continue with empty SSID or password!
}
}
xDelay = 2000 / portTICK_PERIOD_MS;
@@ -480,7 +524,6 @@ extern "C" void app_main(void)
{
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Manual Time Sync failed during startup" );
}
// Set log level for wifi component to WARN level (default: INFO; only relevant for serial console)
// ********************************************
esp_log_level_set("wifi", ESP_LOG_WARN);

View File

@@ -41,6 +41,7 @@
bool isConfigINI = false;
bool isWlanINI = false;
bool isLanINI = false;
static const char *TAG = "WIFI AP";
@@ -99,12 +100,13 @@ void wifi_init_softAP(void)
void SendHTTPResponse(httpd_req_t *req)
{
std::string message = "<h1>AI-on-the-edge - BASIC SETUP</h1><p>This is an access point with a minimal server to setup the minimum required files and information on the device and the SD-card. ";
message += "This mode is always started if one of the following files is missing: /wlan.ini or the /config/config.ini.<p>";
message += "The setup is done in 3 steps: 1. upload full inital configuration (sd-card content), 2. store WLAN access information, 3. reboot (and connect to WLANs)<p><p>";
message += "This mode is always started if neither /wlan.ini nor /lan.ini is preset or the /config/config.ini. is missing<p>";
message += "The setup is done in 3 steps: 1. upload full inital configuration (sd-card content), 2. store WLAN/LAN access information, 3. reboot (and connect to WLANs/LANs)<p><p>";
message += "Please follow the below instructions.<p>";
httpd_resp_send_chunk(req, message.c_str(), strlen(message.c_str()));
isWlanINI = FileExists(WLAN_CONFIG_FILE);
isLanINI = FileExists(LAN_CONFIG_FILE);
if (!isConfigINI)
{
@@ -129,33 +131,53 @@ void SendHTTPResponse(httpd_req_t *req)
httpd_resp_send_chunk(req, message.c_str(), strlen(message.c_str()));
return;
}
if (!isWlanINI)
if (!isWlanINI && !isLanINI)
{
message = "<h3>2. WLAN access credentials</h3><p>";
message = "<h3>2. WLAN access credentials</h3><p>";
message += "Interface: <select id=\"iface\" onchange=\"toggle()\">";
message += "<option value=\"wifi\" selected>Wi-Fi</option>";
message += "<option value=\"eth\">Ethernet</option></select><p>";
/* Wi-Fi-only fields wrapped in a <div id="wifi"> for easy hide/show */
message += "<div id=\"wifi\">";
message += "<table>";
message += "<tr><td>WLAN-SSID</td><td><input type=\"text\" name=\"ssid\" id=\"ssid\"></td><td>SSID of the WLAN</td></tr>";
message += "<tr><td>WLAN-Password</td><td><input type=\"text\" name=\"password\" id=\"password\"></td><td>ATTENTION: the password will not be encrypted during the sending.</td><tr>";
message += "</table><p>";
message += "<h4>ATTENTION:<h4>Be sure about the WLAN settings. They cannot be reset afterwards. If ssid or password is wrong, you need to take out the sd-card and manually change them in \"wlan.ini\"!<p>";
httpd_resp_send_chunk(req, message.c_str(), strlen(message.c_str()));
// message = "</tr><tr><td> Hostname</td><td><input type=\"text\" name=\"hostname\" id=\"hostname\"></td><td></td>";
// message += "</tr><tr><td>Fixed IP</td><td><input type=\"text\" name=\"ip\" id=\"ip\"></td><td>Leave emtpy if set by router (DHCP)</td></tr>";
// message += "<tr><td>Gateway</td><td><input type=\"text\" name=\"gateway\" id=\"gateway\"></td><td>Leave emtpy if set by router (DHCP)</td></tr>";
// message += "<tr><td>Netmask</td><td><input type=\"text\" name=\"netmask\" id=\"netmask\"></td><td>Leave emtpy if set by router (DHCP)</td>";
// message += "</tr><tr><td>DNS</td><td><input type=\"text\" name=\"dns\" id=\"dns\"></td><td>Leave emtpy if set by router (DHCP)</td></tr>";
// message += "<tr><td>RSSI Threshold</td><td><input type=\"number\" name=\"name\" id=\"threshold\" min=\"-100\" max=\"0\" step=\"1\" value = \"0\"></td><td>WLAN Mesh Parameter: Threshold for RSSI value to check for start switching access point in a mesh system (if actual RSSI is lower). Possible values: -100 to 0, 0 = disabled - Value will be transfered to wlan.ini at next startup)</td></tr>";
// httpd_resp_send_chunk(req, message.c_str(), strlen(message.c_str()));
message = "<button class=\"button\" type=\"button\" onclick=\"wr()\">Write wlan.ini</button>";
message += "<script language=\"JavaScript\">async function wr(){";
message += "api = \"/config?\"+\"ssid=\"+document.getElementById(\"ssid\").value+\"&pwd=\"+document.getElementById(\"password\").value;";
// message += "api = \"/config?\"+\"ssid=\"+document.getElementById(\"ssid\").value+\"&pwd=\"+document.getElementById(\"password\").value+\"&hn=\"+document.getElementById(\"hostname\").value+\"&ip=\"+document.getElementById(\"ip\").value+\"&gw=\"+document.getElementById(\"gateway\").value+\"&nm=\"+document.getElementById(\"netmask\").value+\"&dns=\"+document.getElementById(\"dns\").value+\"&rssithreshold=\"+document.getElementById(\"threshold\").value;";
message += "fetch(api);await new Promise(resolve => setTimeout(resolve, 1000));location.reload();}</script>";
httpd_resp_send_chunk(req, message.c_str(), strlen(message.c_str()));
return;
}
message += "<tr><td>WLAN-SSID</td><td><input id=\"ssid\"></td><td>SSID of the WLAN</td></tr>";
message += "<tr><td>WLAN-Password</td><td><input id=\"password\"></td><td><b>ATTENTION:</b> sent unencrypted</td></tr>";
message += "</table></div><p>";
message += "<h4>ATTENTION:</h4>Be sure about the WLAN/LAN settings. They cannot be reset afterwards. "
"If SSID or password are wrong you must edit <i>wlan.ini</i> on the SD-card!<p>";
httpd_resp_send_chunk(req, message.c_str(), message.length());
/* ---- Button + JS ---------------------------------------------------- */
message.clear();
message = "<button class=\"button\" id=\"saveBtn\" onclick=\"wr()\">Write wlan.ini</button>";
message += R"JS(
<script>
function toggle(){
const wifiSelected = (iface.value === 'wifi');
wifi.style.display = wifiSelected ? 'block' : 'none';
/* change button label */
saveBtn.textContent = wifiSelected ? 'Write wlan.ini' : 'Write lan.ini';
}
async function wr(){
let url = '/config?type=' + iface.value;
if(iface.value==='wifi'){
url += '&ssid=' + encodeURIComponent(ssid.value) +
'&pwd=' + encodeURIComponent(password.value);
}
await fetch(url);
await new Promise(r=>setTimeout(r,500));
location.reload();
}
</script>)JS";
httpd_resp_send_chunk(req, message.c_str(), message.length());
/* finish response */
httpd_resp_send_chunk(req, nullptr, 0);
return; // done
}
message = "<h3>3. Reboot</h3><p>";
message += "After triggering the reboot, the zip-files gets extracted and written to the sd-card.<br>The ESP32 will restart two times and then connect to your access point. Please find the IP in your router settings and access it with the new ip-address.<p>";
@@ -207,6 +229,8 @@ esp_err_t config_ini_handler(httpd_req_t *req)
std::string dns = "";
std::string rssithreshold = ""; //rssi threshold for WIFI roaming
std::string text = "";
std::string type = "";
if (httpd_req_get_url_query_str(req, _query, 400) == ESP_OK)
@@ -266,27 +290,49 @@ esp_err_t config_ini_handler(httpd_req_t *req)
ESP_LOGD(TAG, "rssithreshold is found: %s", _valuechar);
rssithreshold = UrlDecode(std::string(_valuechar));
}
if (httpd_query_key_value(_query, "type", _valuechar, 100) == ESP_OK)
{
ESP_LOGD(TAG, "type is found: %s", _valuechar);
type = UrlDecode(std::string(_valuechar));
}
}
FILE* configfilehandle = fopen(WLAN_CONFIG_FILE, "w");
FILE* configfilehandle = nullptr;
if(type == "wifi"){
configfilehandle = fopen(WLAN_CONFIG_FILE, "w");
text = ";++++++++++++++++++++++++++++++++++\n";
text += "; AI on the edge - WLAN configuration\n";
text += "; ssid: Name of WLAN network (mandatory), e.g. \"WLAN-SSID\"\n";
text += "; password: Password of WLAN network (mandatory), e.g. \"PASSWORD\"\n\n";
fputs(text.c_str(), configfilehandle);
if (ssid.length())
ssid = "ssid = \"" + ssid + "\"\n";
else
text = ";++++++++++++++++++++++++++++++++++\n";
text += "; AI on the edge - WLAN configuration\n";
text += "; ssid: Name of WLAN network (mandatory), e.g. \"WLAN-SSID\"\n";
text += "; password: Password of WLAN network (mandatory), e.g. \"PASSWORD\"\n\n";
fputs(text.c_str(), configfilehandle);
if (ssid.length())
ssid = "ssid = \"" + ssid + "\"\n";
else
ssid = "ssid = \"\"\n";
fputs(ssid.c_str(), configfilehandle);
if (pwd.length())
pwd = "password = \"" + pwd + "\"\n";
else
pwd = "password = \"\"\n";
fputs(pwd.c_str(), configfilehandle);
}
else{
configfilehandle = fopen(LAN_CONFIG_FILE, "w");
text = ";++++++++++++++++++++++++++++++++++\n";
text += "; AI on the edge - WLAN configuration\n";
text += "; ssid: Name of WLAN network (mandatory), e.g. \"WLAN-SSID\"\n";
text += "; password: Password of WLAN network (mandatory), e.g. \"PASSWORD\"\n\n";
fputs(text.c_str(), configfilehandle);
ssid = "ssid = \"\"\n";
fputs(ssid.c_str(), configfilehandle);
if (pwd.length())
pwd = "password = \"" + pwd + "\"\n";
else
fputs(ssid.c_str(), configfilehandle);
pwd = "password = \"\"\n";
fputs(pwd.c_str(), configfilehandle);
fputs(pwd.c_str(), configfilehandle);
}
text = "\n;++++++++++++++++++++++++++++++++++\n";
text += "; Hostname: Name of device in network\n";
@@ -333,22 +379,24 @@ esp_err_t config_ini_handler(httpd_req_t *req)
dns = ";dns = \"xxx.xxx.xxx.xxx\"\n";
fputs(dns.c_str(), configfilehandle);
text = "\n;++++++++++++++++++++++++++++++++++\n";
text += "; WIFI Roaming:\n";
text += "; Network assisted roaming protocol is activated by default\n";
text += "; AP / mesh system needs to support roaming protocol 802.11k/v\n";
text += ";\n";
text += "; Optional feature (usually not neccessary):\n";
text += "; RSSI Threshold for client requested roaming query (RSSI < RSSIThreshold)\n";
text += "; Note: This parameter can be configured via WebUI configuration\n";
text += "; Default: 0 = Disable client requested roaming query\n\n";
fputs(text.c_str(), configfilehandle);
if(type == "wifi"){
text = "\n;++++++++++++++++++++++++++++++++++\n";
text += "; WIFI Roaming:\n";
text += "; Network assisted roaming protocol is activated by default\n";
text += "; AP / mesh system needs to support roaming protocol 802.11k/v\n";
text += ";\n";
text += "; Optional feature (usually not neccessary):\n";
text += "; RSSI Threshold for client requested roaming query (RSSI < RSSIThreshold)\n";
text += "; Note: This parameter can be configured via WebUI configuration\n";
text += "; Default: 0 = Disable client requested roaming query\n\n";
fputs(text.c_str(), configfilehandle);
if (rssithreshold.length())
rssithreshold = "RSSIThreshold = " + rssithreshold + "\n";
else
rssithreshold = "RSSIThreshold = 0\n";
fputs(rssithreshold.c_str(), configfilehandle);
if (rssithreshold.length())
rssithreshold = "RSSIThreshold = " + rssithreshold + "\n";
else
rssithreshold = "RSSIThreshold = 0\n";
fputs(rssithreshold.c_str(), configfilehandle);
}
fflush(configfilehandle);
fclose(configfilehandle);
@@ -507,14 +555,15 @@ void CheckStartAPMode()
{
isConfigINI = FileExists(CONFIG_FILE);
isWlanINI = FileExists(WLAN_CONFIG_FILE);
isLanINI = FileExists(LAN_CONFIG_FILE);
if (!isConfigINI)
ESP_LOGW(TAG, "config.ini not found!");
if (!isWlanINI)
ESP_LOGW(TAG, "wlan.ini not found!");
if (!isWlanINI && !isLanINI)
ESP_LOGW(TAG, "wlan.ini and lan.ini not found!");
if (!isConfigINI || !isWlanINI)
if (!isConfigINI || (!isWlanINI && !isLanINI))
{
ESP_LOGI(TAG, "Starting access point for remote configuration");
StatusLED(AP_OR_OTA, 2, true);

View File

@@ -10,7 +10,7 @@
[platformio]
src_dir = main
default_envs = esp32cam
default_envs = esp32s3-aleksei
[common:idf]
build_flags =
@@ -72,6 +72,31 @@ build_flags =
board_build.partitions = partitions.csv
monitor_speed = 115200
; The main env - default
[env:esp32s3-aleksei]
extends = common:esp32-idf
board = seeed_xiao_esp32s3
framework = espidf
build_flags =
; ### common imported :
${common:esp32-idf.build_flags}
${flags:runtime.build_flags}
; ### Sofware options : (can be set in defines.h)
-D BOARD_ESP32_S3_ALEKSEI
-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
board_build.partitions = partitions.csv
monitor_speed = 115200
; full standalone dev mode
; As sample, the board is nod32s instead of esp32cam (do not change nothing in fact :)

2339
code/sdkconfig.esp32s3 Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -37,23 +37,58 @@ void test_createSequenceMetrics()
NumberPost *number_1 = new NumberPost;
number_1->name = "main";
number_1->ReturnValue = "123.456";
number_1->ReturnRawValue = "N23.456";
number_1->ReturnPreValue = "986.543";
number_1->ErrorMessageText = "";
NUMBERS.push_back(number_1);
const std::string metricNamePrefix = "ai_on_the_edge_device";
const std::string metricName = metricNamePrefix + "_flow_value";
const std::string metricName1 = metricNamePrefix + "_flow_value";
const std::string metricName2 = metricNamePrefix + "_flow_raw_value";
const std::string metricName3 = metricNamePrefix + "_flow_pre_value";
const std::string metricName4 = metricNamePrefix + "_flow_error";
std::string expected1 ;
expected1 = "# HELP " + metricName1 + " current value of meter readout\n# TYPE " + metricName1 + " gauge\n" +
metricName1 + "{sequence=\"" + number_1->name + "\"} " + number_1->ReturnValue + "\n";
expected1 += "# HELP " + metricName2 + " current raw value of meter readout\n# TYPE " + metricName2 + " gauge\n" +
metricName2 + "{sequence=\"" + number_1->name + "\"} " + "NaN" + "\n";
expected1 += "# HELP " + metricName3 + " previous value of meter readout\n# TYPE " + metricName3 + " gauge\n" +
metricName3 + "{sequence=\"" + number_1->name + "\"} " + number_1->ReturnPreValue + "\n";
expected1 += "# HELP " + metricName4 + " Error message text != 'no error'\n# TYPE " + metricName4 + " gauge\n" +
metricName4 + "{sequence=\"" + number_1->name + "\"} " + "1" + "\n";
std::string expected1 = "# HELP " + metricName + " current value of meter readout\n# TYPE " + metricName + " gauge\n" +
metricName + "{sequence=\"" + number_1->name + "\"} " + number_1->ReturnValue + "\n";
TEST_ASSERT_EQUAL_STRING(expected1.c_str(), createSequenceMetrics(metricNamePrefix, NUMBERS).c_str());
NumberPost *number_2 = new NumberPost;
number_2->name = "secondary";
number_2->ReturnValue = "1.0";
number_2->ReturnRawValue = "01.000";
number_2->ReturnPreValue = "0.987";
number_2->ErrorMessageText = "no error";
NUMBERS.push_back(number_2);
std::string expected2 = "# HELP " + metricName + " current value of meter readout\n# TYPE " + metricName + " gauge\n" +
metricName + "{sequence=\"" + number_1->name + "\"} " + number_1->ReturnValue + "\n" +
metricName + "{sequence=\"" + number_2->name + "\"} " + number_2->ReturnValue + "\n";
std::string expected2 ;
expected2 = "# HELP " + metricName1 + " current value of meter readout\n# TYPE " + metricName1 + " gauge\n" +
metricName1 + "{sequence=\"" + number_1->name + "\"} " + number_1->ReturnValue + "\n" +
metricName1 + "{sequence=\"" + number_2->name + "\"} " + number_2->ReturnValue + "\n";
expected2 += "# HELP " + metricName2 + " current raw value of meter readout\n# TYPE " + metricName2 + " gauge\n" +
metricName2 + "{sequence=\"" + number_1->name + "\"} " + "NaN" + "\n" +
metricName2 + "{sequence=\"" + number_2->name + "\"} " + number_2->ReturnRawValue + "\n";
expected2 += "# HELP " + metricName3 + " previous value of meter readout\n# TYPE " + metricName3 + " gauge\n" +
metricName3 + "{sequence=\"" + number_1->name + "\"} " + number_1->ReturnPreValue + "\n" +
metricName3 + "{sequence=\"" + number_2->name + "\"} " + number_2->ReturnPreValue + "\n";
expected2 += "# HELP " + metricName4 + " Error message text != 'no error'\n# TYPE " + metricName4 + " gauge\n" +
metricName4 + "{sequence=\"" + number_1->name + "\"} " + "1" + "\n" +
metricName4 + "{sequence=\"" + number_2->name + "\"} " + "0" + "\n";
TEST_ASSERT_EQUAL_STRING(expected2.c_str(), createSequenceMetrics(metricNamePrefix, NUMBERS).c_str());
}

View File

@@ -31,7 +31,6 @@ AlignmentAlgo
CNNGoodThreshold
PreValueAgeStartup
ErrorMessage
CheckDigitIncreaseConsistency
IO0
IO1
IO3

View File

@@ -3,9 +3,26 @@ Default Value: `other`
Select the Meter Type so the sensors have the right units in Homeassistant.
!!! Note
For `Watermeter` you need to have Homeassistant 2022.11 or newer!
Please also make sure that the selected Meter Type matches the dimension of the value provided by the meter!
Eg. if your meter provides `m³`, you need to also set it to `m³`.
Alternatively you can set the parameter `DecimalShift` to `3` so the value is converted to `liters`!
List of supported options:
- `other`
- `water_m3` (uses `m^3/min` as rate)
- `water_l` (uses `l/h` as rate, not officially supported by Homeassistant!)
- `water_gal` (uses `gal/h` as rate, not officially supported by Homeassistant!)
- `water_ft3` (uses `ft^3/min` as rate)
- `gas_m3` (uses `m^3/min` as rate)
- `gas_ft3` (uses `ft^3/min` as rate)
- `energy_wh` (uses `W` as rate)
- `energy_kwh` (uses `KW` as rate)
- `energy_mwh` (uses `MW` as rate)
- `energy_gj` (uses `GJ/h` as rate, not officially supported by Homeassistant!)
- `temperature_c` (uses `+C/min` as rate)
- `temperature_f` (uses `°F/min` as rate)
- `temperature_k` (uses `K/min` as rate)
!!! Note
Not all options are supported by Homeassistant, see `SensorDeviceClass.VOLUME_FLOW_RATE` in [https://developers.home-assistant.io/docs/core/entity/sensor/#available-device-classes](https://developers.home-assistant.io/docs/core/entity/sensor/#available-device-classes)!

View File

@@ -6,3 +6,6 @@ Default Value: `false`
An additional consistency check.
It especially improves the zero crossing check between digits.
!!! Note
This parameter must be prefixed with `<NUMBER>` followed by a dot (eg. `main.CheckDigitIncreaseConsistency`). `<NUMBER>` is the name of the number sequence defined in the ROI's.

5
sd-card/Readme.md Normal file
View File

@@ -0,0 +1,5 @@
# SD Card content
This folder contains the files which are required to setup the SD card.
❗ Do not directly copy this folder onto your SD card, **it will not work!** Instead, you can use any of the artifacts generaded in any of the Pipeline runs of the [Build-Pipeline](https://github.com/jomjol/AI-on-the-edge-device/actions/workflows/build.yaml).
The files in the `html` folder here only serve as templates. The real `html` folder get generated using the Github actions.

View File

@@ -1,145 +1,145 @@
[TakeImage]
;RawImagesLocation = /log/source
;RawImagesRetention = 15
WaitBeforeTakingPicture = 2
CamGainceiling = x8
CamQuality = 10
CamBrightness = 0
CamContrast = 0
CamSaturation = 0
CamSharpness = 0
CamAutoSharpness = false
CamSpecialEffect = no_effect
CamWbMode = auto
CamAwb = true
CamAwbGain = true
CamAec = true
CamAec2 = true
CamAeLevel = 2
CamAecValue = 600
CamAgc = true
CamAgcGain = 8
CamBpc = true
CamWpc = true
CamRawGma = true
CamLenc = true
CamHmirror = false
CamVflip = false
CamDcw = true
CamDenoise = 0
CamZoom = false
CamZoomOffsetX = 0
CamZoomOffsetY = 0
CamZoomSize = 0
LEDIntensity = 50
Demo = false
[Alignment]
InitialRotate = 0.0
SearchFieldX = 20
SearchFieldY = 20
AlignmentAlgo = default
/config/ref0.jpg 103 271
/config/ref1.jpg 442 142
[Digits]
Model = /config/dig-cont_0712_s3_q.tflite
CNNGoodThreshold = 0.5
;ROIImagesLocation = /log/digit
;ROIImagesRetention = 3
main.dig1 294 126 30 54 false
main.dig2 343 126 30 54 false
main.dig3 391 126 30 54 false
[Analog]
Model = /config/ana-cont_1300_s2.tflite
CNNGoodThreshold = 0.5
;ROIImagesLocation = /log/analog
;ROIImagesRetention = 3
main.ana1 432 230 92 92 false
main.ana2 379 332 92 92 false
main.ana3 283 374 92 92 false
main.ana4 155 328 92 92 false
[PostProcessing]
main.DecimalShift = 0
main.AnalogDigitTransitionStart = 9.2
main.ChangeRateThreshold = 2
PreValueUse = true
PreValueAgeStartup = 720
main.AllowNegativeRates = false
main.MaxRateValue = 0.05
;main.MaxRateType = AbsoluteChange
main.ExtendedResolution = false
main.IgnoreLeadingNaN = false
ErrorMessage = true
CheckDigitIncreaseConsistency = false
;[MQTT]
;Uri = mqtt://IP-ADRESS:1883
;MainTopic = watermeter
;ClientID = watermeter
;user = USERNAME
;password = PASSWORD
RetainMessages = false
HomeassistantDiscovery = false
;MeterType = other
;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
;[InfluxDB]
;Uri = undefined
;Database = undefined
;user = undefined
;password = undefined
;main.Measurement = undefined
;main.Field = undefined
;[InfluxDBv2]
;Uri = undefined
;Bucket = undefined
;Org = undefined
;Token = undefined
;main.Measurement = undefined
;main.Field = undefined
;[Webhook]
;Uri = undefined
;ApiKey = undefined
;UploadImg = 0
;[GPIO]
;MainTopicMQTT = wasserzaehler/GPIO
;IO0 = input disabled 10 false false
;IO1 = input disabled 10 false false
;IO3 = input disabled 10 false false
;IO4 = built-in-led disabled 10 false false
;IO12 = input-pullup disabled 10 false false
;IO13 = input-pullup disabled 10 false false
LEDType = WS2812
LEDNumbers = 2
LEDColor = 150 150 150
[AutoTimer]
Interval = 5
[DataLogging]
DataLogActive = true
DataFilesRetention = 3
[Debug]
LogLevel = 1
LogfilesRetention = 3
[System]
TimeZone = CET-1CEST,M3.5.0,M10.5.0/3
;TimeServer = pool.ntp.org
;Hostname = undefined
RSSIThreshold = -75
CPUFrequency = 160
Tooltip = true
SetupMode = true
[TakeImage]
;RawImagesLocation = /log/source
;RawImagesRetention = 15
WaitBeforeTakingPicture = 2
CamGainceiling = x8
CamQuality = 10
CamBrightness = 0
CamContrast = 0
CamSaturation = 0
CamSharpness = 0
CamAutoSharpness = false
CamSpecialEffect = no_effect
CamWbMode = auto
CamAwb = true
CamAwbGain = true
CamAec = true
CamAec2 = true
CamAeLevel = 2
CamAecValue = 600
CamAgc = true
CamAgcGain = 8
CamBpc = true
CamWpc = true
CamRawGma = true
CamLenc = true
CamHmirror = false
CamVflip = false
CamDcw = true
CamDenoise = 0
CamZoom = false
CamZoomOffsetX = 0
CamZoomOffsetY = 0
CamZoomSize = 0
LEDIntensity = 50
Demo = false
[Alignment]
InitialRotate = 0.0
SearchFieldX = 20
SearchFieldY = 20
AlignmentAlgo = default
/config/ref0.jpg 103 271
/config/ref1.jpg 442 142
[Digits]
Model = /config/dig-cont_0712_s3_q.tflite
CNNGoodThreshold = 0.5
;ROIImagesLocation = /log/digit
;ROIImagesRetention = 3
main.dig1 294 126 30 54 false
main.dig2 343 126 30 54 false
main.dig3 391 126 30 54 false
[Analog]
Model = /config/ana-cont_1300_s2.tflite
CNNGoodThreshold = 0.5
;ROIImagesLocation = /log/analog
;ROIImagesRetention = 3
main.ana1 432 230 92 92 false
main.ana2 379 332 92 92 false
main.ana3 283 374 92 92 false
main.ana4 155 328 92 92 false
[PostProcessing]
main.DecimalShift = 0
main.AnalogDigitTransitionStart = 9.2
main.ChangeRateThreshold = 2
PreValueUse = true
PreValueAgeStartup = 720
main.AllowNegativeRates = false
main.MaxRateValue = 0.05
;main.MaxRateType = AbsoluteChange
main.ExtendedResolution = false
main.IgnoreLeadingNaN = false
ErrorMessage = true
main.CheckDigitIncreaseConsistency = false
;[MQTT]
;Uri = mqtt://IP-ADRESS:1883
;MainTopic = watermeter
;ClientID = watermeter
;user = USERNAME
;password = PASSWORD
RetainMessages = false
HomeassistantDiscovery = false
;MeterType = other
;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
;[InfluxDB]
;Uri = undefined
;Database = undefined
;user = undefined
;password = undefined
;main.Measurement = undefined
;main.Field = undefined
;[InfluxDBv2]
;Uri = undefined
;Bucket = undefined
;Org = undefined
;Token = undefined
;main.Measurement = undefined
;main.Field = undefined
;[Webhook]
;Uri = undefined
;ApiKey = undefined
;UploadImg = 0
[GPIO]
;IO0 = input disabled 10 false false
;IO1 = input disabled 10 false false
;IO3 = input disabled 10 false false
;IO4 = built-in-led disabled 10 false false
IO12 = external-flash-ws281x disabled 10 false false
;IO13 = input-pullup disabled 10 false false
LEDType = WS2812
LEDNumbers = 4
LEDColor = 200 200 200
[AutoTimer]
Interval = 5
SleepWhileIdle = false
[DataLogging]
DataLogActive = true
DataFilesRetention = 3
[Debug]
LogLevel = 1
LogfilesRetention = 3
[System]
TimeZone = CET-1CEST,M3.5.0,M10.5.0/3
;TimeServer = pool.ntp.org
;Hostname = undefined
RSSIThreshold = -75
CPUFrequency = 160
Tooltip = true
SetupMode = true

View File

@@ -43,14 +43,14 @@ AlignmentAlgo = default
/config/ref1.jpg 536 113
[Digits]
Model = /config/dig-cont_0710_s3_q.tflite
Model = /config/dig-cont_0810_s3_q.tflite
CNNGoodThreshold = 0.5
;ROIImagesLocation = /log/digit
;ROIImagesRetention = 3
main.dig1 438 62 49 71 false
[Analog]
Model = /config/ana-cont_1300_s2.tflite
Model = /config/ana-cont_1400_s2_q.tflite
;ROIImagesLocation = /log/analog
;ROIImagesRetention = 3
main.ana1 452 199 120 120 false
@@ -67,7 +67,7 @@ main.AllowNegativeRates = true
main.ExtendedResolution = true
main.IgnoreLeadingNaN = false
ErrorMessage = true
CheckDigitIncreaseConsistency = false
main.CheckDigitIncreaseConsistency = false
;[MQTT]
;Uri = mqtt://IP-ADRESS:1883
@@ -76,11 +76,14 @@ CheckDigitIncreaseConsistency = false
;user = USERNAME
;password = PASSWORD
RetainMessages = false
;DomoticzTopicIn = undefined
;main.DomoticzIDX = undefined
HomeassistantDiscovery = false
;MeterType = other
;CACert = /config/certs/RootCA.pem
;ClientCert = /config/certs/client.pem.crt
;ClientKey = /config/certs/client.pem.key
;ValidateServerCert = true
;[InfluxDB]
;Uri = undefined
@@ -133,4 +136,3 @@ TimeZone = CET-1CEST,M3.5.0,M10.5.0/3
RSSIThreshold = -75
CPUFrequency = 160
SetupMode = false

View File

@@ -1,6 +1,7 @@
/* The UI can also be run locally, but you have to set the IP of your devide accordingly.
/* The UI can also be run locally, but you have to set the IP of your device accordingly.
* And you also might have to disable CORS in your webbrowser!
* Eg using https://chromewebstore.google.com/detail/allow-cors-access-control/lhobafahddgcelffkeicbaginigeejlf?utm_source=ext_app_menu on chrome
* Keep empty to disable using it. Enabling it will break access through a forwared port, see
* https://github.com/jomjol/AI-on-the-edge-device/issues/2681 */
var domainname_for_testing = "";

View File

@@ -79,6 +79,18 @@
transform: translate(-50%,-50%);
-ms-transform: translate(-50%,-50%);
}
#reboot_button {
float: none;
background-color: #f44336;
color: white;
padding: 5px;
border-radius:
5px; font-weight: bold;
text-align: center;
text-decoration: none;
display: inline-block;
}
</style>
<link href="firework.css?v=$COMMIT_HASH" rel="stylesheet">
@@ -188,13 +200,11 @@
param;
function doReboot() {
if (confirm("Are you sure you want to reboot? Did you save your changes?")) {
var stringota = domainname + "/reboot";
window.location = stringota;
window.location.href = stringota;
window.location.assign(stringota);
window.location.replace(stringota);
}
var stringota = domainname + "/reboot";
window.location = stringota;
window.location.href = stringota;
window.location.assign(stringota);
window.location.replace(stringota);
}
function ChangeSelection(){
@@ -215,50 +225,45 @@
document.getElementById("overlay").style.display = "block";
document.getElementById("overlaytext").innerHTML = "Save Alignment Marker...";
if (confirm("Are you sure you want to save the new alignment marker configuration?")) {
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function task() {
while (true) {
WriteConfigININew();
if (neueref1 == 1 && neueref2 == 1) {
UpdateConfigReferences(domainname);
}
else if (neueref1 == 1) {
var anzneueref = 1;
UpdateConfigReference(anzneueref, domainname);
}
else if (neueref2 == 1) {
var anzneueref = 2;
UpdateConfigReference(anzneueref, domainname);
}
SaveConfigToServer(domainname);
document.getElementById("updatemarker").disabled = false;
// document.getElementById("savemarker").disabled = true;
// document.getElementById("enhancecontrast").disabled = true;
EnDisableItem(false, "savemarker", true);
EnDisableItem(false, "enhancecontrast", true);
document.getElementById("overlay").style.display = "none";
firework.launch('Alignment marker saved. They will get applied after next reboot', 'success', 5000);
return;
async function task() {
while (true) {
WriteConfigININew();
if (neueref1 == 1 && neueref2 == 1) {
UpdateConfigReferences(domainname);
}
else if (neueref1 == 1) {
var anzneueref = 1;
UpdateConfigReference(anzneueref, domainname);
}
else if (neueref2 == 1) {
var anzneueref = 2;
UpdateConfigReference(anzneueref, domainname);
}
}
setTimeout(function () {
// Delay so the overlay gets shown
task();
}, 1);
}
else {
document.getElementById("overlay").style.display = "none";
SaveConfigToServer(domainname);
document.getElementById("updatemarker").disabled = false;
// document.getElementById("savemarker").disabled = true;
// document.getElementById("enhancecontrast").disabled = true;
EnDisableItem(false, "savemarker", true);
EnDisableItem(false, "enhancecontrast", true);
document.getElementById("overlay").style.display = "none";
firework.launch('Alignment marker saved. They will get applied after the next reboot!<br><br>\n<a id="reboot_button" onclick="doReboot()">reboot now</a>', 'success', 5000);
return;
}
}
setTimeout(function () {
// Delay so the overlay gets shown
task();
}, 1);
}
function EnhanceContrast() {

View File

@@ -5,6 +5,20 @@
<meta charset="UTF-8" />
<title>Analog ROI</title>
<style>
#reboot_button {
float: none;
background-color: #f44336;
color: white;
padding: 5px;
border-radius:
5px; font-weight: bold;
text-align: center;
text-decoration: none;
display: inline-block;
}
</style>
<link href="edit_style.css" rel="stylesheet">
<link href="firework.css?v=$COMMIT_HASH" rel="stylesheet">
@@ -185,13 +199,11 @@ The following settings are only used for easier setup, they are <b>not</b> persi
domainname = getDomainname();
function doReboot() {
if (confirm("Are you sure you want to reboot? Did you save your changes?")) {
var stringota = getDomainname() + "/reboot";
window.location = stringota;
window.location.href = stringota;
window.location.assign(stringota);
window.location.replace(stringota);
}
var stringota = getDomainname() + "/reboot";
window.location = stringota;
window.location.href = stringota;
window.location.assign(stringota);
window.location.replace(stringota);
}
function EnDisableAnalog() {
@@ -331,16 +343,14 @@ The following settings are only used for easier setup, they are <b>not</b> persi
}
function SaveToConfig() {
if (confirm("Are you sure you want to save the new analog ROI configuration?")) {
//_zwcat = getConfigCategory();
cofcat["Analog"]["enabled"] = document.getElementById("Category_Analog_enabled").checked;
WriteConfigININew();
SaveConfigToServer(domainname);
UpdateROIs();
document.getElementById("saveroi").disabled = true;
//_zwcat = getConfigCategory();
cofcat["Analog"]["enabled"] = document.getElementById("Category_Analog_enabled").checked;
WriteConfigININew();
SaveConfigToServer(domainname);
UpdateROIs();
document.getElementById("saveroi").disabled = true;
firework.launch('Configuration saved. It will get applied after next reboot', 'success', 5000);
}
firework.launch('Configuration saved. It will get applied after the next reboot!<br><br>\n<a id="reboot_button" onclick="doReboot()">reboot now</a>', 'success', 5000);
}
function ShowMultiplier() {

View File

@@ -20,6 +20,18 @@
textarea {
font-size: 15px;
}
#reboot_button {
float: none;
background-color: #f44336;
color: white;
padding: 5px;
border-radius:
5px; font-weight: bold;
text-align: center;
text-decoration: none;
display: inline-block;
}
</style>
<link href="firework.css?v=$COMMIT_HASH" rel="stylesheet">
@@ -38,15 +50,8 @@
</td>
</table>
<table>
<td>
<button class="button" onclick="saveTextAsFile()">Save Config</button>
</td>
<td>
<button class="button" id="reboot" type="button" onclick="doReboot()">Reboot to activate changes</button>
</td>
</table>
<hr>
<button class="button" onclick="saveTextAsFile()">Save Config</button>
<script type="text/javascript" src="readconfigparam.js?v=$COMMIT_HASH"></script>
<script type="text/javascript" src="readconfigcommon.js?v=$COMMIT_HASH"></script>
@@ -64,23 +69,19 @@
function saveTextAsFile()
{
if (confirm("Are you sure you want to save the configuration?")) {
FileDeleteOnServer("/config/config.ini", domainname);
var textToSave = document.getElementById("inputTextToSave").value;
FileSendContent(textToSave, "/config/config.ini", domainname);
FileDeleteOnServer("/config/config.ini", domainname);
var textToSave = document.getElementById("inputTextToSave").value;
FileSendContent(textToSave, "/config/config.ini", domainname);
firework.launch('Configuration saved. It will get applied after next reboot', 'success', 5000);
}
firework.launch('Configuration saved. It will get applied after the next reboot!<br><br>\n<a id="reboot_button" onclick="doReboot()">reboot now</a>', 'success', 5000);
}
function doReboot() {
if (confirm("Are you sure you want to reboot?")) {
var stringota = "/reboot";
window.location = stringota;
window.location.href = stringota;
window.location.assign(stringota);
window.location.replace(stringota);
}
var stringota = "/reboot";
window.location = stringota;
window.location.href = stringota;
window.location.assign(stringota);
window.location.replace(stringota);
}
LoadConfigNeu();

File diff suppressed because it is too large Load Diff

View File

@@ -4,6 +4,19 @@
<head>
<meta charset="UTF-8" />
<title>Digit ROI</title>
<style>
#reboot_button {
float: none;
background-color: #f44336;
color: white;
padding: 5px;
border-radius:
5px; font-weight: bold;
text-align: center;
text-decoration: none;
display: inline-block;
}
</style>
<link href="edit_style.css" rel="stylesheet">
<link href="firework.css?v=$COMMIT_HASH" rel="stylesheet">
@@ -204,13 +217,11 @@
domainname = getDomainname();
function doReboot() {
if (confirm("Are you sure you want to reboot? Did you save your changes?")) {
var stringota = getDomainname() + "/reboot";
window.location = stringota;
window.location.href = stringota;
window.location.assign(stringota);
window.location.replace(stringota);
}
var stringota = getDomainname() + "/reboot";
window.location = stringota;
window.location.href = stringota;
window.location.assign(stringota);
window.location.replace(stringota);
}
function EnDisableDigits() {
@@ -359,16 +370,14 @@
}
function SaveToConfig() {
if (confirm("Are you sure you want to save the new digit ROI configuration?")) {
// _zwcat = getConfigCategory();
cofcat["Digits"]["enabled"] = document.getElementById("Category_Digits_enabled").checked;
WriteConfigININew();
SaveConfigToServer(domainname);
UpdateROIs();
document.getElementById("saveroi").disabled = true;
// _zwcat = getConfigCategory();
cofcat["Digits"]["enabled"] = document.getElementById("Category_Digits_enabled").checked;
WriteConfigININew();
SaveConfigToServer(domainname);
UpdateROIs();
document.getElementById("saveroi").disabled = true;
firework.launch('Configuration saved. It will get applied after next reboot', 'success', 5000);
}
firework.launch('Configuration saved. It will get applied after the next reboot!<br><br>\n<a id="reboot_button" onclick="doReboot()">reboot now</a>', 'success', 5000);
}
function ShowMultiplier() {

View File

@@ -395,22 +395,12 @@
<script type="text/javascript">
var canvas = document.getElementById('canvas'),
domainname = getDomainname(),
context = canvas.getContext('2d'),
imageObj = new Image(),
isActReference = false,
param,
category;
function doReboot() {
if (confirm("Are you sure you want to reboot? Did you save the config?")) {
var stringota = domainname + "/reboot";
window.location = stringota;
window.location.href = stringota;
window.location.assign(stringota);
window.location.replace(stringota);
}
}
domainname = getDomainname(),
context = canvas.getContext('2d'),
imageObj = new Image(),
isActReference = false,
param,
category;
function cameraParameterChanged() {
document.getElementById("savereferenceimage").disabled = true;
@@ -738,7 +728,7 @@
if (xhttp.responseText == "CamSettingsSet") {
document.getElementById("overlay").style.display = "none";
firework.launch('Cam Settings saved', 'success', 2000);
firework.launch('Cam Settings saved', 'success', 5000);
return;
}
else {

17
sd-card/html/md5.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -25,6 +25,7 @@
</style>
<link href="firework.css?v=$COMMIT_HASH" rel="stylesheet">
<script src="md5.min.js?v=$COMMIT_HASH"></script>
<script type="text/javascript" src="jquery-3.6.0.min.js?v=$COMMIT_HASH"></script>
<script type="text/javascript" src="common.js?v=$COMMIT_HASH"></script>
<script type="text/javascript" src="firework.js?v=$COMMIT_HASH"></script>
@@ -142,7 +143,7 @@
function doRebootAfterUpdate() {
var xhttp = new XMLHttpRequest();
xhttp.open("GET", "/reboot", true);
xhttp.open("GET", domainname + "/reboot", true);
xhttp.send();
}
@@ -169,15 +170,35 @@
}
};
var _toDo = domainname + "/ota?task=emptyfirmwaredir";
xhttp.open("GET", _toDo, true);
var url = domainname + "/ota?task=emptyfirmwaredir";
xhttp.open("GET", url, true);
xhttp.send();
}
function extract() {
document.getElementById("status").innerText = "Status: Processing on device...";
function validateMd5(md5_on_device, callback) {
const reader = new FileReader();
reader.onload = (event) => {
const fileContent = event.target.result;
md5_on_webbrowser = md5(fileContent);
console.log("MD5 on device: " + md5_on_device + ", MD5 on web browser: " + md5_on_webbrowser);
if (md5_on_device == md5_on_webbrowser) {
console.log("MD5 values are equal");
callback(true);
}
else {
console.log("MD5 values are NOT equal!");
callback(false);
}
}
var fileInput = document.getElementById("file_selector").files;
reader.readAsArrayBuffer(fileInput[0]);
}
function extract() {
var xhttp = new XMLHttpRequest();
/* first delete the old firmware */
xhttp.onreadystatechange = function() {
@@ -186,9 +207,9 @@
document.cookie = "page=overview.html?v=$COMMIT_HASH" + "; path=/"; // Make sure after the reboot we go to the overview page
if (xhttp.responseText.startsWith("reboot")) { // Reboot required
console.log("Upload completed, the device will now restart and install the update!");
console.log("The device will now reboot and install the update!");
document.getElementById("status").innerText = "Status: Installing...";
firework.launch('Upload completed, the device will now restart and install the update', 'success', 5000);
firework.launch('Upload completed and validated. The device will now reboot and install the update', 'success', 5000);
/* Tell it to reboot */
doRebootAfterUpdate();
@@ -228,8 +249,8 @@
var file_name = document.getElementById("file_selector").value;
filePath = file_name.split(/[\\\/]/).pop();
var _toDo = domainname + "/ota?task=update&file=" + filePath;
xhttp.open("GET", _toDo, true);
var url = domainname + "/ota?task=update&file=" + filePath;
xhttp.open("GET", url, true);
xhttp.send();
}
@@ -242,7 +263,7 @@
function upload() {
document.getElementById("status").innerText = "Status: Uploading...";
var upload_path = "/upload/firmware/" + filePath;
var url = domainname + "/upload/firmware/" + filePath + "?md5";
var file = _("file_selector").files[0];
var formdata = new FormData();
@@ -253,7 +274,7 @@
ajax.addEventListener("error", errorHandler, false);
ajax.addEventListener("abort", abortHandler, false);
ajax.open("POST", upload_path);
ajax.open("POST", url);
ajax.send(file);
}
@@ -263,16 +284,43 @@
" MB of " + (event.total / 1024/ 1024).toFixed(2) + " MB";
var percent = (event.loaded / event.total) * 100;
_("progressBar").value = Math.round(percent);
_("status").innerHTML = "Status: " + Math.round(percent) + "% uploaded. Please wait...";
if (Math.round(percent) == 100) {
_("progressBar").value = 0; //will clear progress bar after successful upload
_("loaded_n_total").innerHTML = "";
_("status").innerHTML = "Status: Upload completed. Validating file...";
}
else {
_("status").innerHTML = "Status: " + Math.round(percent) + "% uploaded...";
}
}
function completeHandler(event) {
_("status").innerHTML = "Status: " + event.target.responseText;
_("progressBar").value = 0; //will clear progress bar after successful upload
_("loaded_n_total").innerHTML = "";
extract();
console.log("Upload completed");
console.log("Response: " + event.target.responseText);
try {
md5_on_device = JSON.parse(event.target.responseText).md5;
validateMd5(md5_on_device, (result) => {
if (result == true) {
_("status").innerHTML = "Status: The uploaded file is valid, installing it...";
extract();
}
else {
_("status").innerHTML = "Status: The file got corrupted! Please upload it again!";
firework.launch('Upload failed, the file got corrupted! Please upload it again!', 'danger', 30000);
document.getElementById("start_OTA_button").disabled = false;
}
});
}
catch (e) {
// If the firmware is to old, it will return the file sever page instead of the JSON object with the MD5 sum.
// In juch case just proceed to keep legacy support.
console.log("It seems to be a legacy firmware, installing the update without validation!");
_("status").innerHTML = "Status: It seems to be a legacy firmware, installing the update without validation...";
extract();
}
}

View File

@@ -6,7 +6,6 @@ var ref = new Array(2);
var NUMBERS = new Array(0);
var REFERENCES = new Array(0);
function getNUMBERSList() {
_domainname = getDomainname();
var namenumberslist = "";
@@ -33,7 +32,6 @@ function getNUMBERSList() {
return namenumberslist;
}
function getDATAList() {
_domainname = getDomainname();
datalist = "";
@@ -62,7 +60,6 @@ function getDATAList() {
return datalist;
}
function getTFLITEList() {
_domainname = getDomainname();
tflitelist = "";
@@ -90,7 +87,6 @@ function getTFLITEList() {
return tflitelist;
}
function ParseConfig() {
config_split = config_gesamt.split("\n");
var aktline = 0;
@@ -172,7 +168,7 @@ function ParseConfig() {
category[catname]["enabled"] = false;
category[catname]["found"] = false;
param[catname] = new Object();
ParamAddValue(param, catname, "DecimalShift", 1, true);
ParamAddValue(param, catname, "DecimalShift", 1, true, "0");
ParamAddValue(param, catname, "AnalogToDigitTransitionStart", 1, true, "9.2");
ParamAddValue(param, catname, "ChangeRateThreshold", 1, true, "2");
// ParamAddValue(param, catname, "PreValueUse", 1, true, "true");
@@ -185,7 +181,7 @@ function ParseConfig() {
ParamAddValue(param, catname, "IgnoreLeadingNaN", 1, true, "false");
// ParamAddValue(param, catname, "IgnoreAllNaN", 1, true, "false");
ParamAddValue(param, catname, "ErrorMessage");
ParamAddValue(param, catname, "CheckDigitIncreaseConsistency");
ParamAddValue(param, catname, "CheckDigitIncreaseConsistency", 1, true, "false");
var catname = "MQTT";
category[catname] = new Object();
@@ -251,7 +247,7 @@ function ParseConfig() {
ParamAddValue(param, catname, "IO1", 6, false, "", [null, null, /^[0-9]*$/, null, null, /^[a-zA-Z0-9_-]*$/]);
ParamAddValue(param, catname, "IO3", 6, false, "", [null, null, /^[0-9]*$/, null, null, /^[a-zA-Z0-9_-]*$/]);
ParamAddValue(param, catname, "IO4", 6, false, "", [null, null, /^[0-9]*$/, null, null, /^[a-zA-Z0-9_-]*$/]);
ParamAddValue(param, catname, "IO12", 6, false, "", [null, null, /^[0-9]*$/, null, null, /^[a-zA-Z0-9_-]*$/]);
ParamAddValue(param, catname, "IO47", 6, false, "", [null, null, /^[0-9]*$/, null, null, /^[a-zA-Z0-9_-]*$/]);
ParamAddValue(param, catname, "IO13", 6, false, "", [null, null, /^[0-9]*$/, null, null, /^[a-zA-Z0-9_-]*$/]);
ParamAddValue(param, catname, "LEDType");
ParamAddValue(param, catname, "LEDNumbers");
@@ -359,7 +355,6 @@ function ParseConfig() {
}
}
function ParamAddValue(param, _cat, _param, _anzParam = 1, _isNUMBER = false, _defaultValue = "", _checkRegExList = null) {
param[_cat][_param] = new Object();
param[_cat][_param]["found"] = false;
@@ -371,7 +366,6 @@ function ParamAddValue(param, _cat, _param, _anzParam = 1, _isNUMBER = false, _d
param[_cat][_param].checkRegExList = _checkRegExList;
};
function ParseConfigParamAll(_aktline, _catname) {
++_aktline;
@@ -403,7 +397,6 @@ function ParseConfigParamAll(_aktline, _catname) {
return _aktline;
}
function ParamExtractValue(_param, _linesplit, _catname, _paramname, _aktline, _iscom, _anzvalue = 1) {
if ((_linesplit[0].toUpperCase() == _paramname.toUpperCase()) && (_linesplit.length > _anzvalue)) {
_param[_catname][_paramname]["found"] = true;
@@ -417,7 +410,6 @@ function ParamExtractValue(_param, _linesplit, _catname, _paramname, _aktline, _
}
}
function ParamExtractValueAll(_param, _linesplit, _catname, _aktline, _iscom) {
for (var paramname in _param[_catname]) {
_AktROI = "default";
@@ -475,7 +467,6 @@ function ParamExtractValueAll(_param, _linesplit, _catname, _aktline, _iscom) {
}
}
function getCamConfig() {
ParseConfig();
@@ -653,12 +644,10 @@ function getCamConfig() {
return param;
}
function getConfigParameters() {
return param;
}
function WriteConfigININew() {
// Cleanup empty NUMBERS
for (var j = 0; j < NUMBERS.length; ++j) {
@@ -760,7 +749,6 @@ function WriteConfigININew() {
}
}
function isCommented(input) {
let isComment = false;
@@ -772,7 +760,6 @@ function isCommented(input) {
return [isComment, input];
}
function SaveConfigToServer(_domainname){
// leere Zeilen am Ende löschen
var zw = config_split.length - 1;
@@ -792,17 +779,14 @@ function SaveConfigToServer(_domainname){
FileSendContent(config_gesamt, "/config/config.ini", _domainname);
}
function getConfig() {
return config_gesamt;
}
function getConfigCategory() {
return category;
}
function ExtractROIs(_aktline, _type){
var linesplit = ZerlegeZeile(_aktline);
abc = getNUMBERS(linesplit[0], _type);
@@ -819,7 +803,6 @@ function ExtractROIs(_aktline, _type){
}
}
function getNUMBERS(_name, _type, _create = true) {
_pospunkt = _name.indexOf (".");
@@ -879,7 +862,6 @@ function getNUMBERS(_name, _type, _create = true) {
return neuroi;
}
function CopyReferenceToImgTmp(_domainname) {
for (index = 0; index < 2; ++index) {
_filenamevon = REFERENCES[index]["name"];
@@ -894,12 +876,10 @@ function CopyReferenceToImgTmp(_domainname) {
}
}
function GetReferencesInfo(){
return REFERENCES;
}
function UpdateConfigReferences(_domainname){
for (var index = 0; index < 2; ++index) {
_filenamenach = REFERENCES[index]["name"];
@@ -914,7 +894,6 @@ function UpdateConfigReferences(_domainname){
}
}
function UpdateConfigReference(_anzneueref, _domainname){
var index = 0;
@@ -939,12 +918,10 @@ function UpdateConfigReference(_anzneueref, _domainname){
FileCopyOnServer(_filenamevon, _filenamenach, _domainname);
}
function getNUMBERInfo(){
return NUMBERS;
}
function RenameNUMBER(_alt, _neu){
if ((_neu.indexOf(".") >= 0) || (_neu.indexOf(",") >= 0) || (_neu.indexOf(" ") >= 0) || (_neu.indexOf("\"") >= 0)) {
return "Number sequence name must not contain , . \" or a space";
@@ -972,7 +949,6 @@ function RenameNUMBER(_alt, _neu){
return "";
}
function DeleteNUMBER(_delete){
if (NUMBERS.length == 1) {
return "One number sequence is mandatory. Therefore this cannot be deleted"
@@ -993,7 +969,6 @@ function DeleteNUMBER(_delete){
return "";
}
function CreateNUMBER(_numbernew){
found = false;
@@ -1041,7 +1016,6 @@ function CreateNUMBER(_numbernew){
return "";
}
function getROIInfo(_typeROI, _number){
index = -1;
@@ -1059,7 +1033,6 @@ function getROIInfo(_typeROI, _number){
}
}
function RenameROI(_number, _type, _alt, _neu){
if ((_neu.includes("=")) || (_neu.includes(".")) || (_neu.includes(":")) || (_neu.includes(",")) || (_neu.includes(";")) || (_neu.includes(" ")) || (_neu.includes("\""))) {
return "ROI name must not contain . : , ; = \" or space";
@@ -1098,7 +1071,6 @@ function RenameROI(_number, _type, _alt, _neu){
return "";
}
function DeleteNUMBER(_delte) {
if (NUMBERS.length == 1) {
return "The last number cannot be deleted"
@@ -1119,7 +1091,6 @@ function DeleteNUMBER(_delte) {
return "";
}
function CreateROI(_number, _type, _pos, _roinew, _x, _y, _dx, _dy, _CCW){
_indexnumber = -1;

View File

@@ -34,13 +34,11 @@ p {font-size: 1em;}
<script>
function doReboot() {
// if (confirm("Are you sure you want to reboot the ESP32?")) {
var stringota = getDomainname() + "/reboot";
window.location = stringota;
window.location.href = stringota;
window.location.assign(stringota);
window.location.replace(stringota);
// }
var stringota = getDomainname() + "/reboot";
window.location = stringota;
window.location.href = stringota;
window.location.assign(stringota);
window.location.replace(stringota);
}
</script>

View File

@@ -29,7 +29,7 @@
// var xhttp = new XMLHttpRequest();
// xhttp.onreadystatechange = function() {if (xhttp.readyState == 4) {if (xhttp.status == 200) {document.reload();}}};
if (!file.name.includes("remote-setup")){
if (!confirm("The zip file name should contain \"...remote-setup...\". Are you sure that you have downloaded the correct file?"))
if (!confirm("The zip file name should contain \"...remote-setup...\". Are you sure you have downloaded the correct file?"))
return;
}
@@ -41,7 +41,7 @@ if (!file.name.includes("remote-setup")){
var file = document.getElementById("newfile").files[0];
if (!file.name.includes("remote-setup"))
{
if (!confirm("The zip file name should contain \"...remote-setup...\". Are you sure that you have downloaded the correct file?"))
if (!confirm("The zip file name should contain \"...remote-setup...\". Are you sure you have downloaded the correct file?"))
return;
}

3
webinstaller/Readme.md Normal file
View File

@@ -0,0 +1,3 @@
# Webinstaller
This folder is used to provide the required files to generate the [Web-Installer](https://jomjol.github.io/AI-on-the-edge-device/).
The Webinstaller gets automatically updated on a release using the Github actions.

View File

@@ -0,0 +1,2 @@
# Binary folder of the webinstaller
The firmware itself (`firmware.bin`) gets copied to this folder through the Github action.

View File

@@ -67,21 +67,19 @@
<div class="footer">
<div class="footer-section">
<span>Support & Contact Us</span>
<span>Support & Contact</span>
<a href="https://github.com/jomjol/AI-on-the-edge-device" target="_blank" title="GitHub">
<img src="https://github.com/jomjol/AI-on-the-edge-device/images/github-logo.png" alt="GitHub">
</a>
<img src="https://github.com/jomjol/AI-on-the-edge-device/images/gmail-logo.png" alt="Email">
<img src="https://raw.githubusercontent.com/jomjol/AI-on-the-edge-device/refs/heads/main/images/github-logo.png" alt="GitHub">
</a>
<a href="https://github.com/jomjol/AI-on-the-edge-device/discussions" target="_blank" title="GitHub">
<img src="https://github.com/jomjol/AI-on-the-edge-device/images/discussion-logo" alt="GitHub">
<img src="https://raw.githubusercontent.com/jomjol/AI-on-the-edge-device/refs/heads/main/images/discussion-logo.png" alt="GitHub">
</a>
</div>
<div class="footer-section">
<span>Donations</span>
<a href="https://www.paypal.com/donate?hosted_button_id=8TRSVYNYKDSWL" target="_blank" title="Donate via PayPal">
<img src="https://github.com/jomjol/AI-on-the-edge-device/images/paypal.png" alt="PayPal" style="width: 60px; height: auto;">
<img src="https://raw.githubusercontent.com/jomjol/AI-on-the-edge-device/refs/heads/main/images/paypal.png" alt="PayPal" style="width: 60px; height: auto;">
</a>
</div>
</div>