Compare commits

...

85 Commits

Author SHA1 Message Date
CaCO3
03c84a1ff3 Release 15.1.1 (#2232)
* Testcase for #2145 and debug-log (#2151)

* new models ana-cont-11.0.5, ana-class100-1.5.7, dig-class100-1.6.0

* Testcase for #2145
Added debug log, if allowNegativeRates is handeled

* Fix timezone config parser (#2169)

* make sure to parse the whole config line

* fix crash on empty timezone parameter

---------

Co-authored-by: CaCO3 <caco@ruinelli.ch>

* Enhance ROI pages (#2161)

* Check if the ROIs are equidistant. Only if not, untick the checkbox

* renaming

* Check if the ROIs have same y, dy and dx. If so, tick the sync checkbox

* only allow editing space when box is checked

* fix sync check

* show inner frame on all ROIs

* cleanup

* Check if the ROIs have same dy and dx. If so, tick the sync checkbox

* checkbox position

* renaming

* renaming

* show inner frame and cross hairs on all ROIs

* update ROIs on ticking checkboxes

* show timezone hint

* fix deleting last ROI

* cleanup

---------

Co-authored-by: CaCO3 <caco@ruinelli.ch>

* restart timeout on progress, catch error (#2170)

* restart timeout on progress, catch error

* .

---------

Co-authored-by: CaCO3 <caco@ruinelli.ch>

* BugFix #2167

* Release 15.1 preparations (#2171)

* Update Changelog.md

* Update Changelog.md

* Update Changelog.md

* Update changelog

* Fix links to PR

* Formating

* 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

* Update Changelog.md

* Update Changelog.md

* Update Changelog.md

---------

Co-authored-by: Slider0007 <jobbelle@gmx.net>
Co-authored-by: Slider0007 <115730895+Slider0007@users.noreply.github.com>

* fix typo

* Replace relative documentation links with absolute ones pointing to the external documentation (#2180)

Co-authored-by: CaCO3 <caco@ruinelli.ch>

* Sort model files in configuration combobox (#2189)

* new models ana-cont-11.0.5, ana-class100-1.5.7, dig-class100-1.6.0

* Testcase for #2145
Added debug log, if allowNegativeRates is handeled

* Sort model files in combobox

* reboot task - increase stack size (#2201)

Avoid stack overflow

* Update interface_influxdb.cpp

* Update Changelog.md

* Show PSRAM usage (#2206)

* centralize PSRAM usage (application code only)

* update logging

* update logging

* fix use after free

* initialize buffer

* free rgb_image before ussing it for new allocation

* use wrapper function

* switch log level to debug

* .

* undo adding free() calls

* .

* add names to all CImage instances

* .

* .

* .

* revert changes of stbi_image_free() with free_psram_heap() on the places where is is not in PSRAM

* .

* typos

* typo

* Added MQTT Outbox explanation/warning

* added CONFIG_SPIRAM_USE_MEMMAP explanation

---------

Co-authored-by: CaCO3 <caco@ruinelli.ch>

* Disable custom MQTT Outbox. This also moves the MQTT Publishing memory usage back to the internal RAM!

* log MQTT connection refused reasons (#2216)

* Revert PSRAM usage as it lead to memory fragmentation. (#2224)

See https://github.com/jomjol/AI-on-the-edge-device/issues/2200 for details

Co-authored-by: CaCO3 <caco@ruinelli.ch>

* Fix missing value data in graph (#2230)

* fix missing value data

---------

Co-authored-by: CaCO3 <caco@ruinelli.ch>

* Update Changelog.md (#2231)

---------

Co-authored-by: Frank Haverland <fspapaping@googlemail.com>
Co-authored-by: CaCO3 <caco@ruinelli.ch>
Co-authored-by: jomjol <30766535+jomjol@users.noreply.github.com>
Co-authored-by: Slider0007 <jobbelle@gmx.net>
Co-authored-by: Slider0007 <115730895+Slider0007@users.noreply.github.com>
2023-03-23 21:38:43 +01:00
jomjol
b5e0d6ee66 Implement InfluxDBv1 and minor changes (#2211)
* Testcase for #2145 and debug-log (#2151)

* new models ana-cont-11.0.5, ana-class100-1.5.7, dig-class100-1.6.0

* Testcase for #2145
Added debug log, if allowNegativeRates is handeled

* Fix timezone config parser (#2169)

* make sure to parse the whole config line

* fix crash on empty timezone parameter

---------

Co-authored-by: CaCO3 <caco@ruinelli.ch>

* Enhance ROI pages (#2161)

* Check if the ROIs are equidistant. Only if not, untick the checkbox

* renaming

* Check if the ROIs have same y, dy and dx. If so, tick the sync checkbox

* only allow editing space when box is checked

* fix sync check

* show inner frame on all ROIs

* cleanup

* Check if the ROIs have same dy and dx. If so, tick the sync checkbox

* checkbox position

* renaming

* renaming

* show inner frame and cross hairs on all ROIs

* update ROIs on ticking checkboxes

* show timezone hint

* fix deleting last ROI

* cleanup

---------

Co-authored-by: CaCO3 <caco@ruinelli.ch>

* restart timeout on progress, catch error (#2170)

* restart timeout on progress, catch error

* .

---------

Co-authored-by: CaCO3 <caco@ruinelli.ch>

* BugFix #2167

* Release 15.1 preparations (#2171)

* Update Changelog.md

* Update Changelog.md

* Update Changelog.md

* Update changelog

* Fix links to PR

* Formating

* 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

* Update Changelog.md

* Update Changelog.md

* Update Changelog.md

---------

Co-authored-by: Slider0007 <jobbelle@gmx.net>
Co-authored-by: Slider0007 <115730895+Slider0007@users.noreply.github.com>

* fix typo

* Replace relative documentation links with absolute ones pointing to the external documentation (#2180)

Co-authored-by: CaCO3 <caco@ruinelli.ch>

* Sort model files in configuration combobox (#2189)

* new models ana-cont-11.0.5, ana-class100-1.5.7, dig-class100-1.6.0

* Testcase for #2145
Added debug log, if allowNegativeRates is handeled

* Sort model files in combobox

* reboot task - increase stack size (#2201)

Avoid stack overflow

* Update interface_influxdb.cpp

* Update Changelog.md

---------

Co-authored-by: Frank Haverland <fspapaping@googlemail.com>
Co-authored-by: CaCO3 <caco3@ruinelli.ch>
Co-authored-by: CaCO3 <caco@ruinelli.ch>
Co-authored-by: Slider0007 <jobbelle@gmx.net>
Co-authored-by: Slider0007 <115730895+Slider0007@users.noreply.github.com>
2023-03-19 18:31:01 +01:00
CaCO3
7d33c3ee5f release 15.1.0 (#2172)
* Testcase for #2145 and debug-log (#2151)

* new models ana-cont-11.0.5, ana-class100-1.5.7, dig-class100-1.6.0

* Testcase for #2145
Added debug log, if allowNegativeRates is handeled

* Fix timezone config parser (#2169)

* make sure to parse the whole config line

* fix crash on empty timezone parameter

---------

Co-authored-by: CaCO3 <caco@ruinelli.ch>

* Enhance ROI pages (#2161)

* Check if the ROIs are equidistant. Only if not, untick the checkbox

* renaming

* Check if the ROIs have same y, dy and dx. If so, tick the sync checkbox

* only allow editing space when box is checked

* fix sync check

* show inner frame on all ROIs

* cleanup

* Check if the ROIs have same dy and dx. If so, tick the sync checkbox

* checkbox position

* renaming

* renaming

* show inner frame and cross hairs on all ROIs

* update ROIs on ticking checkboxes

* show timezone hint

* fix deleting last ROI

* cleanup

---------

Co-authored-by: CaCO3 <caco@ruinelli.ch>

* restart timeout on progress, catch error (#2170)

* restart timeout on progress, catch error

* .

---------

Co-authored-by: CaCO3 <caco@ruinelli.ch>

* BugFix #2167

* Release 15.1 preparations (#2171)

* Update Changelog.md

* Update Changelog.md

* Update Changelog.md

* Update changelog

* Fix links to PR

* Formating

* 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

* Update Changelog.md

* Update Changelog.md

* Update Changelog.md

---------

Co-authored-by: Slider0007 <jobbelle@gmx.net>
Co-authored-by: Slider0007 <115730895+Slider0007@users.noreply.github.com>

---------

Co-authored-by: Frank Haverland <fspapaping@googlemail.com>
Co-authored-by: CaCO3 <caco@ruinelli.ch>
Co-authored-by: jomjol <30766535+jomjol@users.noreply.github.com>
Co-authored-by: Slider0007 <jobbelle@gmx.net>
Co-authored-by: Slider0007 <115730895+Slider0007@users.noreply.github.com>
2023-03-12 22:52:30 +01:00
jomjol
53cee961f4 Update Changelog.md 2023-03-11 21:32:14 +01:00
jomjol
c81f901a19 Merge branch 'master' of https://github.com/jomjol/AI-on-the-edge-device 2023-03-11 21:21:12 +01:00
CaCO3
6642f6f995 Merge branch 'rolling' into master 2023-03-11 09:03:52 +01:00
Slider0007
431df73e52 Fix mqtt enable (#2144) 2023-03-08 17:42:43 +01:00
jomjol
1f5d4de5f3 Cleanup tflite (#2133)
* Clean & Update

* Update tflite
2023-03-06 07:14:21 +01:00
Slider0007
ce00192684 Print startblock after NTP snyc only once (#2129) 2023-03-05 19:14:45 +01:00
Yveaux
28f3ad0242 Update FeatureRequest.md (#2130)
Added #36 Run demo without camera
2023-03-05 17:37:51 +01:00
CaCO3
2dfd55e1c3 move Wifi, LWIP and BSSI to PSRAm (#2117)
Co-authored-by: CaCO3 <caco@ruinelli.ch>
2023-03-05 08:43:37 +01:00
CaCO3
f84f20b2e8 Set CPU frequency (#2125)
* move Wifi, LWIP and BSSI to PSRAm

* Allow setting the CPU frequency to 240 MHz (default: 160 MHz)

* .

* .

* .

* .

* Update sdkconfig.defaults

---------

Co-authored-by: CaCO3 <caco@ruinelli.ch>
2023-03-05 08:31:07 +01:00
CaCO3
806adcb4d0 Use PSRAM for MQTT publishing and make it scheduled (#2113)
* Run the Homeassistant Discovery directly after connecting to the broker. Before it was delayed 10s and happened while the first round alredy was in progress

* schedule sending HA discovery and static topics

* Allow setting QOS for MQTT topics

* .

* .

* change MQTT QOS1 expiration time from (default) 30 to 5s

* add logging of heap change on MQTT topic sendings

* wait for MQTT transmission timeout after publishing

* use QOS0 for Homeassistant Discovery topics. the messages then could possibly get lost but we save a lot of heap

* .

* use PSRAM for the MQTT outbox

* use QOS1 for HA discovery again

* .

* .

* disable delay, not needed with PSRAM

* .

* consolidated scheduledSendingOf_DiscoveryAndStaticTopics into sendingOf_DiscoveryAndStaticTopics_scheduled

* Send Homeasstsiatnt Discovery and static data in MQTT step instead of when the wifi gets connected

* "WIFI roaming" by channel scan (AP switching at low RSSI) (#2120)

* Activate 802.11kv wifi mesh roaming

* Activate roaming by scanning

* Revert stack reducation

* move Wifi, LWIP and BSSI to PSRAm

* added State Class "measurement" to rate_per_time_unit (#2116)

Co-authored-by: CaCO3 <caco@ruinelli.ch>

* use QOS0 for Homeassistant Discovery topics. the messages then could possibly get lost but we save a lot of heap

* use QOS0 for Homeassistant Discovery topics. the messages then could possibly get lost but we save a lot of heap

# Conflicts:
#	code/components/jomjol_mqtt/server_mqtt.cpp

# Conflicts:
#	code/components/jomjol_mqtt/server_mqtt.cpp

* .

* .

* move to next PR

* Update code/components/jomjol_mqtt/server_mqtt.cpp

* Update code/components/jomjol_mqtt/server_mqtt.cpp

---------

Co-authored-by: CaCO3 <caco@ruinelli.ch>
Co-authored-by: Slider0007 <115730895+Slider0007@users.noreply.github.com>
2023-03-05 07:57:54 +01:00
Attila Darazs
4dc4752823 Add GJ (gigajoule) as an energy meter unit (#2114)
Some district heating meters are displaying their values in GJ and Home
Assistant is also capable of accepting heating values in this format.
2023-03-04 18:31:32 +01:00
CaCO3
827423023c added State Class "measurement" to rate_per_time_unit (#2116)
Co-authored-by: CaCO3 <caco@ruinelli.ch>
2023-03-04 13:16:23 +01:00
Slider0007
6ca7897fa0 "WIFI roaming" by channel scan (AP switching at low RSSI) (#2120)
* Activate 802.11kv wifi mesh roaming

* Activate roaming by scanning

* Revert stack reducation
2023-03-04 11:49:08 +01:00
CaCO3
39b8b5b07c updated log message 2023-03-02 20:44:21 +01:00
Slider0007
5b98acaa32 ImageQuality: Set low input limit to 8 (#2108) 2023-03-01 21:04:04 +01:00
Slider0007
d3d241c7b9 Logfile: Print start indication block after time sync (#2106)
* logfile: Print start indication after time sync

* No time set at boot -> keep log_1970-01-01.txt
2023-03-01 20:55:12 +01:00
CaCO3
8c7e86fea4 Master (#2104)
* Replace deprecated actions (#2016)

* Update build.yaml

* Update build.yaml

* Update manual-update-webinstaller.yml

* Update manual-update-webinstaller.yml

* Update manual-update-webinstaller.yml

* Update manual-update-webinstaller.yml

* Update build.yaml

* preparations for v15.0 (#2063)

* Migrated parameters, see https://github.com/jomjol/AI-on-the-edge-device/pull/2023

* remove no longer used "topic" parameter. This is a backport from b21e3c6c9d

* Fix wrong url-encoding, see https://github.com/jomjol/AI-on-the-edge-device/issues/2036 resp. https://github.com/jomjol/AI-on-the-edge-device/issues/2036

* Threashold -> Threshold

* updated changelog

---------

Co-authored-by: CaCO3 <caco@ruinelli.ch>

* DataLogActive is true by default

* updated changelog

* Bugfix #1933 (again :-))

* Update Influxdb

* re-add missing dropdownbox filling for Postprocessing Individual Parameters

* stop auto filling the release notes, it causes more confusion than it helps

* Update Changelog.md

---------

Co-authored-by: CaCO3 <caco@ruinelli.ch>
Co-authored-by: jomjol <30766535+jomjol@users.noreply.github.com>
2023-03-01 20:54:14 +01:00
CaCO3
98d85b2c6c Merge branch 'rolling' into master 2023-02-28 22:01:05 +01:00
CaCO3
f42e9c71f2 Update Changelog.md 2023-02-28 21:39:50 +01:00
CaCO3
00be21f9e1 stop auto filling the release notes, it causes more confusion than it helps 2023-02-28 21:37:30 +01:00
CaCO3
c694d9f363 re-add missing dropdownbox filling for Postprocessing Individual Parameters 2023-02-28 21:35:54 +01:00
CaCO3
a7dc37761b The raw topic should have no unit and must be State Class "None", so it is handled as a string (#2103)
Co-authored-by: CaCO3 <caco@ruinelli.ch>
2023-02-28 20:57:15 +01:00
Slider0007
2dd2d03f6c SD card basic R/W check + folder/file presence check (#2085)
* SD card basic RW check + folder structure check

* Default LED blink repeat 2x

* Abort booting when SD basic R/W check failed

* SD R/W error+missing folder,file > load reduced UI
2023-02-28 18:25:27 +01:00
jomjol
4c407499d2 Update Influxdb 2023-02-27 22:19:10 +01:00
jomjol
b97d808b54 Merge branch 'bugfix-influxdb-v15.0.1' 2023-02-27 21:55:38 +01:00
jomjol
9f2e91a9df Bugfix #1933 (again :-)) 2023-02-27 21:54:50 +01:00
jomjol
18e96d62a6 Update readconfigparam.js 2023-02-27 19:46:15 +01:00
CaCO3
a1a77ae5d9 Improve MQTT (#2091)
* moved functions

* use hostname as default MQTT maintopic if parameter is not set

* use hostname as default MQTT client ID

* Only send Homassistant Discovery and Static Topics on the first connect. Retry in next round if any topic failed

* .

* add missing return code usage

* send maintopic/connection on every round like the system topics

---------

Co-authored-by: CaCO3 <caco@ruinelli.ch>
2023-02-27 18:26:46 +01:00
Frank Haverland
c5b20f3680 new models ana-cont-11.0.5, ana-class100-1.5.7, dig-class100-1.6.0 (#2089) 2023-02-27 18:25:30 +01:00
CaCO3
add6cf5c33 catch empty values (#2096)
Co-authored-by: CaCO3 <caco@ruinelli.ch>
2023-02-27 14:38:46 +01:00
CaCO3
493bd4df2f use a plotly version which only provides the basics, see https://github.com/plotly/plotly.js/blob/master/dist/README.md#plotlyjs-basic (#2095)
Co-authored-by: CaCO3 <caco@ruinelli.ch>
2023-02-27 14:22:47 +01:00
CaCO3
53ff190860 enhance data pages (#2088)
* enhance data pages

* add checkbox to show relative values

* remove static entries, they get overwritten by dynamic oes

---------

Co-authored-by: CaCO3 <caco@ruinelli.ch>
2023-02-26 23:08:39 +01:00
CaCO3
9a9aa68a65 do not show value on recognition page (#2083)
* do not show value on recognition page

* .

* .

* .

* .

---------

Co-authored-by: CaCO3 <caco@ruinelli.ch>
2023-02-26 18:28:26 +01:00
CaCO3
a92cb69067 Fix missing number list (#2082)
* remove unused label references

* fix missing number list

---------

Co-authored-by: CaCO3 <caco@ruinelli.ch>
2023-02-25 22:03:16 +01:00
CaCO3
90fa44380c Migrate some UART logs to Logfile logs (#2079)
* repolaced ESP_LOGE() with LogFile.WriteToFile(ESP_LOG_ERROR()

* converted more UART logs to logfile logs

---------

Co-authored-by: CaCO3 <caco@ruinelli.ch>
2023-02-25 21:18:56 +01:00
Slider0007
7a9f61a8d8 Boot phase: Add more error handling + provide verbose output in error cases (#2020)
* WLAN: add error handling

* WLAN: parameter global struct

* WLAN.ini -> more info text

* RSSIThreshold

* Rename logs

* Boot process: error handling

* Update texts

* Comments

* Init sequence

* Prepare for check dir creation

* add check makedir, update logs

* Blink code for OTA+SoftAP

* Blink code for missing time snyc

* Update

* reboot -> switch LED off

* Update log texts

* Update

* Update log texts

* create empty default folders at startup

* Update

* Adapt log level

* Print log level switch

* Update

* Update text

* Add SD free space to log

* WIFI/MQTT disconnect message set to WARN (+ ERROR)
2023-02-25 21:12:52 +01:00
CaCO3
a8f8189543 Keep the html folder on a flast structure (no subfolders). Else the OTA fails until >15.0.x because it does not create the needed subfolders (#2080)
Co-authored-by: CaCO3 <caco@ruinelli.ch>
2023-02-25 18:27:43 +01:00
CaCO3
2c4bda9e66 Restructure webui menu (#2070)
Co-authored-by: CaCO3 <caco@ruinelli.ch>
2023-02-25 14:21:16 +01:00
CaCO3
7b2a80a13d Enhance pages (#2075)
* enhance pages

* .

* .

* .

* .

* .

* .

---------

Co-authored-by: CaCO3 <caco@ruinelli.ch>
2023-02-25 14:20:55 +01:00
CaCO3
95d312b920 Update Changelog.md 2023-02-24 20:23:20 +01:00
CaCO3
886cd4ffa5 Master (#2073)
* Replace deprecated actions (#2016)

* Update build.yaml

* Update build.yaml

* Update manual-update-webinstaller.yml

* Update manual-update-webinstaller.yml

* Update manual-update-webinstaller.yml

* Update manual-update-webinstaller.yml

* Update build.yaml

* preparations for v15.0 (#2063)

* Migrated parameters, see https://github.com/jomjol/AI-on-the-edge-device/pull/2023

* remove no longer used "topic" parameter. This is a backport from b21e3c6c9d

* Fix wrong url-encoding, see https://github.com/jomjol/AI-on-the-edge-device/issues/2036 resp. https://github.com/jomjol/AI-on-the-edge-device/issues/2036

* Threashold -> Threshold

* updated changelog

---------

Co-authored-by: CaCO3 <caco@ruinelli.ch>

* DataLogActive is true by default

* updated changelog

---------

Co-authored-by: CaCO3 <caco@ruinelli.ch>
2023-02-24 20:22:04 +01:00
CaCO3
10e0435383 Merge branch 'rolling'
# Conflicts:
#	Changelog.md
#	code/components/jomjol_helper/Helper.cpp
#	code/components/jomjol_helper/Helper.h
#	sd-card/html/edit_config_param.html
#	sd-card/html/readconfigparam.js
2023-02-24 20:19:45 +01:00
CaCO3
b0de37b762 Move parameter doc to use tooltips generated using the online documentation (#2059)
* prework

* add tooltips

* .

* Update build.yaml

* remove tooltip link and embed it directly

* embedd tooltip directly

* .

* .

* .

* .

* .

* .

* .

* .

* .

* .

* fix disabled tooltips

* fix disabled tooltips

* renamed Parameter in UI, added units, fixed labels

* .

* .

* .

---------

Co-authored-by: CaCO3 <caco@ruinelli.ch>
2023-02-24 18:36:26 +01:00
CaCO3
22e4b39f77 updated changelog 2023-02-23 23:40:57 +01:00
CaCO3
2c1a7f4c9e DataLogActive is true by default 2023-02-23 23:39:02 +01:00
CaCO3
3960823439 DataLogActive is true by default 2023-02-23 23:38:36 +01:00
CaCO3
d5ef08546a preparations for v15.0 (#2063)
* Migrated parameters, see https://github.com/jomjol/AI-on-the-edge-device/pull/2023

* remove no longer used "topic" parameter. This is a backport from b21e3c6c9d

* Fix wrong url-encoding, see https://github.com/jomjol/AI-on-the-edge-device/issues/2036 resp. https://github.com/jomjol/AI-on-the-edge-device/issues/2036

* Threashold -> Threshold

* updated changelog

---------

Co-authored-by: CaCO3 <caco@ruinelli.ch>
2023-02-22 23:36:18 +01:00
CaCO3
3d711f495e updated save message 2023-02-22 23:20:52 +01:00
jomjol
b21e3c6c9d Remove ";Topic = ", Delete Checkbox "AllowNegativeRate" 2023-02-22 21:38:31 +01:00
CaCO3
23d2ae627d Update config.ini 2023-02-22 11:33:44 +01:00
CaCO3
3f62abf878 add missing config.ini entries for InfluxDBv2 2023-02-22 09:42:25 +01:00
CaCO3
025f4af9f2 V14.1 backport to rolling (#2058)
* Migrate parameters to v14.1 branch (#2023)

* Migrated parameters

* -

* .

* .

* .

* .

* .

* Remove unneeded checkboxes for true/false

* Remove ";"

* Correct MaintTopic

* Added missing parameters to UI: FlipImageSize, InitialMirror
Removed checkbox in UI for ErrorMessage
Added migration of pboolean parameters: enable them if they where disabled, set them to their default value, then enable them
Switch SetRetainFlag internally to a boolean

* .

* CamImages -> RawImages

* CamImages -> RawImages

* catch error on unknown parameter

* fix missing case insensitivity

* fix typo

* fixmissing rename

* fix migration of ExtendedResolution

* Delete ClassFlowMakeImage.cpp

* Delete ClassFlowMakeImage.h

---------

Co-authored-by: CaCO3 <caco@ruinelli.ch>
Co-authored-by: jomjol <30766535+jomjol@users.noreply.github.com>

* Update Changelog.md

---------

Co-authored-by: CaCO3 <caco@ruinelli.ch>
Co-authored-by: jomjol <30766535+jomjol@users.noreply.github.com>
2023-02-21 23:18:24 +01:00
CaCO3
3d92860c5e Updated web pages (#2055)
* allow longer file paths

* updated web pages

* updated the setup pages

* .

* Update server_file.cpp

---------

Co-authored-by: CaCO3 <caco@ruinelli.ch>
2023-02-21 22:35:50 +01:00
CaCO3
2ed9fb8eb5 Create folders as needed (#2056)
* allow longer file paths

* create folders as needed

---------

Co-authored-by: CaCO3 <caco@ruinelli.ch>
2023-02-21 22:14:09 +01:00
jomjol
598db004ae Update Changelog.md 2023-02-20 20:16:39 +01:00
jomjol
70332fe142 Update tflite 2023-02-20 20:03:39 +01:00
Slider0007
10da8c4f94 Support saturation & contrast (#2048)
* reference image: use correct value for contrast

* Update quality, contrast & saturation parameter

* fix for saturation,contrast+error handling
2023-02-19 21:15:16 +01:00
jomjol
5bac1c68d9 Correct BugFix in InfluxDB 2023-02-18 12:46:14 +01:00
jomjol
008dba7e11 fix for #2036 2023-02-18 11:44:56 +01:00
Yonz
7283bfd506 Update FeatureRequest.md (#2033)
Added:  #35 Use the same model, but provide the image from a Smartphone Camera
2023-02-16 21:05:34 +01:00
CaCO3
56788652ae Replace deprecated actions (#2016)
* Update build.yaml

* Update build.yaml

* Update manual-update-webinstaller.yml

* Update manual-update-webinstaller.yml

* Update manual-update-webinstaller.yml

* Update manual-update-webinstaller.yml

* Update build.yaml
2023-02-12 00:18:51 +01:00
CaCO3
44e186e65b Update backup.html (#2015) 2023-02-11 21:45:08 +01:00
jomjol
55be652dc1 Update Changelog.md 2023-02-09 20:16:18 +01:00
jomjol
1acd72d33e Implement InfluxDB v2 (#2004)
* Implement InfluxDBv2 Innitial

* Update incl. UI

* Correct UI

* Update UI - Indiv. Param.

* Update edit_config_param.html

* Correct Timeshift

* Update Fieldname
2023-02-09 20:13:08 +01:00
jomjol
795bcd0d21 Merge branch 'InfluxDB-Fix-Timeshift' into rolling 2023-02-08 20:31:17 +01:00
jomjol
0b2e38935b Update Changelog.md 2023-02-08 20:29:01 +01:00
jomjol
a9c5bebb45 Update interface_influxdb.cpp 2023-02-08 19:56:28 +01:00
Joerg Rosenkranz
876adc51af Fix small typo (#1995) 2023-02-08 19:54:10 +01:00
CaCO3
bf090f3762 Update README.md 2023-02-07 23:13:11 +01:00
CaCO3
30a50720e5 Update manual-update-webinstaller.yml 2023-02-05 20:45:25 +01:00
CaCO3
6c891ab7fe Update build.yaml 2023-02-05 20:35:02 +01:00
CaCO3
3fa16c5624 Use the preprocessed Web UI for the manual.zip (#1983)
* Update build.yaml

* Update build.yaml

* Update build.yaml
2023-02-05 15:31:04 +01:00
Slider0007
b9134f923e ota_page: Add missing quotation mark (#1977) 2023-02-05 08:10:59 +01:00
CaCO3
06f4d417b5 Cleanup config (#1972)
* Update config.ini

* Update config.ini

* Update config.ini
2023-02-05 08:10:29 +01:00
Slider0007
55efc3b3f4 wifi disconnect before deinit (#1978) 2023-02-05 08:09:53 +01:00
jomjol
800e231301 Update Changelog.md 2023-02-04 21:45:02 +01:00
jomjol
34a3d6d6e3 Merge branch 'Increase-max-JPG-size' into rolling 2023-02-04 21:38:37 +01:00
CaCO3
4bfe5422c5 fix broken webinstaller update 2023-02-03 21:46:08 +01:00
CaCO3
d63dc08f33 Update manual-update-webinstaller.yml 2023-02-03 21:43:40 +01:00
Slider0007
2ee85001eb Cleanup defines.h (#1967)
* Update defines.h

* Update: Disable USE_HIMEM_IF_AVAILABLE
2023-02-03 21:02:32 +01:00
Nicolas Liaudat
1d2f920819 Option alignment algo off (#1924)
* Update edit_config_param.html

* Update ClassFlowAlignment.cpp
2023-02-03 21:01:43 +01:00
jomjol
69583db99e Correct influx utc (#1964)
* Update interface_influxdb.cpp

* Correct to UTC
2023-02-02 19:14:55 +01:00
119 changed files with 12571 additions and 2406 deletions

View File

@@ -70,15 +70,24 @@ jobs:
#run: echo "Testing... ${{ github.ref_name }}, ${{ steps.vars.outputs.sha_short }}" > ./sd-card/html/version.txt; mkdir -p ./code/.pio/build/esp32cam/; cd ./code/.pio/build/esp32cam/; echo "${{ steps.vars.outputs.sha_short }}" > firmware.bin; cp firmware.bin partitions.bin; cp firmware.bin bootloader.bin # Testing
run: cd code; platformio run --environment esp32cam
- name: Prepare Web UI (copy data from repo and update hashes in all files)
- name: Prepare Web UI (copy data from repo, generate tooltip pages and update hashes in all files)
run: |
rm -rf ./html
mkdir html
cp ./sd-card/html/* ./html/
cp -r ./sd-card/html/* ./html/
python -m pip install markdown
mkdir html/param-tooltips
cd tools/parameter-tooltip-generator
bash generate-param-doc-tooltips.sh
cd ../..
cp -r ./sd-card/html/* ./html/
echo "Replacing variables..."
cd html; find . -type f -exec sed -i 's/$COMMIT_HASH/${{ steps.vars.outputs.sha_short }}/g' {} \;
#########################################################################################
## Pack for Update
#########################################################################################
@@ -86,7 +95,7 @@ jobs:
# New OTA concept
# update__version.zip file with following content:
# - /firmware.bin
# - (optional) /html/*
# - (optional) /html/* (inkl. subfolders)
# - (optional) /config/*.tfl
runs-on: ubuntu-latest
needs: build
@@ -149,7 +158,7 @@ jobs:
# New Remote Setup concept
# remote_setup__version.zip file with following content:
# - /firmware.bin
# - /html/*
# - /html/* (inkl. subfolders)
# - /config/*
runs-on: ubuntu-latest
needs: build
@@ -324,24 +333,23 @@ jobs:
# extract the version used in next step
- id: get_version
if: startsWith(github.ref, 'refs/tags/')
uses: battila7/get-version-action@v2
uses: Simply007/get-version-action@v2
# the changelog [unreleased] will now be changed to the release version
- name: Update changelog
uses: thomaseizinger/keep-a-changelog-new-release@v1
if: startsWith(github.ref, 'refs/tags/')
with:
changelogPath: Changelog.md
version: ${{ steps.get_version.outputs.version-without-v }}
# # the changelog [unreleased] will now be changed to the release version
# - name: Update changelog
# uses: thomaseizinger/keep-a-changelog-new-release@v1
# if: startsWith(github.ref, 'refs/tags/')
# with:
# changelogPath: Changelog.md
# version: ${{ steps.get_version.outputs.version-without-v }}
# the release notes will be extracted from changelog
- name: Extract release notes
id: extract-release-notes
if: startsWith(github.ref, 'refs/tags/')
uses: ffurrer2/extract-release-notes@v1
with:
changelog_file: Changelog.md
# # the release notes will be extracted from changelog
# - name: Extract release notes
# id: extract-release-notes
# if: startsWith(github.ref, 'refs/tags/')
# uses: ffurrer2/extract-release-notes@v1
# with:
# changelog_file: Changelog.md
# Releases should only be created on master by tagging the last commit.
# all artifacts in firmware folder pushed to the release
@@ -392,7 +400,7 @@ jobs:
- name: Get version of last release
id: last_release
uses: InsonusK/get-latest-release@v1.0.1
uses: mindojo/get-latest-release@0b8ef1434d7468d6bffcc8263baff5c777f72321
with:
myToken: ${{ github.token }}
exclude_types: "draft|prerelease"

View File

@@ -32,10 +32,10 @@ jobs:
- name: Get version of last release
id: last_release
uses: InsonusK/get-latest-release@v1.0.1
uses: mindojo/get-latest-release@0b8ef1434d7468d6bffcc8263baff5c777f72321
with:
myToken: ${{ github.token }}
exclude_types: "release"
exclude_types: "draft|prerelease"
view_top: 1
- name: Add binary to Web Installer and update manifest

2
.gitignore vendored
View File

@@ -23,3 +23,5 @@ CTestTestfile.cmake
_deps
code/edgeAI.code-workspace
.DS_Store
tools/parameter-tooltip-generator/html
tools/parameter-tooltip-generator/AI-on-the-edge-device-docs

View File

@@ -1,6 +1,137 @@
## [14.0.2] - 2023-02-05
## [15.1.1] - 2023-03-23
**Stabilization and Improved User Experience**
### Update Procedure
Update Procedure see [online documentation](https://jomjol.github.io/AI-on-the-edge-device-docs/Installation/#update-ota-over-the-air)
### Changes
For a full list of changes see [Full list of changes](https://github.com/jomjol/AI-on-the-edge-device/compare/v15.1.0...v15.1.1)
#### Added
- [#2206](https://github.com/jomjol/AI-on-the-edge-device/pull/2206) Log PSRAM usage
- [#2216](https://github.com/jomjol/AI-on-the-edge-device/pull/2216) Log MQTT connection refused reasons
#### Changed
- n.a.
#### Fixed
- [#2224](https://github.com/jomjol/AI-on-the-edge-device/pull/2224), [#2213](https://github.com/jomjol/AI-on-the-edge-device/pull/2213) Reverted some of the PSRAM usage changes due to negative sideffects
- [#2203](https://github.com/jomjol/AI-on-the-edge-device/issues/2203) Correct API for pure InfluxDB v1
- [#2180](https://github.com/jomjol/AI-on-the-edge-device/pull/2180) Fixed links in Parameter Documentation
- Various minor fixes
#### Removed
- n.a.
## [15.1.0] - 2023-03-12
### Update Procedure
Update Procedure see [online documentation](https://jomjol.github.io/AI-on-the-edge-device-docs/Installation/#update-ota-over-the-air)
:bangbang: Afterwards you should force-reload the Web Interface (usually Ctrl-F5 will do it)!
:bangbang: Afterwards you should check your configuration for errors!
### Changes
For a full list of changes see [Full list of changes](https://github.com/jomjol/AI-on-the-edge-device/compare/v15.0.3...v15.1.0)
#### Added
- The Configuration page has now tooltips with enhanced documentation
- MQTT:
- Added `GJ` (`gigajoule`) as an energy meter unit
- Removed State Class and unit from `raw` topic
- Various Improvements (Only send Homeassistant Discovery the first time we connect, ...) (https://github.com/jomjol/AI-on-the-edge-device/pull/2091
- Added Expert Parameter to change CPU Clock from `160` to `240 Mhz`
- SD card basic read/write check and a folder/file presence check at boot to indicate SD card issues or missing folders / files ([#2085](https://github.com/jomjol/AI-on-the-edge-device/pull/2085))
- Simplified "WIFI roaming" by client triggered channel scan (AP switching at low RSSI) -> using expert parameter "RSSIThreshold" ([#2120](https://github.com/jomjol/AI-on-the-edge-device/pull/2120))
- Log WLAN disconnect reason codes (see [WLAN disconnect reasons](https://jomjol.github.io/AI-on-the-edge-device-docs/WLAN-disconnect-reason))
- Support of InfluxDB v2 ([#2004](https://github.com/jomjol/AI-on-the-edge-device/pull/2004))
#### Changed
- Updated models (tflite files), removed old versions (https://github.com/jomjol/AI-on-the-edge-device/pull/2089, https://github.com/jomjol/AI-on-the-edge-device/pull/2133)
:bangbang: **Attention:** Update your configuration!
- Hybrid CNN network to `dig-cont_0611_s3`
- Analog CNN network to `ana-cont-11.0.5` and `ana-clas100-1.5.7`
- Digital CNN network to `dig-class100-1.6.0`
- Various Web interface Improvements/Enhancements:
- Restructured Menu (Needs cache clearing to be applied)
- Enhanced `Previous Value` page
- Improved/faster Graph page
- Various minor improvements
- ROI config pages improvements
- Improved Backup Functionality
- Added log file logs for Firmware Update
- Improved memory management (moved various stuff to external PSRAM, https://github.com/jomjol/AI-on-the-edge-device/pull/2117)
- Camera driver update: Support of contrast and saturation ([#2048](https://github.com/jomjol/AI-on-the-edge-device/pull/2048))
:bangbang: **Attention**: This could have impact to old configurations. Please check your configuration and potentially adapt parametrization, if detection is negativly affected.
- Improved error handling and provide more verbose output in error cases during boot phase ([#2020](https://github.com/jomjol/AI-on-the-edge-device/pull/2020))
- Red board LED is indicating more different errors and states (see [Status LED Blink Codes](https://jomjol.github.io/AI-on-the-edge-device-docs/StatusLED-BlinkCodes))
- Logfile: Print start indication block after time is synced to indicate start in logfile after a cold boot
- `Image Quality Index`: Limit lower input range to 8 to avoid system instabilities
#### Fixed
- Various minor fixes
- Added State Class "measurement" to rate_per_time_unit
- GPIO: Avoid MQTT publishing to empty topic when "MQTT enable" flag is not set
- Fix timezone config parser
- Remote Setup truncated long passwords (https://github.com/jomjol/AI-on-the-edge-device/issues/2167)
- Problem with timestamp in InfluxDB interface
#### Removed
- n.a.
## [15.0.3] - 2023-02-28
**Name: Parameter Migration**
### Update Procedure
Update Procedure see [online documentation](https://jomjol.github.io/AI-on-the-edge-device-docs/Installation/#update-ota-over-the-air)
:bangbang: Afterwards you should force-reload the Web Interface (usually Ctrl-F5 will do it).
### Changes
This release only migrates some parameters, see #2023 for details and a list of all parameter changes.
The parameter migration happens automatically on the next startup. No user interaction is required.
A backup of the config is stored on the SD-card as `config.bak`.
Beside of the parameter change and the bugfix listed below, no changes are contained in this release!
If you want to revert back to `v14` or earlier, you will have to revert the migration changes in `config.ini` manually!
#### Added
- n.a.
#### Changed
- [#2023](https://github.com/jomjol/AI-on-the-edge-device/pull/2023) Migrated Parameters
- Removed old `Topic` parameter, it is not used anymore
#### Fixed
- [#2036](https://github.com/jomjol/AI-on-the-edge-device/issues/2036) Fix wrong url-encoding
- **NEW v15.0.2:** [#1933](https://github.com/jomjol/AI-on-the-edge-device/issues/1933) Bugfix InfluxDB Timestamp
- **NEW v15.0.3:** Re-added lost dropdownbox filling for Postprocessing Individual Parameters
#### Removed
- n.a.
## [14.0.3] -2023-02-05
**Name: Stabilization and Improved User Experience**
Thanks to over 80 Pull Requests from 6 contributors, we can anounce another great release with many many improvements and new features:
@@ -54,7 +185,7 @@ For a full list of changes see [Full list of changes](https://github.com/jomjol/
## [13.0.8] - 2022-12-19
**Home Assistant MQTT Discovery Support**
**Name: Home Assistant MQTT Discovery Support**
### Update Procedure see [online documentation](https://jomjol.github.io/AI-on-the-edge-device-docs/Installation/#update-ota-over-the-air)
@@ -134,7 +265,7 @@ For a full list of changes see [Full list of changes](https://github.com/jomjol/
## [12.0.1] 2022-09-29
Improve **u**ser e**x**perience
Name: Improve **u**ser e**x**perience
:bangbang: The release breaks a few things in ota update :bangbang:
@@ -802,7 +933,10 @@ External Illumination
- Initial Version
[14.0.0]: https://github.com/jomjol/AI-on-the-edge-device/compare/v13.0.8...v14.0.2
[15.1.1]: https://github.com/jomjol/AI-on-the-edge-device/compare/v15.1.0...v15.1.1
[15.1.0]: https://github.com/jomjol/AI-on-the-edge-device/compare/v15.0.3...v15.1.0
[15.0.3]: https://github.com/jomjol/AI-on-the-edge-device/compare/v14.0.3...v15.0.3
[14.0.3]: https://github.com/jomjol/AI-on-the-edge-device/compare/v13.0.8...v14.0.3
[13.0.8]: https://github.com/jomjol/AI-on-the-edge-device/compare/v12.0.1...v13.0.8
[13.0.7]: https://github.com/jomjol/AI-on-the-edge-device/compare/v12.0.1...v13.0.7
[13.0.5]: https://github.com/jomjol/AI-on-the-edge-device/compare/v12.0.1...v13.0.5

View File

@@ -10,6 +10,14 @@
____
#### #36 Run demo without camera
Demo mode requires a working camera (if not, one receives a 'Cam bad' error). Would be nice to demo or play around on other ESP32 boards (or on ESP32-CAM boards when you broke the camera cable...).
#### #35 Use the same model, but provide the image from a Smartphone Camera
as reading the Electricity or Water meter every few minutues only delivers apparent accuracy (DE: "Scheingenauigkeit") you could just as well take a picture with your Smartphone evey so often (e.g. once a week when you are in the Basement anyway), then with some "semi clever" tricks pass this image to the model developed here, and the values then on to who ever needs them e.g. via MQTT.
IMO: It is not needed to have that many readings (datapoints) as our behaviour (Use of electricity or water) doesn't vary that much, say, over a weeks time. The interpolation between weekly readings will give sufficient information on the power and/or water usage.
#### #34 implement state and Roi for water leak detection
for example see Roi on the next picture..
![grafik](https://user-images.githubusercontent.com/38385805/207858812-2a6ba41d-1a8c-4fa1-9b6a-53cdd113c106.png)

View File

@@ -82,10 +82,10 @@ static void gpioHandlerTask(void *arg) {
void GpioPin::gpioInterrupt(int value) {
#ifdef ENABLE_MQTT
if (_mqttTopic != "") {
if (_mqttTopic.compare("") != 0) {
ESP_LOGD(TAG, "gpioInterrupt %s %d", _mqttTopic.c_str(), value);
MQTTPublish(_mqttTopic, value ? "true" : "false");
MQTTPublish(_mqttTopic, value ? "true" : "false", 1);
}
#endif //ENABLE_MQTT
currentState = value;
@@ -115,7 +115,7 @@ void GpioPin::init()
}
#ifdef ENABLE_MQTT
if ((_mqttTopic != "") && ((_mode == GPIO_PIN_MODE_OUTPUT) || (_mode == GPIO_PIN_MODE_OUTPUT_PWM) || (_mode == GPIO_PIN_MODE_BUILT_IN_FLASH_LED))) {
if ((_mqttTopic.compare("") != 0) && ((_mode == GPIO_PIN_MODE_OUTPUT) || (_mode == GPIO_PIN_MODE_OUTPUT_PWM) || (_mode == GPIO_PIN_MODE_BUILT_IN_FLASH_LED))) {
std::function<bool(std::string, char*, int)> f = std::bind(&GpioPin::handleMQTT, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
MQTTregisterSubscribeFunction(_mqttTopic, f);
}
@@ -141,8 +141,8 @@ void GpioPin::setValue(bool value, gpio_set_source setSource, std::string* error
gpio_set_level(_gpio, value);
#ifdef ENABLE_MQTT
if ((_mqttTopic != "") && (setSource != GPIO_SET_SOURCE_MQTT)) {
MQTTPublish(_mqttTopic, value ? "true" : "false");
if ((_mqttTopic.compare("") != 0) && (setSource != GPIO_SET_SOURCE_MQTT)) {
MQTTPublish(_mqttTopic, value ? "true" : "false", 1);
}
#endif //ENABLE_MQTT
}
@@ -153,7 +153,8 @@ void GpioPin::publishState() {
if (newState != currentState) {
ESP_LOGD(TAG,"publish state of GPIO %d new state %d", _gpio, newState);
#ifdef ENABLE_MQTT
MQTTPublish(_mqttTopic, newState ? "true" : "false");
if (_mqttTopic.compare("") != 0)
MQTTPublish(_mqttTopic, newState ? "true" : "false", 1);
#endif //ENABLE_MQTT
currentState = newState;
}
@@ -359,9 +360,9 @@ bool GpioHandler::readConfig()
gpio_int_type_t intType = resolveIntType(toLower(splitted[2]));
uint16_t dutyResolution = (uint8_t)atoi(splitted[3].c_str());
#ifdef ENABLE_MQTT
bool mqttEnabled = toLower(splitted[4]) == "true";
bool mqttEnabled = (toLower(splitted[4]) == "true");
#endif // ENABLE_MQTT
bool httpEnabled = toLower(splitted[5]) == "true";
bool httpEnabled = (toLower(splitted[5]) == "true");
char gpioName[100];
if (splitted.size() >= 7) {
strcpy(gpioName, trim(splitted[6]).c_str());

View File

@@ -7,6 +7,7 @@
#include "esp_log.h"
#include "Helper.h"
#include "statusled.h"
#include "CImageBasis.h"
#include "server_ota.h"
@@ -63,7 +64,6 @@ static camera_config_t camera_config = {
.fb_count = 1, //if more than one, i2s runs in continuous mode. Use only with JPEG
.fb_location = CAMERA_FB_IN_PSRAM, /*!< The location where the frame buffer will be allocated */
.grab_mode = CAMERA_GRAB_LATEST, // only from new esp32cam version
};
@@ -126,63 +126,97 @@ void CCamera::ledc_init(void)
}
static size_t jpg_encode_stream(void * arg, size_t index, const void* data, size_t len){
static size_t jpg_encode_stream(void * arg, size_t index, const void* data, size_t len)
{
jpg_chunking_t *j = (jpg_chunking_t *)arg;
if(!index){
if(!index) {
j->len = 0;
}
if(httpd_resp_send_chunk(j->req, (const char *)data, len) != ESP_OK){
if(httpd_resp_send_chunk(j->req, (const char *)data, len) != ESP_OK) {
return 0;
}
j->len += len;
return len;
}
bool CCamera::SetBrightnessContrastSaturation(int _brightness, int _contrast, int _saturation)
{
bool result = false;
sensor_t * s = esp_camera_sensor_get();
if (_brightness > -100)
_brightness = min(2, max(-2, _brightness));
if (_contrast > -100)
_contrast = min(2, max(-2, _contrast));
if (_saturation > -100)
_saturation = min(2, max(-2, _saturation));
_brightness = min(2, max(-2, _brightness));
_contrast = min(2, max(-2, _contrast));
_saturation = min(2, max(-2, _saturation));
if (_saturation > -100)
sensor_t * s = esp_camera_sensor_get();
if (s) {
s->set_saturation(s, _saturation);
if (_contrast > -100)
s->set_contrast(s, _contrast);
if (_brightness > -100)
s->set_brightness(s, _brightness);
if ((_brightness != brightness) && (_brightness > -100))
result = true;
if ((_contrast != contrast) && (_contrast > -100))
result = true;
if ((_saturation != saturation) && (_saturation > -100))
result = true;
if (_brightness > -100)
brightness = _brightness;
if (_contrast > -100)
contrast = _contrast;
if (_saturation > -100)
saturation = _saturation;
/* Workaround - bug in cam library - enable bits are set without using bitwise OR logic -> only latest enable setting is used */
/* Library version: https://github.com/espressif/esp32-camera/commit/5c8349f4cf169c8a61283e0da9b8cff10994d3f3 */
/* Reference: https://esp32.com/viewtopic.php?f=19&t=14376#p93178 */
/* The memory structure is as follows for
byte_0 = enable_bits
byte_0->bit0 = enable saturation and hue --> OK
byte_0->bit1 = enable saturation --> OK
byte_0->bit2 = enable brightness and contrast --> OK
byte_0->bit3 = enable green -> blue spitial effect (Antique and blunish and greenish and readdish and b&w) enable
byte_0->bit4 = anable gray -> read spitial effect (Antique and blunish and greenish and readdish and b&w) enable
byte_0->bit5 = remove (UV) in YUV color system
byte_0->bit6 = enable negative
byte_0->bit7 = remove (Y) in YUV color system
byte_1 = saturation1 0-255 --> ?
byte_2 = hue 0-255 --> OK
byte_3 = saturation2 0-255 --> OK
byte_4 = reenter saturation2 in documents --> ?
byte_5 = spital effect green -> blue 0-255 --> ?
byte_6 = spital effect gray -> read 0-255 --> ?
byte_7 = contrast lower byte 0-255 --> OK
byte_8 = contrast higher byte 0-255 --> OK
byte_9 = brightness 0-255 --> OK
byte_10= if byte_10==4 contrast effective --> ?
*/
if (result && isFixedExposure)
//s->set_reg(s, 0x7C, 0xFF, 2); // Optional feature - hue setting: Select byte 2 in register 0x7C to set hue value
//s->set_reg(s, 0x7D, 0xFF, 0); // Optional feature - hue setting: Hue value 0 - 255
s->set_reg(s, 0xFF, 0x01, 0); // Select DSP bank
s->set_reg(s, 0x7C, 0xFF, 0); // Select byte 0 in register 0x7C
s->set_reg(s, 0x7D, 7, 7); // Set bit 0, 1, 2 in register 0x7D to enable saturation, contrast, brightness and hue control
}
else {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "SetBrightnessContrastSaturation: Failed to get control structure");
}
if (((_brightness != brightness) || (_contrast != contrast) || (_saturation != saturation)) && isFixedExposure)
EnableAutoExposure(waitbeforepicture_org);
return result;
brightness = _brightness;
contrast = _contrast;
saturation = _saturation;
ESP_LOGD(TAG, "brightness %d, contrast: %d, saturation %d", brightness, contrast, saturation);
return true;
}
void CCamera::SetQualitySize(int qual, framesize_t resol)
{
sensor_t * s = esp_camera_sensor_get();
s->set_quality(s, qual);
s->set_framesize(s, resol);
qual = min(63, max(8, qual)); // Limit quality from 8..63 (values lower than 8 tent to be unstable)
sensor_t * s = esp_camera_sensor_get();
if (s) {
s->set_quality(s, qual);
s->set_framesize(s, resol);
}
else {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "SetQualitySize: Failed to get control structure");
}
ActualResolution = resol;
ActualQuality = qual;
@@ -191,41 +225,45 @@ void CCamera::SetQualitySize(int qual, framesize_t resol)
image_height = 240;
image_width = 320;
}
if (resol == FRAMESIZE_VGA)
else if (resol == FRAMESIZE_VGA)
{
image_height = 480;
image_width = 640;
}
}
void CCamera::EnableAutoExposure(int flash_duration)
{
ESP_LOGD(TAG, "EnableAutoExposure");
LEDOnOff(true);
if (flash_duration > 0)
if (flash_duration > 0) {
LightOnOff(true);
const TickType_t xDelay = flash_duration / portTICK_PERIOD_MS;
vTaskDelay( xDelay );
const TickType_t xDelay = flash_duration / portTICK_PERIOD_MS;
vTaskDelay( xDelay );
}
camera_fb_t * fb = esp_camera_fb_get();
esp_camera_fb_return(fb);
fb = esp_camera_fb_get();
if (!fb) {
ESP_LOGE(TAG, "Camera Capture Failed");
LEDOnOff(false);
LightOnOff(false);
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Capture Failed (Procedure 'EnableAutoExposure') --> Reboot! "
"Check that your camera module is working and connected properly.");
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "EnableAutoExposure: Capture Failed. "
"Check camera module and/or proper electrical connection");
//doReboot();
}
esp_camera_fb_return(fb);
sensor_t * s = esp_camera_sensor_get();
s->set_gain_ctrl(s, 0);
s->set_exposure_ctrl(s, 0);
if (s) {
s->set_gain_ctrl(s, 0);
s->set_exposure_ctrl(s, 0);
}
else {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "EnableAutoExposure: Failed to get control structure to set gain+exposure");
}
LEDOnOff(false);
LightOnOff(false);
@@ -237,22 +275,21 @@ void CCamera::EnableAutoExposure(int flash_duration)
esp_err_t CCamera::CaptureToBasisImage(CImageBasis *_Image, int delay)
{
#ifdef DEBUG_DETAIL_ON
LogFile.WriteHeapInfo("CCamera::CaptureToBasisImage - Start");
LogFile.WriteHeapInfo("CaptureToBasisImage - Start");
#endif
_Image->EmptyImage(); //Delete previous stored raw image -> black image
LEDOnOff(true);
if (delay > 0)
{
if (delay > 0) {
LightOnOff(true);
const TickType_t xDelay = delay / portTICK_PERIOD_MS;
vTaskDelay( xDelay );
}
#ifdef DEBUG_DETAIL_ON
LogFile.WriteHeapInfo("CCamera::CaptureToBasisImage - After LightOn");
LogFile.WriteHeapInfo("CaptureToBasisImage - After LightOn");
#endif
camera_fb_t * fb = esp_camera_fb_get();
@@ -262,9 +299,8 @@ esp_err_t CCamera::CaptureToBasisImage(CImageBasis *_Image, int delay)
LEDOnOff(false);
LightOnOff(false);
ESP_LOGE(TAG, "CaptureToBasisImage: Capture Failed");
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "is not working anymore (CCamera::CaptureToBasisImage) - most probably caused by a hardware problem (instablility, ...). "
"System will reboot.");
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "is not working anymore (CaptureToBasisImage) - most probably caused "
"by a hardware problem (instablility, ...). System will reboot.");
doReboot();
return ESP_FAIL;
@@ -275,12 +311,17 @@ esp_err_t CCamera::CaptureToBasisImage(CImageBasis *_Image, int delay)
loadNextDemoImage(fb);
}
CImageBasis* _zwImage = new CImageBasis();
_zwImage->LoadFromMemory(fb->buf, fb->len);
CImageBasis* _zwImage = new CImageBasis("zwImage");
if (_zwImage) {
_zwImage->LoadFromMemory(fb->buf, fb->len);
}
else {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "CaptureToBasisImage: Can't allocate _zwImage");
}
esp_camera_fb_return(fb);
#ifdef DEBUG_DETAIL_ON
LogFile.WriteHeapInfo("CCamera::CaptureToBasisImage - After fb_get");
LogFile.WriteHeapInfo("CaptureToBasisImage - After fb_get");
#endif
LEDOnOff(false);
@@ -292,7 +333,7 @@ esp_err_t CCamera::CaptureToBasisImage(CImageBasis *_Image, int delay)
// vTaskDelay( xDelay ); // wait for power to recover
#ifdef DEBUG_DETAIL_ON
LogFile.WriteHeapInfo("CCamera::CaptureToBasisImage - After LoadFromMemory");
LogFile.WriteHeapInfo("CaptureToBasisImage - After LoadFromMemory");
#endif
stbi_uc* p_target;
@@ -320,7 +361,7 @@ esp_err_t CCamera::CaptureToBasisImage(CImageBasis *_Image, int delay)
delete _zwImage;
#ifdef DEBUG_DETAIL_ON
LogFile.WriteHeapInfo("CCamera::CaptureToBasisImage - Done");
LogFile.WriteHeapInfo("CaptureToBasisImage - Done");
#endif
return ESP_OK;
@@ -333,8 +374,7 @@ esp_err_t CCamera::CaptureToFile(std::string nm, int delay)
LEDOnOff(true); // Switched off to save power !
if (delay > 0)
{
if (delay > 0) {
LightOnOff(true);
const TickType_t xDelay = delay / portTICK_PERIOD_MS;
vTaskDelay( xDelay );
@@ -344,11 +384,10 @@ esp_err_t CCamera::CaptureToFile(std::string nm, int delay)
esp_camera_fb_return(fb);
fb = esp_camera_fb_get();
if (!fb) {
ESP_LOGE(TAG, "CaptureToFile: Camera Capture Failed");
LEDOnOff(false);
LightOnOff(false);
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Capture Failed (CCamera::CaptureToFile) --> Reboot! "
"Check that your camera module is working and connected properly.");
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "CaptureToFile: Capture Failed. "
"Check camera module and/or proper electrical connection");
//doReboot();
return ESP_FAIL;
@@ -395,24 +434,21 @@ esp_err_t CCamera::CaptureToFile(std::string nm, int delay)
}
FILE * fp = fopen(nm.c_str(), "wb");
if (fp == NULL) /* If an error occurs during the file creation */
{
fprintf(stderr, "fopen() failed for '%s'\n", nm.c_str());
if (fp == NULL) { // If an error occurs during the file creation
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "CaptureToFile: Failed to open file " + nm);
}
else
{
else {
fwrite(buf, sizeof(uint8_t), buf_len, fp);
fclose(fp);
}
}
if (converted)
free(buf);
esp_camera_fb_return(fb);
if (delay > 0)
{
LightOnOff(false);
}
return ESP_OK;
}
@@ -420,29 +456,26 @@ esp_err_t CCamera::CaptureToFile(std::string nm, int delay)
esp_err_t CCamera::CaptureToHTTP(httpd_req_t *req, int delay)
{
camera_fb_t * fb = NULL;
esp_err_t res = ESP_OK;
size_t fb_len = 0;
int64_t fr_start = esp_timer_get_time();
LEDOnOff(true);
if (delay > 0)
{
if (delay > 0) {
LightOnOff(true);
const TickType_t xDelay = delay / portTICK_PERIOD_MS;
vTaskDelay( xDelay );
}
fb = esp_camera_fb_get();
camera_fb_t *fb = esp_camera_fb_get();
esp_camera_fb_return(fb);
fb = esp_camera_fb_get();
if (!fb) {
ESP_LOGE(TAG, "Camera capture failed");
LEDOnOff(false);
LightOnOff(false);
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "CaptureToFile: Capture Failed. "
"Check camera module and/or proper electrical connection");
httpd_resp_send_500(req);
// doReboot();
@@ -482,9 +515,7 @@ esp_err_t CCamera::CaptureToHTTP(httpd_req_t *req, int delay)
ESP_LOGI(TAG, "JPG: %uKB %ums", (uint32_t)(fb_len/1024), (uint32_t)((fr_end - fr_start)/1000));
if (delay > 0)
{
LightOnOff(false);
}
return res;
}
@@ -494,19 +525,18 @@ void CCamera::LightOnOff(bool status)
{
GpioHandler* gpioHandler = gpio_handler_get();
if ((gpioHandler != NULL) && (gpioHandler->isEnabled())) {
ESP_LOGD(TAG, "Use gpioHandler flashLigh");
ESP_LOGD(TAG, "Use gpioHandler to trigger flashlight");
gpioHandler->flashLightEnable(status);
} else {
}
else {
#ifdef USE_PWM_LEDFLASH
if (status)
{
if (status) {
ESP_LOGD(TAG, "Internal Flash-LED turn on with PWM %d", led_intensity);
ESP_ERROR_CHECK(ledc_set_duty(LEDC_MODE, LEDC_CHANNEL, led_intensity));
// Update duty to apply the new value
ESP_ERROR_CHECK(ledc_update_duty(LEDC_MODE, LEDC_CHANNEL));
}
else
{
else {
ESP_LOGD(TAG, "Internal Flash-LED turn off PWM");
ESP_ERROR_CHECK(ledc_set_duty(LEDC_MODE, LEDC_CHANNEL, 0));
ESP_ERROR_CHECK(ledc_update_duty(LEDC_MODE, LEDC_CHANNEL));
@@ -528,15 +558,17 @@ void CCamera::LightOnOff(bool status)
void CCamera::LEDOnOff(bool status)
{
// Init the GPIO
gpio_pad_select_gpio(BLINK_GPIO);
/* Set the GPIO as a push/pull output */
gpio_set_direction(BLINK_GPIO, GPIO_MODE_OUTPUT);
if (xHandle_task_StatusLED == NULL) {
// Init the GPIO
gpio_pad_select_gpio(BLINK_GPIO);
/* Set the GPIO as a push/pull output */
gpio_set_direction(BLINK_GPIO, GPIO_MODE_OUTPUT);
if (!status)
gpio_set_level(BLINK_GPIO, 1);
else
gpio_set_level(BLINK_GPIO, 0);
if (!status)
gpio_set_level(BLINK_GPIO, 1);
else
gpio_set_level(BLINK_GPIO, 0);
}
}
@@ -555,33 +587,33 @@ void CCamera::GetCameraParameter(httpd_req_t *req, int &qual, framesize_t &resol
ESP_LOGD(TAG, "Query: %s", _query);
if (httpd_query_key_value(_query, "size", _size, 10) == ESP_OK)
{
#ifdef DEBUG_DETAIL_ON
#ifdef DEBUG_DETAIL_ON
ESP_LOGD(TAG, "Size: %s", _size);
#endif
#endif
if (strcmp(_size, "QVGA") == 0)
resol = FRAMESIZE_QVGA; // 320x240
if (strcmp(_size, "VGA") == 0)
else if (strcmp(_size, "VGA") == 0)
resol = FRAMESIZE_VGA; // 640x480
if (strcmp(_size, "SVGA") == 0)
else if (strcmp(_size, "SVGA") == 0)
resol = FRAMESIZE_SVGA; // 800x600
if (strcmp(_size, "XGA") == 0)
else if (strcmp(_size, "XGA") == 0)
resol = FRAMESIZE_XGA; // 1024x768
if (strcmp(_size, "SXGA") == 0)
else if (strcmp(_size, "SXGA") == 0)
resol = FRAMESIZE_SXGA; // 1280x1024
if (strcmp(_size, "UXGA") == 0)
else if (strcmp(_size, "UXGA") == 0)
resol = FRAMESIZE_UXGA; // 1600x1200
}
if (httpd_query_key_value(_query, "quality", _qual, 10) == ESP_OK)
{
#ifdef DEBUG_DETAIL_ON
#ifdef DEBUG_DETAIL_ON
ESP_LOGD(TAG, "Quality: %s", _qual);
#endif
#endif
qual = atoi(_qual);
if (qual > 63)
if (qual > 63) // Limit to max. 63
qual = 63;
if (qual < 0)
qual = 0;
else if (qual < 8) // Limit to min. 8
qual = 8;
}
}
}
@@ -591,28 +623,29 @@ framesize_t CCamera::TextToFramesize(const char * _size)
{
if (strcmp(_size, "QVGA") == 0)
return FRAMESIZE_QVGA; // 320x240
if (strcmp(_size, "VGA") == 0)
else if (strcmp(_size, "VGA") == 0)
return FRAMESIZE_VGA; // 640x480
if (strcmp(_size, "SVGA") == 0)
else if (strcmp(_size, "SVGA") == 0)
return FRAMESIZE_SVGA; // 800x600
if (strcmp(_size, "XGA") == 0)
else if (strcmp(_size, "XGA") == 0)
return FRAMESIZE_XGA; // 1024x768
if (strcmp(_size, "SXGA") == 0)
else if (strcmp(_size, "SXGA") == 0)
return FRAMESIZE_SXGA; // 1280x1024
if (strcmp(_size, "UXGA") == 0)
return FRAMESIZE_UXGA; // 1600x1200
else if (strcmp(_size, "UXGA") == 0)
return FRAMESIZE_UXGA; // 1600x1200
return ActualResolution;
}
CCamera::CCamera()
{
#ifdef DEBUG_DETAIL_ON
ESP_LOGD(TAG, "CreateClassCamera");
#endif
brightness = -5;
contrast = -5;
saturation = -5;
#ifdef DEBUG_DETAIL_ON
ESP_LOGD(TAG, "CreateClassCamera");
#endif
brightness = 0;
contrast = 0;
saturation = 0;
isFixedExposure = false;
ledc_init();

View File

@@ -228,7 +228,7 @@ static esp_err_t http_resp_dir_html(httpd_req_t *req, const char *dirpath, const
if (chunksize > 0){
if (httpd_resp_send_chunk(req, chunk, chunksize) != ESP_OK) {
fclose(fd);
ESP_LOGE(TAG, "File sending failed!");
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "File sending failed!");
return ESP_FAIL;
}
}
@@ -267,7 +267,7 @@ static esp_err_t http_resp_dir_html(httpd_req_t *req, const char *dirpath, const
strlcpy(entrypath + dirpath_len, entry->d_name, sizeof(entrypath) - dirpath_len);
ESP_LOGD(TAG, "Entrypath: %s", entrypath);
if (stat(entrypath, &entry_stat) == -1) {
ESP_LOGE(TAG, "Failed to stat %s: %s", entrytype, entry->d_name);
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to stat " + string(entrytype) + ": " + string(entry->d_name));
continue;
}
@@ -357,7 +357,7 @@ static esp_err_t send_datafile(httpd_req_t *req, bool send_full_file)
fd = fopen(currentfilename.c_str(), "r");
if (!fd) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to read file: " + std::string(currentfilename) +"!");
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to read file: " + currentfilename + "!");
/* Respond with 404 Error */
httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, get404());
return ESP_FAIL;
@@ -373,7 +373,7 @@ static esp_err_t send_datafile(httpd_req_t *req, bool send_full_file)
/* Adapted from https://www.geeksforgeeks.org/implement-your-own-tail-read-last-n-lines-of-a-huge-file/ */
if (fseek(fd, 0, SEEK_END)) {
ESP_LOGE(TAG, "Failed to get to end of file!");
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to get to end of file!");
return ESP_FAIL;
}
else {
@@ -381,7 +381,7 @@ static esp_err_t send_datafile(httpd_req_t *req, bool send_full_file)
ESP_LOGI(TAG, "File contains %ld bytes", pos);
if (fseek(fd, pos - std::min((long)LOGFILE_LAST_PART_BYTES, pos), SEEK_SET)) { // Go LOGFILE_LAST_PART_BYTES bytes back from EOF
ESP_LOGE(TAG, "Failed to go back %ld bytes within the file!", std::min((long)LOGFILE_LAST_PART_BYTES, pos));
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to go back " + to_string(std::min((long)LOGFILE_LAST_PART_BYTES, pos)) + " bytes within the file!");
return ESP_FAIL;
}
}
@@ -404,7 +404,7 @@ static esp_err_t send_datafile(httpd_req_t *req, bool send_full_file)
/* Send the buffer contents as HTTP response chunk */
if (httpd_resp_send_chunk(req, chunk, chunksize) != ESP_OK) {
fclose(fd);
ESP_LOGE(TAG, "File sending failed!");
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "File sending failed!");
/* Abort sending file */
httpd_resp_sendstr_chunk(req, NULL);
/* Respond with 500 Internal Server Error */
@@ -443,7 +443,7 @@ static esp_err_t send_logfile(httpd_req_t *req, bool send_full_file)
fd = fopen(currentfilename.c_str(), "r");
if (!fd) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to read file: " + std::string(currentfilename.c_str()) +"!");
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to read file: " + currentfilename + "!");
/* Respond with 404 Error */
httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, get404());
return ESP_FAIL;
@@ -459,7 +459,7 @@ static esp_err_t send_logfile(httpd_req_t *req, bool send_full_file)
/* Adapted from https://www.geeksforgeeks.org/implement-your-own-tail-read-last-n-lines-of-a-huge-file/ */
if (fseek(fd, 0, SEEK_END)) {
ESP_LOGE(TAG, "Failed to get to end of file!");
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to get to end of file!");
return ESP_FAIL;
}
else {
@@ -467,7 +467,7 @@ static esp_err_t send_logfile(httpd_req_t *req, bool send_full_file)
ESP_LOGI(TAG, "File contains %ld bytes", pos);
if (fseek(fd, pos - std::min((long)LOGFILE_LAST_PART_BYTES, pos), SEEK_SET)) { // Go LOGFILE_LAST_PART_BYTES bytes back from EOF
ESP_LOGE(TAG, "Failed to go back %ld bytes within the file!", std::min((long)LOGFILE_LAST_PART_BYTES, pos));
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to go back " + to_string(std::min((long)LOGFILE_LAST_PART_BYTES, pos)) + " bytes within the file!");
return ESP_FAIL;
}
}
@@ -490,7 +490,7 @@ static esp_err_t send_logfile(httpd_req_t *req, bool send_full_file)
/* Send the buffer contents as HTTP response chunk */
if (httpd_resp_send_chunk(req, chunk, chunksize) != ESP_OK) {
fclose(fd);
ESP_LOGE(TAG, "File sending failed!");
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "File sending failed!");
/* Abort sending file */
httpd_resp_sendstr_chunk(req, NULL);
/* Respond with 500 Internal Server Error */
@@ -530,7 +530,7 @@ static esp_err_t download_get_handler(httpd_req_t *req)
if (!filename) {
ESP_LOGE(TAG, "Filename is too long");
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Filename is too long");
/* Respond with 414 Error */
httpd_resp_send_err(req, HTTPD_414_URI_TOO_LONG, "Filename too long");
return ESP_FAIL;
@@ -571,7 +571,7 @@ static esp_err_t download_get_handler(httpd_req_t *req)
fd = fopen(filepath, "r");
if (!fd) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to read file: " + std::string(filepath) +"!");
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to read file: " + std::string(filepath) + "!");
/* Respond with 404 Error */
httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, get404());
return ESP_FAIL;
@@ -592,7 +592,7 @@ static esp_err_t download_get_handler(httpd_req_t *req)
/* Send the buffer contents as HTTP response chunk */
if (httpd_resp_send_chunk(req, chunk, chunksize) != ESP_OK) {
fclose(fd);
ESP_LOGE(TAG, "File sending failed!");
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "File sending failed!");
/* Abort sending file */
httpd_resp_sendstr_chunk(req, NULL);
/* Respond with 500 Internal Server Error */
@@ -634,14 +634,14 @@ static esp_err_t upload_post_handler(httpd_req_t *req)
/* Filename cannot have a trailing '/' */
if (filename[strlen(filename) - 1] == '/') {
ESP_LOGE(TAG, "Invalid filename: %s", filename);
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Invalid filename: " + string(filename));
/* Respond with 400 Bad Request */
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid filename");
return ESP_FAIL;
}
if (stat(filepath, &file_stat) == 0) {
ESP_LOGE(TAG, "File already exists: %s", filepath);
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "File already exists: " + string(filepath));
/* Respond with 400 Bad Request */
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "File already exists");
return ESP_FAIL;
@@ -649,7 +649,7 @@ static esp_err_t upload_post_handler(httpd_req_t *req)
/* File cannot be larger than a limit */
if (req->content_len > MAX_FILE_SIZE) {
ESP_LOGE(TAG, "File too large: %d bytes", req->content_len);
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "File too large: " + to_string(req->content_len) + " bytes");
/* Respond with 400 Bad Request */
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST,
"File size must be less than "
@@ -661,7 +661,7 @@ static esp_err_t upload_post_handler(httpd_req_t *req)
fd = fopen(filepath, "w");
if (!fd) {
ESP_LOGE(TAG, "Failed to create file: %s", filepath);
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to create file: " + string(filepath));
/* Respond with 500 Internal Server Error */
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to create file");
return ESP_FAIL;
@@ -692,7 +692,7 @@ static esp_err_t upload_post_handler(httpd_req_t *req)
fclose(fd);
unlink(filepath);
ESP_LOGE(TAG, "File reception failed!");
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "File reception failed!");
/* Respond with 500 Internal Server Error */
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to receive file");
return ESP_FAIL;
@@ -705,7 +705,7 @@ static esp_err_t upload_post_handler(httpd_req_t *req)
fclose(fd);
unlink(filepath);
ESP_LOGE(TAG, "File write failed!");
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "File write failed!");
/* Respond with 500 Internal Server Error */
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to write file to storage");
return ESP_FAIL;
@@ -780,7 +780,7 @@ static esp_err_t delete_post_handler(httpd_req_t *req)
if (httpd_query_key_value(_query, "task", _valuechar, 30) == ESP_OK)
{
ESP_LOGD(TAG, "task is found: %s", _valuechar);
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "task is found: " + string(_valuechar));
_task = std::string(_valuechar);
}
}
@@ -826,26 +826,26 @@ static esp_err_t delete_post_handler(httpd_req_t *req)
/* Filename cannot have a trailing '/' */
if (filename[strlen(filename) - 1] == '/') {
ESP_LOGE(TAG, "Invalid filename: %s", filename);
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Invalid filename: " + string(filename));
/* Respond with 400 Bad Request */
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid filename");
return ESP_FAIL;
}
if (strcmp(filename, "wlan.ini") == 0) {
ESP_LOGE(TAG, "Trying to delete protected file : %s", filename);
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to delete protected file : " + string(filename));
httpd_resp_send_err(req, HTTPD_403_FORBIDDEN, "Not allowed to delete wlan.ini");
return ESP_FAIL;
}
if (stat(filepath, &file_stat) == -1) {
ESP_LOGE(TAG, "File does not exist: %s", filename);
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "File does not exist: " + string(filename));
/* Respond with 400 Bad Request */
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "File does not exist");
return ESP_FAIL;
}
ESP_LOGI(TAG, "Deleting file: %s", filename);
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Deleting file: " + string(filename));
/* Delete file */
unlink(filepath);
@@ -887,7 +887,7 @@ void delete_all_in_directory(std::string _directory)
std::string filename;
if (!dir) {
ESP_LOGE(TAG, "Failed to stat dir: %s", _directory.c_str());
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to stat dir: " + _directory);
return;
}
@@ -896,7 +896,7 @@ void delete_all_in_directory(std::string _directory)
if (!(entry->d_type == DT_DIR)){
if (strcmp("wlan.ini", entry->d_name) != 0){ // auf wlan.ini soll nicht zugegriffen werden !!!
filename = _directory + "/" + std::string(entry->d_name);
ESP_LOGI(TAG, "Deleting file: %s", filename.c_str());
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Deleting file: " + filename);
/* Delete file */
unlink(filename.c_str());
}
@@ -930,7 +930,7 @@ std::string unzip_new(std::string _in_zip_file, std::string _target_zip, std::st
// Get and print information about each file in the archive.
int numberoffiles = (int)mz_zip_reader_get_num_files(&zip_archive);
ESP_LOGI(TAG, "Numbers of files to be extracted: %d", numberoffiles);
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Numbers of files to be extracted: " + to_string(numberoffiles));
sort_iter = 0;
{
@@ -953,7 +953,7 @@ std::string unzip_new(std::string _in_zip_file, std::string _target_zip, std::st
p = mz_zip_reader_extract_file_to_heap(&zip_archive, archive_filename, &uncomp_size, 0);
if (!p)
{
ESP_LOGE(TAG, "mz_zip_reader_extract_file_to_heap() failed on file %s", archive_filename);
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "mz_zip_reader_extract_file_to_heap() failed on file " + string(archive_filename));
mz_zip_reader_end(&zip_archive);
return ret;
}
@@ -996,8 +996,12 @@ std::string unzip_new(std::string _in_zip_file, std::string _target_zip, std::st
ESP_LOGI(TAG, "Filename to extract: %s, Zwischenfilename: %s", zw.c_str(), filename_zw.c_str());
std::string folder = filename_zw.substr(0, filename_zw.find_last_of('/'));
MakeDir(folder);
// extrahieren in zwischendatei
DeleteFile(filename_zw);
FILE* fpTargetFile = fopen(filename_zw.c_str(), "wb");
uint writtenbytes = fwrite(p, 1, (uint)uncomp_size, fpTargetFile);
fclose(fpTargetFile);
@@ -1011,21 +1015,22 @@ std::string unzip_new(std::string _in_zip_file, std::string _target_zip, std::st
else
{
isokay = false;
ESP_LOGD(TAG, "ERROR in writting extracted file (function fwrite) extracted file \"%s\", size %u", archive_filename, (uint)uncomp_size);
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "ERROR in writting extracted file (function fwrite) extracted file \"" +
string(archive_filename) + "\", size " + to_string(uncomp_size));
}
DeleteFile(zw);
if (!isokay)
ESP_LOGE(TAG, "ERROR in fwrite \"%s\", size %u", archive_filename, (uint)uncomp_size);
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "ERROR in fwrite \"" + string(archive_filename) + "\", size " + to_string(uncomp_size));
isokay = isokay && RenameFile(filename_zw, zw);
if (!isokay)
ESP_LOGE(TAG, "ERROR in Rename \"%s\" to \"%s\"", filename_zw.c_str(), zw.c_str());
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "ERROR in Rename \"" + filename_zw + "\" to \"" + zw);
if (isokay)
ESP_LOGI(TAG, "Successfully extracted file \"%s\", size %u", archive_filename, (uint)uncomp_size);
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Successfully extracted file \"" + string(archive_filename) + "\", size " + to_string(uncomp_size));
else
{
ESP_LOGE(TAG, "ERROR in extracting file \"%s\", size %u", archive_filename, (uint)uncomp_size);
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "ERROR in extracting file \"" + string(archive_filename) + "\", size " + to_string(uncomp_size));
ret = "ERROR";
}
mz_free(p);
@@ -1059,7 +1064,7 @@ void unzip(std::string _in_zip_file, std::string _target_directory){
status = mz_zip_reader_init_file(&zip_archive, _in_zip_file.c_str(), 0);
if (!status)
{
ESP_LOGD(TAG, "mz_zip_reader_init_file() failed!");
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "mz_zip_reader_init_file() failed!");
return;
}
@@ -1071,7 +1076,7 @@ void unzip(std::string _in_zip_file, std::string _target_directory){
status = mz_zip_reader_init_file(&zip_archive, _in_zip_file.c_str(), sort_iter ? MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY : 0);
if (!status)
{
ESP_LOGD(TAG, "mz_zip_reader_init_file() failed!");
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "mz_zip_reader_init_file() failed!");
return;
}
@@ -1085,7 +1090,7 @@ void unzip(std::string _in_zip_file, std::string _target_directory){
p = mz_zip_reader_extract_file_to_heap(&zip_archive, archive_filename, &uncomp_size, 0);
if (!p)
{
ESP_LOGD(TAG, "mz_zip_reader_extract_file_to_heap() failed!");
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "mz_zip_reader_extract_file_to_heap() failed!");
mz_zip_reader_end(&zip_archive);
return;
}
@@ -1121,19 +1126,19 @@ void register_server_file_uri(httpd_handle_t server, const char *base_path)
/* Validate file storage base path */
if (!base_path) {
// if (!base_path || strcmp(base_path, "/spiffs") != 0) {
ESP_LOGE(TAG, "File server base_path not set");
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "File server base_path not set");
// return ESP_ERR_INVALID_ARG;
}
if (server_data) {
ESP_LOGE(TAG, "File server already started");
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "File server already started");
// return ESP_ERR_INVALID_STATE;
}
/* Allocate memory for server data */
server_data = (file_server_data *) calloc(1, sizeof(struct file_server_data));
if (!server_data) {
ESP_LOGE(TAG, "Failed to allocate memory for server data");
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to allocate memory for server data");
// return ESP_ERR_NO_MEM;
}
strlcpy(server_data->base_path, base_path,

View File

@@ -39,6 +39,7 @@
#include "ClassLogFile.h"
#include "Helper.h"
#include "statusled.h"
#include "../../include/defines.h"
/*an ota data write buffer ready to write to the flash*/
@@ -56,9 +57,9 @@ bool initial_setup = false;
static void infinite_loop(void)
{
int i = 0;
ESP_LOGI(TAG, "When a new firmware is available on the server, press the reset button to download it");
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "When a new firmware is available on the server, press the reset button to download it");
while(1) {
ESP_LOGI(TAG, "Waiting for a new firmware... %d", ++i);
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Waiting for a new firmware... (" + to_string(++i) + ")");
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
@@ -66,6 +67,8 @@ static void infinite_loop(void)
void task_do_Update_ZIP(void *pvParameter)
{
StatusLED(AP_OR_OTA, 1, true); // Signaling an OTA update
std::string filetype = toUpper(getFileType(_file_name_update));
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "File: " + _file_name_update + " Filetype: " + filetype);
@@ -87,13 +90,13 @@ void task_do_Update_ZIP(void *pvParameter)
ota_update_task(retfirmware);
}
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Trigger reboot due to firmware update.");
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Trigger reboot due to firmware update");
doRebootOTA();
} else if (filetype == "BIN")
{
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Do firmware update - file: " + _file_name_update);
ota_update_task(_file_name_update);
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Trigger reboot due to firmware update.");
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Trigger reboot due to firmware update");
doRebootOTA();
}
else
@@ -108,7 +111,7 @@ void CheckUpdate()
FILE *pfile;
if ((pfile = fopen("/sdcard/update.txt", "r")) == NULL)
{
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "No update triggered.");
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "No pending update");
return;
}
@@ -120,13 +123,13 @@ void CheckUpdate()
std::string _szw = std::string(zw);
if (_szw == "init")
{
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Inital Setup triggered.");
initial_setup = true; }
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Inital Setup triggered");
}
}
fclose(pfile);
DeleteFile("/sdcard/update.txt"); // Prevent Boot Loop!!!
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Update during boot triggered - Update File: " + _file_name_update);
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Start update process (" + _file_name_update + ")");
xTaskCreate(&task_do_Update_ZIP, "task_do_Update_ZIP", configMINIMAL_STACK_SIZE * 35, NULL, tskIDLE_PRIORITY+1, NULL);
@@ -148,10 +151,10 @@ static bool ota_update_task(std::string fn)
const esp_partition_t *configured = esp_ota_get_boot_partition();
const esp_partition_t *running = esp_ota_get_running_partition();
if (configured != running) {
ESP_LOGW(TAG, "Configured OTA boot partition at offset 0x%08x, but running from offset 0x%08x",
configured->address, running->address);
ESP_LOGW(TAG, "(This can happen if either the OTA boot data or preferred boot image become somehow corrupted.)");
if (configured != running) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Configured OTA boot partition at offset " + to_string(configured->address) +
", but running from offset " + to_string(running->address));
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "(This can happen if either the OTA boot data or preferred boot image become somehow corrupted.)");
}
ESP_LOGI(TAG, "Running partition type %d subtype %d (offset 0x%08x)",
running->type, running->subtype, running->address);
@@ -179,7 +182,7 @@ static bool ota_update_task(std::string fn)
while (data_read > 0) {
if (data_read < 0) {
ESP_LOGE(TAG, "Error: SSL data read error");
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Error: SSL data read error");
return false;
} else if (data_read > 0) {
if (image_header_was_checked == false) {
@@ -203,16 +206,17 @@ static bool ota_update_task(std::string fn)
// check current version with last invalid partition
if (last_invalid_app != NULL) {
if (memcmp(invalid_app_info.version, new_app_info.version, sizeof(new_app_info.version)) == 0) {
ESP_LOGW(TAG, "New version is the same as invalid version.");
ESP_LOGW(TAG, "Previously, there was an attempt to launch the firmware with %s version, but it failed.", invalid_app_info.version);
ESP_LOGW(TAG, "The firmware has been rolled back to the previous version.");
LogFile.WriteToFile(ESP_LOG_WARN, TAG, "New version is the same as invalid version");
LogFile.WriteToFile(ESP_LOG_WARN, TAG, "Previously, there was an attempt to launch the firmware with " +
string(invalid_app_info.version) + " version, but it failed");
LogFile.WriteToFile(ESP_LOG_WARN, TAG, "The firmware has been rolled back to the previous version");
infinite_loop();
}
}
/*
if (memcmp(new_app_info.version, running_app_info.version, sizeof(new_app_info.version)) == 0) {
ESP_LOGW(TAG, "Current running version is the same as a new. We will not continue the update.");
LogFile.WriteToFile(ESP_LOG_WARN, TAG, "Current running version is the same as a new. We will not continue the update");
infinite_loop();
}
*/
@@ -220,12 +224,12 @@ static bool ota_update_task(std::string fn)
err = esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &update_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ota_begin failed (%s)", esp_err_to_name(err));
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "esp_ota_begin failed (" + string(esp_err_to_name(err)) + ")");
return false;
}
ESP_LOGI(TAG, "esp_ota_begin succeeded");
} else {
ESP_LOGE(TAG, "received package is not fit len");
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "received package is not fit len");
return false;
}
}
@@ -241,7 +245,7 @@ static bool ota_update_task(std::string fn)
// * `errno` to check for underlying transport connectivity closure if any
//
if (errno == ECONNRESET || errno == ENOTCONN) {
ESP_LOGE(TAG, "Connection closed, errno = %d", errno);
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Connection closed, errno = " + to_string(errno));
break;
}
}
@@ -254,15 +258,15 @@ static bool ota_update_task(std::string fn)
err = esp_ota_end(update_handle);
if (err != ESP_OK) {
if (err == ESP_ERR_OTA_VALIDATE_FAILED) {
ESP_LOGE(TAG, "Image validation failed, image is corrupted");
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Image validation failed, image is corrupted");
}
ESP_LOGE(TAG, "esp_ota_end failed (%s)!", esp_err_to_name(err));
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "esp_ota_end failed (" + string(esp_err_to_name(err)) + ")!");
return false;
}
err = esp_ota_set_boot_partition(update_partition);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ota_set_boot_partition failed (%s)!", esp_err_to_name(err));
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "esp_ota_set_boot_partition failed (" + string(esp_err_to_name(err)) + ")!");
}
// ESP_LOGI(TAG, "Prepare to restart system!");
@@ -329,7 +333,7 @@ void CheckOTAUpdate(void)
ESP_LOGI(TAG, "Diagnostics completed successfully! Continuing execution...");
esp_ota_mark_app_valid_cancel_rollback();
} else {
ESP_LOGE(TAG, "Diagnostics failed! Start rollback to the previous version...");
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Diagnostics failed! Start rollback to the previous version...");
esp_ota_mark_app_invalid_rollback_and_reboot();
}
}
@@ -353,7 +357,7 @@ void CheckOTAUpdate(void)
ESP_LOGI(TAG, "Diagnostics completed successfully! Continuing execution...");
esp_ota_mark_app_valid_cancel_rollback();
} else {
ESP_LOGE(TAG, "Diagnostics failed! Start rollback to the previous version...");
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Diagnostics failed! Start rollback to the previous version...");
esp_ota_mark_app_invalid_rollback_and_reboot();
}
}
@@ -445,7 +449,7 @@ esp_err_t handler_ota_update(httpd_req_t *req)
if ((filetype == "ZIP") || (filetype == "BIN"))
{
FILE *pfile;
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Update for reboot.");
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Update for reboot");
pfile = fopen("/sdcard/update.txt", "w");
fwrite(fn.c_str(), fn.length(), 1, pfile);
fclose(pfile);
@@ -522,7 +526,7 @@ esp_err_t handler_ota_update(httpd_req_t *req)
}
else
{
ESP_LOGD(TAG, "File does not exist: %s", fn.c_str());
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "File does not exist: " + fn);
}
/* Respond with an empty chunk to signal HTTP response completion */
std::string zw = "file deleted\n";
@@ -537,7 +541,7 @@ esp_err_t handler_ota_update(httpd_req_t *req)
httpd_resp_send(req, zw.c_str(), strlen(zw.c_str()));
httpd_resp_send_chunk(req, NULL, 0);
ESP_LOGE(TAG, "ota without parameter - should not be the case!");
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "ota without parameter - should not be the case!");
/*
const char* resp_str;
@@ -586,6 +590,9 @@ void task_reboot(void *KillAutoFlow)
KillTFliteTasks(); // Kill autoflow task if executed in extra task, if not don't kill parent task
}
Camera.LightOnOff(false);
StatusLEDOff();
/* Stop service tasks */
#ifdef ENABLE_MQTT
MQTTdestroy_client(true);
@@ -600,20 +607,20 @@ void task_reboot(void *KillAutoFlow)
vTaskDelay(5000 / portTICK_PERIOD_MS);
hard_restart(); // Reset type: System reset (Triggered by watchdog), if esp_restart stalls (WDT needs to be activated)
ESP_LOGE(TAG, "Reboot failed!");
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Reboot failed!");
vTaskDelete(NULL); //Delete this task if it comes to this point
}
void doReboot()
{
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Reboot triggered by Software (5s).");
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Reboot triggered by Software (5s)");
LogFile.WriteToFile(ESP_LOG_WARN, TAG, "Reboot in 5sec");
BaseType_t xReturned = xTaskCreate(&task_reboot, "task_reboot", configMINIMAL_STACK_SIZE * 3, (void*) true, 10, NULL);
BaseType_t xReturned = xTaskCreate(&task_reboot, "task_reboot", configMINIMAL_STACK_SIZE * 4, (void*) true, 10, NULL);
if( xReturned != pdPASS )
{
ESP_LOGE(TAG, "task_reboot not created -> force reboot without killing flow");
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "task_reboot not created -> force reboot without killing flow");
task_reboot((void*) false);
}
vTaskDelay(10000 / portTICK_PERIOD_MS); // Prevent serving web client fetch response until system is shuting down
@@ -624,6 +631,8 @@ void doRebootOTA()
{
LogFile.WriteToFile(ESP_LOG_WARN, TAG, "Reboot in 5sec");
Camera.LightOnOff(false);
StatusLEDOff();
esp_camera_deinit();
vTaskDelay(5000 / portTICK_PERIOD_MS);
@@ -641,7 +650,7 @@ esp_err_t handler_reboot(httpd_req_t *req)
#endif
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "handler_reboot");
ESP_LOGI(TAG, "!!! System will restart within 5 sec!!!");
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "!!! System will restart within 5 sec!!!");
std::string response =
"<html><head><script>"

View File

@@ -1,5 +1,5 @@
#include "ClassFlowAlignment.h"
#include "ClassFlowMakeImage.h"
#include "ClassFlowTakeImage.h"
#include "ClassFlow.h"
#include "server_tflite.h"
@@ -8,6 +8,7 @@
#include "ClassLogFile.h"
#include "psram.h"
#include "../../include/defines.h"
@@ -31,7 +32,7 @@ void ClassFlowAlignment::SetInitialParameter(void)
ImageBasis = NULL;
ImageTMP = NULL;
#ifdef ALGROI_LOAD_FROM_MEM_AS_JPG
AlgROI = (ImageData*)heap_caps_malloc(sizeof(ImageData), MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM);
AlgROI = (ImageData*)malloc_psram_heap(std::string(TAG) + "->AlgROI", sizeof(ImageData), MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM);
#endif
previousElement = NULL;
disabled = false;
@@ -46,16 +47,16 @@ ClassFlowAlignment::ClassFlowAlignment(std::vector<ClassFlow*>* lfc)
for (int i = 0; i < ListFlowControll->size(); ++i)
{
if (((*ListFlowControll)[i])->name().compare("ClassFlowMakeImage") == 0)
if (((*ListFlowControll)[i])->name().compare("ClassFlowTakeImage") == 0)
{
ImageBasis = ((ClassFlowMakeImage*) (*ListFlowControll)[i])->rawImage;
ImageBasis = ((ClassFlowTakeImage*) (*ListFlowControll)[i])->rawImage;
}
}
if (!ImageBasis) // the function take pictures does not exist --> must be created first ONLY FOR TEST PURPOSES
{
ESP_LOGD(TAG, "CImageBasis had to be created");
ImageBasis = new CImageBasis(namerawimage);
ImageBasis = new CImageBasis("ImageBasis", namerawimage);
}
}
@@ -65,7 +66,7 @@ bool ClassFlowAlignment::ReadParameter(FILE* pfile, string& aktparamgraph)
std::vector<string> splitted;
int suchex = 40;
int suchey = 40;
int alg_algo = 0;
int alg_algo = 0; //default=0; 1 =HIGHACCURACY; 2= FAST; 3= OFF //add disable aligment algo |01.2023
aktparamgraph = trim(aktparamgraph);
@@ -74,7 +75,7 @@ bool ClassFlowAlignment::ReadParameter(FILE* pfile, string& aktparamgraph)
if (!this->GetNextParagraph(pfile, aktparamgraph))
return false;
if (aktparamgraph.compare("[Alignment]") != 0) //Paragraph does not fit MakeImage
if (aktparamgraph.compare("[Alignment]") != 0) //Paragraph does not fit Alignment
return false;
while (this->getNextLine(pfile, &aktparamgraph) && !this->isNewParagraph(aktparamgraph))
@@ -130,6 +131,8 @@ bool ClassFlowAlignment::ReadParameter(FILE* pfile, string& aktparamgraph)
alg_algo = 1;
if (toUpper(splitted[1]) == "FAST")
alg_algo = 2;
if (toUpper(splitted[1]) == "OFF") //no align algo if set to 3 = off => no draw ref //add disable aligment algo |01.2023
alg_algo = 3;
}
}
@@ -145,7 +148,10 @@ bool ClassFlowAlignment::ReadParameter(FILE* pfile, string& aktparamgraph)
#endif
}
LoadReferenceAlignmentValues();
//no align algo if set to 3 = off => no draw ref //add disable aligment algo |01.2023
if(References[0].alignment_algo != 3){
LoadReferenceAlignmentValues();
}
return true;
@@ -184,7 +190,7 @@ bool ClassFlowAlignment::doFlow(string time)
if (!ImageTMP)
{
ImageTMP = new CImageBasis(ImageBasis);
ImageTMP = new CImageBasis("ImageTMP", ImageBasis);
if (!ImageTMP)
{
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Can't allocate ImageTMP -> Exec this round aborted!");
@@ -194,7 +200,7 @@ bool ClassFlowAlignment::doFlow(string time)
}
delete AlignAndCutImage;
AlignAndCutImage = new CAlignAndCutImage(ImageBasis, ImageTMP);
AlignAndCutImage = new CAlignAndCutImage("AlignAndCutImage", ImageBasis, ImageTMP);
if (!AlignAndCutImage)
{
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Can't allocate AlignAndCutImage -> Exec this round aborted!");
@@ -202,7 +208,7 @@ bool ClassFlowAlignment::doFlow(string time)
return false;
}
CRotateImage rt(AlignAndCutImage, ImageTMP, initialflip);
CRotateImage rt("rawImage", AlignAndCutImage, ImageTMP, initialflip);
if (initialflip)
{
int _zw = ImageBasis->height;
@@ -234,14 +240,22 @@ bool ClassFlowAlignment::doFlow(string time)
AlignAndCutImage->SaveToFile(FormatFileName("/sdcard/img_tmp/rot.jpg"));
}
if (!AlignAndCutImage->Align(&References[0], &References[1]))
{
SaveReferenceAlignmentValues();
}
//no align algo if set to 3 = off //add disable aligment algo |01.2023
if(References[0].alignment_algo != 3){
if (!AlignAndCutImage->Align(&References[0], &References[1]))
{
SaveReferenceAlignmentValues();
}
}// no align
#ifdef ALGROI_LOAD_FROM_MEM_AS_JPG
if (AlgROI) {
DrawRef(ImageTMP);
//no align algo if set to 3 = off => no draw ref //add disable aligment algo |01.2023
if(References[0].alignment_algo != 3){
DrawRef(ImageTMP);
}
tfliteflow.DigitalDrawROI(ImageTMP);
tfliteflow.AnalogDrawROI(ImageTMP);
ImageTMP->writeToMemoryAsJPG((ImageData*)AlgROI, 90);
@@ -258,7 +272,10 @@ bool ClassFlowAlignment::doFlow(string time)
delete ImageTMP;
ImageTMP = NULL;
LoadReferenceAlignmentValues();
//no align algo if set to 3 = off => no draw ref //add disable aligment algo |01.2023
if(References[0].alignment_algo != 3){
LoadReferenceAlignmentValues();
}
return true;
}

View File

@@ -34,7 +34,7 @@ ClassFlowCNNGeneral::ClassFlowCNNGeneral(ClassFlowAlignment *_flowalign, t_CNNTy
CNNType = AutoDetect;
CNNType = _cnntype;
flowpostalignment = _flowalign;
logfileRetentionInDays = 5;
imagesRetention = 5;
}
@@ -324,9 +324,9 @@ bool ClassFlowCNNGeneral::ReadParameter(FILE* pfile, string& aktparamgraph)
while (this->getNextLine(pfile, &aktparamgraph) && !this->isNewParagraph(aktparamgraph))
{
splitted = ZerlegeZeile(aktparamgraph);
if ((toUpper(splitted[0]) == "LOGIMAGELOCATION") && (splitted.size() > 1))
if ((toUpper(splitted[0]) == "ROIIMAGESLOCATION") && (splitted.size() > 1))
{
this->LogImageLocation = "/sdcard" + splitted[1];
this->imagesLocation = "/sdcard" + splitted[1];
this->isLogImage = true;
}
if ((toUpper(splitted[0]) == "LOGIMAGESELECT") && (splitted.size() > 1))
@@ -335,9 +335,9 @@ bool ClassFlowCNNGeneral::ReadParameter(FILE* pfile, string& aktparamgraph)
isLogImageSelect = true;
}
if ((toUpper(splitted[0]) == "LOGFILERETENTIONINDAYS") && (splitted.size() > 1))
if ((toUpper(splitted[0]) == "ROIIMAGESRETENTION") && (splitted.size() > 1))
{
this->logfileRetentionInDays = std::stoi(splitted[1]);
this->imagesRetention = std::stoi(splitted[1]);
}
if ((toUpper(splitted[0]) == "MODEL") && (splitted.size() > 1))
@@ -381,8 +381,10 @@ bool ClassFlowCNNGeneral::ReadParameter(FILE* pfile, string& aktparamgraph)
for (int _ana = 0; _ana < GENERAL.size(); ++_ana)
for (int i = 0; i < GENERAL[_ana]->ROI.size(); ++i)
{
GENERAL[_ana]->ROI[i]->image = new CImageBasis(modelxsize, modelysize, modelchannel);
GENERAL[_ana]->ROI[i]->image_org = new CImageBasis(GENERAL[_ana]->ROI[i]->deltax, GENERAL[_ana]->ROI[i]->deltay, 3);
GENERAL[_ana]->ROI[i]->image = new CImageBasis("ROI " + GENERAL[_ana]->ROI[i]->name,
modelxsize, modelysize, modelchannel);
GENERAL[_ana]->ROI[i]->image_org = new CImageBasis("ROI " + GENERAL[_ana]->ROI[i]->name + " original",
GENERAL[_ana]->ROI[i]->deltax, GENERAL[_ana]->ROI[i]->deltay, 3);
}
return true;

View File

@@ -25,6 +25,7 @@ extern "C" {
#endif //ENABLE_MQTT
#include "server_help.h"
#include "server_tflite.h"
#include "../../include/defines.h"
static const char* TAG = "CTRL";
@@ -38,8 +39,8 @@ std::string ClassFlowControll::doSingleStep(std::string _stepname, std::string _
ESP_LOGD(TAG, "Step %s start", _stepname.c_str());
if ((_stepname.compare("[MakeImage]") == 0) || (_stepname.compare(";[MakeImage]") == 0)){
_classname = "ClassFlowMakeImage";
if ((_stepname.compare("[TakeImage]") == 0) || (_stepname.compare(";[TakeImage]") == 0)){
_classname = "ClassFlowTakeImage";
}
if ((_stepname.compare("[Alignment]") == 0) || (_stepname.compare(";[Alignment]") == 0)){
_classname = "ClassFlowAlignment";
@@ -60,11 +61,14 @@ std::string ClassFlowControll::doSingleStep(std::string _stepname, std::string _
if ((_stepname.compare("[InfluxDB]") == 0) || (_stepname.compare(";[InfluxDB]") == 0)){
_classname = "ClassFlowInfluxDB";
}
if ((_stepname.compare("[InfluxDBv2]") == 0) || (_stepname.compare(";[InfluxDBv2]") == 0)){
_classname = "ClassFlowInfluxDBv2";
}
#endif //ENABLE_INFLUXDB
for (int i = 0; i < FlowControll.size(); ++i)
if (FlowControll[i]->name().compare(_classname) == 0){
if (!(FlowControll[i]->name().compare("ClassFlowMakeImage") == 0)) // if it is a MakeImage, the image does not need to be included, this happens automatically with the html query.
if (!(FlowControll[i]->name().compare("ClassFlowTakeImage") == 0)) // if it is a TakeImage, the image does not need to be included, this happens automatically with the html query.
FlowControll[i]->doFlow("");
result = FlowControll[i]->getHTMLSingleStep(_host);
}
@@ -77,7 +81,7 @@ std::string ClassFlowControll::doSingleStep(std::string _stepname, std::string _
std::string ClassFlowControll::TranslateAktstatus(std::string _input)
{
if (_input.compare("ClassFlowMakeImage") == 0)
if (_input.compare("ClassFlowTakeImage") == 0)
return ("Take Image");
if (_input.compare("ClassFlowAlignment") == 0)
return ("Aligning");
@@ -90,6 +94,8 @@ std::string ClassFlowControll::TranslateAktstatus(std::string _input)
#ifdef ENABLE_INFLUXDB
if (_input.compare("ClassFlowInfluxDB") == 0)
return ("Sending InfluxDB");
if (_input.compare("ClassFlowInfluxDBv2") == 0)
return ("Sending InfluxDBv2");
#endif //ENABLE_INFLUXDB
if (_input.compare("ClassFlowPostProcessing") == 0)
return ("Post-Processing");
@@ -171,7 +177,7 @@ bool ClassFlowControll::StartMQTTService() {
/* Start the MQTT service */
for (int i = 0; i < FlowControll.size(); ++i) {
if (FlowControll[i]->name().compare("ClassFlowMQTT") == 0) {
return ((ClassFlowMQTT*) (FlowControll[i]))->Start(AutoIntervall);
return ((ClassFlowMQTT*) (FlowControll[i]))->Start(AutoInterval);
}
}
return false;
@@ -183,19 +189,20 @@ void ClassFlowControll::SetInitialParameter(void)
{
AutoStart = false;
SetupModeActive = false;
AutoIntervall = 10; // Minutes
AutoInterval = 10; // Minutes
flowdigit = NULL;
flowanalog = NULL;
flowpostprocessing = NULL;
disabled = false;
aktRunNr = 0;
aktstatus = "Flow task not yet created";
aktstatusWithTime = aktstatus;
}
bool ClassFlowControll::isAutoStart(long &_intervall)
bool ClassFlowControll::isAutoStart(long &_interval)
{
_intervall = AutoIntervall * 60 * 1000; // AutoInterval: minutes -> ms
_interval = AutoInterval * 60 * 1000; // AutoInterval: minutes -> ms
return AutoStart;
}
@@ -206,10 +213,10 @@ ClassFlow* ClassFlowControll::CreateClassFlow(std::string _type)
_type = trim(_type);
if (toUpper(_type).compare("[MAKEIMAGE]") == 0)
if (toUpper(_type).compare("[TAKEIMAGE]") == 0)
{
cfc = new ClassFlowMakeImage(&FlowControll);
flowmakeimage = (ClassFlowMakeImage*) cfc;
cfc = new ClassFlowTakeImage(&FlowControll);
flowtakeimage = (ClassFlowTakeImage*) cfc;
}
if (toUpper(_type).compare("[ALIGNMENT]") == 0)
{
@@ -233,6 +240,8 @@ ClassFlow* ClassFlowControll::CreateClassFlow(std::string _type)
#ifdef ENABLE_INFLUXDB
if (toUpper(_type).compare("[INFLUXDB]") == 0)
cfc = new ClassFlowInfluxDB(&FlowControll);
if (toUpper(_type).compare("[INFLUXDBV2]") == 0)
cfc = new ClassFlowInfluxDBv2(&FlowControll);
#endif //ENABLE_INFLUXDB
if (toUpper(_type).compare("[WRITELIST]") == 0)
cfc = new ClassFlowWriteList(&FlowControll);
@@ -265,8 +274,10 @@ ClassFlow* ClassFlowControll::CreateClassFlow(std::string _type)
void ClassFlowControll::InitFlow(std::string config)
{
aktstatus = "Initialization";
aktstatusWithTime = aktstatus;
//#ifdef ENABLE_MQTT
//MQTTPublish(mqttServer_getMainTopic() + "/" + "status", "Initialization", false); // Right now, not possible -> MQTT Service is going to be started later
//MQTTPublish(mqttServer_getMainTopic() + "/" + "status", "Initialization", 1, false); // Right now, not possible -> MQTT Service is going to be started later
//#endif //ENABLE_MQTT
string line;
@@ -290,6 +301,7 @@ void ClassFlowControll::InitFlow(std::string config)
while ((line.size() > 0) && !(feof(pFile)))
{
cfc = CreateClassFlow(line);
// printf("Name: %s\n", cfc->name().c_str());
if (cfc)
{
ESP_LOGD(TAG, "Start ReadParameter (%s)", line.c_str());
@@ -310,6 +322,12 @@ void ClassFlowControll::InitFlow(std::string config)
}
std::string* ClassFlowControll::getActStatusWithTime()
{
return &aktstatusWithTime;
}
std::string* ClassFlowControll::getActStatus()
{
return &aktstatus;
@@ -319,21 +337,22 @@ std::string* ClassFlowControll::getActStatus()
void ClassFlowControll::setActStatus(std::string _aktstatus)
{
aktstatus = _aktstatus;
aktstatusWithTime = aktstatus;
}
void ClassFlowControll::doFlowMakeImageOnly(string time)
void ClassFlowControll::doFlowTakeImageOnly(string time)
{
std::string zw_time;
for (int i = 0; i < FlowControll.size(); ++i)
{
if (FlowControll[i]->name() == "ClassFlowMakeImage") {
if (FlowControll[i]->name() == "ClassFlowTakeImage") {
zw_time = getCurrentTimeString("%H:%M:%S");
std::string flowStatus = TranslateAktstatus(FlowControll[i]->name());
aktstatus = flowStatus + " (" + zw_time + ")";
aktstatus = TranslateAktstatus(FlowControll[i]->name());
aktstatusWithTime = aktstatus + " (" + zw_time + ")";
#ifdef ENABLE_MQTT
MQTTPublish(mqttServer_getMainTopic() + "/" + "status", flowStatus, false);
MQTTPublish(mqttServer_getMainTopic() + "/" + "status", aktstatus, 1, false);
#endif //ENABLE_MQTT
FlowControll[i]->doFlow(time);
@@ -347,6 +366,7 @@ bool ClassFlowControll::doFlow(string time)
bool result = true;
std::string zw_time;
int repeat = 0;
int qos = 1;
#ifdef DEBUG_DETAIL_ON
LogFile.WriteHeapInfo("ClassFlowControll::doFlow - Start");
@@ -363,11 +383,11 @@ bool ClassFlowControll::doFlow(string time)
for (int i = 0; i < FlowControll.size(); ++i)
{
zw_time = getCurrentTimeString("%H:%M:%S");
std::string flowStatus = TranslateAktstatus(FlowControll[i]->name());
aktstatus = flowStatus + " (" + zw_time + ")";
//LogFile.WriteToFile(ESP_LOG_INFO, TAG, aktstatus);
aktstatus = TranslateAktstatus(FlowControll[i]->name());
aktstatusWithTime = aktstatus + " (" + zw_time + ")";
//LogFile.WriteToFile(ESP_LOG_INFO, TAG, aktstatusWithTime);
#ifdef ENABLE_MQTT
MQTTPublish(mqttServer_getMainTopic() + "/" + "status", flowStatus, false);
MQTTPublish(mqttServer_getMainTopic() + "/" + "status", aktstatus, qos, false);
#endif //ENABLE_MQTT
#ifdef DEBUG_DETAIL_ON
@@ -397,11 +417,11 @@ bool ClassFlowControll::doFlow(string time)
}
zw_time = getCurrentTimeString("%H:%M:%S");
std::string flowStatus = "Flow finished";
aktstatus = flowStatus + " (" + zw_time + ")";
//LogFile.WriteToFile(ESP_LOG_INFO, TAG, aktstatus);
aktstatus = "Flow finished";
aktstatusWithTime = aktstatus + " (" + zw_time + ")";
//LogFile.WriteToFile(ESP_LOG_INFO, TAG, aktstatusWithTime);
#ifdef ENABLE_MQTT
MQTTPublish(mqttServer_getMainTopic() + "/" + "status", flowStatus, false);
MQTTPublish(mqttServer_getMainTopic() + "/" + "status", aktstatus, qos, false);
#endif //ENABLE_MQTT
return result;
@@ -527,7 +547,7 @@ bool ClassFlowControll::ReadParameter(FILE* pfile, string& aktparamgraph)
if ((toUpper(aktparamgraph).compare("[AUTOTIMER]") != 0) && (toUpper(aktparamgraph).compare("[DEBUG]") != 0) &&
(toUpper(aktparamgraph).compare("[SYSTEM]") != 0 && (toUpper(aktparamgraph).compare("[DATALOGGING]") != 0))) // Paragraph passt nicht zu MakeImage
(toUpper(aktparamgraph).compare("[SYSTEM]") != 0 && (toUpper(aktparamgraph).compare("[DATALOGGING]") != 0))) // Paragraph passt nicht zu Debug oder DataLogging
return false;
while (this->getNextLine(pfile, &aktparamgraph) && !this->isNewParagraph(aktparamgraph))
@@ -541,9 +561,9 @@ bool ClassFlowControll::ReadParameter(FILE* pfile, string& aktparamgraph)
}
}
if ((toUpper(splitted[0]) == "INTERVALL") && (splitted.size() > 1))
if ((toUpper(splitted[0]) == "INTERVAL") && (splitted.size() > 1))
{
AutoIntervall = std::stof(splitted[1]);
AutoInterval = std::stof(splitted[1]);
}
if ((toUpper(splitted[0]) == "DATALOGACTIVE") && (splitted.size() > 1))
@@ -557,12 +577,12 @@ bool ClassFlowControll::ReadParameter(FILE* pfile, string& aktparamgraph)
}
}
if ((toUpper(splitted[0]) == "DATALOGRETENTIONINDAYS") && (splitted.size() > 1))
if ((toUpper(splitted[0]) == "DATAFILESRETENTION") && (splitted.size() > 1))
{
LogFile.SetDataLogRetention(std::stoi(splitted[1]));
}
if ((toUpper(splitted[0]) == "LOGFILE") && (splitted.size() > 1))
if ((toUpper(splitted[0]) == "LOGLEVEL") && (splitted.size() > 1))
{
/* matches esp_log_level_t */
if ((toUpper(splitted[1]) == "TRUE") || (toUpper(splitted[1]) == "2"))
@@ -581,26 +601,33 @@ bool ClassFlowControll::ReadParameter(FILE* pfile, string& aktparamgraph)
{
LogFile.setLogLevel(ESP_LOG_DEBUG);
}
/* If system reboot was not triggered by user and reboot was caused by execption -> keep log level to DEBUG */
if (!getIsPlannedReboot() && (esp_reset_reason() == ESP_RST_PANIC))
LogFile.setLogLevel(ESP_LOG_DEBUG);
}
if ((toUpper(splitted[0]) == "LOGFILERETENTIONINDAYS") && (splitted.size() > 1))
if ((toUpper(splitted[0]) == "LOGFILESRETENTION") && (splitted.size() > 1))
{
LogFile.SetLogFileRetention(std::stoi(splitted[1]));
}
/* TimeServer and TimeZone got already read from the config, see setupTime () */
if ((toUpper(splitted[0]) == "RSSITHREASHOLD") && (splitted.size() > 1))
#if (defined WLAN_USE_ROAMING_BY_SCANNING || (defined WLAN_USE_MESH_ROAMING && defined WLAN_USE_MESH_ROAMING_ACTIVATE_CLIENT_TRIGGERED_QUERIES))
if ((toUpper(splitted[0]) == "RSSITHRESHOLD") && (splitted.size() > 1))
{
if (ChangeRSSIThreashold(WLAN_CONFIG_FILE, atoi(splitted[1].c_str())))
int RSSIThresholdTMP = atoi(splitted[1].c_str());
RSSIThresholdTMP = min(0, max(-100, RSSIThresholdTMP)); // Verify input limits (-100 - 0)
if (ChangeRSSIThreshold(WLAN_CONFIG_FILE, RSSIThresholdTMP))
{
// reboot necessary so that the new wlan.ini is also used !!!
fclose(pfile);
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Rebooting to activate new RSSITHREASHOLD ...");
esp_restart();
hard_restart();
LogFile.WriteToFile(ESP_LOG_WARN, TAG, "Rebooting to activate new RSSITHRESHOLD ...");
doReboot();
}
}
#endif
if ((toUpper(splitted[0]) == "HOSTNAME") && (splitted.size() > 1))
{
@@ -608,9 +635,7 @@ bool ClassFlowControll::ReadParameter(FILE* pfile, string& aktparamgraph)
{
// reboot necessary so that the new wlan.ini is also used !!!
fclose(pfile);
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Rebooting to activate new HOSTNAME...");
esp_restart();
hard_restart();
LogFile.WriteToFile(ESP_LOG_WARN, TAG, "Rebooting to activate new HOSTNAME...");
doReboot();
}
}
@@ -660,7 +685,7 @@ int ClassFlowControll::CleanTempFolder() {
esp_err_t ClassFlowControll::SendRawJPG(httpd_req_t *req)
{
return flowmakeimage != NULL ? flowmakeimage->SendRawJPG(req) : ESP_FAIL;
return flowtakeimage != NULL ? flowtakeimage->SendRawJPG(req) : ESP_FAIL;
}
@@ -801,7 +826,7 @@ esp_err_t ClassFlowControll::GetJPGStream(std::string _fn, httpd_req_t *req)
return ESP_FAIL;
}
_send = new CImageBasis(flowalignment->ImageBasis);
_send = new CImageBasis("alg_roi", flowalignment->ImageBasis);
if (_send->ImageOkay()) {
if (flowalignment) flowalignment->DrawRef(_send);

View File

@@ -6,7 +6,7 @@
#include <string>
#include "ClassFlow.h"
#include "ClassFlowMakeImage.h"
#include "ClassFlowTakeImage.h"
#include "ClassFlowAlignment.h"
#include "ClassFlowCNNGeneral.h"
#include "ClassFlowPostProcessing.h"
@@ -15,6 +15,7 @@
#endif //ENABLE_MQTT
#ifdef ENABLE_INFLUXDB
#include "ClassFlowInfluxDB.h"
#include "ClassFlowInfluxDBv2.h"
#endif //ENABLE_INFLUXDB
#include "ClassFlowCNNGeneral.h"
#include "ClassFlowWriteList.h"
@@ -29,20 +30,21 @@ protected:
ClassFlowCNNGeneral* flowanalog;
ClassFlowCNNGeneral* flowdigit;
// ClassFlowDigit* flowdigit;
ClassFlowMakeImage* flowmakeimage;
ClassFlowTakeImage* flowtakeimage;
ClassFlow* CreateClassFlow(std::string _type);
bool AutoStart;
float AutoIntervall;
float AutoInterval;
bool SetupModeActive;
void SetInitialParameter(void);
std::string aktstatusWithTime;
std::string aktstatus;
int aktRunNr;
public:
void InitFlow(std::string config);
bool doFlow(string time);
void doFlowMakeImageOnly(string time);
void doFlowTakeImageOnly(string time);
bool getStatusSetupModus(){return SetupModeActive;};
string getReadout(bool _rawvalue, bool _noerror);
string getReadoutAll(int _type);
@@ -68,8 +70,9 @@ public:
std::string doSingleStep(std::string _stepname, std::string _host);
bool isAutoStart(long &_intervall);
bool isAutoStart(long &_interval);
std::string* getActStatusWithTime();
std::string* getActStatus();
void setActStatus(std::string _aktstatus);

View File

@@ -50,6 +50,7 @@ struct NumberPost {
int DecimalShiftInitial;
float AnalogDigitalTransitionStart; // When is the digit > x.1, i.e. when does it start to tilt?
int Nachkomma;
string Fieldname; // Fieldname in InfluxDB2
bool isExtendedResolution;

View File

@@ -24,7 +24,7 @@ ClassFlowImage::ClassFlowImage(const char* logTag)
this->logTag = logTag;
isLogImage = false;
disabled = false;
this->logfileRetentionInDays = 5;
this->imagesRetention = 5;
}
ClassFlowImage::ClassFlowImage(std::vector<ClassFlow*> * lfc, const char* logTag) : ClassFlow(lfc)
@@ -32,7 +32,7 @@ ClassFlowImage::ClassFlowImage(std::vector<ClassFlow*> * lfc, const char* logTag
this->logTag = logTag;
isLogImage = false;
disabled = false;
this->logfileRetentionInDays = 5;
this->imagesRetention = 5;
}
ClassFlowImage::ClassFlowImage(std::vector<ClassFlow*> * lfc, ClassFlow *_prev, const char* logTag) : ClassFlow(lfc, _prev)
@@ -40,7 +40,7 @@ ClassFlowImage::ClassFlowImage(std::vector<ClassFlow*> * lfc, ClassFlow *_prev,
this->logTag = logTag;
isLogImage = false;
disabled = false;
this->logfileRetentionInDays = 5;
this->imagesRetention = 5;
}
@@ -48,7 +48,7 @@ string ClassFlowImage::CreateLogFolder(string time) {
if (!isLogImage)
return "";
string logPath = LogImageLocation + "/" + time.LOGFILE_TIME_FORMAT_DATE_EXTR + "/" + time.LOGFILE_TIME_FORMAT_HOUR_EXTR;
string logPath = imagesLocation + "/" + time.LOGFILE_TIME_FORMAT_DATE_EXTR + "/" + time.LOGFILE_TIME_FORMAT_HOUR_EXTR;
isLogImage = mkdir_r(logPath.c_str(), S_IRWXU) == 0;
if (!isLogImage) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Can't create log folder for analog images. Path " + logPath);
@@ -95,7 +95,7 @@ void ClassFlowImage::RemoveOldLogs()
return;
ESP_LOGD(TAG, "remove old images");
if (logfileRetentionInDays == 0) {
if (imagesRetention == 0) {
return;
}
@@ -104,17 +104,17 @@ void ClassFlowImage::RemoveOldLogs()
char cmpfilename[30];
time(&rawtime);
rawtime = addDays(rawtime, -logfileRetentionInDays + 1);
rawtime = addDays(rawtime, -1 * imagesRetention + 1);
timeinfo = localtime(&rawtime);
//ESP_LOGD(TAG, "ImagefileRetentionInDays: %d", logfileRetentionInDays);
//ESP_LOGD(TAG, "ImagefileRetentionInDays: %d", imagesRetention);
strftime(cmpfilename, 30, LOGFILE_TIME_FORMAT, timeinfo);
//ESP_LOGD(TAG, "file name to compare: %s", cmpfilename);
string folderName = string(cmpfilename).LOGFILE_TIME_FORMAT_DATE_EXTR;
DIR *dir = opendir(LogImageLocation.c_str());
DIR *dir = opendir(imagesLocation.c_str());
if (!dir) {
ESP_LOGE(TAG, "Failed to stat dir: %s", LogImageLocation.c_str());
ESP_LOGE(TAG, "Failed to stat dir: %s", imagesLocation.c_str());
return;
}
@@ -122,7 +122,7 @@ void ClassFlowImage::RemoveOldLogs()
int deleted = 0;
int notDeleted = 0;
while ((entry = readdir(dir)) != NULL) {
string folderPath = LogImageLocation + "/" + entry->d_name;
string folderPath = imagesLocation + "/" + entry->d_name;
if (entry->d_type == DT_DIR) {
//ESP_LOGD(TAG, "Compare %s to %s", entry->d_name, folderName.c_str());
if ((strlen(entry->d_name) == folderName.length()) && (strcmp(entry->d_name, folderName.c_str()) < 0)) {

View File

@@ -10,9 +10,9 @@ using namespace std;
class ClassFlowImage : public ClassFlow
{
protected:
string LogImageLocation;
string imagesLocation;
bool isLogImage;
unsigned short logfileRetentionInDays;
unsigned short imagesRetention;
const char* logTag;
string CreateLogFolder(string time);

View File

@@ -0,0 +1,219 @@
#ifdef ENABLE_INFLUXDB
#include <sstream>
#include "ClassFlowInfluxDBv2.h"
#include "Helper.h"
#include "connect_wlan.h"
#include "time_sntp.h"
#include "interface_influxdb.h"
#include "ClassFlowPostProcessing.h"
#include "esp_log.h"
#include "../../include/defines.h"
#include "ClassLogFile.h"
#include <time.h>
static const char* TAG = "class_flow_influxDbv2";
void ClassFlowInfluxDBv2::SetInitialParameter(void)
{
uri = "";
database = "";
measurement = "";
dborg = "";
dbtoken = "";
// dbfield = "";
OldValue = "";
flowpostprocessing = NULL;
previousElement = NULL;
ListFlowControll = NULL;
disabled = false;
InfluxDBenable = false;
}
ClassFlowInfluxDBv2::ClassFlowInfluxDBv2()
{
SetInitialParameter();
}
ClassFlowInfluxDBv2::ClassFlowInfluxDBv2(std::vector<ClassFlow*>* lfc)
{
SetInitialParameter();
ListFlowControll = lfc;
for (int i = 0; i < ListFlowControll->size(); ++i)
{
if (((*ListFlowControll)[i])->name().compare("ClassFlowPostProcessing") == 0)
{
flowpostprocessing = (ClassFlowPostProcessing*) (*ListFlowControll)[i];
}
}
}
ClassFlowInfluxDBv2::ClassFlowInfluxDBv2(std::vector<ClassFlow*>* lfc, ClassFlow *_prev)
{
SetInitialParameter();
previousElement = _prev;
ListFlowControll = lfc;
for (int i = 0; i < ListFlowControll->size(); ++i)
{
if (((*ListFlowControll)[i])->name().compare("ClassFlowPostProcessing") == 0)
{
flowpostprocessing = (ClassFlowPostProcessing*) (*ListFlowControll)[i];
}
}
}
bool ClassFlowInfluxDBv2::ReadParameter(FILE* pfile, string& aktparamgraph)
{
std::vector<string> splitted;
aktparamgraph = trim(aktparamgraph);
printf("akt param: %s\n", aktparamgraph.c_str());
if (aktparamgraph.size() == 0)
if (!this->GetNextParagraph(pfile, aktparamgraph))
return false;
if (toUpper(aktparamgraph).compare("[INFLUXDBV2]") != 0)
return false;
while (this->getNextLine(pfile, &aktparamgraph) && !this->isNewParagraph(aktparamgraph))
{
// ESP_LOGD(TAG, "while loop reading line: %s", aktparamgraph.c_str());
splitted = ZerlegeZeile(aktparamgraph);
std::string _param = GetParameterName(splitted[0]);
if ((toUpper(_param) == "ORG") && (splitted.size() > 1))
{
this->dborg = splitted[1];
}
if ((toUpper(_param) == "TOKEN") && (splitted.size() > 1))
{
this->dbtoken = splitted[1];
}
if ((toUpper(_param) == "URI") && (splitted.size() > 1))
{
this->uri = splitted[1];
}
if (((toUpper(_param) == "MEASUREMENT")) && (splitted.size() > 1))
{
this->measurement = splitted[1];
}
if (((toUpper(_param) == "FIELDNAME")) && (splitted.size() > 1))
{
handleFieldname(splitted[0], splitted[1]);
}
if (((toUpper(splitted[0]) == "DATABASE")) && (splitted.size() > 1))
{
this->database = splitted[1];
}
}
printf("uri: %s\n", uri.c_str());
printf("measurement: %s\n", measurement.c_str());
printf("org: %s\n", dborg.c_str());
printf("token: %s\n", dbtoken.c_str());
if ((uri.length() > 0) && (database.length() > 0) && (measurement.length() > 0) && (dbtoken.length() > 0) && (dborg.length() > 0))
{
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Init InfluxDB with uri: " + uri + ", measurement: " + measurement + ", org: " + dborg + ", token: *****");
// printf("vor V2 Init\n");
InfluxDB_V2_Init(uri, database, measurement, dborg, dbtoken);
// printf("nach V2 Init\n");
InfluxDBenable = true;
} else {
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "InfluxDBv2 (Verion2 !!!) init skipped as we are missing some parameters");
}
return true;
}
string ClassFlowInfluxDBv2::GetInfluxDBMeasurement()
{
return measurement;
}
void ClassFlowInfluxDBv2::handleFieldname(string _decsep, string _value)
{
string _digit, _decpos;
int _pospunkt = _decsep.find_first_of(".");
// ESP_LOGD(TAG, "Name: %s, Pospunkt: %d", _decsep.c_str(), _pospunkt);
if (_pospunkt > -1)
_digit = _decsep.substr(0, _pospunkt);
else
_digit = "default";
for (int j = 0; j < flowpostprocessing->NUMBERS.size(); ++j)
{
if (_digit == "default") // Set to default first (if nothing else is set)
{
flowpostprocessing->NUMBERS[j]->Fieldname = _value;
}
if (flowpostprocessing->NUMBERS[j]->name == _digit)
{
flowpostprocessing->NUMBERS[j]->Fieldname = _value;
}
}
}
bool ClassFlowInfluxDBv2::doFlow(string zwtime)
{
if (!InfluxDBenable)
return true;
std::string result;
std::string resulterror = "";
std::string resultraw = "";
std::string resultrate = "";
std::string resulttimestamp = "";
string zw = "";
string namenumber = "";
if (flowpostprocessing)
{
std::vector<NumberPost*>* NUMBERS = flowpostprocessing->GetNumbers();
for (int i = 0; i < (*NUMBERS).size(); ++i)
{
result = (*NUMBERS)[i]->ReturnValue;
resultraw = (*NUMBERS)[i]->ReturnRawValue;
resulterror = (*NUMBERS)[i]->ErrorMessageText;
resultrate = (*NUMBERS)[i]->ReturnRateValue;
resulttimestamp = (*NUMBERS)[i]->timeStamp;
if ((*NUMBERS)[i]->Fieldname.length() > 0)
{
namenumber = (*NUMBERS)[i]->Fieldname;
}
else
{
namenumber = (*NUMBERS)[i]->name;
if (namenumber == "default")
namenumber = "value";
else
namenumber = namenumber + "/value";
}
printf("vor sende Influx_DB_V2 - namenumber. %s, result: %s, timestampt: %s", namenumber.c_str(), result.c_str(), resulttimestamp.c_str());
if (result.length() > 0)
InfluxDB_V2_Publish(namenumber, result, resulttimestamp);
// InfluxDB_V2_Publish(namenumber, result, resulttimestamp);
}
}
OldValue = result;
return true;
}
#endif //ENABLE_INFLUXDB

View File

@@ -0,0 +1,41 @@
#ifdef ENABLE_INFLUXDB
#pragma once
#ifndef CLASSFINFLUXDBv2_H
#define CLASSFINFLUXDBv2_H
#include "ClassFlow.h"
#include "ClassFlowPostProcessing.h"
#include <string>
class ClassFlowInfluxDBv2 :
public ClassFlow
{
protected:
std::string uri, database, measurement;
std::string dborg, dbtoken, dbfield;
std::string OldValue;
ClassFlowPostProcessing* flowpostprocessing;
bool InfluxDBenable;
void SetInitialParameter(void);
void handleFieldname(string _decsep, string _value);
public:
ClassFlowInfluxDBv2();
ClassFlowInfluxDBv2(std::vector<ClassFlow*>* lfc);
ClassFlowInfluxDBv2(std::vector<ClassFlow*>* lfc, ClassFlow *_prev);
string GetInfluxDBMeasurement();
bool ReadParameter(FILE* pfile, string& aktparamgraph);
bool doFlow(string time);
string name(){return "ClassFlowInfluxDBv2";};
};
#endif //CLASSFINFLUXDBv2_H
#endif //ENABLE_INFLUXDB

View File

@@ -5,6 +5,7 @@
#include "ClassFlowMQTT.h"
#include "Helper.h"
#include "connect_wlan.h"
#include "read_wlanini.h"
#include "ClassLogFile.h"
#include "time_sntp.h"
@@ -31,18 +32,18 @@ void ClassFlowMQTT::SetInitialParameter(void)
topicError = "";
topicRate = "";
topicTimeStamp = "";
maintopic = hostname;
maintopic = wlan_config.hostname;
topicUptime = "";
topicFreeMem = "";
clientname = "AIOTED-" + getMac();
clientname = wlan_config.hostname;
OldValue = "";
flowpostprocessing = NULL;
user = "";
password = "";
SetRetainFlag = 0;
SetRetainFlag = false;
previousElement = NULL;
ListFlowControll = NULL;
disabled = false;
@@ -95,7 +96,7 @@ bool ClassFlowMQTT::ReadParameter(FILE* pfile, string& aktparamgraph)
if (!this->GetNextParagraph(pfile, aktparamgraph))
return false;
if (toUpper(aktparamgraph).compare("[MQTT]") != 0) // Paragraph does not fit MakeImage
if (toUpper(aktparamgraph).compare("[MQTT]") != 0) // Paragraph does not fit MQTT
return false;
while (this->getNextLine(pfile, &aktparamgraph) && !this->isNewParagraph(aktparamgraph))
@@ -113,10 +114,10 @@ bool ClassFlowMQTT::ReadParameter(FILE* pfile, string& aktparamgraph)
{
this->uri = splitted[1];
}
if ((toUpper(splitted[0]) == "SETRETAINFLAG") && (splitted.size() > 1))
if ((toUpper(splitted[0]) == "RETAINMESSAGES") && (splitted.size() > 1))
{
if (toUpper(splitted[1]) == "TRUE") {
SetRetainFlag = 1;
SetRetainFlag = true;
setMqtt_Server_Retain(SetRetainFlag);
}
}
@@ -155,6 +156,9 @@ bool ClassFlowMQTT::ReadParameter(FILE* pfile, string& aktparamgraph)
else if (toUpper(splitted[1]) == "ENERGY_MWH") {
mqttServer_setMeterType("energy", "MWh", "h", "MW");
}
else if (toUpper(splitted[1]) == "ENERGY_GJ") {
mqttServer_setMeterType("energy", "GJ", "h", "GJ/h");
}
}
if ((toUpper(splitted[0]) == "CLIENTID") && (splitted.size() > 1))
@@ -165,7 +169,6 @@ bool ClassFlowMQTT::ReadParameter(FILE* pfile, string& aktparamgraph)
if (((toUpper(splitted[0]) == "TOPIC") || (toUpper(splitted[0]) == "MAINTOPIC")) && (splitted.size() > 1))
{
maintopic = splitted[1];
mqttServer_setMainTopic(maintopic);
}
}
@@ -174,6 +177,8 @@ bool ClassFlowMQTT::ReadParameter(FILE* pfile, string& aktparamgraph)
* How ever we need the interval parameter from the ClassFlowControll, but that only gets started later.
* To work around this, we delay the start and trigger it from ClassFlowControll::ReadParameter() */
mqttServer_setMainTopic(maintopic);
return true;
}
@@ -184,9 +189,9 @@ string ClassFlowMQTT::GetMQTTMainTopic()
}
bool ClassFlowMQTT::Start(float AutoIntervall)
bool ClassFlowMQTT::Start(float AutoInterval)
{
roundInterval = AutoIntervall; // Minutes
roundInterval = AutoInterval; // Minutes
keepAlive = roundInterval * 60 * 2.5; // Seconds, make sure it is greater thatn 2 rounds!
std::stringstream stream;
@@ -209,6 +214,7 @@ bool ClassFlowMQTT::Start(float AutoIntervall)
bool ClassFlowMQTT::doFlow(string zwtime)
{
bool success;
std::string result;
std::string resulterror = "";
std::string resultraw = "";
@@ -219,8 +225,12 @@ bool ClassFlowMQTT::doFlow(string zwtime)
std::string resultchangabs = "";
string zw = "";
string namenumber = "";
int qos = 1;
publishSystemData();
/* Send the the Homeassistant Discovery and the Static Topics in case they where scheduled */
sendDiscovery_and_static_Topics();
success = publishSystemData(qos);
if (flowpostprocessing && getMQTTisConnected())
{
@@ -246,13 +256,13 @@ bool ClassFlowMQTT::doFlow(string zwtime)
if (result.length() > 0)
MQTTPublish(namenumber + "value", result, SetRetainFlag);
success |= MQTTPublish(namenumber + "value", result, qos, SetRetainFlag);
if (resulterror.length() > 0)
MQTTPublish(namenumber + "error", resulterror, SetRetainFlag);
success |= MQTTPublish(namenumber + "error", resulterror, qos, SetRetainFlag);
if (resultrate.length() > 0) {
MQTTPublish(namenumber + "rate", resultrate, SetRetainFlag);
success |= MQTTPublish(namenumber + "rate", resultrate, qos, SetRetainFlag);
std::string resultRatePerTimeUnit;
if (getTimeUnit() == "h") { // Need conversion to be per hour
@@ -261,22 +271,22 @@ bool ClassFlowMQTT::doFlow(string zwtime)
else { // Keep per minute
resultRatePerTimeUnit = resultrate;
}
MQTTPublish(namenumber + "rate_per_time_unit", resultRatePerTimeUnit, SetRetainFlag);
success |= MQTTPublish(namenumber + "rate_per_time_unit", resultRatePerTimeUnit, qos, SetRetainFlag);
}
if (resultchangabs.length() > 0) {
MQTTPublish(namenumber + "changeabsolut", resultchangabs, SetRetainFlag); // Legacy API
MQTTPublish(namenumber + "rate_per_digitalization_round", resultchangabs, SetRetainFlag);
success |= MQTTPublish(namenumber + "changeabsolut", resultchangabs, qos, SetRetainFlag); // Legacy API
success |= MQTTPublish(namenumber + "rate_per_digitalization_round", resultchangabs, qos, SetRetainFlag);
}
if (resultraw.length() > 0)
MQTTPublish(namenumber + "raw", resultraw, SetRetainFlag);
success |= MQTTPublish(namenumber + "raw", resultraw, qos, SetRetainFlag);
if (resulttimestamp.length() > 0)
MQTTPublish(namenumber + "timestamp", resulttimestamp, SetRetainFlag);
success |= MQTTPublish(namenumber + "timestamp", resulttimestamp, qos, SetRetainFlag);
std::string json = flowpostprocessing->getJsonFromNumber(i, "\n");
MQTTPublish(namenumber + "json", json, SetRetainFlag);
success |= MQTTPublish(namenumber + "json", json, qos, SetRetainFlag);
}
}
@@ -294,10 +304,14 @@ bool ClassFlowMQTT::doFlow(string zwtime)
// result = result + "\t" + zw;
// }
// }
// MQTTPublish(topic, result, SetRetainFlag);
// success |= MQTTPublish(topic, result, qos, SetRetainFlag);
// }
OldValue = result;
if (!success) {
LogFile.WriteToFile(ESP_LOG_WARN, TAG, "One or more MQTT topics failed to be published!");
}
return true;
}

View File

@@ -19,7 +19,7 @@ protected:
std::string OldValue;
ClassFlowPostProcessing* flowpostprocessing;
std::string user, password;
int SetRetainFlag;
bool SetRetainFlag;
int keepAlive; // Seconds
float roundInterval; // Minutes
@@ -32,7 +32,7 @@ public:
ClassFlowMQTT(std::vector<ClassFlow*>* lfc, ClassFlow *_prev);
string GetMQTTMainTopic();
bool Start(float AutoIntervall);
bool Start(float AutoInterval);
bool ReadParameter(FILE* pfile, string& aktparamgraph);
bool doFlow(string time);

View File

@@ -1,6 +1,6 @@
#include "ClassFlowPostProcessing.h"
#include "Helper.h"
#include "ClassFlowMakeImage.h"
#include "ClassFlowTakeImage.h"
#include "ClassLogFile.h"
#include <iomanip>
@@ -285,7 +285,7 @@ ClassFlowPostProcessing::ClassFlowPostProcessing(std::vector<ClassFlow*>* lfc, C
ListFlowControll = NULL;
FilePreValue = FormatFileName("/sdcard/config/prevalue.ini");
ListFlowControll = lfc;
flowMakeImage = NULL;
flowTakeImage = NULL;
UpdatePreValueINI = false;
IgnoreLeadingNaN = false;
flowAnalog = _analog;
@@ -293,9 +293,9 @@ ClassFlowPostProcessing::ClassFlowPostProcessing(std::vector<ClassFlow*>* lfc, C
for (int i = 0; i < ListFlowControll->size(); ++i)
{
if (((*ListFlowControll)[i])->name().compare("ClassFlowMakeImage") == 0)
if (((*ListFlowControll)[i])->name().compare("ClassFlowTakeImage") == 0)
{
flowMakeImage = (ClassFlowMakeImage*) (*ListFlowControll)[i];
flowTakeImage = (ClassFlowTakeImage*) (*ListFlowControll)[i];
}
}
}
@@ -402,7 +402,7 @@ void ClassFlowPostProcessing::handleAllowNegativeRate(string _decsep, string _va
_digit = _decsep.substr(0, _pospunkt);
else
_digit = "default";
for (int j = 0; j < NUMBERS.size(); ++j)
{
bool _rt = false;
@@ -503,7 +503,7 @@ bool ClassFlowPostProcessing::ReadParameter(FILE* pfile, string& aktparamgraph)
return false;
if (aktparamgraph.compare("[PostProcessing]") != 0) // Paragraph does not fit MakeImage
if (aktparamgraph.compare("[PostProcessing]") != 0) // Paragraph does not fit PostProcessing
return false;
InitNUMBERS();
@@ -715,7 +715,7 @@ bool ClassFlowPostProcessing::doFlow(string zwtime)
// Update decimal point, as the decimal places can also change when changing from CNNType Auto --> xyz:
imagetime = flowMakeImage->getTimeImageTaken();
imagetime = flowTakeImage->getTimeImageTaken();
if (imagetime == 0)
time(&imagetime);
@@ -841,6 +841,7 @@ bool ClassFlowPostProcessing::doFlow(string zwtime)
if (!NUMBERS[j]->AllowNegativeRates)
{
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "handleAllowNegativeRate for device: " + NUMBERS[j]->name);
if ((NUMBERS[j]->Value < NUMBERS[j]->PreValue))
{
#ifdef SERIAL_DEBUG
@@ -848,6 +849,7 @@ bool ClassFlowPostProcessing::doFlow(string zwtime)
NUMBERS[j]->PreValue-(2/pow(10, NUMBERS[j]->Nachkomma))
) ;
#endif
// Include inaccuracy of 0.2 for isExtendedResolution.
if (NUMBERS[j]->Value >= (NUMBERS[j]->PreValue-(2/pow(10, NUMBERS[j]->Nachkomma))) && NUMBERS[j]->isExtendedResolution) {
NUMBERS[j]->Value = NUMBERS[j]->PreValue;

View File

@@ -4,7 +4,7 @@
#define CLASSFFLOWPOSTPROCESSING_H
#include "ClassFlow.h"
#include "ClassFlowMakeImage.h"
#include "ClassFlowTakeImage.h"
#include "ClassFlowCNNGeneral.h"
#include "ClassFlowDefineTypes.h"
@@ -15,7 +15,6 @@ class ClassFlowPostProcessing :
public ClassFlow
{
protected:
std::vector<NumberPost*> NUMBERS;
bool UpdatePreValueINI;
int PreValueAgeStartup;
@@ -29,7 +28,7 @@ protected:
string FilePreValue;
ClassFlowMakeImage *flowMakeImage;
ClassFlowTakeImage *flowTakeImage;
bool LoadPreValue(void);
string ShiftDecimal(string in, int _decShift);
@@ -54,6 +53,8 @@ protected:
public:
bool PreValueUse;
std::vector<NumberPost*> NUMBERS;
ClassFlowPostProcessing(std::vector<ClassFlow*>* lfc, ClassFlowCNNGeneral *_analog, ClassFlowCNNGeneral *_digit);
virtual ~ClassFlowPostProcessing(){};

View File

@@ -1,4 +1,4 @@
#include "ClassFlowMakeImage.h"
#include "ClassFlowTakeImage.h"
#include "Helper.h"
#include "ClassLogFile.h"
@@ -17,7 +17,7 @@
static const char* TAG = "flow_make_image";
esp_err_t ClassFlowMakeImage::camera_capture(){
esp_err_t ClassFlowTakeImage::camera_capture(){
string nm = namerawimage;
Camera.CaptureToFile(nm);
time(&TimeImageTaken);
@@ -26,7 +26,7 @@ esp_err_t ClassFlowMakeImage::camera_capture(){
return ESP_OK;
}
void ClassFlowMakeImage::takePictureWithFlash(int flash_duration)
void ClassFlowTakeImage::takePictureWithFlash(int flash_duration)
{
// in case the image is flipped, it must be reset here //
rawImage->width = image_width;
@@ -40,7 +40,7 @@ void ClassFlowMakeImage::takePictureWithFlash(int flash_duration)
if (SaveAllFiles) rawImage->SaveToFile(namerawimage);
}
void ClassFlowMakeImage::SetInitialParameter(void)
void ClassFlowTakeImage::SetInitialParameter(void)
{
waitbeforepicture = 5;
isImageSize = false;
@@ -56,15 +56,15 @@ void ClassFlowMakeImage::SetInitialParameter(void)
}
ClassFlowMakeImage::ClassFlowMakeImage(std::vector<ClassFlow*>* lfc) : ClassFlowImage(lfc, TAG)
ClassFlowTakeImage::ClassFlowTakeImage(std::vector<ClassFlow*>* lfc) : ClassFlowImage(lfc, TAG)
{
LogImageLocation = "/log/source";
logfileRetentionInDays = 5;
imagesLocation = "/log/source";
imagesRetention = 5;
SetInitialParameter();
}
bool ClassFlowMakeImage::ReadParameter(FILE* pfile, string& aktparamgraph)
bool ClassFlowTakeImage::ReadParameter(FILE* pfile, string& aktparamgraph)
{
std::vector<string> splitted;
@@ -77,21 +77,21 @@ bool ClassFlowMakeImage::ReadParameter(FILE* pfile, string& aktparamgraph)
if (!this->GetNextParagraph(pfile, aktparamgraph))
return false;
if (aktparamgraph.compare("[MakeImage]") != 0) // Paragraph does not fit MakeImage
if (aktparamgraph.compare("[TakeImage]") != 0) // Paragraph does not fit TakeImage
return false;
while (this->getNextLine(pfile, &aktparamgraph) && !this->isNewParagraph(aktparamgraph))
{
splitted = ZerlegeZeile(aktparamgraph);
if ((splitted[0] == "LogImageLocation") && (splitted.size() > 1))
if ((toUpper(splitted[0]) == "RAWIMAGESLOCATION") && (splitted.size() > 1))
{
LogImageLocation = "/sdcard" + splitted[1];
imagesLocation = "/sdcard" + splitted[1];
isLogImage = true;
}
if ((splitted[0] == "ImageQuality") && (splitted.size() > 1))
if ((toUpper(splitted[0]) == "IMAGEQUALITY") && (splitted.size() > 1))
ImageQuality = std::stod(splitted[1]);
if ((splitted[0] == "ImageSize") && (splitted.size() > 1))
if ((toUpper(splitted[0]) == "IMAGESIZE") && (splitted.size() > 1))
{
ImageSize = Camera.TextToFramesize(splitted[1].c_str());
isImageSize = true;
@@ -108,9 +108,9 @@ bool ClassFlowMakeImage::ReadParameter(FILE* pfile, string& aktparamgraph)
waitbeforepicture = stoi(splitted[1]);
}
if ((toUpper(splitted[0]) == "LOGFILERETENTIONINDAYS") && (splitted.size() > 1))
if ((toUpper(splitted[0]) == "RAWIMAGESRETENTION") && (splitted.size() > 1))
{
this->logfileRetentionInDays = std::stoi(splitted[1]);
this->imagesRetention = std::stoi(splitted[1]);
}
if ((toUpper(splitted[0]) == "BRIGHTNESS") && (splitted.size() > 1))
@@ -154,7 +154,7 @@ bool ClassFlowMakeImage::ReadParameter(FILE* pfile, string& aktparamgraph)
image_width = Camera.image_width;
image_height = Camera.image_height;
rawImage = new CImageBasis();
rawImage = new CImageBasis("rawImage");
rawImage->CreateEmptyImage(image_width, image_height, 3);
waitbeforepicture_store = waitbeforepicture;
@@ -173,7 +173,7 @@ bool ClassFlowMakeImage::ReadParameter(FILE* pfile, string& aktparamgraph)
}
string ClassFlowMakeImage::getHTMLSingleStep(string host)
string ClassFlowTakeImage::getHTMLSingleStep(string host)
{
string result;
result = "Raw Image: <br>\n<img src=\"" + host + "/img_tmp/raw.jpg\">\n";
@@ -181,14 +181,14 @@ string ClassFlowMakeImage::getHTMLSingleStep(string host)
}
bool ClassFlowMakeImage::doFlow(string zwtime)
bool ClassFlowTakeImage::doFlow(string zwtime)
{
string logPath = CreateLogFolder(zwtime);
int flash_duration = (int) (waitbeforepicture * 1000);
#ifdef DEBUG_DETAIL_ON
LogFile.WriteHeapInfo("ClassFlowMakeImage::doFlow - Before takePictureWithFlash");
LogFile.WriteHeapInfo("ClassFlowTakeImage::doFlow - Before takePictureWithFlash");
#endif
@@ -204,7 +204,7 @@ bool ClassFlowMakeImage::doFlow(string zwtime)
#ifdef DEBUG_DETAIL_ON
LogFile.WriteHeapInfo("ClassFlowMakeImage::doFlow - After takePictureWithFlash");
LogFile.WriteHeapInfo("ClassFlowTakeImage::doFlow - After takePictureWithFlash");
#endif
LogImage(logPath, "raw", NULL, NULL, zwtime, rawImage);
@@ -212,14 +212,14 @@ bool ClassFlowMakeImage::doFlow(string zwtime)
RemoveOldLogs();
#ifdef DEBUG_DETAIL_ON
LogFile.WriteHeapInfo("ClassFlowMakeImage::doFlow - After RemoveOldLogs");
LogFile.WriteHeapInfo("ClassFlowTakeImage::doFlow - After RemoveOldLogs");
#endif
return true;
}
esp_err_t ClassFlowMakeImage::SendRawJPG(httpd_req_t *req)
esp_err_t ClassFlowTakeImage::SendRawJPG(httpd_req_t *req)
{
int flash_duration = (int) (waitbeforepicture * 1000);
time(&TimeImageTaken);
@@ -229,9 +229,9 @@ esp_err_t ClassFlowMakeImage::SendRawJPG(httpd_req_t *req)
}
ImageData* ClassFlowMakeImage::SendRawImage()
ImageData* ClassFlowTakeImage::SendRawImage()
{
CImageBasis *zw = new CImageBasis(rawImage);
CImageBasis *zw = new CImageBasis("SendRawImage", rawImage);
ImageData *id;
int flash_duration = (int) (waitbeforepicture * 1000);
Camera.CaptureToBasisImage(zw, flash_duration);
@@ -243,12 +243,12 @@ ImageData* ClassFlowMakeImage::SendRawImage()
return id;
}
time_t ClassFlowMakeImage::getTimeImageTaken()
time_t ClassFlowTakeImage::getTimeImageTaken()
{
return TimeImageTaken;
}
ClassFlowMakeImage::~ClassFlowMakeImage(void)
ClassFlowTakeImage::~ClassFlowTakeImage(void)
{
delete rawImage;
}

View File

@@ -1,7 +1,7 @@
#pragma once
#ifndef CLASSFFLOWMAKEIMAGE_H
#define CLASSFFLOWMAKEIMAGE_H
#ifndef CLASSFFLOWTAKEIMAGE_H
#define CLASSFFLOWTAKEIMAGE_H
#include "ClassFlowImage.h"
#include "ClassControllCamera.h"
@@ -9,7 +9,7 @@
#include <string>
class ClassFlowMakeImage :
class ClassFlowTakeImage :
public ClassFlowImage
{
protected:
@@ -37,19 +37,19 @@ protected:
public:
CImageBasis *rawImage;
ClassFlowMakeImage(std::vector<ClassFlow*>* lfc);
ClassFlowTakeImage(std::vector<ClassFlow*>* lfc);
bool ReadParameter(FILE* pfile, string& aktparamgraph);
bool doFlow(string time);
string getHTMLSingleStep(string host);
time_t getTimeImageTaken();
string name(){return "ClassFlowMakeImage";};
string name(){return "ClassFlowTakeImage";};
ImageData* SendRawImage();
esp_err_t SendRawJPG(httpd_req_t *req);
~ClassFlowMakeImage(void);
~ClassFlowTakeImage(void);
};
#endif //CLASSFFLOWMAKEIMAGE_H
#endif //CLASSFFLOWTAKEIMAGE_H

View File

@@ -64,14 +64,14 @@ string getESPHeapInfo(){
sprintf(aMsgBuf," | SPI Free: %ld", (long) aFreeSPIHeapSize);
espInfoResultStr += string(aMsgBuf);
sprintf(aMsgBuf," | SPI Larg Block: %ld", (long) aHeapLargestFreeBlockSize);
sprintf(aMsgBuf," | SPI Large Block: %ld", (long) aHeapLargestFreeBlockSize);
espInfoResultStr += string(aMsgBuf);
sprintf(aMsgBuf," | SPI Min Free: %ld", (long) aMinFreeHeapSize);
espInfoResultStr += string(aMsgBuf);
sprintf(aMsgBuf," | Int Free: %ld", (long) (aFreeInternalHeapSize));
espInfoResultStr += string(aMsgBuf);
sprintf(aMsgBuf," | Int Larg Block: %ld", (long) aHeapIntLargestFreeBlockSize);
sprintf(aMsgBuf," | Int Large Block: %ld", (long) aHeapIntLargestFreeBlockSize);
espInfoResultStr += string(aMsgBuf);
sprintf(aMsgBuf," | Int Min Free: %ld", (long) (aMinFreeInternalHeapSize));
espInfoResultStr += string(aMsgBuf);
@@ -224,15 +224,50 @@ void FindReplace(std::string& line, std::string& oldString, std::string& newStri
}
bool MakeDir(std::string _what)
/**
* Create a folder and its parent folders as needed
*/
bool MakeDir(std::string path)
{
int mk_ret = mkdir(_what.c_str(), 0775);
if (mk_ret)
{
ESP_LOGD(TAG, "error with mkdir %s ret %d", _what.c_str(), mk_ret);
return false;
std::string parent;
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Creating folder " + path + "...");
bool bSuccess = false;
int nRC = ::mkdir( path.c_str(), 0775 );
if( nRC == -1 )
{
switch( errno ) {
case ENOENT:
//parent didn't exist, try to create it
parent = path.substr(0, path.find_last_of('/'));
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Need to create parent folder first: " + parent);
if(MakeDir(parent)) {
//Now, try to create again.
bSuccess = 0 == ::mkdir( path.c_str(), 0775 );
}
else {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to create parent folder: " + parent);
bSuccess = false;
}
break;
case EEXIST:
//Done!
bSuccess = true;
break;
default:
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to create folder: " + path + " (errno: " + std::to_string(errno) + ")");
bSuccess = false;
break;
}
}
else {
bSuccess = true;
}
return true;
return bSuccess;
}
@@ -576,9 +611,6 @@ std::vector<string> HelperZerlegeZeile(std::string input, std::string _delimiter
std::vector<string> ZerlegeZeile(std::string input, std::string delimiter)
{
std::vector<string> Output;
input = trim(input, delimiter);
/* The input can have multiple formats:
* - key = value
* - key = value1 value2 value3 ...
@@ -593,12 +625,13 @@ std::vector<string> ZerlegeZeile(std::string input, std::string delimiter)
* As a workaround and to not break any legacy usage, we enforce to only use the
* equal sign, if the key is "password"
*/
if (input.find("password") != string::npos) { // Line contains a password, use the equal sign as the only delimiter and only split on first occurrence
if ((input.find("password") != string::npos) || (input.find("Token") != string::npos)) { // Line contains a password, use the equal sign as the only delimiter and only split on first occurrence
size_t pos = input.find("=");
Output.push_back(trim(input.substr(0, pos), ""));
Output.push_back(trim(input.substr(pos +1, string::npos), ""));
}
else { // Legacy Mode
input = trim(input, delimiter); // sonst werden delimiter am Ende (z.B. == im Token) gelöscht)
size_t pos = findDelimiterPos(input, delimiter);
std::string token;
while (pos != std::string::npos) {
@@ -900,3 +933,64 @@ const char* get404(void) {
" You could try your <a href=index.html target=_parent>luck</a> here!</pre>\n"
"<script>document.cookie = \"page=overview.html\"</script>"; // Make sure we load the overview page
}
std::string UrlDecode(const std::string& value)
{
std::string result;
result.reserve(value.size());
for (std::size_t i = 0; i < value.size(); ++i)
{
auto ch = value[i];
if (ch == '%' && (i + 2) < value.size())
{
auto hex = value.substr(i + 1, 2);
auto dec = static_cast<char>(std::strtol(hex.c_str(), nullptr, 16));
result.push_back(dec);
i += 2;
}
else if (ch == '+')
{
result.push_back(' ');
}
else
{
result.push_back(ch);
}
}
return result;
}
bool replaceString(std::string& s, std::string const& toReplace, std::string const& replaceWith) {
return replaceString(s, toReplace, replaceWith, true);
}
bool replaceString(std::string& s, std::string const& toReplace, std::string const& replaceWith, bool logIt) {
std::size_t pos = s.find(toReplace);
if (pos == std::string::npos) { // Not found
return false;
}
std::string old = s;
s.replace(pos, toReplace.length(), replaceWith);
if (logIt) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Migrated Configfile line '" + old + "' to '" + s + "'");
}
return true;
}
bool isInString(std::string& s, std::string const& toFind) {
std::size_t pos = s.find(toFind);
if (pos == std::string::npos) { // Not found
return false;
}
return true;
}

View File

@@ -76,6 +76,8 @@ enum SystemStatusFlag_t { // One bit per error
SYSTEM_STATUS_PSRAM_BAD = 1 << 0, // 1, Critical Error
SYSTEM_STATUS_HEAP_TOO_SMALL = 1 << 1, // 2, Critical Error
SYSTEM_STATUS_CAM_BAD = 1 << 2, // 4, Critical Error
SYSTEM_STATUS_SDCARD_CHECK_BAD = 1 << 3, // 8, Critical Error
SYSTEM_STATUS_FOLDER_CHECK_BAD = 1 << 4, // 16, Critical Error
// Second Byte
SYSTEM_STATUS_CAM_FB_BAD = 1 << (0+8), // 8, Flow still might work
@@ -93,4 +95,10 @@ std::string getFormatedUptime(bool compact);
const char* get404(void);
std::string UrlDecode(const std::string& value);
bool replaceString(std::string& s, std::string const& toReplace, std::string const& replaceWith);
bool replaceString(std::string& s, std::string const& toReplace, std::string const& replaceWith, bool logIt);
bool isInString(std::string& s, std::string const& toFind);
#endif //HELPER_H

View File

@@ -0,0 +1,42 @@
#include "ClassLogFile.h"
#include "esp_heap_caps.h"
static const char* TAG = "PSRAM";
using namespace std;
void *malloc_psram_heap(std::string name, size_t size, uint32_t caps) {
void *ptr;
ptr = heap_caps_malloc(size, caps);
if (ptr != NULL) {
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Allocated " + to_string(size) + " bytes in PSRAM for '" + name + "'");
}
else {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to allocate " + to_string(size) + " bytes in PSRAM for '" + name + "'!");
}
return ptr;
}
void *calloc_psram_heap(std::string name, size_t n, size_t size, uint32_t caps) {
void *ptr;
ptr = heap_caps_calloc(n, size, caps);
if (ptr != NULL) {
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Allocated " + to_string(size) + " bytes in PSRAM for '" + name + "'");
}
else {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to allocate " + to_string(size) + " bytes in PSRAM for '" + name + "'!");
}
return ptr;
}
void free_psram_heap(std::string name, void *ptr) {
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Freeing memory in PSRAM used for '" + name + "'...");
heap_caps_free(ptr);
}

View File

@@ -0,0 +1,7 @@
#include "esp_heap_caps.h"
void *malloc_psram_heap(std::string name, size_t size, uint32_t caps);
void *calloc_psram_heap(std::string name, size_t n, size_t size, uint32_t caps);
void free_psram_heap(std::string name, void *ptr);

View File

@@ -0,0 +1,166 @@
#include "sdcard_check.h"
#include <string.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#include <inttypes.h>
#include <sys/stat.h>
#include "esp_rom_crc.h"
#include "ClassLogFile.h"
static const char *TAG = "SDCARD";
int SDCardCheckRW(void)
{
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Basic R/W check started...");
FILE* pFile = NULL;
int iCRCMessage = 0;
pFile = fopen("/sdcard/sdcheck.txt","w");
if (pFile == NULL) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Basic R/W check: (E1) No able to open file to write");
return -1;
}
else {
std::string sMessage = "This message is used for a SD-Card basic check!";
iCRCMessage = esp_rom_crc16_le(0, (uint8_t*)sMessage.c_str(), sMessage.length());
if (fwrite(sMessage.c_str(), sMessage.length(), 1, pFile) == 0 ) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Basic R/W check: (E2) Not able to write file");
fclose(pFile);
unlink("/sdcard/sdcheck.txt");
return -2;
}
fclose(pFile);
}
pFile = fopen("/sdcard/sdcheck.txt","r");
if (pFile == NULL) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Basic R/W check: (E3) Not able to open file to read back");
unlink("/sdcard/sdcheck.txt");
return -3;
}
else {
char cReadBuf[50];
if (fgets(cReadBuf, sizeof(cReadBuf), pFile) == 0) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Basic R/W check: (E4) Not able to read file back");
fclose(pFile);
unlink("/sdcard/sdcheck.txt");
return -4;
}
else {
if (esp_rom_crc16_le(0, (uint8_t*)cReadBuf, strlen(cReadBuf)) != iCRCMessage) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Basic R/W check: (E5) Read back, but wrong CRC");
fclose(pFile);
unlink("/sdcard/sdcheck.txt");
return -5;
}
}
fclose(pFile);
}
if (unlink("/sdcard/sdcheck.txt") != 0) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Basic R/W check: (E6) Unable to delete the file");
return -6;
}
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Basic R/W check successful");
return 0;
}
bool SDCardCheckFolderFilePresence()
{
struct stat sb;
bool bRetval = true;
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Folder/file presence check started...");
/* check if folder exists: config */
if (stat("/sdcard/config", &sb) != 0) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Folder/file check: Folder /config not found");
bRetval = false;
}
/* check if folder exists: html */
if (stat("/sdcard/html", &sb) != 0) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Folder/file check: Folder /html not found");
bRetval = false;
}
/* check if folder exists: firmware */
if (stat("/sdcard/firmware", &sb) != 0) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Folder/file check: Folder /firmware not found");
bRetval = false;
}
/* check if folder exists: img_tmp */
if (stat("/sdcard/img_tmp", &sb) != 0) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Folder/file check: Folder /img_tmp not found");
bRetval = false;
}
/* check if folder exists: log */
if (stat("/sdcard/log", &sb) != 0) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Folder/file check: Folder /log not found");
bRetval = false;
}
/* check if folder exists: demo */
if (stat("/sdcard/demo", &sb) != 0) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Folder/file check: Folder /demo not found");
bRetval = false;
}
/* 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");
bRetval = false;
}
/* check if file exists: config.ini */
if (stat("/sdcard/config/config.ini", &sb) != 0) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Folder/file check: File /config/config.ini not found");
bRetval = false;
}
/* check if file exists: index.html */
if (stat("/sdcard/html/index.html", &sb) != 0) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Folder/file check: File /html/index.html not found");
bRetval = false;
}
/* check if file exists: ota.html */
if (stat("/sdcard/html/ota_page.html", &sb) != 0) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Folder/file check: File /html/ota.html not found");
bRetval = false;
}
/* check if file exists: log.html */
if (stat("/sdcard/html/log.html", &sb) != 0) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Folder/file check: File /html/log.html not found");
bRetval = false;
}
/* check if file exists: common.js */
if (stat("/sdcard/html/common.js", &sb) != 0) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Folder/file check: File /html/common.js not found");
bRetval = false;
}
/* check if file exists: gethost.js */
if (stat("/sdcard/html/gethost.js", &sb) != 0) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Folder/file check: File /html/gethost.js not found");
bRetval = false;
}
/* check if file exists: version.txt */
if (stat("/sdcard/html/version.txt", &sb) != 0) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Folder/file check: File /html/version.txt not found");
bRetval = false;
}
if (bRetval)
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Folder/file presence check successful");
return bRetval;
}

View File

@@ -0,0 +1,11 @@
#pragma once
#ifndef COMPONENTS_HELPER_SDCARD_CHECK_H
#define COMPONENTS_HELPER_SDCARD_CHECK_H
#include "../../include/defines.h"
int SDCardCheckRW(void);
bool SDCardCheckFolderFilePresence(void);
#endif /* COMPONENTS_HELPER_SDCARD_CHECK_H */

View File

@@ -0,0 +1,148 @@
#include "statusled.h"
#include <sys/types.h>
#include <sys/stat.h>
#include "driver/gpio.h"
#include "ClassLogFile.h"
#include "../../include/defines.h"
static const char* TAG = "STATUSLED";
TaskHandle_t xHandle_task_StatusLED = NULL;
struct StatusLEDData StatusLEDData = {};
void task_StatusLED(void *pvParameter)
{
//ESP_LOGD(TAG, "task_StatusLED - create");
while (StatusLEDData.bProcessingRequest)
{
//ESP_LOGD(TAG, "task_StatusLED - start");
struct StatusLEDData StatusLEDDataInt = StatusLEDData;
gpio_pad_select_gpio(BLINK_GPIO); // Init the GPIO
gpio_set_direction(BLINK_GPIO, GPIO_MODE_OUTPUT); // Set the GPIO as a push/pull output
gpio_set_level(BLINK_GPIO, 1);// LED off
for (int i=0; i<2; ) // Default: repeat 2 times
{
if (!StatusLEDDataInt.bInfinite)
++i;
for (int j = 0; j < StatusLEDDataInt.iSourceBlinkCnt; ++j)
{
gpio_set_level(BLINK_GPIO, 0);
vTaskDelay(StatusLEDDataInt.iBlinkTime / portTICK_PERIOD_MS);
gpio_set_level(BLINK_GPIO, 1);
vTaskDelay(StatusLEDDataInt.iBlinkTime / portTICK_PERIOD_MS);
}
vTaskDelay(500 / portTICK_PERIOD_MS); // Delay between module code and error code
for (int j = 0; j < StatusLEDDataInt.iCodeBlinkCnt; ++j)
{
gpio_set_level(BLINK_GPIO, 0);
vTaskDelay(StatusLEDDataInt.iBlinkTime / portTICK_PERIOD_MS);
gpio_set_level(BLINK_GPIO, 1);
vTaskDelay(StatusLEDDataInt.iBlinkTime / portTICK_PERIOD_MS);
}
vTaskDelay(1500 / portTICK_PERIOD_MS); // Delay to signal new round
}
StatusLEDData.bProcessingRequest = false;
//ESP_LOGD(TAG, "task_StatusLED - done/wait");
vTaskDelay(10000 / portTICK_PERIOD_MS); // Wait for an upcoming request otherwise continue and delete task to save memory
}
//ESP_LOGD(TAG, "task_StatusLED - delete");
xHandle_task_StatusLED = NULL;
vTaskDelete(NULL); // Delete this task due to no request
}
void StatusLED(StatusLedSource _eSource, int _iCode, bool _bInfinite)
{
//ESP_LOGD(TAG, "StatusLED - start");
if (_eSource == WLAN_CONN) {
StatusLEDData.iSourceBlinkCnt = WLAN_CONN;
StatusLEDData.iCodeBlinkCnt = _iCode;
StatusLEDData.iBlinkTime = 250;
StatusLEDData.bInfinite = _bInfinite;
}
else if (_eSource == WLAN_INIT) {
StatusLEDData.iSourceBlinkCnt = WLAN_INIT;
StatusLEDData.iCodeBlinkCnt = _iCode;
StatusLEDData.iBlinkTime = 250;
StatusLEDData.bInfinite = _bInfinite;
}
else if (_eSource == SDCARD_INIT) {
StatusLEDData.iSourceBlinkCnt = SDCARD_INIT;
StatusLEDData.iCodeBlinkCnt = _iCode;
StatusLEDData.iBlinkTime = 250;
StatusLEDData.bInfinite = _bInfinite;
}
else if (_eSource == SDCARD_CHECK) {
StatusLEDData.iSourceBlinkCnt = SDCARD_CHECK;
StatusLEDData.iCodeBlinkCnt = _iCode;
StatusLEDData.iBlinkTime = 250;
StatusLEDData.bInfinite = _bInfinite;
}
else if (_eSource == CAM_INIT) {
StatusLEDData.iSourceBlinkCnt = CAM_INIT;
StatusLEDData.iCodeBlinkCnt = _iCode;
StatusLEDData.iBlinkTime = 250;
StatusLEDData.bInfinite = _bInfinite;
}
else if (_eSource == PSRAM_INIT) {
StatusLEDData.iSourceBlinkCnt = PSRAM_INIT;
StatusLEDData.iCodeBlinkCnt = _iCode;
StatusLEDData.iBlinkTime = 250;
StatusLEDData.bInfinite = _bInfinite;
}
else if (_eSource == TIME_CHECK) {
StatusLEDData.iSourceBlinkCnt = TIME_CHECK;
StatusLEDData.iCodeBlinkCnt = _iCode;
StatusLEDData.iBlinkTime = 250;
StatusLEDData.bInfinite = _bInfinite;
}
else if (_eSource == AP_OR_OTA) {
StatusLEDData.iSourceBlinkCnt = AP_OR_OTA;
StatusLEDData.iCodeBlinkCnt = _iCode;
StatusLEDData.iBlinkTime = 350;
StatusLEDData.bInfinite = _bInfinite;
}
if (xHandle_task_StatusLED && !StatusLEDData.bProcessingRequest) {
StatusLEDData.bProcessingRequest = true;
BaseType_t xReturned = xTaskAbortDelay(xHandle_task_StatusLED); // Reuse still running status LED task
/*if (xReturned == pdPASS)
ESP_LOGD(TAG, "task_StatusLED - abort waiting delay");*/
}
else if (xHandle_task_StatusLED == NULL) {
StatusLEDData.bProcessingRequest = true;
BaseType_t xReturned = xTaskCreate(&task_StatusLED, "task_StatusLED", 1280, NULL, tskIDLE_PRIORITY+1, &xHandle_task_StatusLED);
if(xReturned != pdPASS)
{
xHandle_task_StatusLED = NULL;
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "task_StatusLED failed to create");
LogFile.WriteHeapInfo("task_StatusLED failed");
}
}
else {
ESP_LOGD(TAG, "task_StatusLED still processing, request skipped"); // Requests with high frequency could be skipped, but LED is only helpful for static states
}
//ESP_LOGD(TAG, "StatusLED - done");
}
void StatusLEDOff(void)
{
if (xHandle_task_StatusLED)
vTaskDelete(xHandle_task_StatusLED); // Delete task for StatusLED to force stop of blinking
gpio_pad_select_gpio(BLINK_GPIO); // Init the GPIO
gpio_set_direction(BLINK_GPIO, GPIO_MODE_OUTPUT); // Set the GPIO as a push/pull output
gpio_set_level(BLINK_GPIO, 1);// LED off
}

View File

@@ -0,0 +1,34 @@
#pragma once
#ifndef STATUSLED_H
#define STATUSLED_H
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
extern TaskHandle_t xHandle_task_StatusLED;
enum StatusLedSource {
WLAN_CONN = 1,
WLAN_INIT = 2,
SDCARD_INIT = 3,
SDCARD_CHECK = 4,
CAM_INIT = 5,
PSRAM_INIT = 6,
TIME_CHECK = 7,
AP_OR_OTA = 8
};
struct StatusLEDData {
int iSourceBlinkCnt = 1;
int iCodeBlinkCnt = 1;
int iBlinkTime = 250;
bool bInfinite = false;
bool bProcessingRequest = false;
};
void StatusLED(StatusLedSource _eSource, int _iCode, bool _bInfinite);
void StatusLEDOff(void);
#endif //STATUSLED_H

View File

@@ -5,12 +5,14 @@
#include <math.h>
#include <algorithm>
#include <esp_log.h>
#include "psram.h"
#include "../../include/defines.h"
static const char* TAG = "c_align_and_cut_image";
CAlignAndCutImage::CAlignAndCutImage(CImageBasis *_org, CImageBasis *_temp)
CAlignAndCutImage::CAlignAndCutImage(std::string _name, CImageBasis *_org, CImageBasis *_temp) : CImageBasis(_name)
{
name = _name;
rgb_image = _org->rgb_image;
channels = _org->channels;
width = _org->width;
@@ -37,7 +39,7 @@ bool CAlignAndCutImage::Align(RefInfo *_temp1, RefInfo *_temp2)
int r0_x, r0_y, r1_x, r1_y;
bool isSimilar1, isSimilar2;
CFindTemplate* ft = new CFindTemplate(rgb_image, channels, width, height, bpp);
CFindTemplate* ft = new CFindTemplate("align", rgb_image, channels, width, height, bpp);
r0_x = _temp1->target_x;
r0_y = _temp1->target_y;
@@ -81,7 +83,7 @@ bool CAlignAndCutImage::Align(RefInfo *_temp1, RefInfo *_temp2)
LogFile.WriteToDedicatedFile("/sdcard/alignment.txt", zw);
#endif*/
CRotateImage rt(this, ImageTMP);
CRotateImage rt("Align", this, ImageTMP);
rt.Translate(dx, dy);
rt.Rotate(d_winkel, _temp1->target_x, _temp1->target_y);
ESP_LOGD(TAG, "Alignment: dx %d - dy %d - rot %f", dx, dy, d_winkel);
@@ -107,7 +109,7 @@ void CAlignAndCutImage::CutAndSave(std::string _template1, int x1, int y1, int d
dy = y2 - y1;
int memsize = dx * dy * channels;
uint8_t* odata = (unsigned char*) GET_MEMORY(memsize);
uint8_t* odata = (unsigned char*) malloc_psram_heap(std::string(TAG) + "->odata", memsize, MALLOC_CAP_SPIRAM);
stbi_uc* p_target;
stbi_uc* p_source;
@@ -186,7 +188,7 @@ CImageBasis* CAlignAndCutImage::CutAndSave(int x1, int y1, int dx, int dy)
dy = y2 - y1;
int memsize = dx * dy * channels;
uint8_t* odata = (unsigned char*)GET_MEMORY(memsize);
uint8_t* odata = (unsigned char*)malloc_psram_heap(std::string(TAG) + "->odata", memsize, MALLOC_CAP_SPIRAM);
stbi_uc* p_target;
stbi_uc* p_source;
@@ -202,7 +204,7 @@ CImageBasis* CAlignAndCutImage::CutAndSave(int x1, int y1, int dx, int dy)
p_target[_channels] = p_source[_channels];
}
CImageBasis* rs = new CImageBasis(odata, channels, dx, dy, bpp);
CImageBasis* rs = new CImageBasis("CutAndSave", odata, channels, dx, dy, bpp);
RGBImageRelease();
rs->SetIndepended();
return rs;

View File

@@ -12,9 +12,9 @@ class CAlignAndCutImage : public CImageBasis
public:
int t0_dx, t0_dy, t1_dx, t1_dy;
CImageBasis *ImageTMP;
CAlignAndCutImage(std::string _image) : CImageBasis(_image) {ImageTMP = NULL;};
CAlignAndCutImage(uint8_t* _rgb_image, int _channels, int _width, int _height, int _bpp) : CImageBasis(_rgb_image, _channels, _width, _height, _bpp) {ImageTMP = NULL;};
CAlignAndCutImage(CImageBasis *_org, CImageBasis *_temp);
CAlignAndCutImage(std::string name, std::string _image) : CImageBasis(name, _image) {ImageTMP = NULL;};
CAlignAndCutImage(std::string name, uint8_t* _rgb_image, int _channels, int _width, int _height, int _bpp) : CImageBasis(name, _rgb_image, _channels, _width, _height, _bpp) {ImageTMP = NULL;};
CAlignAndCutImage(std::string name, CImageBasis *_org, CImageBasis *_temp);
bool Align(RefInfo *_temp1, RefInfo *_temp2);
// void Align(std::string _template1, int x1, int y1, std::string _template2, int x2, int y2, int deltax = 40, int deltay = 40, std::string imageROI = "");

View File

@@ -32,7 +32,7 @@ class CFindTemplate : public CImageBasis
{
public:
int tpl_width, tpl_height, tpl_bpp;
CFindTemplate(uint8_t* _rgb_image, int _channels, int _width, int _height, int _bpp) : CImageBasis(_rgb_image, _channels, _width, _height, _bpp) {};
CFindTemplate(std::string name, uint8_t* _rgb_image, int _channels, int _width, int _height, int _bpp) : CImageBasis(name, _rgb_image, _channels, _width, _height, _bpp) {};
bool FindTemplate(RefInfo *_ref);

View File

@@ -1,5 +1,6 @@
#include "CImageBasis.h"
#include "Helper.h"
#include "psram.h"
#include "ClassLogFile.h"
#include "server_ota.h"
@@ -360,8 +361,9 @@ void CImageBasis::drawCircle(int x1, int y1, int rad, int r, int g, int b, int t
}
CImageBasis::CImageBasis()
CImageBasis::CImageBasis(string _name)
{
name = _name;
externalImage = false;
rgb_image = NULL;
width = 0;
@@ -384,8 +386,9 @@ void CImageBasis::CreateEmptyImage(int _width, int _height, int _channels)
LogFile.WriteHeapInfo("CreateEmptyImage");
#endif
int memsize = width * height * channels;
rgb_image = (unsigned char*)GET_MEMORY(memsize);
memsize = width * height * channels;
rgb_image = (unsigned char*)malloc_psram_heap(std::string(TAG) + "->CImageBasis (" + name + ")", memsize, MALLOC_CAP_SPIRAM);
if (rgb_image == NULL)
{
@@ -435,8 +438,10 @@ void CImageBasis::LoadFromMemory(stbi_uc *_buffer, int len)
{
RGBImageLock();
if (rgb_image)
if (rgb_image != NULL) {
stbi_image_free(rgb_image);
//free_psram_heap(std::string(TAG) + "->rgb_image (LoadFromMemory)", rgb_image);
}
rgb_image = stbi_load_from_memory(_buffer, len, &width, &height, &channels, 3);
bpp = channels;
@@ -454,8 +459,9 @@ void CImageBasis::LoadFromMemory(stbi_uc *_buffer, int len)
}
CImageBasis::CImageBasis(CImageBasis *_copyfrom)
CImageBasis::CImageBasis(string _name, CImageBasis *_copyfrom)
{
name = _name;
islocked = false;
externalImage = false;
channels = _copyfrom->channels;
@@ -469,8 +475,9 @@ CImageBasis::CImageBasis(CImageBasis *_copyfrom)
LogFile.WriteHeapInfo("CImageBasis_copyfrom - Start");
#endif
int memsize = width * height * channels;
rgb_image = (unsigned char*)GET_MEMORY(memsize);
memsize = width * height * channels;
rgb_image = (unsigned char*)malloc_psram_heap(std::string(TAG) + "->CImageBasis (" + name + ")", memsize, MALLOC_CAP_SPIRAM);
if (rgb_image == NULL)
{
@@ -489,8 +496,9 @@ CImageBasis::CImageBasis(CImageBasis *_copyfrom)
}
CImageBasis::CImageBasis(int _width, int _height, int _channels)
CImageBasis::CImageBasis(string _name, int _width, int _height, int _channels)
{
name = _name;
islocked = false;
externalImage = false;
channels = _channels;
@@ -504,8 +512,9 @@ CImageBasis::CImageBasis(int _width, int _height, int _channels)
LogFile.WriteHeapInfo("CImageBasis_width,height,ch - Start");
#endif
int memsize = width * height * channels;
rgb_image = (unsigned char*)GET_MEMORY(memsize);
memsize = width * height * channels;
rgb_image = (unsigned char*)malloc_psram_heap(std::string(TAG) + "->CImageBasis (" + name + ")", memsize, MALLOC_CAP_SPIRAM);
if (rgb_image == NULL)
{
@@ -523,8 +532,9 @@ CImageBasis::CImageBasis(int _width, int _height, int _channels)
}
CImageBasis::CImageBasis(std::string _image)
CImageBasis::CImageBasis(string _name, std::string _image)
{
name = _name;
islocked = false;
channels = 3;
externalImage = false;
@@ -569,8 +579,9 @@ bool CImageBasis::ImageOkay(){
}
CImageBasis::CImageBasis(uint8_t* _rgb_image, int _channels, int _width, int _height, int _bpp)
CImageBasis::CImageBasis(string _name, uint8_t* _rgb_image, int _channels, int _width, int _height, int _bpp)
{
name = _name;
islocked = false;
rgb_image = _rgb_image;
channels = _channels;
@@ -607,8 +618,10 @@ CImageBasis::~CImageBasis()
{
RGBImageLock();
if (!externalImage)
stbi_image_free(rgb_image);
if (!externalImage) {
//stbi_image_free(rgb_image);
free_psram_heap(std::string(TAG) + "->CImageBasis (" + name + ", " + to_string(memsize) + ")", rgb_image);
}
RGBImageRelease();
}
@@ -637,19 +650,19 @@ void CImageBasis::SaveToFile(std::string _imageout)
void CImageBasis::Resize(int _new_dx, int _new_dy)
{
int memsize = _new_dx * _new_dy * channels;
uint8_t* odata = (unsigned char*)GET_MEMORY(memsize);
memsize = _new_dx * _new_dy * channels;
uint8_t* odata = (unsigned char*)malloc_psram_heap(std::string(TAG) + "->odata", memsize, MALLOC_CAP_SPIRAM);
RGBImageLock();
stbir_resize_uint8(rgb_image, width, height, 0, odata, _new_dx, _new_dy, 0, channels);
stbi_image_free(rgb_image);
rgb_image = (unsigned char*)GET_MEMORY(memsize);
rgb_image = (unsigned char*)malloc_psram_heap(std::string(TAG) + "->CImageBasis Resize (" + name + ")", memsize, MALLOC_CAP_SPIRAM);
memCopy(odata, rgb_image, memsize);
width = _new_dx;
height = _new_dy;
stbi_image_free(odata);
free_psram_heap(std::string(TAG) + "->odata", odata);
RGBImageRelease();
}

View File

@@ -30,6 +30,8 @@ class CImageBasis
protected:
bool externalImage;
std::string filename;
std::string name; // Just used for diagnostics
int memsize = 0;
void memCopy(uint8_t* _source, uint8_t* _target, int _size);
bool isInImage(int x, int y);
@@ -37,7 +39,7 @@ class CImageBasis
bool islocked;
public:
uint8_t* rgb_image;
uint8_t* rgb_image = NULL;
int channels;
int width, height, bpp;
@@ -64,11 +66,11 @@ class CImageBasis
void EmptyImage();
CImageBasis();
CImageBasis(std::string _image);
CImageBasis(uint8_t* _rgb_image, int _channels, int _width, int _height, int _bpp);
CImageBasis(int _width, int _height, int _channels);
CImageBasis(CImageBasis *_copyfrom);
CImageBasis(std::string name);
CImageBasis(std::string name, std::string _image);
CImageBasis(std::string name, uint8_t* _rgb_image, int _channels, int _width, int _height, int _bpp);
CImageBasis(std::string name, int _width, int _height, int _channels);
CImageBasis(std::string name, CImageBasis *_copyfrom);
void Resize(int _new_dx, int _new_dy);
void Resize(int _new_dx, int _new_dy, CImageBasis *_target);

View File

@@ -1,7 +1,10 @@
#include <string>
#include "CRotateImage.h"
#include "psram.h"
static const char *TAG = "C ROTATE IMG";
CRotateImage::CRotateImage(CImageBasis *_org, CImageBasis *_temp, bool _flip)
CRotateImage::CRotateImage(std::string _name, CImageBasis *_org, CImageBasis *_temp, bool _flip) : CImageBasis(_name)
{
rgb_image = _org->rgb_image;
channels = _org->channels;
@@ -15,6 +18,7 @@ CRotateImage::CRotateImage(CImageBasis *_org, CImageBasis *_temp, bool _flip)
doflip = _flip;
}
void CRotateImage::Mirror(){
int memsize = width * height * channels;
uint8_t* odata;
@@ -24,7 +28,7 @@ void CRotateImage::Mirror(){
}
else
{
odata = (unsigned char*)GET_MEMORY(memsize);
odata = (unsigned char*)malloc_psram_heap(std::string(TAG) + "->odata", memsize, MALLOC_CAP_SPIRAM);
}
@@ -50,7 +54,7 @@ void CRotateImage::Mirror(){
// memcpy(rgb_image, odata, memsize);
memCopy(odata, rgb_image, memsize);
if (!ImageTMP)
stbi_image_free(odata);
free_psram_heap(std::string(TAG) + "->odata", odata);
if (ImageTMP)
ImageTMP->RGBImageRelease();
@@ -109,7 +113,7 @@ void CRotateImage::Rotate(float _angle, int _centerx, int _centery)
}
else
{
odata = (unsigned char*)GET_MEMORY(memsize);
odata = (unsigned char*)malloc_psram_heap(std::string(TAG) + "->odata", memsize, MALLOC_CAP_SPIRAM);
}
@@ -148,7 +152,7 @@ void CRotateImage::Rotate(float _angle, int _centerx, int _centery)
if (!ImageTMP)
{
stbi_image_free(odata);
free_psram_heap(std::string(TAG) + "->odata", odata);
}
if (ImageTMP)
ImageTMP->RGBImageRelease();
@@ -209,7 +213,7 @@ void CRotateImage::RotateAntiAliasing(float _angle, int _centerx, int _centery)
}
else
{
odata = (unsigned char*)GET_MEMORY(memsize);
odata = (unsigned char*)malloc_psram_heap(std::string(TAG) + "->odata", memsize, MALLOC_CAP_SPIRAM);
}
@@ -269,7 +273,7 @@ void CRotateImage::RotateAntiAliasing(float _angle, int _centerx, int _centery)
if (!ImageTMP)
{
stbi_image_free(odata);
free_psram_heap(std::string(TAG) + "->odata", odata);
}
if (ImageTMP)
ImageTMP->RGBImageRelease();
@@ -300,7 +304,7 @@ void CRotateImage::Translate(int _dx, int _dy)
}
else
{
odata = (unsigned char*)GET_MEMORY(memsize);
odata = (unsigned char*)malloc_psram_heap(std::string(TAG) + "->odata", memsize, MALLOC_CAP_SPIRAM);
}
@@ -336,7 +340,7 @@ void CRotateImage::Translate(int _dx, int _dy)
memCopy(odata, rgb_image, memsize);
if (!ImageTMP)
{
stbi_image_free(odata);
free_psram_heap(std::string(TAG) + "->odata", odata);
}
if (ImageTMP)

View File

@@ -8,12 +8,13 @@
class CRotateImage: public CImageBasis
{
public:
CImageBasis *ImageTMP, *ImageOrg;
bool doflip;
CRotateImage(std::string _image, bool _flip = false) : CImageBasis(_image) {ImageTMP = NULL; ImageOrg = NULL; doflip = _flip;};
CRotateImage(uint8_t* _rgb_image, int _channels, int _width, int _height, int _bpp, bool _flip = false) : CImageBasis(_rgb_image, _channels, _width, _height, _bpp) {ImageTMP = NULL; ImageOrg = NULL; doflip = _flip;};
CRotateImage(CImageBasis *_org, CImageBasis *_temp, bool _flip = false);
CRotateImage(std::string name, std::string _image, bool _flip = false) : CImageBasis(name, _image) {ImageTMP = NULL; ImageOrg = NULL; doflip = _flip;};
CRotateImage(std::string name, uint8_t* _rgb_image, int _channels, int _width, int _height, int _bpp, bool _flip = false) : CImageBasis(name, _rgb_image, _channels, _width, _height, _bpp) {ImageTMP = NULL; ImageOrg = NULL; doflip = _flip;};
CRotateImage(std::string name, CImageBasis *_org, CImageBasis *_temp, bool _flip = false);
void Rotate(float _angle);
void RotateAntiAliasing(float _angle);

View File

@@ -0,0 +1,214 @@
#ifdef ENABLE_INFLUXDB
#include "interface_influxdb.h"
#include "esp_log.h"
#include <time.h>
#include "ClassLogFile.h"
#include "esp_http_client.h"
#include "../../include/defines.h"
static const char *TAG = "INFLUXDB";
std::string _influxDBURI;
std::string _influxDBDatabase;
std::string _influxDBMeasurement;
std::string _influxDBUser;
std::string _influxDBPassword;
static esp_err_t http_event_handler(esp_http_client_event_t *evt)
{
switch(evt->event_id)
{
case HTTP_EVENT_ERROR:
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "HTTP Client Error encountered");
break;
case HTTP_EVENT_ON_CONNECTED:
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "HTTP Client Error encountered");
ESP_LOGI(TAG, "HTTP Client Connected");
break;
case HTTP_EVENT_HEADERS_SENT:
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "HTTP Client sent all request headers");
break;
case HTTP_EVENT_ON_HEADER:
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Header: key=" + std::string(evt->header_key) + ", value=" + std::string(evt->header_value));
break;
case HTTP_EVENT_ON_DATA:
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "HTTP Client data recevied: len=" + std::to_string(evt->data_len));
break;
case HTTP_EVENT_ON_FINISH:
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "HTTP Client finished");
break;
case HTTP_EVENT_DISCONNECTED:
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "HTTP Client Disconnected");
break;
}
return ESP_OK;
}
void InfluxDBPublish(std::string _key, std::string _content, std::string _timestamp) {
char response_buffer[MAX_HTTP_OUTPUT_BUFFER] = {0};
esp_http_client_config_t http_config = {
.user_agent = "ESP32 Meter reader",
.method = HTTP_METHOD_POST,
.event_handler = http_event_handler,
.buffer_size = MAX_HTTP_OUTPUT_BUFFER,
.user_data = response_buffer
};
if (_influxDBUser.length() && _influxDBPassword.length()){
http_config.username = _influxDBUser.c_str();
http_config.password = _influxDBPassword.c_str();
http_config.auth_type = HTTP_AUTH_TYPE_BASIC;
}
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "InfluxDBPublish - Key: " + _key + ", Content: " + _content + ", Timestamp: " + _timestamp);
char nowTimestamp[21];
std::string payload;
if (_timestamp.length() > 0)
{
struct tm tm;
strptime(_timestamp.c_str(), PREVALUE_TIME_FORMAT_OUTPUT, &tm);
time_t t = mktime(&tm); // Time in Localtime (looks like timezone is not used by strptime)
struct tm * ptm;
ptm = gmtime ( &t );
time_t utc = mktime(ptm);
utc = 2*t - utc; // Take care of timezone (looks difficult, but is easy: t = t + (t - utc), weil t-utc = timezone)
sprintf(nowTimestamp,"%ld000000000", (long) utc); // UTC
payload = _influxDBMeasurement + " " + _key + "=" + _content + " " + nowTimestamp;
// payload = _influxDBMeasurement + " " + _key + "=774 " + nowTimestamp;
}
else
{
payload = _influxDBMeasurement + " " + _key + "=" + _content;
}
payload.shrink_to_fit();
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "sending line to influxdb:" + payload);
// use the default retention policy of the database
std::string apiURI = _influxDBURI + "/api/v2/write?bucket=" + _influxDBDatabase + "/";
apiURI.shrink_to_fit();
http_config.url = apiURI.c_str();
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "API URI: " + apiURI);
esp_http_client_handle_t http_client = esp_http_client_init(&http_config);
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "client is initialized");
esp_http_client_set_header(http_client, "Content-Type", "text/plain");
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "header is set");
ESP_ERROR_CHECK(esp_http_client_set_post_field(http_client, payload.c_str(), payload.length()));
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "post payload is set");
esp_err_t err = ESP_ERROR_CHECK_WITHOUT_ABORT(esp_http_client_perform(http_client));
if( err == ESP_OK ) {
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "HTTP request was performed");
int status_code = esp_http_client_get_status_code(http_client);
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "HTTP status code" + std::to_string(status_code));
} else {
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "HTTP request failed");
}
esp_http_client_cleanup(http_client);
}
/*
void InfluxDBPublish(std::string _key, std::string _content, std::string _timestamp) {
char response_buffer[MAX_HTTP_OUTPUT_BUFFER] = {0};
esp_http_client_config_t http_config = {
.user_agent = "ESP32 Meter reader",
.method = HTTP_METHOD_POST,
.event_handler = http_event_handler,
.buffer_size = MAX_HTTP_OUTPUT_BUFFER,
.user_data = response_buffer
};
if (_influxDBUser.length() && _influxDBPassword.length()){
http_config.username = _influxDBUser.c_str();
http_config.password = _influxDBPassword.c_str();
http_config.auth_type = HTTP_AUTH_TYPE_BASIC;
}
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "InfluxDBPublish - Key: " + _key + ", Content: " + _content + ", Timestamp: " + _timestamp);
// Format: #define PREVALUE_TIME_FORMAT_OUTPUT "%Y-%m-%dT%H:%M:%S%z"
struct tm tm;
strptime(_timestamp.c_str(), PREVALUE_TIME_FORMAT_OUTPUT, &tm);
time_t t = mktime(&tm); // t is now your desired time_t
struct tm * ptm;
ptm = gmtime ( &t );
time_t utc = mktime(ptm);
// time_t now;
// time(&now);
char nowTimestamp[21];
// pad with zeroes to get nanoseconds
// sprintf(nowTimestamp,"%ld000000000", (long) now);
// sprintf(nowTimestamp,"%ld000000000", (long) t); // Localtime
sprintf(nowTimestamp,"%ld000000000", (long) utc); // UTC
// LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Test Time Conversion - t: " + std::to_string(t) + ", utc: " + std::to_string(utc));
// LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Test Time Conversion - now: " + std::to_string(now) + ", timestamp: " + std::to_string(t) + "(correct time not used yet)");
std::string payload = _influxDBMeasurement + " " + _key + "=" + _content + " " + nowTimestamp;
payload.shrink_to_fit();
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "sending line to influxdb:" + payload);
// use the default retention policy of the database
std::string apiURI = _influxDBURI + "/api/v2/write?bucket=" + _influxDBDatabase + "/";
apiURI.shrink_to_fit();
http_config.url = apiURI.c_str();
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "API URI: " + apiURI);
esp_http_client_handle_t http_client = esp_http_client_init(&http_config);
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "client is initialized");
esp_http_client_set_header(http_client, "Content-Type", "text/plain");
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "header is set");
ESP_ERROR_CHECK(esp_http_client_set_post_field(http_client, payload.c_str(), payload.length()));
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "post payload is set");
esp_err_t err = ESP_ERROR_CHECK_WITHOUT_ABORT(esp_http_client_perform(http_client));
if( err == ESP_OK ) {
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "HTTP request was performed");
int status_code = esp_http_client_get_status_code(http_client);
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "HTTP status code" + std::to_string(status_code));
} else {
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "HTTP request failed");
}
esp_http_client_cleanup(http_client);
}
*/
void InfluxDBInit(std::string _uri, std::string _database, std::string _measurement, std::string _user, std::string _password){
_influxDBURI = _uri;
_influxDBDatabase = _database;
_influxDBMeasurement = _measurement;
_influxDBUser = _user;
_influxDBPassword = _password;
}
void InfluxDBdestroy() {
}
#endif //ENABLE_INFLUXDB

View File

@@ -0,0 +1,24 @@
#ifdef ENABLE_INFLUXDB
#pragma once
#ifndef INTERFACE_INFLUXDB_H
#define INTERFACE_INFLUXDB_H
#include <string>
#include <map>
#include <functional>
// Interface to InfluxDB v1.x
void InfluxDBInit(std::string _influxDBURI, std::string _database, std::string _measurement, std::string _user, std::string _password);
void InfluxDBPublish(std::string _key, std::string _content, std::string _timestamp);
// Interface to InfluxDB v2.x
void InfluxDB_V2_Init(std::string _uri, std::string _database, std::string _measurement, std::string _org, std::string _token);
void InfluxDB_V2_Publish(std::string _key, std::string _content, std::string _timestamp);
void InfluxDBdestroy();
#endif //INTERFACE_INFLUXDB_H
#endif //ENABLE_INFLUXDB

View File

@@ -16,6 +16,101 @@ std::string _influxDBMeasurement;
std::string _influxDBUser;
std::string _influxDBPassword;
std::string _influxDB_V2_URI;
std::string _influxDB_V2_Database;
std::string _influxDB_V2_Measurement;
std::string _influxDB_V2_Token;
std::string _influxDB_V2_Org;
static esp_err_t http_event_handler(esp_http_client_event_t *evt);
void InfluxDB_V2_Init(std::string _uri, std::string _database, std::string _measurement, std::string _org, std::string _token)
{
_influxDB_V2_URI = _uri;
_influxDB_V2_Database = _database;
_influxDB_V2_Measurement = _measurement;
_influxDB_V2_Org = _org;
_influxDB_V2_Token = _token;
}
void InfluxDB_V2_Publish(std::string _key, std::string _content, std::string _timestamp)
{
char response_buffer[MAX_HTTP_OUTPUT_BUFFER] = {0};
esp_http_client_config_t http_config = {
.user_agent = "ESP32 Meter reader",
.method = HTTP_METHOD_POST,
.event_handler = http_event_handler,
.buffer_size = MAX_HTTP_OUTPUT_BUFFER,
.user_data = response_buffer
};
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "InfluxDB_V2_Publish - Key: " + _key + ", Content: " + _content + ", Timestamp: " + _timestamp);
// Format: #define PREVALUE_TIME_FORMAT_OUTPUT "%Y-%m-%dT%H:%M:%S%z"
char nowTimestamp[21];
std::string payload;
if (_timestamp.length() > 0)
{
struct tm tm;
strptime(_timestamp.c_str(), PREVALUE_TIME_FORMAT_OUTPUT, &tm);
time_t t = mktime(&tm); // Time in Localtime (looks like timezone is not used by strptime)
struct tm * ptm;
ptm = gmtime ( &t );
time_t utc = mktime(ptm);
utc = 2*t - utc; // Take care of timezone (looks difficult, but is easy: t = t + (t - utc), weil t-utc = timezone)
sprintf(nowTimestamp,"%ld000000000", (long) utc); // UTC
payload = _influxDB_V2_Measurement + " " + _key + "=" + _content + " " + nowTimestamp;
// payload = _influxDB_V2_Measurement + " " + _key + "=774 " + nowTimestamp;
}
else
{
payload = _influxDB_V2_Measurement + " " + _key + "=" + _content;
// payload = _influxDB_V2_Measurement + " " + _key + "=774";
}
payload.shrink_to_fit();
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "sending line to influxdb:" + payload);
std::string apiURI = _influxDB_V2_URI + "/api/v2/write?org=" + _influxDB_V2_Org + "&bucket=" + _influxDB_V2_Database;
apiURI.shrink_to_fit();
http_config.url = apiURI.c_str();
ESP_LOGI(TAG, "http_config: %s", http_config.url); // Add mark on log to see when it restarted
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "API URI: " + apiURI);
esp_http_client_handle_t http_client = esp_http_client_init(&http_config);
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "client is initialized");
esp_http_client_set_header(http_client, "Content-Type", "text/plain");
std::string _zw = "Token " + _influxDB_V2_Token;
// LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Tokenheader: %s\n", _zw.c_str());
esp_http_client_set_header(http_client, "Authorization", _zw.c_str());
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "header is set");
ESP_ERROR_CHECK(esp_http_client_set_post_field(http_client, payload.c_str(), payload.length()));
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "post payload is set");
esp_err_t err = ESP_ERROR_CHECK_WITHOUT_ABORT(esp_http_client_perform(http_client));
if( err == ESP_OK ) {
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "HTTP request was performed");
int status_code = esp_http_client_get_status_code(http_client);
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "HTTP status code" + std::to_string(status_code));
} else {
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "HTTP request failed");
}
esp_http_client_cleanup(http_client);
}
static esp_err_t http_event_handler(esp_http_client_event_t *evt)
{
switch(evt->event_id)
@@ -64,35 +159,37 @@ void InfluxDBPublish(std::string _key, std::string _content, std::string _timest
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "InfluxDBPublish - Key: " + _key + ", Content: " + _content + ", Timestamp: " + _timestamp);
// Format: #define PREVALUE_TIME_FORMAT_OUTPUT "%Y-%m-%dT%H:%M:%S%z"
struct tm tm;
strptime(_timestamp.c_str(), PREVALUE_TIME_FORMAT_OUTPUT, &tm);
time_t t = mktime(&tm); // t is now your desired time_t
struct tm * ptm;
ptm = gmtime ( &t );
time_t utc = mktime(ptm);
// time_t now;
// time(&now);
char nowTimestamp[21];
// pad with zeroes to get nanoseconds
// sprintf(nowTimestamp,"%ld000000000", (long) now);
// sprintf(nowTimestamp,"%ld000000000", (long) t); // Localtime
sprintf(nowTimestamp,"%ld000000000", (long) utc); // UTC
std::string payload;
// LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Test Time Conversion - t: " + std::to_string(t) + ", utc: " + std::to_string(utc));
// LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Test Time Conversion - now: " + std::to_string(now) + ", timestamp: " + std::to_string(t) + "(correct time not used yet)");
if (_timestamp.length() > 0)
{
struct tm tm;
strptime(_timestamp.c_str(), PREVALUE_TIME_FORMAT_OUTPUT, &tm);
time_t t = mktime(&tm); // Time in Localtime (looks like timezone is not used by strptime)
struct tm * ptm;
ptm = gmtime ( &t );
time_t utc = mktime(ptm);
utc = 2*t - utc; // Take care of timezone (looks difficult, but is easy: t = t + (t - utc), weil t-utc = timezone)
sprintf(nowTimestamp,"%ld000000000", (long) utc); // UTC
payload = _influxDBMeasurement + " " + _key + "=" + _content + " " + nowTimestamp;
// payload = _influxDBMeasurement + " " + _key + "=774 " + nowTimestamp;
}
else
{
payload = _influxDBMeasurement + " " + _key + "=" + _content;
}
std::string payload = _influxDBMeasurement + " " + _key + "=" + _content + " " + nowTimestamp;
payload.shrink_to_fit();
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "sending line to influxdb:" + payload);
// use the default retention policy of the database
std::string apiURI = _influxDBURI + "/api/v2/write?bucket=" + _influxDBDatabase + "/";
std::string apiURI = _influxDBURI + "/write?db=" + _influxDBDatabase + "/";
apiURI.shrink_to_fit();
http_config.url = apiURI.c_str();

View File

@@ -8,10 +8,17 @@
#include <map>
#include <functional>
// Interface to InfluxDB v1.x
void InfluxDBInit(std::string _influxDBURI, std::string _database, std::string _measurement, std::string _user, std::string _password);
void InfluxDBdestroy();
void InfluxDBPublish(std::string _key, std::string _content, std::string _timestamp);
// Interface to InfluxDB v2.x
void InfluxDB_V2_Init(std::string _uri, std::string _database, std::string _measurement, std::string _org, std::string _token);
void InfluxDB_V2_Publish(std::string _key, std::string _content, std::string _timestamp);
void InfluxDBdestroy();
#endif //INTERFACE_INFLUXDB_H
#endif //ENABLE_INFLUXDB

View File

@@ -15,6 +15,7 @@ extern "C" {
#endif
#include "Helper.h"
#include "time_sntp.h"
#include "../../include/defines.h"
static const char *TAG = "LOGFILE";
@@ -78,11 +79,11 @@ void ClassLogFile::WriteToData(std::string _timestamp, std::string _name, std::s
}
void ClassLogFile::setLogLevel(esp_log_level_t _logLevel){
loglevel = _logLevel;
void ClassLogFile::setLogLevel(esp_log_level_t _logLevel)
{
std::string levelText;
// Print log level to log file
switch(_logLevel) {
case ESP_LOG_WARN:
levelText = "WARNING";
@@ -95,13 +96,16 @@ void ClassLogFile::setLogLevel(esp_log_level_t _logLevel){
case ESP_LOG_DEBUG:
levelText = "DEBUG";
break;
case ESP_LOG_ERROR:
default:
levelText = "ERROR";
break;
}
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Set log level to " + levelText);
ESP_LOGI(TAG, "Log Level set to %s", levelText.c_str());
// set new log level
loglevel = _logLevel;
/*
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Test");
@@ -318,15 +322,23 @@ void ClassLogFile::RemoveOldLogFile()
//ESP_LOGD(TAG, "compare log file: %s to %s", entry->d_name, cmpfilename);
if ((strlen(entry->d_name) == strlen(cmpfilename)) && (strcmp(entry->d_name, cmpfilename) < 0)) {
//ESP_LOGD(TAG, "delete log file: %s", entry->d_name);
std::string filepath = logroot + "/" + entry->d_name;
if (unlink(filepath.c_str()) == 0) {
deleted ++;
} else {
ESP_LOGE(TAG, "can't delete file: %s", entry->d_name);
notDeleted ++;
std::string filepath = logroot + "/" + entry->d_name;
if ((strcmp(entry->d_name, "log_1970-01-01.txt") == 0) && getTimeWasNotSetAtBoot()) { // keep logfile log_1970-01-01.txt if time was not set at boot (some boot logs are in there)
//ESP_LOGD(TAG, "Skip deleting this file: %s", entry->d_name);
notDeleted++;
}
} else {
notDeleted ++;
else {
if (unlink(filepath.c_str()) == 0) {
deleted++;
}
else {
ESP_LOGE(TAG, "can't delete file: %s", entry->d_name);
notDeleted++;
}
}
}
else {
notDeleted++;
}
}
}
@@ -386,14 +398,17 @@ void ClassLogFile::RemoveOldDataLog()
}
void ClassLogFile::CreateLogDirectories()
bool ClassLogFile::CreateLogDirectories()
{
MakeDir("/sdcard/log");
MakeDir("/sdcard/log/data");
MakeDir("/sdcard/log/analog");
MakeDir("/sdcard/log/digit");
MakeDir("/sdcard/log/message");
MakeDir("/sdcard/log/source");
bool bRetval = false;
bRetval = MakeDir("/sdcard/log");
bRetval = MakeDir("/sdcard/log/data");
bRetval = MakeDir("/sdcard/log/analog");
bRetval = MakeDir("/sdcard/log/digit");
bRetval = MakeDir("/sdcard/log/message");
bRetval = MakeDir("/sdcard/log/source");
return bRetval;
}

View File

@@ -35,7 +35,7 @@ public:
void CloseLogFileAppendHandle();
void CreateLogDirectories();
bool CreateLogDirectories();
void RemoveOldLogFile();
void RemoveOldDataLog();

View File

@@ -14,6 +14,7 @@ std::map<std::string, std::function<void()>>* connectFunktionMap = NULL;
std::map<std::string, std::function<bool(std::string, char*, int)>>* subscribeFunktionMap = NULL;
int failedOnRound = -1;
int MQTTReconnectCnt = 0;
esp_mqtt_event_id_t esp_mqtt_ID = MQTT_EVENT_ANY;
// ESP_EVENT_ANY_ID
@@ -25,11 +26,12 @@ bool mqtt_connected = false;
esp_mqtt_client_handle_t client = NULL;
std::string uri, client_id, lwt_topic, lwt_connected, lwt_disconnected, user, password, maintopic;
int keepalive, SetRetainFlag;
void (*callbackOnConnected)(std::string, int) = NULL;
int keepalive;
bool SetRetainFlag;
void (*callbackOnConnected)(std::string, bool) = NULL;
bool MQTTPublish(std::string _key, std::string _content, int retained_flag)
bool MQTTPublish(std::string _key, std::string _content, int qos, bool retained_flag)
{
if (!mqtt_enabled) { // MQTT sevice not started / configured (MQTT_Init not called before)
return false;
@@ -49,7 +51,7 @@ bool MQTTPublish(std::string _key, std::string _content, int retained_flag)
#ifdef DEBUG_DETAIL_ON
long long int starttime = esp_timer_get_time();
#endif
int msg_id = esp_mqtt_client_publish(client, _key.c_str(), _content.c_str(), 0, 1, retained_flag);
int msg_id = esp_mqtt_client_publish(client, _key.c_str(), _content.c_str(), 0, qos, retained_flag);
#ifdef DEBUG_DETAIL_ON
ESP_LOGD(TAG, "Publish msg_id %d in %lld ms", msg_id, (esp_timer_get_time() - starttime)/1000);
#endif
@@ -58,7 +60,7 @@ bool MQTTPublish(std::string _key, std::string _content, int retained_flag)
#ifdef DEBUG_DETAIL_ON
starttime = esp_timer_get_time();
#endif
msg_id = esp_mqtt_client_publish(client, _key.c_str(), _content.c_str(), 0, 1, retained_flag);
msg_id = esp_mqtt_client_publish(client, _key.c_str(), _content.c_str(), 0, qos, retained_flag);
#ifdef DEBUG_DETAIL_ON
ESP_LOGD(TAG, "Publish msg_id %d in %lld ms", msg_id, (esp_timer_get_time() - starttime)/1000);
#endif
@@ -88,29 +90,39 @@ static esp_err_t mqtt_event_handler_cb(esp_mqtt_event_handle_t event) {
std::string topic = "";
switch (event->event_id) {
case MQTT_EVENT_BEFORE_CONNECT:
ESP_LOGD(TAG, "MQTT_EVENT_BEFORE_CONNECT");
mqtt_initialized = true;
break;
case MQTT_EVENT_CONNECTED:
ESP_LOGD(TAG, "MQTT_EVENT_CONNECTED");
MQTTReconnectCnt = 0;
mqtt_initialized = true;
mqtt_connected = true;
MQTTconnected();
break;
case MQTT_EVENT_DISCONNECTED:
ESP_LOGD(TAG, "MQTT_EVENT_DISCONNECTED");
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Disconnected from broker");
mqtt_connected = false;
MQTTReconnectCnt++;
LogFile.WriteToFile(ESP_LOG_WARN, TAG, "Disconnected, trying to reconnect");
if (MQTTReconnectCnt >= 5) {
MQTTReconnectCnt = 0;
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Disconnected, multiple reconnect attempts failed, still retrying...");
}
break;
case MQTT_EVENT_SUBSCRIBED:
ESP_LOGD(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id);
break;
case MQTT_EVENT_UNSUBSCRIBED:
ESP_LOGD(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id);
break;
case MQTT_EVENT_PUBLISHED:
ESP_LOGD(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id);
break;
case MQTT_EVENT_DATA:
ESP_LOGD(TAG, "MQTT_EVENT_DATA");
ESP_LOGD(TAG, "TOPIC=%.*s", event->topic_len, event->topic);
@@ -125,7 +137,33 @@ static esp_err_t mqtt_event_handler_cb(esp_mqtt_event_handle_t event) {
ESP_LOGW(TAG, "no handler available\r\n");
}
break;
case MQTT_EVENT_ERROR:
// http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718033 --> chapter 3.2.2.3
// The server does not support the level of the MQTT protocol requested by the client
// NOTE: Only protocol 3.1.1 is supported (refer to setting in sdkconfig)
if (event->error_handle->connect_return_code == MQTT_CONNECTION_REFUSE_PROTOCOL) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Connection refused, unacceptable protocol version (0x01)");
}
// The client identifier is correct UTF-8 but not allowed by the server
// e.g. clientID empty (cannot be the case -> default set in firmware)
else if (event->error_handle->connect_return_code == MQTT_CONNECTION_REFUSE_ID_REJECTED) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Connection refused, identifier rejected (0x02)");
}
// The network connection has been made but the MQTT service is unavailable
else if (event->error_handle->connect_return_code == MQTT_CONNECTION_REFUSE_SERVER_UNAVAILABLE) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Connection refused, Server unavailable (0x03)");
}
// The data in the user name or password is malformed
else if (event->error_handle->connect_return_code == MQTT_CONNECTION_REFUSE_BAD_USERNAME) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Connection refused, malformed data in username or password (0x04)");
}
// The client is not authorized to connect
else if (event->error_handle->connect_return_code == MQTT_CONNECTION_REFUSE_NOT_AUTHORIZED) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Connection refused, not authorized. Check username/password (0x05)");
}
#ifdef DEBUG_DETAIL_ON
ESP_LOGD(TAG, "MQTT_EVENT_ERROR - esp_mqtt_error_codes:");
ESP_LOGD(TAG, "error_type:%d", event->error_handle->error_type);
@@ -135,8 +173,9 @@ static esp_err_t mqtt_event_handler_cb(esp_mqtt_event_handle_t event) {
ESP_LOGD(TAG, "esp_tls_stack_err:%d", event->error_handle->esp_tls_stack_err);
ESP_LOGD(TAG, "esp_tls_cert_verify_flags:%d", event->error_handle->esp_tls_cert_verify_flags);
#endif
mqtt_connected = false;
break;
default:
ESP_LOGD(TAG, "Other event id:%d", event->event_id);
break;
@@ -153,7 +192,7 @@ static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_
bool MQTT_Configure(std::string _mqttURI, std::string _clientid, std::string _user, std::string _password,
std::string _maintopic, std::string _lwt, std::string _lwt_connected, std::string _lwt_disconnected,
int _keepalive, int _SetRetainFlag, void *_callbackOnConnected) {
int _keepalive, bool _SetRetainFlag, void *_callbackOnConnected) {
if ((_mqttURI.length() == 0) || (_maintopic.length() == 0) || (_clientid.length() == 0))
{
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Init aborted! Config error (URI, MainTopic or ClientID missing)");
@@ -168,7 +207,7 @@ bool MQTT_Configure(std::string _mqttURI, std::string _clientid, std::string _us
keepalive = _keepalive;
SetRetainFlag = _SetRetainFlag;
maintopic = _maintopic;
callbackOnConnected = ( void (*)(std::string, int) )(_callbackOnConnected);
callbackOnConnected = ( void (*)(std::string, bool) )(_callbackOnConnected);
if (_user.length() && _password.length()){
user = _user;
@@ -220,8 +259,7 @@ int MQTT_Init() {
.buffer_size = 1536, // size of MQTT send/receive buffer (Default: 1024)
.reconnect_timeout_ms = 15000, // Try to reconnect to broker (Default: 10000ms)
.network_timeout_ms = 20000, // Network Timeout (Default: 10000ms)
.message_retransmit_timeout = 3000 // Tiem after message resent when broker not acknowledged (QoS1, QoS2)
.message_retransmit_timeout = 3000 // Time after message resent when broker not acknowledged (QoS1, QoS2)
};
if (user.length() && password.length()){
@@ -312,8 +350,7 @@ bool mqtt_handler_flow_start(std::string _topic, char* _data, int _data_len) {
void MQTTconnected(){
if (mqtt_connected) {
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Connected to broker");
MQTTPublish(lwt_topic, lwt_connected, true); // Publish "connected" to maintopic/connection
if (connectFunktionMap != NULL) {
for(std::map<std::string, std::function<void()>>::iterator it = connectFunktionMap->begin(); it != connectFunktionMap->end(); ++it) {
it->second();
@@ -332,7 +369,7 @@ void MQTTconnected(){
}
}
vTaskDelay(10000 / portTICK_PERIOD_MS); // Delay execution of callback routine after connection got established
/* Send Static Topics and Homeassistant Discovery */
if (callbackOnConnected) { // Call onConnected callback routine --> mqtt_server
callbackOnConnected(maintopic, SetRetainFlag);
}

View File

@@ -11,11 +11,11 @@
bool MQTT_Configure(std::string _mqttURI, std::string _clientid, std::string _user, std::string _password,
std::string _maintopic, std::string _lwt, std::string _lwt_connected, std::string _lwt_disconnected,
int _keepalive, int SetRetainFlag, void *callbackOnConnected);
int _keepalive, bool SetRetainFlag, void *callbackOnConnected);
int MQTT_Init();
void MQTTdestroy_client(bool _disable);
bool MQTTPublish(std::string _key, std::string _content, int retained_flag = 1); // retained Flag as Standart
bool MQTTPublish(std::string _key, std::string _content, int qos, bool retained_flag = 1); // retained Flag as Standart
bool getMQTTisEnabled();
bool getMQTTisConnected();

View File

@@ -0,0 +1,305 @@
/* This is a modification of https://github.com/espressif/esp-mqtt/blob/master/lib/mqtt_outbox.c
* to use the PSRAM instead of the internal heap.
*/
#include "mqtt_outbox.h"
#include <stdlib.h>
#include <string.h>
#include "sys/queue.h"
#include "esp_log.h"
#include "esp_heap_caps.h"
/* Enable this to use the PSRAM for MQTT Publishing.
* This saves 10 kBytes of RAM, see https://github.com/jomjol/AI-on-the-edge-device/pull/2113
* However we can run into PSRAM fragmentation issues, leading to insufficient large blocks to load the model.
* See https://github.com/jomjol/AI-on-the-edge-device/issues/2200 */
#define USE_PSRAM
#ifdef CONFIG_MQTT_CUSTOM_OUTBOX
static const char *TAG = "outbox";
typedef struct outbox_item {
char *buffer;
int len;
int msg_id;
int msg_type;
int msg_qos;
outbox_tick_t tick;
pending_state_t pending;
STAILQ_ENTRY(outbox_item) next;
} outbox_item_t;
STAILQ_HEAD(outbox_list_t, outbox_item);
outbox_handle_t outbox_init(void)
{
#ifdef USE_PSRAM
outbox_handle_t outbox = heap_caps_calloc(1, sizeof(struct outbox_list_t), MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM);
#else
outbox_handle_t outbox = calloc(1, sizeof(struct outbox_list_t));
#endif
//ESP_MEM_CHECK(TAG, outbox, return NULL);
STAILQ_INIT(outbox);
return outbox;
}
outbox_item_handle_t outbox_enqueue(outbox_handle_t outbox, outbox_message_handle_t message, outbox_tick_t tick)
{
#ifdef USE_PSRAM
outbox_item_handle_t item = heap_caps_calloc(1, sizeof(outbox_item_t), MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM);
#else
outbox_item_handle_t item = calloc(1, sizeof(outbox_item_t));
#endif
//ESP_MEM_CHECK(TAG, item, return NULL);
item->msg_id = message->msg_id;
item->msg_type = message->msg_type;
item->msg_qos = message->msg_qos;
item->tick = tick;
item->len = message->len + message->remaining_len;
item->pending = QUEUED;
#ifdef USE_PSRAM
item->buffer = heap_caps_malloc(message->len + message->remaining_len, MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM);
#else
item->buffer = malloc(message->len + message->remaining_len);
#endif
/*ESP_MEM_CHECK(TAG, item->buffer, {
free(item);
return NULL;
});*/
memcpy(item->buffer, message->data, message->len);
if (message->remaining_data) {
memcpy(item->buffer + message->len, message->remaining_data, message->remaining_len);
}
STAILQ_INSERT_TAIL(outbox, item, next);
ESP_LOGD(TAG, "ENQUEUE msgid=%d, msg_type=%d, len=%d, size=%d", message->msg_id, message->msg_type, message->len + message->remaining_len, outbox_get_size(outbox));
return item;
}
outbox_item_handle_t outbox_get(outbox_handle_t outbox, int msg_id)
{
outbox_item_handle_t item;
STAILQ_FOREACH(item, outbox, next) {
if (item->msg_id == msg_id) {
return item;
}
}
return NULL;
}
outbox_item_handle_t outbox_dequeue(outbox_handle_t outbox, pending_state_t pending, outbox_tick_t *tick)
{
outbox_item_handle_t item;
STAILQ_FOREACH(item, outbox, next) {
if (item->pending == pending) {
if (tick) {
*tick = item->tick;
}
return item;
}
}
return NULL;
}
esp_err_t outbox_delete_item(outbox_handle_t outbox, outbox_item_handle_t item_to_delete)
{
outbox_item_handle_t item;
STAILQ_FOREACH(item, outbox, next) {
if (item == item_to_delete) {
STAILQ_REMOVE(outbox, item, outbox_item, next);
#ifdef USE_PSRAM
heap_caps_free(item->buffer);
heap_caps_free(item);
#else
free(item->buffer);
free(item);
#endif
return ESP_OK;
}
}
return ESP_FAIL;
}
uint8_t *outbox_item_get_data(outbox_item_handle_t item, size_t *len, uint16_t *msg_id, int *msg_type, int *qos)
{
if (item) {
*len = item->len;
*msg_id = item->msg_id;
*msg_type = item->msg_type;
*qos = item->msg_qos;
return (uint8_t *)item->buffer;
}
return NULL;
}
esp_err_t outbox_delete(outbox_handle_t outbox, int msg_id, int msg_type)
{
outbox_item_handle_t item, tmp;
STAILQ_FOREACH_SAFE(item, outbox, next, tmp) {
if (item->msg_id == msg_id && (0xFF & (item->msg_type)) == msg_type) {
STAILQ_REMOVE(outbox, item, outbox_item, next);
#ifdef USE_PSRAM
heap_caps_free(item->buffer);
heap_caps_free(item);
#else
free(item->buffer);
free(item);
#endif
ESP_LOGD(TAG, "DELETED msgid=%d, msg_type=%d, remain size=%d", msg_id, msg_type, outbox_get_size(outbox));
return ESP_OK;
}
}
return ESP_FAIL;
}
esp_err_t outbox_delete_msgid(outbox_handle_t outbox, int msg_id)
{
outbox_item_handle_t item, tmp;
STAILQ_FOREACH_SAFE(item, outbox, next, tmp) {
if (item->msg_id == msg_id) {
STAILQ_REMOVE(outbox, item, outbox_item, next);
#ifdef USE_PSRAM
heap_caps_free(item->buffer);
heap_caps_free(item);
#else
free(item->buffer);
free(item);
#endif
}
}
return ESP_OK;
}
esp_err_t outbox_set_pending(outbox_handle_t outbox, int msg_id, pending_state_t pending)
{
outbox_item_handle_t item = outbox_get(outbox, msg_id);
if (item) {
item->pending = pending;
return ESP_OK;
}
return ESP_FAIL;
}
pending_state_t outbox_item_get_pending(outbox_item_handle_t item)
{
if (item) {
return item->pending;
}
return QUEUED;
}
esp_err_t outbox_set_tick(outbox_handle_t outbox, int msg_id, outbox_tick_t tick)
{
outbox_item_handle_t item = outbox_get(outbox, msg_id);
if (item) {
item->tick = tick;
return ESP_OK;
}
return ESP_FAIL;
}
esp_err_t outbox_delete_msgtype(outbox_handle_t outbox, int msg_type)
{
outbox_item_handle_t item, tmp;
STAILQ_FOREACH_SAFE(item, outbox, next, tmp) {
if (item->msg_type == msg_type) {
STAILQ_REMOVE(outbox, item, outbox_item, next);
#ifdef USE_PSRAM
heap_caps_free(item->buffer);
heap_caps_free(item);
#else
free(item->buffer);
free(item);
#endif
}
}
return ESP_OK;
}
int outbox_delete_single_expired(outbox_handle_t outbox, outbox_tick_t current_tick, outbox_tick_t timeout)
{
int msg_id = -1;
outbox_item_handle_t item;
STAILQ_FOREACH(item, outbox, next) {
if (current_tick - item->tick > timeout) {
STAILQ_REMOVE(outbox, item, outbox_item, next);
#ifdef USE_PSRAM
heap_caps_free(item->buffer);
#else
free(item->buffer);
#endif
msg_id = item->msg_id;
#ifdef USE_PSRAM
heap_caps_free(item);
#else
free(item);
#endif
return msg_id;
}
}
return msg_id;
}
int outbox_delete_expired(outbox_handle_t outbox, outbox_tick_t current_tick, outbox_tick_t timeout)
{
int deleted_items = 0;
outbox_item_handle_t item, tmp;
STAILQ_FOREACH_SAFE(item, outbox, next, tmp) {
if (current_tick - item->tick > timeout) {
STAILQ_REMOVE(outbox, item, outbox_item, next);
#ifdef USE_PSRAM
heap_caps_free(item->buffer);
heap_caps_free(item);
#else
free(item->buffer);
free(item);
#endif
deleted_items ++;
}
}
return deleted_items;
}
int outbox_get_size(outbox_handle_t outbox)
{
int siz = 0;
outbox_item_handle_t item;
STAILQ_FOREACH(item, outbox, next) {
// Suppressing "use after free" warning as this could happen only if queue is in inconsistent state
// which never happens if STAILQ interface used
siz += item->len; // NOLINT(clang-analyzer-unix.Malloc)
}
return siz;
}
void outbox_delete_all_items(outbox_handle_t outbox)
{
outbox_item_handle_t item, tmp;
STAILQ_FOREACH_SAFE(item, outbox, next, tmp) {
STAILQ_REMOVE(outbox, item, outbox_item, next);
#ifdef USE_PSRAM
heap_caps_free(item->buffer);
heap_caps_free(item);
#else
free(item->buffer);
free(item);
#endif
}
}
void outbox_destroy(outbox_handle_t outbox)
{
outbox_delete_all_items(outbox);
#ifdef USE_PSRAM
heap_caps_free(outbox);
#else
free(outbox);
#endif
}
#endif /* CONFIG_MQTT_CUSTOM_OUTBOX */

View File

@@ -0,0 +1,66 @@
/* This is an adaption of https://github.com/espressif/esp-mqtt/blob/master/lib/include/mqtt_outbox.h
* This file is subject to the terms and conditions defined in
* file 'LICENSE', which is part of this source code package.
* Tuan PM <tuanpm at live dot com>
*/
#ifndef _MQTT_OUTOBX_H_
#define _MQTT_OUTOBX_H_
//#include "platform.h"
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
struct outbox_item;
typedef struct outbox_list_t *outbox_handle_t;
typedef struct outbox_item *outbox_item_handle_t;
typedef struct outbox_message *outbox_message_handle_t;
typedef long long outbox_tick_t;
typedef struct outbox_message {
uint8_t *data;
int len;
int msg_id;
int msg_qos;
int msg_type;
uint8_t *remaining_data;
int remaining_len;
} outbox_message_t;
typedef enum pending_state {
QUEUED,
TRANSMITTED,
ACKNOWLEDGED,
CONFIRMED
} pending_state_t;
outbox_handle_t outbox_init(void);
outbox_item_handle_t outbox_enqueue(outbox_handle_t outbox, outbox_message_handle_t message, outbox_tick_t tick);
outbox_item_handle_t outbox_dequeue(outbox_handle_t outbox, pending_state_t pending, outbox_tick_t *tick);
outbox_item_handle_t outbox_get(outbox_handle_t outbox, int msg_id);
uint8_t *outbox_item_get_data(outbox_item_handle_t item, size_t *len, uint16_t *msg_id, int *msg_type, int *qos);
esp_err_t outbox_delete(outbox_handle_t outbox, int msg_id, int msg_type);
esp_err_t outbox_delete_msgid(outbox_handle_t outbox, int msg_id);
esp_err_t outbox_delete_msgtype(outbox_handle_t outbox, int msg_type);
esp_err_t outbox_delete_item(outbox_handle_t outbox, outbox_item_handle_t item);
int outbox_delete_expired(outbox_handle_t outbox, outbox_tick_t current_tick, outbox_tick_t timeout);
/**
* @brief Deletes single expired message returning it's message id
*
* @return msg id of the deleted message, -1 if no expired message in the outbox
*/
int outbox_delete_single_expired(outbox_handle_t outbox, outbox_tick_t current_tick, outbox_tick_t timeout);
esp_err_t outbox_set_pending(outbox_handle_t outbox, int msg_id, pending_state_t pending);
pending_state_t outbox_item_get_pending(outbox_item_handle_t item);
esp_err_t outbox_set_tick(outbox_handle_t outbox, int msg_id, outbox_tick_t tick);
int outbox_get_size(outbox_handle_t outbox);
void outbox_destroy(outbox_handle_t outbox);
void outbox_delete_all_items(outbox_handle_t outbox);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -7,6 +7,7 @@
#include "esp_log.h"
#include "ClassLogFile.h"
#include "connect_wlan.h"
#include "read_wlanini.h"
#include "server_mqtt.h"
#include "interface_mqtt.h"
#include "time_sntp.h"
@@ -29,8 +30,9 @@ std::string timeUnit = "";
std::string rateUnit = "Unit/Minute";
float roundInterval; // Minutes
int keepAlive = 0; // Seconds
int retainFlag;
bool retainFlag;
static std::string maintopic;
bool sendingOf_DiscoveryAndStaticTopics_scheduled = true; // Set it to true to make sure it gets sent at least once after startup
void mqttServer_setParameter(std::vector<NumberPost*>* _NUMBERS, int _keepAlive, float _roundInterval) {
@@ -46,8 +48,9 @@ void mqttServer_setMeterType(std::string _meterType, std::string _valueUnit, std
rateUnit = _rateUnit;
}
void sendHomeAssistantDiscoveryTopic(std::string group, std::string field,
std::string name, std::string icon, std::string unit, std::string deviceClass, std::string stateClass, std::string entityCategory) {
bool sendHomeAssistantDiscoveryTopic(std::string group, std::string field,
std::string name, std::string icon, std::string unit, std::string deviceClass, std::string stateClass, std::string entityCategory,
int qos) {
std::string version = std::string(libfive_git_version());
if (version == "") {
@@ -130,26 +133,31 @@ void sendHomeAssistantDiscoveryTopic(std::string group, std::string field,
"}" +
"}";
MQTTPublish(topicFull, payload, true);
return MQTTPublish(topicFull, payload, qos, true);
}
void MQTThomeassistantDiscovery() {
if (!getMQTTisConnected())
return;
bool MQTThomeassistantDiscovery(int qos) {
bool allSendsSuccessed = false;
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "MQTT - Sending Homeassistant Discovery Topics (Meter Type: " + meterType + ", Value Unit: " + valueUnit + " , Rate Unit: " + rateUnit + ")...");
if (!getMQTTisConnected()) {
LogFile.WriteToFile(ESP_LOG_WARN, TAG, "Unable to send Homeassistant Discovery Topics, we are not connected to the MQTT broker!");
return false;
}
// Group | Field | User Friendly Name | Icon | Unit | Device Class | State Class | Entity Category
sendHomeAssistantDiscoveryTopic("", "uptime", "Uptime", "clock-time-eight-outline", "s", "", "", "diagnostic");
sendHomeAssistantDiscoveryTopic("", "MAC", "MAC Address", "network-outline", "", "", "", "diagnostic");
sendHomeAssistantDiscoveryTopic("", "hostname", "Hostname", "network-outline", "", "", "", "diagnostic");
sendHomeAssistantDiscoveryTopic("", "freeMem", "Free Memory", "memory", "B", "", "measurement", "diagnostic");
sendHomeAssistantDiscoveryTopic("", "wifiRSSI", "Wi-Fi RSSI", "wifi", "dBm", "signal_strength", "", "diagnostic");
sendHomeAssistantDiscoveryTopic("", "CPUtemp", "CPU Temperature", "thermometer", "°C", "temperature", "measurement", "diagnostic");
sendHomeAssistantDiscoveryTopic("", "interval", "Interval", "clock-time-eight-outline", "min", "" , "measurement", "diagnostic");
sendHomeAssistantDiscoveryTopic("", "IP", "IP", "network-outline", "", "", "", "diagnostic");
sendHomeAssistantDiscoveryTopic("", "status", "Status", "list-status", "", "", "", "diagnostic");
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Publishing Homeassistant Discovery topics (Meter Type: '" + meterType + "', Value Unit: '" + valueUnit + "' , Rate Unit: '" + rateUnit + "') ...");
int aFreeInternalHeapSizeBefore = heap_caps_get_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
// Group | Field | User Friendly Name | Icon | Unit | Device Class | State Class | Entity Category
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic("", "uptime", "Uptime", "clock-time-eight-outline", "s", "", "", "diagnostic", qos);
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic("", "MAC", "MAC Address", "network-outline", "", "", "", "diagnostic", qos);
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic("", "hostname", "Hostname", "network-outline", "", "", "", "diagnostic", qos);
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic("", "freeMem", "Free Memory", "memory", "B", "", "measurement", "diagnostic", qos);
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic("", "wifiRSSI", "Wi-Fi RSSI", "wifi", "dBm", "signal_strength", "", "diagnostic", qos);
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic("", "CPUtemp", "CPU Temperature", "thermometer", "°C", "temperature", "measurement", "diagnostic", qos);
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic("", "interval", "Interval", "clock-time-eight-outline", "min", "" , "measurement", "diagnostic", qos);
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic("", "IP", "IP", "network-outline", "", "", "", "diagnostic", qos);
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic("", "status", "Status", "list-status", "", "", "", "diagnostic", qos);
for (int i = 0; i < (*NUMBERS).size(); ++i) {
@@ -158,76 +166,141 @@ void MQTThomeassistantDiscovery() {
group = "";
}
// Group | Field | User Friendly Name | Icon | Unit | Device Class | State Class | Entity Category
sendHomeAssistantDiscoveryTopic(group, "value", "Value", "gauge", valueUnit, meterType, "total_increasing", "");
sendHomeAssistantDiscoveryTopic(group, "raw", "Raw Value", "raw", valueUnit, "", "total_increasing", "diagnostic");
sendHomeAssistantDiscoveryTopic(group, "error", "Error", "alert-circle-outline", "", "", "", "diagnostic");
// Group | Field | User Friendly Name | Icon | Unit | Device Class | State Class | Entity Category
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic(group, "value", "Value", "gauge", valueUnit, meterType, "total_increasing", "", qos);
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic(group, "raw", "Raw Value", "raw", "", "", "", "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_digitalization_round */
// sendHomeAssistantDiscoveryTopic(group, "rate", "Rate (Unit/Minute)", "swap-vertical", "", "", "", ""); // Legacy, always Unit per Minute
sendHomeAssistantDiscoveryTopic(group, "rate_per_time_unit", "Rate (" + rateUnit + ")", "swap-vertical", rateUnit, "", "", "");
sendHomeAssistantDiscoveryTopic(group, "rate_per_digitalization_round", "Change since last digitalization round", "arrow-expand-vertical", valueUnit, "", "measurement", ""); // correctly the Unit is Uint/Interval!
sendHomeAssistantDiscoveryTopic(group, "timestamp", "Timestamp", "clock-time-eight-outline", "", "timestamp", "", "diagnostic");
sendHomeAssistantDiscoveryTopic(group, "json", "JSON", "code-json", "", "", "", "diagnostic");
sendHomeAssistantDiscoveryTopic(group, "problem", "Problem", "alert-outline", "", "problem", "", ""); // Special binary sensor which is based on error topic
// 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, "", "measurement", "", qos);
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic(group, "rate_per_digitalization_round", "Change since last digitalization 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");
int aFreeInternalHeapSizeAfter = heap_caps_get_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
int aMinFreeInternalHeapSize = heap_caps_get_minimum_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Int. Heap Usage before Publishing Homeassistand Discovery Topics: " +
to_string(aFreeInternalHeapSizeBefore) + ", after: " + to_string(aFreeInternalHeapSizeAfter) + ", delta: " +
to_string(aFreeInternalHeapSizeBefore - aFreeInternalHeapSizeAfter) + ", lowest free: " + to_string(aMinFreeInternalHeapSize));
return allSendsSuccessed;
}
void publishSystemData() {
if (!getMQTTisConnected())
return;
bool publishSystemData(int qos) {
bool allSendsSuccessed = false;
if (!getMQTTisConnected()) {
LogFile.WriteToFile(ESP_LOG_WARN, TAG, "Unable to send System Topics, we are not connected to the MQTT broker!");
return false;
}
char tmp_char[50];
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Publishing system MQTT topics...");
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Publishing System MQTT topics...");
int aFreeInternalHeapSizeBefore = heap_caps_get_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
allSendsSuccessed |= MQTTPublish(maintopic + "/" + std::string(LWT_TOPIC), LWT_CONNECTED, qos, retainFlag); // Publish "connected" to maintopic/connection
sprintf(tmp_char, "%ld", (long)getUpTime());
MQTTPublish(maintopic + "/" + "uptime", std::string(tmp_char), retainFlag);
allSendsSuccessed |= MQTTPublish(maintopic + "/" + "uptime", std::string(tmp_char), qos, retainFlag);
sprintf(tmp_char, "%lu", (long) getESPHeapSize());
MQTTPublish(maintopic + "/" + "freeMem", std::string(tmp_char), retainFlag);
allSendsSuccessed |= MQTTPublish(maintopic + "/" + "freeMem", std::string(tmp_char), qos, retainFlag);
sprintf(tmp_char, "%d", get_WIFI_RSSI());
MQTTPublish(maintopic + "/" + "wifiRSSI", std::string(tmp_char), retainFlag);
allSendsSuccessed |= MQTTPublish(maintopic + "/" + "wifiRSSI", std::string(tmp_char), qos, retainFlag);
sprintf(tmp_char, "%d", (int)temperatureRead());
MQTTPublish(maintopic + "/" + "CPUtemp", std::string(tmp_char), retainFlag);
allSendsSuccessed |= MQTTPublish(maintopic + "/" + "CPUtemp", std::string(tmp_char), qos, retainFlag);
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Successfully published all System MQTT topics");
int aFreeInternalHeapSizeAfter = heap_caps_get_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
int aMinFreeInternalHeapSize = heap_caps_get_minimum_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Int. Heap Usage before publishing System Topics: " +
to_string(aFreeInternalHeapSizeBefore) + ", after: " + to_string(aFreeInternalHeapSizeAfter) + ", delta: " +
to_string(aFreeInternalHeapSizeBefore - aFreeInternalHeapSizeAfter) + ", lowest free: " + to_string(aMinFreeInternalHeapSize));
return allSendsSuccessed;
}
void publishStaticData() {
if (!getMQTTisConnected())
return;
bool publishStaticData(int qos) {
bool allSendsSuccessed = false;
if (!getMQTTisConnected()) {
LogFile.WriteToFile(ESP_LOG_WARN, TAG, "Unable to send Static Topics, we are not connected to the MQTT broker!");
return false;
}
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Publishing static MQTT topics...");
MQTTPublish(maintopic + "/" + "MAC", getMac(), retainFlag);
MQTTPublish(maintopic + "/" + "IP", *getIPAddress(), retainFlag);
MQTTPublish(maintopic + "/" + "hostname", hostname, retainFlag);
int aFreeInternalHeapSizeBefore = heap_caps_get_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
allSendsSuccessed |= MQTTPublish(maintopic + "/" + "MAC", getMac(), qos, retainFlag);
allSendsSuccessed |= MQTTPublish(maintopic + "/" + "IP", *getIPAddress(), qos, retainFlag);
allSendsSuccessed |= MQTTPublish(maintopic + "/" + "hostname", wlan_config.hostname, qos, retainFlag);
std::stringstream stream;
stream << std::fixed << std::setprecision(1) << roundInterval; // minutes
MQTTPublish(maintopic + "/" + "interval", stream.str(), retainFlag);
allSendsSuccessed |= MQTTPublish(maintopic + "/" + "interval", stream.str(), qos, retainFlag);
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Successfully published all Static MQTT topics");
int aFreeInternalHeapSizeAfter = heap_caps_get_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
int aMinFreeInternalHeapSize = heap_caps_get_minimum_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Int. Heap Usage before Publishing Static Topics: " +
to_string(aFreeInternalHeapSizeBefore) + ", after: " + to_string(aFreeInternalHeapSizeAfter) + ", delta: " +
to_string(aFreeInternalHeapSizeBefore - aFreeInternalHeapSizeAfter) + ", lowest free: " + to_string(aMinFreeInternalHeapSize));
return allSendsSuccessed;
}
esp_err_t sendDiscovery_and_static_Topics(httpd_req_t *req) {
if (HomeassistantDiscovery) {
MQTThomeassistantDiscovery();
}
publishStaticData();
const char* resp_str = (const char*) req->user_ctx;
httpd_resp_send(req, resp_str, strlen(resp_str));
esp_err_t scheduleSendingDiscovery_and_static_Topics(httpd_req_t *req) {
sendingOf_DiscoveryAndStaticTopics_scheduled = true;
char msg[] = "MQTT Homeassistant Discovery and Static Topics scheduled";
httpd_resp_send(req, msg, strlen(msg));
return ESP_OK;
}
void GotConnected(std::string maintopic, int retainFlag) {
if (HomeassistantDiscovery) {
MQTThomeassistantDiscovery();
esp_err_t sendDiscovery_and_static_Topics(void) {
bool success = false;
if (!sendingOf_DiscoveryAndStaticTopics_scheduled) {
// Flag not set, nothing to do
return ESP_OK;
}
publishStaticData();
publishSystemData();
if (HomeassistantDiscovery) {
success = MQTThomeassistantDiscovery(1);
}
success |= publishStaticData(1);
if (success) { // Success, clear the flag
sendingOf_DiscoveryAndStaticTopics_scheduled = false;
return ESP_OK;
}
else {
LogFile.WriteToFile(ESP_LOG_WARN, TAG, "One or more MQTT topics failed to be published, will try sending them in the next round!");
/* Keep sendingOf_DiscoveryAndStaticTopics_scheduled set so we can retry after the next round */
return ESP_FAIL;
}
}
void GotConnected(std::string maintopic, bool retainFlag) {
// Nothing to do
}
void register_server_mqtt_uri(httpd_handle_t server) {
@@ -235,8 +308,8 @@ void register_server_mqtt_uri(httpd_handle_t server) {
uri.method = HTTP_GET;
uri.uri = "/mqtt_publish_discovery";
uri.handler = sendDiscovery_and_static_Topics;
uri.user_ctx = (void*) "MQTT Discovery and Static Topics sent";
uri.handler = scheduleSendingDiscovery_and_static_Topics;
uri.user_ctx = (void*) "";
httpd_register_uri_handler(server, &uri);
}
@@ -251,7 +324,7 @@ void SetHomeassistantDiscoveryEnabled(bool enabled) {
}
void setMqtt_Server_Retain(int _retainFlag) {
void setMqtt_Server_Retain(bool _retainFlag) {
retainFlag = _retainFlag;
}

View File

@@ -10,16 +10,17 @@
void SetHomeassistantDiscoveryEnabled(bool enabled);
void mqttServer_setParameter(std::vector<NumberPost*>* _NUMBERS, int interval, float roundInterval);
void mqttServer_setMeterType(std::string meterType, std::string valueUnit, std::string timeUnit,std::string rateUnit);
void setMqtt_Server_Retain(int SetRetainFlag);
void setMqtt_Server_Retain(bool SetRetainFlag);
void mqttServer_setMainTopic( std::string maintopic);
std::string mqttServer_getMainTopic();
void register_server_mqtt_uri(httpd_handle_t server);
void publishSystemData();
bool publishSystemData(int qos);
std::string getTimeUnit(void);
void GotConnected(std::string maintopic, int SetRetainFlag);
void GotConnected(std::string maintopic, bool SetRetainFlag);
esp_err_t sendDiscovery_and_static_Topics(void);
#endif //SERVERMQTT_H

View File

@@ -1,6 +1,7 @@
#include "CTfLiteClass.h"
#include "ClassLogFile.h"
#include "Helper.h"
#include "psram.h"
#include "esp_log.h"
#include "../../include/defines.h"
@@ -250,7 +251,7 @@ bool CTfLiteClass::ReadFileToModel(std::string _fn)
LogFile.WriteHeapInfo("CTLiteClass::Alloc modelfile start");
#endif
modelfile = (unsigned char*)GET_MEMORY(size);
modelfile = (unsigned char*)malloc_psram_heap(std::string(TAG) + "->modelfile", size, MALLOC_CAP_SPIRAM);
if(modelfile != NULL)
{
@@ -305,17 +306,17 @@ CTfLiteClass::CTfLiteClass()
this->input = nullptr;
this->output = nullptr;
this->kTensorArenaSize = 800 * 1024; /// according to testfile: 108000 - so far 600;; 2021-09-11: 200 * 1024
this->tensor_arena = (uint8_t*)GET_MEMORY(kTensorArenaSize);
this->tensor_arena = (uint8_t*)malloc_psram_heap(std::string(TAG) + "->tensor_arena", kTensorArenaSize, MALLOC_CAP_SPIRAM);
}
CTfLiteClass::~CTfLiteClass()
{
free(modelfile);
free(this->tensor_arena);
delete this->interpreter;
delete this->error_reporter;
free_psram_heap(std::string(TAG) + "->modelfile", modelfile);
free_psram_heap(std::string(TAG) + "->tensor_arena", this->tensor_arena);
}

View File

@@ -10,6 +10,7 @@
#include "../../include/defines.h"
#include "Helper.h"
#include "statusled.h"
#include "esp_camera.h"
#include "time_sntp.h"
@@ -21,7 +22,11 @@
#include "server_GPIO.h"
#include "server_file.h"
#include "read_wlanini.h"
#include "connect_wlan.h"
#include "psram.h"
ClassFlowControll tfliteflow;
@@ -30,7 +35,7 @@ TaskHandle_t xHandletask_autodoFlow = NULL;
bool bTaskAutoFlowCreated = false;
bool flowisrunning = false;
long auto_intervall = 0;
long auto_interval = 0;
bool auto_isrunning = false;
int countRounds = 0;
@@ -44,20 +49,24 @@ static const char *TAG = "TFLITE SERVER";
void CheckIsPlannedReboot()
{
FILE *pfile;
if ((pfile = fopen("/sdcard/reboot.txt", "r")) == NULL)
{
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Not a planned reboot.");
if ((pfile = fopen("/sdcard/reboot.txt", "r")) == NULL) {
//LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Initial boot or not a planned reboot");
isPlannedReboot = false;
}
else
{
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Planned reboot.");
else {
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Planned reboot");
DeleteFile("/sdcard/reboot.txt"); // Prevent Boot Loop!!!
isPlannedReboot = true;
}
}
bool getIsPlannedReboot()
{
return isPlannedReboot;
}
int getCountFlowRounds()
{
return countRounds;
@@ -143,12 +152,12 @@ esp_err_t handler_get_heap(httpd_req_t *req)
std::string zw = "Heap info:<br>" + getESPHeapInfo();
#ifdef TASK_ANALYSIS_ON
char* pcTaskList = (char*) heap_caps_calloc(1, sizeof(char) * 768, MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM);
char* pcTaskList = (char*) calloc_psram_heap(std::string(TAG) + "->pcTaskList", 1, sizeof(char) * 768, MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM);
if (pcTaskList) {
vTaskList(pcTaskList);
zw = zw + "<br><br>Task info:<br><pre>Name | State | Prio | Lowest stacksize | Creation order | CPU (-1=NoAffinity)<br>"
+ std::string(pcTaskList) + "</pre>";
heap_caps_free(pcTaskList);
free_psram_heap(std::string(TAG) + "->pcTaskList", pcTaskList);
}
else {
zw = zw + "<br><br>Task info:<br>ERROR - Allocation of TaskList buffer in PSRAM failed";
@@ -370,64 +379,109 @@ esp_err_t handler_wasserzaehler(httpd_req_t *req)
return ESP_OK;
}
zw = tfliteflow.getReadout(_rawValue, _noerror);
if (zw.length() > 0)
httpd_resp_sendstr_chunk(req, zw.c_str());
std::string *status = tfliteflow.getActStatus();
string query = std::string(_query);
// ESP_LOGD(TAG, "Query: %s, query.c_str());
if (query.find("full") != std::string::npos)
{
string txt;
txt = "<body style=\"font-family: arial\">";
if ((countRounds <= 1) && (*status != std::string("Flow finished"))) { // First round not completed yet
txt += "<h3>Please wait for the first round to complete!</h3><h3>Current state: " + *status + "</h3>\n";
}
else {
txt += "<h3>Value</h3>";
}
httpd_resp_sendstr_chunk(req, txt.c_str());
}
zw = tfliteflow.getReadout(_rawValue, _noerror);
if (zw.length() > 0)
httpd_resp_sendstr_chunk(req, zw.c_str());
if (query.find("full") != std::string::npos)
{
string txt, zw;
txt = "<p>Aligned Image: <p><img src=\"/img_tmp/alg_roi.jpg\"> <p>\n";
txt = txt + "Digital Counter: <p> ";
httpd_resp_sendstr_chunk(req, txt.c_str());
std::vector<HTMLInfo*> htmlinfodig;
htmlinfodig = tfliteflow.GetAllDigital();
for (int i = 0; i < htmlinfodig.size(); ++i)
{
if (tfliteflow.GetTypeDigital() == Digital)
if ((countRounds <= 1) && (*status != std::string("Flow finished"))) { // First round not completed yet
// Nothing to do
}
else {
/* Digital ROIs */
txt = "<body style=\"font-family: arial\">";
txt += "<h3>Recognized Digit ROIs (previous round)</h3>\n";
txt += "<table style=\"border-spacing: 5px\"><tr style=\"text-align: center; vertical-align: top;\">\n";
std::vector<HTMLInfo*> htmlinfodig;
htmlinfodig = tfliteflow.GetAllDigital();
for (int i = 0; i < htmlinfodig.size(); ++i)
{
if (htmlinfodig[i]->val == 10)
zw = "NaN";
else
zw = to_string((int) htmlinfodig[i]->val);
if (tfliteflow.GetTypeDigital() == Digital)
{
if (htmlinfodig[i]->val == 10)
zw = "NaN";
else
zw = to_string((int) htmlinfodig[i]->val);
txt = "<img src=\"/img_tmp/" + htmlinfodig[i]->filename + "\"> " + zw;
txt += "<td style=\"width: 100px\"><h4>" + zw + "</h4><p><img src=\"/img_tmp/" + htmlinfodig[i]->filename + "\"></p></td>\n";
}
else
{
std::stringstream stream;
stream << std::fixed << std::setprecision(1) << htmlinfodig[i]->val;
zw = stream.str();
txt += "<td style=\"width: 100px\"><h4>" + zw + "</h4><p><img src=\"/img_tmp/" + htmlinfodig[i]->filename + "\"></p></td>\n";
}
delete htmlinfodig[i];
}
else
htmlinfodig.clear();
txt += "</tr></table>\n";
httpd_resp_sendstr_chunk(req, txt.c_str());
/* Analog ROIs */
txt = "<h3>Recognized Analog ROIs (previous round)</h3>\n";
txt += "<table style=\"border-spacing: 5px\"><tr style=\"text-align: center; vertical-align: top;\">\n";
std::vector<HTMLInfo*> htmlinfoana;
htmlinfoana = tfliteflow.GetAllAnalog();
for (int i = 0; i < htmlinfoana.size(); ++i)
{
std::stringstream stream;
stream << std::fixed << std::setprecision(1) << htmlinfodig[i]->val;
stream << std::fixed << std::setprecision(1) << htmlinfoana[i]->val;
zw = stream.str();
txt = "<img src=\"/img_tmp/" + htmlinfodig[i]->filename + "\"> " + zw;
txt += "<td style=\"width: 150px;\"><h4>" + zw + "</h4><p><img src=\"/img_tmp/" + htmlinfoana[i]->filename + "\"></p></td>\n";
delete htmlinfoana[i];
}
htmlinfoana.clear();
txt += "</tr>\n</table>\n";
httpd_resp_sendstr_chunk(req, txt.c_str());
/* Full Image
* Only show it after the image got taken and aligned */
txt = "<h3>Aligned Image (current round)</h3>\n";
if ((*status == std::string("Initialization")) ||
(*status == std::string("Initialization (delayed)")) ||
(*status == std::string("Take Image"))) {
txt += "<p>Current state: " + *status + "</p>\n";
}
else {
txt += "<img src=\"/img_tmp/alg_roi.jpg\">\n";
}
httpd_resp_sendstr_chunk(req, txt.c_str());
delete htmlinfodig[i];
}
htmlinfodig.clear();
txt = " <p> Analog Meter: <p> ";
httpd_resp_sendstr_chunk(req, txt.c_str());
std::vector<HTMLInfo*> htmlinfoana;
htmlinfoana = tfliteflow.GetAllAnalog();
for (int i = 0; i < htmlinfoana.size(); ++i)
{
std::stringstream stream;
stream << std::fixed << std::setprecision(1) << htmlinfoana[i]->val;
zw = stream.str();
txt = "<img src=\"/img_tmp/" + htmlinfoana[i]->filename + "\"> " + zw;
httpd_resp_sendstr_chunk(req, txt.c_str());
delete htmlinfoana[i];
}
htmlinfoana.clear();
}
/* Respond with an empty chunk to signal HTTP response completion */
@@ -564,11 +618,11 @@ esp_err_t handler_editflow(httpd_req_t *req)
string out2 = out.substr(0, out.length() - 4) + "_org.jpg";
CAlignAndCutImage *caic = new CAlignAndCutImage(in);
CAlignAndCutImage *caic = new CAlignAndCutImage("cutref", in);
caic->CutAndSave(out2, x, y, dx, dy);
delete caic;
CImageBasis *cim = new CImageBasis(out2);
CImageBasis *cim = new CImageBasis("cutref", out2);
if (enhance)
{
cim->Contrast(90);
@@ -620,8 +674,8 @@ esp_err_t handler_editflow(httpd_req_t *req)
// string zwzw = "Do " + _task + " start\n"; ESP_LOGD(TAG, zwzw.c_str());
Camera.SetBrightnessContrastSaturation(bri, con, sat);
Camera.SetLEDIntensity(intens);
ESP_LOGD(TAG, "test_take - vor MakeImage");
std::string zw = tfliteflow.doSingleStep("[MakeImage]", _host);
ESP_LOGD(TAG, "test_take - vor TakeImage");
std::string zw = tfliteflow.doSingleStep("[TakeImage]", _host);
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
httpd_resp_send(req, zw.c_str(), zw.length());
}
@@ -664,7 +718,7 @@ esp_err_t handler_statusflow(httpd_req_t *req)
ESP_LOGD(TAG, "handler_prevalue: %s", req->uri);
#endif
string* zw = tfliteflow.getActStatus();
string* zw = tfliteflow.getActStatusWithTime();
resp_str = zw->c_str();
httpd_resp_send(req, resp_str, HTTPD_RESP_USE_STRLEN);
@@ -807,31 +861,25 @@ void task_autodoFlow(void *pvParameter)
bTaskAutoFlowCreated = true;
if (!isPlannedReboot)
if (!isPlannedReboot && (esp_reset_reason() == ESP_RST_PANIC))
{
if (esp_reset_reason() == ESP_RST_PANIC) {
LogFile.WriteToFile(ESP_LOG_WARN, TAG, "Restarted due to an Exception/panic! Postponing first round start by 5 minutes to allow for an OTA Update or to fetch the log!");
LogFile.WriteToFile(ESP_LOG_WARN, TAG, "Setting logfile level to DEBUG until the next reboot!");
LogFile.setLogLevel(ESP_LOG_DEBUG);
tfliteflow.setActStatus("Initialization (delayed)");
//#ifdef ENABLE_MQTT
//MQTTPublish(mqttServer_getMainTopic() + "/" + "status", "Initialization (delayed)", false); // Right now, not possible -> MQTT Service is going to be started later
//#endif //ENABLE_MQTT
vTaskDelay(60*5000 / portTICK_RATE_MS); // Wait 5 minutes to give time to do an OTA Update or fetch the log
}
tfliteflow.setActStatus("Initialization (delayed)");
//#ifdef ENABLE_MQTT
//MQTTPublish(mqttServer_getMainTopic() + "/" + "status", "Initialization (delayed)", false); // Right now, not possible -> MQTT Service is going to be started later
//#endif //ENABLE_MQTT
vTaskDelay(60*5000 / portTICK_PERIOD_MS); // Wait 5 minutes to give time to do an OTA update or fetch the log
}
ESP_LOGD(TAG, "task_autodoFlow: start");
doInit();
auto_isrunning = tfliteflow.isAutoStart(auto_intervall);
auto_isrunning = tfliteflow.isAutoStart(auto_interval);
if (isSetupModusActive())
{
auto_isrunning = false;
std::string zw_time = getCurrentTimeString(LOGFILE_TIME_FORMAT);
tfliteflow.doFlowMakeImageOnly(zw_time);
tfliteflow.doFlowTakeImageOnly(zw_time);
}
while (auto_isrunning)
@@ -861,21 +909,37 @@ void task_autodoFlow(void *pvParameter)
LogFile.RemoveOldLogFile();
LogFile.RemoveOldDataLog();
}
// Round finished -> Logfile
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Round #" + std::to_string(countRounds) +
" completed (" + std::to_string(getUpTime() - roundStartTime) + " seconds)");
//CPU Temp -> Logfile
// CPU Temp -> Logfile
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "CPU Temperature: " + std::to_string((int)temperatureRead()) + "°C");
// WIFI Signal Strength (RSSI) -> Logfile
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "WIFI Signal (RSSI): " + std::to_string(get_WIFI_RSSI()) + "dBm");
//Round finished -> Logfile
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Round #" + std::to_string(countRounds) +
" completed (" + std::to_string(getUpTime() - roundStartTime) + " seconds)");
// Check if time is synchronized (if NTP is configured)
if (getUseNtp() && !getTimeIsSet()) {
LogFile.WriteToFile(ESP_LOG_WARN, TAG, "Time server is configured, but time is not yet set!");
StatusLED(TIME_CHECK, 1, false);
}
#if (defined WLAN_USE_MESH_ROAMING && defined WLAN_USE_MESH_ROAMING_ACTIVATE_CLIENT_TRIGGERED_QUERIES)
wifiRoamingQuery();
#endif
// Scan channels and check if an AP with better RSSI is available, then disconnect and try to reconnect to AP with better RSSI
// NOTE: Keep this direct before the following task delay, because scan is done in blocking mode and this takes ca. 1,5 - 2s.
#ifdef WLAN_USE_ROAMING_BY_SCANNING
wifiRoamByScanning();
#endif
fr_delta_ms = (esp_timer_get_time() - fr_start) / 1000;
if (auto_intervall > fr_delta_ms)
if (auto_interval > fr_delta_ms)
{
const TickType_t xDelay = (auto_intervall - fr_delta_ms) / portTICK_PERIOD_MS;
const TickType_t xDelay = (auto_interval - fr_delta_ms) / portTICK_PERIOD_MS;
ESP_LOGD(TAG, "Autoflow: sleep for: %ldms", (long) xDelay);
vTaskDelay( xDelay );
}
@@ -892,11 +956,12 @@ void TFliteDoAutoStart()
ESP_LOGD(TAG, "getESPHeapInfo: %s", getESPHeapInfo().c_str());
xReturned = xTaskCreatePinnedToCore(&task_autodoFlow, "task_autodoFlow", 16 * 1024, NULL, tskIDLE_PRIORITY+2, &xHandletask_autodoFlow, 0);
//xReturned = xTaskCreate(&task_autodoFlow, "task_autodoFlow", 16 * 1024, NULL, tskIDLE_PRIORITY+2, &xHandletask_autodoFlow);
uint32_t stackSize = 16 * 1024;
xReturned = xTaskCreatePinnedToCore(&task_autodoFlow, "task_autodoFlow", stackSize, NULL, tskIDLE_PRIORITY+2, &xHandletask_autodoFlow, 0);
if( xReturned != pdPASS )
{
ESP_LOGD(TAG, "ERROR task_autodoFlow konnte nicht erzeugt werden!");
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Creation task_autodoFlow failed. Requested stack size:" + std::to_string(stackSize));
LogFile.WriteHeapInfo("Creation task_autodoFlow failed");
}
ESP_LOGD(TAG, "getESPHeapInfo: %s", getESPHeapInfo().c_str());
}

View File

@@ -21,6 +21,7 @@ bool isSetupModusActive();
int getCountFlowRounds();
void CheckIsPlannedReboot();
bool getIsPlannedReboot();
esp_err_t GetJPG(std::string _filename, httpd_req_t *req);
esp_err_t GetRawJPG(httpd_req_t *req);

View File

@@ -25,6 +25,8 @@ static const char *TAG = "SNTP";
static std::string timeZone = "";
static std::string timeServer = "undefined";
static bool useNtp = true;
static bool timeWasNotSetAtBoot = false;
static bool timeWasNotSetAtBoot_PrintStartBlock = false;
std::string getNtpStatusText(sntp_sync_status_t status);
static void setTimeZone(std::string _tzstring);
@@ -59,7 +61,13 @@ std::string getCurrentTimeString(const char * frm)
void time_sync_notification_cb(struct timeval *tv)
{
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Time is now successfully synced with NTP Server " +
if (timeWasNotSetAtBoot_PrintStartBlock) {
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "=================================================");
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "==================== Start ======================");
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "== Logs before time sync -> log_1970-01-01.txt ==");
timeWasNotSetAtBoot_PrintStartBlock = false;
}
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Time is synced with NTP Server " +
getServerName() + ": " + getCurrentTimeString("%Y-%m-%d %H:%M:%S"));
}
@@ -111,6 +119,11 @@ bool getUseNtp(void) {
return useNtp;
}
bool getTimeWasNotSetAtBoot(void)
{
return timeWasNotSetAtBoot;
}
std::string getServerName(void) {
char buf[100];
@@ -140,7 +153,7 @@ bool setupTime() {
ConfigFile configFile = ConfigFile(CONFIG_FILE);
if (!configFile.ConfigFileExists()){
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "No ConfigFile defined - exit setupTime() ");
LogFile.WriteToFile(ESP_LOG_WARN, TAG, "No ConfigFile defined - exit setupTime()!");
return false;
}
@@ -162,10 +175,15 @@ bool setupTime() {
while (configFile.getNextLine(&line, disabledLine, eof) &&
!configFile.isNewParagraph(line)) {
splitted = ZerlegeZeile(line);
splitted = ZerlegeZeile(line, "=");
if (toUpper(splitted[0]) == "TIMEZONE") {
timeZone = splitted[1];
if (splitted.size() <= 1) { // parameter part is empty
timeZone = "";
}
else {
timeZone = splitted[1];
}
}
if (toUpper(splitted[0]) == "TIMESERVER") {
@@ -225,6 +243,8 @@ bool setupTime() {
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "The local time is unknown, starting with " + std::string(strftime_buf));
if (useNtp) {
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Once the NTP server provides a time, we will switch to that one");
timeWasNotSetAtBoot = true;
timeWasNotSetAtBoot_PrintStartBlock = true;
}
}

View File

@@ -22,6 +22,7 @@ std::string ConvertTimeToString(time_t _time, const char * frm);
bool getTimeIsSet(void);
bool getTimeWasNotSetAtBoot(void);
bool getUseNtp(void);
bool setupTime();

View File

@@ -1,12 +1,24 @@
#include "connect_wlan.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 "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
@@ -17,72 +29,41 @@
#include "interface_mqtt.h"
#endif //ENABLE_MQTT
#include <fstream>
#include <string>
#include <vector>
#include <sstream>
#include <iostream>
#include "ClassLogFile.h"
#include "read_wlanini.h"
#include "Helper.h"
#include "statusled.h"
//////////////////////
#include <string.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_wifi.h"
#include "esp_wnm.h"
#include "esp_rrm.h"
#include "esp_mbo.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_mac.h"
#include "nvs_flash.h"
#include "esp_netif.h"
/////////////////////
#include "../../include/defines.h"
/* FreeRTOS event group to signal when we are connected*/
static EventGroupHandle_t s_wifi_event_group;
static const char *TAG = "WIFI";
static int s_retry_num = 0;
bool WIFIConnected = false;
static bool APWithBetterRSSI = false;
static bool WIFIConnected = false;
static int WIFIReconnectCnt = 0;
///////////////////////////////////////////////////////////
int BlinkDauer;
int BlinkAnzahl;
bool BlinkOff;
bool BlinkIsRunning = false;
std::string hostname = "";
std::string std_hostname = "watermeter";
std::string ipadress = "";
std::string ssid = "";
int RSSIThreashold;
void strinttoip4(const char *ip, int &a, int &b, int &c, int &d) {
std::string zw = std::string(ip);
std::stringstream s(zw);
char ch; //to temporarily store the '.'
s >> a >> ch >> b >> ch >> c >> ch >> d;
}
std::string BssidToString(const char* c) {
char cBssid[25];
sprintf(cBssid, "%02x:%02x:%02x:%02x:%02x:%02x", c[0], c[1], c[2], c[3], c[4], c[5]);
return std::string(cBssid);
}
/////////////////////////////////
/////////////////////////////////
#ifdef WLAN_USE_MESH_ROAMING
int RSSI_Threshold = WLAN_WIFI_RSSI_THRESHOLD;
/* rrm ctx */
int rrm_ctx = 0;
/* FreeRTOS event group to signal when we are connected & ready to make a request */
static EventGroupHandle_t wifi_event_group;
/* esp netif object representing the WIFI station */
static esp_netif_t *sta_netif = NULL;
//static const char *TAG = "roaming_example";
static inline uint32_t WPA_GET_LE32(const uint8_t *a)
{
return ((uint32_t) a[3] << 24) | (a[2] << 16) | (a[1] << 8) | a[0];
@@ -105,6 +86,7 @@ static inline uint32_t WPA_GET_LE32(const uint8_t *a)
#define ETH_ALEN 6
#endif
#define MAX_NEIGHBOR_LEN 512
static char * get_btm_neighbor_list(uint8_t *report, size_t report_len)
{
@@ -121,10 +103,10 @@ static char * get_btm_neighbor_list(uint8_t *report, size_t report_len)
* PHY Type[1]
* Optional Subelements[variable]
*/
#define NR_IE_MIN_LEN (ETH_ALEN + 4 + 1 + 1 + 1)
#define NR_IE_MIN_LEN (ETH_ALEN + 4 + 1 + 1 + 1)
if (!report || report_len == 0) {
ESP_LOGI(TAG, "RRM neighbor report is not valid");
ESP_LOGD(TAG, "Roaming: RRM neighbor report is not valid");
return NULL;
}
@@ -140,14 +122,14 @@ static char * get_btm_neighbor_list(uint8_t *report, size_t report_len)
if (pos[0] != WLAN_EID_NEIGHBOR_REPORT ||
nr_len < NR_IE_MIN_LEN) {
ESP_LOGI(TAG, "CTRL: Invalid Neighbor Report element: id=%u len=%u",
ESP_LOGD(TAG, "Roaming CTRL: Invalid Neighbor Report element: id=%u len=%u",
data[0], nr_len);
ret = -1;
goto cleanup;
}
if (2U + nr_len > report_len) {
ESP_LOGI(TAG, "CTRL: Invalid Neighbor Report element: id=%u len=%zu nr_len=%u",
ESP_LOGD(TAG, "Roaming CTRL: Invalid Neighbor Report element: id=%u len=%zu nr_len=%u",
data[0], report_len, nr_len);
ret = -1;
goto cleanup;
@@ -190,8 +172,8 @@ static char * get_btm_neighbor_list(uint8_t *report, size_t report_len)
pos += s_len;
}
ESP_LOGI(TAG, "RMM neigbor report bssid=" MACSTR
ESP_LOGI(TAG, "Roaming: RMM neigbor report bssid=" MACSTR
" info=0x%x op_class=%u chan=%u phy_type=%u%s%s%s%s",
MAC2STR(nr), WPA_GET_LE32(nr + ETH_ALEN),
nr[ETH_ALEN + 4], nr[ETH_ALEN + 5],
@@ -199,6 +181,10 @@ static char * get_btm_neighbor_list(uint8_t *report, size_t report_len)
lci[0] ? " lci=" : "", lci,
civic[0] ? " civic=" : "", civic);
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Roaming: RMM neigbor report BSSID: " + BssidToString((char*)nr) +
", Channel: " + std::to_string(nr[ETH_ALEN + 5]));
/* neighbor start */
len += snprintf(buf + len, MAX_NEIGHBOR_LEN - len, " neighbor=");
/* bssid */
@@ -236,18 +222,19 @@ void neighbor_report_recv_cb(void *ctx, const uint8_t *report, size_t report_len
int *val = (int*) ctx;
uint8_t *pos = (uint8_t *)report;
int cand_list = 0;
int ret;
if (!report) {
ESP_LOGE(TAG, "report is null");
ESP_LOGD(TAG, "Roaming: Neighbor report is null");
return;
}
if (*val != rrm_ctx) {
ESP_LOGE(TAG, "rrm_ctx value didn't match, not initiated by us");
ESP_LOGE(TAG, "Roaming: rrm_ctx value didn't match, not initiated by us");
return;
}
/* dump report info */
ESP_LOGI(TAG, "rrm: neighbor report len=%d", report_len);
ESP_LOG_BUFFER_HEXDUMP(TAG, pos, report_len, ESP_LOG_INFO);
ESP_LOGD(TAG, "Roaming: RRM neighbor report len=%d", report_len);
ESP_LOG_BUFFER_HEXDUMP(TAG, pos, report_len, ESP_LOG_DEBUG);
/* create neighbor list */
char *neighbor_list = get_btm_neighbor_list(pos + 1, report_len - 1);
@@ -266,8 +253,10 @@ void neighbor_report_recv_cb(void *ctx, const uint8_t *report, size_t report_len
esp_wifi_scan_get_ap_records(&number, &ap_records);
cand_list = 1;
}
/* send AP btm query, this will cause STA to roam as well */
esp_wnm_send_bss_transition_mgmt_query(REASON_FRAME_LOSS, neighbor_list, cand_list);
/* send AP btm query requesting to roam depending on candidate list of AP */
// btm_query_reasons: https://github.com/espressif/esp-idf/blob/release/v4.4/components/wpa_supplicant/esp_supplicant/include/esp_wnm.h
ret = esp_wnm_send_bss_transition_mgmt_query(REASON_FRAME_LOSS, neighbor_list, cand_list); // query reason 16 -> LOW RSSI --> (btm_query_reason)16
ESP_LOGD(TAG, "neighbor_report_recv_cb retval - bss_transisition_query: %d", ret);
cleanup:
if (neighbor_list)
@@ -275,276 +264,399 @@ cleanup:
}
static void esp_bss_rssi_low_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
static void esp_bss_rssi_low_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data)
{
int retval = -1;
wifi_event_bss_rssi_low_t *event = (wifi_event_bss_rssi_low_t*) event_data;
ESP_LOGI(TAG, "%s:bss rssi is=%d", __func__, event->rssi);
/* Lets check channel conditions */
rrm_ctx++;
if (esp_rrm_send_neighbor_rep_request(neighbor_report_recv_cb, &rrm_ctx) < 0) {
/* failed to send neighbor report request */
ESP_LOGI(TAG, "failed to send neighbor report request");
if (esp_wnm_send_bss_transition_mgmt_query(REASON_FRAME_LOSS, NULL, 0) < 0) {
ESP_LOGI(TAG, "failed to send btm query");
}
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Roaming Event: RSSI " + std::to_string(event->rssi) +
" < RSSI_Threshold " + std::to_string(wlan_config.rssi_threshold));
/* If RRM is supported, call RRM and then send BTM query to AP */
if (esp_rrm_is_rrm_supported_connection() && esp_wnm_is_btm_supported_connection())
{
/* Lets check channel conditions */
rrm_ctx++;
retval = esp_rrm_send_neighbor_rep_request(neighbor_report_recv_cb, &rrm_ctx);
ESP_LOGD(TAG, "esp_rrm_send_neighbor_rep_request retval: %d", retval);
if (retval == 0)
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Roaming: RRM + BTM query sent");
else
ESP_LOGD(TAG, "esp_rrm_send_neighbor_rep_request retval: %d", retval);
}
/* If RRM is not supported or RRM request failed, send directly BTM query to AP */
if (retval < 0 && esp_wnm_is_btm_supported_connection())
{
// btm_query_reasons: https://github.com/espressif/esp-idf/blob/release/v4.4/components/wpa_supplicant/esp_supplicant/include/esp_wnm.h
retval = esp_wnm_send_bss_transition_mgmt_query(REASON_FRAME_LOSS, NULL, 0); // query reason 16 -> LOW RSSI --> (btm_query_reason)16
ESP_LOGD(TAG, "esp_wnm_send_bss_transition_mgmt_query retval: %d", retval);
if (retval == 0)
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Roaming: BTM query sent");
else
ESP_LOGD(TAG, "esp_wnm_send_bss_transition_mgmt_query retval: %d", retval);
}
}
#endif
void printRoamingFeatureSupport(void)
{
if (esp_rrm_is_rrm_supported_connection())
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Roaming: RRM (802.11k) supported by AP");
else
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Roaming: RRM (802.11k) NOT supported by AP");
//////////////////////////////////
//////////////////////////////////
if (esp_wnm_is_btm_supported_connection())
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Roaming: BTM (802.11v) supported by AP");
else
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Roaming: BTM (802.11v) NOT supported by AP");
}
#ifdef WLAN_USE_MESH_ROAMING_ACTIVATE_CLIENT_TRIGGERED_QUERIES
void wifiRoamingQuery(void)
{
/* Query only if WIFI is connected and feature is supported by AP */
if (WIFIConnected && (esp_rrm_is_rrm_supported_connection() || esp_wnm_is_btm_supported_connection())) {
/* Client is allowed to send query to AP for roaming request if RSSI is lower than threshold */
/* Note 1: Set RSSI threshold funtion needs to be called to trigger WIFI_EVENT_STA_BSS_RSSI_LOW */
/* Note 2: Additional querys will be sent after flow round is finshed --> server_tflite.cpp - function "task_autodoFlow" */
/* Note 3: RSSI_Threshold = 0 --> Disable client query by application (WebUI parameter) */
if (wlan_config.rssi_threshold != 0 && get_WIFI_RSSI() != -127 && (get_WIFI_RSSI() < wlan_config.rssi_threshold))
esp_wifi_set_rssi_threshold(wlan_config.rssi_threshold);
}
}
#endif // WLAN_USE_MESH_ROAMING_ACTIVATE_CLIENT_TRIGGERED_QUERIES
#endif // WLAN_USE_MESH_ROAMING
#ifdef WLAN_USE_ROAMING_BY_SCANNING
std::string getAuthModeName(const wifi_auth_mode_t auth_mode)
{
std::string AuthModeNames[] = {"OPEN", "WEP", "WPA PSK", "WPA2 PSK", "WPA WPA2 PSK", "WPA2 ENTERPRISE",
"WPA3 PSK", "WPA2 WPA3 PSK", "WAPI_PSK", "MAX"};
return AuthModeNames[auth_mode];
}
void wifi_scan(void)
{
wifi_scan_config_t wifi_scan_config;
memset(&wifi_scan_config, 0, sizeof(wifi_scan_config));
wifi_scan_config.ssid = (uint8_t*)wlan_config.ssid.c_str(); // only scan for configured SSID
wifi_scan_config.show_hidden = true; // scan also hidden SSIDs
wifi_scan_config.channel = 0; // scan all channels
esp_wifi_scan_start(&wifi_scan_config, true); // not using event handler SCAN_DONE by purpose to keep SYS_EVENT heap smaller
// and the calling task task_autodoFlow is after scan is finish in wait state anyway
// Scan duration: ca. (120ms + 30ms) * Number of channels -> ca. 1,5 - 2s
uint16_t max_number_of_ap_found = 10; // max. number of APs, value will be updated by function "esp_wifi_scan_get_ap_num"
esp_wifi_scan_get_ap_num(&max_number_of_ap_found); // get actual found APs
wifi_ap_record_t* wifi_ap_records = new wifi_ap_record_t[max_number_of_ap_found]; // Allocate necessary record datasets
if (wifi_ap_records == NULL) {
esp_wifi_scan_get_ap_records(0, NULL); // free internal heap
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "wifi_scan: Failed to allocate heap for wifi_ap_records");
return;
}
else {
if (esp_wifi_scan_get_ap_records(&max_number_of_ap_found, wifi_ap_records) != ESP_OK) { // Retrieve results (and free internal heap)
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "wifi_scan: esp_wifi_scan_get_ap_records: Error retrieving datasets");
free(wifi_ap_records);
return;
}
}
wifi_ap_record_t currentAP;
esp_wifi_sta_get_ap_info(&currentAP);
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Roaming: Current AP BSSID=" + BssidToString((char*)currentAP.bssid));
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Roaming: Scan completed, APs found with configured SSID: " + std::to_string(max_number_of_ap_found));
for (int i = 0; i < max_number_of_ap_found; i++) {
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Roaming: " + std::to_string(i+1) +
": SSID=" + std::string((char*)wifi_ap_records[i].ssid) +
", BSSID=" + BssidToString((char*)wifi_ap_records[i].bssid) +
", RSSI=" + std::to_string(wifi_ap_records[i].rssi) +
", CH=" + std::to_string(wifi_ap_records[i].primary) +
", AUTH=" + getAuthModeName(wifi_ap_records[i].authmode));
if (wifi_ap_records[i].rssi > (currentAP.rssi + 5) && // RSSI is better than actual RSSI + 5 --> Avoid switching to AP with roughly same RSSI
(strcmp(BssidToString((char*)wifi_ap_records[i].bssid).c_str(), BssidToString((char*)currentAP.bssid).c_str()) != 0))
{
APWithBetterRSSI = true;
}
}
free(wifi_ap_records);
}
void wifiRoamByScanning(void)
{
if (wlan_config.rssi_threshold != 0 && get_WIFI_RSSI() != -127 && (get_WIFI_RSSI() < wlan_config.rssi_threshold)) {
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Roaming: Start scan of all channels for SSID " + wlan_config.ssid);
wifi_scan();
if (APWithBetterRSSI) {
APWithBetterRSSI = false;
LogFile.WriteToFile(ESP_LOG_WARN, TAG, "Roaming: AP with better RSSI in range, disconnecting to switch AP...");
esp_wifi_disconnect();
}
else {
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Roaming: Scan completed, stay on current AP");
}
}
}
#endif // WLAN_USE_ROAMING_BY_SCANNING
std::string* getIPAddress()
{
return &ipadress;
return &wlan_config.ipaddress;
}
std::string* getSSID()
{
return &ssid;
return &wlan_config.ssid;
}
void task_doBlink(void *pvParameter)
static void event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data)
{
ESP_LOGI("BLINK", "Flash - start");
while (BlinkIsRunning)
{
// ESP_LOGI("BLINK", "Blinken - wait");
vTaskDelay(100 / portTICK_PERIOD_MS);
}
BlinkIsRunning = true;
// Init the GPIO
gpio_pad_select_gpio(BLINK_GPIO);
/* Set the GPIO as a push/pull output */
gpio_set_direction(BLINK_GPIO, GPIO_MODE_OUTPUT);
for (int i = 0; i < BlinkAnzahl; ++i)
{
if (BlinkAnzahl > 1)
{
gpio_set_level(BLINK_GPIO, 1);
vTaskDelay(BlinkDauer / portTICK_PERIOD_MS);
}
gpio_set_level(BLINK_GPIO, 0);
vTaskDelay(BlinkDauer / portTICK_PERIOD_MS);
}
if (BlinkOff)
gpio_set_level(BLINK_GPIO, 1);
ESP_LOGI("BLINK", "Flash - done");
BlinkIsRunning = false;
vTaskDelete(NULL); //Delete this task if it exits from the loop above
}
void LEDBlinkTask(int _dauer, int _anz, bool _off)
{
BlinkDauer = _dauer;
BlinkAnzahl = _anz;
BlinkOff = _off;
xTaskCreate(&task_doBlink, "task_doBlink", 4 * 1024, NULL, tskIDLE_PRIORITY+1, NULL);
}
/////////////////////////////////////////////////////////
static void 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) {
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START)
{
WIFIConnected = false;
LEDBlinkTask(200, 1, true);
esp_wifi_connect();
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
WIFIConnected = false;
// if (s_retry_num < EXAMPLE_ESP_MAXIMUM_RETRY) {
esp_wifi_connect();
s_retry_num++;
ESP_LOGI(TAG, "retrying connection to the AP");
// } else {
// xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
// }
ESP_LOGI(TAG,"connection to the AP failed");
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
ipadress = std::string(ip4addr_ntoa((const ip4_addr*) &event->ip_info.ip));
s_retry_num = 0;
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
LEDBlinkTask(1000, 5, true);
}
else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED)
{
/* Disconnect reason: https://github.com/espressif/esp-idf/blob/d825753387c1a64463779bbd2369e177e5d59a79/components/esp_wifi/include/esp_wifi_types.h */
wifi_event_sta_disconnected_t *disconn = (wifi_event_sta_disconnected_t *)event_data;
if (disconn->reason == WIFI_REASON_ROAMING) {
LogFile.WriteToFile(ESP_LOG_WARN, TAG, "Disconnected (" + std::to_string(disconn->reason) + ", Roaming 802.11kv)");
// --> no reconnect neccessary, it should automatically reconnect to new AP
}
else {
WIFIConnected = false;
if (disconn->reason == WIFI_REASON_NO_AP_FOUND) {
LogFile.WriteToFile(ESP_LOG_WARN, TAG, "Disconnected (" + std::to_string(disconn->reason) + ", No AP)");
StatusLED(WLAN_CONN, 1, false);
}
else if (disconn->reason == WIFI_REASON_AUTH_EXPIRE ||
disconn->reason == WIFI_REASON_AUTH_FAIL ||
disconn->reason == WIFI_REASON_NOT_AUTHED ||
disconn->reason == WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT ||
disconn->reason == WIFI_REASON_HANDSHAKE_TIMEOUT) {
LogFile.WriteToFile(ESP_LOG_WARN, TAG, "Disconnected (" + std::to_string(disconn->reason) + ", Auth fail)");
StatusLED(WLAN_CONN, 2, false);
}
else if (disconn->reason == WIFI_REASON_BEACON_TIMEOUT) {
LogFile.WriteToFile(ESP_LOG_WARN, TAG, "Disconnected (" + std::to_string(disconn->reason) + ", Timeout)");
StatusLED(WLAN_CONN, 3, false);
}
else {
LogFile.WriteToFile(ESP_LOG_WARN, TAG, "Disconnected (" + std::to_string(disconn->reason) + ")");
StatusLED(WLAN_CONN, 4, false);
}
WIFIReconnectCnt++;
esp_wifi_connect(); // Try to connect again
}
if (WIFIReconnectCnt >= 10) {
WIFIReconnectCnt = 0;
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Disconnected, multiple reconnect attempts failed (" +
std::to_string(disconn->reason) + "), still retrying...");
}
}
else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_CONNECTED)
{
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Connected to: " + wlan_config.ssid + ", RSSI: " +
std::to_string(get_WIFI_RSSI()));
#ifdef WLAN_USE_MESH_ROAMING
printRoamingFeatureSupport();
#ifdef WLAN_USE_MESH_ROAMING_ACTIVATE_CLIENT_TRIGGERED_QUERIES
// wifiRoamingQuery(); // Avoid client triggered query during processing flow (reduce risk of heap shortage). Request will be triggered at the end of every round anyway
#endif //WLAN_USE_MESH_ROAMING_ACTIVATE_CLIENT_TRIGGERED_QUERIES
#endif //WLAN_USE_MESH_ROAMING
}
else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)
{
WIFIConnected = true;
#ifdef ENABLE_MQTT
WIFIReconnectCnt = 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
}
#endif //ENABLE_MQTT
}
}
void strinttoip4(const char *ip, int &a, int &b, int &c, int &d) {
std::string zw = std::string(ip);
std::stringstream s(zw);
char ch; //to temporarily store the '.'
s >> a >> ch >> b >> ch >> c >> ch >> d;
}
void wifi_init_sta(const char *_ssid, const char *_password, const char *_hostname, const char *_ipadr, const char *_gw, const char *_netmask, const char *_dns, int _rssithreashold)
esp_err_t wifi_init_sta(void)
{
RSSI_Threshold = _rssithreashold;
s_wifi_event_group = xEventGroupCreate();
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;
}
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
retval = esp_event_loop_create_default();
if (retval != ESP_OK) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "esp_event_loop_create_default: Error: " + std::to_string(retval));
return retval;
}
esp_netif_t *my_sta = esp_netif_create_default_wifi_sta();
if ((_ipadr != NULL) && (_gw != NULL) && (_netmask != NULL))
if (!wlan_config.ipaddress.empty() && !wlan_config.gateway.empty() && !wlan_config.netmask.empty())
{
ESP_LOGI(TAG, "set IP %s, GW %s, Netmask %s manual", _ipadr, _gw, _netmask);
esp_netif_dhcpc_stop(my_sta);
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Manual interface config -> IP: " + wlan_config.ipaddress + ", Gateway: " +
std::string(wlan_config.gateway) + ", Netmask: " + std::string(wlan_config.netmask));
esp_netif_dhcpc_stop(my_sta); // Stop DHCP service
esp_netif_ip_info_t ip_info;
int a, b, c, d;
strinttoip4(_ipadr, a, b, c, d);
IP4_ADDR(&ip_info.ip, a, b, c, d);
strinttoip4(_gw, a, b, c, d);
IP4_ADDR(&ip_info.gw, a, b, c, d);
strinttoip4(_netmask, a, b, c, d);
IP4_ADDR(&ip_info.netmask, a, b, c, d);
strinttoip4(wlan_config.ipaddress.c_str(), a, b, c, d);
IP4_ADDR(&ip_info.ip, a, b, c, d); // Set static IP address
esp_netif_set_ip_info(my_sta, &ip_info);
strinttoip4(wlan_config.gateway.c_str(), a, b, c, d);
IP4_ADDR(&ip_info.gw, a, b, c, d); // Set gateway
strinttoip4(wlan_config.netmask.c_str(), a, b, c, d);
IP4_ADDR(&ip_info.netmask, a, b, c, d); // Set netmask
esp_netif_set_ip_info(my_sta, &ip_info); // Set static IP configuration
}
else {
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Automatic interface config --> Use DHCP service");
}
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
retval = esp_wifi_init(&cfg);
if (retval != ESP_OK) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "esp_wifi_init: Error: " + std::to_string(retval));
return retval;
}
if ((_ipadr != NULL) && (_gw != NULL) && (_netmask != NULL))
if (!wlan_config.ipaddress.empty() && !wlan_config.gateway.empty() && !wlan_config.netmask.empty())
{
if (_dns == NULL)
_dns = _gw;
ESP_LOGI(TAG, "set DNS manual");
if (wlan_config.dns.empty()) {
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "No DNS server, use gateway");
wlan_config.dns = wlan_config.gateway;
}
else {
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Manual interface config -> DNS: " + wlan_config.dns);
}
esp_netif_dns_info_t dns_info;
ip4_addr_t ip;
ip.addr = esp_ip4addr_aton(_dns);
ip.addr = esp_ip4addr_aton(wlan_config.dns.c_str());
ip_addr_set_ip4_u32(&dns_info.ip, ip.addr);
ESP_ERROR_CHECK(esp_netif_set_dns_info(my_sta, ESP_NETIF_DNS_MAIN, &dns_info));
}
esp_event_handler_instance_t instance_any_id;
esp_event_handler_instance_t instance_got_ip;
esp_event_handler_instance_t instance_bss_rssi_low;
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
ESP_EVENT_ANY_ID,
&event_handler,
NULL,
&instance_any_id));
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
IP_EVENT_STA_GOT_IP,
&event_handler,
NULL,
&instance_got_ip));
retval = esp_netif_set_dns_info(my_sta, ESP_NETIF_DNS_MAIN, &dns_info);
if (retval != ESP_OK) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "esp_netif_set_dns_info: Error: " + std::to_string(retval));
return retval;
}
}
retval = esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID,
&event_handler, NULL, NULL);
if (retval != ESP_OK) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "esp_event_handler_instance_register - WIFI_ANY: Error: " + std::to_string(retval));
return retval;
}
retval = esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP,
&event_handler, NULL, NULL);
if (retval != ESP_OK) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "esp_event_handler_instance_register - GOT_IP: Error: " + std::to_string(retval));
return retval;
}
#ifdef WLAN_USE_MESH_ROAMING
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
WIFI_EVENT_STA_BSS_RSSI_LOW,
&esp_bss_rssi_low_handler,
NULL,
&instance_bss_rssi_low));
retval = esp_event_handler_instance_register(WIFI_EVENT, WIFI_EVENT_STA_BSS_RSSI_LOW,
&esp_bss_rssi_low_handler, NULL, NULL);
if (retval != ESP_OK) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "esp_event_handler_instance_register - BSS_RSSI_LOW: Error: " + std::to_string(retval));
return retval;
}
#endif
wifi_config_t wifi_config = { };
strcpy((char*)wifi_config.sta.ssid, (const char*)_ssid);
strcpy((char*)wifi_config.sta.password, (const char*)_password);
wifi_config.sta.scan_method = WIFI_ALL_CHANNEL_SCAN; // Scan all channels instead of stopping after first match
wifi_config.sta.sort_method = WIFI_CONNECT_AP_BY_SIGNAL; // Sort by signal strength and keep up to 4 best APs
//wifi_config.sta.failure_retry_cnt = 3; // IDF version 5.0 will support this
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config) );
ESP_ERROR_CHECK(esp_wifi_start() );
#ifdef WLAN_USE_MESH_ROAMING
wifi_config.sta.rm_enabled = 1; // 802.11k (Radio Resource Management)
wifi_config.sta.btm_enabled = 1; // 802.11v (BSS Transition Management)
//wifi_config.sta.mbo_enabled = 1; // Multiband Operation (better use of Wi-Fi network resources in roaming decisions) -> not activated to save heap
wifi_config.sta.pmf_cfg.capable = 1; // 802.11w (Protected Management Frame, activated by default if other device also advertizes PMF capability)
//wifi_config.sta.ft_enabled = 1; // 802.11r (BSS Fast Transition) -> Upcoming IDF version 5.0 will support 11r
#endif
if (_hostname != NULL)
strcpy((char*)wifi_config.sta.ssid, (const char*)wlan_config.ssid.c_str());
strcpy((char*)wifi_config.sta.password, (const char*)wlan_config.password.c_str());
retval = esp_wifi_set_mode(WIFI_MODE_STA);
if (retval != ESP_OK) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "esp_wifi_set_mode: Error: " + std::to_string(retval));
return retval;
}
retval = esp_wifi_set_config(WIFI_IF_STA, &wifi_config);
if (retval != ESP_OK) {
if (retval == ESP_ERR_WIFI_PASSWORD) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "esp_wifi_set_config: SSID password invalid! Error: " + std::to_string(retval));
}
else {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "esp_wifi_set_config: Error: " + std::to_string(retval));
}
return retval;
}
retval = esp_wifi_start();
if (retval != ESP_OK) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "esp_wifi_start: Error: " + std::to_string(retval));
return retval;
}
if (!wlan_config.hostname.empty())
{
esp_err_t ret = tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_STA , _hostname);
hostname = std::string(_hostname);
if(ret != ESP_OK ){
ESP_LOGE(TAG,"Failed to set hostname: %d",ret);
retval = tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_STA , wlan_config.hostname.c_str());
if(retval != ESP_OK ) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to set hostname! Error: " + std::to_string(retval));
}
else {
ESP_LOGI(TAG,"Set hostname to: %s", _hostname);
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Set hostname to: " + wlan_config.hostname);
}
}
ESP_LOGI(TAG, "wifi_init_sta finished.");
/* Waiting until either the connection is established (WIFI_CONNECTED_BIT) or connection failed for the maximum
* number of re-tries (WIFI_FAIL_BIT). The bits are set by event_handler() (see above) */
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
pdFALSE,
pdFALSE,
portMAX_DELAY);
/* xEventGroupWaitBits() returns the bits before the call returned, hence we can test which event actually
* happened. */
if (bits & WIFI_CONNECTED_BIT) {
#ifdef __HIDE_PASSWORD
ESP_LOGI(TAG, "Connected with AP: %s, password: XXXXXXX", _ssid);
#else
ESP_LOGI(TAG, "Connected with AP: %s, password: %s", _ssid, _password);
#endif
} else if (bits & WIFI_FAIL_BIT) {
#ifdef __HIDE_PASSWORD
ESP_LOGI(TAG, "Failed to connect with AP: %s, password: XXXXXXXX", _ssid);
#else
ESP_LOGI(TAG, "Failed to connect with AP: %s, password: %s", _ssid, _password);
#endif
} else {
ESP_LOGE(TAG, "UNEXPECTED EVENT");
}
ssid = std::string(_ssid);
/* The event will not be processed after unregister */
// ESP_ERROR_CHECK(esp_event_handler_instance_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, instance_got_ip));
// ESP_ERROR_CHECK(esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, instance_any_id));
// vEventGroupDelete(s_wifi_event_group);
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Init successful");
return ESP_OK;
}
int get_WIFI_RSSI()
{
wifi_ap_record_t ap;
esp_wifi_sta_get_ap_info(&ap);
return ap.rssi;
}
void wifi_init_sta(const char *_ssid, const char *_password, const char *_hostname)
{
wifi_init_sta(_ssid, _password, _hostname, NULL, NULL, NULL, NULL, 0);
}
void wifi_init_sta(const char *_ssid, const char *_password)
{
wifi_init_sta(_ssid, _password, NULL, NULL, NULL, NULL, NULL, 0);
wifi_ap_record_t ap;
if (esp_wifi_sta_get_ap_info(&ap) == ESP_OK)
return ap.rssi;
else
return -127; // Return -127 if no info available e.g. not connected
}
@@ -556,12 +668,13 @@ bool getWIFIisConnected()
void WIFIDestroy()
{
esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, event_handler);
esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, event_handler);
esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, event_handler);
#ifdef WLAN_USE_MESH_ROAMING
esp_event_handler_unregister(WIFI_EVENT, WIFI_EVENT_STA_BSS_RSSI_LOW, esp_bss_rssi_low_handler);
#endif
esp_wifi_disconnect();
esp_wifi_stop();
esp_wifi_deinit();
}

View File

@@ -5,18 +5,19 @@
#include <string>
void wifi_init_sta(const char *_ssid, const char *_password, const char *_hostname, const char *_ipadr, const char *_gw, const char *_netmask, const char *_dns, int _rssithreashold);
void wifi_init_sta(const char *_ssid, const char *_password, const char *_hostname);
void wifi_init_sta(const char *_ssid, const char *_password);
int wifi_init_sta(void);
std::string* getIPAddress();
std::string* getSSID();
int get_WIFI_RSSI();
bool getWIFIisConnected();
void WIFIDestroy();
extern std::string hostname;
extern std::string std_hostname;
extern int RSSIThreashold;
#if (defined WLAN_USE_MESH_ROAMING && defined WLAN_USE_MESH_ROAMING_ACTIVATE_CLIENT_TRIGGERED_QUERIES)
void wifiRoamingQuery(void);
#endif
#ifdef WLAN_USE_ROAMING_BY_SCANNING
void wifiRoamByScanning(void);
#endif
#endif //CONNECT_WLAN_H

View File

@@ -11,9 +11,14 @@
#include <iostream>
#include <string.h>
#include "esp_log.h"
#include "ClassLogFile.h"
#include "../../include/defines.h"
static const char *TAG = "WLAN.INI";
static const char *TAG = "WLANINI";
struct wlan_config wlan_config = {};
std::vector<string> ZerlegeZeileWLAN(std::string input, std::string _delimiter = "")
{
@@ -40,207 +45,189 @@ std::vector<string> ZerlegeZeileWLAN(std::string input, std::string _delimiter =
}
bool LoadWlanFromFile(std::string fn, char *&_ssid, char *&_password, char *&_hostname, char *&_ipadr, char *&_gw, char *&_netmask, char *&_dns, int &_rssithreashold)
int LoadWlanFromFile(std::string fn)
{
std::string ssid = "";
std::string passphrase = "";
std::string ipaddress = "";
std::string gw = "";
std::string netmask = "";
std::string dns = "";
int rssithreshold = 0;
std::string line = "";
std::string tmp = "";
std::vector<string> splitted;
hostname = std_hostname;
FILE* pFile;
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;
}
pFile = fopen(fn.c_str(), "r");
if (!pFile)
return false;
ESP_LOGD(TAG, "LoadWlanFromFile: wlan.ini opened");
ESP_LOGD(TAG, "file loaded");
if (pFile == NULL)
return false;
char zw[1024];
fgets(zw, 1024, pFile);
line = std::string(zw);
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, "%s", line.c_str());
splitted = ZerlegeZeileWLAN(line, "=");
splitted[0] = trim(splitted[0], " ");
//ESP_LOGD(TAG, "line: %s", line.c_str());
if (line[0] != ';') { // Skip lines which starts with ';'
if ((splitted.size() > 1) && (toUpper(splitted[0]) == "HOSTNAME")){
hostname = trim(splitted[1]);
if ((hostname[0] == '"') && (hostname[hostname.length()-1] == '"')){
hostname = hostname.substr(1, hostname.length()-2);
splitted = ZerlegeZeileWLAN(line, "=");
splitted[0] = trim(splitted[0], " ");
if ((splitted.size() > 1) && (toUpper(splitted[0]) == "SSID")){
tmp = trim(splitted[1]);
if ((tmp[0] == '"') && (tmp[tmp.length()-1] == '"')){
tmp = tmp.substr(1, tmp.length()-2);
}
wlan_config.ssid = tmp;
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "SSID: " + wlan_config.ssid);
}
else if ((splitted.size() > 1) && (toUpper(splitted[0]) == "PASSWORD")){
tmp = splitted[1];
if ((tmp[0] == '"') && (tmp[tmp.length()-1] == '"')){
tmp = tmp.substr(1, tmp.length()-2);
}
wlan_config.password = tmp;
#ifndef __HIDE_PASSWORD
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Password: " + wlan_config.password);
#else
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Password: XXXXXXXX");
#endif
}
else 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);
}
#if (defined WLAN_USE_ROAMING_BY_SCANNING || (defined WLAN_USE_MESH_ROAMING && defined WLAN_USE_MESH_ROAMING_ACTIVATE_CLIENT_TRIGGERED_QUERIES))
else if ((splitted.size() > 1) && (toUpper(splitted[0]) == "RSSITHRESHOLD")){
tmp = trim(splitted[1]);
if ((tmp[0] == '"') && (tmp[tmp.length()-1] == '"')){
tmp = tmp.substr(1, tmp.length()-2);
}
wlan_config.rssi_threshold = atoi(tmp.c_str());
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "RSSIThreshold: " + std::to_string(wlan_config.rssi_threshold));
}
#endif
}
if ((splitted.size() > 1) && (toUpper(splitted[0]) == "SSID")){
ssid = trim(splitted[1]);
if ((ssid[0] == '"') && (ssid[ssid.length()-1] == '"')){
ssid = ssid.substr(1, ssid.length()-2);
}
}
if ((splitted.size() > 1) && (toUpper(splitted[0]) == "RSSITHREASHOLD")){
string _s = trim(splitted[1]);
if ((_s[0] == '"') && (_s[_s.length()-1] == '"')){
_s = _s.substr(1, ssid.length()-2);
}
rssithreshold = atoi(_s.c_str());
}
if ((splitted.size() > 1) && (toUpper(splitted[0]) == "PASSWORD")){
passphrase = splitted[1];
if ((passphrase[0] == '"') && (passphrase[passphrase.length()-1] == '"')){
passphrase = passphrase.substr(1, passphrase.length()-2);
}
}
if ((splitted.size() > 1) && (toUpper(splitted[0]) == "IP")){
ipaddress = splitted[1];
if ((ipaddress[0] == '"') && (ipaddress[ipaddress.length()-1] == '"')){
ipaddress = ipaddress.substr(1, ipaddress.length()-2);
}
}
if ((splitted.size() > 1) && (toUpper(splitted[0]) == "GATEWAY")){
gw = splitted[1];
if ((gw[0] == '"') && (gw[gw.length()-1] == '"')){
gw = gw.substr(1, gw.length()-2);
}
}
if ((splitted.size() > 1) && (toUpper(splitted[0]) == "NETMASK")){
netmask = splitted[1];
if ((netmask[0] == '"') && (netmask[netmask.length()-1] == '"')){
netmask = netmask.substr(1, netmask.length()-2);
}
}
if ((splitted.size() > 1) && (toUpper(splitted[0]) == "DNS")){
dns = splitted[1];
if ((dns[0] == '"') && (dns[dns.length()-1] == '"')){
dns = dns.substr(1, dns.length()-2);
}
}
if (fgets(zw, 1024, pFile) == NULL)
{
/* read next line */
if (fgets(zw, sizeof(zw), pFile) == NULL) {
line = "";
}
else
{
else {
line = std::string(zw);
}
}
fclose(pFile);
// Check if Hostname was empty in .ini if yes set to std_hostname
if(hostname.length() == 0){
hostname = std_hostname;
/* Check if SSID is empty (mandatory parameter) */
if (wlan_config.ssid.empty()) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "SSID empty. Device init aborted!");
return -2;
}
_hostname = new char[hostname.length() + 1];
strcpy(_hostname, hostname.c_str());
_ssid = new char[ssid.length() + 1];
strcpy(_ssid, ssid.c_str());
_password = new char[passphrase.length() + 1];
strcpy(_password, passphrase.c_str());
if (ipaddress.length() > 0)
{
_ipadr = new char[ipaddress.length() + 1];
strcpy(_ipadr, ipaddress.c_str());
/* Check if password is empty (mandatory parameter) */
if (wlan_config.password.empty()) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Password empty. Device init aborted!");
return -2;
}
else
_ipadr = NULL;
if (gw.length() > 0)
{
_gw = new char[gw.length() + 1];
strcpy(_gw, gw.c_str());
}
else
_gw = NULL;
if (netmask.length() > 0)
{
_netmask = new char[netmask.length() + 1];
strcpy(_netmask, netmask.c_str());
}
else
_netmask = NULL;
if (dns.length() > 0)
{
_dns = new char[dns.length() + 1];
strcpy(_dns, dns.c_str());
}
else
_dns = NULL;
_rssithreashold = rssithreshold;
RSSIThreashold = rssithreshold;
return true;
return 0;
}
bool ChangeHostName(std::string fn, std::string _newhostname)
{
if (_newhostname == hostname)
if (_newhostname == wlan_config.hostname)
return false;
string line = "";
std::string line = "";
std::vector<string> splitted;
std::vector<string> neuesfile;
bool found = false;
std::vector<string> neuesfile;
FILE* pFile = NULL;
FILE* pFile;
fn = FormatFileName(fn);
pFile = fopen(fn.c_str(), "r");
ESP_LOGD(TAG, "file loaded\n");
if (pFile == NULL)
if (pFile == NULL) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "ChangeHostName: Unable to open file wlan.ini (read)");
return false;
}
char zw[1024];
fgets(zw, 1024, pFile);
line = std::string(zw);
ESP_LOGD(TAG, "ChangeHostName: wlan.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, "%s", line.c_str());
//ESP_LOGD(TAG, "ChangeHostName: line: %s", line.c_str());
splitted = ZerlegeZeileWLAN(line, "=");
splitted[0] = trim(splitted[0], " ");
if ((splitted.size() > 1) && (toUpper(splitted[0]) == "HOSTNAME")){
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, 1024, pFile) == NULL)
if (fgets(zw, sizeof(zw), pFile) == NULL)
{
line = "";
}
@@ -252,52 +239,64 @@ bool ChangeHostName(std::string fn, std::string _newhostname)
if (!found)
{
line = "\nhostname = \"" + _newhostname + "\"\n";
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());
//ESP_LOGD(TAG, "%s", neuesfile[i].c_str());
fputs(neuesfile[i].c_str(), pFile);
}
fclose(pFile);
ESP_LOGD(TAG, "*** Hostname update done ***");
ESP_LOGD(TAG, "ChangeHostName done");
return true;
}
bool ChangeRSSIThreashold(std::string fn, int _newrssithreashold)
#if (defined WLAN_USE_ROAMING_BY_SCANNING || (defined WLAN_USE_MESH_ROAMING && defined WLAN_USE_MESH_ROAMING_ACTIVATE_CLIENT_TRIGGERED_QUERIES))
bool ChangeRSSIThreshold(std::string fn, int _newrssithreshold)
{
if (RSSIThreashold == _newrssithreashold)
if (wlan_config.rssi_threshold == _newrssithreshold)
return false;
string line = "";
std::string line = "";
std::vector<string> splitted;
std::vector<string> neuesfile;
bool found = false;
std::vector<string> neuesfile;
FILE* pFile = NULL;
FILE* pFile;
fn = FormatFileName(fn);
pFile = fopen(fn.c_str(), "r");
ESP_LOGD(TAG, "file loaded\n");
if (pFile == NULL)
if (pFile == NULL) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "ChangeRSSIThreshold: Unable to open file wlan.ini (read)");
return false;
}
char zw[1024];
fgets(zw, 1024, pFile);
line = std::string(zw);
ESP_LOGD(TAG, "ChangeRSSIThreshold: wlan.ini opened");
char zw[256];
if (fgets(zw, sizeof(zw), pFile) == NULL) {
line = "";
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "ChangeRSSIThreshold: File opened, but empty or content not readable");
return false;
}
else {
line = std::string(zw);
}
while ((line.size() > 0) || !(feof(pFile)))
{
@@ -305,43 +304,68 @@ bool ChangeRSSIThreashold(std::string fn, int _newrssithreashold)
splitted = ZerlegeZeileWLAN(line, "=");
splitted[0] = trim(splitted[0], " ");
if ((splitted.size() > 1) && (toUpper(splitted[0]) == "RSSITHREASHOLD")){
line = "RSSIThreashold = " + to_string(_newrssithreashold) + "\n";
/* Workaround to eliminate line with typo "RSSIThreashold" or "rssi" if existing */
if (((splitted.size() > 1) && (toUpper(splitted[0]) == "RSSITHREASHOLD")) ||
((splitted.size() > 1) && (toUpper(splitted[0]) == ";RSSITHREASHOLD")) ||
((splitted.size() > 1) && (toUpper(splitted[0]) == "RSSI")) ||
((splitted.size() > 1) && (toUpper(splitted[0]) == ";RSSI"))) {
if (fgets(zw, sizeof(zw), pFile) == NULL) {
line = "";
}
else {
line = std::string(zw);
}
continue;
}
if ((splitted.size() > 1) && ((toUpper(splitted[0]) == "RSSITHRESHOLD") || (toUpper(splitted[0]) == ";RSSITHRESHOLD"))) {
line = "RSSIThreshold = " + to_string(_newrssithreshold) + "\n";
found = true;
}
neuesfile.push_back(line);
if (fgets(zw, 1024, pFile) == NULL)
{
if (fgets(zw, sizeof(zw), pFile) == NULL) {
line = "";
}
else
{
else {
line = std::string(zw);
}
}
if (!found)
{
line = "RSSIThreashold = " + to_string(_newrssithreashold) + "\n";
line = "\n;++++++++++++++++++++++++++++++++++\n";
line += "; WIFI Roaming:\n";
line += "; Network assisted roaming protocol is activated by default\n";
line += "; AP / mesh system needs to support roaming protocol 802.11k/v\n";
line += ";\n";
line += "; Optional feature (usually not neccessary):\n";
line += "; RSSI Threshold for client requested roaming query (RSSI < RSSIThreshold)\n";
line += "; Note: This parameter can be configured via WebUI configuration\n";
line += "; Default: 0 = Disable client requested roaming query\n\n";
line += "RSSIThreshold = " + to_string(_newrssithreshold) + "\n";
neuesfile.push_back(line);
}
fclose(pFile);
pFile = fopen(fn.c_str(), "w+");
if (pFile == NULL) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "ChangeRSSIThreshold: 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());
//ESP_LOGD(TAG, "%s", neuesfile[i].c_str());
fputs(neuesfile[i].c_str(), pFile);
}
fclose(pFile);
ESP_LOGD(TAG, "*** RSSIThreashold update done ***");
ESP_LOGD(TAG, "ChangeRSSIThreshold done");
return true;
}
#endif

View File

@@ -5,10 +5,22 @@
#include <string>
bool LoadWlanFromFile(std::string fn, char *&_ssid, char *&_password, char *&_hostname, char *&_ipadr, char *&_gw, char *&_netmask, char *&_dns, int &_rssithreashold);
struct wlan_config {
std::string ssid = "";
std::string password = "";
std::string hostname = "watermeter"; // Default: watermeter
std::string ipaddress = "";
std::string gateway = "";
std::string netmask = "";
std::string dns = "";
int rssi_threshold = 0; // Default: 0 -> ROAMING disabled
};
extern struct wlan_config wlan_config;
int LoadWlanFromFile(std::string fn);
bool ChangeHostName(std::string fn, std::string _newhostname);
bool ChangeRSSIThreashold(std::string fn, int _newrssithreashold);
bool ChangeRSSIThreshold(std::string fn, int _newrssithreshold);
#endif //READ_WLANINI_H

View File

@@ -21,7 +21,9 @@
// use himem //https://github.com/jomjol/AI-on-the-edge-device/issues/1842
#define USE_HIMEM_IF_AVAILABLE
#if (CONFIG_SPIRAM_BANKSWITCH_ENABLE)
#define USE_HIMEM_IF_AVAILABLE 1
#endif
/* Uncomment this to generate task list with stack sizes using the /heap handler
PLEASE BE AWARE: The following CONFIG parameters have to to be set in
@@ -56,11 +58,11 @@
//#define CONFIG_IDF_TARGET_ARCH_XTENSA //not needed with platformio/espressif32 @ 5.2.0
//ClassControllCamera + ClassFlowMakeImage + connect_wlan + main
//ClassControllCamera + ClassFlowTakeImage + connect_wlan + main
#define FLASH_GPIO GPIO_NUM_4
#define BLINK_GPIO GPIO_NUM_33
//ClassFlowMQTT + interface_mqtt + connect_wlan + main
//interface_mqtt + read_wlanini
#define __HIDE_PASSWORD
//ClassControllCamera
@@ -69,12 +71,13 @@
//server_GPIO
#define __LEDGLOBAL
//ClassControllCamera + ClassFlowMakeImage
//ClassControllCamera + ClassFlowTakeImage
#define CAMERA_MODEL_AI_THINKER
#define BOARD_ESP32CAM_AITHINKER
//server_GPIO + server_file + SoftAP
#define CONFIG_FILE "/sdcard/config/config.ini"
#define CONFIG_FILE_BACKUP "/sdcard/config/config.bak"
//ClassFlowControll + Main + SoftAP
#define WLAN_CONFIG_FILE "/sdcard/wlan.ini"
@@ -116,7 +119,6 @@
//ClassFlowControll: Serve alg_roi.jpg from memory as JPG
#define ALGROI_LOAD_FROM_MEM_AS_JPG // Load ALG_ROI.JPG as rendered JPG from RAM
#define ALGROI_LOAD_FROM_MEM_AS_JPG__SHOW_TAKE_IMAGE_PROCESS // Show take image image processing on webinterface (overview.html)
//ClassFlowMQTT
#define LWT_TOPIC "connection"
@@ -129,14 +131,8 @@
//CImageBasis
#define HTTP_BUFFER_SENT 1024
#define GET_MEMORY(X) heap_caps_malloc(X, MALLOC_CAP_SPIRAM)
#define MAX_JPG_SIZE 128000
//CAlignAndCutImage + CImageBasis
#define _USE_MATH_DEFINES
#define GET_MEMORY(X) heap_caps_malloc(X, MALLOC_CAP_SPIRAM)
//make_stb + stb_image_resize + stb_image_write + stb_image //do not work if not in make_stb.cpp
//#define STB_IMAGE_IMPLEMENTATION
//#define STB_IMAGE_WRITE_IMPLEMENTATION
@@ -159,15 +155,23 @@
}
#define SUPRESS_TFLITE_ERRORS // use, to avoid error messages from TFLITE
//connect_wlan
#define WLAN_USE_MESH_ROAMING
#define WLAN_WIFI_RSSI_THRESHOLD -50
#define EXAMPLE_ESP_MAXIMUM_RETRY 1000
/* The event group allows multiple bits for each event, but we only care about two events:
* - we are connected to the AP with an IP
* - we failed to connect after the maximum amount of retries */
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
// connect_wlan.cpp
//******************************
/* WIFI roaming functionalities 802.11k+v (uses ca. 6kB - 8kB internal RAM; if SCAN CACHE activated: + 1kB / beacon)
PLEASE BE AWARE: The following CONFIG parameters have to to be set in
sdkconfig.defaults before use of this function is possible!!
CONFIG_WPA_11KV_SUPPORT=y
CONFIG_WPA_SCAN_CACHE=n
CONFIG_WPA_MBO_SUPPORT=n
CONFIG_WPA_11R_SUPPORT=n
*/
//#define WLAN_USE_MESH_ROAMING // 802.11v (BSS Transition Management) + 802.11k (Radio Resource Management) (ca. 6kB - 8kB internal RAM neccessary)
//#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
//ClassFlowCNNGeneral
#define Analog_error 3

View File

@@ -1,4 +1,8 @@
//#include <string>
#include <iostream>
#include <string>
#include <vector>
#include <regex>
//#include "freertos/FreeRTOS.h"
//#include "freertos/task.h"
//#include "freertos/event_groups.h"
@@ -8,6 +12,7 @@
//#include "esp_psram.h" // Comming in IDF 5.0, see https://docs.espressif.com/projects/esp-idf/en/v5.0-beta1/esp32/migration-guides/release-5.x/system.html?highlight=esp_psram_get_size
//#include "spiram.h"
#include "esp32/spiram.h"
#include "esp_pm.h"
// SD-Card ////////////////////
@@ -29,13 +34,17 @@
#include "server_file.h"
#include "server_ota.h"
#include "time_sntp.h"
#include "configFile.h"
//#include "ClassControllCamera.h"
#include "server_main.h"
#include "server_camera.h"
#ifdef ENABLE_MQTT
#include "server_mqtt.h"
#endif //ENABLE_MQTT
//#include "Helper.h"
#include "Helper.h"
#include "statusled.h"
#include "sdcard_check.h"
#include "../../include/defines.h"
//#include "server_GPIO.h"
@@ -78,8 +87,13 @@ extern std::string getFwVersion(void);
extern std::string getHTMLversion(void);
extern std::string getHTMLcommit(void);
std::vector<std::string> splitString(const std::string& str);
void migrateConfiguration(void);
bool setCpuFrequency(void);
static const char *TAG = "MAIN";
bool Init_NVS_SDCard()
{
esp_err_t ret = nvs_flash_init();
@@ -87,9 +101,8 @@ bool Init_NVS_SDCard()
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
////////////////////////////////////////////////
ESP_LOGI(TAG, "Using SDMMC peripheral");
ESP_LOGD(TAG, "Using SDMMC peripheral");
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
// This initializes the slot without card detect (CD) and write protect (WP) signals.
@@ -97,20 +110,19 @@ bool Init_NVS_SDCard()
sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
// To use 1-line SD mode, uncomment the following line:
#ifdef __SD_USE_ONE_LINE_MODE__
slot_config.width = 1;
#endif
#ifdef __SD_USE_ONE_LINE_MODE__
slot_config.width = 1;
#endif
// GPIOs 15, 2, 4, 12, 13 should have external 10k pull-ups.
// Internal pull-ups are not sufficient. However, enabling internal pull-ups
// does make a difference some boards, so we do that here.
gpio_set_pull_mode(GPIO_NUM_15, GPIO_PULLUP_ONLY); // CMD, needed in 4- and 1- line modes
gpio_set_pull_mode(GPIO_NUM_2, GPIO_PULLUP_ONLY); // D0, needed in 4- and 1-line modes
#ifndef __SD_USE_ONE_LINE_MODE__
gpio_set_pull_mode(GPIO_NUM_4, GPIO_PULLUP_ONLY); // D1, needed in 4-line mode only
gpio_set_pull_mode(GPIO_NUM_12, GPIO_PULLUP_ONLY); // D2, needed in 4-line mode only
#endif
#ifndef __SD_USE_ONE_LINE_MODE__
gpio_set_pull_mode(GPIO_NUM_4, GPIO_PULLUP_ONLY); // D1, needed in 4-line mode only
gpio_set_pull_mode(GPIO_NUM_12, GPIO_PULLUP_ONLY); // D2, needed in 4-line mode only
#endif
gpio_set_pull_mode(GPIO_NUM_13, GPIO_PULLUP_ONLY); // D3, needed in 4- and 1-line modes
// Options for mounting the filesystem.
@@ -122,244 +134,330 @@ bool Init_NVS_SDCard()
.allocation_unit_size = 16 * 1024
};
sdmmc_card_t* card;
// Use settings defined above to initialize SD card and mount FAT filesystem.
// Note: esp_vfs_fat_sdmmc_mount is an all-in-one convenience function.
// Please check its source code and implement error recovery when developing
// production applications.
sdmmc_card_t* card;
ret = esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, &card);
if (ret != ESP_OK) {
if (ret == ESP_FAIL) {
ESP_LOGE(TAG, "Failed to mount filesystem. "
"If you want the card to be formatted, set format_if_mount_failed = true.");
} else {
ESP_LOGE(TAG, "Failed to initialize the card (%s). "
"Make sure SD card lines have pull-up resistors in place.", esp_err_to_name(ret));
ESP_LOGE(TAG, "Failed to mount FAT filesystem on SD card. Check SD card filesystem (only FAT supported) or try another card");
StatusLED(SDCARD_INIT, 1, true);
}
else if (ret == 263) { // Error code: 0x107 --> usually: SD not found
ESP_LOGE(TAG, "SD card init failed. Check if SD card is properly inserted into SD card slot or try another card");
StatusLED(SDCARD_INIT, 2, true);
}
else {
ESP_LOGE(TAG, "SD card init failed. Check error code or try another card");
StatusLED(SDCARD_INIT, 3, true);
}
return false;
}
sdmmc_card_print_info(stdout, card);
//sdmmc_card_print_info(stdout, card); // With activated CONFIG_NEWLIB_NANO_FORMAT --> capacity not printed correctly anymore
SaveSDCardInfo(card);
return true;
}
void task_MainInitError_blink(void *pvParameter)
{
gpio_pad_select_gpio(BLINK_GPIO);
gpio_set_direction(BLINK_GPIO, GPIO_MODE_OUTPUT);
TickType_t xDelay;
xDelay = 100 / portTICK_PERIOD_MS;
ESP_LOGD(TAG, "SD-Card could not be inialized - STOP THE PROGRAMM HERE");
while (1)
{
gpio_set_level(BLINK_GPIO, 1);
vTaskDelay( xDelay );
gpio_set_level(BLINK_GPIO, 0);
vTaskDelay( xDelay );
}
vTaskDelete(NULL); //Delete this task if it exits from the loop above
}
extern "C" void app_main(void)
{
//#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
//#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
#endif
#ifdef DISABLE_BROWNOUT_DETECTOR
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector
#endif
ESP_LOGI(TAG, "\n\n\n\n\n"); // Add mark on log to see when it restarted
#ifdef HEAP_TRACING_MAIN_START
ESP_ERROR_CHECK( heap_trace_start(HEAP_TRACE_LEAKS) );
#endif
// ********************************************
// Highlight start of app_main
// ********************************************
ESP_LOGI(TAG, "\n\n\n\n================ Start app_main =================");
// Init camera
// ********************************************
PowerResetCamera();
esp_err_t camStatus = Camera.InitCam();
Camera.LightOnOff(false);
xDelay = 2000 / portTICK_PERIOD_MS;
ESP_LOGD(TAG, "After camera initialization: sleep for: %ldms", (long) xDelay);
vTaskDelay( xDelay );
xDelay = 2000 / portTICK_PERIOD_MS;
ESP_LOGD(TAG, "After camera initialization: sleep for: %ldms", (long) xDelay * CONFIG_FREERTOS_HZ/portTICK_PERIOD_MS);
vTaskDelay( xDelay );
// Init SD card
// ********************************************
if (!Init_NVS_SDCard())
{
xTaskCreate(&task_MainInitError_blink, "task_MainInitError_blink", configMINIMAL_STACK_SIZE * 64, NULL, tskIDLE_PRIORITY+1, NULL);
return; // No way to continue without SD-Card!
ESP_LOGE(TAG, "Device init aborted!");
return; // No way to continue without working SD card!
}
setupTime();
// SD card: Create log directories (if not already existing)
// ********************************************
LogFile.CreateLogDirectories(); // mandatory for logging + image saving
string versionFormated = getFwVersion() + ", Date/Time: " + std::string(BUILD_TIME) + \
// ********************************************
// Highlight start of logfile logging
// Default Log Level: INFO -> Everything which needs to be logged during boot should be have level INFO, WARN OR ERROR
// ********************************************
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "=================================================");
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
}
// Migrate parameter in config.ini to new naming (firmware 15.0 and newer)
// ********************************************
migrateConfiguration();
// Init time (as early as possible, but SD card needs to be initialized)
// ********************************************
setupTime(); // NTP time service: Status of time synchronization will be checked after every round (server_tflite.cpp)
// Set CPU Frequency
// ********************************************
setCpuFrequency();
// SD card: Create further mandatory directories (if not already existing)
// Correct creation of these folders will be checked with function "SDCardCheckFolderFilePresence"
// ********************************************
MakeDir("/sdcard/firmware"); // mandatory for OTA firmware update
MakeDir("/sdcard/img_tmp"); // mandatory for setting up alignment marks
MakeDir("/sdcard/demo"); // mandatory for demo mode
// Check for updates
// ********************************************
CheckOTAUpdate();
CheckUpdate();
// Start SoftAP for initial remote setup
// Note: Start AP if no wlan.ini and/or config.ini available, e.g. SD card empty; function does not exit anymore until reboot
// ********************************************
#ifdef ENABLE_SOFTAP
CheckStartAPMode();
#endif
// SD card: Check presence of some mandatory folders / files
// ********************************************
if (!SDCardCheckFolderFilePresence()) {
StatusLED(SDCARD_CHECK, 4, true);
setSystemStatusFlag(SYSTEM_STATUS_FOLDER_CHECK_BAD); // reduced web interface going to be loaded
}
// Check version information
// ********************************************
std::string versionFormated = getFwVersion() + ", Date/Time: " + std::string(BUILD_TIME) + \
", Web UI: " + getHTMLversion();
if (std::string(GIT_TAG) != "") { // We are on a tag, add it as prefix
string versionFormated = "Tag: '" + std::string(GIT_TAG) + "', " + versionFormated;
versionFormated = "Tag: '" + std::string(GIT_TAG) + "', " + versionFormated;
}
LogFile.CreateLogDirectories();
MakeDir("/sdcard/demo"); // needed for demo mode
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "=================================================");
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "==================== Startup ====================");
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "=================================================");
LogFile.WriteToFile(ESP_LOG_INFO, TAG, versionFormated);
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Reset reason: " + getResetReason());
#ifdef DEBUG_ENABLE_SYSINFO
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL( 4, 0, 0 )
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Device Info : " + get_device_info() );
ESP_LOGD(TAG, "Device infos %s", get_device_info().c_str());
#endif
#endif //DEBUG_ENABLE_SYSINFO
#ifdef USE_HIMEM_IF_AVAILABLE
#ifdef DEBUG_HIMEM_MEMORY_CHECK
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Himem mem check : " + himem_memory_check() );
ESP_LOGD(TAG, "Himem mem check %s", himem_memory_check().c_str());
#endif
#endif
CheckIsPlannedReboot();
CheckOTAUpdate();
CheckUpdate();
#ifdef ENABLE_SOFTAP
CheckStartAPMode(); // if no wlan.ini and/or config.ini --> AP ist startet and this function does not exit anymore until reboot
#endif
#ifdef HEAP_TRACING_MAIN_WIFI
ESP_ERROR_CHECK( heap_trace_start(HEAP_TRACE_LEAKS) );
#endif
char *ssid = NULL, *passwd = NULL, *hostname = NULL, *ip = NULL, *gateway = NULL, *netmask = NULL, *dns = NULL; int rssithreashold = 0;
LoadWlanFromFile(WLAN_CONFIG_FILE, ssid, passwd, hostname, ip, gateway, netmask, dns, rssithreashold);
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "WLAN-Settings - RSSI-Threashold: " + to_string(rssithreashold));
if (ssid != NULL && passwd != NULL)
#ifdef __HIDE_PASSWORD
ESP_LOGD(TAG, "WLan: %s, XXXXXX", ssid);
#else
ESP_LOGD(TAG, "WLan: %s, %s", ssid, passwd);
#endif
else
ESP_LOGD(TAG, "No SSID and PASSWORD set!!!");
if (hostname != NULL)
ESP_LOGD(TAG, "Hostname: %s", hostname);
else
ESP_LOGD(TAG, "Hostname not set");
if (ip != NULL && gateway != NULL && netmask != NULL)
ESP_LOGD(TAG, "Fixed IP: %s, Gateway %s, Netmask %s", ip, gateway, netmask);
if (dns != NULL)
ESP_LOGD(TAG, "DNS IP: %s", dns);
wifi_init_sta(ssid, passwd, hostname, ip, gateway, netmask, dns, rssithreashold);
xDelay = 2000 / portTICK_PERIOD_MS;
ESP_LOGD(TAG, "main: sleep for: %ldms", (long) xDelay);
vTaskDelay( xDelay );
#ifdef HEAP_TRACING_MAIN_WIFI
ESP_ERROR_CHECK( heap_trace_stop() );
heap_trace_dump();
#endif
#ifdef HEAP_TRACING_MAIN_START
ESP_ERROR_CHECK( heap_trace_start(HEAP_TRACE_LEAKS) );
#endif
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "=================================================");
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "================== Main Started =================");
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "=================================================");
if (getHTMLcommit().substr(0, 7) == "?")
LogFile.WriteToFile(ESP_LOG_WARN, TAG, std::string("Failed to read file html/version.txt to parse Web UI version"));
if (getHTMLcommit().substr(0, 7) != std::string(GIT_REV).substr(0, 7)) { // Compare the first 7 characters of both hashes
LogFile.WriteToFile(ESP_LOG_WARN, TAG, std::string("Web UI version (") + getHTMLcommit() + ") does not match firmware version (" + std::string(GIT_REV) + ") !");
LogFile.WriteToFile(ESP_LOG_WARN, TAG, "Please make sure to setup the SD-Card properly (check the documentation) or re-install using the AI-on-the-edge-device__update__*.zip!");
LogFile.WriteToFile(ESP_LOG_WARN, TAG, "Web UI version (" + getHTMLcommit() + ") does not match firmware version (" + std::string(GIT_REV) + ")");
LogFile.WriteToFile(ESP_LOG_WARN, TAG, "Recommendation: Repeat installation using AI-on-the-edge-device__update__*.zip");
}
std::string zw = getCurrentTimeString("%Y%m%d-%H%M%S");
ESP_LOGD(TAG, "time %s", zw.c_str());
// Check reboot reason
// ********************************************
CheckIsPlannedReboot();
if (!getIsPlannedReboot() && (esp_reset_reason() == ESP_RST_PANIC)) { // If system reboot was not triggered by user and reboot was caused by execption
LogFile.WriteToFile(ESP_LOG_WARN, TAG, "Reset reason: " + getResetReason());
LogFile.WriteToFile(ESP_LOG_WARN, TAG, "Device was rebooted due to a software exception! Log level is set to DEBUG until the next reboot. "
"Flow init is delayed by 5 minutes to check the logs or do an OTA update");
LogFile.WriteToFile(ESP_LOG_WARN, TAG, "Keep device running until crash occurs again and check logs after device is up again");
LogFile.setLogLevel(ESP_LOG_DEBUG);
}
else {
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Reset reason: " + getResetReason());
}
#ifdef HEAP_TRACING_MAIN_START
ESP_ERROR_CHECK( heap_trace_stop() );
heap_trace_dump();
#endif
#ifdef HEAP_TRACING_MAIN_START
ESP_ERROR_CHECK( heap_trace_stop() );
heap_trace_dump();
#endif
#ifdef HEAP_TRACING_MAIN_WIFI
ESP_ERROR_CHECK( heap_trace_start(HEAP_TRACE_LEAKS) );
#endif
/* Check if PSRAM can be initalized */
esp_err_t ret;
ret = esp_spiram_init();
if (ret == ESP_FAIL) { // Failed to init PSRAM, most likely not available or broken
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to initialize PSRAM (" + std::to_string(ret) + ")!");
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Either your device misses the PSRAM chip or it is broken!");
setSystemStatusFlag(SYSTEM_STATUS_PSRAM_BAD);
}
else { // PSRAM init ok
/* Check if PSRAM provides at least 4 MB */
size_t psram_size = esp_spiram_get_size();
// size_t psram_size = esp_psram_get_size(); // comming in IDF 5.0
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "The device has " + std::to_string(psram_size/1024/1024) + " MBytes of PSRAM");
if (psram_size < (4*1024*1024)) { // PSRAM is below 4 MBytes
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "At least 4 MBytes are required!");
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Does the device really have a 4 Mbytes PSRAM?");
setSystemStatusFlag(SYSTEM_STATUS_PSRAM_BAD);
// 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;
}
}
/* Check available Heap memory */
size_t _hsize = getESPHeapSize();
if (_hsize < 4000000) { // Check available Heap memory for a bit less than 4 MB (a test on a good device showed 4187558 bytes to be available)
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Not enough Heap memory available. Expected around 4 MBytes, but only " + std::to_string(_hsize) + " Bytes are available! That is not enough for this firmware!");
setSystemStatusFlag(SYSTEM_STATUS_HEAP_TOO_SMALL);
} else { // Heap memory is ok
if (camStatus != ESP_OK) {
LogFile.WriteToFile(ESP_LOG_WARN, TAG, "Failed to initialize camera module, retrying...");
PowerResetCamera();
esp_err_t camStatus = Camera.InitCam();
Camera.LightOnOff(false);
xDelay = 2000 / portTICK_PERIOD_MS;
ESP_LOGD(TAG, "After camera initialization: sleep for: %ldms", (long) xDelay);
vTaskDelay( xDelay );
if (camStatus != ESP_OK) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to initialize camera module!");
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Check that your camera module is working and connected properly!");
setSystemStatusFlag(SYSTEM_STATUS_CAM_BAD);
}
} else { // Test Camera
if (!Camera.testCamera()) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Camera Framebuffer cannot be initialized!");
/* Easiest would be to simply restart here and try again,
how ever there seem to be systems where it fails at startup but still work corectly later.
Therefore we treat it still as successed! */
setSystemStatusFlag(SYSTEM_STATUS_CAM_FB_BAD);
}
else {
Camera.LightOnOff(false);
}
}
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;
ESP_LOGD(TAG, "main: sleep for: %ldms", (long) xDelay*10);
ESP_LOGD(TAG, "main: sleep for: %ldms", (long) xDelay * CONFIG_FREERTOS_HZ/portTICK_PERIOD_MS);
vTaskDelay( xDelay );
// Set log level for wifi component to WARN level (default: INFO; only relevant for serial console)
// ********************************************
esp_log_level_set("wifi", ESP_LOG_WARN);
#ifdef HEAP_TRACING_MAIN_WIFI
ESP_ERROR_CHECK( heap_trace_stop() );
heap_trace_dump();
#endif
#ifdef DEBUG_ENABLE_SYSINFO
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL( 4, 0, 0 )
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Device Info : " + get_device_info() );
ESP_LOGD(TAG, "Device infos %s", get_device_info().c_str());
#endif
#endif //DEBUG_ENABLE_SYSINFO
#ifdef USE_HIMEM_IF_AVAILABLE
#ifdef DEBUG_HIMEM_MEMORY_CHECK
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Himem mem check : " + himem_memory_check() );
ESP_LOGD(TAG, "Himem mem check %s", himem_memory_check().c_str());
#endif
#endif
// Init external PSRAM
// ********************************************
esp_err_t PSRAMStatus = esp_spiram_init();
if (PSRAMStatus != ESP_OK) { // ESP_FAIL -> Failed to init PSRAM
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "PSRAM init failed (" + std::to_string(PSRAMStatus) + ")! PSRAM not found or defective");
setSystemStatusFlag(SYSTEM_STATUS_PSRAM_BAD);
StatusLED(PSRAM_INIT, 1, true);
}
else { // ESP_OK -> PSRAM init OK --> continue to check PSRAM size
size_t psram_size = esp_spiram_get_size(); // size_t psram_size = esp_psram_get_size(); // comming in IDF 5.0
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "PSRAM size: " + std::to_string(psram_size) + " byte (" + std::to_string(psram_size/1024/1024) +
"MB / " + std::to_string(psram_size/1024/1024*8) + "MBit)");
// Check PSRAM size
// ********************************************
if (psram_size < (4*1024*1024)) { // PSRAM is below 4 MBytes (32Mbit)
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "PSRAM size >= 4MB (32Mbit) is mandatory to run this application");
setSystemStatusFlag(SYSTEM_STATUS_PSRAM_BAD);
StatusLED(PSRAM_INIT, 2, true);
}
else { // PSRAM size OK --> continue to check heap size
size_t _hsize = getESPHeapSize();
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Total heap: " + std::to_string(_hsize) + " byte");
// Check heap memory
// ********************************************
if (_hsize < 4000000) { // Check available Heap memory for a bit less than 4 MB (a test on a good device showed 4187558 bytes to be available)
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Total heap >= 4000000 byte is mandatory to run this application");
setSystemStatusFlag(SYSTEM_STATUS_HEAP_TOO_SMALL);
StatusLED(PSRAM_INIT, 3, true);
}
else { // HEAP size OK --> continue to check camera init
// Check camera init
// ********************************************
if (camStatus != ESP_OK) { // Camera init failed, retry to init
char camStatusHex[33];
sprintf(camStatusHex,"0x%02x", camStatus);
LogFile.WriteToFile(ESP_LOG_WARN, TAG, "Camera init failed (" + std::string(camStatusHex) + "), retrying...");
PowerResetCamera();
camStatus = Camera.InitCam();
Camera.LightOnOff(false);
xDelay = 2000 / portTICK_PERIOD_MS;
ESP_LOGD(TAG, "After camera initialization: sleep for: %ldms", (long) xDelay * CONFIG_FREERTOS_HZ/portTICK_PERIOD_MS);
vTaskDelay( xDelay );
if (camStatus != ESP_OK) { // Camera init failed again
sprintf(camStatusHex,"0x%02x", camStatus);
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Camera init failed (" + std::string(camStatusHex) +
")! Check camera module and/or proper electrical connection");
setSystemStatusFlag(SYSTEM_STATUS_CAM_BAD);
StatusLED(CAM_INIT, 1, true);
}
}
else { // ESP_OK -> Camera init OK --> continue to perform camera framebuffer check
// Camera framebuffer check
// ********************************************
if (!Camera.testCamera()) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Camera framebuffer check failed");
// Easiest would be to simply restart here and try again,
// how ever there seem to be systems where it fails at startup but still work correctly later.
// Therefore we treat it still as successed! */
setSystemStatusFlag(SYSTEM_STATUS_CAM_FB_BAD);
StatusLED(CAM_INIT, 2, false);
}
Camera.LightOnOff(false); // make sure flashlight is off before start of flow
// Print camera infos
// ********************************************
char caminfo[50];
sensor_t * s = esp_camera_sensor_get();
sprintf(caminfo, "PID: 0x%02x, VER: 0x%02x, MIDL: 0x%02x, MIDH: 0x%02x", s->id.PID, s->id.VER, s->id.MIDH, s->id.MIDL);
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Camera info: " + std::string(caminfo));
}
}
}
}
// Print Device info
// ********************************************
esp_chip_info_t chipInfo;
esp_chip_info(&chipInfo);
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Device info: CPU cores: " + std::to_string(chipInfo.cores) +
", Chip revision: " + std::to_string(chipInfo.revision));
// Print SD-Card info
// ********************************************
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "SD card info: Name: " + getSDCardName() + ", Capacity: " +
getSDCardCapacity() + "MB, Free: " + getSDCardFreePartitionSpace() + "MB");
xDelay = 2000 / portTICK_PERIOD_MS;
ESP_LOGD(TAG, "main: sleep for: %ldms", (long) xDelay * CONFIG_FREERTOS_HZ/portTICK_PERIOD_MS);
vTaskDelay( xDelay );
// Start webserver + register handler
// ********************************************
ESP_LOGD(TAG, "starting servers");
server = start_webserver();
@@ -376,25 +474,300 @@ extern "C" void app_main(void)
ESP_LOGD(TAG, "Before reg server main");
register_server_main_uri(server, "/sdcard");
/* Testing */
// Only for testing purpose
//setSystemStatusFlag(SYSTEM_STATUS_CAM_FB_BAD);
//setSystemStatusFlag(SYSTEM_STATUS_PSRAM_BAD);
/* Main Init has successed or only an error which allows to continue operation */
// Check main init + start TFlite task
// ********************************************
if (getSystemStatus() == 0) { // No error flag is set
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Initialization completed successfully!");
ESP_LOGD(TAG, "Before do autostart");
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Initialization completed successfully! Starting flow task ...");
TFliteDoAutoStart();
}
else if (isSetSystemStatusFlag(SYSTEM_STATUS_CAM_FB_BAD) || // Non critical errors occured, we try to continue...
isSetSystemStatusFlag(SYSTEM_STATUS_NTP_BAD)) {
LogFile.WriteToFile(ESP_LOG_WARN, TAG, "Initialization completed with errors, but trying to continue...");
ESP_LOGD(TAG, "Before do autostart");
isSetSystemStatusFlag(SYSTEM_STATUS_NTP_BAD)) {
LogFile.WriteToFile(ESP_LOG_WARN, TAG, "Initialization completed with errors! Starting flow task ...");
TFliteDoAutoStart();
}
else { // Any other error is critical and makes running the flow impossible.
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Initialization failed. Not starting flows!");
else { // Any other error is critical and makes running the flow impossible. Init is going to abort.
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Initialization failed. Flow task start aborted. Loading reduced web interface...");
}
}
void migrateConfiguration(void) {
bool migrated = false;
if (!FileExists(CONFIG_FILE)) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Config file seems to be missing!");
return;
}
std::string section = "";
std::ifstream ifs(CONFIG_FILE);
std::string content((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>()));
/* Split config file it array of lines */
std::vector<std::string> configLines = splitString(content);
/* Process each line */
for (int i = 0; i < configLines.size(); i++) {
//ESP_LOGI(TAG, "Line %d: %s", i, configLines[i].c_str());
if (configLines[i].find("[") != std::string::npos) { // Start of new section
section = configLines[i];
replaceString(section, ";", "", false); // Remove possible semicolon (just for the string comparison)
//ESP_LOGI(TAG, "New section: %s", section.c_str());
}
/* Migrate parameters as needed
* For the boolean parameters, we make them enabled all the time now:
* 1. If they where disabled, set them to their default value
* 2. Enable them
* Notes:
* The migration has some simplifications:
* - Case Sensitiveness must be like in the initial config.ini
* - No Whitespace after a semicollon
* - Only one whitespace before/after the equal sign
*/
if (section == "[MakeImage]") {
migrated = migrated | replaceString(configLines[i], "[MakeImage]", "[TakeImage]"); // Rename the section itself
}
if (section == "[MakeImage]" || section == "[TakeImage]") {
migrated = migrated | replaceString(configLines[i], "LogImageLocation", "RawImagesLocation");
migrated = migrated | replaceString(configLines[i], "LogfileRetentionInDays", "RawImagesRetention");
migrated = migrated | replaceString(configLines[i], ";Demo = true", ";Demo = false"); // Set it to its default value
migrated = migrated | replaceString(configLines[i], ";Demo", "Demo"); // Enable it
migrated = migrated | replaceString(configLines[i], ";FixedExposure = true", ";FixedExposure = false"); // Set it to its default value
migrated = migrated | replaceString(configLines[i], ";FixedExposure", "FixedExposure"); // Enable it
}
if (section == "[Alignment]") {
migrated = migrated | replaceString(configLines[i], ";InitialMirror = true", ";InitialMirror = false"); // Set it to its default value
migrated = migrated | replaceString(configLines[i], ";InitialMirror", "InitialMirror"); // Enable it
migrated = migrated | replaceString(configLines[i], ";FlipImageSize = true", ";FlipImageSize = false"); // Set it to its default value
migrated = migrated | replaceString(configLines[i], ";FlipImageSize", "FlipImageSize"); // Enable it
}
if (section == "[Digits]") {
migrated = migrated | replaceString(configLines[i], "LogImageLocation", "ROIImagesLocation");
migrated = migrated | replaceString(configLines[i], "LogfileRetentionInDays", "ROIImagesRetention");
}
if (section == "[Analog]") {
migrated = migrated | replaceString(configLines[i], "LogImageLocation", "ROIImagesLocation");
migrated = migrated | replaceString(configLines[i], "LogfileRetentionInDays", "ROIImagesRetention");
migrated = migrated | replaceString(configLines[i], "ExtendedResolution", ";UNUSED_PARAMETER"); // This parameter is no longer used
}
if (section == "[PostProcessing]") {
migrated = migrated | replaceString(configLines[i], ";PreValueUse = true", ";PreValueUse = false"); // Set it to its default value
migrated = migrated | replaceString(configLines[i], ";PreValueUse", "PreValueUse"); // Enable it
/* AllowNegativeRates has a <NUMBER> as prefix! */
if (isInString(configLines[i], "AllowNegativeRates") && isInString(configLines[i], ";")) { // It is the parameter "AllowNegativeRates" and it is commented out
migrated = migrated | replaceString(configLines[i], "true", "false"); // Set it to its default value
migrated = migrated | replaceString(configLines[i], ";", ""); // Enable it
}
/* IgnoreLeadingNaN has a <NUMBER> as prefix! */
if (isInString(configLines[i], "IgnoreLeadingNaN") && isInString(configLines[i], ";")) { // It is the parameter "IgnoreLeadingNaN" and it is commented out
migrated = migrated | replaceString(configLines[i], "true", "false"); // Set it to its default value
migrated = migrated | replaceString(configLines[i], ";", ""); // Enable it
}
/* ExtendedResolution has a <NUMBER> as prefix! */
if (isInString(configLines[i], "ExtendedResolution") && isInString(configLines[i], ";")) { // It is the parameter "ExtendedResolution" and it is commented out
migrated = migrated | replaceString(configLines[i], "true", "false"); // Set it to its default value
migrated = migrated | replaceString(configLines[i], ";", ""); // Enable it
}
migrated = migrated | replaceString(configLines[i], ";ErrorMessage = true", ";ErrorMessage = false"); // Set it to its default value
migrated = migrated | replaceString(configLines[i], ";ErrorMessage", "ErrorMessage"); // Enable it
migrated = migrated | replaceString(configLines[i], ";CheckDigitIncreaseConsistency = true", ";CheckDigitIncreaseConsistency = false"); // Set it to its default value
migrated = migrated | replaceString(configLines[i], ";CheckDigitIncreaseConsistency", "CheckDigitIncreaseConsistency"); // Enable it
}
if (section == "[MQTT]") {
migrated = migrated | replaceString(configLines[i], "SetRetainFlag", "RetainMessages"); // First rename it, enable it with its default value
migrated = migrated | replaceString(configLines[i], ";RetainMessages = true", ";RetainMessages = false"); // Set it to its default value
migrated = migrated | replaceString(configLines[i], ";RetainMessages", "RetainMessages"); // Enable it
migrated = migrated | replaceString(configLines[i], ";HomeassistantDiscovery = true", ";HomeassistantDiscovery = false"); // Set it to its default value
migrated = migrated | replaceString(configLines[i], ";HomeassistantDiscovery", "HomeassistantDiscovery"); // Enable it
if (configLines[i].rfind("Topic", 0) != std::string::npos) // only if string starts with "Topic" (Was the naming in very old version)
{
migrated = migrated | replaceString(configLines[i], "Topic", "MainTopic");
}
}
if (section == "[InfluxDB]") {
}
if (section == "[GPIO]") {
}
if (section == "[DataLogging]") {
migrated = migrated | replaceString(configLines[i], "DataLogRetentionInDays", "DataFilesRetention");
/* DataLogActive is true by default! */
migrated = migrated | replaceString(configLines[i], ";DataLogActive = false", ";DataLogActive = true"); // Set it to its default value
migrated = migrated | replaceString(configLines[i], ";DataLogActive", "DataLogActive"); // Enable it
}
if (section == "[AutoTimer]") {
migrated = migrated | replaceString(configLines[i], "Intervall", "Interval");
migrated = migrated | replaceString(configLines[i], ";AutoStart = true", ";AutoStart = false"); // Set it to its default value
migrated = migrated | replaceString(configLines[i], ";AutoStart", "AutoStart"); // Enable it
}
if (section == "[Debug]") {
migrated = migrated | replaceString(configLines[i], "Logfile ", "LogLevel "); // Whitespace needed so it does not match `LogfileRetentionInDays`
/* LogLevel (resp. LogFile) was originally a boolean, but we switched it to an int
* For both cases (true/false), we set it to level 2 (WARNING) */
migrated = migrated | replaceString(configLines[i], "LogLevel = true", "LogLevel = 2");
migrated = migrated | replaceString(configLines[i], "LogLevel = false", "LogLevel = 2");
migrated = migrated | replaceString(configLines[i], "LogfileRetentionInDays", "LogfilesRetention");
}
if (section == "[System]") {
migrated = migrated | replaceString(configLines[i], "RSSIThreashold", "RSSIThreshold");
migrated = migrated | replaceString(configLines[i], "AutoAdjustSummertime", ";UNUSED_PARAMETER"); // This parameter is no longer used
migrated = migrated | replaceString(configLines[i], ";SetupMode = true", ";SetupMode = false"); // Set it to its default value
migrated = migrated | replaceString(configLines[i], ";SetupMode", "SetupMode"); // Enable it
}
}
if (migrated) { // At least one replacement happened
if (! RenameFile(CONFIG_FILE, CONFIG_FILE_BACKUP)) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to create backup of Config file!");
return;
}
FILE* pfile = fopen(CONFIG_FILE, "w");
for (int i = 0; i < configLines.size(); i++) {
fwrite(configLines[i].c_str() , configLines[i].length(), 1, pfile);
fwrite("\n" , 1, 1, pfile);
}
fclose(pfile);
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Config file migrated. Saved backup to " + string(CONFIG_FILE_BACKUP));
}
}
std::vector<std::string> splitString(const std::string& str) {
std::vector<std::string> tokens;
std::stringstream ss(str);
std::string token;
while (std::getline(ss, token, '\n')) {
tokens.push_back(token);
}
return tokens;
}
/*bool replace_all(std::string& s, std::string const& toReplace, std::string const& replaceWith) {
std::string buf;
std::size_t pos = 0;
std::size_t prevPos;
bool found = false;
// Reserves rough estimate of final size of string.
buf.reserve(s.size());
while (true) {
prevPos = pos;
pos = s.find(toReplace, pos);
if (pos == std::string::npos) {
break;
}
found = true;
buf.append(s, prevPos, pos - prevPos);
buf += replaceWith;
pos += toReplace.size();
}
buf.append(s, prevPos, s.size() - prevPos);
s.swap(buf);
return found;
}*/
bool setCpuFrequency(void) {
ConfigFile configFile = ConfigFile(CONFIG_FILE);
string cpuFrequency = "160";
esp_pm_config_esp32_t pm_config;
if (!configFile.ConfigFileExists()){
LogFile.WriteToFile(ESP_LOG_WARN, TAG, "No ConfigFile defined - exit setCpuFrequency()!");
return false;
}
std::vector<std::string> splitted;
std::string line = "";
bool disabledLine = false;
bool eof = false;
/* Load config from config file */
while ((!configFile.GetNextParagraph(line, disabledLine, eof) ||
(line.compare("[System]") != 0)) && !eof) {}
if (eof) {
return false;
}
if (disabledLine) {
return false;
}
while (configFile.getNextLine(&line, disabledLine, eof) &&
!configFile.isNewParagraph(line)) {
splitted = ZerlegeZeile(line);
if (toUpper(splitted[0]) == "CPUFREQUENCY") {
cpuFrequency = splitted[1];
break;
}
}
if (esp_pm_get_configuration(&pm_config) != ESP_OK) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to read CPU Frequency!");
return false;
}
if (cpuFrequency == "160") { // 160 is the default
// No change needed
}
else if (cpuFrequency == "240") {
pm_config.max_freq_mhz = 240;
pm_config.min_freq_mhz = pm_config.max_freq_mhz;
if (esp_pm_configure(&pm_config) != ESP_OK) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to set new CPU frequency!");
return false;
}
}
else {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Unknown CPU frequency: " + cpuFrequency + "! "
"It must be 160 or 240!");
return false;
}
if (esp_pm_get_configuration(&pm_config) == ESP_OK) {
LogFile.WriteToFile(ESP_LOG_INFO, TAG, string("CPU frequency: ") + to_string(pm_config.max_freq_mhz) + " MHz");
}
return true;
}

View File

@@ -8,6 +8,7 @@
#include "time_sntp.h"
#include "connect_wlan.h"
#include "read_wlanini.h"
#include "version.h"
@@ -83,7 +84,7 @@ esp_err_t info_get_handler(httpd_req_t *req)
else if (_task.compare("Hostname") == 0)
{
std::string zw;
zw = std::string(hostname);
zw = std::string(wlan_config.hostname);
httpd_resp_sendstr(req, zw.c_str());
return ESP_OK;
}
@@ -214,7 +215,10 @@ esp_err_t hello_main_handler(httpd_req_t *req)
if (filetosend == "/sdcard/html/index.html") {
if (isSetSystemStatusFlag(SYSTEM_STATUS_PSRAM_BAD) || // Initialization failed with crritical errors!
isSetSystemStatusFlag(SYSTEM_STATUS_CAM_BAD)) {
isSetSystemStatusFlag(SYSTEM_STATUS_CAM_BAD) ||
isSetSystemStatusFlag(SYSTEM_STATUS_SDCARD_CHECK_BAD) ||
isSetSystemStatusFlag(SYSTEM_STATUS_FOLDER_CHECK_BAD))
{
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "We have a critical error, not serving main page!");
char buf[20];
@@ -227,13 +231,13 @@ esp_err_t hello_main_handler(httpd_req_t *req)
}
}
message += "<br>Please check <a href=\"https://jomjol.github.io/AI-on-the-edge-device-docs/Error-Codes\" target=_blank>jomjol.github.io/AI-on-the-edge-device-docs/Error-Codes</a> for more information!";
message += "<br>Please check logs with log viewer and/or <a href=\"https://jomjol.github.io/AI-on-the-edge-device-docs/Error-Codes\" target=_blank>jomjol.github.io/AI-on-the-edge-device-docs/Error-Codes</a> for more information!";
message += "<br><br><button onclick=\"window.location.href='/reboot';\">Reboot</button>";
message += "&nbsp;<button onclick=\"window.open('/ota_page.html');\">OTA Update</button>";
message += "&nbsp;<button onclick=\"window.open('/log.html');\">Log Viewer</button>";
message += "&nbsp;<button onclick=\"window.open('/info.html');\">Show System Info</button>";
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, message.c_str());
return ESP_FAIL;
httpd_resp_send(req, message.c_str(), message.length());
return ESP_OK;
}
else if (isSetupModusActive()) {
ESP_LOGD(TAG, "System is in setup mode --> index.html --> setup.html");

View File

@@ -27,6 +27,7 @@
#include "server_help.h"
#include "defines.h"
#include "Helper.h"
#include "statusled.h"
#include "server_ota.h"
#include "lwip/err.h"
@@ -40,22 +41,24 @@
bool isConfigINI = false;
bool isWlanINI = false;
static const char *TAG = "wifi softAP";
static const char *TAG = "WIFI AP";
static void wifi_event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
if (event_id == WIFI_EVENT_AP_STACONNECTED) {
wifi_event_ap_staconnected_t* event = (wifi_event_ap_staconnected_t*) event_data;
ESP_LOGI(TAG, "station "MACSTR" join, AID=%d",
ESP_LOGI(TAG, "station " MACSTR " join, AID=%d",
MAC2STR(event->mac), event->aid);
} else if (event_id == WIFI_EVENT_AP_STADISCONNECTED) {
wifi_event_ap_stadisconnected_t* event = (wifi_event_ap_stadisconnected_t*) event_data;
ESP_LOGI(TAG, "station "MACSTR" leave, AID=%d",
ESP_LOGI(TAG, "station " MACSTR " leave, AID=%d",
MAC2STR(event->mac), event->aid);
}
}
void wifi_init_softAP(void)
{
ESP_ERROR_CHECK(esp_netif_init());
@@ -87,7 +90,7 @@ void wifi_init_softAP(void)
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
ESP_LOGI(TAG, "wifi_init_softap finished. SSID:%s password:%s channel:%d",
ESP_LOGI(TAG, "started with SSID \"%s\", password: \"%s\", channel: %d. Connect to AP and open http://192.168.4.1",
EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS, EXAMPLE_ESP_WIFI_CHANNEL);
}
@@ -136,18 +139,18 @@ void SendHTTPResponse(httpd_req_t *req)
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</td></tr>";
// message += "<tr><td>gateway</td><td><input type=\"text\" name=\"gateway\" id=\"gateway\"></td><td>Leave emtpy if set by router</td></tr>";
// message += "<tr><td>netmask</td><td><input type=\"text\" name=\"netmask\" id=\"netmask\"></td><td>Leave emtpy if set by router</td>";
// message += "</tr><tr><td>DNS</td><td><input type=\"text\" name=\"dns\" id=\"dns\"></td><td>Leave emtpy if set by router</td></tr>";
// message += "<tr><td>RSSI Threashold</td><td><input type=\"number\" name=\"name\" id=\"threashold\" min=\"-100\" max=\"0\" step=\"1\" value = \"0\"></td><td>WLAN Mesh Parameter: Threashold for RSSI value to check for start switching access point in a mesh system.Possible values: -100 to 0, 0 = disabled - Value will be transfered to wlan.ini at next startup)</td></tr>";
// 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+\"&rssi=\"+document.getElementById(\"threashold\").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;
@@ -164,8 +167,6 @@ void SendHTTPResponse(httpd_req_t *req)
}
esp_err_t test_handler(httpd_req_t *req)
{
SendHTTPResponse(req);
@@ -182,7 +183,7 @@ esp_err_t reboot_handlerAP(httpd_req_t *req)
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Trigger reboot due to firmware update.");
doRebootOTA();
return ESP_OK;
};
}
esp_err_t config_ini_handler(httpd_req_t *req)
@@ -201,134 +202,164 @@ esp_err_t config_ini_handler(httpd_req_t *req)
std::string hn = ""; // hostname
std::string ip = "";
std::string gw = ""; // gateway
std::string nm = ""; // nm
std::string nm = ""; // netmask
std::string dns = "";
std::string rssi = "";
std::string rssithreshold = ""; //rssi threshold for WIFI roaming
std::string text = "";
if (httpd_req_get_url_query_str(req, _query, 400) == ESP_OK)
{
ESP_LOGD(TAG, "Query: %s", _query);
if (httpd_query_key_value(_query, "ssid", _valuechar, 30) == ESP_OK)
if (httpd_query_key_value(_query, "ssid", _valuechar, 100) == ESP_OK)
{
ESP_LOGD(TAG, "ssid is found: %s", _valuechar);
ssid = std::string(_valuechar);
ssid = UrlDecode(std::string(_valuechar));
}
if (httpd_query_key_value(_query, "pwd", _valuechar, 30) == ESP_OK)
if (httpd_query_key_value(_query, "pwd", _valuechar, 100) == ESP_OK)
{
ESP_LOGD(TAG, "pwd is found: %s", _valuechar);
pwd = std::string(_valuechar);
pwd = UrlDecode(std::string(_valuechar));
}
if (httpd_query_key_value(_query, "ssid", _valuechar, 30) == ESP_OK)
if (httpd_query_key_value(_query, "ssid", _valuechar, 100) == ESP_OK)
{
ESP_LOGD(TAG, "ssid is found: %s", _valuechar);
ssid = std::string(_valuechar);
ssid = UrlDecode(std::string(_valuechar));
}
if (httpd_query_key_value(_query, "hn", _valuechar, 30) == ESP_OK)
if (httpd_query_key_value(_query, "hn", _valuechar, 100) == ESP_OK)
{
ESP_LOGD(TAG, "hostname is found: %s", _valuechar);
hn = std::string(_valuechar);
hn = UrlDecode(std::string(_valuechar));
}
if (httpd_query_key_value(_query, "ip", _valuechar, 30) == ESP_OK)
if (httpd_query_key_value(_query, "ip", _valuechar, 100) == ESP_OK)
{
ESP_LOGD(TAG, "ip is found: %s", _valuechar);
ip = std::string(_valuechar);
ip = UrlDecode(std::string(_valuechar));
}
if (httpd_query_key_value(_query, "gw", _valuechar, 30) == ESP_OK)
if (httpd_query_key_value(_query, "gw", _valuechar, 100) == ESP_OK)
{
ESP_LOGD(TAG, "gateway is found: %s", _valuechar);
gw = std::string(_valuechar);
gw = UrlDecode(std::string(_valuechar));
}
if (httpd_query_key_value(_query, "nm", _valuechar, 30) == ESP_OK)
if (httpd_query_key_value(_query, "nm", _valuechar, 100) == ESP_OK)
{
ESP_LOGD(TAG, "netmask is found: %s", _valuechar);
nm = std::string(_valuechar);
nm = UrlDecode(std::string(_valuechar));
}
if (httpd_query_key_value(_query, "dns", _valuechar, 30) == ESP_OK)
if (httpd_query_key_value(_query, "dns", _valuechar, 100) == ESP_OK)
{
ESP_LOGD(TAG, "dns is found: %s", _valuechar);
dns = std::string(_valuechar);
dns = UrlDecode(std::string(_valuechar));
}
if (httpd_query_key_value(_query, "rssi", _valuechar, 30) == ESP_OK)
if (httpd_query_key_value(_query, "rssithreshold", _valuechar, 100) == ESP_OK)
{
ESP_LOGD(TAG, "rssi is found: %s", _valuechar);
rssi = std::string(_valuechar);
ESP_LOGD(TAG, "rssithreshold is found: %s", _valuechar);
rssithreshold = UrlDecode(std::string(_valuechar));
}
};
}
FILE* 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
ssid = ";ssid = \"\"\n";
ssid = "ssid = \"\"\n";
fputs(ssid.c_str(), configfilehandle);
if (pwd.length())
pwd = "password = \"" + pwd + "\"\n";
else
pwd = ";password = \"\"\n";
pwd = "password = \"\"\n";
fputs(pwd.c_str(), configfilehandle);
text = "\n;++++++++++++++++++++++++++++++++++\n";
text += "; Hostname: Name of device in network\n";
text += "; This parameter can be configured via WebUI configuration\n";
text += "; Default: \"watermeter\", if nothing is configured\n\n";
fputs(text.c_str(), configfilehandle);
if (hn.length())
hn = "hostname = \"" + hn + "\"\n";
else
hn = ";hostname = \"\"\n";
hn = ";hostname = \"watermeter\"\n";
fputs(hn.c_str(), configfilehandle);
text = "\n;++++++++++++++++++++++++++++++++++\n";
text += "; Fixed IP: If you like to use fixed IP instead of DHCP (default), the following\n";
text += "; parameters needs to be configured: ip, gateway, netmask are mandatory, dns optional\n\n";
fputs(text.c_str(), configfilehandle);
if (ip.length())
ip = "ip = \"" + ip + "\"\n";
else
ip = ";ip = \"\"\n";
ip = ";ip = \"xxx.xxx.xxx.xxx\"\n";
fputs(ip.c_str(), configfilehandle);
if (gw.length())
gw = "gateway = \"" + gw + "\"\n";
else
gw = ";gateway = \"\"\n";
gw = ";gateway = \"xxx.xxx.xxx.xxx\"\n";
fputs(gw.c_str(), configfilehandle);
if (nm.length())
nm = "netmask = \"" + nm + "\"\n";
else
nm = ";netmask = \"\"\n";
nm = ";netmask = \"xxx.xxx.xxx.xxx\"\n";
fputs(nm.c_str(), configfilehandle);
text = "\n;++++++++++++++++++++++++++++++++++\n";
text += "; DNS server (optional, if no DNS is configured, gateway address will be used)\n\n";
fputs(text.c_str(), configfilehandle);
if (dns.length())
dns = "dns = \"" + dns + "\"\n";
else
dns = ";dns = \"\"\n";
dns = ";dns = \"xxx.xxx.xxx.xxx\"\n";
fputs(dns.c_str(), configfilehandle);
if (rssi.length())
rssi = "RSSIThreashold = \"" + rssi + "\"\n";
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
rssi = ";rssi = \"\"\n";
fputs(rssi.c_str(), configfilehandle);
rssithreshold = "RSSIThreshold = 0\n";
fputs(rssithreshold.c_str(), configfilehandle);
fflush(configfilehandle);
fclose(configfilehandle);
std::string zw = "ota without parameter - should not be the case!";
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
httpd_resp_send(req, zw.c_str(), strlen(zw.c_str()));
httpd_resp_send_chunk(req, NULL, 0);
ESP_LOGE(TAG, "end config.ini");
httpd_resp_send(req, zw.c_str(), zw.length());
ESP_LOGD(TAG, "end config.ini");
return ESP_OK;
};
}
esp_err_t upload_post_handlerAP(httpd_req_t *req)
@@ -470,21 +501,28 @@ httpd_handle_t start_webserverAP(void)
return NULL;
}
void CheckStartAPMode()
{
isConfigINI = FileExists(CONFIG_FILE);
isWlanINI = FileExists(WLAN_CONFIG_FILE);
if (!isConfigINI or !isWlanINI)
if (!isConfigINI)
ESP_LOGW(TAG, "config.ini not found!");
if (!isWlanINI)
ESP_LOGW(TAG, "wlan.ini not found!");
if (!isConfigINI || !isWlanINI)
{
ESP_LOGI(TAG, "Starting access point for remote configuration");
StatusLED(AP_OR_OTA, 2, true);
wifi_init_softAP();
start_webserverAP();
while(1) { // wait until reboot within task_do_Update_ZIP
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
}
#endif //#ifdef ENABLE_SOFTAP

View File

@@ -1,7 +1,7 @@
##################################################
# Application specific configuration
# Edit this file instead of sdkconfig.esp32cam!
# After editting, make sure to explicitly delete
# After editing make sure to explicitly delete
# sdkconfig.esp32cam to apply your changes!
##################################################
@@ -94,11 +94,14 @@ CONFIG_SPIRAM_SPEED_40M=y
CONFIG_SPIRAM=y
CONFIG_SPIRAM_BOOT_INIT=y
CONFIG_SPIRAM_USE_MALLOC=y
#CONFIG_SPIRAM_USE_MEMMAP=y => Does not work: "cam_dma_config(306): frame buffer malloc failed"
CONFIG_SPIRAM_MEMTEST=y
CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=16384
CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=40960
CONFIG_SPIRAM_CACHE_WORKAROUND=y
CONFIG_SPIRAM_IGNORE_NOTFOUND=y
#CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y => Leads to memory fragmentation, see https://github.com/jomjol/AI-on-the-edge-device/issues/2200
#CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=y => Leads to memory fragmentation, see https://github.com/jomjol/AI-on-the-edge-device/issues/2200
CONFIG_ESP_INT_WDT_TIMEOUT_MS=300
@@ -118,6 +121,9 @@ CONFIG_MQTT_MSG_ID_INCREMENTAL=y
CONFIG_MQTT_SKIP_PUBLISH_IF_DISCONNECTED=y
CONFIG_MQTT_TASK_CORE_SELECTION_ENABLED=y
CONFIG_MQTT_USE_CORE_0=y
CONFIG_MQTT_USE_CUSTOM_CONFIG=y
#CONFIG_MQTT_OUTBOX_EXPIRED_TIMEOUT_MS=5000
#CONFIG_MQTT_CUSTOM_OUTBOX=y # -> Use custom outbox in components/jomjol_mqtt/mqtt_outbox.h/cpp. If USE_PSRAM is enabled in there, it will save 10 kBytes of internal RAM. How ever it also leads to memory fragmentation, see https://github.com/jomjol/AI-on-the-edge-device/issues/2200
CONFIG_FREERTOS_TASK_FUNCTION_WRAPPER=n
@@ -133,7 +139,16 @@ CONFIG_GC032A_SUPPORT=n
CONFIG_GC0308_SUPPORT=n
CONFIG_BF3005_SUPPORT=n
#only necessary for task analysis (include/defines -> TASK_ANALYSIS_ON)
CONFIG_SYSTEM_EVENT_TASK_STACK_SIZE=4864
#only necessary for WIFI mesh roaming (include/defines.h -> WLAN_USE_MESH_ROAMING)
#CONFIG_WPA_11KV_SUPPORT=y
#CONFIG_WPA_SCAN_CACHE=n
#CONFIG_WPA_MBO_SUPPORT=n
#CONFIG_WPA_11R_SUPPORT=n // Will be supported with ESP-IDF v5.0
#CONFIG_WPA_DEBUG_PRINT=n
#only necessary for task analysis (include/defines.h -> TASK_ANALYSIS_ON)
#set in [env:esp32cam-dev-task-analysis]
#CONFIG_FREERTOS_USE_TRACE_FACILITY=1
#CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=y
@@ -144,3 +159,5 @@ CONFIG_BF3005_SUPPORT=n
#I (2112) esp_himem: Initialized. Using last 8 32KB address blocks for bank switching on 4352 KB of physical memory.
CONFIG_SPIRAM_BANKSWITCH_ENABLE=n
#CONFIG_SPIRAM_BANKSWITCH_RESERVE is not set
CONFIG_PM_ENABLE=y

View File

@@ -9,11 +9,11 @@ UnderTestPost* setUpClassFlowPostprocessing(t_CNNType digType, t_CNNType anaType
ClassFlowCNNGeneral* _analog;
ClassFlowCNNGeneral* _digit;
std::vector<ClassFlow*> FlowControll;
ClassFlowMakeImage* flowmakeimage;
ClassFlowTakeImage* flowtakeimage;
// wird im doFlow verwendet
flowmakeimage = new ClassFlowMakeImage(&FlowControll);
FlowControll.push_back(flowmakeimage);
flowtakeimage = new ClassFlowTakeImage(&FlowControll);
FlowControll.push_back(flowtakeimage);
// Die Modeltypen werden gesetzt, da keine Modelle verwendet werden.
_analog = new ClassFlowCNNGeneral(nullptr, anaType);

View File

@@ -4,7 +4,7 @@
#include <unity.h>
#include <ClassFlowPostProcessing.h>
#include <ClassFlowCNNGeneral.h>
#include <ClassFlowMakeImage.h>
#include <ClassFlowTakeImage.h>
#include <Helper.h>

View File

@@ -76,3 +76,30 @@ void testNegative() {
}
/**
* @brief Fehlerberichte aus Issues
*
*/
void testNegative_Issues() {
// Ohne decimal_shift
std::vector<float> digits = { 2.0, 2.0, 0.0, 1.0, 7.2, 9.0, 8.0};
std::vector<float> analogs = { };
double preValue_extended = 22018.080;
double preValue = 22018.08;
const char* expected = "22017.98";
// https://github.com/jomjol/AI-on-the-edge-device/issues/2145#issuecomment-1461899094
// extendResolution=false
// value < preValue
// Prüfung eingeschaltet => Fehler
preValue = 22018.08; // zu groß
UnderTestPost* underTestPost = init_do_flow(analogs, digits, Digital100, false, false, -2);
setAllowNegatives(underTestPost, false);
setPreValue(underTestPost, preValue_extended);
std::string result = process_doFlow(underTestPost);
TEST_ASSERT_EQUAL_STRING("Neg. Rate - Read: - Raw: 22017.98 - Pre: 22018.08 ", underTestPost->getReadoutError().c_str());
TEST_ASSERT_EQUAL_STRING(expected, result.c_str());
delete underTestPost;
}

View File

@@ -108,9 +108,9 @@ extern "C" void app_main()
esp_log_level_set("*", ESP_LOG_DEBUG); // set all components to ERROR level
UNITY_BEGIN();
RUN_TEST(testNegative);
RUN_TEST(testNegative_Issues);
/* RUN_TEST(testNegative);
RUN_TEST(test_analogToDigit_Standard);
RUN_TEST(test_analogToDigit_Transition);
RUN_TEST(test_doFlowPP);
@@ -121,6 +121,6 @@ extern "C" void app_main()
// getReadoutRawString test
RUN_TEST(test_getReadoutRawString);
*/
UNITY_END();
}

View File

@@ -3,4 +3,4 @@ The firmware got moved to the [Release page](https://github.com/jomjol/AI-on-the
# Installation Guide
You find the complete installation guide at <https://github.com/jomjol/AI-on-the-edge-device/wiki/Installation>
You find the complete installation guide at https://jomjol.github.io/AI-on-the-edge-device-docs/Installation/

Binary file not shown.

Binary file not shown.

View File

@@ -1,7 +1,7 @@
[MakeImage]
;LogImageLocation = /log/source
[TakeImage]
;RawImagesLocation = /log/source
WaitBeforeTakingPicture = 5
;LogfileRetentionInDays = 15
;RawImagesRetention = 15
Brightness = 0
Contrast = 0
Saturation = 0
@@ -9,7 +9,7 @@ LEDIntensity = 50
ImageQuality = 12
ImageSize = VGA
FixedExposure = false
;Demo = true
Demo = false
[Alignment]
InitialRotate = 179
@@ -22,20 +22,19 @@ FlipImageSize = false
/config/ref1.jpg 442 142
[Digits]
Model = /config/dig-cont_0600_s3.tflite
Model = /config/dig-cont_0611_s3_q.tflite
CNNGoodThreshold = 0.5
;LogImageLocation = /log/digit
;LogfileRetentionInDays = 3
;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_11.3.1_s2.tflite
Model = /config/ana-cont_1105_s2_q.tflite
CNNGoodThreshold = 0.5
;LogImageLocation = /log/analog
;LogfileRetentionInDays = 3
ExtendedResolution = true
;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
@@ -46,11 +45,11 @@ main.DecimalShift = 0
main.AnalogDigitalTransitionStart = 9.2
PreValueUse = true
PreValueAgeStartup = 720
AllowNegativeRates = false
main.AllowNegativeRates = false
main.MaxRateValue = 0.05
;main.MaxRateType = AbsoluteChange
;main.ExtendedResolution = false
;main.IgnoreLeadingNaN = true
main.ExtendedResolution = false
main.IgnoreLeadingNaN = false
ErrorMessage = true
CheckDigitIncreaseConsistency = false
@@ -60,17 +59,25 @@ CheckDigitIncreaseConsistency = false
;ClientID = watermeter
;user = USERNAME
;password = PASSWORD
;SetRetainFlag = true
;HomeassistantDiscovery = true
RetainMessages = false
HomeassistantDiscovery = false
;MeterType = other
;[InfluxDB]
;Uri = undefined
;Database =
;Database = undefined
;Measurement = undefined
;user = undefined
;password = undefined
;[InfluxDBv2]
;Uri = undefined
;Database = undefined
;Measurement = undefined
;Org = undefined
;Token = undefined
;main.Fieldname = undefined
;[GPIO]
;MainTopicMQTT = wasserzaehler/GPIO
;IO0 = input disabled 10 false false
@@ -85,19 +92,20 @@ LEDColor = 150 150 150
[AutoTimer]
AutoStart = true
Intervall = 5
Interval = 5
[DataLogging]
DataLogActive = true
DataLogRetentionInDays = 3
DataFilesRetention = 3
[Debug]
Logfile = 1
LogfileRetentionInDays = 3
LogLevel = 1
LogfilesRetention = 3
[System]
TimeZone = CET-1CEST,M3.5.0,M10.5.0/3
;TimeServer = pool.ntp.org
;AutoAdjustSummertime = false
;Hostname = undefined
;RSSIThreshold = 0
CPUFrequency = 160
SetupMode = true

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -121,9 +121,15 @@ function fetchFiles(urls, filesData, index, retry, zipFilename) {
else if (retry == 1) { // longer timeout
xhr.timeout = 5000; // time in milliseconds
}
else { // very long timeout
else if (retry == 2) { // longer timeout
xhr.timeout = 10000; // time in milliseconds
}
else if (retry == 3) { // longer timeout
xhr.timeout = 20000; // time in milliseconds
}
else { // very long timeout
xhr.timeout = 30000; // time in milliseconds
}
xhr.onload = () => { // Request finished
//console.log(url + " done");
@@ -140,6 +146,20 @@ function fetchFiles(urls, filesData, index, retry, zipFilename) {
}
};
xhr.onprogress = (e) => { // XMLHttpRequest progress ... extend timeout
xhr.timeout = xhr.timeout + 500;
};
xhr.onerror = (e) => { // XMLHttpRequest error loading
console.log("Error on fetching " + url + "!");
if (retry > 5) {
setStatus("<span style=\"color: red\">Backup failed, please restart the device and try again!</span>");
}
else {
fetchFiles(urls, filesData, index, retry+1, zipFilename);
}
};
xhr.ontimeout = (e) => { // XMLHttpRequest timed out
console.log("Timeout on fetching " + url + "!");
if (retry > 5) {

View File

@@ -1,7 +1,7 @@
/* The UI can also be run locally, but you have to set the IP of your devide accordingly.
* And you also might have to disable CORS in your webbrowser! */
var domainname_for_testing = "192.168.178.44";
var domainname_for_testing = "192.168.1.153";

View File

@@ -5,6 +5,7 @@
body {
height: 100%;
margin: 2px;
font-family: Arial, Helvetica, sans-serif;
}
.box {
@@ -33,19 +34,22 @@
<script type="text/javascript" src="common.js?v=$COMMIT_HASH"></script>
</head>
<body>
<h3>Todays Data</h3>
<h4>Last part of Todays Data</h4>
<div class="box">
<div class="row header">
<button onClick="reload();">Reload</button>
<button onClick="window.open(getDomainname() + '/datafileact');">Show full data</button>
<button onClick="window.location.href = getDomainname() + '/fileserver/log/data/'">Show older data files</button>
<button onClick="window.location.href = 'graph.html?v=$COMMIT_HASH'">Show graph</button>
<button onClick="reload();">Refresh</button>
<button onClick="window.open(getDomainname() + '/datafileact');">Show Full File</button>
<button onClick="window.location.href = getDomainname() + '/fileserver/log/data/'">Show Data Files</button>
<button onClick="window.location.href = 'graph.html?v=$COMMIT_HASH'">Show Graph</button>
</div>
<div class="row content" id="data"><br><br><br><b>Loading Data file, please wait...</b></div>
<div class="row footer">
<button onClick="reload();">Reload</button>
<button onClick="window.open(getDomainname() + '/datafileact');">Show full data</button>
<button onClick="window.location.href = getDomainname() + '/fileserver/log/data/'">Show older data files</button>
<button onClick="window.location.href = 'graph.html?v=$COMMIT_HASH'">Show graph</button>
<button onClick="reload();">Refresh</button>
<button onClick="window.open(getDomainname() + '/datafileact');">Show Full File</button>
<button onClick="window.location.href = getDomainname() + '/fileserver/log/data/'">Show Data Files</button>
<button onClick="window.location.href = 'graph.html?v=$COMMIT_HASH'">Show Graph</button>
<p></p>
</div>
</div>
</body>

View File

@@ -2,7 +2,7 @@
<html>
<head>
<link rel="icon" href="favicon.ico?v=$COMMIT_HASH" type="image/x-icon">
<title>Make Alignment</title>
<title>Alignment Marks</title>
<meta charset="utf-8"/>
<style>
@@ -48,7 +48,11 @@ select {
<body style="font-family: arial; padding: 0px 10px;">
<h2>Define Alignment Structure in Reference Image</h2>
<h2>Alignment Marks</h2>
<p>On this page you define two Reference Marks.
See <a href=https://jomjol.github.io/AI-on-the-edge-device-docs/Alignment/ target=_blank>https://jomjol.github.io/AI-on-the-edge-device-docs/Alignment/</a> for explanations.</p>
<p>After saving the Reference Marks, you can define the <a href=edit_digits.html>digit</a> resp. <a href=edit_analog.html>analog</a> ROI's.<br>
Only after those steps a reboot is required.</p>
<table>
<tr>
@@ -88,11 +92,9 @@ select {
<table>
<tr>
<td><input class="button" type="submit" name="saveroi" onclick="SaveToConfig()" value="Save"></td>
<td><input class="button" type="submit" name="saveroi" onclick="SaveToConfig()" value="Save">
<p>Proceed to update the <a href=edit_digits.html>digit</a> resp. <a href=edit_analog.html>analog</a> ROI's when you are done.</p></td>
</tr>
<tr>
<td><button class="button" id="reboot" type="button" onclick="doReboot()">Reboot to activate changes</button></td>
</tr>
</table>
<script type="text/javascript" src="common.js?v=$COMMIT_HASH"></script>
@@ -113,7 +115,7 @@ select {
param;
function doReboot() {
if (confirm("Are you sure you want to reboot? Did you save the config?")) {
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;
@@ -131,7 +133,7 @@ function SaveToConfig(){
WriteConfigININew();
UpdateConfigReference(domainname)
SaveConfigToServer(domainname);
firework.launch('Configuration got updated. Please reboot to activate changes!', 'success', 5000);
firework.launch('Configuration got updated. It will get applied after the next reboot!', 'success', 5000);
}
function EnhanceContrast(){

View File

@@ -3,7 +3,7 @@
<head>
<link rel="icon" href="favicon.ico?v=$COMMIT_HASH" type="image/x-icon">
<meta charset="utf-8"/>
<title>Make Analog Alignment</title>
<title>Analog ROI's</title>
<style>
h1 {font-size: 2em;}
@@ -67,10 +67,12 @@ th, td {
<body style="font-family: arial; padding: 0px 10px;">
<h2><input type="checkbox" id="Category_Analog_enabled" value="1" onclick = 'EnDisableAnalog()' checked >
Edit Analog</h2>
<h2>Analog ROI's</h2>
<p>On this page you define ROI's for the analog counters.
See <a href=https://jomjol.github.io/AI-on-the-edge-device-docs/ROI-Configuration/ target=_blank>https://jomjol.github.io/AI-on-the-edge-device-docs/ROI-Configuration/</a> for explanations.</p>
<input type="checkbox" id="Category_Analog_enabled" value="1" onclick = 'EnDisableAnalog()' checked ><label for="Category_Analog_enabled">Enable Analog ROI's</label></p>
<div id="div1">
<table>
<tr>
@@ -83,9 +85,6 @@ th, td {
<tr>
<class id="Numbers_text" style="color:black;"><b>Number: </b></class>
<select id="Numbers_value1" onchange="numberChanged()">
<option value="0" selected>default</option>
<option value="1" >NT</option>
<option value="2" >HT</option>
</select>
<input class="move" type="submit" id="renameNumber" name="renameNumber" onclick="renameNumber()" value="Rename">
<input class="move" type="submit" id="newNumber" name="newNumber" onclick="newNumber()" value="New">
@@ -103,8 +102,6 @@ th, td {
<tr>
<td>
<select id="index" name="index" onchange="ChangeSelection()" tabindex=1>
<option value="0" selected>ROI 0</option>
<option value="1" >ROI 1</option>
</select>
</td>
<td>
@@ -118,26 +115,26 @@ th, td {
<tr>
<td>x: <input type="number" name="refx" id="refx" step=1 onchange="valuemanualchanged()" tabindex=2></td>
<td>Δx: <input type="number" name="refdx" id="refdx" step=1 onchange="valuemanualchangeddx()" tabindex=4></td>
<td><label for="lockAR"> Lock aspect ratio: </label><input type="checkbox" id="lockAR" name="lockAR" value="1" onclick="changelockAR()" checked tabindex=6></td>
<td><input type="checkbox" id="lockAspectRatio" name="lockAspectRatio" value="1" onclick="changelockAspectRatio()" checked tabindex=6><label for="lockAspectRatio"> Lock aspect ratio</label></td>
</tr>
<tr>
<td>y: <input type="number" name="refy" id="refy" step=1 onchange="valuemanualchanged()" tabindex=3></td>
<td>Δy: <input type="number" name="refdy" id="refdy" step=1 onchange="valuemanualchanged()" tabindex=5></td>
<td><label for="lockSizes"> Synchronize Δx and Δy between ROIs</label><input type="checkbox" id="lockSizes" name="lockSizes" value="1" onclick="changelockSizes()" checked tabindex=7></td>
<td><input type="checkbox" id="lockSizes" name="lockSizes" value="1" onclick="changelockSizes()" checked tabindex=7><label for="lockSizes"> Synchronize Δx and Δy between ROIs</label></td>
</tr>
<tr>
<td colspan="2"></td>
<td><label for="CCW"> Counter-Clockwise Rotation: </label><input type="checkbox" id="CCW" name="CCW" value="0" onclick="changeCCW()" unchecked tabindex=8></td>
<td><input type="checkbox" id="CCW" name="CCW" value="0" onclick="changeCCW()" unchecked tabindex=8><label for="CCW"> Counter-Clockwise Rotation: </label></td>
</tr>
</table>
</div>
<table>
<tr>
<td><input class="button" type="submit" id="saveroi" name="saveroi" onclick="SaveToConfig()" value="Save" tabindex=9></td>
<td><input class="button" type="submit" id="saveroi" name="saveroi" onclick="SaveToConfig()" value="Save" tabindex=9>
</tr>
<tr>
<td><button class="button" id="reboot" type="button" onclick="doReboot()">Reboot to activate changes</button></td>
<td><button class="button" id="reboot" type="button" onclick="doReboot()">Reboot to activate the changes</button></td>
</tr>
</table>
@@ -157,13 +154,13 @@ th, td {
cofcat,
param,
enhanceCon = false;
lockAR = true;
lockSizes = true;
lockAspectRatio = true;
lockSizes = false;
domainname = getDomainname();
function doReboot() {
if (confirm("Are you sure you want to reboot? Did you save the config?")) {
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;
@@ -220,6 +217,7 @@ function deleteROI(){
aktindex = ROIInfo.length - 1;
}
UpdateROIs();
draw();
}
function newROI(){
@@ -257,12 +255,13 @@ function moveNext(){
UpdateROIs();
}
function changelockAR(){
lockAR = document.getElementById("lockAR").checked;
function changelockAspectRatio(){
lockAspectRatio = document.getElementById("lockAspectRatio").checked;
}
function changelockSizes(){
lockSizes = document.getElementById("lockSizes").checked;
UpdateROIs();
}
function changeCCW(){
@@ -281,7 +280,7 @@ function changeCCW(){
function ChangeSelection(){
aktindex = parseInt(document.getElementById("index").value);
// lockAR = true;
// lockAspectRatio = true;
UpdateROIs();
}
@@ -290,7 +289,7 @@ function SaveToConfig(){
cofcat["Analog"]["enabled"] = document.getElementById("Category_Analog_enabled").checked;
WriteConfigININew();
SaveConfigToServer(domainname);
firework.launch('Configuration got updated. Please reboot to activate changes!', 'success', 5000);
firework.launch('Configuration got updated. It will get applied after the next reboot!', 'success', 5000);
}
@@ -315,6 +314,7 @@ function UpdateROIs(_sel){
document.getElementById("newROI").disabled = false;
document.getElementById("deleteROI").disabled = true;
document.getElementById("index").disabled = true;
document.getElementById("saveroi").disabled = true;
document.getElementById("renameROI").disabled = true;
document.getElementById("moveNext").disabled = true;
document.getElementById("movePrevious").disabled = true;
@@ -359,7 +359,7 @@ function UpdateROIs(_sel){
document.getElementById("moveNext").disabled = true;
}
document.getElementById("lockAR").checked = lockAR;
document.getElementById("lockAspectRatio").checked = lockAspectRatio;
document.getElementById("lockSizes").checked = lockSizes;
document.getElementById("refx").value = ROIInfo[aktindex]["x"];
@@ -417,6 +417,29 @@ function UpdateROIs(_sel){
param = getConfigParameters();
cofcat = getConfigCategory();
UpdateNUMBERS();
/* Check if the ROIs have same dy and dx. If so, tick the sync checkbox */
var all_dx_dy_Identical = true;
if (ROIInfo.length > 1) {
for (var i = 1; i < (ROIInfo.length); ++i) { // 2nd .. last ROI
if (parseInt(ROIInfo[i].dx) != parseInt(ROIInfo[0].dx) ||
parseInt(ROIInfo[i].dy) != parseInt(ROIInfo[0].dy)) {
all_dx_dy_Identical = false;
break;
}
}
}
if (all_dx_dy_Identical) {
lockSizes = true;
console.log("All ROI have the same dX and dY, ticking the sync checkbox!");
document.getElementById("lockSizes").checked = lockSizes;
}
else {
console.log("Not all ROI have the same dX and dY, unticking the sync checkbox!");
}
drawImage();
draw();
}
@@ -508,11 +531,17 @@ function drawTextBG(context, txt, x, y, padding) {
if (typeof ROIInfo === 'undefined') { // During init, ROIInfo is not defined yet
return;
}
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
context.drawImage(imageObj, 0, 0);
if (ROIInfo.length == 0) {
return;
}
context.font = "15px Arial";
context.fillStyle = "red";
context.textAlign = "center";
@@ -538,7 +567,7 @@ function drawTextBG(context, txt, x, y, padding) {
{
if (_nb != _number)
{
lw = 1;
lw = 2;
context.lineWidth = lw;
context.strokeStyle = "#990000";
var x0 = parseInt(ROIInfo[_nb].x) - parseInt(lw/2);
@@ -547,12 +576,24 @@ function drawTextBG(context, txt, x, y, padding) {
var dy = parseInt(ROIInfo[_nb].dy) + parseInt(lw);
context.strokeRect(x0, y0, dx, dy);
drawTextBG(context, ROIInfo[_nb]["name"], x0+dx/2-0.5, y0-13, 5);
lw = 1;
var x0 = parseInt(ROIInfo[_nb].x) - parseInt(lw/2);
var y0 = parseInt(ROIInfo[_nb].y) - parseInt(lw/2);
var dx = parseInt(ROIInfo[_nb].dx) + parseInt(lw);
var dy = parseInt(ROIInfo[_nb].dy) + parseInt(lw);
context.strokeRect(x0, y0, dx, dy);
context.lineWidth = lw;
context.beginPath();
context.arc(x0+dx/2, y0+dy/2, dx/2, 0, 2 * Math.PI);
context.moveTo(x0+dx/2, y0);
context.lineTo(x0+dx/2, y0+dy);
context.moveTo(x0, y0+dy/2);
context.lineTo(x0+dx, y0+dy/2);
context.stroke();
}
}
lw = 4
context.lineWidth = lw;
context.strokeStyle = "#FF0000";
@@ -620,7 +661,7 @@ function drawTextBG(context, txt, x, y, padding) {
zw = getCoords(this)
if (lockAR) {
if (lockAspectRatio) {
rect.h = (e.pageY - zw.top) - rect.startY;
rect.w = Math.round(rect.h * ROIInfo[aktindex]["ar"]); }
else {
@@ -655,7 +696,7 @@ function drawTextBG(context, txt, x, y, padding) {
if (!drag) {
rect.w = document.getElementById("refdx").value;
rect.h = document.getElementById("refdy").value;
if (lockAR) {
if (lockAspectRatio) {
rect.w = Math.round(rect.h * ROIInfo[aktindex]["ar"]);
document.getElementById("refdx").value = rect.w;
}
@@ -670,7 +711,7 @@ function drawTextBG(context, txt, x, y, padding) {
if (!drag) {
rect.w = document.getElementById("refdx").value;
rect.h = document.getElementById("refdy").value;
if (lockAR) {
if (lockAspectRatio) {
rect.h = Math.round(rect.w / ROIInfo[aktindex]["ar"]);
document.getElementById("refdy").value = rect.h;
}

View File

@@ -63,7 +63,7 @@ function saveTextAsFile()
FileDeleteOnServer("/config/config.ini", domainname);
var textToSave = document.getElementById("inputTextToSave").value;
FileSendContent(textToSave, "/config/config.ini", domainname);
firework.launch('Configuration got updated. Please reboot to activate changes!', 'success', 5000);
firework.launch('Configuration got updated. It will get applied after the next reboot!', 'success', 5000);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,7 @@
<head>
<link rel="icon" href="favicon.ico?v=$COMMIT_HASH" type="image/x-icon">
<meta charset="utf-8"/>
<title>Make Digital Alignment</title>
<title>Digit ROI's</title>
<style>
h1 {font-size: 2em;}
@@ -58,8 +58,15 @@ th, td {
<body style="font-family: arial; padding: 0px 10px;">
<h2><input type="checkbox" id="Category_Digits_enabled" value="1" onclick = 'EnDisableDigits()' checked >
Edit Digits</h2>
<h2>Digit ROI's</h2>
<p>On this page you define ROI's for the digits.
See <a href=https://jomjol.github.io/AI-on-the-edge-device-docs/ROI-Configuration/ target=_blank>https://jomjol.github.io/AI-on-the-edge-device-docs/ROI-Configuration/</a> for explanations.</p>
<p><input type="checkbox" id="Category_Digits_enabled" value="1" onclick = 'EnDisableDigits()' checked><label for="Category_Digits_enabled">Enable Digit ROI's</label></p>
<p>After saving the digit ROI's, you can define the <a href=edit_analog.html>analog</a> ROI's if your meter has analog counters.<br>
Only after those steps a reboot is required.</p>
<div id="div1">
@@ -74,9 +81,6 @@ th, td {
<tr>
<class id="Numbers_text" style="color:black;"><b>Number:</b> </class>
<select id="Numbers_value1" onchange="numberChanged()">
<option value="0" selected>default</option>
<option value="1" >NT</option>
<option value="2" >HT</option>
</select>
<input class="move" type="submit" id="renameNumber" name="renameNumber" onclick="renameNumber()" value="Rename">
<input class="move" type="submit" id="newNumber" name="newNumber" onclick="newNumber()" value="New">
@@ -94,8 +98,6 @@ th, td {
<tr>
<td>
<select id="index" name="index" onchange="ChangeSelection()" tabindex=1>
<option value="0" selected>ROI 0</option>
<option value="1" >ROI 1</option>
</select>
</td>
<td>
@@ -108,7 +110,7 @@ th, td {
<tr>
<td>x: <input type="number" name="refx" id="refx" step=1 onchange="valuemanualchanged()" tabindex=2></td>
<td>Δx: <input type="number" name="refdx" id="refdx" step=1 onchange="valuemanualchangeddx()" tabindex=4></td>
<td><input type="checkbox" id="lockAR" name="lockAR" value="1" onclick="changelockAR()" checked tabindex=6><label for="lockAR"> Lock aspect ratio </label></td>
<td><input type="checkbox" id="lockAspectRatio" name="lockAspectRatio" value="1" onclick="changelockAspectRatio()" checked tabindex=6><label for="lockAspectRatio"> Lock aspect ratio </label></td>
</tr>
<tr>
<td>y: <input type="number" name="refy" id="refy" step=1 onchange="valuemanualchanged()" tabindex=3></td>
@@ -126,11 +128,9 @@ th, td {
<table>
<tr>
<td><input class="button" type="submit" id="saveroi" name="saveroi" onclick="SaveToConfig()" value="Save" tabindex=10></td>
<td><input class="button" type="submit" id="saveroi" name="saveroi" onclick="SaveToConfig()" value="Save" tabindex=10>
<p>Proceed to update the <a href=edit_analog.html>analog</a> ROI's when you are done or <a href=reboot_page.html>reboot</a> if there are no analogue counters.</p></td>
</tr>
<tr>
<td><button class="button" id="reboot" type="button" onclick="doReboot()">Reboot to activate changes</button></td>
</tr>
</table>
<script type="text/javascript" src="common.js?v=$COMMIT_HASH"></script>
@@ -149,14 +149,14 @@ th, td {
cofcat,
param,
enhanceCon = false;
lockAR = true;
lockSizes = true;
lockAspectRatio = true;
lockSizes = false;
lockSpaceEquidistant = true;
space = 3;
domainname = getDomainname();
function doReboot() {
if (confirm("Are you sure you want to reboot? Did you save the config?")) {
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;
@@ -264,12 +264,14 @@ function moveNext(){
valuemanualchanged();
}
function changelockAR(){
lockAR = document.getElementById("lockAR").checked;
function changelockAspectRatio(){
lockAspectRatio = document.getElementById("lockAspectRatio").checked;
}
function changelockSizes(){
lockSizes = document.getElementById("lockSizes").checked;
UpdateROIs();
valuemanualchangedspace();
if (!lockSizes) {
firework.launch("For best results it is in most cases advised to keep the y, Δx and Δy identical!", 'warning', 10000);
@@ -278,11 +280,18 @@ function changelockSizes(){
function changeLockSpaceEquidistant(){
lockSpaceEquidistant = document.getElementById("lockSpaceEquidistant").checked;
if (!lockSpaceEquidistant) {
document.getElementById("space").disabled = true;
}
else {
document.getElementById("space").disabled = false;
}
UpdateROIs();
}
function ChangeSelection(){
aktindex = parseInt(document.getElementById("index").value);
// lockAR = true;
// lockAspectRatio = true;
UpdateROIs();
}
@@ -291,7 +300,7 @@ function SaveToConfig(){
cofcat["Digits"]["enabled"] = document.getElementById("Category_Digits_enabled").checked;
WriteConfigININew();
SaveConfigToServer(domainname);
firework.launch('Configuration got updated. Please reboot to activate changes!', 'success', 5000);
firework.launch('Configuration got updated. It will get applied after the next reboot!', 'success', 5000);
}
@@ -362,10 +371,16 @@ function UpdateROIs(_sel){
document.getElementById("moveNext").disabled = true;
}
document.getElementById("lockAR").checked = lockAR;
document.getElementById("lockAspectRatio").checked = lockAspectRatio;
document.getElementById("lockSizes").checked = lockSizes;
document.getElementById("lockSpaceEquidistant").checked = lockSpaceEquidistant;
document.getElementById("space").value = space;
if (!lockSpaceEquidistant) {
document.getElementById("space").disabled = true;
}
else {
document.getElementById("space").disabled = false;
}
document.getElementById("refx").value = ROIInfo[aktindex]["x"];
document.getElementById("refy").value = ROIInfo[aktindex]["y"];
@@ -418,8 +433,53 @@ function UpdateROIs(_sel){
cofcat = getConfigCategory();
UpdateNUMBERS();
space = ROIInfo[1].x - parseInt(ROIInfo[0].x) - parseInt(ROIInfo[0].dx);
document.getElementById("space").value = space;
/* Check if the ROIs are equidistant. Only if not, untick the checkbox */
if (ROIInfo.length > 1) {
var distanceROI0_to_ROI1 = parseInt(ROIInfo[1].x) - (parseInt(ROIInfo[0].x) + parseInt(ROIInfo[0].dx)); // Distance between 1st and 2nd ROI
//console.log("0->1: " + distanceROI0_to_ROI1);
for (var i = 1; i < (ROIInfo.length - 1); ++i) { // 2nd .. 2nd-last ROI
//console.log(i + "->" + i+1 + ": " + (parseInt(ROIInfo[i+1].x) - (parseInt(ROIInfo[i].x) + parseInt(ROIInfo[i].dx))));
if (distanceROI0_to_ROI1 != (parseInt(ROIInfo[i+1].x) - (parseInt(ROIInfo[i].x) + parseInt(ROIInfo[i].dx)))) {
console.log("Not equidistant, unticking the checkbox!");
lockSpaceEquidistant = false;
document.getElementById("lockSpaceEquidistant").checked = lockSpaceEquidistant;
if (!lockSpaceEquidistant) {
document.getElementById("space").disabled = true;
}
else {
document.getElementById("space").disabled = false;
}
break;
}
}
}
/* Check if the ROIs have same y, dy and dx. If so, tick the sync checkbox */
var all_y_dx_dy_Identical = true;
if (ROIInfo.length > 1) {
for (var i = 1; i < (ROIInfo.length); ++i) { // 2nd .. last ROI
if (parseInt(ROIInfo[i].y) != parseInt(ROIInfo[0].y) ||
parseInt(ROIInfo[i].dx) != parseInt(ROIInfo[0].dx) ||
parseInt(ROIInfo[i].dy) != parseInt(ROIInfo[0].dy)) {
all_y_dx_dy_Identical = false;
break;
}
}
}
if (all_y_dx_dy_Identical) {
lockSizes = true;
console.log("All ROI have the same Y, dX and dY, ticking the sync checkbox!");
document.getElementById("lockSizes").checked = lockSizes;
}
else {
console.log("Not all ROI have the same Y, dX and dY, unticking the sync checkbox!");
}
if (ROIInfo.length > 1) {
space = ROIInfo[1].x - parseInt(ROIInfo[0].x) - parseInt(ROIInfo[0].dx);
document.getElementById("space").value = space;
}
drawImage();
draw();
@@ -553,8 +613,19 @@ function draw() {
var dy = parseInt(ROIInfo[_nb].dy) + parseInt(lw);
context.strokeRect(x0, y0, dx, dy);
drawTextBG(context, ROIInfo[_nb]["name"], x0+dx/2, y0-12, 5);
}
lw = 1
var x0 = parseInt(ROIInfo[_nb].x) - parseInt(lw/2);
var y0 = parseInt(ROIInfo[_nb].y) - parseInt(lw/2);
var dx = parseInt(ROIInfo[_nb].dx) + parseInt(lw);
var dy = parseInt(ROIInfo[_nb].dy) + parseInt(lw);
context.lineWidth = lw;
context.beginPath();
context.moveTo(x0, y0+dy/2);
context.lineTo(x0+dx, y0+dy/2);
context.stroke();
context.strokeRect(x0+dx*0.2, y0+dy*0.2, dx*0.6, dy*0.6);
}
}
lw = 4
@@ -630,7 +701,7 @@ function draw() {
return;
}
if (lockAR) {
if (lockAspectRatio) {
rect.h = (e.pageY - zw.top) - rect.startY;
rect.w = Math.round(rect.h * ROIInfo[aktindex]["ar"]); }
else {
@@ -670,7 +741,7 @@ function draw() {
rect.w = document.getElementById("refdx").value;
rect.h = document.getElementById("refdy").value;
if (lockAR) {
if (lockAspectRatio) {
rect.w = Math.round(rect.h * ROIInfo[aktindex]["ar"]);
document.getElementById("refdx").value = rect.w;
}
@@ -695,7 +766,7 @@ function draw() {
rect.w = document.getElementById("refdx").value;
rect.h = document.getElementById("refdy").value;
if (lockAR) {
if (lockAspectRatio) {
rect.h = Math.round(rect.w / ROIInfo[aktindex]["ar"]);
document.getElementById("refdy").value = rect.h;
}

View File

@@ -28,12 +28,12 @@ p {font-size: 1em;}
<p>
This is the first time you started the digitizer after the initial installation. You have been automatically routed to the <b>initial setup procedure</b>.
Here you adjust the settings for your counter within five steps. In the final step the inital setup will be disabled and it will restart to the normal mode.
All settings will also be accessible there.
<br>
<br>
You can navigate forward and backward during the setup with the buttons "Next" and "Previous".<br><br>
<span color=red>Do not forget to save in each step!</span><br>
Here you adjust the settings for your meter within five steps. In the final step the inital setup will be disabled and it will restart to the normal mode.
All settings will also be accessible there. See <a href=https://jomjol.github.io/AI-on-the-edge-device-docs/initial-setup target=_blank>
https://jomjol.github.io/AI-on-the-edge-device-docs/initial-setup</a> for additional explanations.</p>
</p>
<p>You can navigate forward and backward during the setup with the buttons "Next" and "Previous".<br><br>
<span color=red>Do not forget to save in each step before heading to another step!</span><br>
</p>
<p>
This is an overview over the five steps:
@@ -41,26 +41,27 @@ p {font-size: 1em;}
<p>
<ol>
<li>Create reference image <br>
Base for the position referencing and the identification of the digits and counters.</li>
<li>Define two unique references <br>
Used to align the individual camera shot and identify the absolut positions</li>
<li>Define the digits <br>
Digital digits to be recognized</li>
<li>Define the analog counters <br>
Analog counters to be identified</li>
<li>General settings <br>
Most settings can be used with default value at the beginning. MQTT connection can be specified here</li>
<li><p>Create the <b>Reference Image</b>.<br>
It is the base for the position referencing and the identification of the digits and counters.</p></li>
<li><p>Define two unique <b>Reference Marks</b>.<br>
They is used to align the individual camera images and identify the absolut positions.</p></li>
<li></p>Define <b>ROI's</b> for the <b>Digits</b>.<br>
They will be used to digitize the digit part of your meter.<br>
If your meter has no digits, this step can be skipped.</p></li>
<li>Define <b>ROI's</b>> for the <b>Analog Counters</b>.<br>
They will be used to digitize the analog part of your meter.<br>
If your meter has no analog counters, this step can be skipped.</p></li>
<li><p><b>General Settings</b><br>
Further configuration of your device.</p></li>
</ol>
<p>
After step 5 setup mode is completed, it then reboots and starts into the normal mode.
<br><br>
<p>Please be patient when switching to another step. The device takes some time to load all needed information!</p>
If you need support, have a look to the <a href=https://jomjol.github.io/AI-on-the-edge-device-docs target=_blank>documenation</a> or the <a href=https://github.com/jomjol/AI-on-the-edge-device/discussions target=_blank>discussion</a> pages.<br>
<p>After step 5 the setup is completed, you then can reboot and starts into the normal operation mode.</p>
<h4>Have fun with the digitizer!</h4>
<p>If you need support, have a look to the <a href=https://jomjol.github.io/AI-on-the-edge-device-docs target=_blank>documentation</a> or the <a href=https://github.com/jomjol/AI-on-the-edge-device/discussions target=_blank>discussion</a> pages.</p>
<p><b>Have fun with your digitizer!</b></p>
</body>
</html>

View File

@@ -28,16 +28,11 @@ p {font-size: 1em;}
<body style="font-family: arial">
<h4>Finished!</h4>
<h4>Step 6: Setup Completed!</h4>
<p>
Now you are finished with the setup and ready to reboot to the normal mode.
<br>
Once you have pushed below button, the setup modus will be left and the digitizer will restart to normal operation mode.
<br>
The Web Interface will automatically reload. It will take two to four minutes, until you get the first reading.
<br>
All settings can be changed as well in the configuration menu in the Normal modus.
<p>Congratulations, you completed the setup and are now ready to reboot to the normal mode!</p>
<p>Once you have pushed the button below, the setup modus will be left and the digitizer will restart to normal operation mode.<br>
The Web Interface will automatically reload. It will take some minutes until you get the first reading.
</p>
<p>

View File

@@ -2,7 +2,7 @@
<html>
<head>
<link rel="icon" href="favicon.ico?v=$COMMIT_HASH" type="image/x-icon">
<title>Make Reference</title>
<title>Reference Image</title>
<meta charset="utf-8"/>
<style>
@@ -36,8 +36,12 @@ table {
</head>
<body style="font-family: arial; padding: 0px 10px;">
<h2>Create Reference out of Raw Image</h2>
<p><b>Note: After saving a new Reference Image, make sure to update the Alignment Marks and the ROI's and reboot once!</b></p>
<h2>Reference Image</h2>
<p>On this page you define the Reference Image.
See <a href=https://jomjol.github.io/AI-on-the-edge-device-docs/Reference-Image/ target=_blank>https://jomjol.github.io/AI-on-the-edge-device-docs/Reference-Image/</a> for explanations.</p>
<p>After saving a new Reference Image, make sure to update the <a href=edit_alignment.html>Alignment Marks</a> and then
the <a href=edit_digits.html>digit</a> resp. <a href=edit_analog.html>analog</a> ROI's.<br>
Only after those steps a reboot is required.</p>
<table>
<tr>
@@ -49,8 +53,8 @@ table {
<td style="padding-top: 10px"><label for="mirror" id="labelmirror">Mirror Image:</label></td>
<td style="padding-top: 10px"><input type="checkbox" id="mirror" name="mirror" value="1" onchange="drawRotated()"></td>
<td>
<class id="MakeImage_LEDIntensity_text" style="color:black;">LEDIntensity: </class>
<input type="number" id="MakeImage_LEDIntensity_value1" size="13" value=0 min="0" max="100" style="float: right; clear: both;">
<class id="TakeImage_LEDIntensity_text" style="color:black;">LEDIntensity: </class>
<input type="number" id="TakeImage_LEDIntensity_value1" size="13" value=0 min="0" max="100" style="float: right; clear: both;">
</td>
</tr>
<tr>
@@ -58,8 +62,8 @@ table {
<td><label for="flip" id="labelflip">Flip Image Size:</label></td>
<td><input type="checkbox" id="flip" name="flip" value="1" onchange="drawRotated()"></td>
<td>
<class id="MakeImage_Brightness_text" style="color:black;">Brightness: </class>
<input type="number" id="MakeImage_Brightness_value1" size="13" value=0 min="-2" max="2" style="float: right; clear: both;">
<class id="TakeImage_Brightness_text" style="color:black;">Brightness: </class>
<input type="number" id="TakeImage_Brightness_value1" size="13" value=0 min="-2" max="2" style="float: right; clear: both;">
</td>
</tr>
@@ -67,16 +71,16 @@ table {
<td><label for="mirror">Pre-rotate Angle:</label></td>
<td><input type="number" id="prerotateangle" name="prerotateangle" value="0" min="-360" max="360" onchange="drawRotated()">Degrees</td>
<td>
<class id="MakeImage_Contrast_text" style="color:black;">Contrast</class>
<input type="number" id="MakeImage_Contrast_value1" size="13" value=0 min="-2" max="2" style="float: right; clear: both;">
<class id="TakeImage_Contrast_text" style="color:black;">Contrast</class>
<input type="number" id="TakeImage_Contrast_value1" size="13" value=0 min="-2" max="2" style="float: right; clear: both;">
</td>
</tr>
<tr>
<td><label for="mirror">Fine Alignment:</label></td>
<td><input type="number" id="finerotate" name="finerotate" value=0.0 min="-1" max="1" step="0.2" onchange="drawRotated()">Degrees</td>
<td>
<class id="MakeImage_Saturation_text" style="color:black;">Saturation</class>
<input type="number" id="MakeImage_Saturation_value1" size="13" value=0 min="-2" max="2" style="float: right; clear: both;">
<class id="TakeImage_Saturation_text" style="color:black;">Saturation</class>
<input type="number" id="TakeImage_Saturation_value1" size="13" value=0 min="-2" max="2" style="float: right; clear: both;">
</td>
</tr>
@@ -87,10 +91,8 @@ table {
<td><canvas id="canvas"></canvas></td>
</tr>
<tr>
<td><input class="button" type="button" id="updatereferenceimage" value="Save" onclick="SaveReference()"></td>
</tr>
<tr>
<td><button class="button" id="reboot" type="button" onclick="doReboot()">Reboot to activate saved Reference</button></td>
<td><input class="button" type="button" id="updatereferenceimage" value="Save" onclick="SaveReference()">
<p>Proceed to update the <a href=edit_alignment.html>Alignment Marks</a> when you are done.</p></td>
</tr>
</table>
@@ -121,15 +123,15 @@ table {
function doTake(){
var xhttp = new XMLHttpRequest();
if (param["MakeImage"]["Brightness"].found && param["MakeImage"]["Brightness"].enabled)
if (param["TakeImage"]["Brightness"].found && param["TakeImage"]["Brightness"].enabled)
{
_intensity = document.getElementById("MakeImage_LEDIntensity_value1").value;
_intensity = document.getElementById("TakeImage_LEDIntensity_value1").value;
if (_intensity == "") _intensity = "50";
_brightness = document.getElementById("MakeImage_Brightness_value1").value;
_contrast = document.getElementById("MakeImage_Contrast_value1").value;
_saturation = document.getElementById("MakeImage_Saturation_value1").value;
_brightness = document.getElementById("TakeImage_Brightness_value1").value;
_contrast = document.getElementById("TakeImage_Contrast_value1").value;
_saturation = document.getElementById("TakeImage_Saturation_value1").value;
url = getDomainname() + "/editflow?task=test_take&bri=" + _brightness;
url = url + "&con=" + _saturation + "&sat=" + _saturation + "&int=" + _intensity;
url = url + "&con=" + _contrast + "&sat=" + _saturation + "&int=" + _intensity;
}
else
{
@@ -166,24 +168,24 @@ table {
document.getElementById("labelflip").style = "color:lightgrey;";
}
if (param["MakeImage"]["Brightness"].found && param["MakeImage"]["Brightness"].enabled)
if (param["TakeImage"]["Brightness"].found && param["TakeImage"]["Brightness"].enabled)
{
document.getElementById("MakeImage_Brightness_value1").disabled = false;
document.getElementById("MakeImage_Contrast_value1").disabled = false;
document.getElementById("MakeImage_Saturation_value1").disabled = false;
document.getElementById("MakeImage_LEDIntensity_value1").disabled = false;
document.getElementById("TakeImage_Brightness_value1").disabled = false;
document.getElementById("TakeImage_Contrast_value1").disabled = false;
document.getElementById("TakeImage_Saturation_value1").disabled = false;
document.getElementById("TakeImage_LEDIntensity_value1").disabled = false;
}
else
{
document.getElementById("MakeImage_Brightness_value1").type = "hidden";
document.getElementById("MakeImage_Brightness_text").style.visibility = "hidden";
document.getElementById("TakeImage_Brightness_value1").type = "hidden";
document.getElementById("TakeImage_Brightness_text").style.visibility = "hidden";
}
// if (param["MakeImage"]["Saturation"].found)
// document.getElementById("MakeImage_Saturation_value1").disabled = false;
// if (param["MakeImage"]["Contrast"].found)
// document.getElementById("MakeImage_Contrast_value1").disabled = false;
// if (param["TakeImage"]["Saturation"].found)
// document.getElementById("TakeImage_Saturation_value1").disabled = false;
// if (param["TakeImage"]["Contrast"].found)
// document.getElementById("TakeImage_Contrast_value1").disabled = false;
isActReference = false;
loadCanvas(url);
@@ -205,16 +207,16 @@ table {
document.getElementById("prerotateangle").disabled = true;
document.getElementById("updatereferenceimage").disabled = true;
document.getElementById("take").disabled = true;
document.getElementById("MakeImage_Brightness_value1").disabled = true;
document.getElementById("MakeImage_Saturation_value1").disabled = true;
document.getElementById("MakeImage_Contrast_value1").disabled = true;
document.getElementById("MakeImage_LEDIntensity_value1").disabled = true;
document.getElementById("TakeImage_Brightness_value1").disabled = true;
document.getElementById("TakeImage_Saturation_value1").disabled = true;
document.getElementById("TakeImage_Contrast_value1").disabled = true;
document.getElementById("TakeImage_LEDIntensity_value1").disabled = true;
document.getElementById("mirror").disabled = false;
document.getElementById("flip").disabled = false;
if (!(param["MakeImage"]["Brightness"].found))
if (!(param["TakeImage"]["Brightness"].found))
{
document.getElementById("MakeImage_Brightness_value1").type = "hidden";
document.getElementById("MakeImage_Brightness_text").style.visibility = "hidden";
document.getElementById("TakeImage_Brightness_value1").type = "hidden";
document.getElementById("TakeImage_Brightness_text").style.visibility = "hidden";
}
@@ -236,7 +238,7 @@ table {
}
function SaveReference(){
if (confirm("Are you sure you want to update the reference image?")) {
if (confirm("Are you sure you want to update the Reference Image?")) {
param["Alignment"]["InitialRotate"].value1 = document.getElementById("prerotateangle").value;
if ((param["Alignment"]["InitialMirror"].found == true) && (document.getElementById("mirror").checked))
@@ -253,12 +255,12 @@ table {
else
param["Alignment"]["FlipImageSize"].value1 = "false";
if (param["MakeImage"]["Brightness"].found && param["MakeImage"]["Brightness"].enabled)
if (param["TakeImage"]["Brightness"].found && param["TakeImage"]["Brightness"].enabled)
{
ReadParameter(param, "MakeImage", "Brightness", false);
ReadParameter(param, "MakeImage", "Contrast", false);
ReadParameter(param, "MakeImage", "Saturation", false);
ReadParameter(param, "MakeImage", "LEDIntensity", false);
ReadParameter(param, "TakeImage", "Brightness", false);
ReadParameter(param, "TakeImage", "Contrast", false);
ReadParameter(param, "TakeImage", "Saturation", false);
ReadParameter(param, "TakeImage", "LEDIntensity", false);
}
var canvas = document.getElementById("canvas");
@@ -270,7 +272,7 @@ table {
SaveCanvasToImage(canvas, "/config/reference.jpg", true, getDomainname());
showReference(param);
UpdatePage();
firework.launch('Reference got updated. Please reboot to activate changes!', 'success', 5000);
firework.launch('Reference got updated. It will get applied after the next reboot!', 'success', 5000);
}
}
@@ -307,30 +309,30 @@ table {
ParseConfig();
param = getConfigParameters();
param["MakeImage"]["LEDIntensity"]["enabled"] = true;
param["MakeImage"]["Brightness"]["enabled"] = true;
param["MakeImage"]["Contrast"]["enabled"] = true;
param["MakeImage"]["Saturation"]["enabled"] = true;
param["TakeImage"]["LEDIntensity"]["enabled"] = true;
param["TakeImage"]["Brightness"]["enabled"] = true;
param["TakeImage"]["Contrast"]["enabled"] = true;
param["TakeImage"]["Saturation"]["enabled"] = true;
if (!param["MakeImage"]["LEDIntensity"]["found"])
if (!param["TakeImage"]["LEDIntensity"]["found"])
{
param["MakeImage"]["LEDIntensity"]["found"] = true;
param["MakeImage"]["LEDIntensity"]["value1"] = "50";
param["TakeImage"]["LEDIntensity"]["found"] = true;
param["TakeImage"]["LEDIntensity"]["value1"] = "50";
}
if (!param["MakeImage"]["Brightness"]["found"])
if (!param["TakeImage"]["Brightness"]["found"])
{
param["MakeImage"]["Brightness"]["found"] = true;
param["MakeImage"]["Brightness"]["value1"] = "0";
param["TakeImage"]["Brightness"]["found"] = true;
param["TakeImage"]["Brightness"]["value1"] = "0";
}
if (!param["MakeImage"]["Contrast"]["found"])
if (!param["TakeImage"]["Contrast"]["found"])
{
param["MakeImage"]["Contrast"]["found"] = true;
param["MakeImage"]["Contrast"]["value1"] = "0";
param["TakeImage"]["Contrast"]["found"] = true;
param["TakeImage"]["Contrast"]["value1"] = "0";
}
if (!param["MakeImage"]["Saturation"]["found"])
if (!param["TakeImage"]["Saturation"]["found"])
{
param["MakeImage"]["Saturation"]["found"] = true;
param["MakeImage"]["Saturation"]["value1"] = "0";
param["TakeImage"]["Saturation"]["found"] = true;
param["TakeImage"]["Saturation"]["value1"] = "0";
}
UpdateInput();
@@ -338,10 +340,10 @@ table {
}
function UpdateInput() {
WriteParameter(param, category, "MakeImage", "Brightness", false);
WriteParameter(param, category, "MakeImage", "Contrast", false);
WriteParameter(param, category, "MakeImage", "Saturation", false);
WriteParameter(param, category, "MakeImage", "LEDIntensity", false);
WriteParameter(param, category, "TakeImage", "Brightness", false);
WriteParameter(param, category, "TakeImage", "Contrast", false);
WriteParameter(param, category, "TakeImage", "Saturation", false);
WriteParameter(param, category, "TakeImage", "LEDIntensity", false);
}

View File

@@ -20,12 +20,14 @@ p {font-size: 1em;}
<body style="font-family: arial">
<h4>Reference Image</h4>
<h4>Step 1: Define a Reference Image</h4>
The reference image is the base to define the digits, counters and references positions.
<p>
Firstly you see the default image. Use the Button "Create New Reference" to start to create your own reference image.<br>
Most important feature is a straight alignment of the image. Use the Pre-rotate angle and the fine alignment to adjust the rotation of the image<br>
Store the reference image by pushing <b>"Update Reference Image"</b>. A reboot is not yet required.
Most important feature is a straight alignment of the image. Use the Pre-rotate angle and the fine alignment to adjust the rotation of the image.
</p>
<p>
Don't forget to save your changes with the <b>"Save"</b> button!
</p>
</body>

View File

@@ -19,15 +19,15 @@ p {font-size: 1em;}
<body style="font-family: arial">
<h4>Alignment References</h4>
Two opposite alignment references are needed to identify unique fix points on the image.
<h4>Step 2: Define two Alignment Marks</h4>
Two opposite alignment marks are needed to identify unique fix points on the image.
<p>
Mark the reference by drag and dop with the mouse or with the coordinates and push <b>"Update Reference"</b>.
<br>
You can switch between the two reference with <b>"Select Reference"</b>.
</p>
<p>
Don't forget to save your changes! A reboot is not yet required.
Don't forget to save your changes with the <b>"Save"</b> button!
</p>
</body>

View File

@@ -20,7 +20,7 @@ p {font-size: 1em;}
<body style="font-family: arial">
<h4>Define Digits</h4>
<h4>Step 3: Define ROI's for your Digits</h4>
Here you define your digits you want to read. If you have more than one number on the reading you can define several numbers with the <b>"Number"</b> selector. There you can also define new numbers.
<p>
With the drop down menue <b>"ROI x"</b> you can change between the different digits. Mark them with the mouse or the coordinates.
@@ -29,7 +29,7 @@ Here you define your digits you want to read. If you have more than one number o
You can change it with <b>"move Next" / "move Previous"</b>.
</p>
<p>
Don't forget to save your changes! A reboot is not yet required.
Don't forget to save your changes with the <b>"Save"</b> button!
</p>
</body>

View File

@@ -19,7 +19,7 @@ p {font-size: 1em;}
<body style="font-family: arial">
<h4>Define Digits</h4>
<h4>Step 4: Define ROI's for your Analog Counters</h4>
Here you define your analog counters you want to read. If you have more than one number on the reading you can define several numbers with the <b>"Number"</b> selector. There you can also define new numbers. If you do not have analog counters delete all ROIs.
<p>
With the drop down menue <b>"ROI x"</b> you can change between the different counters. Mark them with the mouse or the coordinates.
@@ -28,7 +28,7 @@ Here you define your analog counters you want to read. If you have more than one
You can change it with <b>"move Next" / "move Previous"</b>.
</p>
<p>
Don't forget to save your changes with <b>"Save all to Config.ini"</b>! A reboot is not yet required.
Don't forget to save your changes with the <b>"Save"</b> button!
</p>
</body>

View File

@@ -19,12 +19,12 @@ p {font-size: 1em;}
<body style="font-family: arial">
<h4>General configuration parameters</h4>
<p>Here you can define additional settings. The settings should fit for a normal setup.</p>
<p>You will also be availabl</p>
<h4>Step 5: General Configuration Settings</h4>
<p>Here you can define additional settings. The default settings should fit for a normal/initial setup.</p>
<p>You will also be able to change them later, so don't worry if you do not understand the parameters yet!</p>
</p>
<p>
Don't forget to save your changes!
<br><span color=red>You should not reboot here, but leave the setup modus on the next page!</span>
Don't forget to save your changes with the <b>"Save"</b> button!
</p>
</body>

View File

@@ -1,26 +1 @@
<!DOCTYPE html>
<html style="width: fit-content">
<head>
<link rel="icon" href="favicon.ico?v=$COMMIT_HASH" type="image/x-icon">
<title>AI on the edge</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
h1 {font-size: 2em; margin-block-end: 0.3em;}
h2 {font-size: 1.5em;margin-block-start: 0.3em;}
h3 {font-size: 1.2em;}
p {font-size: 1em;}
</style>
</head>
<body style="font-family: arial">
<h4>Finished!</h4>
Read below!
</body>
</html>
<!-- This page is never shown -->

View File

@@ -13,7 +13,7 @@ function getbasepath(){
{
// host = "http://192.168.2.219"; // jomjol interner test
// host = "http://192.168.178.46"; // jomjol interner test
host = "http://192.168.178.62"; // jomjol interner Real
host = "http://192.168.178.44"; // jomjol interner Real
// host = "http://192.168.43.191";
// host = "."; // jomjol interner localhost

1
sd-card/html/github.min.css vendored Normal file
View File

@@ -0,0 +1 @@
.hljs{display:block;overflow-x:auto;padding:.5em;color:#333;background:#f8f8f8}.hljs-comment,.hljs-quote{color:#998;font-style:italic}.hljs-keyword,.hljs-selector-tag,.hljs-subst{color:#333;font-weight:700}.hljs-literal,.hljs-number,.hljs-tag .hljs-attr,.hljs-template-variable,.hljs-variable{color:teal}.hljs-doctag,.hljs-string{color:#d14}.hljs-section,.hljs-selector-id,.hljs-title{color:#900;font-weight:700}.hljs-subst{font-weight:400}.hljs-class .hljs-title,.hljs-type{color:#458;font-weight:700}.hljs-attribute,.hljs-name,.hljs-tag{color:navy;font-weight:400}.hljs-link,.hljs-regexp{color:#009926}.hljs-bullet,.hljs-symbol{color:#990073}.hljs-built_in,.hljs-builtin-name{color:#0086b3}.hljs-meta{color:#999;font-weight:700}.hljs-deletion{background:#fdd}.hljs-addition{background:#dfd}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}

View File

@@ -1,21 +1,138 @@
<html>
<head>
<script type="text/javascript" src='plotly-2.14.0.min.js?v=$COMMIT_HASH'></script>
<script type="text/javascript" src='plotly-basic-2.18.2.min.js?v=$COMMIT_HASH'></script>
<script type="text/javascript" src="common.js?v=$COMMIT_HASH"></script>
<script type="text/javascript" src="readconfigcommon.js?v=$COMMIT_HASH"></script>
<script type="text/javascript" src="readconfigparam.js?v=$COMMIT_HASH"></script>
<style>
textarea {
width: 600px;
height: 300px;
body {
font-family: Arial, Helvetica, sans-serif;
}
</style>
<script>
function run() {
var el = document.getElementById('cnsl');
el && eval(el.value);
datefile = document.getElementById("datafiles").value;
numbername = document.getElementById("numbers").value;
showRrelativeValues = document.getElementById("showRrelativeValues").checked;
//alert("Auslesen: " + datefile + " " + numbername);
_domainname = getDomainname();
fetch(_domainname + '/fileserver/log/data/' + datefile)
.then(response => {
// handle the response
if (response.status == 404) {
firework.launch("No log data available for " + dateString, 'warning', 10000);
}
response.text()
.then( result => {
var lines = result.split("\n");
var traceValue = { x: [], y: [], type: 'scatter', line: {width: 6}, name: 'Value'};
var tracePreValue = { x: [], y: [], type: 'scatter', line: {width: 2}, name: 'Previous Value', visible: 'legendonly'};
var traceChangeRate = { x: [], y: [], type: 'bar', yaxis: 'y2', opacity: 0.2, name: 'Change Rate'};
var traceChangeAbsolute = { x: [], y: [], type: 'bar', yaxis: 'y2', opacity: 0.2, name: 'Change Absolute', visible: 'legendonly'};
var timex = 1;
for (let line of lines) {
{
//console.log(line);
if (line.split(",")[1] == numbername)
{
var value = line.split(",")[3];
var preValue = line.split(",")[4];
var changeRate = line.split(",")[5];
var changeAbsolute = line.split(",")[6];
var time = line.split(",")[0];
//console.log("> "+time+" "+value+"\n");
traceValue.x.push(time);
/* Catch empty fields */
if (value == "" || isNaN(value)) {
value = NaN;
}
if (preValue == "" || isNaN(preValue)) {
preValue = NaN;
}
if (changeRate == "" || isNaN(changeRate)) {
changeRate = NaN;
}
if (changeAbsolute == "" || isNaN(changeAbsolute)) {
changeAbsolute = NaN;
}
traceValue.y.push(value);
tracePreValue.y.push(preValue);
traceChangeRate.y.push(changeRate);
traceChangeAbsolute.y.push(changeAbsolute);
}
}
}
//console.log(trace);
// Copy time to all traces
tracePreValue.x = traceValue.x;
traceChangeRate.x = traceValue.x;
traceChangeAbsolute.x = traceValue.x;
//console.log(traceValue.y);
var offsetValue = traceValue.y[0];
var offsetPreValue = tracePreValue.y[0];
traceValue.connectgaps = true;
if (showRrelativeValues) {
traceValue.y.forEach(function(part, index, arr) {
arr[index] = arr[index] - offsetValue;
});
tracePreValue.y.forEach(function(part, index, arr) {
arr[index] = arr[index] - offsetPreValue;
});
}
// console.log(traceValue.x)
var data = [traceValue, tracePreValue, traceChangeRate, traceChangeAbsolute];
var layout = {
showlegend: true,
colorway: ['green', 'black', 'blue', 'black'],
yaxis: {title: 'Value'},
yaxis2: {
title: 'Change',
overlaying: 'y',
side: 'right'
},
margin: {
l: 50,
r: 50,
b: 50,
t: 50,
pad: 4
},
legend: {
x: 0.2,
y: 0.9,
xanchor: 'right'
}
};
document.getElementById("chart").innerHTML = "";
Plotly.newPlot('chart', data, layout, {displayModeBar: true});
});
}).catch((error) => {
// handle the error
console.log(error);
});
}
</script>
<link href="firework.css?v=$COMMIT_HASH" rel="stylesheet">
@@ -23,67 +140,16 @@
<script type="text/javascript" src="firework.js?v=$COMMIT_HASH"></script>
</head>
<body>
<div id='chart'></div>
<h3>Data Graph</h3>
<div id='chart'><p>Loading...<br></p></div>
<select id="datafiles" onchange="run();"></select>
<select id="numbers" onchange="run();"></select>
<select id="datatype" onchange="run();">
<option value="3">Value</option>
<option value="4">PreValue</option>
<option value="5">Change-Rate</option>
<option value="6">Change-Absolut</option>
</select>
<input type="checkbox" id="showRrelativeValues" onclick = 'run();' unchecked ><label for="showRrelativeValues">Show relative values</label>
<button onclick="run();">Refresh</button>
&nbsp;&nbsp;|&nbsp;&nbsp;
<button onClick="window.location.href = 'data.html?v=$COMMIT_HASH'">Show data</button>
<button onClick="window.location.href = getDomainname() + '/fileserver/log/data/'">Show data files</button>
<!-- <button onclick="document.getElementById('editor').hidden = false; this.hidden = true;" >Editor</button> -->
<div id='editor' hidden='true'>
<textarea id="cnsl">
datefile = document.getElementById("datafiles").value;
numbername = document.getElementById("numbers").value;
datatype = document.getElementById("datatype").value;
//alert("Auslesen: " + datefile + " " + numbername);
_domainname = getDomainname();
fetch(_domainname + '/fileserver/log/data/' + datefile)
.then(response => {
// handle the response
if (response.status == 404) {
firework.launch("No log data available for " + dateString, 'warning', 10000);
}
response.text()
.then( result => {
var lines = result.split("\n");
var trace = {
x: [],
y: [],
type: 'scatter'
};
var timex = 1;
for (let line of lines) {
{
console.log(line);
if (line.split(",")[1] == numbername)
{
var value = line.split(",")[datatype];
var time = line.split(",")[0];
console.log("> "+time+" "+value+"\n");
trace.x.push(time);
// timex += 1;
trace.y.push(value);
}
}
}
console.log(trace);
var data = [trace];
Plotly.newPlot('chart', data);
});
}).catch((error) => {
// handle the error
console.log(error);
});
</textarea><br />
<button onclick="run();">run</button>
</div>
<script>

Some files were not shown because too many files have changed in this diff Show More