Compare commits

..

51 Commits

Author SHA1 Message Date
CaCO3
7836323fbc Prepare RC5 (#3422)
* Update Changelog.md

* Update Changelog.md

* Update Changelog.md

* Update Changelog.md
2024-12-05 01:05:38 +01:00
CaCO3
708fd68cf5 Remove Autostart parameter and make the flow to be always enabled (#3423)
* removed Autostart parameter and make it enabled by default

* .

* show menu entry in UI

* cleanup migration

* .

* .

* .

* .

* .

* .

* .

* .
2024-12-05 01:04:24 +01:00
github-actions[bot]
1b5ff2ef1a docs(contributor): contrib-readme-action has updated readme 2024-12-04 22:40:54 +00:00
CaCO3
1701aaed42 Update README.md (#3421) 2024-12-04 23:40:24 +01:00
CaCO3
c07e5a740a Add files via upload 2024-12-04 23:36:29 +01:00
SURYANSH RAI
3868cf98f6 Better Footer with social links #3311 (#3361)
* Added footer

* Update README.md

* Update index.html

* Update index.html

* Add files via upload

* Delete images/mail.jpg

* Update README.md

* Update sd-card/html/index.html

* Update docs/index.html

---------

Co-authored-by: CaCO3 <caco3@ruinelli.ch>
2024-12-04 23:33:58 +01:00
CaCO3
179005f4ce Extend Homeassistant discovery with button for flow start (#3415)
* feat(homeassistant discovery): Add button for flow start

* Update

* Update

* Allow Flow Start MQTT topic to have a zero-length payload

* remove unused payload

---------

Co-authored-by: Slider0007 <jobbelle@gmx.net>
2024-12-04 23:17:06 +01:00
SybexX
50ada0a5a8 fix (#3418) 2024-12-04 22:57:43 +01:00
SybexX
be7146c886 fix (#3417) 2024-12-03 21:12:19 +01:00
CaCO3
8f89a396f8 typo 2024-12-02 23:03:48 +01:00
SybexX
9e84d28ee9 overview_add_date_and_time (#3398)
* overview_add_date_and_time

* Update overview.html

---------

Co-authored-by: CaCO3 <caco3@ruinelli.ch>
2024-12-02 00:26:54 +01:00
CaCO3
3f4aaf303f Revert ec00e94
See https://github.com/jomjol/AI-on-the-edge-device/issues/3305#issuecomment-2510295853
2024-12-02 00:14:52 +01:00
gneluka
08baf1824c Add support for Domoticz MQTT integration (#3359)
* initial changes to add domoticz support

* error correction

* further changes

* further changes

* Refactoring

* further refactoring

* further refactoring

* Update DomoticzTopicIn.md

* Update NUMBER.DomoticzIDX.md

* Update DomoticzTopicIn.md

* Update NUMBER.DomoticzIDX.md

* Update config.ini

* Update edit_config_template.html

* Update edit_config_template.html

* Update edit_config_template.html

* Update DomoticzTopicIn.md

---------

Co-authored-by: CaCO3 <caco3@ruinelli.ch>
2024-12-02 00:07:02 +01:00
SybexX
2c7740ec16 Update CTfLiteClass.cpp (#3397) 2024-11-21 22:01:57 +01:00
SybexX
3ee32d8cf0 Merge pull request #3393 from tkopczuk/patch-1
Fix 'AnalogToDigitTransitionStart' always using 9.2 regardless of the configured value.
2024-11-20 03:33:23 +01:00
Tomek Kopczuk
25ae0465d8 Fix 'AnalogToDigitTransitionStart' always using 9.2 regardless of the configured value.
After the recent parameter rename we're trying to compare the toUpper version of the config parameter against "AnalogToDigitTransitionStart" - which will always fail - hence always using the default value of 9.2.
2024-11-19 23:14:24 +01:00
CaCO3
14e9d6a9cc renamed issue templates 2024-11-15 17:45:40 +01:00
CaCO3
94c2de1c2a Rename blank.yaml to x_blank.yaml 2024-11-15 17:27:48 +01:00
CaCO3
541e9841c2 Rename x_plain.yaml to blank.yaml 2024-11-15 17:27:23 +01:00
jomjol
fc3c70c63a Update dig-cont tlite to v712
Minor errata in quantization
2024-11-01 18:23:17 +01:00
github-actions[bot]
bfde8316ce docs(contributor): contrib-readme-action has updated readme 2024-10-19 08:36:57 +00:00
Bilal Mirza
fe0fec7fac Allow to Add a Move to Top button to enhance the ease of navigation for README readers. (#3344)
* Allow to Add a Move to Top button to enhance the ease of navigation for README readers.

* totally top of readme

* .

* Update README.md

* Update README.md

---------

Co-authored-by: CaCO3 <caco3@ruinelli.ch>
2024-10-19 10:36:19 +02:00
jomjol
56abefe3df Update esp-nn and tflite to newest version (#3346)
* Pulled down update esp-nn

* Pulled tflite to master

* Update from main
2024-10-18 21:48:59 +02:00
CaCO3
43f15fcba3 Fix HA menu entry (#3342)
* Fix HA menu entry

* Update index.html

* Update index.html

* Update index.html

* Update index.html

* Update index.html
2024-10-18 10:45:05 +02:00
Bilal Mirza
dadf18f5c9 Fix: Broken Links i.e 404 page not found #3335 (#3336)
* Fix: Broken Links

* Update README.md

---------

Co-authored-by: CaCO3 <caco3@ruinelli.ch>
2024-10-14 22:07:14 +02:00
CaCO3
c448ece680 Update the Homeasistant Discovery topics (#3332)
Update the Homeassistant Discovery topics
2024-10-13 23:04:28 +02:00
CaCO3
07d6eca456 Update README.md 2024-10-13 16:12:24 +02:00
CaCO3
4083d35b61 Update README.md 2024-10-13 15:55:30 +02:00
CaCO3
2319f8d302 Update README.md 2024-10-13 14:51:24 +02:00
Ranjana761
224e4380a8 Add batches to README.md (#3330)
* Update README.md

* Update README.md
2024-10-13 12:00:24 +02:00
jomjol
14d0c88a28 Update dig-cont_0711_s3_q.tflite
Error corrected version ov 0710 (adjusted quantization)
2024-10-13 10:13:06 +02:00
Farookh Zaheer Siddiqui
3c9b2c46c8 Update FeatureRequest.md (#3328) 2024-10-09 21:29:12 +02:00
jomjol
0752694600 Return vo v0700 2024-10-09 21:28:58 +02:00
github-actions[bot]
cf0ed268dd docs(contributor): contrib-readme-action has updated readme 2024-10-08 22:14:26 +00:00
CaCO3
f1979a142e Manual update of contributors list (#3325)
* Create manually-update-contributors-list

* Update and rename manually-update-contributors-list to manual-update-contributors-li.yamlst

* Update build.yaml

* Rename manual-update-contributors-li.yamlst to manual-update-contributors-li.yaml

* Rename manual-update-contributors-li.yaml to manual-update-contributors-list.yaml

* Update README.md
2024-10-09 00:13:43 +02:00
Austin Drenski
2a4f0f4a2d Update softAP.cpp (#3324) 2024-10-08 23:34:25 +02:00
KrishCode
45269bf45b Fixed docs README.md (#3323)
* Fixed docs README.md

I improved the file and enhanced its readability

* Update README.md

* Update README.md

* Update README.md

---------

Co-authored-by: CaCO3 <caco3@ruinelli.ch>
2024-10-08 23:33:14 +02:00
Naman Tyagi
57db60fee4 fix: corrected grammatical and typographical errors in changelog (#3321) 2024-10-07 21:40:11 +02:00
Naman Tyagi
e1c49f39f0 Update README.md (#3322) 2024-10-07 21:39:09 +02:00
jomjol
7bebae3776 Clean up of old tflite files 2024-10-07 19:52:18 +02:00
CaCO3
0a4560ea95 Update Changelog.md 2024-10-07 00:21:07 +02:00
CaCO3
b44db21714 Update esp32-camera submodule to v2.0.13 (#3316)
Co-authored-by: CaCO3 <caco@ruinelli.ch>
2024-10-07 00:20:38 +02:00
CaCO3
34796ed091 Update build.yaml 2024-10-07 00:19:47 +02:00
CaCO3
a46dfd1c23 Add demo files (#3315)
* updated changelog

* add demo files

* Update build.yaml

* Update build.yaml

* Update build.yaml

* added missing files

* .

* .

---------

Co-authored-by: CaCO3 <caco@ruinelli.ch>
2024-10-07 00:06:03 +02:00
CaCO3
32eb583036 Add contributor list (#3317)
* feat: #3310 Automatically Update Contributor List  (#3312)

* Create main.yml

* Update README.md

* docs(contributor): contrib-readme-action has updated readme

* Update main.yml

* docs(contributor): contrib-readme-action has updated readme

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Update build.yaml

* docs(contributor): contrib-readme-action has updated readme

* Delete .github/workflows/main.yml

* Update README.md

* docs(contributor): contrib-readme-action has updated readme

* Update README.md

* docs(contributor): contrib-readme-action has updated readme

* Update build.yaml

* Update README.md

---------

Co-authored-by: Ayush Jhawar <111112495+Ayushjhawar8@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-10-06 23:25:19 +02:00
michael
6ee83b8413 Update ClassControllCamera.cpp (#3313)
https://github.com/jomjol/AI-on-the-edge-device/issues/3300#issuecomment-2395561022
2024-10-06 22:42:27 +02:00
CaCO3
f034232f36 Update config.ini 2024-10-06 22:33:24 +02:00
CaCO3
b80e43dfe9 Update config.ini 2024-10-06 22:31:56 +02:00
CaCO3
40c7c253ea Update config.ini 2024-10-06 22:29:06 +02:00
CaCO3
d11b312a96 Typos (#3309)
* Update edit_reference.html

* Update edit_config_template.html

* Update edit_config_template.html

* Update edit_reference.html
2024-10-06 16:57:26 +02:00
Naman Tyagi
2c1e531ed2 Fix grammar and typos in README.md for clarity and consistency (#3308) 2024-10-06 16:53:16 +02:00
78 changed files with 1805 additions and 407 deletions

View File

@@ -53,6 +53,7 @@ jobs:
./code/.pio/build/esp32cam/partitions.bin
./code/.pio/build/esp32cam/bootloader.bin
./html/*
./demo/*
key: generated-files-${{ github.run_id }}
restore-keys: generated-files # This matches above key as it is only used as a prefix. it the restores the nearest cache, see https://github.com/restore-keys:/blob/main/tips-and-workarounds.md#update-a-cache
@@ -87,6 +88,11 @@ jobs:
echo "Replacing variables..."
cd html; find . -type f -exec sed -i 's/$COMMIT_HASH/${{ steps.vars.outputs.sha_short }}/g' {} \;
- name: Prepare Demo mode files
run: |
rm -rf ./demo
mkdir demo
cp -r ./sd-card/demo/* ./demo/
#########################################################################################
## Pack for Update
@@ -97,6 +103,7 @@ jobs:
# - /firmware.bin
# - (optional) /html/* (inkl. subfolders)
# - (optional) /config/*.tfl
# - (optional) /demo/*
runs-on: ubuntu-latest
needs: build
@@ -111,6 +118,7 @@ jobs:
./code/.pio/build/esp32cam/partitions.bin
./code/.pio/build/esp32cam/bootloader.bin
./html/*
./demo/*
key: generated-files-${{ github.run_id }}
restore-keys: generated-files # This matches above key as it is only used as a prefix. it the restores the nearest cache, see https://github.com/restore-keys:/blob/main/tips-and-workarounds.md#update-a-cache
@@ -135,6 +143,9 @@ jobs:
- name: Add Web UI to update
run: cp -r ./html ./update/
- name: Add Demo mode files to update
run: cp -r ./demo ./update/
- name: Add CNN to update
run: |
@@ -158,6 +169,7 @@ jobs:
# remote_setup__version.zip file with following content:
# - /firmware.bin
# - /html/* (inkl. subfolders)
# - /demo/*
# - /config/*
runs-on: ubuntu-latest
needs: build
@@ -173,6 +185,7 @@ jobs:
./code/.pio/build/esp32cam/partitions.bin
./code/.pio/build/esp32cam/bootloader.bin
./html/*
./demo/*
key: generated-files-${{ github.run_id }}
restore-keys: generated-files # This matches above key as it is only used as a prefix. it the restores the nearest cache, see https://github.com/restore-keys:/blob/main/tips-and-workarounds.md#update-a-cache
@@ -197,6 +210,9 @@ jobs:
- name: Add Web UI to remote_setup
run: cp -r ./html ./remote_setup/
- name: Add Demo mode files to update
run: cp -r ./demo ./update/
- name: Add whole config folder to remote_setup
run: |
rm -rf ./remote_setup/config/
@@ -229,6 +245,7 @@ jobs:
./code/.pio/build/esp32cam/partitions.bin
./code/.pio/build/esp32cam/bootloader.bin
./html/*
./demo/*
key: generated-files-${{ github.run_id }}
restore-keys: generated-files # This matches above key as it is only used as a prefix. it the restores the nearest cache, see https://github.com/restore-keys:/blob/main/tips-and-workarounds.md#update-a-cache
@@ -257,7 +274,9 @@ jobs:
cp -f "./code/.pio/build/esp32cam/bootloader.bin" "manual_setup/bootloader.bin"
cp -f "./code/.pio/build/esp32cam/partitions.bin" "manual_setup/partitions.bin"
rm -rf ./sd-card/html
rm -rf ./sd-card/demo
cp -r ./html ./sd-card/ # Overwrite the Web UI with the preprocessed files
cp -r ./demo ./sd-card/
cd sd-card; zip -r ../manual_setup/sd-card.zip *; cd ..
cd ./manual_setup
@@ -271,7 +290,7 @@ jobs:
#########################################################################################
## Prepare and create release
#########################################################################################
release:
prepare-release:
runs-on: ubuntu-latest
needs: [pack-for-update, pack-for-manual_setup, pack-for-remote_setup]
if: startsWith(github.ref, 'refs/tags/')
@@ -377,7 +396,7 @@ jobs:
# Make sure to also update update-webinstaller.yml!
update-web-installer:
if: github.event_name == 'release' && github.event.action == 'published' # Only run on release but not on prerelease
needs: [release]
needs: [prepare-release]
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}

View File

@@ -0,0 +1,21 @@
# This updates the Contributors list in the README.md
# it only gets run on:
# - Manually triggered
name: Manually update contributors list
on:
workflow_dispatch: # Run on manual trigger
jobs:
manually-update-contributors-list:
runs-on: ubuntu-latest
name: A job to automatically update the contributors list in the README.md
permissions:
contents: write
pull-requests: write
steps:
- name: Contribute List
uses: akhilmhdh/contributors-readme-action@v2.3.10
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,3 +1,53 @@
## [16.0.0-RC5] - 2024-12-05
For a full list of changes see [Full list of changes](https://github.com/jomjol/AI-on-the-edge-device/compare/v15.7.0...v16.0.0)
#### Known issues
Please check the [issues](https://github.com/jomjol/AI-on-the-edge-device/issues) and
[discussions](https://github.com/jomjol/AI-on-the-edge-device/discussions) before reporting a new issue.
#### Core Changes
Only changes since RC4 are listed:
- Removed `Autostart` parameter and make the flow to be always enabled (#3423)
- Enable `Flow start` menu entry in UI (#3423)
- Updated the Homeassistant Discovery topics (#3332):
- `raw` has now set the `State Class` to `measurement`. Before it was always set to `""`.
- `value` has now only set the `State Class` to `total_increasing` if the parameter `Allow Negative Rates` is **not** set. Else it uses `measurement` since the rate could also be negative. Before it was always set to `total_increasing`.
- The `rate_per_time_unit` topic of an **Energy** meter needs a `Device Class`=`power`. For `gas` and `water` it should be `volume_flow_rate`. Before it was always set to `""`.
- Added button for `flow start` (#3415)
- Added support for Domoticz MQTT integration (#3359)
- Added Date and time to overview page
- Updated submodules and models
**:warning: Please check your Homeassistant instance to make sure it is handled correctly!**
#### Bug Fixes
Only changes since RC3 are listed:
- Added fix for ledintensity (#3418)
- Added fix for OV2640 brightness contrast saturation (#3417)
- Added fix for 'AnalogToDigitTransitionStart' always using 9.2 regardless of the configured value (#3393)
- Addef fix for HA menu entry (#3342)
## [16.0.0-RC4] - 2024-10-06
For a full list of changes see [Full list of changes](https://github.com/jomjol/AI-on-the-edge-device/compare/v15.7.0...v16.0.0-RC1)
#### Known issues
Please check the [issues](https://github.com/jomjol/AI-on-the-edge-device/issues) and
[discussions](https://github.com/jomjol/AI-on-the-edge-device/discussions) before reporting a new issue.
#### Core Changes
Only changes since RC3 are listed:
- Update esp32-camera submodule to `v2.0.13` (#3316)
- Added contributor list (#3317)
- Added files for demo mode (#3315)
#### Bug Fixes
Only changes since RC2 are listed:
- Added delay in InitCam (#3313) to fix `Camera not detected` issues
## [16.0.0-RC3] - 2024-10-05
For a full list of changes see [Full list of changes](https://github.com/jomjol/AI-on-the-edge-device/compare/v15.7.0...v16.0.0-RC1)
@@ -562,16 +612,16 @@ Intermediate Digits
- Implementation of new CNN types to detect intermediate values of digits with rolling numbers
- By default the old algo (0, 1, ..., 9, "N") is active (due to the limited types of digits trained so far)
- Activation can be done by selection a tflite file with the new trained model in the 'config.ini'
- Activation can be done by selecting a tflite file with the new trained model in the 'config.ini'
- **Details can be found in the [wiki](https://github.com/jomjol/AI-on-the-edge-device/wiki/Neural-Network-Types)** (different types, trained image types, naming convention)
- Updated neural network files (and adaption to new naming convention)
- Updated neural network files (and adaptation to new naming convention)
- Published a tool to download and combine log files - **Thanks to **
- Published a tool to download and combine log files - Thanks to [Contributor]
- Files see ['/tools/logfile-tool'](tbd), How-to see [wiki](https://github.com/jomjol/AI-on-the-edge-device/wiki/Gasmeter-Log-Downloader)
- Bug Fix: InfluxDB enabling in grahic configuration
- Bug Fix: InfluxDB enabling in graphic configuration
## [10.6.2](https://github.com/jomjol/AI-on-the-edge-device/releases/tag/v10.6.2), 2022-07-24
@@ -585,7 +635,7 @@ Stability Increase
- **NEW 10.6.1**: Bug Fix: tflite-filename with ".", HTML spelling error
- IndluxDB: direct injection into InfluxDB - thanks to **[wetneb](https://github.com/wetneb)**
- InfluxDB: direct injection into InfluxDB - thanks to **[wetneb](https://github.com/wetneb)**
- MQTT: implemented "Retain Flag" and extend with absolute Change (in addition to rate)
@@ -1100,7 +1150,7 @@ External Illumination
- Bug in configuration of analog ROIs corrected
- minor bug correction
- minor Bug correction
## [1.0.1](2020-09-05)
@@ -1109,7 +1159,7 @@ External Illumination
- preValue.ini Bug corrected
- minor bug correction
- minor Bug correction
## [1.0.0](2020-09-04)

View File

@@ -40,16 +40,16 @@ ____
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.
as reading the Electricity or Water meter every few minutes only delivers apparent accuracy (DE: "Scheingenauigkeit") you could just as well take a picture with your Smartphone every 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 than on to whoever 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..
for example see Roi in the next picture..
![grafik](https://user-images.githubusercontent.com/38385805/207858812-2a6ba41d-1a8c-4fa1-9b6a-53cdd113c106.png)
in case of position change between the measurments set this state to true, if there is no change set it back to false.
in case of position change between the measurements set this state to true, if there is no change set it back to false.
In a defined time window this movement can lead into an alarm state / water leak..
haveing this state in the mqtt broker can trigger functions like closing the ater pipe walve and so on...
having this state in the mqtt broker can trigger functions like closing the water pipe valve and so on...
@@ -65,7 +65,7 @@ haveing this state in the mqtt broker can trigger functions like closing the ate
#### #31 Implement InfluxDB v2.x interface
* Currently only InfluxDB v1.x is supportet, extend to v2.x
* Currently only InfluxDB v1.x is supported, extend to v2.x
* Remark: interface has changed
* see [#1160](https://github.com/jomjol/AI-on-the-edge-device/issues/1160)
@@ -82,7 +82,7 @@ haveing this state in the mqtt broker can trigger functions like closing the ate
#### #28 Improved error handling for ROIs
* In case a ROI is out of the image, there is no error message, but a non sense image is used
* Implement a error message for wrong configuratioin of ROI
* Implement a error message for wrong configuration of ROI
#### #27 Use Homie Spec for Mqtt binding

721
README.md
View File

@@ -1,108 +1,667 @@
# Welcome to the AI-on-the-edge-device
<img src="images/icon/watermeter.svg" width="100px">
<h1 align="center">AI on the Edge Device: Digitizing Your non-digital meters with an ESP32-CAM</h1>
<br>
<br>
Artificial intelligence based systems have become established in our everyday lives. Just think of speech or image recognition. Most of the systems rely on either powerful processors or a direct connection to the cloud for doing the calculations there. With the increasing power of modern processors, the AI systems are coming closer to the end user which is usually called **edge computing**.
Here, this edge computing is put into a practically oriented example, where an AI network is implemented on an ESP32 device so: **AI on the edge**.
## Table of Contents
- [Key Features 🚀](#key-features-)
- [Workflow 🔧](#workflow-)
- [Impressions 📷](#impressions-)
- [AI-on-the-edge-device on a Water Meter 💧](#ai-on-the-edge-device-on-a-water-meter-)
- [Web Interface (Water Meter) 💻](#web-interface-water-meter-)
- [AI-on-the-edge-device on an Electrical Power Meter ⚡](#ai-on-the-edge-device-on-an-electrical-power-meter-)
- [Setup 🛠️](#setup-%EF%B8%8F)
- [Download 🔽](#download-)
- [Flashing the ESP32 💾](#flashing-the-esp32-)
- [Flashing the SD Card 💾](#flashing-the-sd-card-)
- [Casing 🛠️](#casing-%EF%B8%8F)
- [Donate ☕](#donate-)
- [Support 💬](#support-)
- [Changes and History 📜](#changes-and-history-)
- [Build It Yourself 🔨](#build-it-yourself-)
- [Tools 🛠️](#tools-%EF%B8%8F)
- [Additional Ideas 💡](#additional-ideas-)
- [Our Contributors ❤️](#our-contributors-%EF%B8%8F)
This project allows you to digitize your **analog** water, gas, power and other meters using cheap and easily available hardware.
All you need is an [ESP32 board with a supported camera](https://jomjol.github.io/AI-on-the-edge-device-docs/Hardware-Compatibility/) and something of a practical hand.
<img src="images/esp32-cam.png" width="200px">
## Key features
- Tensorflow Lite (TFlite) integration including easy-to-use wrapper
- Inline image processing (feature detection, alignment, ROI extraction)
- **Small** and **cheap** device (3 x 4.5 x 2 cm³, < 10 EUR)
- Integrated camera and illumination
- Web interface for administration and control
- OTA interface for updating directly via the web interface
- Full integration into Homeassistant
- Support for Influx DB 1 and 2
- MQTT
- REST API
## Workflow
The device takes a photo of your meter at a defined interval. It then extracts the Regions of Interest (ROIs) from the image and runs them through artificial intelligence. As a result, you get the digitized value of your meter.
There are several options for what to do with that value. Either send it to an MQTT broker, write it to an InfluxDb or simply provide access to it via a REST API.
<img src="https://raw.githubusercontent.com/jomjol/AI-on-the-edge-device/master/images/idea.jpg" width="600">
## Impressions
### AI-on-the-edge-device on a Water Meter
<img src="https://raw.githubusercontent.com/jomjol/AI-on-the-edge-device/master/images/watermeter_all.jpg" width="200"><img src="https://raw.githubusercontent.com/jomjol/AI-on-the-edge-device/master/images/main.jpg" width="200"><img src="https://raw.githubusercontent.com/jomjol/AI-on-the-edge-device/master/images/size.png" width="200">
### Web Interface (Water Meter)
<img src="https://raw.githubusercontent.com/jomjol/AI-on-the-edge-device/master/images/watermeter.jpg" width="600">
### AI-on-the-edge-device on a Electrical Power Meter
<img src="https://raw.githubusercontent.com/jomjol/AI-on-the-edge-device/master/images/powermeter.jpg" width="600">
<p align="center">
<a href="#top">
<img src="https://img.shields.io/badge/Back%20to%20Top-000000?style=for-the-badge&logo=github&logoColor=white" alt="Back to Top">
</a>
</p>
## Setup
There is growing [documentation](https://jomjol.github.io/AI-on-the-edge-device-docs/) which provides you with a lot of information. Head there to get a start, set it up and configure it.
There are also articles in the German Heise magazine "make:" about the setup and technical background (behind a paywall): [DIY - Setup](https://www.heise.de/select/make/2021/2/2103513300897420296)
A lot of people created useful Youtube videos which might help you getting started.
Here a small selection:
[![made-with-c++](https://img.shields.io/badge/Made%20with-C++-1f425f.svg)](https://github.com/jomjol/AI-on-the-edge-device/tree/main/code)
[![Doc](https://img.shields.io/badge/Doc-MkDocs-526CFE.svg)](https://jomjol.github.io/AI-on-the-edge-device-docs/)
[![Total downloads](https://img.shields.io/github/downloads/jomjol/AI-on-the-edge-device/total.svg)](https://GitHub.com/jomjol/AI-on-the-edge-device/releases/)
[![GitHub release](https://img.shields.io/github/release/jomjol/AI-on-the-edge-device.svg)](https://GitHub.com/jomjol/AI-on-the-edge-device/releases/)
[![GitHub forks](https://img.shields.io/github/forks/jomjol/AI-on-the-edge-device.svg?style=social&label=Fork&maxAge=2592000)](https://GitHub.com/jomjol/AI-on-the-edge-device/network/)
[![GitHub stars](https://img.shields.io/github/stars/jomjol/AI-on-the-edge-device.svg?style=social&label=Star&maxAge=2592000)](https://GitHub.com/jomjol/AI-on-the-edge-device/stargazers/)
- [youtube.com/watch?v=HKBofb1cnNc](https://www.youtube.com/watch?v=HKBofb1cnNc)
- [youtube.com/watch?v=yyf0ORNLCk4](https://www.youtube.com/watch?v=yyf0ORNLCk4)
- [youtube.com/watch?v=XxmTubGek6M](https://www.youtube.com/watch?v=XxmTubGek6M)
- [youtube.com/watch?v=mDIJEyElkAU](https://www.youtube.com/watch?v=mDIJEyElkAU)
- [youtube.com/watch?v=SssiPkyKVVs](https://www.youtube.com/watch?v=SssiPkyKVVs)
- [youtube.com/watch?v=MAHE_QyHZFQ](https://www.youtube.com/watch?v=MAHE_QyHZFQ)
- [youtube.com/watch?v=Uap_6bwtILQ](https://www.youtube.com/watch?v=Uap_6bwtILQ)
<p align="center" id="top">
<img src="images/icon/watermeter.svg" width="150px">
</p>
For further background information, head to [Neural Networks](https://www.heise.de/select/make/2021/6/2126410443385102621), [Training Neural Networks](https://www.heise.de/select/make/2022/1/2134114065999161585) and [Programming on the ESP32](https://www.heise.de/select/make/2022/2/2204010051597422030).
Artificial intelligence is everywhere, from speech to image recognition. While most AI systems rely on powerful processors or cloud computing, **edge computing** brings AI closer to the end user by utilizing the capabilities of modern processors.
This project demonstrates edge computing using the **ESP32**, a low-cost, AI-capable device, to digitize your analog meters—whether water, gas, or electricity. With affordable hardware and simple instructions, you can turn any standard meter into a smart device.
### Download
Let's explore how to make **AI on the Edge** a reality! 🌟
All you need is an [ESP32 board with a supported camera](https://jomjol.github.io/AI-on-the-edge-device-docs/Hardware-Compatibility/) and some practical skills. 🛠️
---
<br>
## Key Features 🚀
- 🔗 **Tensorflow Lite (TFLite) integration** including an easy-to-use wrapper.
- 📸 **Inline image processing** (feature detection, alignment, ROI extraction).
- 💡 **Small** and **affordable** device (3 x 4.5 x 2 cm³, less than 10 EUR).
- 📷 Integrated camera and illumination.
- 🌐 Web interface for administration and control.
- 🔄 OTA interface for updating directly via the web interface.
- 🏠 Full integration with Home Assistant.
- 📊 Support for **Influx DB 1** and **2**.
- 📡 **MQTT protocol** support.
- 📥 **REST API** available for data access.
<br>
## Workflow 🔧
The device captures a photo of your meter at set intervals. It then extracts the Regions of Interest (ROIs) from the image and runs them through artificial intelligence. As a result, you get the digitized value of your meter.
There are several options for what to do with that value:
- 📤 Send it to a **MQTT broker**.
- 📝 Write it to an **InfluxDb**.
- 🔗 Provide access via a **REST API**.
<p align="center">
<img src="images/idea.jpg" width="600">
</p>
---
<br>
## Impressions 📷
+ ### AI-on-the-edge-device on a Water Meter 💧
<p align="center">
<img src="images/watermeter_all.jpg" width="200"><img
src="images/main.jpg" width="200"><img
src="images/size.png" width="200">
</p>
+ ### Web Interface (Water Meter) 💻
<p align="center">
<img src="images/watermeter.jpg" width="600">
</p>
+ ### AI-on-the-edge-device on an Electrical Power Meter ⚡
<p align="center">
<img src="images/powermeter.jpg" width="600">
</p>
---
<br>
## Setup 🛠️
There is growing [documentation](https://jomjol.github.io/AI-on-the-edge-device-docs/) which provides you with a lot of information. Head there to get started, set it up, and configure it.
There are also articles in the German Heise magazine "make:" about the setup and technical background (behind a paywall): [DIY - Setup](https://www.heise.de/select/make/2021/2/2103513300897420296) 📰
A lot of people have created useful YouTube videos that might help you get started:
- 🎥 [youtube.com/watch?v=HKBofb1cnNc](https://www.youtube.com/watch?v=HKBofb1cnNc)
- 🎥 [youtube.com/watch?v=yyf0ORNLCk4](https://www.youtube.com/watch?v=yyf0ORNLCk4)
- 🎥 [youtube.com/watch?v=XxmTubGek6M](https://www.youtube.com/watch?v=XxmTubGek6M)
- 🎥 [youtube.com/watch?v=mDIJEyElkAU](https://www.youtube.com/watch?v=mDIJEyElkAU)
- 🎥 [youtube.com/watch?v=SssiPkyKVVs](https://www.youtube.com/watch?v=SssiPkyKVVs)
- 🎥 [youtube.com/watch?v=MAHE_QyHZFQ](https://www.youtube.com/watch?v=MAHE_QyHZFQ)
- 🎥 [youtube.com/watch?v=Uap_6bwtILQ](https://www.youtube.com/watch?v=Uap_6bwtILQ)
For further background information, head to:
- [Neural Networks](https://www.heise.de/select/make/2021/6/2126410443385102621)
- [Training Neural Networks](https://www.heise.de/select/make/2022/1/2134114065999161585)
- [Programming on the ESP32](https://www.heise.de/select/make/2022/2/2204010051597422030)
---
<br>
## Download 🔽
The latest available version can be found on the [Releases page](https://github.com/jomjol/AI-on-the-edge-device/releases).
### Flashing the ESP32
Initially you will have to flash the ESP32 via a USB connection. Later updates are possible directly over the air (OTA using WIFI).
---
<br>
## Flashing the ESP32 💾
Initially, you will have to flash the ESP32 via a USB connection. Later updates are possible directly over the air (OTA using Wi-Fi).
There are different ways to flash your ESP32:
- The preferred way is the [Web Installer and Console](https://jomjol.github.io/AI-on-the-edge-device/index.html) which is a browser-based tool to flash the ESP32 and extract the log over USB:
![](images/web-installer.png)
- The preferred way is the [Web Installer and Console](https://jomjol.github.io/AI-on-the-edge-device/index.html), a browser-based tool to flash the ESP32 and extract the log over USB:
![](images/web-installer.png)
- Flash Tool from Espressif
- ESPtool (command-line tool)
See the [documentation](https://jomjol.github.io/AI-on-the-edge-device-docs/Installation/) for more information.
### Flashing the SD Card
The SD card can be setup automatically after the firmware got installed. See the [documentation](https://jomjol.github.io/AI-on-the-edge-device-docs/Installation/#remote-setup-using-the-built-in-access-point) for details. For this to work, the SD card must be FAT formated (which is the default on a new SD card).
Alternatively the SD card still can be setup manually, see the [documentation](https://jomjol.github.io/AI-on-the-edge-device-docs/Installation/#3-sd-card) for details!
---
## Casing
Various 3D-printable housing can be found here:
- https://www.thingiverse.com/thing:4573481 (Water Meter)
- https://www.thingiverse.com/thing:5028229 (Power Meter)
- https://www.thingiverse.com/thing:5224101 (Gas Meter)
- https://www.thingiverse.com/thing:4571627 (ESP32-cam housing only)
<br>
## Donate
If you would like to support the developer with a cup of coffee, you can do that via [PayPal](https://www.paypal.com/donate?hosted_button_id=8TRSVYNYKDSWL).
## Flashing the SD Card 💾
The SD card can be set up automatically after the firmware is installed. See the [documentation](https://jomjol.github.io/AI-on-the-edge-device-docs/Installation/#remote-setup-using-the-built-in-access-point) for details. For this to work, the SD card must be FAT formatted (which is the default on a new SD card).
<a href="https://www.paypal.com/donate?hosted_button_id=8TRSVYNYKDSWL"><img border="0" src="images/paypal.png" width="200px" target="_blank"></a>
Alternatively, the SD card can still be set up manually. See the [documentation](https://jomjol.github.io/AI-on-the-edge-device-docs/Installation/#3-sd-card) for details.
## Support
If you have any technical problems please search the [discussions](https://github.com/jomjol/AI-on-the-edge-device/discussions). In case you found a ug or have a feature request, please open an [issue](https://github.com/jomjol/AI-on-the-edge-device/issues).
---
In other cases you can contact the developer via email: <img src="https://raw.githubusercontent.com/jomjol/AI-on-the-edge-device/master/images/mail.jpg" height="25">
<br>
## Changes and History
See [Changelog](Changelog.md).
## Casing 🛠️
Various 3D-printable housings can be found here:
- 💧 [Water Meter](https://www.thingiverse.com/thing:4573481)
- ⚡ [Power Meter](https://www.thingiverse.com/thing:5028229)
- 🔥 [Gas Meter](https://www.thingiverse.com/thing:5224101)
- 📷 [ESP32-cam housing only](https://www.thingiverse.com/thing:4571627)
## Build It Yourself
See [Build Instructions](code/README.md).
---
## Tools
* Logfile downloader and combiner (Thx to [reserve85](https://github.com/reserve85))
* Files see ['/tools/logfile-tool'](tbd), how-to see [documentation](https://jomjol.github.io/AI-on-the-edge-device-docs/outdated--Gasmeter-Log-Downloader/)
<br>
## Additional Ideas
There are some ideas and feature requests which are not currently being pursued mainly due to capacity reasons on the part of the developers.
They features are collected in the [issues](https://github.com/jomjol/AI-on-the-edge-device/issues) and in [FeatureRequest.md](FeatureRequest.md).
## Donate ☕
If you'd like to support the developer with a cup of coffee, you can do so via [PayPal](https://www.paypal.com/donate?hosted_button_id=8TRSVYNYKDSWL).
<p align="center">
<a href="https://www.paypal.com/donate?hosted_button_id=8TRSVYNYKDSWL"><img border="0" src="images/paypal.png" width="200px" target="_blank"></a>
</p>
---
<br>
## Support 💬
If you have any technical problems, please search the [discussions](https://github.com/jomjol/AI-on-the-edge-device/discussions). In case you find a bug or have a feature request, please open an [issue](https://github.com/jomjol/AI-on-the-edge-device/issues).
For any other issues, you can contact the developer via email:
<p align="center">
<img src="images/mail.jpg" height="25">
</p>
---
<br>
## Changes and History 📜
See the [Changelog](Changelog.md) for detailed information.
---
<br>
## Build It Yourself 🔨
See the [Build Instructions](code/README.md) for step-by-step guidance.
---
<br>
## Tools 🛠️
* Logfile downloader and combiner (Thanks to [reserve85](https://github.com/reserve85))
* It can be found at ['/tools/logfile-tool'](https://github.com/jomjol/AI-on-the-edge-device/tree/main/tools/logfile-tool).
---
<br>
## Additional Ideas 💡
There are some ideas and feature requests which are not currently being pursued—mainly due to capacity constraints on the part of the developers. These features are collected in the [issues](https://github.com/jomjol/AI-on-the-edge-device/issues) and in [FeatureRequest.md](FeatureRequest.md).
---
<br>
## Our Contributors ❤️
<!-- Do not manually edit this section! It should get updated using the Github action "Manually update contributors list" -->
<!-- readme: contributors -start -->
<table>
<tbody>
<tr>
<td align="center">
<a href="https://github.com/jomjol">
<img src="https://avatars.githubusercontent.com/u/30766535?v=4" width="100;" alt="jomjol"/>
<br />
<sub><b>jomjol</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/caco3">
<img src="https://avatars.githubusercontent.com/u/1783586?v=4" width="100;" alt="caco3"/>
<br />
<sub><b>CaCO3</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/haverland">
<img src="https://avatars.githubusercontent.com/u/412645?v=4" width="100;" alt="haverland"/>
<br />
<sub><b>Frank Haverland</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/Slider0007">
<img src="https://avatars.githubusercontent.com/u/115730895?v=4" width="100;" alt="Slider0007"/>
<br />
<sub><b>Slider0007</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/SybexX">
<img src="https://avatars.githubusercontent.com/u/587201?v=4" width="100;" alt="SybexX"/>
<br />
<sub><b>michael</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/nliaudat">
<img src="https://avatars.githubusercontent.com/u/6782613?v=4" width="100;" alt="nliaudat"/>
<br />
<sub><b>Nicolas Liaudat</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/Zwer2k">
<img src="https://avatars.githubusercontent.com/u/10438794?v=4" width="100;" alt="Zwer2k"/>
<br />
<sub><b>Zwer2k</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/phlupp">
<img src="https://avatars.githubusercontent.com/u/6304863?v=4" width="100;" alt="phlupp"/>
<br />
<sub><b>phlupp</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/jasaw">
<img src="https://avatars.githubusercontent.com/u/721280?v=4" width="100;" alt="jasaw"/>
<br />
<sub><b>jasaw</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/dockSquadron">
<img src="https://avatars.githubusercontent.com/u/11964767?v=4" width="100;" alt="dockSquadron"/>
<br />
<sub><b>dockSquadron</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/rdmueller">
<img src="https://avatars.githubusercontent.com/u/1856308?v=4" width="100;" alt="rdmueller"/>
<br />
<sub><b>Ralf D. Müller</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/cristianmitran">
<img src="https://avatars.githubusercontent.com/u/36613624?v=4" width="100;" alt="cristianmitran"/>
<br />
<sub><b>cristianmitran</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/michaeljoos72">
<img src="https://avatars.githubusercontent.com/u/20517474?v=4" width="100;" alt="michaeljoos72"/>
<br />
<sub><b>michaeljoos72</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/henrythasler">
<img src="https://avatars.githubusercontent.com/u/6277203?v=4" width="100;" alt="henrythasler"/>
<br />
<sub><b>Henry Thasler</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/amantyagiprojects">
<img src="https://avatars.githubusercontent.com/u/174239452?v=4" width="100;" alt="amantyagiprojects"/>
<br />
<sub><b>Naman Tyagi</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/pixeldoc2000">
<img src="https://avatars.githubusercontent.com/u/376715?v=4" width="100;" alt="pixeldoc2000"/>
<br />
<sub><b>pixel::doc</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/mad2xlc">
<img src="https://avatars.githubusercontent.com/u/37449746?v=4" width="100;" alt="mad2xlc"/>
<br />
<sub><b>Stefan</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/jochenchrist">
<img src="https://avatars.githubusercontent.com/u/2930448?v=4" width="100;" alt="jochenchrist"/>
<br />
<sub><b>jochenchrist</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/parhedberg">
<img src="https://avatars.githubusercontent.com/u/13777521?v=4" width="100;" alt="parhedberg"/>
<br />
<sub><b>parhedberg</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/slovdahl">
<img src="https://avatars.githubusercontent.com/u/1417619?v=4" width="100;" alt="slovdahl"/>
<br />
<sub><b>Sebastian Lövdahl</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/RaHehl">
<img src="https://avatars.githubusercontent.com/u/7577984?v=4" width="100;" alt="RaHehl"/>
<br />
<sub><b>Raphael Hehl</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/LordGuilly">
<img src="https://avatars.githubusercontent.com/u/13271835?v=4" width="100;" alt="LordGuilly"/>
<br />
<sub><b>LordGuilly</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/bilalmirza74">
<img src="https://avatars.githubusercontent.com/u/84387676?v=4" width="100;" alt="bilalmirza74"/>
<br />
<sub><b>Bilal Mirza</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/muggenhor">
<img src="https://avatars.githubusercontent.com/u/484066?v=4" width="100;" alt="muggenhor"/>
<br />
<sub><b>Giel van Schijndel</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/ppisljar">
<img src="https://avatars.githubusercontent.com/u/13629809?v=4" width="100;" alt="ppisljar"/>
<br />
<sub><b>Peter Pisljar</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/ralf1307">
<img src="https://avatars.githubusercontent.com/u/46164027?v=4" width="100;" alt="ralf1307"/>
<br />
<sub><b>Ralf Rachinger</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/Ranjana761">
<img src="https://avatars.githubusercontent.com/u/129291313?v=4" width="100;" alt="Ranjana761"/>
<br />
<sub><b>Ranjana761</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/SkylightXD">
<img src="https://avatars.githubusercontent.com/u/16561545?v=4" width="100;" alt="SkylightXD"/>
<br />
<sub><b>SkylightXD</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/ottk3">
<img src="https://avatars.githubusercontent.com/u/5236802?v=4" width="100;" alt="ottk3"/>
<br />
<sub><b>Sven Rojek</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/Turbo87">
<img src="https://avatars.githubusercontent.com/u/141300?v=4" width="100;" alt="Turbo87"/>
<br />
<sub><b>Tobias Bieniek</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/tkopczuk">
<img src="https://avatars.githubusercontent.com/u/101632?v=4" width="100;" alt="tkopczuk"/>
<br />
<sub><b>Tomek Kopczuk</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/yonz2">
<img src="https://avatars.githubusercontent.com/u/13886257?v=4" width="100;" alt="yonz2"/>
<br />
<sub><b>Yonz</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/Yveaux">
<img src="https://avatars.githubusercontent.com/u/7716005?v=4" width="100;" alt="Yveaux"/>
<br />
<sub><b>Yveaux</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/flooxo">
<img src="https://avatars.githubusercontent.com/u/93255373?v=4" width="100;" alt="flooxo"/>
<br />
<sub><b>flox_x</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/gneluka">
<img src="https://avatars.githubusercontent.com/u/32097881?v=4" width="100;" alt="gneluka"/>
<br />
<sub><b>gneluka</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/kalwados">
<img src="https://avatars.githubusercontent.com/u/11840444?v=4" width="100;" alt="kalwados"/>
<br />
<sub><b>kalwados</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/kub3let">
<img src="https://avatars.githubusercontent.com/u/95883234?v=4" width="100;" alt="kub3let"/>
<br />
<sub><b>kub3let</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/pfeifferch">
<img src="https://avatars.githubusercontent.com/u/73090220?v=4" width="100;" alt="pfeifferch"/>
<br />
<sub><b>pfeifferch</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/rstephan">
<img src="https://avatars.githubusercontent.com/u/8532364?v=4" width="100;" alt="rstephan"/>
<br />
<sub><b>rstephan</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/smartboart">
<img src="https://avatars.githubusercontent.com/u/38385805?v=4" width="100;" alt="smartboart"/>
<br />
<sub><b>smartboart</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/AngryApostrophe">
<img src="https://avatars.githubusercontent.com/u/89547888?v=4" width="100;" alt="AngryApostrophe"/>
<br />
<sub><b>AngryApostrophe</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/wetneb">
<img src="https://avatars.githubusercontent.com/u/309908?v=4" width="100;" alt="wetneb"/>
<br />
<sub><b>Antonin Delpeuch</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/adarazs">
<img src="https://avatars.githubusercontent.com/u/6269603?v=4" width="100;" alt="adarazs"/>
<br />
<sub><b>Attila Darazs</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/austindrenski">
<img src="https://avatars.githubusercontent.com/u/21338699?v=4" width="100;" alt="austindrenski"/>
<br />
<sub><b>Austin Drenski</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/PLCHome">
<img src="https://avatars.githubusercontent.com/u/29116097?v=4" width="100;" alt="PLCHome"/>
<br />
<sub><b>PLCHome</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/CFenner">
<img src="https://avatars.githubusercontent.com/u/9592452?v=4" width="100;" alt="CFenner"/>
<br />
<sub><b>Christopher Fenner</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/dkneisz">
<img src="https://avatars.githubusercontent.com/u/43378003?v=4" width="100;" alt="dkneisz"/>
<br />
<sub><b>Dave</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/FarukhS52">
<img src="https://avatars.githubusercontent.com/u/129654632?v=4" width="100;" alt="FarukhS52"/>
<br />
<sub><b>Farookh Zaheer Siddiqui</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/hex7c0">
<img src="https://avatars.githubusercontent.com/u/4419146?v=4" width="100;" alt="hex7c0"/>
<br />
<sub><b>Francesco Carnielli</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/040medien">
<img src="https://avatars.githubusercontent.com/u/115072?v=4" width="100;" alt="040medien"/>
<br />
<sub><b>Frederik Kemner</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/eltociear">
<img src="https://avatars.githubusercontent.com/u/22633385?v=4" width="100;" alt="eltociear"/>
<br />
<sub><b>Ikko Eltociear Ashimine</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/queeek">
<img src="https://avatars.githubusercontent.com/u/9533371?v=4" width="100;" alt="queeek"/>
<br />
<sub><b>Ina</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/joergrosenkranz">
<img src="https://avatars.githubusercontent.com/u/310438?v=4" width="100;" alt="joergrosenkranz"/>
<br />
<sub><b>Joerg Rosenkranz</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/Innovatorcloudy">
<img src="https://avatars.githubusercontent.com/u/183274513?v=4" width="100;" alt="Innovatorcloudy"/>
<br />
<sub><b>KrishCode</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/myxor">
<img src="https://avatars.githubusercontent.com/u/1397377?v=4" width="100;" alt="myxor"/>
<br />
<sub><b>Marco H</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/rainman110">
<img src="https://avatars.githubusercontent.com/u/3213107?v=4" width="100;" alt="rainman110"/>
<br />
<sub><b>Martin Siggel</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/mkelley88">
<img src="https://avatars.githubusercontent.com/u/5567324?v=4" width="100;" alt="mkelley88"/>
<br />
<sub><b>Matthew T. Kelley</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/toolsfactory">
<img src="https://avatars.githubusercontent.com/u/7744975?v=4" width="100;" alt="toolsfactory"/>
<br />
<sub><b>Michael Geissler</b></sub>
</a>
</td>
</tr>
<tbody>
</table>
<!-- readme: contributors -end -->
---
<div align="center">
<a href="#top">
<img src="https://img.shields.io/badge/Back%20to%20Top-000000?style=for-the-badge&logo=github&logoColor=white" alt="Back to Top">
</a>
</div>

View File

@@ -32,6 +32,8 @@
#include "MainFlowControl.h"
#include "ov2640_sharpness.h"
#include "ov2640_specialEffect.h"
#include "ov2640_contrast_brightness.h"
#if (ESP_IDF_VERSION_MAJOR >= 5)
#include "soc/periph_defs.h"
@@ -122,12 +124,18 @@ esp_err_t CCamera::InitCam(void)
{
ESP_LOGD(TAG, "Init Camera");
TickType_t cam_xDelay = 100 / portTICK_PERIOD_MS;
CCstatus.ImageQuality = camera_config.jpeg_quality;
CCstatus.ImageFrameSize = camera_config.frame_size;
// De-init in case it was already initialized
esp_camera_deinit();
vTaskDelay(cam_xDelay);
// initialize the camera
esp_camera_deinit(); // De-init in case it was already initialized
esp_err_t err = esp_camera_init(&camera_config);
vTaskDelay(cam_xDelay);
if (err != ESP_OK)
{
@@ -221,13 +229,12 @@ void CCamera::ledc_init(void)
#endif
}
void CCamera::SetLEDIntensity(float _intrel)
int CCamera::SetLEDIntensity(int _intrel)
{
_intrel = min(_intrel, (float)100);
_intrel = max(_intrel, (float)0);
_intrel = _intrel / 100;
CCstatus.ImageLedIntensity = (int)(_intrel * 8191);
ESP_LOGD(TAG, "Set led_intensity to %d of 8191", CCstatus.ImageLedIntensity);
// CCstatus.ImageLedIntensity = (int)(std::min(std::max((float)0, _intrel), (float)100) / 100 * 8191)
Camera.LedIntensity = (int)((float)(std::min(std::max(0, _intrel), 100)) / 100 * 8191);
ESP_LOGD(TAG, "Set led_intensity to %i of 8191", Camera.LedIntensity);
return Camera.LedIntensity;
}
bool CCamera::getCameraInitSuccessful(void)
@@ -242,46 +249,46 @@ esp_err_t CCamera::setSensorDatenFromCCstatus(void)
if (s != NULL)
{
s->set_framesize(s, CCstatus.ImageFrameSize);
s->set_quality(s, CCstatus.ImageQuality); // 0 - 63
s->set_brightness(s, CCstatus.ImageBrightness); // -2 to 2
s->set_contrast(s, CCstatus.ImageContrast); // -2 to 2
// s->set_contrast(s, CCstatus.ImageContrast); // -2 to 2
// s->set_brightness(s, CCstatus.ImageBrightness); // -2 to 2
SetCamContrastBrightness(s, CCstatus.ImageContrast, CCstatus.ImageBrightness);
s->set_saturation(s, CCstatus.ImageSaturation); // -2 to 2
// s->set_sharpness(s, CCstatus.ImageSharpness); // auto-sharpness is not officially supported, default to 0
SetCamSharpness(CCstatus.ImageAutoSharpness, CCstatus.ImageSharpness);
s->set_denoise(s, CCstatus.ImageDenoiseLevel); // The OV2640 does not support it, OV3660 and OV5640 (0 to 8)
s->set_special_effect(s, CCstatus.ImageSpecialEffect); // 0 to 6 (0 - No Effect, 1 - Negative, 2 - Grayscale, 3 - Red Tint, 4 - Green Tint, 5 - Blue Tint, 6 - Sepia)
s->set_wb_mode(s, CCstatus.ImageWbMode); // 0 to 4 - if awb_gain enabled (0 - Auto, 1 - Sunny, 2 - Cloudy, 3 - Office, 4 - Home)
s->set_ae_level(s, CCstatus.ImageAeLevel); // -2 to 2
s->set_aec_value(s, CCstatus.ImageAecValue); // 0 to 1200
s->set_agc_gain(s, CCstatus.ImageAgcGain); // 0 to 30
s->set_quality(s, CCstatus.ImageQuality); // 0 - 63
// s->set_gainceiling(s, CCstatus.ImageGainceiling); // Image gain (GAINCEILING_x2, x4, x8, x16, x32, x64 or x128)
ov5640_set_gainceiling(s, CCstatus.ImageGainceiling);
s->set_lenc(s, CCstatus.ImageLenc); // 0 = disable , 1 = enable
SetCamGainceiling(s, CCstatus.ImageGainceiling);
s->set_gain_ctrl(s, CCstatus.ImageAgc); // 0 = disable , 1 = enable
s->set_exposure_ctrl(s, CCstatus.ImageAec); // 0 = disable , 1 = enable
s->set_hmirror(s, CCstatus.ImageHmirror); // 0 = disable , 1 = enable
s->set_vflip(s, CCstatus.ImageVflip); // 0 = disable , 1 = enable
s->set_whitebal(s, CCstatus.ImageAwb); // 0 = disable , 1 = enable
s->set_aec2(s, CCstatus.ImageAec2); // 0 = disable , 1 = enable
s->set_aec_value(s, CCstatus.ImageAecValue); // 0 to 1200
// s->set_special_effect(s, CCstatus.ImageSpecialEffect); // 0 to 6 (0 - No Effect, 1 - Negative, 2 - Grayscale, 3 - Red Tint, 4 - Green Tint, 5 - Blue Tint, 6 - Sepia)
SetCamSpecialEffect(s, CCstatus.ImageSpecialEffect);
s->set_wb_mode(s, CCstatus.ImageWbMode); // 0 to 4 - if awb_gain enabled (0 - Auto, 1 - Sunny, 2 - Cloudy, 3 - Office, 4 - Home)
s->set_ae_level(s, CCstatus.ImageAeLevel); // -2 to 2
s->set_dcw(s, CCstatus.ImageDcw); // 0 = disable , 1 = enable
s->set_bpc(s, CCstatus.ImageBpc); // 0 = disable , 1 = enable
s->set_wpc(s, CCstatus.ImageWpc); // 0 = disable , 1 = enable
s->set_raw_gma(s, CCstatus.ImageRawGma); // 0 = disable , 1 = enable
s->set_awb_gain(s, CCstatus.ImageAwbGain); // 0 = disable , 1 = enable
s->set_whitebal(s, CCstatus.ImageAwb); // 0 = disable , 1 = enable
s->set_agc_gain(s, CCstatus.ImageAgcGain); // 0 to 30
s->set_raw_gma(s, CCstatus.ImageRawGma); // 0 = disable , 1 = enable
s->set_lenc(s, CCstatus.ImageLenc); // 0 = disable , 1 = enable
s->set_dcw(s, CCstatus.ImageDcw); // 0 = disable , 1 = enable
// s->set_sharpness(s, CCstatus.ImageSharpness); // auto-sharpness is not officially supported, default to 0
SetCamSharpness(CCstatus.ImageAutoSharpness, CCstatus.ImageSharpness);
s->set_denoise(s, CCstatus.ImageDenoiseLevel); // The OV2640 does not support it, OV3660 and OV5640 (0 to 8)
TickType_t xDelay2 = 100 / portTICK_PERIOD_MS;
vTaskDelay(xDelay2);
TickType_t cam_xDelay = 100 / portTICK_PERIOD_MS;
vTaskDelay(cam_xDelay);
return ESP_OK;
}
@@ -300,30 +307,37 @@ esp_err_t CCamera::getSensorDatenToCCstatus(void)
CCstatus.CamSensor_id = s->id.PID;
CCstatus.ImageFrameSize = (framesize_t)s->status.framesize;
CCstatus.ImageContrast = s->status.contrast;
CCstatus.ImageBrightness = s->status.brightness;
CCstatus.ImageSaturation = s->status.saturation;
CCstatus.ImageQuality = s->status.quality;
CCstatus.ImageGainceiling = (gainceiling_t)s->status.gainceiling;
CCstatus.ImageQuality = s->status.quality;
CCstatus.ImageBrightness = s->status.brightness;
CCstatus.ImageContrast = s->status.contrast;
CCstatus.ImageSaturation = s->status.saturation;
// CCstatus.ImageSharpness = s->status.sharpness; // gibt -1 zurück, da es nicht unterstützt wird
CCstatus.ImageWbMode = s->status.wb_mode;
CCstatus.ImageAwb = s->status.awb;
CCstatus.ImageAwbGain = s->status.awb_gain;
CCstatus.ImageAec = s->status.aec;
CCstatus.ImageAec2 = s->status.aec2;
CCstatus.ImageAeLevel = s->status.ae_level;
CCstatus.ImageAecValue = s->status.aec_value;
CCstatus.ImageAgc = s->status.agc;
CCstatus.ImageAgcGain = s->status.agc_gain;
CCstatus.ImageBpc = s->status.bpc;
CCstatus.ImageWpc = s->status.wpc;
CCstatus.ImageRawGma = s->status.raw_gma;
CCstatus.ImageLenc = s->status.lenc;
CCstatus.ImageSpecialEffect = s->status.special_effect;
CCstatus.ImageAec = s->status.aec;
CCstatus.ImageHmirror = s->status.hmirror;
CCstatus.ImageVflip = s->status.vflip;
CCstatus.ImageAwb = s->status.awb;
CCstatus.ImageAec2 = s->status.aec2;
CCstatus.ImageAecValue = s->status.aec_value;
CCstatus.ImageSpecialEffect = s->status.special_effect;
CCstatus.ImageWbMode = s->status.wb_mode;
CCstatus.ImageAeLevel = s->status.ae_level;
CCstatus.ImageDcw = s->status.dcw;
CCstatus.ImageBpc = s->status.bpc;
CCstatus.ImageWpc = s->status.wpc;
CCstatus.ImageAwbGain = s->status.awb_gain;
CCstatus.ImageAgcGain = s->status.agc_gain;
CCstatus.ImageRawGma = s->status.raw_gma;
CCstatus.ImageLenc = s->status.lenc;
// CCstatus.ImageSharpness = s->status.sharpness; // gibt -1 zurück, da es nicht unterstützt wird
CCstatus.ImageDenoiseLevel = s->status.denoise;
return ESP_OK;
@@ -334,31 +348,96 @@ esp_err_t CCamera::getSensorDatenToCCstatus(void)
}
}
// on the OV5640, gainceiling must be set with the real value (x2>>>level = 2, .... x128>>>level = 128)
int CCamera::ov5640_set_gainceiling(sensor_t *s, gainceiling_t level)
// on the OV5640, gainceiling must be set with the real value (x2>>>gainceilingLevel = 2, .... x128>>>gainceilingLevel = 128)
int CCamera::SetCamGainceiling(sensor_t *s, gainceiling_t gainceilingLevel)
{
int ret = 0;
if (CCstatus.CamSensor_id == OV2640_PID)
{
ret = s->set_gainceiling(s, CCstatus.ImageGainceiling); // Image gain (GAINCEILING_x2, x4, x8, x16, x32, x64 or x128)
ret = s->set_gainceiling(s, gainceilingLevel); // Image gain (GAINCEILING_x2, x4, x8, x16, x32, x64 or x128)
}
else
{
int _level = (1 << ((int)level + 1));
int _level = (1 << ((int)gainceilingLevel + 1));
ret = s->set_reg(s, 0x3A18, 0xFF, (_level >> 8) & 3) || s->set_reg(s, 0x3A19, 0xFF, _level & 0xFF);
ret = s->set_reg(s, 0x3A18, 0xFF, (_level >> 8) & 3) || s->set_reg(s, 0x3A19, 0xFF, _level & 0xFF);
if (ret == 0)
{
// ESP_LOGD(TAG, "Set gainceiling to: %d", level);
s->status.gainceiling = level;
}
if (ret == 0)
{
// ESP_LOGD(TAG, "Set gainceiling to: %d", gainceilingLevel);
s->status.gainceiling = gainceilingLevel;
}
}
return ret;
}
void CCamera::SetCamSharpness(bool autoSharpnessEnabled, int sharpnessLevel)
{
sensor_t *s = esp_camera_sensor_get();
if (s != NULL)
{
if (CCstatus.CamSensor_id == OV2640_PID)
{
sharpnessLevel = min(2, max(-2, sharpnessLevel));
// The OV2640 does not officially support sharpness, so the detour is made with the ov2640_sharpness.cpp.
if (autoSharpnessEnabled)
{
ov2640_enable_auto_sharpness(s);
}
else
{
ov2640_set_sharpness(s, sharpnessLevel);
}
}
else
{
sharpnessLevel = min(3, max(-3, sharpnessLevel));
// for CAMERA_OV5640 and CAMERA_OV3660
if (autoSharpnessEnabled)
{
// autoSharpness is not supported, default to zero
s->set_sharpness(s, 0);
}
else
{
s->set_sharpness(s, sharpnessLevel);
}
}
}
else
{
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "SetCamSharpness, Failed to get Cam control structure");
}
}
void CCamera::SetCamSpecialEffect(sensor_t *s, int specialEffect)
{
if (CCstatus.CamSensor_id == OV2640_PID)
{
ov2640_set_special_effect(s, specialEffect);
}
else
{
s->set_special_effect(s, specialEffect);
}
}
void CCamera::SetCamContrastBrightness(sensor_t *s, int _contrast, int _brightness)
{
if (CCstatus.CamSensor_id == OV2640_PID)
{
ov2640_set_contrast_brightness(s, _contrast, _brightness);
}
else
{
s->set_contrast(s, _contrast); // -2 to 2
s->set_brightness(s, _brightness); // -2 to 2
}
}
// - It always zooms to the image center when offsets are zero
// - if imageSize = 0 then the image is not zoomed
// - if imageSize = max value, then the image is fully zoomed in
@@ -512,46 +591,6 @@ void CCamera::SetQualityZoomSize(int qual, framesize_t resol, bool zoomEnabled,
}
}
void CCamera::SetCamSharpness(bool _autoSharpnessEnabled, int _sharpnessLevel)
{
sensor_t *s = esp_camera_sensor_get();
if (s != NULL)
{
if (CCstatus.CamSensor_id == OV2640_PID)
{
_sharpnessLevel = min(2, max(-2, _sharpnessLevel));
// The OV2640 does not officially support sharpness, so the detour is made with the ov2640_sharpness.cpp.
if (_autoSharpnessEnabled)
{
ov2640_enable_auto_sharpness(s);
}
else
{
ov2640_set_sharpness(s, _sharpnessLevel);
}
}
else
{
_sharpnessLevel = min(3, max(-3, _sharpnessLevel));
// for CAMERA_OV5640 and CAMERA_OV3660
if (_autoSharpnessEnabled)
{
// autoSharpness is not supported, default to zero
s->set_sharpness(s, 0);
}
else
{
s->set_sharpness(s, _sharpnessLevel);
}
}
}
else
{
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "SetCamSharpness, Failed to get Cam control structure");
}
}
void CCamera::SetCamWindow(sensor_t *s, int frameSizeX, int frameSizeY, int xOffset, int yOffset, int xTotal, int yTotal, int xOutput, int yOutput, int imageVflip)
{
if (CCstatus.CamSensor_id == OV2640_PID)
@@ -904,6 +943,7 @@ esp_err_t CCamera::CaptureToStream(httpd_req_t *req, bool FlashlightOn)
{
Camera.setSensorDatenFromCCstatus(); // CCstatus >>> Kamera
Camera.SetQualityZoomSize(CCstatus.ImageQuality, CCstatus.ImageFrameSize, CCstatus.ImageZoomEnabled, CCstatus.ImageZoomOffsetX, CCstatus.ImageZoomOffsetY, CCstatus.ImageZoomSize, CCstatus.ImageVflip);
Camera.LedIntensity = CCstatus.ImageLedIntensity;
CFstatus.changedCameraSettings = false;
}
@@ -994,8 +1034,8 @@ void CCamera::LightOnOff(bool status)
#ifdef USE_PWM_LEDFLASH
if (status)
{
ESP_LOGD(TAG, "Internal Flash-LED turn on with PWM %d", CCstatus.ImageLedIntensity);
ESP_ERROR_CHECK(ledc_set_duty(LEDC_MODE, LEDC_CHANNEL, CCstatus.ImageLedIntensity));
ESP_LOGD(TAG, "Internal Flash-LED turn on with PWM %d", Camera.LedIntensity);
ESP_ERROR_CHECK(ledc_set_duty(LEDC_MODE, LEDC_CHANNEL, Camera.LedIntensity));
// Update duty to apply the new value
ESP_ERROR_CHECK(ledc_update_duty(LEDC_MODE, LEDC_CHANNEL));
}
@@ -1049,38 +1089,58 @@ void CCamera::SetImageWidthHeightFromResolution(framesize_t resol)
{
if (resol == FRAMESIZE_QVGA)
{
CCstatus.ImageHeight = 240;
CCstatus.ImageWidth = 320;
CCstatus.ImageHeight = 240;
}
else if (resol == FRAMESIZE_VGA)
{
CCstatus.ImageHeight = 480;
CCstatus.ImageWidth = 640;
CCstatus.ImageHeight = 480;
}
else if (resol == FRAMESIZE_SVGA)
{
CCstatus.ImageHeight = 600;
CCstatus.ImageWidth = 800;
CCstatus.ImageHeight = 600;
}
else if (resol == FRAMESIZE_XGA)
{
CCstatus.ImageHeight = 768;
CCstatus.ImageWidth = 1024;
CCstatus.ImageHeight = 768;
}
else if (resol == FRAMESIZE_HD)
{
CCstatus.ImageHeight = 720;
CCstatus.ImageWidth = 1280;
CCstatus.ImageHeight = 720;
}
else if (resol == FRAMESIZE_SXGA)
{
CCstatus.ImageHeight = 1024;
CCstatus.ImageWidth = 1280;
CCstatus.ImageHeight = 1024;
}
else if (resol == FRAMESIZE_UXGA)
{
CCstatus.ImageHeight = 1200;
CCstatus.ImageWidth = 1600;
CCstatus.ImageHeight = 1200;
}
else if (resol == FRAMESIZE_QXGA)
{
CCstatus.ImageWidth = 2048;
CCstatus.ImageHeight = 1536;
}
else if (resol == FRAMESIZE_WQXGA)
{
CCstatus.ImageWidth = 2560;
CCstatus.ImageHeight = 1600;
}
else if (resol == FRAMESIZE_QSXGA)
{
CCstatus.ImageWidth = 2560;
CCstatus.ImageHeight = 1920;
}
else
{
CCstatus.ImageWidth = 640;
CCstatus.ImageHeight = 480;
}
}
@@ -1110,8 +1170,24 @@ framesize_t CCamera::TextToFramesize(const char *_size)
{
return FRAMESIZE_UXGA; // 1600x1200
}
else if (strcmp(_size, "QXGA") == 0)
{
return FRAMESIZE_QXGA; // 2048x1536
}
else if (strcmp(_size, "WQXGA") == 0)
{
return FRAMESIZE_WQXGA; // 2560x1600
}
else if (strcmp(_size, "QSXGA") == 0)
{
return FRAMESIZE_QSXGA; // 2560x1920
}
else
{
return FRAMESIZE_VGA; // 640x480
}
return CCstatus.ImageFrameSize;
// return CCstatus.ImageFrameSize;
}
std::vector<std::string> demoFiles;

View File

@@ -80,6 +80,8 @@ protected:
void SanitizeZoomParams(int imageSize, int frameSizeX, int frameSizeY, int &imageWidth, int &imageHeight, int &zoomOffsetX, int &zoomOffsetY);
public:
int LedIntensity = 4096;
CCamera(void);
esp_err_t InitCam(void);
@@ -89,16 +91,18 @@ public:
esp_err_t setSensorDatenFromCCstatus(void);
esp_err_t getSensorDatenToCCstatus(void);
int ov5640_set_gainceiling(sensor_t *s, gainceiling_t level);
int SetCamGainceiling(sensor_t *s, gainceiling_t gainceilingLevel);
void SetCamSharpness(bool autoSharpnessEnabled, int sharpnessLevel);
void SetCamSpecialEffect(sensor_t *s, int specialEffect);
void SetCamContrastBrightness(sensor_t *s, int _contrast, int _brightness);
esp_err_t CaptureToHTTP(httpd_req_t *req, int delay = 0);
esp_err_t CaptureToStream(httpd_req_t *req, bool FlashlightOn);
void SetQualityZoomSize(int qual, framesize_t resol, bool zoomEnabled, int zoomOffsetX, int zoomOffsetY, int imageSize, int imageVflip);
void SetZoomSize(bool zoomEnabled, int zoomOffsetX, int zoomOffsetY, int imageSize, int imageVflip);
void SetCamSharpness(bool _autoSharpnessEnabled, int _sharpnessLevel);
void SetLEDIntensity(float _intrel);
int SetLEDIntensity(int _intrel);
bool testCamera(void);
bool getCameraInitSuccessful(void);
void useDemoMode(void);

View File

@@ -0,0 +1,88 @@
// Workaround - bug in cam library - enable bits are set without using bitwise OR logic -> only latest enable setting is used
// 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 reddish and b&w) enable
byte_0->bit4 = anable gray -> red spitial effect (Antique and blunish and greenish and reddish 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 -> red 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 --> ?
*/
#include <stdint.h>
#include "esp_camera.h"
#include "ov2640_contrast_brightness.h"
static const uint8_t brightness_regs[6][5] = {
{0x7C, 0x7D, 0x7C, 0x7D, 0x7D },
{0x00, 0x04, 0x09, 0x00, 0x00 }, /* -2 */
{0x00, 0x04, 0x09, 0x10, 0x00 }, /* -1 */
{0x00, 0x04, 0x09, 0x20, 0x00 }, /* 0 */
{0x00, 0x04, 0x09, 0x30, 0x00 }, /* +1 */
{0x00, 0x04, 0x09, 0x40, 0x00 }, /* +2 */
};
static const uint8_t contrast_regs[6][7] = {
{0x7C, 0x7D, 0x7C, 0x7D, 0x7D, 0x7D, 0x7D },
{0x00, 0x04, 0x07, 0x20, 0x18, 0x34, 0x06 }, /* -2 */
{0x00, 0x04, 0x07, 0x20, 0x1c, 0x2a, 0x06 }, /* -1 */
{0x00, 0x04, 0x07, 0x20, 0x20, 0x20, 0x06 }, /* 0 */
{0x00, 0x04, 0x07, 0x20, 0x24, 0x16, 0x06 }, /* +1 */
{0x00, 0x04, 0x07, 0x20, 0x28, 0x0c, 0x06 }, /* +2 */
};
int ov2640_set_contrast_brightness(sensor_t *sensor, int _contrast, int _brightness)
{
int ret=0;
_contrast += 3;
if (_contrast <= 0) {
_contrast = 3;
}
else if (_contrast > 5)
{
_contrast = 5;
}
sensor->status.contrast = _contrast-3;
_brightness += 3;
if (_brightness <= 0) {
_brightness = 3;
}
else if (_brightness > 5)
{
_brightness = 5;
}
int brightness = brightness_regs[_brightness][3];
sensor->status.brightness = _brightness-3;
// sensor->set_reg(sensor, int reg, int mask, int value)
sensor->set_reg(sensor, 0xFF, 0x01, 0x00); // Select DSP bank
for (int i=0; i<7; i++)
{
if (i == 5)
{
sensor->set_reg(sensor, contrast_regs[0][i], 0xFF, (brightness | contrast_regs[_contrast][i]));
}
else
{
sensor->set_reg(sensor, contrast_regs[0][i], 0xFF, contrast_regs[_contrast][i]);
}
}
return ret;
}

View File

@@ -0,0 +1,10 @@
#pragma once
#ifndef OV2640_CONTRAST_BRIGHTNESS_H
#define OV2640_CONTRAST_BRIGHTNESS_H
#include "esp_camera.h"
int ov2640_set_contrast_brightness(sensor_t *sensor, int _contrast, int _brightness);
#endif

View File

@@ -2,10 +2,9 @@
#include "esp_camera.h"
#include "ov2640_sharpness.h"
const static uint8_t OV2640_SHARPNESS_AUTO[]=
{
//reg, val, mask
//reg, val, mask
0xFF, 0x00, 0xFF,
0x92, 0x01, 0xFF,
0x93, 0x20, 0x20,
@@ -14,7 +13,7 @@ const static uint8_t OV2640_SHARPNESS_AUTO[]=
const static uint8_t OV2640_SHARPNESS_MANUAL[]=
{
//reg, val, mask
//reg, val, mask
0xFF, 0x00, 0xFF,
0x92, 0x01, 0xFF,
0x93, 0x00, 0x20,
@@ -23,7 +22,7 @@ const static uint8_t OV2640_SHARPNESS_MANUAL[]=
const static uint8_t OV2640_SHARPNESS_LEVEL0[]=
{
//reg, val, mask
//reg, val, mask
0xFF, 0x00, 0xFF,
0x92, 0x01, 0xFF,
0x93, 0xC0, 0x1F,
@@ -31,7 +30,7 @@ const static uint8_t OV2640_SHARPNESS_LEVEL0[]=
};
const static uint8_t OV2640_SHARPNESS_LEVEL1[]=
{
//reg, val, mask
//reg, val, mask
0xFF, 0x00, 0xFF,
0x92, 0x01, 0xFF,
0x93, 0xC1, 0x1F,
@@ -39,7 +38,7 @@ const static uint8_t OV2640_SHARPNESS_LEVEL1[]=
};
const static uint8_t OV2640_SHARPNESS_LEVEL2[]=
{
//reg, val, mask
//reg, val, mask
0xFF, 0x00, 0xFF,
0x92, 0x01, 0xFF,
0x93, 0xC2, 0x1F,
@@ -47,7 +46,7 @@ const static uint8_t OV2640_SHARPNESS_LEVEL2[]=
};
const static uint8_t OV2640_SHARPNESS_LEVEL3[]=
{
//reg, val, mask
//reg, val, mask
0xFF, 0x00, 0xFF,
0x92, 0x01, 0xFF,
0x93, 0xC4, 0x1F,
@@ -55,7 +54,7 @@ const static uint8_t OV2640_SHARPNESS_LEVEL3[]=
};
const static uint8_t OV2640_SHARPNESS_LEVEL4[]=
{
//reg, val, mask
//reg, val, mask
0xFF, 0x00, 0xFF,
0x92, 0x01, 0xFF,
0x93, 0xC8, 0x1F,
@@ -63,7 +62,7 @@ const static uint8_t OV2640_SHARPNESS_LEVEL4[]=
};
const static uint8_t OV2640_SHARPNESS_LEVEL5[]=
{
//reg, val, mask
//reg, val, mask
0xFF, 0x00, 0xFF,
0x92, 0x01, 0xFF,
0x93, 0xD0, 0x1F,
@@ -71,7 +70,7 @@ const static uint8_t OV2640_SHARPNESS_LEVEL5[]=
};
const static uint8_t OV2640_SHARPNESS_LEVEL6[]=
{
//reg, val, mask
//reg, val, mask
0xFF, 0x00, 0xFF,
0x92, 0x01, 0xFF,
0x93, 0xDF, 0x1F,
@@ -91,7 +90,6 @@ const static uint8_t *OV2640_SETTING_SHARPNESS[]=
#define OV2640_MAXLEVEL_SHARPNESS 6
static int table_mask_write(sensor_t *sensor, const uint8_t* ptab)
{
uint8_t address;
@@ -101,9 +99,9 @@ static int table_mask_write(sensor_t *sensor, const uint8_t* ptab)
const uint8_t *pdata = ptab;
if (pdata == NULL)
{
{
return -1;
}
}
while (1)
{
@@ -112,9 +110,9 @@ static int table_mask_write(sensor_t *sensor, const uint8_t* ptab)
mask = *pdata++;
if ((address == 0) && (value == 0) && (mask == 0))
{
{
break;
}
}
sensor->set_reg(sensor, address, mask, value);
}
@@ -122,31 +120,32 @@ static int table_mask_write(sensor_t *sensor, const uint8_t* ptab)
return 0;
}
int ov2640_enable_auto_sharpness(sensor_t *sensor)
{
table_mask_write(sensor, OV2640_SHARPNESS_AUTO);
sensor->status.sharpness = 0;
return 0;
}
int ov2640_set_sharpness(sensor_t *sensor, int sharpness)
{
int sharpness_temp = 0;
int sharpness_temp = 0;
if (sharpness < -3)
{
{
sharpness_temp = -3;
}
}
if (sharpness > OV2640_MAXLEVEL_SHARPNESS - 3)
{
{
sharpness_temp = OV2640_MAXLEVEL_SHARPNESS - 3;
}
}
table_mask_write(sensor, OV2640_SHARPNESS_MANUAL);
table_mask_write(sensor, OV2640_SETTING_SHARPNESS[sharpness_temp + 3]);
sensor->status.sharpness = sharpness;
return 0;
}

View File

@@ -0,0 +1,66 @@
// Workaround - bug in cam library - enable bits are set without using bitwise OR logic -> only latest enable setting is used
// 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 reddish and b&w) enable
byte_0->bit4 = anable gray -> red spitial effect (Antique and blunish and greenish and reddish 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 -> red 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 --> ?
*/
#include <stdint.h>
#include "esp_camera.h"
#include "ov2640_specialEffect.h"
static const uint8_t special_effects_regs[8][5] = {
{0x7C, 0x7D, 0x7C, 0x7D, 0x7D},
{0x00, 0X00, 0x05, 0X80, 0X80}, /* no effect */
{0x00, 0X40, 0x05, 0X80, 0X80}, /* negative */
{0x00, 0X18, 0x05, 0X80, 0X80}, /* black and white */
{0x00, 0X18, 0x05, 0X40, 0XC0}, /* reddish */
{0x00, 0X18, 0x05, 0X40, 0X40}, /* greenish */
{0x00, 0X18, 0x05, 0XA0, 0X40}, /* blue */
{0x00, 0X18, 0x05, 0X40, 0XA6}, /* retro */
};
int ov2640_set_special_effect(sensor_t *sensor, int effect)
{
int ret = 0;
effect++;
if (effect <= 0 || effect > 7)
{
effect = 1;
}
sensor->status.special_effect = effect - 1;
int registerValue = 0x06; // enable saturation, contrast, brightness
registerValue |= special_effects_regs[effect][1];
// sensor->set_reg(sensor, int reg, int mask, int value)
sensor->set_reg(sensor, 0xFF, 0x01, 0x00); // Select DSP bank
sensor->set_reg(sensor, special_effects_regs[0][0], 0xFF, 0x00);
sensor->set_reg(sensor, special_effects_regs[0][1], 0x5E, registerValue);
for (int i = 2; i < 5; i++)
{
sensor->set_reg(sensor, special_effects_regs[0][i], 0xFF, special_effects_regs[effect][i]);
}
return ret;
}

View File

@@ -0,0 +1,10 @@
#pragma once
#ifndef OV2640_SPECIALEFFECT_H
#define OV2640_SPECIALEFFECT_H
#include "esp_camera.h"
int ov2640_set_special_effect(sensor_t *sensor, int effect);
#endif

View File

@@ -101,6 +101,7 @@ esp_err_t handler_capture(httpd_req_t *req)
{
Camera.setSensorDatenFromCCstatus(); // CCstatus >>> Kamera
Camera.SetQualityZoomSize(CCstatus.ImageQuality, CCstatus.ImageFrameSize, CCstatus.ImageZoomEnabled, CCstatus.ImageZoomOffsetX, CCstatus.ImageZoomOffsetY, CCstatus.ImageZoomSize, CCstatus.ImageVflip);
Camera.LedIntensity = CCstatus.ImageLedIntensity;
CFstatus.changedCameraSettings = false;
}
@@ -159,6 +160,7 @@ esp_err_t handler_capture_with_light(httpd_req_t *req)
{
Camera.setSensorDatenFromCCstatus(); // CCstatus >>> Kamera
Camera.SetQualityZoomSize(CCstatus.ImageQuality, CCstatus.ImageFrameSize, CCstatus.ImageZoomEnabled, CCstatus.ImageZoomOffsetX, CCstatus.ImageZoomOffsetY, CCstatus.ImageZoomSize, CCstatus.ImageVflip);
Camera.LedIntensity = CCstatus.ImageLedIntensity;
CFstatus.changedCameraSettings = false;
}
@@ -241,6 +243,7 @@ esp_err_t handler_capture_save_to_file(httpd_req_t *req)
{
Camera.setSensorDatenFromCCstatus(); // CCstatus >>> Kamera
Camera.SetQualityZoomSize(CCstatus.ImageQuality, CCstatus.ImageFrameSize, CCstatus.ImageZoomEnabled, CCstatus.ImageZoomOffsetX, CCstatus.ImageZoomOffsetY, CCstatus.ImageZoomSize, CCstatus.ImageVflip);
Camera.LedIntensity = CCstatus.ImageLedIntensity;
CFstatus.changedCameraSettings = false;
}

View File

@@ -196,7 +196,7 @@ bool ClassFlowControll::StartMQTTService()
void ClassFlowControll::SetInitialParameter(void)
{
AutoStart = false;
AutoStart = true;
SetupModeActive = false;
AutoInterval = 10; // Minutes
flowdigit = NULL;
@@ -210,7 +210,8 @@ void ClassFlowControll::SetInitialParameter(void)
bool ClassFlowControll::getIsAutoStart(void)
{
return AutoStart;
//return AutoStart;
return true; // Flow must always be enabled, else the manual trigger (REST, MQTT) will not work!
}
@@ -557,10 +558,6 @@ bool ClassFlowControll::ReadParameter(FILE* pfile, string& aktparamgraph)
while (this->getNextLine(pfile, &aktparamgraph) && !this->isNewParagraph(aktparamgraph)) {
splitted = ZerlegeZeile(aktparamgraph, " =");
if ((toUpper(splitted[0]) == "AUTOSTART") && (splitted.size() > 1)) {
AutoStart = alphanumericToBoolean(splitted[1]);
}
if ((toUpper(splitted[0]) == "INTERVAL") && (splitted.size() > 1)) {
if (isStringNumeric(splitted[1]))

View File

@@ -66,6 +66,8 @@ struct NumberPost {
float AnalogToDigitTransitionStart; // AnalogToDigitTransitionStartValue; FIXME: need a better description; When is the digit > x.1, i.e. when does it start to tilt?
int Nachkomma; // decimalPlaces; usually defined by the number of analog ROIs; affected by DecimalShift
string DomoticzIdx; // Domoticz counter Idx
string FieldV1; // influxdbFieldName_v1; Name of the Field in InfluxDBv1
string MeasurementV1; // influxdbMeasurementName_v1; Name of the Measurement in InfluxDBv1

View File

@@ -51,6 +51,7 @@ void ClassFlowMQTT::SetInitialParameter(void)
ListFlowControll = NULL;
disabled = false;
keepAlive = 25*60;
domoticzintopic = "";
}
ClassFlowMQTT::ClassFlowMQTT()
@@ -105,43 +106,44 @@ bool ClassFlowMQTT::ReadParameter(FILE* pfile, string& aktparamgraph)
while (this->getNextLine(pfile, &aktparamgraph) && !this->isNewParagraph(aktparamgraph))
{
splitted = ZerlegeZeile(aktparamgraph);
if ((toUpper(splitted[0]) == "CACERT") && (splitted.size() > 1))
std::string _param = GetParameterName(splitted[0]);
if ((toUpper(_param) == "CACERT") && (splitted.size() > 1))
{
this->caCertFilename = splitted[1];
}
if ((toUpper(splitted[0]) == "CLIENTCERT") && (splitted.size() > 1))
if ((toUpper(_param) == "CLIENTCERT") && (splitted.size() > 1))
{
this->clientCertFilename = splitted[1];
}
if ((toUpper(splitted[0]) == "CLIENTKEY") && (splitted.size() > 1))
if ((toUpper(_param) == "CLIENTKEY") && (splitted.size() > 1))
{
this->clientKeyFilename = splitted[1];
}
if ((toUpper(splitted[0]) == "USER") && (splitted.size() > 1))
if ((toUpper(_param) == "USER") && (splitted.size() > 1))
{
this->user = splitted[1];
}
if ((toUpper(splitted[0]) == "PASSWORD") && (splitted.size() > 1))
if ((toUpper(_param) == "PASSWORD") && (splitted.size() > 1))
{
this->password = splitted[1];
}
if ((toUpper(splitted[0]) == "URI") && (splitted.size() > 1))
if ((toUpper(_param) == "URI") && (splitted.size() > 1))
{
this->uri = splitted[1];
}
if ((toUpper(splitted[0]) == "RETAINMESSAGES") && (splitted.size() > 1))
if ((toUpper(_param) == "RETAINMESSAGES") && (splitted.size() > 1))
{
if (toUpper(splitted[1]) == "TRUE") {
SetRetainFlag = true;
setMqtt_Server_Retain(SetRetainFlag);
}
}
if ((toUpper(splitted[0]) == "HOMEASSISTANTDISCOVERY") && (splitted.size() > 1))
if ((toUpper(_param) == "HOMEASSISTANTDISCOVERY") && (splitted.size() > 1))
{
if (toUpper(splitted[1]) == "TRUE")
SetHomeassistantDiscoveryEnabled(true);
}
if ((toUpper(splitted[0]) == "METERTYPE") && (splitted.size() > 1)) {
if ((toUpper(_param) == "METERTYPE") && (splitted.size() > 1)) {
/* Use meter type for the device class
Make sure it is a listed one on https://developers.home-assistant.io/docs/core/entity/sensor/#available-device-classes */
if (toUpper(splitted[1]) == "WATER_M3") {
@@ -151,7 +153,7 @@ bool ClassFlowMQTT::ReadParameter(FILE* pfile, string& aktparamgraph)
mqttServer_setMeterType("water", "L", "h", "L/h");
}
else if (toUpper(splitted[1]) == "WATER_FT3") {
mqttServer_setMeterType("water", "ft³", "m", "ft³/m"); // Minutes
mqttServer_setMeterType("water", "ft³", "m", "ft³/m"); // m = Minutes
}
else if (toUpper(splitted[1]) == "WATER_GAL") {
mqttServer_setMeterType("water", "gal", "h", "gal/h");
@@ -160,7 +162,7 @@ bool ClassFlowMQTT::ReadParameter(FILE* pfile, string& aktparamgraph)
mqttServer_setMeterType("gas", "", "h", "m³/h");
}
else if (toUpper(splitted[1]) == "GAS_FT3") {
mqttServer_setMeterType("gas", "ft³", "m", "ft³/m"); // Minutes
mqttServer_setMeterType("gas", "ft³", "m", "ft³/m"); // m = Minutes
}
else if (toUpper(splitted[1]) == "ENERGY_WH") {
mqttServer_setMeterType("energy", "Wh", "h", "W");
@@ -176,15 +178,26 @@ bool ClassFlowMQTT::ReadParameter(FILE* pfile, string& aktparamgraph)
}
}
if ((toUpper(splitted[0]) == "CLIENTID") && (splitted.size() > 1))
if ((toUpper(_param) == "CLIENTID") && (splitted.size() > 1))
{
this->clientname = splitted[1];
}
if (((toUpper(splitted[0]) == "TOPIC") || (toUpper(splitted[0]) == "MAINTOPIC")) && (splitted.size() > 1))
if (((toUpper(_param) == "TOPIC") || (toUpper(splitted[0]) == "MAINTOPIC")) && (splitted.size() > 1))
{
maintopic = splitted[1];
}
if (((toUpper(_param) == "DOMOTICZTOPICIN")) && (splitted.size() > 1))
{
this->domoticzintopic = splitted[1];
}
if (((toUpper(_param) == "DOMOTICZIDX")) && (splitted.size() > 1))
{
handleIdx(splitted[0], splitted[1]);
}
}
/* Note:
@@ -193,6 +206,7 @@ bool ClassFlowMQTT::ReadParameter(FILE* pfile, string& aktparamgraph)
* To work around this, we delay the start and trigger it from ClassFlowControll::ReadParameter() */
mqttServer_setMainTopic(maintopic);
mqttServer_setDmoticzInTopic(domoticzintopic);
return true;
}
@@ -210,7 +224,7 @@ bool ClassFlowMQTT::Start(float AutoInterval)
mqttServer_setParameter(flowpostprocessing->GetNumbers(), keepAlive, roundInterval);
bool MQTTConfigCheck = MQTT_Configure(uri, clientname, user, password, maintopic, LWT_TOPIC, LWT_CONNECTED,
bool MQTTConfigCheck = MQTT_Configure(uri, clientname, user, password, maintopic, domoticzintopic, LWT_TOPIC, LWT_CONNECTED,
LWT_DISCONNECTED, caCertFilename, clientCertFilename, clientKeyFilename,
keepAlive, SetRetainFlag, (void *)&GotConnected);
@@ -235,6 +249,8 @@ bool ClassFlowMQTT::doFlow(string zwtime)
std::string resultchangabs = "";
string zw = "";
string namenumber = "";
string domoticzpayload = "";
string DomoticzIdx = "";
int qos = 1;
/* Send the the Homeassistant Discovery and the Static Topics in case they where scheduled */
@@ -258,16 +274,20 @@ bool ClassFlowMQTT::doFlow(string zwtime)
resultchangabs = (*NUMBERS)[i]->ReturnChangeAbsolute; // Units per round
resulttimestamp = (*NUMBERS)[i]->timeStamp;
DomoticzIdx = (*NUMBERS)[i]->DomoticzIdx;
domoticzpayload = "{\"command\":\"udevice\",\"idx\":" + DomoticzIdx + ",\"svalue\":\""+ result + "\"}";
namenumber = (*NUMBERS)[i]->name;
if (namenumber == "default")
namenumber = maintopic + "/";
else
namenumber = maintopic + "/" + namenumber + "/";
if ((domoticzintopic.length() > 0) && (result.length() > 0))
success |= MQTTPublish(domoticzintopic, domoticzpayload, qos, SetRetainFlag);
if (result.length() > 0)
if (result.length() > 0)
success |= MQTTPublish(namenumber + "value", result, qos, SetRetainFlag);
if (resulterror.length() > 0)
success |= MQTTPublish(namenumber + "error", resulterror, qos, SetRetainFlag);
@@ -325,6 +345,26 @@ bool ClassFlowMQTT::doFlow(string zwtime)
return true;
}
void ClassFlowMQTT::handleIdx(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]->DomoticzIdx = _value;
}
if (flowpostprocessing->NUMBERS[j]->name == _digit)
{
flowpostprocessing->NUMBERS[j]->DomoticzIdx = _value;
}
}
}
#endif //ENABLE_MQTT

View File

@@ -23,9 +23,9 @@ protected:
bool SetRetainFlag;
int keepAlive; // Seconds
float roundInterval; // Minutes
std::string maintopic;
std::string maintopic, domoticzintopic;
void SetInitialParameter(void);
void handleIdx(string _decsep, string _value);
public:
ClassFlowMQTT();

View File

@@ -564,7 +564,7 @@ bool ClassFlowPostProcessing::ReadParameter(FILE* pfile, string& aktparamgraph)
handleDecimalSeparator(splitted[0], splitted[1]);
}
if ((toUpper(_param) == "AnalogToDigitTransitionStart") && (splitted.size() > 1)) {
if ((toUpper(_param) == "ANALOGTODIGITTRANSITIONSTART") && (splitted.size() > 1)) {
handleAnalogToDigitTransitionStart(splitted[0], splitted[1]);
}

View File

@@ -499,8 +499,8 @@ bool ClassFlowTakeImage::ReadParameter(FILE *pfile, string &aktparamgraph)
{
if (isStringNumeric(splitted[1]))
{
float ledintensity = std::stof(splitted[1]);
Camera.SetLEDIntensity(ledintensity);
int ledintensity = std::stoi(splitted[1]);
CCstatus.ImageLedIntensity = Camera.SetLEDIntensity(ledintensity);
}
}
@@ -559,6 +559,7 @@ bool ClassFlowTakeImage::doFlow(string zwtime)
{
Camera.setSensorDatenFromCCstatus(); // CCstatus >>> Kamera
Camera.SetQualityZoomSize(CCstatus.ImageQuality, CCstatus.ImageFrameSize, CCstatus.ImageZoomEnabled, CCstatus.ImageZoomOffsetX, CCstatus.ImageZoomOffsetY, CCstatus.ImageZoomSize, CCstatus.ImageVflip);
Camera.LedIntensity = CCstatus.ImageLedIntensity;
CFstatus.changedCameraSettings = false;
}

View File

@@ -148,31 +148,39 @@ esp_err_t setCCstatusToCFstatus(void)
CFstatus.CamSensor_id = CCstatus.CamSensor_id;
CFstatus.ImageFrameSize = CCstatus.ImageFrameSize;
CFstatus.ImageGainceiling = CCstatus.ImageGainceiling;
CFstatus.ImageContrast = CCstatus.ImageContrast;
CFstatus.ImageBrightness = CCstatus.ImageBrightness;
CFstatus.ImageSaturation = CCstatus.ImageSaturation;
CFstatus.ImageQuality = CCstatus.ImageQuality;
CFstatus.ImageBrightness = CCstatus.ImageBrightness;
CFstatus.ImageContrast = CCstatus.ImageContrast;
CFstatus.ImageSaturation = CCstatus.ImageSaturation;
CFstatus.ImageSharpness = CCstatus.ImageSharpness;
CFstatus.ImageAutoSharpness = CCstatus.ImageAutoSharpness;
CFstatus.ImageWbMode = CCstatus.ImageWbMode;
CFstatus.ImageAwb = CCstatus.ImageAwb;
CFstatus.ImageAwbGain = CCstatus.ImageAwbGain;
CFstatus.ImageAec = CCstatus.ImageAec;
CFstatus.ImageAec2 = CCstatus.ImageAec2;
CFstatus.ImageAeLevel = CCstatus.ImageAeLevel;
CFstatus.ImageAecValue = CCstatus.ImageAecValue;
CFstatus.ImageGainceiling = CCstatus.ImageGainceiling;
CFstatus.ImageAgc = CCstatus.ImageAgc;
CFstatus.ImageAgcGain = CCstatus.ImageAgcGain;
CFstatus.ImageBpc = CCstatus.ImageBpc;
CFstatus.ImageWpc = CCstatus.ImageWpc;
CFstatus.ImageRawGma = CCstatus.ImageRawGma;
CFstatus.ImageLenc = CCstatus.ImageLenc;
CFstatus.ImageSpecialEffect = CCstatus.ImageSpecialEffect;
CFstatus.ImageAec = CCstatus.ImageAec;
CFstatus.ImageHmirror = CCstatus.ImageHmirror;
CFstatus.ImageVflip = CCstatus.ImageVflip;
CFstatus.ImageAwb = CCstatus.ImageAwb;
CFstatus.ImageAec2 = CCstatus.ImageAec2;
CFstatus.ImageAecValue = CCstatus.ImageAecValue;
CFstatus.ImageSpecialEffect = CCstatus.ImageSpecialEffect;
CFstatus.ImageWbMode = CCstatus.ImageWbMode;
CFstatus.ImageAeLevel = CCstatus.ImageAeLevel;
CFstatus.ImageDcw = CCstatus.ImageDcw;
CFstatus.ImageBpc = CCstatus.ImageBpc;
CFstatus.ImageWpc = CCstatus.ImageWpc;
CFstatus.ImageAwbGain = CCstatus.ImageAwbGain;
CFstatus.ImageAgcGain = CCstatus.ImageAgcGain;
CFstatus.ImageRawGma = CCstatus.ImageRawGma;
CFstatus.ImageLenc = CCstatus.ImageLenc;
CFstatus.ImageSharpness = CCstatus.ImageSharpness;
CFstatus.ImageAutoSharpness = CCstatus.ImageAutoSharpness;
CFstatus.ImageDenoiseLevel = CCstatus.ImageDenoiseLevel;
CFstatus.ImageLedIntensity = CCstatus.ImageLedIntensity;
@@ -192,31 +200,39 @@ esp_err_t setCFstatusToCCstatus(void)
// CCstatus.CamSensor_id = CFstatus.CamSensor_id;
CCstatus.ImageFrameSize = CFstatus.ImageFrameSize;
CCstatus.ImageGainceiling = CFstatus.ImageGainceiling;
CCstatus.ImageContrast = CFstatus.ImageContrast;
CCstatus.ImageBrightness = CFstatus.ImageBrightness;
CCstatus.ImageSaturation = CFstatus.ImageSaturation;
CCstatus.ImageQuality = CFstatus.ImageQuality;
CCstatus.ImageBrightness = CFstatus.ImageBrightness;
CCstatus.ImageContrast = CFstatus.ImageContrast;
CCstatus.ImageSaturation = CFstatus.ImageSaturation;
CCstatus.ImageSharpness = CFstatus.ImageSharpness;
CCstatus.ImageAutoSharpness = CFstatus.ImageAutoSharpness;
CCstatus.ImageWbMode = CFstatus.ImageWbMode;
CCstatus.ImageAwb = CFstatus.ImageAwb;
CCstatus.ImageAwbGain = CFstatus.ImageAwbGain;
CCstatus.ImageAec = CFstatus.ImageAec;
CCstatus.ImageAec2 = CFstatus.ImageAec2;
CCstatus.ImageAeLevel = CFstatus.ImageAeLevel;
CCstatus.ImageAecValue = CFstatus.ImageAecValue;
CCstatus.ImageGainceiling = CFstatus.ImageGainceiling;
CCstatus.ImageAgc = CFstatus.ImageAgc;
CCstatus.ImageAgcGain = CFstatus.ImageAgcGain;
CCstatus.ImageBpc = CFstatus.ImageBpc;
CCstatus.ImageWpc = CFstatus.ImageWpc;
CCstatus.ImageRawGma = CFstatus.ImageRawGma;
CCstatus.ImageLenc = CFstatus.ImageLenc;
CCstatus.ImageSpecialEffect = CFstatus.ImageSpecialEffect;
CCstatus.ImageAec = CFstatus.ImageAec;
CCstatus.ImageHmirror = CFstatus.ImageHmirror;
CCstatus.ImageVflip = CFstatus.ImageVflip;
CCstatus.ImageAwb = CFstatus.ImageAwb;
CCstatus.ImageAec2 = CFstatus.ImageAec2;
CCstatus.ImageAecValue = CFstatus.ImageAecValue;
CCstatus.ImageSpecialEffect = CFstatus.ImageSpecialEffect;
CCstatus.ImageWbMode = CFstatus.ImageWbMode;
CCstatus.ImageAeLevel = CFstatus.ImageAeLevel;
CCstatus.ImageDcw = CFstatus.ImageDcw;
CCstatus.ImageBpc = CFstatus.ImageBpc;
CCstatus.ImageWpc = CFstatus.ImageWpc;
CCstatus.ImageAwbGain = CFstatus.ImageAwbGain;
CCstatus.ImageAgcGain = CFstatus.ImageAgcGain;
CCstatus.ImageRawGma = CFstatus.ImageRawGma;
CCstatus.ImageLenc = CFstatus.ImageLenc;
CCstatus.ImageSharpness = CFstatus.ImageSharpness;
CCstatus.ImageAutoSharpness = CFstatus.ImageAutoSharpness;
CCstatus.ImageDenoiseLevel = CFstatus.ImageDenoiseLevel;
CCstatus.ImageLedIntensity = CFstatus.ImageLedIntensity;
@@ -238,43 +254,43 @@ esp_err_t setCFstatusToCam(void)
if (s != NULL)
{
s->set_framesize(s, CFstatus.ImageFrameSize);
// s->set_contrast(s, CFstatus.ImageContrast); // -2 to 2
// s->set_brightness(s, CFstatus.ImageBrightness); // -2 to 2
Camera.SetCamContrastBrightness(s, CFstatus.ImageContrast, CFstatus.ImageBrightness);
s->set_saturation(s, CFstatus.ImageSaturation); // -2 to 2
s->set_quality(s, CFstatus.ImageQuality); // 0 - 63
s->set_brightness(s, CFstatus.ImageBrightness); // -2 to 2
s->set_contrast(s, CFstatus.ImageContrast); // -2 to 2
s->set_saturation(s, CFstatus.ImageSaturation); // -2 to 2
// s->set_sharpness(s, CFstatus.ImageSharpness); // auto-sharpness is not officially supported, default to 0
Camera.SetCamSharpness(CFstatus.ImageAutoSharpness, CFstatus.ImageSharpness);
s->set_denoise(s, CFstatus.ImageDenoiseLevel); // The OV2640 does not support it, OV3660 and OV5640 (0 to 8)
s->set_special_effect(s, CFstatus.ImageSpecialEffect); // 0 to 6 (0 - No Effect, 1 - Negative, 2 - Grayscale, 3 - Red Tint, 4 - Green Tint, 5 - Blue Tint, 6 - Sepia)
s->set_wb_mode(s, CFstatus.ImageWbMode); // 0 to 4 - if awb_gain enabled (0 - Auto, 1 - Sunny, 2 - Cloudy, 3 - Office, 4 - Home)
s->set_ae_level(s, CFstatus.ImageAeLevel); // -2 to 2
s->set_aec_value(s, CFstatus.ImageAecValue); // 0 to 1200
s->set_agc_gain(s, CFstatus.ImageAgcGain); // 0 to 30
// s->set_gainceiling(s, CFstatus.ImageGainceiling); // Image gain (GAINCEILING_x2, x4, x8, x16, x32, x64 or x128)
Camera.ov5640_set_gainceiling(s, CFstatus.ImageGainceiling);
Camera.SetCamGainceiling(s, CFstatus.ImageGainceiling);
s->set_lenc(s, CFstatus.ImageLenc); // 0 = disable , 1 = enable
s->set_gain_ctrl(s, CFstatus.ImageAgc); // 0 = disable , 1 = enable
s->set_exposure_ctrl(s, CFstatus.ImageAec); // 0 = disable , 1 = enable
s->set_hmirror(s, CFstatus.ImageHmirror); // 0 = disable , 1 = enable
s->set_vflip(s, CFstatus.ImageVflip); // 0 = disable , 1 = enable
s->set_hmirror(s, CFstatus.ImageHmirror); // 0 = disable , 1 = enable
s->set_vflip(s, CFstatus.ImageVflip); // 0 = disable , 1 = enable
s->set_aec2(s, CFstatus.ImageAec2); // 0 = disable , 1 = enable
s->set_whitebal(s, CFstatus.ImageAwb); // 0 = disable , 1 = enable
s->set_aec2(s, CFstatus.ImageAec2); // 0 = disable , 1 = enable
s->set_aec_value(s, CFstatus.ImageAecValue); // 0 to 1200
// s->set_special_effect(s, CFstatus.ImageSpecialEffect); // 0 to 6 (0 - No Effect, 1 - Negative, 2 - Grayscale, 3 - Red Tint, 4 - Green Tint, 5 - Blue Tint, 6 - Sepia)
Camera.SetCamSpecialEffect(s, CFstatus.ImageSpecialEffect);
s->set_wb_mode(s, CFstatus.ImageWbMode); // 0 to 4 - if awb_gain enabled (0 - Auto, 1 - Sunny, 2 - Cloudy, 3 - Office, 4 - Home)
s->set_ae_level(s, CFstatus.ImageAeLevel); // -2 to 2
s->set_bpc(s, CFstatus.ImageBpc); // 0 = disable , 1 = enable
s->set_wpc(s, CFstatus.ImageWpc); // 0 = disable , 1 = enable
s->set_dcw(s, CFstatus.ImageDcw); // 0 = disable , 1 = enable
s->set_bpc(s, CFstatus.ImageBpc); // 0 = disable , 1 = enable
s->set_wpc(s, CFstatus.ImageWpc); // 0 = disable , 1 = enable
s->set_awb_gain(s, CFstatus.ImageAwbGain); // 0 = disable , 1 = enable
s->set_agc_gain(s, CFstatus.ImageAgcGain); // 0 to 30
s->set_raw_gma(s, CFstatus.ImageRawGma); // 0 = disable , 1 = enable
s->set_lenc(s, CFstatus.ImageLenc); // 0 = disable , 1 = enable
s->set_awb_gain(s, CFstatus.ImageAwbGain); // 0 = disable , 1 = enable
s->set_whitebal(s, CFstatus.ImageAwb); // 0 = disable , 1 = enable
s->set_dcw(s, CFstatus.ImageDcw); // 0 = disable , 1 = enable
// s->set_sharpness(s, CFstatus.ImageSharpness); // auto-sharpness is not officially supported, default to 0
Camera.SetCamSharpness(CFstatus.ImageAutoSharpness, CFstatus.ImageSharpness);
s->set_denoise(s, CFstatus.ImageDenoiseLevel); // The OV2640 does not support it, OV3660 and OV5640 (0 to 8)
TickType_t xDelay2 = 100 / portTICK_PERIOD_MS;
vTaskDelay(xDelay2);
@@ -406,8 +422,7 @@ esp_err_t handler_flow_start(httpd_req_t *req)
else
{
LogFile.WriteToFile(ESP_LOG_WARN, TAG, "Flow start triggered by REST API, but flow is not active!");
const char *resp_str = "WARNING: Flow start triggered by REST API, but flow is not active";
httpd_resp_send(req, resp_str, HTTPD_RESP_USE_STRLEN);
httpd_resp_send_err(req, HTTPD_403_FORBIDDEN, "Flow start triggered by REST API, but flow is not active");
}
#ifdef DEBUG_DETAIL_ON
@@ -1348,9 +1363,8 @@ esp_err_t handler_editflow(httpd_req_t *req)
std::string _ledi = std::string(_valuechar);
if (isStringNumeric(_ledi))
{
float _ImageLedIntensity = std::stof(_valuechar);
Camera.SetLEDIntensity(_ImageLedIntensity);
CFstatus.ImageLedIntensity = CCstatus.ImageLedIntensity;
int _ImageLedIntensity = std::stoi(_valuechar);
CFstatus.ImageLedIntensity = Camera.SetLEDIntensity(_ImageLedIntensity);
}
}
@@ -1487,6 +1501,28 @@ esp_err_t handler_rssi(httpd_req_t *req)
return ESP_OK;
}
esp_err_t handler_current_date(httpd_req_t *req)
{
#ifdef DEBUG_DETAIL_ON
LogFile.WriteHeapInfo("handler_uptime - Start");
#endif
std::string formatedDateAndTime = getCurrentTimeString("%Y-%m-%d %H:%M:%S");
// std::string formatedDate = getCurrentTimeString("%Y-%m-%d");
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
httpd_resp_send(req, formatedDateAndTime.c_str(), formatedDateAndTime.length());
/* Respond with an empty chunk to signal HTTP response completion */
httpd_resp_sendstr_chunk(req, NULL);
#ifdef DEBUG_DETAIL_ON
LogFile.WriteHeapInfo("handler_uptime - End");
#endif
return ESP_OK;
}
esp_err_t handler_uptime(httpd_req_t *req)
{
#ifdef DEBUG_DETAIL_ON
@@ -1798,6 +1834,11 @@ void register_server_main_flow_task_uri(httpd_handle_t server)
camuri.user_ctx = (void *)"Light Off";
httpd_register_uri_handler(server, &camuri);
camuri.uri = "/date";
camuri.handler = handler_current_date;
camuri.user_ctx = (void *)"Light Off";
httpd_register_uri_handler(server, &camuri);
camuri.uri = "/uptime";
camuri.handler = handler_uptime;
camuri.user_ctx = (void *)"Light Off";

View File

@@ -34,7 +34,7 @@ bool mqtt_initialized = false;
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;
std::string uri, client_id, lwt_topic, lwt_connected, lwt_disconnected, user, password, maintopic, domoticz_in_topic;
std::string caCert, clientCert, clientKey;
int keepalive;
bool SetRetainFlag;
@@ -174,7 +174,8 @@ static esp_err_t mqtt_event_handler_cb(esp_mqtt_event_handle_t event) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Connection refused, not authorized. Check username/password (0x05)");
}
else {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Connection Refused with unknown event id:" + std::to_string(event->error_handle->connect_return_code));
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Other event id:" + event->error_handle->connect_return_code);
ESP_LOGE(TAG, "Other event id:%d", event->error_handle->connect_return_code);
}
#ifdef DEBUG_DETAIL_ON
@@ -204,7 +205,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,
std::string _maintopic, std::string _domoticz_in_topic, std::string _lwt, std::string _lwt_connected, std::string _lwt_disconnected,
std::string _cacertfilename, std::string _clientcertfilename, std::string _clientkeyfilename,
int _keepalive, bool _SetRetainFlag, void *_callbackOnConnected) {
if ((_mqttURI.length() == 0) || (_maintopic.length() == 0) || (_clientid.length() == 0))
@@ -221,6 +222,7 @@ bool MQTT_Configure(std::string _mqttURI, std::string _clientid, std::string _us
keepalive = _keepalive;
SetRetainFlag = _SetRetainFlag;
maintopic = _maintopic;
domoticz_in_topic = _domoticz_in_topic;
callbackOnConnected = ( void (*)(std::string, bool) )(_callbackOnConnected);
if (_clientcertfilename.length() && _clientkeyfilename.length()){
@@ -387,13 +389,7 @@ bool mqtt_handler_flow_start(std::string _topic, char* _data, int _data_len)
{
ESP_LOGD(TAG, "Handler called: topic %s, data %.*s", _topic.c_str(), _data_len, _data);
if (_data_len > 0) {
MQTTCtrlFlowStart(_topic);
}
else {
LogFile.WriteToFile(ESP_LOG_WARN, TAG, "handler_flow_start: handler called, but no data");
}
MQTTCtrlFlowStart(_topic);
return ESP_OK;
}

View File

@@ -10,7 +10,7 @@
#include <functional>
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,
std::string _maintopic, std::string _domoticz_in_topic, std::string _lwt, std::string _lwt_connected, std::string _lwt_disconnected,
std::string _cacertfilename, std::string _clientcertfilename, std::string _clientkeyfilename,
int _keepalive, bool SetRetainFlag, void *callbackOnConnected);
int MQTT_Init();

View File

@@ -32,10 +32,11 @@ std::string rateUnit = "Unit/Minute";
float roundInterval; // Minutes
int keepAlive = 0; // Seconds
bool retainFlag;
static std::string maintopic;
static std::string maintopic, domoticzintopic;
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) {
NUMBERS = _NUMBERS;
keepAlive = _keepAlive;
@@ -85,9 +86,12 @@ bool sendHomeAssistantDiscoveryTopic(std::string group, std::string field,
* This means a maintopic "home/test/watermeter" is transformed to the discovery topic "homeassistant/sensor/watermeter/..."
*/
std::string node_id = createNodeId(maintopic);
if (field == "problem") { // Special binary sensor which is based on error topic
if (field == "problem") { // Special case: Binary sensor which is based on error topic
topicFull = "homeassistant/binary_sensor/" + node_id + "/" + configTopic + "/config";
}
else if (field == "flowstart") { // Special case: Button
topicFull = "homeassistant/button/" + node_id + "/" + configTopic + "/config";
}
else {
topicFull = "homeassistant/sensor/" + node_id + "/" + configTopic + "/config";
}
@@ -101,7 +105,7 @@ bool sendHomeAssistantDiscoveryTopic(std::string group, std::string field,
"\"icon\": \"mdi:" + icon + "\",";
if (group != "") {
if (field == "problem") { // Special binary sensor which is based on error topic
if (field == "problem") { // Special case: Binary sensor which is based on error topic
payload += "\"state_topic\": \"~/" + group + "/error\",";
payload += "\"value_template\": \"{{ 'OFF' if 'no error' in value else 'ON'}}\",";
}
@@ -110,10 +114,13 @@ bool sendHomeAssistantDiscoveryTopic(std::string group, std::string field,
}
}
else {
if (field == "problem") { // Special binary sensor which is based on error topic
if (field == "problem") { // Special case: Binary sensor which is based on error topic
payload += "\"state_topic\": \"~/error\",";
payload += "\"value_template\": \"{{ 'OFF' if 'no error' in value else 'ON'}}\",";
}
else if (field == "flowstart") { // Special case: Button
payload += "\"cmd_t\":\"~/ctrl/flow_start\","; // Add command topic
}
else {
payload += "\"state_topic\": \"~/" + field + "\",";
}
@@ -176,6 +183,7 @@ bool MQTThomeassistantDiscovery(int 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);
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic("", "flowstart", "Manual Flow Start", "timer-play-outline", "", "", "", "", qos);
for (int i = 0; i < (*NUMBERS).size(); ++i) {
@@ -184,13 +192,25 @@ bool MQTThomeassistantDiscovery(int qos) {
group = "";
}
/* If "Allow neg. rate" is true, use "measurement" instead of "total_increasing" for the State Class, see https://github.com/jomjol/AI-on-the-edge-device/issues/3331 */
std::string value_state_class = "total_increasing";
if ((*NUMBERS)[i]->AllowNegativeRates) {
value_state_class = "measurement";
}
/* Energy meters need a different Device Class, see https://github.com/jomjol/AI-on-the-edge-device/issues/3333 */
std::string rate_device_class = "volume_flow_rate";
if (meterType == "energy") {
rate_device_class = "power";
}
// Group | Field | User Friendly Name | Icon | Unit | Device Class | State Class | Entity Category
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic(group, "value", "Value", "gauge", valueUnit, meterType, "total_increasing", "", qos);
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic(group, "raw", "Raw Value", "raw", "", "", "", "diagnostic", qos);
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic(group, "value", "Value", "gauge", valueUnit, meterType, value_state_class, "", qos); // State Class = "total_increasing" if <NUMBERS>.AllowNegativeRates = false, else use "measurement"
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic(group, "raw", "Raw Value", "raw", valueUnit, meterType, "measurement", "diagnostic", qos);
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic(group, "error", "Error", "alert-circle-outline", "", "", "", "diagnostic", qos);
/* Not announcing "rate" as it is better to use rate_per_time_unit resp. rate_per_digitization_round */
// allSendsSuccessed |= sendHomeAssistantDiscoveryTopic(group, "rate", "Rate (Unit/Minute)", "swap-vertical", "", "", "", ""); // Legacy, always Unit per Minute
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic(group, "rate_per_time_unit", "Rate (" + rateUnit + ")", "swap-vertical", rateUnit, "", "measurement", "", qos);
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic(group, "rate_per_time_unit", "Rate (" + rateUnit + ")", "swap-vertical", rateUnit, rate_device_class, "measurement", "", qos);
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic(group, "rate_per_digitization_round", "Change since last Digitization round", "arrow-expand-vertical", valueUnit, "", "measurement", "", qos); // correctly the Unit is Unit/Interval!
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic(group, "timestamp", "Timestamp", "clock-time-eight-outline", "", "timestamp", "", "diagnostic", qos);
allSendsSuccessed |= sendHomeAssistantDiscoveryTopic(group, "json", "JSON", "code-json", "", "", "", "diagnostic", qos);
@@ -355,4 +375,9 @@ std::string mqttServer_getMainTopic() {
return maintopic;
}
void mqttServer_setDmoticzInTopic( std::string _domoticzintopic) {
domoticzintopic = _domoticzintopic;
}
#endif //ENABLE_MQTT

View File

@@ -12,6 +12,9 @@ void mqttServer_setParameter(std::vector<NumberPost*>* _NUMBERS, int interval, f
void mqttServer_setMeterType(std::string meterType, std::string valueUnit, std::string timeUnit,std::string rateUnit);
void setMqtt_Server_Retain(bool SetRetainFlag);
void mqttServer_setMainTopic( std::string maintopic);
void mqttServer_setDmoticzInTopic( std::string domoticzintopic);
std::string mqttServer_getMainTopic();
void register_server_mqtt_uri(httpd_handle_t server);

View File

@@ -246,9 +246,18 @@ void CTfLiteClass::GetInputTensorSize()
long CTfLiteClass::GetFileSize(std::string filename)
{
struct stat stat_buf;
long rc = stat(filename.c_str(), &stat_buf);
return rc == 0 ? stat_buf.st_size : -1;
struct stat stat_buf;
long rc = -1;
FILE *pFile = fopen(filename.c_str(), "rb"); // previously only "rb
if (pFile != NULL)
{
rc = stat(filename.c_str(), &stat_buf);
fclose(pFile);
}
return rc == 0 ? stat_buf.st_size : -1;
}
@@ -270,25 +279,33 @@ bool CTfLiteClass::ReadFileToModel(std::string _fn)
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Loading Model " + _fn + " /size: " + std::to_string(size) + " bytes...");
#ifdef DEBUG_DETAIL_ON
LogFile.WriteHeapInfo("CTLiteClass::Alloc modelfile start");
#endif
modelfile = (unsigned char*)psram_get_shared_model_memory();
if(modelfile != NULL)
if (modelfile != NULL)
{
FILE* f = fopen(_fn.c_str(), "rb"); // previously only "r
fread(modelfile, 1, size, f);
fclose(f);
FILE *pFile = fopen(_fn.c_str(), "rb"); // previously only "rb
if (pFile != NULL)
{
fread(modelfile, 1, size, pFile);
fclose(pFile);
#ifdef DEBUG_DETAIL_ON
LogFile.WriteHeapInfo("CTLiteClass::Alloc modelfile successful");
#endif
#ifdef DEBUG_DETAIL_ON
LogFile.WriteHeapInfo("CTLiteClass::Alloc modelfile successful");
#endif
return true;
}
return true;
}
else
{
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "CTfLiteClass::ReadFileToModel: Model does not exist");
return false;
}
}
else
{
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "CTfLiteClass::ReadFileToModel: Can't allocate enough memory: " + std::to_string(size));

View File

@@ -10,7 +10,7 @@ dependencies:
component_hash: null
source:
type: idf
version: 5.3.0
version: 5.3.1
manifest_hash: 6995555b9b41e897235448c868ca92c0c3401fd2ff90df084be9bb8629958f2c
target: esp32
version: 1.0.0

View File

@@ -727,8 +727,7 @@ void migrateConfiguration(void) {
}
else 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
migrated = migrated | replaceString(configLines[i], "Autostart", ";UNUSED_PARAMETER"); // This parameter is no longer used
}
else if (section == "[Debug]") {
migrated = migrated | replaceString(configLines[i], "Logfile ", "LogLevel "); // Whitespace needed so it does not match `LogfileRetentionInDays`
@@ -741,7 +740,7 @@ void migrateConfiguration(void) {
else 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
}

View File

@@ -459,7 +459,7 @@ httpd_handle_t start_webserver(void)
config.server_port = 80;
config.ctrl_port = 32768;
config.max_open_sockets = 5; //20210921 --> previously 7
config.max_uri_handlers = 40; // Make sure this fits all URI handlers. Memory usage in bytes: 6*max_uri_handlers
config.max_uri_handlers = 41; // Make sure this fits all URI handlers. Memory usage in bytes: 6*max_uri_handlers
config.max_resp_headers = 8;
config.backlog_conn = 5;
config.lru_purge_enable = true; // this cuts old connections if new ones are needed.

View File

@@ -98,8 +98,8 @@ void wifi_init_softAP(void)
void SendHTTPResponse(httpd_req_t *req)
{
std::string message = "<h1>AI-on-the-edge - BASIC SETUP</h1><p>This is an access point with a minimal server to setup the minimum required files and information on the device and the SD-card. ";
message += "This mode is always startet if one of the following files is missing: /wlan.ini or the /config/config.ini.<p>";
message += "The setup is done in 3 steps: 1. upload full inital configuration (sd-card content), 2. store WLAN acces information, 3. reboot (and connect to WLANs)<p><p>";
message += "This mode is always started if one of the following files is missing: /wlan.ini or the /config/config.ini.<p>";
message += "The setup is done in 3 steps: 1. upload full inital configuration (sd-card content), 2. store WLAN access information, 3. reboot (and connect to WLANs)<p><p>";
message += "Please follow the below instructions.<p>";
httpd_resp_send_chunk(req, message.c_str(), strlen(message.c_str()));
@@ -109,7 +109,7 @@ void SendHTTPResponse(httpd_req_t *req)
{
message = "<h3>1. Upload initial configuration to sd-card</h3><p>";
message += "The configuration file config.ini is missing and most propably the full configuration and html folder on the sd-card. ";
message += "This is normal after the first flashing of the firmware and an empty sd-card. Please upload \"remote_setup.zip\", which contains an full inital configuration.<p>";
message += "This is normal after the first flashing of the firmware and an empty sd-card. Please upload \"remote_setup.zip\", which contains a full inital configuration.<p>";
message += "<input id=\"newfile\" type=\"file\"><br>";
message += "<button class=\"button\" style=\"width:300px\" id=\"doUpdate\" type=\"button\" onclick=\"upload()\">Upload File</button><p>";
message += "The upload might take up to 60s. After a succesfull upload the page will be updated.";
@@ -135,7 +135,7 @@ void SendHTTPResponse(httpd_req_t *req)
message += "<tr><td>WLAN-SSID</td><td><input type=\"text\" name=\"ssid\" id=\"ssid\"></td><td>SSID of the WLAN</td></tr>";
message += "<tr><td>WLAN-Password</td><td><input type=\"text\" name=\"password\" id=\"password\"></td><td>ATTENTION: the password will not be encrypted during the sending.</td><tr>";
message += "</table><p>";
message += "<h4>ATTENTION:<h4>Be sure about the WLAN settings. They cannot be reseted afterwards. If ssid or password is wrong, you need to take out the sd-card and manually change them in \"wlan.ini\"!<p>";
message += "<h4>ATTENTION:<h4>Be sure about the WLAN settings. They cannot be reset afterwards. If ssid or password is wrong, you need to take out the sd-card and manually change them in \"wlan.ini\"!<p>";
httpd_resp_send_chunk(req, message.c_str(), strlen(message.c_str()));
// message = "</tr><tr><td> Hostname</td><td><input type=\"text\" name=\"hostname\" id=\"hostname\"></td><td></td>";

View File

@@ -3,6 +3,29 @@
<link rel="icon" href="https://raw.githubusercontent.com/jomjol/AI-on-the-edge-device/master/images/icon/favicon.ico" type="image/x-icon">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.3.1/dist/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<script type="module" src="https://unpkg.com/esp-web-tools@9.0.3/dist/web/install-button.js?module"></script>
<style>
.footer {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px;
background-color: #d8d8d8;
margin-top: 20px;
}
.footer-section {
display: flex;
align-items: center;
}
.footer-section img {
width: 24px;
height: 24px;
margin-left: 10px;
}
.donation-cards img {
height: 20px;
margin-right: 5px;
}
</style>
<body style="padding: 20px; padding-left: 60px; padding-right: 60px;">
<table>
@@ -34,5 +57,27 @@
<p><esp-web-install-button manifest="manifest.json"></esp-web-install-button></p>
<hr>
<p style="font-size: small;">Installer and Console powered by <a href=https://esphome.github.io/esp-web-tools/ target=_blank>ESP Web Tools</a></p>
<div class="footer">
<div class="footer-section">
<span>Support & Contact Us</span>
<a href="https://github.com/jomjol/AI-on-the-edge-device" target="_blank" title="GitHub">
<img src="https://github.com/jomjol/AI-on-the-edge-device/images/github-logo.png" alt="GitHub">
</a>
<img src="https://github.com/jomjol/AI-on-the-edge-device/images/gmail-logo.png" alt="Email">
</a>
<a href="https://github.com/jomjol/AI-on-the-edge-device/discussions" target="_blank" title="GitHub">
<img src="https://github.com/jomjol/AI-on-the-edge-device/images/discussion-logo" alt="GitHub">
</a>
</div>
<div class="footer-section">
<span>Donations</span>
<a href="https://www.paypal.com/donate?hosted_button_id=8TRSVYNYKDSWL" target="_blank" title="Donate via PayPal">
<img src="https://github.com/jomjol/AI-on-the-edge-device/images/paypal.png" alt="PayPal" style="width: 60px; height: auto;">
</a>
</div>
</div>
</body>
</html>

BIN
images/discussion-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
images/github-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 509 B

BIN
images/gmail-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 514 B

View File

@@ -2,11 +2,4 @@
Default Value: `true`
!!! Warning
This is an **Expert Parameter**! Only change it if you understand what it does!
Automatically start the Flow (Digitization Rounds) immediately after power up.
!!! Note
Typically this is set to `true`.
The main reasons to set it to `false` is when you want to trigger it manually using the
[REST API](../REST-API) or [MQTT-API](../MQTT-API) or for debugging.
This parameter is no longer available. The flow is now always enabled. If you want it to be disabled, set an interval which is high enough (eg. 1440 = 24h).

View File

@@ -4,4 +4,8 @@ Default Value: `5`
Unit: Minutes
Interval in which the Flow (Digitization Round) is run.
It will run immediately on startup and then the next time after the given interval.
If a round takes longer than this interval, the next round gets postponed until the current round completes.
!!! Note
If you want the flow to be disabled, set an interval which is high enough (eg. 1440 = 24h).

View File

@@ -0,0 +1,6 @@
# Parameter `DomoticzTopicIn`
Default Value: `domoticz/in`
Domoticz "in" topic as configured on the "MQTT Client Gateway" setup page on the Domoticz system. Used to publish counter/s value/s.
Parameter &lt;NUMBER&gt;.DomoticzIDX is required (see below).

View File

@@ -0,0 +1,4 @@
# Parameter `<NUMBER>.DomoticzIDX`
Default Value: `0`
The Idx number for the counter device. Can be obtained from the devices setup page on the Domoticz system.

View File

@@ -38,12 +38,12 @@ Demo = false
InitialRotate = 0.0
SearchFieldX = 20
SearchFieldY = 20
AlignmentAlgo = Default
AlignmentAlgo = default
/config/ref0.jpg 103 271
/config/ref1.jpg 442 142
[Digits]
Model = /config/dig-cont_0710_s3_q.tflite
Model = /config/dig-cont_0712_s3_q.tflite
CNNGoodThreshold = 0.5
;ROIImagesLocation = /log/digit
;ROIImagesRetention = 3
@@ -87,20 +87,23 @@ HomeassistantDiscovery = false
;CACert = /config/certs/RootCA.pem
;ClientCert = /config/certs/client.pem.crt
;ClientKey = /config/certs/client.pem.key
;DomoticzTopicIn = domoticz/in
;main.DomoticzIDX = 0
;[InfluxDB]
;Uri = undefined
;Database = undefined
;Measurement = undefined
;user = undefined
;password = undefined
;main.Measurement = undefined
;main.Field = undefined
;[InfluxDBv2]
;Uri = undefined
;Bucket = undefined
;Measurement = undefined
;Org = undefined
;Token = undefined
;main.Measurement = undefined
;main.Field = undefined
;[Webhook]
@@ -115,13 +118,12 @@ HomeassistantDiscovery = false
;IO3 = input disabled 10 false false
;IO4 = built-in-led disabled 10 false false
;IO12 = input-pullup disabled 10 false false
;IO13 = input-pullup disabled 10 false false
;IO13 = input-pullup disabled 10 false false
LEDType = WS2812
LEDNumbers = 2
LEDColor = 150 150 150
[AutoTimer]
AutoStart = true
Interval = 5
[DataLogging]
@@ -138,4 +140,5 @@ TimeZone = CET-1CEST,M3.5.0,M10.5.0/3
;Hostname = undefined
RSSIThreshold = -75
CPUFrequency = 160
Tooltip = true
SetupMode = true

Binary file not shown.

BIN
sd-card/demo/530.07077.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
sd-card/demo/530.07325.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
sd-card/demo/530.12067.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
sd-card/demo/530.21419.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
sd-card/demo/530.48435.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
sd-card/demo/530.70265.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
sd-card/demo/530.95675.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
sd-card/demo/531.10877.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
sd-card/demo/531.24108.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
sd-card/demo/531.38301.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
sd-card/demo/531.63071.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
sd-card/demo/531.82235.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

136
sd-card/demo/config.ini Normal file
View File

@@ -0,0 +1,136 @@
[TakeImage]
;RawImagesLocation = /log/source
;RawImagesRetention = 15
WaitBeforeTakingPicture = 2
CamGainceiling = x8
CamQuality = 10
CamBrightness = 0
CamContrast = 0
CamSaturation = 0
CamSharpness = 0
CamAutoSharpness = false
CamSpecialEffect = no_effect
CamWbMode = auto
CamAwb = true
CamAwbGain = true
CamAec = true
CamAec2 = true
CamAeLevel = 2
CamAecValue = 600
CamAgc = true
CamAgcGain = 8
CamBpc = true
CamWpc = true
CamRawGma = true
CamLenc = true
CamHmirror = false
CamVflip = false
CamDcw = true
CamDenoise = 0
CamZoom = false
CamZoomOffsetX = 0
CamZoomOffsetY = 0
CamZoomSize = 0
LEDIntensity = 0
Demo = true
[Alignment]
InitialRotate = -34.6
SearchFieldX = 20
SearchFieldY = 20
AlignmentAlgo = default
/config/ref0.jpg 30 189
/config/ref1.jpg 536 113
[Digits]
Model = /config/dig-cont_0710_s3_q.tflite
CNNGoodThreshold = 0.5
;ROIImagesLocation = /log/digit
;ROIImagesRetention = 3
main.dig1 438 62 49 71 false
[Analog]
Model = /config/ana-cont_1300_s2.tflite
;ROIImagesLocation = /log/analog
;ROIImagesRetention = 3
main.ana1 452 199 120 120 false
[PostProcessing]
main.DecimalShift = 0
;main.AnalogToDigitTransitionStart =
main.ChangeRateThreshold = 2
PreValueUse = true
PreValueAgeStartup = 720
main.AllowNegativeRates = true
;main.MaxRateValue = 0
;main.MaxRateType = AbsoluteChange
main.ExtendedResolution = true
main.IgnoreLeadingNaN = false
ErrorMessage = true
CheckDigitIncreaseConsistency = false
;[MQTT]
;Uri = mqtt://IP-ADRESS:1883
;MainTopic = watermeter
;ClientID = watermeter
;user = USERNAME
;password = PASSWORD
RetainMessages = false
HomeassistantDiscovery = false
;MeterType = other
;CACert = /config/certs/RootCA.pem
;ClientCert = /config/certs/client.pem.crt
;ClientKey = /config/certs/client.pem.key
;[InfluxDB]
;Uri = undefined
;Database = undefined
;user = undefined
;password = undefined
;main.Measurement = undefined
;main.Field =
;[InfluxDBv2]
;Uri = undefined
;Bucket = undefined
;Org = undefined
;Token = undefined
;main.Measurement = undefined
;main.Field = undefined
;[Webhook]
;Uri = undefined
;ApiKey = undefined
;UploadImg = 0
;[GPIO]
;IO0 = input disabled 10 false false
;IO1 = input disabled 10 false false
;IO3 = input disabled 10 false false
;IO4 = built-in-led disabled 10 false false
;IO12 = input-pullup disabled 10 false false
;IO13 = input-pullup disabled 10 false false
LEDType = WS2812
LEDNumbers = 2
LEDColor = 150 150 150
[AutoTimer]
Interval = 1
[DataLogging]
DataLogActive = false
DataFilesRetention = 3
[Debug]
LogLevel = 3
LogfilesRetention = 3
[System]
Tooltip = true
TimeZone = CET-1CEST,M3.5.0,M10.5.0/3
;TimeServer = pool.ntp.org
;Hostname = undefined
RSSIThreshold = -75
CPUFrequency = 160
SetupMode = false

12
sd-card/demo/files.txt Normal file
View File

@@ -0,0 +1,12 @@
530.07077.jpg
530.07325.jpg
530.12067.jpg
530.21419.jpg
530.48435.jpg
530.70265.jpg
530.95675.jpg
531.10877.jpg
531.24108.jpg
531.38301.jpg
531.63071.jpg
531.82235.jpg

View File

View File

@@ -0,0 +1 @@
main 2024-10-06T23:02:42+0200 0.05

12
sd-card/demo/readme.txt Normal file
View File

@@ -0,0 +1,12 @@
This folder contains a prepared demo setup.
All you need to do is:
1. Copy the following files to the config folder of your SD-Card:
- config.ini
- ref0.jpg
- ref1.jpg
- reference.jpg
- prevalue.ini
1. Restart the device
More details at https://jomjol.github.io/AI-on-the-edge-device-docs/Demo-Mode

BIN
sd-card/demo/ref0.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
sd-card/demo/ref1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
sd-card/demo/reference.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

@@ -504,7 +504,7 @@
<tr class="expert" unused_id="TakeImage_CamAgcGain_ex3">
<td class="indent1">
<class id="TakeImage_CamAgcGain_text" style="color:black;">Gain Manuall Value</class>
<class id="TakeImage_CamAgcGain_text" style="color:black;">Gain Manual Value</class>
</td>
<td>
<input required type="number" id="TakeImage_CamAgcGain_value1" size="13" min="0" max="30" onchange="cameraParameterChanged()"
@@ -936,8 +936,8 @@
<label for=PostProcessing_AnalogToDigitTransitionStart_enabled><class id="PostProcessing_AnalogToDigitTransitionStart_text" style="color:black;">Analog to Digit Transition Start</class></label>
</td>
<td>
<input required type="number" id="PostProcessing_AnalogToDigitTransitionStart_value1" step="0.1" min="3.0" max="9.9" value="9.2"
oninput="(!validity.rangeUnderflow||(value=3.0)) && (!validity.rangeOverflow||(value=9.9)) &&
<input required type="number" id="PostProcessing_AnalogToDigitTransitionStart_value1" step="0.1" min="5.0" max="9.9" value="9.2"
oninput="(!validity.rangeUnderflow||(value=5.0)) && (!validity.rangeOverflow||(value=9.9)) &&
(!validity.stepMismatch||(value=parseInt(this.value)));">
</td>
<td>$TOOLTIP_PostProcessing_NUMBER.AnalogToDigitTransitionStart</td>
@@ -1178,6 +1178,36 @@
<td>$TOOLTIP_MQTT_MeterType</td>
</tr>
<tr class="MQTTItem">
<td class="indent1">
<input type="checkbox" id="MQTT_DomoticzTopicIn_enabled" value="1" onclick = 'InvertEnableItem("MQTT", "DomoticzTopicIn")' unchecked >
<label for=MQTT_DomoticzTopicIn_enabled><class id="MQTT_DomoticzTopicIn_text" style="color:black;">Domoticz "in" topic</class></label>
</td>
<td>
<input type="text" id="MQTT_DomoticzTopicIn_value1">
</td>
<td>$TOOLTIP_MQTT_DomoticzTopicIn</td>
</tr>
<tr class="MQTTItem" style="margin-top:12px">
<td class="indent1" style="padding-top:25px" colspan="3">
<b>Parameter per number sequence:</b>
<select
style="font-weight: bold; margin-left:17px" id="NumbersMQTTIdx_value1" onchange="numberMQTTIdxChanged()">
</select>
</td>
</tr>
<tr class="MQTTItem">
<td class="indent2">
<input type="checkbox" id="MQTT_DomoticzIDX_enabled" value="1" onclick = 'InvertEnableItem("MQTT", "DomoticzIDX")' unchecked >
<label for=MQTT_DomoticzIDX_enabled><class id="MQTT_DomoticzIDX_text" style="color:black;">Domoticz Counter Idx:</class></label>
</td>
<td>
<input type="text" id="MQTT_DomoticzIDX_value1">
</td>
<td>$TOOLTIP_MQTT_NUMBER.DomoticzIDX</td>
</tr>
<!------------- INFLUXDB v1 ------------------>
<tr style="border-bottom: 2px solid lightgray;">
@@ -1890,6 +1920,7 @@
<!------------- Autotimer ------------------>
<!--
<tr style="border-bottom: 2px solid lightgray;">
<td colspan="3" style="padding-left: 0px; padding-bottom: 3px;"><h4>Auto Timer</h4></td>
</tr>
@@ -1906,6 +1937,7 @@
</td>
<td>$TOOLTIP_AutoTimer_AutoStart</td>
</tr>
-->
<tr>
<td class="indent1">
@@ -2120,6 +2152,7 @@ function LoadConfigNeu() {
UpdateInput();
var sel = document.getElementById("Numbers_value1");
UpdateInputIndividual(sel);
UpdateExpertModus();
UpdateTooltipModus();
document.getElementById("divall").style.display = '';
@@ -2162,6 +2195,11 @@ function InitIndivParameter() {
_indexInfluxv1.remove(0);
}
var _indexMQTTIdx = document.getElementById("NumbersMQTTIdx_value1");
while (_indexMQTTIdx.length) {
_indexMQTTIdx.remove(0);
}
for (var i = 0; i < NUMBERS.length; ++i) {
var option = document.createElement("option");
option.text = NUMBERS[i]["name"];
@@ -2177,11 +2215,17 @@ function InitIndivParameter() {
optionInfluxv1.text = NUMBERS[i]["name"];
optionInfluxv1.value = i;
_indexInfluxv1.add(optionInfluxv1);
var optionMQTTIdx = document.createElement("option");
optionMQTTIdx.text = NUMBERS[i]["name"];
optionMQTTIdx.value = i;
_indexMQTTIdx.add(optionMQTTIdx);
}
_index.selectedIndex = 0;
_indexInflux.selectedIndex = 0;
_indexInfluxv1.selectedIndex = 0;
_indexMQTTIdx.selectedIndex = 0;
}
function UpdateInputIndividual(sel) {
@@ -2200,6 +2244,7 @@ function UpdateInputIndividual(sel) {
ReadParameter(param, "InfluxDBv2", "Field", true, NUNBERSAkt);
ReadParameter(param, "InfluxDB", "Measurement", true, NUNBERSAkt);
ReadParameter(param, "InfluxDBv2", "Measurement", true, NUNBERSAkt);
ReadParameter(param, "MQTT", "DomoticzIDX", true, NUNBERSAkt);
}
// var sel = document.getElementById("Numbers_value1");
@@ -2218,6 +2263,8 @@ function UpdateInputIndividual(sel) {
WriteParameter(param, category, "InfluxDBv2", "Field", true, NUNBERSAkt);
WriteParameter(param, category, "InfluxDB", "Measurement", true, NUNBERSAkt);
WriteParameter(param, category, "InfluxDBv2", "Measurement", true, NUNBERSAkt);
WriteParameter(param, category, "MQTT", "DomoticzIDX", true, NUNBERSAkt);
}
function UpdateInput() {
@@ -2306,6 +2353,7 @@ function UpdateInput() {
WriteParameter(param, category, "MQTT", "CACert", true);
WriteParameter(param, category, "MQTT", "ClientCert", true);
WriteParameter(param, category, "MQTT", "ClientKey", true);
WriteParameter(param, category, "MQTT", "DomoticzTopicIn", true);
WriteParameter(param, category, "InfluxDB", "Uri", true);
WriteParameter(param, category, "InfluxDB", "Database", true);
@@ -2335,7 +2383,7 @@ function UpdateInput() {
WriteParameter(param, category, "GPIO", "LEDNumbers", false);
WriteParameter(param, category, "GPIO", "LEDColor", false);
WriteParameter(param, category, "AutoTimer", "AutoStart", false);
//WriteParameter(param, category, "AutoTimer", "AutoStart", false);
WriteParameter(param, category, "AutoTimer", "Interval", false);
WriteParameter(param, category, "DataLogging", "DataLogActive", false);
@@ -2473,6 +2521,7 @@ function ReadParameterAll() {
ReadParameter(param, "MQTT", "CACert", true);
ReadParameter(param, "MQTT", "ClientCert", true);
ReadParameter(param, "MQTT", "ClientKey", true);
ReadParameter(param, "MQTT", "DomoticzTopicIn", true);
ReadParameter(param, "InfluxDB", "Uri", true);
ReadParameter(param, "InfluxDB", "Database", true);
@@ -2509,7 +2558,7 @@ function ReadParameterAll() {
param["GPIO"]["LEDNumbers"]["found"] = true;
param["GPIO"]["LEDColor"]["found"] = true;
ReadParameter(param, "AutoTimer", "AutoStart", false);
//ReadParameter(param, "AutoTimer", "AutoStart", false);
ReadParameter(param, "AutoTimer", "Interval", false);
ReadParameter(param, "DataLogging", "DataLogActive", false);
@@ -2527,7 +2576,7 @@ function ReadParameterAll() {
var sel = document.getElementById("Numbers_value1");
UpdateInputIndividual(sel);
// FormatDecimalValue(param, "PostProcessing", "MaxRateValue");
}
@@ -2555,7 +2604,7 @@ function UpdateExpertModus() {
_style_pur = '';
_hidden = false;
document.getElementById("Button_Edit_Config_Raw").style.display = "";
firework.launch("Expert view activated. Please use carefully", 'warning', 5000);
firework.launch("Expert view activated. Please use it carefully", 'warning', 5000);
}
else {
document.getElementById("Button_Edit_Config_Raw").style.display = "none";
@@ -2821,7 +2870,7 @@ function camSettingsSet(){
}
catch (error){}
document.getElementById("overlaytext").innerHTML = "Device is busy, plase waiting...<br><br>Current step: " + _xhttp.responseText;
document.getElementById("overlaytext").innerHTML = "Device is busy, please wait.<br><br>Current step: " + _xhttp.responseText;
console.log("Device is busy, waiting 2s then checking again...");
await sleep(2000);
}
@@ -2871,6 +2920,38 @@ function numberChanged() {
if (_selInflux.selectedIndex != _neu) {
_selInflux.selectedIndex = _neu
}
var _sel3 = document.getElementById("NumbersInfluxDB_value1");
if (_sel3.selectedIndex != _neu) {
_sel3.selectedIndex = _neu
}
var _sel4 = document.getElementById("NumbersMQTTIdx_value1");
if (_sel4.selectedIndex != _neu) {
_sel4.selectedIndex = _neu
}
}
function numberMQTTIdxChanged() {
var sel = document.getElementById("NumbersMQTTIdx_value1");
_neu = sel.selectedIndex;
UpdateInputIndividual(sel);
var _sel2 = document.getElementById("Numbers_value1");
if (_sel2.selectedIndex != _neu) {
_sel2.selectedIndex = _neu
}
var _sel3 = document.getElementById("NumbersInfluxDB_value1");
if (_sel3.selectedIndex != _neu) {
_sel3.selectedIndex = _neu
}
var _sel4 = document.getElementById("NumbersInfluxDBv2_value1");
if (_sel4.selectedIndex != _neu) {
_sel4.selectedIndex = _neu
}
}
function numberInfluxDBv2Changed() {
@@ -2887,6 +2968,11 @@ function numberInfluxDBv2Changed() {
if (_sel3.selectedIndex != _neu) {
_sel3.selectedIndex = _neu
}
var _sel4 = document.getElementById("NumbersMQTTIdx_value1");
if (_sel4.selectedIndex != _neu) {
_sel4.selectedIndex = _neu
}
}
function numberInfluxDBChanged() {
@@ -2903,6 +2989,11 @@ function numberInfluxDBChanged() {
if (_sel3.selectedIndex != _neu) {
_sel3.selectedIndex = _neu
}
var _sel4 = document.getElementById("NumbersMQTTIdx_value1");
if (_sel4.selectedIndex != _neu) {
_sel4.selectedIndex = _neu
}
}
function getParameterByName(name, url = window.location.href) {

View File

@@ -617,7 +617,7 @@
}
catch (error){}
document.getElementById("overlaytext").innerHTML = "Device is busy, plase waiting...<br><br>Current step: " + _xhttp.responseText;
document.getElementById("overlaytext").innerHTML = "Device is busy, please wait.<br><br>Current step: " + _xhttp.responseText;
console.log("Device is busy, waiting 5s then checking again...");
await sleep(2000);
}
@@ -1053,7 +1053,7 @@
if (document.getElementById("ExpertModus_enabled").checked) {
_style_pur = '';
_hidden = false;
firework.launch("Expert parameter view activated. Please use carefully", 'warning', 5000);
firework.launch("Expert parameter view activated. Please use it carefully", 'warning', 5000);
}
const expert = document.querySelectorAll(".expert");

View File

@@ -69,6 +69,30 @@
return "";
}
</script>
<style>
/* Add these styles to your existing CSS file or in a <style> tag in the head */
.footer {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px;
background-color: #d8d8d8;
margin-top: 20px;
}
.footer-section {
display: flex;
align-items: center;
}
.footer-section img {
width: 24px;
height: 24px;
margin-left: 10px;
}
.donation-cards img {
height: 20px;
margin-right: 5px;
}
</style>
</head>
<body>
@@ -134,9 +158,9 @@
</ul>
</li>
<li id="ManualControl" style="display:none;"><a>Manual Control <i class="arrow down"></i></a> <!-- Workaround: Hide menu if no entry is available -->
<ul class="submenu">
<!--<li><a href="#" onclick="flow_start()">Start Round</a></li>--> <!-- Needs to be adapted on code side first to ensure proper user feedback -->
<li id="HASendDiscovery" style="display:none;"><a href="#" onclick="HA_send_discovery()">Resend HA Discovery</a></li>
<ul class="submenu" style="width: 300px">
<li><a href="#" onclick="flow_start()">Start Round</a></li>
<li id="HASendDiscovery" style="width: 300px" style="display:none;"><a href="#" onclick="HA_send_discovery()">Resend Homeassistant Discovery</a></li>
</ul>
</li>
</ul>
@@ -146,6 +170,28 @@
<span id="Version" style="font-size: 10px; margin-top: -5px;padding-left: 10px;">Loading version...</span>
<!-- # Disabled footer, since it wastes a lot of space and the images are broken
<div class="footer">
<div class="footer-section">
<span>Support & Contact Us</span>
<a href="https://github.com/jomjol/AI-on-the-edge-device" target="_blank" title="GitHub">
<img src="https://github.com/jomjol/AI-on-the-edge-device/images/github-logo.png" alt="GitHub">
</a>
<img src="https://github.com/jomjol/AI-on-the-edge-device/images/gmail-logo.png" alt="Email">
</a>
<a href="https://github.com/jomjol/AI-on-the-edge-device/discussions" target="_blank" title="GitHub">
<img src="https://github.com/jomjol/AI-on-the-edge-device/images/discussion-logo" alt="GitHub">
</a>
</div>
<div class="footer-section">
<span>Donations</span>
<a href="https://www.paypal.com/donate?hosted_button_id=8TRSVYNYKDSWL" target="_blank" title="Donate via PayPal">
<img src="https://github.com/jomjol/AI-on-the-edge-device/images/paypal.png" alt="PayPal" style="width: 60px; height: auto;">
</a>
</div>
</div> -->
<script type="text/javascript">
LoadHostname();
LoadFwVersion();
@@ -158,13 +204,14 @@
console.log("Loading page: " + getCookie("page"));
document.getElementById('maincontent').src = getCookie("page");
/*
function flow_start() {
var url = getDomainname() + '/flow_start';
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
if (xhttp.responseText.substring(0,3) == "001") {
firework.launch(xhttp.responseText, 'success', 5000);
/*if (xhttp.responseText.substring(0,3) == "001") {
firework.launch('Flow start triggered', 'success', 5000);
window.location.reload();
}
@@ -173,13 +220,13 @@
}
else if (xhttp.responseText.substring(0,3) == "099") {
firework.launch('Flow start triggered, but start not possible (no flow task available)', 'danger', 5000);
}
}*/
}
}
xhttp.open("GET", url, true);
xhttp.send();
}
*/
function HA_send_discovery_visibility() {
loadConfig(domainname);
@@ -187,19 +234,19 @@
category = getConfigCategory();
param = getConfigParameters();
if (category["MQTT"]["enabled"] && param["MQTT"]["HomeassistantDiscovery"].value == "true") {
if (category["MQTT"]["enabled"] && param["MQTT"]["HomeassistantDiscovery"].value1 == "true") {
document.getElementById("ManualControl").style.display="";
document.getElementById("HASendDiscovery").style.display="";
}
}
function HA_send_discovery() {
console.log("HA Discovery scheduled");
console.log("Homeassistant Discovery topic sending scheduled");
var url = getDomainname() + '/mqtt_publish_discovery';
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
firework.launch('Sending HA discovery topics scheduled. The sending will be processed in state "Publish to MQTT"', 'success', 5000);
firework.launch('Sending Homeassistant discovery topics scheduled. It will get sent in the step "Publish to MQTT" of the next digitization round', 'success', 5000);
}
}
xhttp.open("GET", url, true);

View File

@@ -118,6 +118,7 @@
</tr>
<tr>
<td class="tg-4">
<div id="sntp_date" ></div>
<div id="timestamp" ></div>
<div id="cputemp" ></div>
<div id="rssi" ></div>
@@ -153,6 +154,7 @@
loadValue("prevalue", "prevalue", "border-collapse: collapse; width: 100%");
loadValue("error", "error", "border-collapse: collapse; width: 100%");
loadStatus();
loadSntpDate();
loadCPUTemp();
loadRSSI();
loadUptime();
@@ -234,6 +236,20 @@
xhttp.send();
}
function loadSntpDate() {
url = domainname + '/date';
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function () {
if (this.readyState == 4 && this.status == 200) {
var _rsp = xhttp.responseText;
$('#sntp_date').html("Date/Time on device: " + _rsp);
}
}
xhttp.open("GET", url, true);
xhttp.send();
}
function loadUptime() {
url = domainname + '/uptime';
var xhttp = new XMLHttpRequest();

View File

@@ -198,6 +198,8 @@ function ParseConfig() {
ParamAddValue(param, catname, "user");
ParamAddValue(param, catname, "password");
ParamAddValue(param, catname, "RetainMessages");
ParamAddValue(param, catname, "DomoticzTopicIn");
ParamAddValue(param, catname, "DomoticzIDX", 1, true);
ParamAddValue(param, catname, "HomeassistantDiscovery");
ParamAddValue(param, catname, "MeterType");
ParamAddValue(param, catname, "CACert");
@@ -265,7 +267,7 @@ function ParseConfig() {
category[catname]["enabled"] = false;
category[catname]["found"] = false;
param[catname] = new Object();
ParamAddValue(param, catname, "AutoStart");
//ParamAddValue(param, catname, "AutoStart");
ParamAddValue(param, catname, "Interval");
var catname = "DataLogging";