Compare commits

...

45 Commits

Author SHA1 Message Date
jomjol
74d4f20858 v15.4.0 2023-12-22 23:38:31 +01:00
jomjol
e790a14caa prepare v15.4.0 versuch 2 2023-12-22 23:31:13 +01:00
jomjol
f023a6b739 Revert "v15.4.0"
This reverts commit 374462a7d8.
2023-12-22 23:22:57 +01:00
jomjol
1e463188ea Revert "Update Changelog.md"
This reverts commit 9991196961.
2023-12-22 23:22:08 +01:00
jomjol
9991196961 Update Changelog.md 2023-12-22 23:04:52 +01:00
jomjol
ecece0f7fc Update dependencies.lock 2023-12-22 22:55:54 +01:00
jomjol
1b3b7595c1 Merge branch 'rolling' 2023-12-22 22:46:36 +01:00
jomjol
6d10f712d1 Update Changelog.md 2023-12-22 22:46:16 +01:00
jomjol
b84a2db050 Update config.ini 2023-12-22 22:45:32 +01:00
jomjol
374462a7d8 v15.4.0 2023-12-22 22:43:08 +01:00
Frank Haverland
4facd7be05 new images on analog and digits (#2756)
* new images on dig-class100, older ana-class100 removed

* new images on analog and digits
2023-12-22 22:42:33 +01:00
jomjol
3baf5865ad Prepare 15.4.0 2023-12-21 19:21:22 +01:00
CaCO3
02138c44ac rename InfluxDBv2 parameter Database to Bucket (#2704)
* rename InfluxDBv2 parameter Database to Basket

* only enable the field if it is a boolean

* corrected "Basket" to "Bucket"

---------

Co-authored-by: CaCO3 <caco@ruinelli.ch>
2023-11-22 23:43:23 +01:00
Slider0007
1094c8a0a8 fix(influxdb): Consider DST setting for UTC time conversion (#2679)
* fix(influxdb): Consider DST setting for UTC time conversion

* Update
2023-11-12 12:04:18 +01:00
LordGuilly
75b15b8e9d added config entries for MQTT TLS (#2651)
3 new entries in the config section, for setting file paths for
        - Root CA
        - Client Certificate
        - Client Private Key
 (all set as expert parameters)

- logging cert filenames

added config entries for MQTT TLS

 3 new entries in the config section, for setting file paths for
        - Root CA
        - Client Certificate
        - Client Private Key

- logging cert filenames

MQTT-TLS: Updates for the PR comments

- config.ini now has default values closer to "real" life filenames
- MQTT cert entries are hidden as Expert parameters
- Fixed debug logging at MQTT interface for unhandled messages
2023-11-07 22:58:16 +01:00
CaCO3
36c12b400b rework test-domain check 2023-11-07 22:42:15 +01:00
CaCO3
35d90cd0ee update readme 2023-10-21 22:39:21 +02:00
CaCO3
999a8d9374 updated readme 2023-10-21 22:35:13 +02:00
CaCO3
8a4269c6a0 Add files via upload 2023-10-21 22:32:25 +02:00
CaCO3
8f5579cca5 updated readme 2023-10-21 22:30:29 +02:00
CaCO3
e58b3a2cf8 update webinstaller 2023-10-21 22:23:08 +02:00
CaCO3
222ee0921c udpate webinstaller 2023-10-21 22:19:07 +02:00
CaCO3
e2bfcd26c9 update web installer 2023-10-21 22:15:47 +02:00
CaCO3
18917b2d82 Delete docs/manifest_template.json 2023-10-21 01:06:52 +02:00
CaCO3
f0dea3abcb Delete docs/binary/firmware.bin 2023-10-21 01:06:42 +02:00
CaCO3
bd2d8b4a15 Update manual-update-webinstaller.yml 2023-10-21 01:06:15 +02:00
CaCO3
80e3f50a5b Update manifest.json 2023-10-21 01:05:19 +02:00
CaCO3
f6ca32d69f Update manual-update-webinstaller.yml 2023-10-21 01:03:00 +02:00
CaCO3
49e919c481 Update manual-update-webinstaller.yml 2023-10-21 01:00:21 +02:00
CaCO3
de768c4f44 Delete releases/download directory 2023-10-21 00:49:24 +02:00
CaCO3
002fc033aa Delete firmware directory 2023-10-21 00:47:05 +02:00
CaCO3
f4af8de699 fix paypal button (#2666)
* Add files via upload

* Update README.md

* Update README.md

* Update README.md

* Update README.md
2023-10-21 00:43:36 +02:00
CaCO3
e4d6707a0b Update README.md 2023-10-21 00:27:52 +02:00
CaCO3
9f03e68690 Update README.md 2023-10-21 00:27:32 +02:00
CaCO3
b1df7df580 added label action 2023-10-20 23:35:07 +02:00
CaCO3
ebc9be4a28 added label action 2023-10-20 23:27:10 +02:00
jomjol
5d0fc73c13 Updated feature request 2023-09-03 15:48:00 +02:00
jomjol
40a1aa0430 Update submodules, include only needed layers of tflite (#2586)
* Initial version

* Working Version

* Update

* Update main.cpp

* Updated Docu
2023-08-20 21:49:28 +02:00
TheDubliner
d7a733512f 📚 Fix minor typos, grammar and formatting issues. (#2575) 2023-08-15 21:37:22 +02:00
Frank Haverland
dc9f1aad27 new images on dig-class100, older ana-class100 removed (#2545) 2023-08-02 21:46:00 +02:00
Frank Haverland
cd3e641bcc #2540 fix trunc of first with imprecise precision (#2543)
* added more debug for #2447

* new model on new images dig-class100-0165_s2

* #2465 fix first digit with extended_Resolution=false

* fix #2491

* #2540 fix trunc of first with imprecise precision
2023-07-30 18:25:56 +02:00
Slider0007
ad72ffa37c mqtt_handler_set_prevalue: fix memory leak (#2544) 2023-07-30 18:25:35 +02:00
Giel van Schijndel
2d45a0ed26 fix: provide proper error messages for invalid /info?type= query parameter (#2533) 2023-07-24 21:44:06 +02:00
Giel van Schijndel
e8065ef414 fix(fileserver): avoid sending *two* "last-chunk" sequences (#2532)
Because a zero-sized chunk indicates the end of a HTTP response sending
another such zero-sized chunk is not interpretable by the HTTP client.

See [RFC2616 3.6.1] "Chunked Transfer Encoding" for details.

[RFC2616 3.6.1]: https://datatracker.ietf.org/doc/html/rfc2616#section-3.6.1
2023-07-24 21:43:56 +02:00
pfeifferch
33893eb566 Shortcut Icon hinzugefügt (#2530)
* Add files via upload

Shortcut icon

* Update index.html

Shortcut icon
2023-07-23 20:33:04 +02:00
55 changed files with 1170 additions and 186 deletions

View File

@@ -3,7 +3,32 @@
# Make sure to also add the response to .github/workflows/reply-bot.yml!
# Due to the way it works, you have to add each response twice, once for the issue, once for the discussions!
labels:
labels:
#######################################################################
# Bot Response: Documentation
#######################################################################
- name: bot-reply Documentation
labeled:
issue:
body: |
Please have a look on our documentation: https://jomjol.github.io/AI-on-the-edge-device-docs
discussion:
body: |
Please have a look on our documentation: https://jomjol.github.io/AI-on-the-edge-device-docs
#######################################################################
# Bot Response: ROI setup
#######################################################################
- name: bot-reply ROI Setup
labeled:
issue:
body: |
Make sure to setup your ROIs properly. Have a look on our documentation: https://jomjol.github.io/AI-on-the-edge-device-docs/ROI-Configuration/#how-to-setup-the-digit-rois-perfectly
discussion:
body: |
Make sure to setup your ROIs properly. Have a look on our documentation: https://jomjol.github.io/AI-on-the-edge-device-docs/ROI-Configuration/#how-to-setup-the-digit-rois-perfectly
#######################################################################
# Bot Response: Logfile
#######################################################################

View File

@@ -380,7 +380,7 @@ jobs:
#########################################################################################
## Update the Web Installer on a release
#########################################################################################
# This is the same as in the update-webinstaller.yml
# Make sure to also update update-webinstaller.yml!
update-web-installer:
needs: [release]
environment:
@@ -408,12 +408,14 @@ jobs:
- name: Add binary to Web Installer and update manifest
run: |
echo "Updating Web installer to use firmware from ${{ steps.last_release.outputs.tag_name }}..."
rm -f docs/binary/firmware.bin
wget https://github.com/jomjol/AI-on-the-edge-device/releases/download/${{ steps.last_release.outputs.tag_name }}/AI-on-the-edge-device__update__${{ steps.last_release.outputs.tag_name }}.zip
unzip AI-on-the-edge-device__update__${{ steps.last_release.outputs.tag_name }}.zip
cp -f firmware.bin docs/binary/firmware.bin
cp -f docs/manifest_template.json docs/manifest.json
sed -i 's/VERSION/${{ steps.last_release.outputs.tag_name }}/g' docs/manifest.json
echo "Updating index and manifest file..."
sed -i 's/$VERSION/${{ steps.last_release.outputs.tag_name }}/g' docs/index.html
sed -i 's/$VERSION/${{ steps.last_release.outputs.tag_name }}/g' docs/manifest.json
- name: Setup Pages
uses: actions/configure-pages@v2

View File

@@ -1,7 +1,7 @@
# This updates the Web Installer with the files from the docs folder and the binary of the latest release
# it only gets run on:
# - Changes to the docs folder in the `rolling` branch
# - On a release
# - Manually triggered
# Make sure to also update the lower part of build.yml!
name: Manual Web Installer Update
@@ -40,12 +40,14 @@ jobs:
- name: Add binary to Web Installer and update manifest
run: |
echo "Updating Web installer to use firmware from ${{ steps.last_release.outputs.tag_name }}..."
rm -f docs/binary/firmware.bin
wget https://github.com/jomjol/AI-on-the-edge-device/releases/download/${{ steps.last_release.outputs.tag_name }}/AI-on-the-edge-device__update__${{ steps.last_release.outputs.tag_name }}.zip
unzip AI-on-the-edge-device__update__${{ steps.last_release.outputs.tag_name }}.zip
cp -f firmware.bin docs/binary/firmware.bin
cp -f docs/manifest_template.json docs/manifest.json
sed -i 's/VERSION/${{ steps.last_release.outputs.tag_name }}/g' docs/manifest.json
echo "Updating index and manifest file..."
sed -i 's/$VERSION/${{ steps.last_release.outputs.tag_name }}/g' docs/index.html
sed -i 's/$VERSION/${{ steps.last_release.outputs.tag_name }}/g' docs/manifest.json
- name: Setup Pages
uses: actions/configure-pages@v2

View File

@@ -1,8 +1,40 @@
## [15.4.0] - 2023-12-22
### Changes
For a full list of changes see [Full list of changes](https://github.com/jomjol/AI-on-the-edge-device/compare/15.4.0...v15.3.0)
#### Changed
- Updates submodules (esp-nn, tflite-micro-example, esp-camera)
- Explicitly included needed tflite network layers (instead of all) , resulting in much smaller firmware size
- Added shortcut icon
- Rename in InfluxDB 'Database' to 'Bucket'
- Updated analog tflite files
- dig-class100-0167_s2_q.tflite
- dig-class11_1700_s2.tflite
- ana-cont_1208_s2_q.tflite
#### Fixed
* InfluxDB: consider DST setting for UTC time conversion
* Minor html response bugfix
- Memory leakage (MQTT)
## [15.3.0] - 2023-07-22
### Changes
For a full list of changes see [Full list of changes](https://github.com/jomjol/AI-on-the-edge-device/compare/v15.2.1...v15.2.4)
For a full list of changes see [Full list of changes](https://github.com/jomjol/AI-on-the-edge-device/compare/v15.3.0...v15.2.4)
#### Changed

View File

@@ -2,15 +2,41 @@
**There are a lot of ideas for further improvements, but only limited capacity on side of the developer.** Therefore I have created this page as a collection of ideas.
1. Who ever has a new idea can put it here, so it that it is not forgotten.
1. Whoever has a new idea can put it here, so that it is not forgotten.
2. Who ever has time, capacity and passion to support, can take any of the ideas and implement them.
I will support and help where ever I can!
2. Whoever has the time, capacity and passion to support the project can take any of the ideas and implement them. I will provide support and help wherever I can!
____
#### #40 Trigger with cron like exact time slot
* https://github.com/jomjol/AI-on-the-edge-device/issues/2470
#### #39 upnp implementation to auto detect the device
* https://github.com/jomjol/AI-on-the-edge-device/issues/2481
#### #38 Energy Saving
* Deep sleep between recognition
* https://github.com/jomjol/AI-on-the-edge-device/issues/2486
#### #37 Auto init SD card
* Fully implement the SD card handling (including formatting) into the firmware
* https://github.com/jomjol/AI-on-the-edge-device/issues/2488Demo
#### #36 Run demo without camera
Demo mode requires a working camera (if not, one receives a 'Cam bad' error). Would be nice to demo or play around on other ESP32 boards (or on ESP32-CAM boards when you broke the camera cable...).
#### #35 Use the same model, but provide the image from a Smartphone Camera
@@ -51,7 +77,7 @@ haveing this state in the mqtt broker can trigger functions like closing the ate
#### ~~#29 Add favicon and use the hostname for the website~~- implemented v11.3.1
~~* https://github.com/jomjol/AI-on-the-edge-device/issues/927~~
* ~~https://github.com/jomjol/AI-on-the-edge-device/issues/927~~
#### #28 Improved error handling for ROIs
@@ -89,7 +115,7 @@ haveing this state in the mqtt broker can trigger functions like closing the ate
#### ~~#22 Direct hint to the different neural network files in the other repositories~~- implemented >v11.3.1
~~* https://github.com/jomjol/AI-on-the-edge-device/issues/644~~
* ~~https://github.com/jomjol/AI-on-the-edge-device/issues/644~~

View File

@@ -1,31 +1,31 @@
# Welcome to the AI-on-the-edge-device
<img src="images/icon/watermeter.svg" width="100px">
Artificial intelligence based systems have been established in our every days live. Just think of speech or image recognition. Most of the systems relay on either powerful processors or a direct connection to the cloud for doing the calculations up 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 brought into a practical oriented example, where a AI network is implemented on a ESP32 device so: **AI on the edge**.
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**.
This projects allows you to digitalize your **analoge** water, gas, power and other meters using cheap and easily available hardware.
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 a bit of a practical hand.
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 (3x4.5x2 cm³, < 10 EUR)
- camera and illumination integrated
- Web surface to administrate and control
- OTA-Interface to update directly through the web interface
- 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
- 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 (ROI's) out of it and runs them through an artificial inteligence. As a result, you get the digitalized value of your meter.
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 what to do with that value. Either send it to a MQTT broker, write it to an InfluxDb or simply provide it throug a REST API.
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">
@@ -41,62 +41,68 @@ There are several options what to do with that value. Either send it to a MQTT b
## Setup
There is a 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 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 a articles in the German Heise magazine "make:" about the setup and the technical background (behind a paywall) : [DIY - Setup](https://www.heise.de/select/make/2021/2/2103513300897420296)
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)
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)
A lot of people created useful Youtube videos which might help you getting started.
Here a small selection:
- [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) and [Programming on the ESP32](https://www.heise.de/select/make/2022/2/2204010051597422030).
### Download
The latest available version is available on the [Releases page](https://github.com/jomjol/AI-on-the-edge-device/releases).
The latest available version can be found on the [Releases page](https://github.com/jomjol/AI-on-the-edge-device/releases).
### Flashing of the ESP32
Initially you will have to flash the ESP32 through an USB connection. Later an update is possible directly over the Air (OTA).
### 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).
There are different ways to flash your ESP32:
- [Web Installer and Console](https://jomjol.github.io/AI-on-the-edge-device/index.html) (Webbrowser based tool to flash the ESP32 and extract the Log over USB)
- The prefered 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)
- Flash Tool from Espressif
- ESPtool (Command Line Tool)
- ESPtool (command-line tool)
See the [Docu](https://jomjol.github.io/AI-on-the-edge-device-docs/Installation/) for more information.
See the [documentation](https://jomjol.github.io/AI-on-the-edge-device-docs/Installation/) for more information.
### Flashing the SD-Card
The SD-Card must be flashed separately, see the [Docu](https://jomjol.github.io/AI-on-the-edge-device-docs/Installation/) for details.
### 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
A 3d-printable housing can be found here:
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)
## Build it yourself
See [Build Instructions](code/README.md).
- https://www.thingiverse.com/thing:4571627 (ESP32-cam housing only)
## 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).
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).
<form action="https://www.paypal.com/donate" method="post" target="_top">
<input type="hidden" name="hosted_button_id" value="8TRSVYNYKDSWL" />
<input type="image" src="https://www.paypalobjects.com/en_US/DK/i/btn/btn_donateCC_LG.gif" border="0" name="submit" title="PayPal - The safer, easier way to pay online!" alt="Donate with PayPal button" />
<img alt="" border="0" src="https://www.paypal.com/en_DE/i/scr/pixel.gif" width="1" height="1" />
</form>
If you have any technical topics, you can create an [Issue](https://github.com/jomjol/AI-on-the-edge-device/issues).
<a href="https://www.paypal.com/donate?hosted_button_id=8TRSVYNYKDSWL"><img border="0" src="images/paypal.png" width="200px" target="_blank"></a>
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">
## 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">
## Changes and History
See [Changelog](Changelog.md)
See [Changelog](Changelog.md).
## 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 [Docu](https://jomjol.github.io/AI-on-the-edge-device-docs/outdated--Gasmeter-Log-Downloader/)
* Files see ['/tools/logfile-tool'](tbd), how-to see [documentation](https://jomjol.github.io/AI-on-the-edge-device-docs/outdated--Gasmeter-Log-Downloader/)
## Additional Ideas
There are some ideas and feature requests which are not followed currently - mainly due to capacity reasons on side of the developer. They are collected here: [FeatureRequest.md](FeatureRequest.md)
------
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).

1
code/.gitignore vendored
View File

@@ -1,4 +1,5 @@
.pio
.idea
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json

View File

@@ -8,6 +8,15 @@ git checkout rolling
git submodule update --init
```
## Update Submodules
```
cd /components/submodule-name (e.g. tflite-micro-example)
git checkout VERSION (e.g. HASH of latest tflite-micro-example build)
cd ../../ (auf Ebene von code)
git submodule update --init
```
Evt. muss man vorher noch einige Verzeichnisse in compenents von Hand löschen, da sie beim checkout nicht gelöscht wurden (vor update -- init)
## Build and Flash within terminal
See further down to build it within an IDE.
### Compile

View File

@@ -2,6 +2,6 @@ FILE(GLOB_RECURSE app_sources ${CMAKE_CURRENT_SOURCE_DIR}/*.*)
idf_component_register(SRCS ${app_sources}
INCLUDE_DIRS "." "../../include" "miniz"
REQUIRES vfs tflite-lib esp_http_server app_update esp_http_client nvs_flash jomjol_tfliteclass jomjol_flowcontroll spiffs jomjol_helper jomjol_controlGPIO)
REQUIRES vfs esp_http_server app_update esp_http_client nvs_flash jomjol_tfliteclass jomjol_flowcontroll spiffs jomjol_helper jomjol_controlGPIO)

View File

@@ -588,7 +588,9 @@ static esp_err_t download_get_handler(httpd_req_t *req)
/* Read file in chunks into the scratch buffer */
chunksize = fread(chunk, 1, SERVER_FILER_SCRATCH_BUFSIZE, fd);
/* Send the buffer contents as HTTP response chunk */
/* Send buffer contents as HTTP chunk. If empty this functions as a
* last-chunk message, signaling end-of-response, to the HTTP client.
* See RFC 2616, section 3.6.1 for details on Chunked Transfer Encoding. */
if (httpd_resp_send_chunk(req, chunk, chunksize) != ESP_OK) {
fclose(fd);
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "File sending failed!");
@@ -606,8 +608,6 @@ static esp_err_t download_get_handler(httpd_req_t *req)
fclose(fd);
ESP_LOGD(TAG, "File successfully sent");
/* Respond with an empty chunk to signal HTTP response completion */
httpd_resp_send_chunk(req, NULL, 0);
return ESP_OK;
}

View File

@@ -145,7 +145,8 @@ int ClassFlowCNNGeneral::PointerEvalHybridNew(float number, float number_of_pred
{
// on first digit is no spezial logic for transition needed
// we use the recognition as given. The result is the int value of the recognition
result = (int) ((int) trunc(number) + 10) % 10;
// add precisition of 2 digits and round before trunc
result = (int) ((int) trunc(round((number+10 % 10)*100)) ) / 100;
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "PointerEvalHybridNew - No predecessor - Result = " + std::to_string(result) +
" number: " + std::to_string(number) + " number_of_predecessors = " + std::to_string(number_of_predecessors)+ " eval_predecessors = " + std::to_string(eval_predecessors) + " Digital_Uncertainty = " + std::to_string(Digital_Uncertainty));
@@ -484,7 +485,7 @@ bool ClassFlowCNNGeneral::doFlow(string time)
if (!doAlignAndCut(time)){
return false;
};
}
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "doFlow after alignment");
@@ -852,10 +853,9 @@ bool ClassFlowCNNGeneral::doNeuralNetwork(string time)
bool ClassFlowCNNGeneral::isExtendedResolution(int _number)
{
if (!(CNNType == Digital))
return true;
return false;
if (CNNType == Digital)
return false;
return true;
}

View File

@@ -20,7 +20,7 @@ static const char* TAG = "INFLUXDBV2";
void ClassFlowInfluxDBv2::SetInitialParameter(void)
{
uri = "";
database = "";
bucket = "";
dborg = "";
dbtoken = "";
// dbfield = "";
@@ -109,9 +109,9 @@ bool ClassFlowInfluxDBv2::ReadParameter(FILE* pfile, string& aktparamgraph)
{
handleMeasurement(splitted[0], splitted[1]);
}
if (((toUpper(splitted[0]) == "DATABASE")) && (splitted.size() > 1))
if (((toUpper(splitted[0]) == "BUCKET")) && (splitted.size() > 1))
{
this->database = splitted[1];
this->bucket = splitted[1];
}
}
@@ -119,11 +119,11 @@ bool ClassFlowInfluxDBv2::ReadParameter(FILE* pfile, string& aktparamgraph)
printf("org: %s\n", dborg.c_str());
printf("token: %s\n", dbtoken.c_str());
if ((uri.length() > 0) && (database.length() > 0) && (dbtoken.length() > 0) && (dborg.length() > 0))
if ((uri.length() > 0) && (bucket.length() > 0) && (dbtoken.length() > 0) && (dborg.length() > 0))
{
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Init InfluxDB with uri: " + uri + ", org: " + dborg + ", token: *****");
// printf("vor V2 Init\n");
InfluxDB_V2_Init(uri, database, dborg, dbtoken);
InfluxDB_V2_Init(uri, bucket, dborg, dbtoken);
// printf("nach V2 Init\n");
InfluxDBenable = true;
} else {

View File

@@ -15,7 +15,7 @@ class ClassFlowInfluxDBv2 :
public ClassFlow
{
protected:
std::string uri, database;
std::string uri, bucket;
std::string dborg, dbtoken, dbfield;
std::string OldValue;
ClassFlowPostProcessing* flowpostprocessing;

View File

@@ -37,6 +37,9 @@ void ClassFlowMQTT::SetInitialParameter(void)
topicUptime = "";
topicFreeMem = "";
caCertFilename = "";
clientCertFilename = "";
clientKeyFilename = "";
clientname = wlan_config.hostname;
OldValue = "";
@@ -102,6 +105,18 @@ 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))
{
this->caCertFilename = splitted[1];
}
if ((toUpper(splitted[0]) == "CLIENTCERT") && (splitted.size() > 1))
{
this->clientCertFilename = splitted[1];
}
if ((toUpper(splitted[0]) == "CLIENTKEY") && (splitted.size() > 1))
{
this->clientKeyFilename = splitted[1];
}
if ((toUpper(splitted[0]) == "USER") && (splitted.size() > 1))
{
this->user = splitted[1];
@@ -196,7 +211,8 @@ bool ClassFlowMQTT::Start(float AutoInterval)
mqttServer_setParameter(flowpostprocessing->GetNumbers(), keepAlive, roundInterval);
bool MQTTConfigCheck = MQTT_Configure(uri, clientname, user, password, maintopic, LWT_TOPIC, LWT_CONNECTED,
LWT_DISCONNECTED, keepAlive, SetRetainFlag, (void *)&GotConnected);
LWT_DISCONNECTED, caCertFilename, clientCertFilename, clientKeyFilename,
keepAlive, SetRetainFlag, (void *)&GotConnected);
if (!MQTTConfigCheck) {
return false;

View File

@@ -19,6 +19,7 @@ protected:
std::string OldValue;
ClassFlowPostProcessing* flowpostprocessing;
std::string user, password;
std::string caCertFilename, clientCertFilename, clientKeyFilename;
bool SetRetainFlag;
int keepAlive; // Seconds
float roundInterval; // Minutes

View File

@@ -2,6 +2,6 @@ FILE(GLOB_RECURSE app_sources ${CMAKE_CURRENT_SOURCE_DIR}/*.*)
idf_component_register(SRCS ${app_sources}
INCLUDE_DIRS "."
REQUIRES tflite-lib esp_http_client jomjol_logfile)
REQUIRES esp_http_client jomjol_logfile)

View File

@@ -16,16 +16,16 @@ std::string _influxDBUser;
std::string _influxDBPassword;
std::string _influxDB_V2_URI;
std::string _influxDB_V2_Database;
std::string _influxDB_V2_Bucket;
std::string _influxDB_V2_Token;
std::string _influxDB_V2_Org;
static esp_err_t http_event_handler(esp_http_client_event_t *evt);
void InfluxDB_V2_Init(std::string _uri, std::string _database, std::string _org, std::string _token)
void InfluxDB_V2_Init(std::string _uri, std::string _bucket, std::string _org, std::string _token)
{
_influxDB_V2_URI = _uri;
_influxDB_V2_Database = _database;
_influxDB_V2_Bucket = _bucket;
_influxDB_V2_Org = _org;
_influxDB_V2_Token = _token;
}
@@ -49,20 +49,16 @@ void InfluxDB_V2_Publish(std::string _measurement, std::string _key, std::string
if (_timestamp.length() > 0)
{
struct tm tm;
time_t t;
time(&t);
localtime_r(&t, &tm); // Extract DST setting from actual time to consider it for timestamp evaluation
strptime(_timestamp.c_str(), PREVALUE_TIME_FORMAT_OUTPUT, &tm);
time_t t = mktime(&tm); // Time in Localtime (looks like timezone is not used by strptime)
// struct tm * ptm;
// ptm = gmtime ( &t );
// time_t utc = mktime(ptm);
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Use handover timestamp: " + _timestamp + " converted GMT timestamp: " + std::to_string(t));
// utc = 2*t - utc; // Take care of timezone (looks difficult, but is easy: t = t + (t - utc), weil t-utc = timezone)
// LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "time conversion utc after: " + std::to_string(utc));
t = mktime(&tm);
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Timestamp: " + _timestamp + ", Timestamp (UTC): " + std::to_string(t));
sprintf(nowTimestamp,"%ld000000000", (long) t); // UTC
payload = _measurement + " " + _key + "=" + _content + " " + nowTimestamp;
}
else
@@ -74,7 +70,7 @@ void InfluxDB_V2_Publish(std::string _measurement, std::string _key, std::string
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "sending line to influxdb:" + payload);
std::string apiURI = _influxDB_V2_URI + "/api/v2/write?org=" + _influxDB_V2_Org + "&bucket=" + _influxDB_V2_Database;
std::string apiURI = _influxDB_V2_URI + "/api/v2/write?org=" + _influxDB_V2_Org + "&bucket=" + _influxDB_V2_Bucket;
apiURI.shrink_to_fit();
http_config.url = apiURI.c_str();
ESP_LOGI(TAG, "http_config: %s", http_config.url); // Add mark on log to see when it restarted
@@ -165,20 +161,16 @@ void InfluxDBPublish(std::string _measurement, std::string _key, std::string _co
if (_timestamp.length() > 0)
{
struct tm tm;
time_t t;
time(&t);
localtime_r(&t, &tm); // Extract DST setting from actual time to consider it for timestamp evaluation
strptime(_timestamp.c_str(), PREVALUE_TIME_FORMAT_OUTPUT, &tm);
time_t t = mktime(&tm); // Time in Localtime (looks like timezone is not used by strptime)
// struct tm * ptm;
// ptm = gmtime ( &t );
// time_t utc = mktime(ptm);
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Use handover timestamp: " + _timestamp + " converted GMT timestamp: " + std::to_string(t));
// utc = 2*t - utc; // Take care of timezone (looks difficult, but is easy: t = t + (t - utc), weil t-utc = timezone)
// LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "time conversion utc after: " + std::to_string(utc));
t = mktime(&tm);
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Timestamp: " + _timestamp + ", Timestamp (UTC): " + std::to_string(t));
sprintf(nowTimestamp,"%ld000000000", (long) t); // UTC
payload = _measurement + " " + _key + "=" + _content + " " + nowTimestamp;
}
else
@@ -191,7 +183,7 @@ void InfluxDBPublish(std::string _measurement, std::string _key, std::string _co
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "sending line to influxdb:" + payload);
// use the default retention policy of the database
// use the default retention policy of the bucket
std::string apiURI = _influxDBURI + "/write?db=" + _influxDBDatabase;
// std::string apiURI = _influxDBURI + "/api/v2/write?bucket=" + _influxDBDatabase + "/";

View File

@@ -13,7 +13,7 @@ void InfluxDBInit(std::string _influxDBURI, std::string _database, std::string _
void InfluxDBPublish(std::string _measurement, std::string _key, std::string _content, std::string _timestamp);
// Interface to InfluxDB v2.x
void InfluxDB_V2_Init(std::string _uri, std::string _database, std::string _org, std::string _token);
void InfluxDB_V2_Init(std::string _uri, std::string _bucket, std::string _org, std::string _token);
void InfluxDB_V2_Publish(std::string _measurement, std::string _key, std::string _content, std::string _timestamp);

View File

@@ -2,6 +2,9 @@
#include "interface_mqtt.h"
#include "esp_log.h"
#if DEBUG_DETAIL_ON
#include "esp_timer.h"
#endif
#include "connect_wlan.h"
#include "mqtt_client.h"
#include "ClassLogFile.h"
@@ -32,6 +35,7 @@ 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 caCert, clientCert, clientKey;
int keepalive;
bool SetRetainFlag;
void (*callbackOnConnected)(std::string, bool) = NULL;
@@ -169,6 +173,10 @@ static esp_err_t mqtt_event_handler_cb(esp_mqtt_event_handle_t event) {
else if (event->error_handle->connect_return_code == MQTT_CONNECTION_REFUSE_NOT_AUTHORIZED) {
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Connection refused, not authorized. Check username/password (0x05)");
}
else {
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
ESP_LOGD(TAG, "MQTT_EVENT_ERROR - esp_mqtt_error_codes:");
@@ -198,6 +206,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 _cacertfilename, std::string _clientcertfilename, std::string _clientkeyfilename,
int _keepalive, bool _SetRetainFlag, void *_callbackOnConnected) {
if ((_mqttURI.length() == 0) || (_maintopic.length() == 0) || (_clientid.length() == 0))
{
@@ -215,6 +224,25 @@ bool MQTT_Configure(std::string _mqttURI, std::string _clientid, std::string _us
maintopic = _maintopic;
callbackOnConnected = ( void (*)(std::string, bool) )(_callbackOnConnected);
if (_clientcertfilename.length() && _clientkeyfilename.length()){
std::ifstream cert_ifs(_clientcertfilename);
std::string cert_content((std::istreambuf_iterator<char>(cert_ifs)), (std::istreambuf_iterator<char>()));
clientCert = cert_content;
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "using clientCert: " + _clientcertfilename);
std::ifstream key_ifs(_clientkeyfilename);
std::string key_content((std::istreambuf_iterator<char>(key_ifs)), (std::istreambuf_iterator<char>()));
clientKey = key_content;
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "using clientKey: " + _clientkeyfilename);
}
if (_cacertfilename.length() ){
std::ifstream ifs(_cacertfilename);
std::string content((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>()));
caCert = content;
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "using caCert: " + _cacertfilename);
}
if (_user.length() && _password.length()){
user = _user;
password = _password;
@@ -268,6 +296,20 @@ int MQTT_Init() {
mqtt_cfg.session.keepalive = keepalive;
mqtt_cfg.buffer.size = 1536; // size of MQTT send/receive buffer (Default: 1024)
if (caCert.length()){
mqtt_cfg.broker.verification.certificate = caCert.c_str();
mqtt_cfg.broker.verification.certificate_len = caCert.length() + 1;
mqtt_cfg.broker.verification.skip_cert_common_name_check = true;
}
if (clientCert.length() && clientKey.length()){
mqtt_cfg.credentials.authentication.certificate = clientCert.c_str();
mqtt_cfg.credentials.authentication.certificate_len = clientCert.length() + 1;
mqtt_cfg.credentials.authentication.key = clientKey.c_str();
mqtt_cfg.credentials.authentication.key_len = clientKey.length() + 1;
}
if (user.length() && password.length()){
mqtt_cfg.credentials.username = user.c_str();
mqtt_cfg.credentials.authentication.password = password.c_str();
@@ -371,8 +413,10 @@ bool mqtt_handler_set_prevalue(std::string _topic, char* _data, int _data_len)
if (cJSON_IsNumber(value)) { // Check if value is a number
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "handler_set_prevalue called: numbersname: " + std::string(numbersname->valuestring) +
", value: " + std::to_string(value->valuedouble));
if (flowctrl.UpdatePrevalue(std::to_string(value->valuedouble), std::string(numbersname->valuestring), true))
if (flowctrl.UpdatePrevalue(std::to_string(value->valuedouble), std::string(numbersname->valuestring), true)) {
cJSON_Delete(jsonData);
return ESP_OK;
}
}
else {
LogFile.WriteToFile(ESP_LOG_WARN, TAG, "handler_set_prevalue: value not a valid number (\"value\": 12345.12345)");
@@ -381,6 +425,7 @@ bool mqtt_handler_set_prevalue(std::string _topic, char* _data, int _data_len)
else {
LogFile.WriteToFile(ESP_LOG_WARN, TAG, "handler_set_prevalue: numbersname not a valid string (\"numbersname\": \"main\")");
}
cJSON_Delete(jsonData);
}
else {
LogFile.WriteToFile(ESP_LOG_WARN, TAG, "handler_set_prevalue: handler called, but no data received");

View File

@@ -11,6 +11,7 @@
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 _cacertfilename, std::string _clientcertfilename, std::string _clientkeyfilename,
int _keepalive, bool SetRetainFlag, void *callbackOnConnected);
int MQTT_Init();
void MQTTdestroy_client(bool _disable);

View File

@@ -12,6 +12,31 @@
static const char *TAG = "TFLITE";
/// Static Resolver muss mit allen Operatoren geladen Werden, die benöägit werden - ABER nur 1x --> gesonderte Funktion /////////////////////////////
static bool MakeStaticResolverDone = false;
static tflite::MicroMutableOpResolver<15> resolver;
void MakeStaticResolver()
{
if (MakeStaticResolverDone)
return;
MakeStaticResolverDone = true;
resolver.AddFullyConnected();
resolver.AddReshape();
resolver.AddSoftmax();
resolver.AddConv2D();
resolver.AddMaxPool2D();
resolver.AddQuantize();
resolver.AddMul();
resolver.AddAdd();
resolver.AddLeakyRelu();
resolver.AddDequantize();
}
////////////////////////////////////////////////////////////////////////////////////////
float CTfLiteClass::GetOutputValue(int nr)
{
TfLiteTensor* output2 = this->interpreter->output(0);
@@ -179,16 +204,20 @@ bool CTfLiteClass::LoadInputImageBasis(CImageBasis *rs)
}
bool CTfLiteClass::MakeAllocate()
{
static tflite::AllOpsResolver resolver;
MakeStaticResolver();
#ifdef DEBUG_DETAIL_ON
LogFile.WriteHeapInfo("CTLiteClass::Alloc start");
#endif
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "CTfLiteClass::MakeAllocate");
this->interpreter = new tflite::MicroInterpreter(this->model, resolver, this->tensor_arena, this->kTensorArenaSize, this->error_reporter);
this->interpreter = new tflite::MicroInterpreter(this->model, resolver, this->tensor_arena, this->kTensorArenaSize);
// this->interpreter = new tflite::MicroInterpreter(this->model, resolver, this->tensor_arena, this->kTensorArenaSize, this->error_reporter);
if (this->interpreter)
{
@@ -285,6 +314,7 @@ bool CTfLiteClass::ReadFileToModel(std::string _fn)
bool CTfLiteClass::LoadModel(std::string _fn)
{
#ifdef SUPRESS_TFLITE_ERRORS
// this->error_reporter = new tflite::ErrorReporter;
this->error_reporter = new tflite::OwnMicroErrorReporter;
#else
this->error_reporter = new tflite::MicroErrorReporter;
@@ -320,16 +350,21 @@ CTfLiteClass::CTfLiteClass()
CTfLiteClass::~CTfLiteClass()
{
delete this->interpreter;
delete this->error_reporter;
// delete this->error_reporter;
psram_free_shared_tensor_arena_and_model_memory();
}
#ifdef SUPRESS_TFLITE_ERRORS
namespace tflite
{
//tflite::ErrorReporter
// int OwnMicroErrorReporter::Report(const char* format, va_list args)
int OwnMicroErrorReporter::Report(const char* format, va_list args)
{
return 0;
}
}
}
#endif

View File

@@ -3,8 +3,12 @@
#ifndef CTFLITECLASS_H
#define CTFLITECLASS_H
#include "tensorflow/lite/micro/all_ops_resolver.h"
#include "tensorflow/lite/micro/micro_error_reporter.h"
#include "tensorflow/lite/micro/micro_mutable_op_resolver.h"
#include "tensorflow/lite/micro/micro_interpreter.h"
#include "tensorflow/lite/micro/micro_mutable_op_resolver.h"
#include "tensorflow/lite/micro/kernels/micro_ops.h"
#include "tensorflow/lite/micro/tflite_bridge/micro_error_reporter.h"
#include "tensorflow/lite/micro/micro_interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "tensorflow/lite/micro/kernels/micro_ops.h"
@@ -13,6 +17,8 @@
#include "CImageBasis.h"
#ifdef SUPRESS_TFLITE_ERRORS
#include "tensorflow/lite/core/api/error_reporter.h"
#include "tensorflow/lite/micro/compatibility.h"
@@ -27,6 +33,7 @@ namespace tflite {
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#endif
class CTfLiteClass
{
protected:
@@ -34,7 +41,6 @@ class CTfLiteClass
const tflite::Model* model;
tflite::MicroInterpreter* interpreter;
TfLiteTensor* output = nullptr;
static tflite::AllOpsResolver resolver;
int kTensorArenaSize;
uint8_t *tensor_arena;
@@ -68,4 +74,6 @@ class CTfLiteClass
int ReadInputDimenstion(int _dim);
};
void MakeStaticResolver();
#endif //CTFLITECLASS_H

View File

@@ -1,3 +1,9 @@
manifest_hash: 63f5c6c9f0bcebc7b9ca12d2aa8b26b2c5f5218d377dc4b2375d9b9ca1df7815
dependencies:
idf:
component_hash: null
source:
type: idf
version: 5.0.2
manifest_hash: f880feca80f04921fc95fd31e9c2936b9896764c15a62f6e2d312c57a62a36db
target: esp32
version: 1.0.0

View File

@@ -173,7 +173,7 @@
fprintf(stderr, "Error at %s:%d\n", __FILE__, __LINE__); \
exit(1); \
}
#define SUPRESS_TFLITE_ERRORS // use, to avoid error messages from TFLITE
// #define SUPRESS_TFLITE_ERRORS // use, to avoid error messages from TFLITE
// connect_wlan.cpp

View File

@@ -642,7 +642,6 @@ void migrateConfiguration(void) {
/* Fieldname has a <NUMBER> as prefix! */
if (isInString(configLines[i], "Fieldname")) { // It is the parameter "Fieldname"
migrated = migrated | replaceString(configLines[i], "Fieldname", "Field"); // Rename it to Field
migrated = migrated | replaceString(configLines[i], ";", ""); // Enable it
}
}
@@ -650,7 +649,10 @@ void migrateConfiguration(void) {
/* Fieldname has a <NUMBER> as prefix! */
if (isInString(configLines[i], "Fieldname")) { // It is the parameter "Fieldname"
migrated = migrated | replaceString(configLines[i], "Fieldname", "Field"); // Rename it to Field
migrated = migrated | replaceString(configLines[i], ";", ""); // Enable it
}
/* Database got renamed to Bucket! */
if (isInString(configLines[i], "Database")) { // It is the parameter "Database"
migrated = migrated | replaceString(configLines[i], "Database", "Bucket"); // Rename it to Bucket
}
}
@@ -721,6 +723,7 @@ std::vector<std::string> splitString(const std::string& str) {
}
/*bool replace_all(std::string& s, std::string const& toReplace, std::string const& replaceWith) {
std::string buf;
std::size_t pos = 0;
@@ -813,4 +816,4 @@ bool setCpuFrequency(void) {
}
return true;
}
}

View File

@@ -39,17 +39,21 @@ esp_err_t info_get_handler(httpd_req_t *req)
char _valuechar[30];
std::string _task;
if (httpd_req_get_url_query_str(req, _query, 200) == ESP_OK)
if (httpd_req_get_url_query_str(req, _query, 200) != ESP_OK)
{
ESP_LOGD(TAG, "Query: %s", _query);
if (httpd_query_key_value(_query, "type", _valuechar, 30) == ESP_OK)
{
ESP_LOGD(TAG, "type is found: %s", _valuechar);
_task = std::string(_valuechar);
}
return httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "invalid query string");
}
ESP_LOGD(TAG, "Query: %s", _query);
if (httpd_query_key_value(_query, "type", _valuechar, 30) != ESP_OK)
{
return httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "missing or invalid 'type' query parameter (too long value?)");
}
ESP_LOGD(TAG, "type is found: %s", _valuechar);
_task = std::string(_valuechar);
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
if (_task.compare("GitBranch") == 0)
@@ -166,6 +170,12 @@ esp_err_t info_get_handler(httpd_req_t *req)
httpd_resp_sendstr(req, zw.c_str());
return ESP_OK;
}
else
{
char formatted[256];
snprintf(formatted, sizeof(formatted), "Unknown value for parameter info 'type': '%s'\n", _task.c_str());
return httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, formatted);
}
return ESP_OK;
}

Binary file not shown.

View File

@@ -18,16 +18,19 @@
For further information about AI-on-the-edge-device please go to <a href=https://github.com/jomjol/AI-on-the-edge-device target=_blank>https://github.com/jomjol/AI-on-the-edge-device</a>.</p>
<p>Notes:</p>
<h2>Notes:</h2>
<ul>
<li>For the installation, make sure to switch the ESP32 to Bootloader mode!</li>
<li>The Webinstall will install the latest firmware (Version <b>$VERSION</b>).</li>
<li>For the installation, make sure to switch the ESP32 to Bootloader mode by keeping the <b>FLASH</b> button pressed while the <b>RESET</b> button gets relesed. if there is no <b>FLASH</b> button, you need to pull <b>GPIO0</b> low!</li>
<li>After the installation, a manual reset might be required!</li>
<li>Please note that not all webbrowsers and operating systems support the needed access to USB!</li>
<li>Please note that not all web browsers and operating systems support the necessary USB access needed for this Webinstaller!</li>
<li>Check the <a href=https://jomjol.github.io/AI-on-the-edge-device-docs/Installation target=_blank>documentation</a> for additional information.</li>
<li>The SD-Card still must be setup separately. This can be done manually or using the new <b>Remote Setup</b>. See the <a href=https://jomjol.github.io/AI-on-the-edge-device-docs/Installation/#3-sd-card target=_blank>documentation</a> for further instructions!</li>
<li>The SD card can be setup automatically after the firmware got installed. See the <a href=https://jomjol.github.io/AI-on-the-edge-device-docs/Installation/#remote-setup-using-the-built-in-access-point target=_blank>documentation</a> for details. For this to work, the SD card must be FAT formated (which is the default on a new SD card).
Alternatively the SD card still can be setup manually, see the <a href=https://jomjol.github.io/AI-on-the-edge-device-docs/Installation/#3-sd-card target=_blank>documentation</a> for details!</li>
</ul>
<hr>
<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>

View File

@@ -1,6 +1,6 @@
{
"name": "AI-on-the-edge",
"version": "13.0.8",
"version": "$VERSION",
"funding_url": "https://www.paypal.com/donate?hosted_button_id=8TRSVYNYKDSWL",
"new_install_prompt_erase": false,
"builds": [

View File

@@ -1,25 +0,0 @@
{
"name": "AI-on-the-edge",
"version": "VERSION",
"funding_url": "https://www.paypal.com/donate?hosted_button_id=8TRSVYNYKDSWL",
"new_install_prompt_erase": false,
"builds": [
{
"chipFamily": "ESP32",
"parts": [
{
"path": "binary/bootloader.bin",
"offset": 4096
},
{
"path": "binary/partitions.bin",
"offset": 32768
},
{
"path": "binary/firmware.bin",
"offset": 65536
}
]
}
]
}

View File

@@ -1,6 +0,0 @@
# Firmware
The firmware got moved to the [Release page](https://github.com/jomjol/AI-on-the-edge-device/releases).
# Installation Guide
You find the complete installation guide at https://jomjol.github.io/AI-on-the-edge-device-docs/Installation/

BIN
images/paypal.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

BIN
images/web-installer.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Binary file not shown.

View File

@@ -31,7 +31,7 @@ main.dig2 343 126 30 54 false
main.dig3 391 126 30 54 false
[Analog]
Model = /config/ana-cont_1207_s2_q.tflite
Model = /config/ana-cont_1208_s2_q.tflite
CNNGoodThreshold = 0.5
;ROIImagesLocation = /log/analog
;ROIImagesRetention = 3
@@ -62,6 +62,9 @@ CheckDigitIncreaseConsistency = false
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
@@ -72,7 +75,7 @@ HomeassistantDiscovery = false
;[InfluxDBv2]
;Uri = undefined
;Database = undefined
;Bucket = undefined
;Measurement = undefined
;Org = undefined
;Token = undefined

Binary file not shown.

Binary file not shown.

View File

@@ -1,19 +1,18 @@
/* The UI can also be run locally, but you have to set the IP of your devide accordingly.
* And you also might have to disable CORS in your webbrowser! */
var domainname_for_testing = "192.168.1.151";
* And you also might have to disable CORS in your webbrowser!
* Keep empty to disable using it. Enabling it will break access through a forwared port, see
* https://github.com/jomjol/AI-on-the-edge-device/issues/2681 */
var domainname_for_testing = "";
//var domainname_for_testing = "192.168.1.151";
/* Returns the domainname with prepended protocol.
Eg. http://watermeter.fritz.box or http://192.168.1.5 */
function getDomainname(){
var host = window.location.hostname;
if (((host == "127.0.0.1") || (host == "localhost") || (host == ""))
// && ((window.location.port == "80") || (window.location.port == ""))
)
{
//console.log("Using pre-defined domainname for testing: " + domainname_for_testing);
if (domainname_for_testing != "") {
console.log("Using pre-defined domainname for testing: " + domainname_for_testing);
domainname = "http://" + domainname_for_testing
}
else

View File

@@ -753,6 +753,39 @@
<td>$TOOLTIP_MQTT_password</td>
</tr>
<tr class="MQTTItem expert" id="exMqtt">
<td class="indent1">
<input type="checkbox" id="MQTT_CACert_enabled" value="1" onclick = 'InvertEnableItem("MQTT", "CACert")' unchecked >
<label for=MQTT_CACert_enabled><class id="MQTT_CACert_text" style="color:black;">Root CA Certificate file</class></label>
</td>
<td>
<input type="text" id="MQTT_CACert_value1">
</td>
<td>$TOOLTIP_MQTT_CACert</td>
</tr>
<tr class="MQTTItem expert" id="exMqtt">
<td class="indent1">
<input type="checkbox" id="MQTT_ClientCert_enabled" value="1" onclick = 'InvertEnableItem("MQTT", "ClientCert")' unchecked >
<label for=MQTT_ClientCert_enabled><class id="MQTT_ClientCert_text" style="color:black;">Client Certificate file</class></label>
</td>
<td>
<input type="text" id="MQTT_ClientCert_value1">
</td>
<td>$TOOLTIP_MQTT_ClientCert</td>
</tr>
<tr class="MQTTItem expert" id="exMqtt">
<td class="indent1">
<input type="checkbox" id="MQTT_ClientKey_enabled" value="1" onclick = 'InvertEnableItem("MQTT", "ClientKey")' unchecked >
<label for=MQTT_ClientKey_enabled><class id="MQTT_ClientKey_text" style="color:black;">Client Key file</class></label>
</td>
<td>
<input type="text" id="MQTT_ClientKey_value1">
</td>
<td>$TOOLTIP_MQTT_ClientKey</td>
</tr>
<tr class="MQTTItem">
<td class="indent1">
<label><class id="MQTT_RetainMessages_text" style="color:black;">Retain Messages</class></label>
@@ -919,13 +952,13 @@
<tr class="InfluxDBv2Item">
<td class="indent1">
<input type="checkbox" id="InfluxDBv2_Database_enabled" value="1" onclick = 'InvertEnableItem("InfluxDBv2", "Database")' unchecked >
<label for=InfluxDBv2_Database_enabled><class id="InfluxDBv2_Database_text" style="color:black;">Database</class></label>
<input type="checkbox" id="InfluxDBv2_Bucket_enabled" value="1" onclick = 'InvertEnableItem("InfluxDBv2", "Bucket")' unchecked >
<label for=InfluxDBv2_Bucket_enabled><class id="InfluxDBv2_Bucket_text" style="color:black;">Bucket</class></label>
</td>
<td>
<input required type="text" id="InfluxDBv2_Database_value1">
<input required type="text" id="InfluxDBv2_Bucket_value1">
</td>
<td>$TOOLTIP_InfluxDBv2_Database</td>
<td>$TOOLTIP_InfluxDBv2_Bucket</td>
</tr>
<tr class="InfluxDBv2Item">
@@ -2089,6 +2122,9 @@ function UpdateInput() {
WriteParameter(param, category, "MQTT", "RetainMessages", false);
WriteParameter(param, category, "MQTT", "HomeassistantDiscovery", false);
WriteParameter(param, category, "MQTT", "MeterType", true);
WriteParameter(param, category, "MQTT", "CACert", true);
WriteParameter(param, category, "MQTT", "ClientCert", true);
WriteParameter(param, category, "MQTT", "ClientKey", true);
WriteParameter(param, category, "InfluxDB", "Uri", true);
WriteParameter(param, category, "InfluxDB", "Database", true);
@@ -2098,7 +2134,7 @@ function UpdateInput() {
// WriteParameter(param, category, "InfluxDB", "Field", true);
WriteParameter(param, category, "InfluxDBv2", "Uri", true);
WriteParameter(param, category, "InfluxDBv2", "Database", true);
WriteParameter(param, category, "InfluxDBv2", "Bucket", true);
// WriteParameter(param, category, "InfluxDBv2", "Measurement", true);
WriteParameter(param, category, "InfluxDBv2", "Org", true);
WriteParameter(param, category, "InfluxDBv2", "Token", true);
@@ -2225,6 +2261,9 @@ function ReadParameterAll()
ReadParameter(param, "MQTT", "RetainMessages", false);
ReadParameter(param, "MQTT", "HomeassistantDiscovery", false);
ReadParameter(param, "MQTT", "MeterType", true);
ReadParameter(param, "MQTT", "CACert", true);
ReadParameter(param, "MQTT", "ClientCert", true);
ReadParameter(param, "MQTT", "ClientKey", true);
ReadParameter(param, "InfluxDB", "Uri", true);
ReadParameter(param, "InfluxDB", "Database", true);
@@ -2233,7 +2272,7 @@ function ReadParameterAll()
ReadParameter(param, "InfluxDB", "password", true);
ReadParameter(param, "InfluxDBv2", "Uri", true);
ReadParameter(param, "InfluxDBv2", "Database", true);
ReadParameter(param, "InfluxDBv2", "Bucket", true);
ReadParameter(param, "InfluxDBv2", "Measurement", true);
ReadParameter(param, "InfluxDBv2", "Org", true);
ReadParameter(param, "InfluxDBv2", "Token", true);

View File

@@ -2,6 +2,8 @@
<html lang="en" xml:lang="en">
<head>
<link rel="icon" href="favicon.ico?v=$COMMIT_HASH" type="image/x-icon">
<link rel="apple-touch-icon" href="watermeter.svg" />
<link rel="shortcut icon" href="watermeter.svg" sizes="196x196">
<title>AI on the edge</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">

View File

@@ -183,6 +183,9 @@ function ParseConfig() {
ParamAddValue(param, catname, "RetainMessages");
ParamAddValue(param, catname, "HomeassistantDiscovery");
ParamAddValue(param, catname, "MeterType");
ParamAddValue(param, catname, "CACert");
ParamAddValue(param, catname, "ClientCert");
ParamAddValue(param, catname, "ClientKey");
var catname = "InfluxDB";
category[catname] = new Object();
@@ -203,7 +206,7 @@ function ParseConfig() {
category[catname]["found"] = false;
param[catname] = new Object();
ParamAddValue(param, catname, "Uri");
ParamAddValue(param, catname, "Database");
ParamAddValue(param, catname, "Bucket");
// ParamAddValue(param, catname, "Measurement");
ParamAddValue(param, catname, "Org");
ParamAddValue(param, catname, "Token");

746
sd-card/html/watermeter.svg Normal file
View File

@@ -0,0 +1,746 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
version="1.1"
id="Layer_1"
x="0px"
y="0px"
viewBox="0 0 310.39999 310.39999"
xml:space="preserve"
sodipodi:docname="watermeter.svg"
width="310.39999"
height="310.39999"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata
id="metadata151"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
id="defs149"><inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 155.2 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="310.39999 : 155.2 : 1"
inkscape:persp3d-origin="155.2 : 103.46666 : 1"
id="perspective1295" /><linearGradient
id="linearGradient888"
inkscape:swatch="solid"><stop
style="stop-color:#ff0000;stop-opacity:1;"
offset="0"
id="stop886" /></linearGradient></defs><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1003"
id="namedview147"
showgrid="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:zoom="0.31481804"
inkscape:cx="-1332.5158"
inkscape:cy="-481.23036"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="Layer_1"
inkscape:pagecheckerboard="0"
showguides="true"
inkscape:guide-bbox="true"
inkscape:snap-text-baseline="false" />
<circle
style="fill:#000000;fill-opacity:1"
cx="155.2"
cy="155.2"
r="155.2"
id="circle6"
inkscape:export-filename="/home/gruinelli/temp/AI-on-the-edge-device/images/icon/favicon.ico"
inkscape:export-xdpi="19.790001"
inkscape:export-ydpi="19.790001" />
<circle
style="fill:#999999"
cx="155.2"
cy="155.2"
id="circle8"
inkscape:export-filename="/home/gruinelli/temp/AI-on-the-edge-device/images/icon/favicon.ico"
inkscape:export-xdpi="19.790001"
inkscape:export-ydpi="19.790001"
r="141.60001" />
<path
style="fill:#ff0000;fill-opacity:1"
d="m 240.8,122 h -14 c -3.2,0 -5.6,-2.4 -5.6,-5.6 V 87.999997 c 0,-3.2 2.4,-5.6 5.6,-5.6 h 14 c 3.2,0 5.6,2.4 5.6,5.6 V 116.4 c -0.4,3.2 -2.8,5.6 -5.6,5.6 z"
id="path10"
inkscape:connector-curvature="0"
inkscape:export-filename="/home/gruinelli/temp/AI-on-the-edge-device/images/icon/favicon.ico"
inkscape:export-xdpi="19.790001"
inkscape:export-ydpi="19.790001" />
<path
style="fill:#000000;fill-opacity:1"
d="m 209.2,122 h -14 c -3.2,0 -5.6,-2.4 -5.6,-5.6 V 87.999997 c 0,-3.2 2.4,-5.6 5.6,-5.6 h 14 c 3.2,0 5.6,2.4 5.6,5.6 V 116.4 c 0,3.2 -2.4,5.6 -5.6,5.6 z"
id="path14"
inkscape:connector-curvature="0"
inkscape:export-filename="/home/gruinelli/temp/AI-on-the-edge-device/images/icon/favicon.ico"
inkscape:export-xdpi="19.790001"
inkscape:export-ydpi="19.790001" />
<path
style="fill:#000002;fill-opacity:1"
d="m 178,122 h -14 c -3.2,0 -5.6,-2.4 -5.6,-5.6 V 87.999997 c 0,-3.2 2.4,-5.6 5.6,-5.6 h 14 c 3.2,0 5.6,2.4 5.6,5.6 V 116.4 c -0.4,3.2 -2.8,5.6 -5.6,5.6 z"
id="path18"
inkscape:connector-curvature="0"
inkscape:export-filename="/home/gruinelli/temp/AI-on-the-edge-device/images/icon/favicon.ico"
inkscape:export-xdpi="19.790001"
inkscape:export-ydpi="19.790001" />
<path
style="fill:#000000;fill-opacity:1"
d="m 146.8,122 h -14 c -3.2,0 -5.6,-2.4 -5.6,-5.6 V 87.999997 c 0,-3.2 2.4,-5.6 5.6,-5.6 h 14 c 3.2,0 5.6,2.4 5.6,5.6 V 116.4 c -0.4,3.2 -2.8,5.6 -5.6,5.6 z"
id="path22"
inkscape:connector-curvature="0"
inkscape:export-filename="/home/gruinelli/temp/AI-on-the-edge-device/images/icon/favicon.ico"
inkscape:export-xdpi="19.790001"
inkscape:export-ydpi="19.790001" />
<path
style="fill:#000003;fill-opacity:1"
d="m 115.2,122 h -14 c -3.2,0 -5.6,-2.4 -5.6,-5.6 V 87.999997 c 0,-3.2 2.4,-5.6 5.6,-5.6 h 14 c 3.2,0 5.6,2.4 5.6,5.6 V 116.4 c 0,3.2 -2.4,5.6 -5.6,5.6 z"
id="path26"
inkscape:connector-curvature="0"
inkscape:export-filename="/home/gruinelli/temp/AI-on-the-edge-device/images/icon/favicon.ico"
inkscape:export-xdpi="19.790001"
inkscape:export-ydpi="19.790001" />
<path
style="fill:#000000;fill-opacity:1"
d="M 84,122 H 70 c -3.2,0 -5.6,-2.4 -5.6,-5.6 V 87.999997 c 0,-3.2 2.4,-5.6 5.6,-5.6 h 14 c 3.2,0 5.6,2.4 5.6,5.6 V 116.4 c -0.4,3.2 -2.8,5.6 -5.6,5.6 z"
id="path30"
inkscape:connector-curvature="0"
inkscape:export-filename="/home/gruinelli/temp/AI-on-the-edge-device/images/icon/favicon.ico"
inkscape:export-xdpi="19.790001"
inkscape:export-ydpi="19.790001" />
<g
id="g116"
transform="translate(-98.8,-98.800003)"
inkscape:export-filename="/home/gruinelli/temp/AI-on-the-edge-device/images/icon/favicon.ico"
inkscape:export-xdpi="19.790001"
inkscape:export-ydpi="19.790001">
</g>
<g
id="g118"
transform="translate(-98.8,-98.800003)"
inkscape:export-filename="/home/gruinelli/temp/AI-on-the-edge-device/images/icon/favicon.ico"
inkscape:export-xdpi="19.790001"
inkscape:export-ydpi="19.790001">
</g>
<g
id="g120"
transform="translate(-98.8,-98.800003)"
inkscape:export-filename="/home/gruinelli/temp/AI-on-the-edge-device/images/icon/favicon.ico"
inkscape:export-xdpi="19.790001"
inkscape:export-ydpi="19.790001">
</g>
<g
id="g122"
transform="translate(-98.8,-98.800003)"
inkscape:export-filename="/home/gruinelli/temp/AI-on-the-edge-device/images/icon/favicon.ico"
inkscape:export-xdpi="19.790001"
inkscape:export-ydpi="19.790001">
</g>
<g
id="g124"
transform="translate(-98.8,-98.800003)"
inkscape:export-filename="/home/gruinelli/temp/AI-on-the-edge-device/images/icon/favicon.ico"
inkscape:export-xdpi="19.790001"
inkscape:export-ydpi="19.790001">
</g>
<g
id="g126"
transform="translate(-98.8,-98.800003)"
inkscape:export-filename="/home/gruinelli/temp/AI-on-the-edge-device/images/icon/favicon.ico"
inkscape:export-xdpi="19.790001"
inkscape:export-ydpi="19.790001">
</g>
<g
id="g128"
transform="translate(-98.8,-98.800003)"
inkscape:export-filename="/home/gruinelli/temp/AI-on-the-edge-device/images/icon/favicon.ico"
inkscape:export-xdpi="19.790001"
inkscape:export-ydpi="19.790001">
</g>
<g
id="g130"
transform="translate(-98.8,-98.800003)"
inkscape:export-filename="/home/gruinelli/temp/AI-on-the-edge-device/images/icon/favicon.ico"
inkscape:export-xdpi="19.790001"
inkscape:export-ydpi="19.790001">
</g>
<g
id="g132"
transform="translate(-98.8,-98.800003)"
inkscape:export-filename="/home/gruinelli/temp/AI-on-the-edge-device/images/icon/favicon.ico"
inkscape:export-xdpi="19.790001"
inkscape:export-ydpi="19.790001">
</g>
<g
id="g134"
transform="translate(-98.8,-98.800003)"
inkscape:export-filename="/home/gruinelli/temp/AI-on-the-edge-device/images/icon/favicon.ico"
inkscape:export-xdpi="19.790001"
inkscape:export-ydpi="19.790001">
</g>
<g
id="g136"
transform="translate(-98.8,-98.800003)"
inkscape:export-filename="/home/gruinelli/temp/AI-on-the-edge-device/images/icon/favicon.ico"
inkscape:export-xdpi="19.790001"
inkscape:export-ydpi="19.790001">
</g>
<g
id="g138"
transform="translate(-98.8,-98.800003)"
inkscape:export-filename="/home/gruinelli/temp/AI-on-the-edge-device/images/icon/favicon.ico"
inkscape:export-xdpi="19.790001"
inkscape:export-ydpi="19.790001">
</g>
<g
id="g140"
transform="translate(-98.8,-98.800003)"
inkscape:export-filename="/home/gruinelli/temp/AI-on-the-edge-device/images/icon/favicon.ico"
inkscape:export-xdpi="19.790001"
inkscape:export-ydpi="19.790001">
</g>
<g
id="g142"
transform="translate(-98.8,-98.800003)"
inkscape:export-filename="/home/gruinelli/temp/AI-on-the-edge-device/images/icon/favicon.ico"
inkscape:export-xdpi="19.790001"
inkscape:export-ydpi="19.790001">
</g>
<g
id="g144"
transform="translate(-98.8,-98.800003)"
inkscape:export-filename="/home/gruinelli/temp/AI-on-the-edge-device/images/icon/favicon.ico"
inkscape:export-xdpi="19.790001"
inkscape:export-ydpi="19.790001">
</g>
<rect
style="fill:none;fill-opacity:1;stroke:#0c0000;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke markers fill"
id="rect958"
width="222.78813"
height="51.122875"
x="44.665253"
y="76.849152"
rx="10"
ry="10"
inkscape:export-filename="/home/gruinelli/temp/AI-on-the-edge-device/images/icon/favicon.ico"
inkscape:export-xdpi="19.790001"
inkscape:export-ydpi="19.790001" />
<g
id="g2493"
transform="matrix(-0.44400058,-1.0430452,1.0430452,-0.44400058,-85.63466,328.03956)"
style="stroke-width:0.882135"
inkscape:export-filename="/home/gruinelli/temp/AI-on-the-edge-device/images/icon/favicon.ico"
inkscape:export-xdpi="19.790001"
inkscape:export-ydpi="19.790001"><path
style="fill:#e6e9ee;stroke-width:0.882135"
d="m 72.8,202.4 v 0 c -16,0 -29.2,-13.2 -29.2,-29.2 v 0 c 0,-16 13.2,-29.2 29.2,-29.2 v 0 c 16,0 29.2,13.2 29.2,29.2 v 0 c 0,16.4 -13.2,29.2 -29.2,29.2 z"
id="path44"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 72.8,198.4 c -0.8,0 -1.2,-0.4 -1.2,-1.2 v -6 c 0,-0.8 0.4,-1.2 1.2,-1.2 0.8,0 1.2,0.4 1.2,1.2 v 6 c 0,0.8 -0.4,1.2 -1.2,1.2 z"
id="path46"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 72.8,156.8 c -0.8,0 -1.2,-0.4 -1.2,-1.2 v -6 c 0,-0.8 0.4,-1.2 1.2,-1.2 0.8,0 1.2,0.4 1.2,1.2 v 5.6 c 0,0.8 -0.4,1.6 -1.2,1.6 z"
id="path48"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 54.8,174.8 h -6 c -0.8,0 -1.2,-0.4 -1.2,-1.2 0,-0.8 0.4,-1.2 1.2,-1.2 h 6 c 0.8,0 1.2,0.4 1.2,1.2 0,0.4 -0.4,1.2 -1.2,1.2 z"
id="path50"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 96.8,174.8 h -6 c -0.8,0 -1.2,-0.4 -1.2,-1.2 0,-0.8 0.4,-1.2 1.2,-1.2 h 6 c 0.8,0 1.2,0.4 1.2,1.2 0,0.4 -0.8,1.2 -1.2,1.2 z"
id="path52"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 56,191.6 c -0.4,0 -0.8,0 -0.8,-0.4 -0.4,-0.4 -0.4,-1.2 0,-1.6 l 4,-4 c 0.4,-0.4 1.2,-0.4 1.6,0 0.4,0.4 0.4,1.2 0,1.6 l -4,4 c -0.4,0 -0.4,0.4 -0.8,0.4 z"
id="path54"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 85.6,162 c -0.4,0 -0.8,0 -0.8,-0.4 -0.4,-0.4 -0.4,-1.2 0,-1.6 l 4,-4 c 0.4,-0.4 1.2,-0.4 1.6,0 0.4,0.4 0.4,1.2 0,1.6 l -4,4 c -0.4,0 -0.4,0.4 -0.8,0.4 z"
id="path56"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 60,162 c -0.4,0 -0.8,0 -0.8,-0.4 l -4,-4 c -0.4,-0.4 -0.4,-1.2 0,-1.6 0.4,-0.4 1.2,-0.4 1.6,0 l 4,4 c 0.4,0.4 0.4,1.2 0,1.6 0,0 -0.4,0.4 -0.8,0.4 z"
id="path58"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 89.6,191.6 c -0.4,0 -0.8,0 -0.8,-0.4 l -4,-4 c -0.4,-0.4 -0.4,-1.2 0,-1.6 0.4,-0.4 1.2,-0.4 1.6,0 l 4,4 c 0.4,0.4 0.4,1.2 0,1.6 0,0 -0.4,0.4 -0.8,0.4 z"
id="path60"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 82,196.8 c -0.4,0 -0.8,-0.4 -1.2,-0.8 l -2.4,-5.6 c -0.4,-0.8 0,-1.2 0.8,-1.6 0.8,-0.4 1.2,0 1.6,0.8 l 2.4,5.6 c 0.4,0.8 0,1.2 -0.8,1.6 0,0 -0.4,0 -0.4,0 z"
id="path62"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 66,158 c -0.4,0 -0.8,-0.4 -1.2,-0.8 l -2.4,-5.6 c -0.4,-0.8 0,-1.2 0.8,-1.6 0.8,-0.4 1.2,0 1.6,0.8 l 2.4,5.6 c 0.4,0.8 0,1.2 -0.8,1.6 0,0 -0.4,0 -0.4,0 z"
id="path64"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 50.8,183.6 c -0.4,0 -0.8,-0.4 -1.2,-0.8 -0.4,-0.8 0,-1.2 0.8,-1.6 l 5.6,-2.4 c 0.8,-0.4 1.2,0 1.6,0.8 0.4,0.8 0,1.2 -0.8,1.6 l -5.6,2.4 z"
id="path66"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 89.6,167.6 c -0.4,0 -0.8,-0.4 -1.2,-0.8 -0.4,-0.8 0,-1.2 0.8,-1.6 l 5.6,-2.4 c 0.8,-0.4 1.2,0 1.6,0.8 0.4,0.8 0,1.2 -0.8,1.6 l -5.6,2.4 c -0.4,0 -0.4,0 -0.4,0 z"
id="path68"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 63.6,196.8 h -0.4 c -0.8,-0.4 -0.8,-1.2 -0.8,-1.6 l 2.4,-5.6 c 0.4,-0.8 1.2,-0.8 1.6,-0.8 0.8,0.4 0.8,1.2 0.8,1.6 l -2.4,5.6 c 0,0.4 -0.8,0.8 -1.2,0.8 z"
id="path70"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 79.6,158 h -0.4 c -0.8,-0.4 -0.8,-1.2 -0.8,-1.6 l 2.4,-5.6 c 0.4,-0.8 1.2,-0.8 1.6,-0.8 0.8,0.4 0.8,1.2 0.8,1.6 l -2.4,5.6 c 0,0.4 -0.8,0.8 -1.2,0.8 z"
id="path72"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="M 56,167.6 H 55.6 L 50,165.2 c -0.8,-0.4 -0.8,-1.2 -0.8,-1.6 0.4,-0.8 1.2,-0.8 1.6,-0.8 l 5.6,2.4 c 0.8,0.4 0.8,1.2 0.8,1.6 0,0.8 -0.4,0.8 -1.2,0.8 z"
id="path74"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 94.8,183.6 h -0.4 l -5.6,-2.4 C 88,180.8 88,180 88,179.6 c 0.4,-0.8 1.2,-0.8 1.6,-0.8 l 5.6,2.4 c 0.8,0.4 0.8,1.2 0.8,1.6 0,0.8 -0.8,0.8 -1.2,0.8 z"
id="path76"
inkscape:connector-curvature="0" /><path
style="fill:#ff0000;fill-opacity:1;stroke-width:0.882135"
d="m 72.8,180.4 v 0 c -4,0 -6.8,-3.2 -6.8,-6.8 v 0 c 0,-4 3.2,-6.8 6.8,-6.8 v 0 c 4,0 6.8,3.2 6.8,6.8 v 0 c 0,3.6 -2.8,6.8 -6.8,6.8 z"
id="path78"
inkscape:connector-curvature="0" /><path
id="rect1441"
style="opacity:1;fill:#ff0000;stroke-width:2.21715"
d="m 72.801641,150.78759 v 0 l 5.635295,19.11133 h -11.27059 z"
sodipodi:nodetypes="ccccc" /></g><g
id="g2493-1"
transform="matrix(-1.0365525,-0.458954,0.458954,-1.0365525,191.69502,452.08902)"
inkscape:transform-center-x="-0.13984455"
inkscape:transform-center-y="1.3468756"
style="stroke-width:0.882135"
inkscape:export-filename="/home/gruinelli/temp/AI-on-the-edge-device/images/icon/favicon.ico"
inkscape:export-xdpi="19.790001"
inkscape:export-ydpi="19.790001"><path
style="fill:#e6e9ee;stroke-width:0.882135"
d="m 72.8,202.4 v 0 c -16,0 -29.2,-13.2 -29.2,-29.2 v 0 c 0,-16 13.2,-29.2 29.2,-29.2 v 0 c 16,0 29.2,13.2 29.2,29.2 v 0 c 0,16.4 -13.2,29.2 -29.2,29.2 z"
id="path44-8"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 72.8,198.4 c -0.8,0 -1.2,-0.4 -1.2,-1.2 v -6 c 0,-0.8 0.4,-1.2 1.2,-1.2 0.8,0 1.2,0.4 1.2,1.2 v 6 c 0,0.8 -0.4,1.2 -1.2,1.2 z"
id="path46-79"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 72.8,156.8 c -0.8,0 -1.2,-0.4 -1.2,-1.2 v -6 c 0,-0.8 0.4,-1.2 1.2,-1.2 0.8,0 1.2,0.4 1.2,1.2 v 5.6 c 0,0.8 -0.4,1.6 -1.2,1.6 z"
id="path48-2"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 54.8,174.8 h -6 c -0.8,0 -1.2,-0.4 -1.2,-1.2 0,-0.8 0.4,-1.2 1.2,-1.2 h 6 c 0.8,0 1.2,0.4 1.2,1.2 0,0.4 -0.4,1.2 -1.2,1.2 z"
id="path50-0"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 96.8,174.8 h -6 c -0.8,0 -1.2,-0.4 -1.2,-1.2 0,-0.8 0.4,-1.2 1.2,-1.2 h 6 c 0.8,0 1.2,0.4 1.2,1.2 0,0.4 -0.8,1.2 -1.2,1.2 z"
id="path52-2"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 56,191.6 c -0.4,0 -0.8,0 -0.8,-0.4 -0.4,-0.4 -0.4,-1.2 0,-1.6 l 4,-4 c 0.4,-0.4 1.2,-0.4 1.6,0 0.4,0.4 0.4,1.2 0,1.6 l -4,4 c -0.4,0 -0.4,0.4 -0.8,0.4 z"
id="path54-3"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 85.6,162 c -0.4,0 -0.8,0 -0.8,-0.4 -0.4,-0.4 -0.4,-1.2 0,-1.6 l 4,-4 c 0.4,-0.4 1.2,-0.4 1.6,0 0.4,0.4 0.4,1.2 0,1.6 l -4,4 c -0.4,0 -0.4,0.4 -0.8,0.4 z"
id="path56-7"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 60,162 c -0.4,0 -0.8,0 -0.8,-0.4 l -4,-4 c -0.4,-0.4 -0.4,-1.2 0,-1.6 0.4,-0.4 1.2,-0.4 1.6,0 l 4,4 c 0.4,0.4 0.4,1.2 0,1.6 0,0 -0.4,0.4 -0.8,0.4 z"
id="path58-5"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 89.6,191.6 c -0.4,0 -0.8,0 -0.8,-0.4 l -4,-4 c -0.4,-0.4 -0.4,-1.2 0,-1.6 0.4,-0.4 1.2,-0.4 1.6,0 l 4,4 c 0.4,0.4 0.4,1.2 0,1.6 0,0 -0.4,0.4 -0.8,0.4 z"
id="path60-9"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 82,196.8 c -0.4,0 -0.8,-0.4 -1.2,-0.8 l -2.4,-5.6 c -0.4,-0.8 0,-1.2 0.8,-1.6 0.8,-0.4 1.2,0 1.6,0.8 l 2.4,5.6 c 0.4,0.8 0,1.2 -0.8,1.6 0,0 -0.4,0 -0.4,0 z"
id="path62-22"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 66,158 c -0.4,0 -0.8,-0.4 -1.2,-0.8 l -2.4,-5.6 c -0.4,-0.8 0,-1.2 0.8,-1.6 0.8,-0.4 1.2,0 1.6,0.8 l 2.4,5.6 c 0.4,0.8 0,1.2 -0.8,1.6 0,0 -0.4,0 -0.4,0 z"
id="path64-8"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 50.8,183.6 c -0.4,0 -0.8,-0.4 -1.2,-0.8 -0.4,-0.8 0,-1.2 0.8,-1.6 l 5.6,-2.4 c 0.8,-0.4 1.2,0 1.6,0.8 0.4,0.8 0,1.2 -0.8,1.6 l -5.6,2.4 z"
id="path66-9"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 89.6,167.6 c -0.4,0 -0.8,-0.4 -1.2,-0.8 -0.4,-0.8 0,-1.2 0.8,-1.6 l 5.6,-2.4 c 0.8,-0.4 1.2,0 1.6,0.8 0.4,0.8 0,1.2 -0.8,1.6 l -5.6,2.4 c -0.4,0 -0.4,0 -0.4,0 z"
id="path68-7"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 63.6,196.8 h -0.4 c -0.8,-0.4 -0.8,-1.2 -0.8,-1.6 l 2.4,-5.6 c 0.4,-0.8 1.2,-0.8 1.6,-0.8 0.8,0.4 0.8,1.2 0.8,1.6 l -2.4,5.6 c 0,0.4 -0.8,0.8 -1.2,0.8 z"
id="path70-36"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 79.6,158 h -0.4 c -0.8,-0.4 -0.8,-1.2 -0.8,-1.6 l 2.4,-5.6 c 0.4,-0.8 1.2,-0.8 1.6,-0.8 0.8,0.4 0.8,1.2 0.8,1.6 l -2.4,5.6 c 0,0.4 -0.8,0.8 -1.2,0.8 z"
id="path72-1"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="M 56,167.6 H 55.6 L 50,165.2 c -0.8,-0.4 -0.8,-1.2 -0.8,-1.6 0.4,-0.8 1.2,-0.8 1.6,-0.8 l 5.6,2.4 c 0.8,0.4 0.8,1.2 0.8,1.6 0,0.8 -0.4,0.8 -1.2,0.8 z"
id="path74-2"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 94.8,183.6 h -0.4 l -5.6,-2.4 C 88,180.8 88,180 88,179.6 c 0.4,-0.8 1.2,-0.8 1.6,-0.8 l 5.6,2.4 c 0.8,0.4 0.8,1.2 0.8,1.6 0,0.8 -0.8,0.8 -1.2,0.8 z"
id="path76-9"
inkscape:connector-curvature="0" /><path
style="fill:#ff0000;fill-opacity:1;stroke-width:0.882135"
d="m 72.8,180.4 v 0 c -4,0 -6.8,-3.2 -6.8,-6.8 v 0 c 0,-4 3.2,-6.8 6.8,-6.8 v 0 c 4,0 6.8,3.2 6.8,6.8 v 0 c 0,3.6 -2.8,6.8 -6.8,6.8 z"
id="path78-3"
inkscape:connector-curvature="0" /><path
id="rect1441-1"
style="opacity:1;fill:#ff0000;stroke-width:2.21715"
d="m 72.801641,150.78759 v 0 l 5.635295,19.11133 h -11.27059 z"
sodipodi:nodetypes="ccccc" /></g><g
id="g2493-9"
transform="matrix(1.1065407,0.24626723,-0.24626723,1.1065407,81.269684,28.240985)"
style="stroke-width:0.882135"
inkscape:export-filename="/home/gruinelli/temp/AI-on-the-edge-device/images/icon/favicon.ico"
inkscape:export-xdpi="19.790001"
inkscape:export-ydpi="19.790001"><path
style="fill:#e6e9ee;stroke-width:0.882135"
d="m 72.8,202.4 v 0 c -16,0 -29.2,-13.2 -29.2,-29.2 v 0 c 0,-16 13.2,-29.2 29.2,-29.2 v 0 c 16,0 29.2,13.2 29.2,29.2 v 0 c 0,16.4 -13.2,29.2 -29.2,29.2 z"
id="path44-4"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 72.8,198.4 c -0.8,0 -1.2,-0.4 -1.2,-1.2 v -6 c 0,-0.8 0.4,-1.2 1.2,-1.2 0.8,0 1.2,0.4 1.2,1.2 v 6 c 0,0.8 -0.4,1.2 -1.2,1.2 z"
id="path46-78"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 72.8,156.8 c -0.8,0 -1.2,-0.4 -1.2,-1.2 v -6 c 0,-0.8 0.4,-1.2 1.2,-1.2 0.8,0 1.2,0.4 1.2,1.2 v 5.6 c 0,0.8 -0.4,1.6 -1.2,1.6 z"
id="path48-4"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 54.8,174.8 h -6 c -0.8,0 -1.2,-0.4 -1.2,-1.2 0,-0.8 0.4,-1.2 1.2,-1.2 h 6 c 0.8,0 1.2,0.4 1.2,1.2 0,0.4 -0.4,1.2 -1.2,1.2 z"
id="path50-5"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 96.8,174.8 h -6 c -0.8,0 -1.2,-0.4 -1.2,-1.2 0,-0.8 0.4,-1.2 1.2,-1.2 h 6 c 0.8,0 1.2,0.4 1.2,1.2 0,0.4 -0.8,1.2 -1.2,1.2 z"
id="path52-0"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 56,191.6 c -0.4,0 -0.8,0 -0.8,-0.4 -0.4,-0.4 -0.4,-1.2 0,-1.6 l 4,-4 c 0.4,-0.4 1.2,-0.4 1.6,0 0.4,0.4 0.4,1.2 0,1.6 l -4,4 c -0.4,0 -0.4,0.4 -0.8,0.4 z"
id="path54-36"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 85.6,162 c -0.4,0 -0.8,0 -0.8,-0.4 -0.4,-0.4 -0.4,-1.2 0,-1.6 l 4,-4 c 0.4,-0.4 1.2,-0.4 1.6,0 0.4,0.4 0.4,1.2 0,1.6 l -4,4 c -0.4,0 -0.4,0.4 -0.8,0.4 z"
id="path56-1"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 60,162 c -0.4,0 -0.8,0 -0.8,-0.4 l -4,-4 c -0.4,-0.4 -0.4,-1.2 0,-1.6 0.4,-0.4 1.2,-0.4 1.6,0 l 4,4 c 0.4,0.4 0.4,1.2 0,1.6 0,0 -0.4,0.4 -0.8,0.4 z"
id="path58-0"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 89.6,191.6 c -0.4,0 -0.8,0 -0.8,-0.4 l -4,-4 c -0.4,-0.4 -0.4,-1.2 0,-1.6 0.4,-0.4 1.2,-0.4 1.6,0 l 4,4 c 0.4,0.4 0.4,1.2 0,1.6 0,0 -0.4,0.4 -0.8,0.4 z"
id="path60-6"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 82,196.8 c -0.4,0 -0.8,-0.4 -1.2,-0.8 l -2.4,-5.6 c -0.4,-0.8 0,-1.2 0.8,-1.6 0.8,-0.4 1.2,0 1.6,0.8 l 2.4,5.6 c 0.4,0.8 0,1.2 -0.8,1.6 0,0 -0.4,0 -0.4,0 z"
id="path62-3"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 66,158 c -0.4,0 -0.8,-0.4 -1.2,-0.8 l -2.4,-5.6 c -0.4,-0.8 0,-1.2 0.8,-1.6 0.8,-0.4 1.2,0 1.6,0.8 l 2.4,5.6 c 0.4,0.8 0,1.2 -0.8,1.6 0,0 -0.4,0 -0.4,0 z"
id="path64-2"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 50.8,183.6 c -0.4,0 -0.8,-0.4 -1.2,-0.8 -0.4,-0.8 0,-1.2 0.8,-1.6 l 5.6,-2.4 c 0.8,-0.4 1.2,0 1.6,0.8 0.4,0.8 0,1.2 -0.8,1.6 l -5.6,2.4 z"
id="path66-06"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 89.6,167.6 c -0.4,0 -0.8,-0.4 -1.2,-0.8 -0.4,-0.8 0,-1.2 0.8,-1.6 l 5.6,-2.4 c 0.8,-0.4 1.2,0 1.6,0.8 0.4,0.8 0,1.2 -0.8,1.6 l -5.6,2.4 c -0.4,0 -0.4,0 -0.4,0 z"
id="path68-1"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 63.6,196.8 h -0.4 c -0.8,-0.4 -0.8,-1.2 -0.8,-1.6 l 2.4,-5.6 c 0.4,-0.8 1.2,-0.8 1.6,-0.8 0.8,0.4 0.8,1.2 0.8,1.6 l -2.4,5.6 c 0,0.4 -0.8,0.8 -1.2,0.8 z"
id="path70-5"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 79.6,158 h -0.4 c -0.8,-0.4 -0.8,-1.2 -0.8,-1.6 l 2.4,-5.6 c 0.4,-0.8 1.2,-0.8 1.6,-0.8 0.8,0.4 0.8,1.2 0.8,1.6 l -2.4,5.6 c 0,0.4 -0.8,0.8 -1.2,0.8 z"
id="path72-5"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="M 56,167.6 H 55.6 L 50,165.2 c -0.8,-0.4 -0.8,-1.2 -0.8,-1.6 0.4,-0.8 1.2,-0.8 1.6,-0.8 l 5.6,2.4 c 0.8,0.4 0.8,1.2 0.8,1.6 0,0.8 -0.4,0.8 -1.2,0.8 z"
id="path74-4"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 94.8,183.6 h -0.4 l -5.6,-2.4 C 88,180.8 88,180 88,179.6 c 0.4,-0.8 1.2,-0.8 1.6,-0.8 l 5.6,2.4 c 0.8,0.4 0.8,1.2 0.8,1.6 0,0.8 -0.8,0.8 -1.2,0.8 z"
id="path76-7"
inkscape:connector-curvature="0" /><path
style="fill:#ff0000;fill-opacity:1;stroke-width:0.882135"
d="m 72.8,180.4 v 0 c -4,0 -6.8,-3.2 -6.8,-6.8 v 0 c 0,-4 3.2,-6.8 6.8,-6.8 v 0 c 4,0 6.8,3.2 6.8,6.8 v 0 c 0,3.6 -2.8,6.8 -6.8,6.8 z"
id="path78-6"
inkscape:connector-curvature="0" /><path
id="rect1441-5"
style="opacity:1;fill:#ff0000;stroke-width:2.21715"
d="m 72.801641,150.78759 v 0 l 5.635295,19.11133 h -11.27059 z"
sodipodi:nodetypes="ccccc" /></g><g
id="g2493-3"
transform="matrix(0.81724208,0.78561769,-0.78561769,0.81724208,322.48101,-24.488096)"
style="stroke-width:0.882135"
inkscape:export-filename="/home/gruinelli/temp/AI-on-the-edge-device/images/icon/favicon.ico"
inkscape:export-xdpi="19.790001"
inkscape:export-ydpi="19.790001"><path
style="fill:#e6e9ee;stroke-width:0.882135"
d="m 72.8,202.4 v 0 c -16,0 -29.2,-13.2 -29.2,-29.2 v 0 c 0,-16 13.2,-29.2 29.2,-29.2 v 0 c 16,0 29.2,13.2 29.2,29.2 v 0 c 0,16.4 -13.2,29.2 -29.2,29.2 z"
id="path44-6"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 72.8,198.4 c -0.8,0 -1.2,-0.4 -1.2,-1.2 v -6 c 0,-0.8 0.4,-1.2 1.2,-1.2 0.8,0 1.2,0.4 1.2,1.2 v 6 c 0,0.8 -0.4,1.2 -1.2,1.2 z"
id="path46-7"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 72.8,156.8 c -0.8,0 -1.2,-0.4 -1.2,-1.2 v -6 c 0,-0.8 0.4,-1.2 1.2,-1.2 0.8,0 1.2,0.4 1.2,1.2 v 5.6 c 0,0.8 -0.4,1.6 -1.2,1.6 z"
id="path48-5"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 54.8,174.8 h -6 c -0.8,0 -1.2,-0.4 -1.2,-1.2 0,-0.8 0.4,-1.2 1.2,-1.2 h 6 c 0.8,0 1.2,0.4 1.2,1.2 0,0.4 -0.4,1.2 -1.2,1.2 z"
id="path50-3"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 96.8,174.8 h -6 c -0.8,0 -1.2,-0.4 -1.2,-1.2 0,-0.8 0.4,-1.2 1.2,-1.2 h 6 c 0.8,0 1.2,0.4 1.2,1.2 0,0.4 -0.8,1.2 -1.2,1.2 z"
id="path52-5"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 56,191.6 c -0.4,0 -0.8,0 -0.8,-0.4 -0.4,-0.4 -0.4,-1.2 0,-1.6 l 4,-4 c 0.4,-0.4 1.2,-0.4 1.6,0 0.4,0.4 0.4,1.2 0,1.6 l -4,4 c -0.4,0 -0.4,0.4 -0.8,0.4 z"
id="path54-6"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 85.6,162 c -0.4,0 -0.8,0 -0.8,-0.4 -0.4,-0.4 -0.4,-1.2 0,-1.6 l 4,-4 c 0.4,-0.4 1.2,-0.4 1.6,0 0.4,0.4 0.4,1.2 0,1.6 l -4,4 c -0.4,0 -0.4,0.4 -0.8,0.4 z"
id="path56-2"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 60,162 c -0.4,0 -0.8,0 -0.8,-0.4 l -4,-4 c -0.4,-0.4 -0.4,-1.2 0,-1.6 0.4,-0.4 1.2,-0.4 1.6,0 l 4,4 c 0.4,0.4 0.4,1.2 0,1.6 0,0 -0.4,0.4 -0.8,0.4 z"
id="path58-9"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 89.6,191.6 c -0.4,0 -0.8,0 -0.8,-0.4 l -4,-4 c -0.4,-0.4 -0.4,-1.2 0,-1.6 0.4,-0.4 1.2,-0.4 1.6,0 l 4,4 c 0.4,0.4 0.4,1.2 0,1.6 0,0 -0.4,0.4 -0.8,0.4 z"
id="path60-1"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 82,196.8 c -0.4,0 -0.8,-0.4 -1.2,-0.8 l -2.4,-5.6 c -0.4,-0.8 0,-1.2 0.8,-1.6 0.8,-0.4 1.2,0 1.6,0.8 l 2.4,5.6 c 0.4,0.8 0,1.2 -0.8,1.6 0,0 -0.4,0 -0.4,0 z"
id="path62-2"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 66,158 c -0.4,0 -0.8,-0.4 -1.2,-0.8 l -2.4,-5.6 c -0.4,-0.8 0,-1.2 0.8,-1.6 0.8,-0.4 1.2,0 1.6,0.8 l 2.4,5.6 c 0.4,0.8 0,1.2 -0.8,1.6 0,0 -0.4,0 -0.4,0 z"
id="path64-7"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 50.8,183.6 c -0.4,0 -0.8,-0.4 -1.2,-0.8 -0.4,-0.8 0,-1.2 0.8,-1.6 l 5.6,-2.4 c 0.8,-0.4 1.2,0 1.6,0.8 0.4,0.8 0,1.2 -0.8,1.6 l -5.6,2.4 z"
id="path66-0"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 89.6,167.6 c -0.4,0 -0.8,-0.4 -1.2,-0.8 -0.4,-0.8 0,-1.2 0.8,-1.6 l 5.6,-2.4 c 0.8,-0.4 1.2,0 1.6,0.8 0.4,0.8 0,1.2 -0.8,1.6 l -5.6,2.4 c -0.4,0 -0.4,0 -0.4,0 z"
id="path68-9"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 63.6,196.8 h -0.4 c -0.8,-0.4 -0.8,-1.2 -0.8,-1.6 l 2.4,-5.6 c 0.4,-0.8 1.2,-0.8 1.6,-0.8 0.8,0.4 0.8,1.2 0.8,1.6 l -2.4,5.6 c 0,0.4 -0.8,0.8 -1.2,0.8 z"
id="path70-3"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 79.6,158 h -0.4 c -0.8,-0.4 -0.8,-1.2 -0.8,-1.6 l 2.4,-5.6 c 0.4,-0.8 1.2,-0.8 1.6,-0.8 0.8,0.4 0.8,1.2 0.8,1.6 l -2.4,5.6 c 0,0.4 -0.8,0.8 -1.2,0.8 z"
id="path72-6"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="M 56,167.6 H 55.6 L 50,165.2 c -0.8,-0.4 -0.8,-1.2 -0.8,-1.6 0.4,-0.8 1.2,-0.8 1.6,-0.8 l 5.6,2.4 c 0.8,0.4 0.8,1.2 0.8,1.6 0,0.8 -0.4,0.8 -1.2,0.8 z"
id="path74-0"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1;stroke-width:0.882135"
d="m 94.8,183.6 h -0.4 l -5.6,-2.4 C 88,180.8 88,180 88,179.6 c 0.4,-0.8 1.2,-0.8 1.6,-0.8 l 5.6,2.4 c 0.8,0.4 0.8,1.2 0.8,1.6 0,0.8 -0.8,0.8 -1.2,0.8 z"
id="path76-6"
inkscape:connector-curvature="0" /><path
style="fill:#ff0000;fill-opacity:1;stroke-width:0.882135"
d="m 72.8,180.4 v 0 c -4,0 -6.8,-3.2 -6.8,-6.8 v 0 c 0,-4 3.2,-6.8 6.8,-6.8 v 0 c 4,0 6.8,3.2 6.8,6.8 v 0 c 0,3.6 -2.8,6.8 -6.8,6.8 z"
id="path78-2"
inkscape:connector-curvature="0" /><path
id="rect1441-6"
style="opacity:1;fill:#ff0000;stroke-width:2.21715"
d="m 72.801641,150.78759 v 0 l 5.635295,19.11133 h -11.27059 z"
sodipodi:nodetypes="ccccc" /></g><text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:33.3333px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="67.090721"
y="114.12241"
id="text1475"
inkscape:export-filename="/home/gruinelli/temp/AI-on-the-edge-device/images/icon/favicon.ico"
inkscape:export-xdpi="19.790001"
inkscape:export-ydpi="19.790001"><tspan
sodipodi:role="line"
id="tspan1473"
x="67.090721"
y="114.12241"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:33.3333px;font-family:Sans;-inkscape-font-specification:'Sans Bold';fill:#ffffff">5</tspan></text><text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:33.3333px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="99.090721"
y="114.12241"
id="text7150"
inkscape:export-filename="/home/gruinelli/temp/AI-on-the-edge-device/images/icon/favicon.ico"
inkscape:export-xdpi="19.790001"
inkscape:export-ydpi="19.790001"><tspan
sodipodi:role="line"
id="tspan7148"
x="99.090721"
y="114.12241"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:33.3333px;font-family:Sans;-inkscape-font-specification:'Sans Bold';fill:#ffffff">2</tspan></text><text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:33.3333px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="131.09073"
y="114.12241"
id="text7154"
inkscape:export-filename="/home/gruinelli/temp/AI-on-the-edge-device/images/icon/favicon.ico"
inkscape:export-xdpi="19.790001"
inkscape:export-ydpi="19.790001"><tspan
sodipodi:role="line"
id="tspan7152"
x="131.09073"
y="114.12241"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:33.3333px;font-family:Sans;-inkscape-font-specification:'Sans Bold';fill:#ffffff">0</tspan></text><text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:33.3333px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="161.09073"
y="114.12241"
id="text7158"
inkscape:export-filename="/home/gruinelli/temp/AI-on-the-edge-device/images/icon/favicon.ico"
inkscape:export-xdpi="19.790001"
inkscape:export-ydpi="19.790001"><tspan
sodipodi:role="line"
id="tspan7156"
x="161.09073"
y="114.12241"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:33.3333px;font-family:Sans;-inkscape-font-specification:'Sans Bold';fill:#ffffff">3</tspan></text><text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:33.3333px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="193.09073"
y="114.12241"
id="text7400"
inkscape:export-filename="/home/gruinelli/temp/AI-on-the-edge-device/images/icon/favicon.ico"
inkscape:export-xdpi="19.790001"
inkscape:export-ydpi="19.790001"><tspan
sodipodi:role="line"
id="tspan7398"
x="193.09073"
y="114.12241"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:33.3333px;font-family:Sans;-inkscape-font-specification:'Sans Bold';fill:#ffffff">0</tspan></text><text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:33.3333px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="225.09073"
y="114.12241"
id="text7404"
inkscape:export-filename="/home/gruinelli/temp/AI-on-the-edge-device/images/icon/favicon.ico"
inkscape:export-xdpi="19.790001"
inkscape:export-ydpi="19.790001"><tspan
sodipodi:role="line"
id="tspan7402"
x="225.09073"
y="114.12241"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:33.3333px;font-family:Sans;-inkscape-font-specification:'Sans Bold';fill:#ffffff">1</tspan></text><rect
style="fill:none;stroke:#1f00ea;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect18406"
width="23.916084"
height="39.886673"
x="222.59164"
y="81.731308"
inkscape:export-filename="/home/gruinelli/temp/AI-on-the-edge-device/images/icon/favicon.ico"
inkscape:export-xdpi="19.790001"
inkscape:export-ydpi="19.790001" /><rect
style="fill:none;stroke:#1f00ea;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect20467"
width="23.916084"
height="39.886673"
x="190.59164"
y="81.731308"
inkscape:export-filename="/home/gruinelli/temp/AI-on-the-edge-device/images/icon/favicon.ico"
inkscape:export-xdpi="19.790001"
inkscape:export-ydpi="19.790001" /><rect
style="fill:none;stroke:#1f00ea;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect20469"
width="23.916084"
height="39.886673"
x="158.59164"
y="81.731308"
inkscape:export-filename="/home/gruinelli/temp/AI-on-the-edge-device/images/icon/favicon.ico"
inkscape:export-xdpi="19.790001"
inkscape:export-ydpi="19.790001" /><circle
style="fill:none;stroke:#02ea00;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path20619"
cx="245.87659"
cy="173.6358"
r="35.34029"
inkscape:export-filename="/home/gruinelli/temp/AI-on-the-edge-device/images/icon/favicon.ico"
inkscape:export-xdpi="19.790001"
inkscape:export-ydpi="19.790001" /><circle
style="fill:none;stroke:#02ea00;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path20619-5"
cx="195.75063"
cy="239.26401"
r="35.34029"
inkscape:export-filename="/home/gruinelli/temp/AI-on-the-edge-device/images/icon/favicon.ico"
inkscape:export-xdpi="19.790001"
inkscape:export-ydpi="19.790001" /></svg>

After

Width:  |  Height:  |  Size: 38 KiB