mirror of
https://github.com/jomjol/AI-on-the-edge-device.git
synced 2025-12-07 03:56:57 +03:00
Compare commits
1400 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c891ab7fe | ||
|
|
6adcd90038 | ||
|
|
676b94f57c | ||
|
|
01df45f1e1 | ||
|
|
f7fc55960b | ||
|
|
3dd2b74226 | ||
|
|
4424337a4e | ||
|
|
0ddcbbc847 | ||
|
|
bbd60c9d19 | ||
|
|
19d20f01de | ||
|
|
fa69272a57 | ||
|
|
4986b1936f | ||
|
|
939271f2a0 | ||
|
|
d40d5ba0f6 | ||
|
|
a851fd393c | ||
|
|
2a8df9de1c | ||
|
|
634deb9f27 | ||
|
|
d795f30276 | ||
|
|
17f951d3cd | ||
|
|
f4f4fcd166 | ||
|
|
90d0827846 | ||
|
|
5a03bf82d7 | ||
|
|
391907b5a1 | ||
|
|
69af3d79c1 | ||
|
|
7b8a5e77a5 | ||
|
|
1a7ff9a4e2 | ||
|
|
70b8572d16 | ||
|
|
c8bfb338c6 | ||
|
|
e6bd4160e1 | ||
|
|
ab30bda586 | ||
|
|
813d49690c | ||
|
|
f5e2f5dd72 | ||
|
|
2029e07495 | ||
|
|
b780815a29 | ||
|
|
bb714acb0b | ||
|
|
ac27539442 | ||
|
|
27f64e9479 | ||
|
|
f0e79de1ba | ||
|
|
074136ec2b | ||
|
|
feb058ccf8 | ||
|
|
57dc02365b | ||
|
|
486b76de1b | ||
|
|
d4b2bec45f | ||
|
|
76ef3a80cc | ||
|
|
e63f98e8b3 | ||
|
|
c63819e9b4 | ||
|
|
a0d6509f42 | ||
|
|
b451e4ebb3 | ||
|
|
845a166d9e | ||
|
|
8b2d80c87e | ||
|
|
426068288d | ||
|
|
81b9b4ab76 | ||
|
|
18b6aee7c7 | ||
|
|
4abbbca212 | ||
|
|
3372359daa | ||
|
|
584f7b9471 | ||
|
|
88855c8f31 | ||
|
|
f70d7aed42 | ||
|
|
b40a42b02b | ||
|
|
f66ea4f01f | ||
|
|
bb79ab3d75 | ||
|
|
0b269e17f3 | ||
|
|
d55abf20c1 | ||
|
|
1fcbc65ee9 | ||
|
|
c595b402df | ||
|
|
1a34fe97e4 | ||
|
|
2c9a6a0f5c | ||
|
|
f8807ca4bc | ||
|
|
e0ec67a14d | ||
|
|
50c02a14f4 | ||
|
|
2f5ecf8722 | ||
|
|
2c65d51210 | ||
|
|
5c53858766 | ||
|
|
4c51a906a3 | ||
|
|
4395135e89 | ||
|
|
827aa48524 | ||
|
|
cf80aa929b | ||
|
|
245302c6ca | ||
|
|
426c24575f | ||
|
|
c3233dc159 | ||
|
|
2a0c61332e | ||
|
|
18d44a8556 | ||
|
|
5dc111e7a5 | ||
|
|
90d7c2e641 | ||
|
|
8bb9c6dc41 | ||
|
|
1da27a6c54 | ||
|
|
35fa2bd353 | ||
|
|
8aa9f48160 | ||
|
|
5dc7bb949a | ||
|
|
457a526792 | ||
|
|
eddf59cdb3 | ||
|
|
d0cf9e495d | ||
|
|
905b5a2ac5 | ||
|
|
14888bca3f | ||
|
|
2735a0862f | ||
|
|
782e1c229b | ||
|
|
287302ba7f | ||
|
|
3de4cc7c56 | ||
|
|
6c153cd376 | ||
|
|
d99c2ce920 | ||
|
|
17eb382b66 | ||
|
|
0df54d1c5b | ||
|
|
9ddc9a4f5a | ||
|
|
86304524e2 | ||
|
|
22ef80db37 | ||
|
|
5a8b39f246 | ||
|
|
284f7b1180 | ||
|
|
bf44745342 | ||
|
|
60ce08e331 | ||
|
|
52296b3cb6 | ||
|
|
17d85ae523 | ||
|
|
e760a38985 | ||
|
|
47d419e3ae | ||
|
|
f779bc8401 | ||
|
|
cb4e6a0a30 | ||
|
|
fd2d4408bf | ||
|
|
6ab2b42c8d | ||
|
|
b21c0ab15b | ||
|
|
adfe2d57d6 | ||
|
|
ec8de6287f | ||
|
|
656110ca02 | ||
|
|
1e08eaf4bf | ||
|
|
bb4f2e4d91 | ||
|
|
f15347598a | ||
|
|
defbd60ccf | ||
|
|
4a462fb79a | ||
|
|
c86cd905da | ||
|
|
184d1a70a2 | ||
|
|
decf72104e | ||
|
|
0ee3c0bb0a | ||
|
|
e05ae5170d | ||
|
|
1896c27dac | ||
|
|
58816275e5 | ||
|
|
a39092497e | ||
|
|
eefdc74e9a | ||
|
|
4b38c1ef00 | ||
|
|
2bb7552b85 | ||
|
|
798f1423c3 | ||
|
|
ee4832323d | ||
|
|
758238a82e | ||
|
|
e661e1d7f2 | ||
|
|
068f1b1817 | ||
|
|
26897ccb15 | ||
|
|
b1a38e0a6d | ||
|
|
7ee308dec6 | ||
|
|
da9f942857 | ||
|
|
8f018732d3 | ||
|
|
68ac6b0185 | ||
|
|
62a2f127b4 | ||
|
|
3a3e1dde90 | ||
|
|
6b84eb0290 | ||
|
|
4d823b354f | ||
|
|
485e55e0b8 | ||
|
|
56cfeb732e | ||
|
|
e6cb9d67ee | ||
|
|
809823a9b0 | ||
|
|
b9abbafefc | ||
|
|
eff713a2a9 | ||
|
|
33f357d8da | ||
|
|
58cbd680e8 | ||
|
|
4121416743 | ||
|
|
a2ec5cc355 | ||
|
|
06309088f4 | ||
|
|
03c99b2cca | ||
|
|
014dc88112 | ||
|
|
efb9830e0b | ||
|
|
1bda03ee04 | ||
|
|
4b23e0c6c3 | ||
|
|
f08e856dfd | ||
|
|
7a1154363b | ||
|
|
f2f117aea1 | ||
|
|
ee3b15990d | ||
|
|
516ebeb0b7 | ||
|
|
2ce25751a9 | ||
|
|
8d44ce4852 | ||
|
|
7fa0b87e6e | ||
|
|
fca37ee699 | ||
|
|
39a02c6d6d | ||
|
|
3a888c54fe | ||
|
|
6a9399cf0b | ||
|
|
14a2510e12 | ||
|
|
85030e39fa | ||
|
|
de223f51f6 | ||
|
|
f11fadf14b | ||
|
|
9b8923c998 | ||
|
|
45aeff3e0f | ||
|
|
603e968ec7 | ||
|
|
66eb1e8d9a | ||
|
|
9b200912b2 | ||
|
|
35a6d5a063 | ||
|
|
577417b054 | ||
|
|
443bd67f81 | ||
|
|
0feeede406 | ||
|
|
f6bf7e38c7 | ||
|
|
e54d914795 | ||
|
|
fc5fbd648e | ||
|
|
c4cd43b3d6 | ||
|
|
5890da10d2 | ||
|
|
f6369ff237 | ||
|
|
67ff06f64e | ||
|
|
295e505eef | ||
|
|
0e2ab869cb | ||
|
|
b0726b6c7e | ||
|
|
834fd60983 | ||
|
|
04f2f23931 | ||
|
|
c6cfe73e23 | ||
|
|
914cfceedf | ||
|
|
f1a836a407 | ||
|
|
c3dd59adde | ||
|
|
297301b701 | ||
|
|
0bd8ec7c98 | ||
|
|
21fa9431a2 | ||
|
|
892bf3e116 | ||
|
|
1b600d54c8 | ||
|
|
da06ebd1a5 | ||
|
|
0d48c526b8 | ||
|
|
6e48ec5fb7 | ||
|
|
9313d6e8c8 | ||
|
|
6083fe6151 | ||
|
|
4088e9ee75 | ||
|
|
a1f01bda0f | ||
|
|
504a2d4e0e | ||
|
|
72b50eaddb | ||
|
|
a39a839f41 | ||
|
|
6868bfe84a | ||
|
|
07913045e2 | ||
|
|
6c65b31fff | ||
|
|
2d89326362 | ||
|
|
b555d88aa7 | ||
|
|
5c43cf460f | ||
|
|
b3111235fc | ||
|
|
5084b438fc | ||
|
|
3d2fd2f70f | ||
|
|
27023547d2 | ||
|
|
2dcd771c15 | ||
|
|
f62f844134 | ||
|
|
149e8f0845 | ||
|
|
434e268d42 | ||
|
|
24f10b4683 | ||
|
|
6863e637aa | ||
|
|
b85e3b11a9 | ||
|
|
93f0f6b07d | ||
|
|
5e101f80b4 | ||
|
|
55f78dce6d | ||
|
|
304b9e0c32 | ||
|
|
db59c2e24a | ||
|
|
fd0ac182d2 | ||
|
|
286915b647 | ||
|
|
3f85f9b755 | ||
|
|
9d5846d0ce | ||
|
|
ba08b85225 | ||
|
|
774aa7d211 | ||
|
|
2997bf0911 | ||
|
|
af29756943 | ||
|
|
268a3024d8 | ||
|
|
2768667eb1 | ||
|
|
88bdcc9365 | ||
|
|
bb4fab8df5 | ||
|
|
39019e9c92 | ||
|
|
f4d086508d | ||
|
|
e23c89ae57 | ||
|
|
53a764abc8 | ||
|
|
4f07c88769 | ||
|
|
fa406d8989 | ||
|
|
1ca54f39f8 | ||
|
|
5867e90f69 | ||
|
|
e87b91f6eb | ||
|
|
4b52e61755 | ||
|
|
eb48212630 | ||
|
|
961662f483 | ||
|
|
306b1a75a5 | ||
|
|
7350864150 | ||
|
|
16d0758ea3 | ||
|
|
877a1b14e5 | ||
|
|
a122b37c81 | ||
|
|
ad137b329b | ||
|
|
85905a7045 | ||
|
|
9847f95c93 | ||
|
|
e9e13588f6 | ||
|
|
6c8a45f2dc | ||
|
|
e7d28f9bde | ||
|
|
33015351d6 | ||
|
|
0a461b2bd0 | ||
|
|
ecf1e78208 | ||
|
|
a8b9acf170 | ||
|
|
45a3138d28 | ||
|
|
7e997889aa | ||
|
|
6c51af7107 | ||
|
|
8be7beab9a | ||
|
|
62ec8d76c6 | ||
|
|
7a634797c4 | ||
|
|
575d59dad2 | ||
|
|
524d800a0a | ||
|
|
7b0e6200d6 | ||
|
|
65d011c9aa | ||
|
|
dc3604f144 | ||
|
|
1eb2dd4efa | ||
|
|
f4d345b902 | ||
|
|
a38837c61b | ||
|
|
b50be7e825 | ||
|
|
74f45bee28 | ||
|
|
47b3b0c708 | ||
|
|
9c390f3026 | ||
|
|
069aac5723 | ||
|
|
9335cd73d5 | ||
|
|
e90beffb44 | ||
|
|
55a06dbe1d | ||
|
|
ae07159afe | ||
|
|
1fff655ef1 | ||
|
|
3815f9cf0a | ||
|
|
e36490c89e | ||
|
|
d4f02f971f | ||
|
|
ec326d9a11 | ||
|
|
6e4df0ef87 | ||
|
|
3aa0411676 | ||
|
|
9f20c126be | ||
|
|
2707e8c9f4 | ||
|
|
0d467d8ad1 | ||
|
|
66be09c98e | ||
|
|
60e9a427a5 | ||
|
|
f72bdb7c45 | ||
|
|
a53188e697 | ||
|
|
0dd63b9b7a | ||
|
|
513e300676 | ||
|
|
dd28859a9f | ||
|
|
ecc43edbba | ||
|
|
bf8ab423e3 | ||
|
|
8116a546b5 | ||
|
|
5c512367e2 | ||
|
|
e15ea561bc | ||
|
|
9e4332314a | ||
|
|
880d9eae20 | ||
|
|
72fcd791db | ||
|
|
39960b15ed | ||
|
|
395b9a4c5b | ||
|
|
5a2753a50b | ||
|
|
57c5c19cca | ||
|
|
c64ed36fa4 | ||
|
|
754981c67f | ||
|
|
5c22986e2e | ||
|
|
1746920f61 | ||
|
|
d33380d2a0 | ||
|
|
889ed9e6be | ||
|
|
2b0e0f7d4e | ||
|
|
2314d7ef18 | ||
|
|
bec3a5ee3b | ||
|
|
c738a9a4da | ||
|
|
e7c8706b1b | ||
|
|
f725adf5ba | ||
|
|
7bb1b08bdb | ||
|
|
d1e7ef1fce | ||
|
|
08f7e1275e | ||
|
|
529690ec60 | ||
|
|
59431a7eaf | ||
|
|
23b5ffbb92 | ||
|
|
9b8594c040 | ||
|
|
3e082ed06e | ||
|
|
22fe50f80a | ||
|
|
885cd71b80 | ||
|
|
8c6805ec7c | ||
|
|
1fc0b41fca | ||
|
|
7e26744e2e | ||
|
|
1d9ef7e634 | ||
|
|
1be49a75b1 | ||
|
|
b6b7587f0a | ||
|
|
e3017c25a9 | ||
|
|
d914e69e7a | ||
|
|
edf9f11048 | ||
|
|
44afd138c5 | ||
|
|
d2b93a7110 | ||
|
|
ff726485ba | ||
|
|
de46cd899e | ||
|
|
37263bb239 | ||
|
|
d21a38f42f | ||
|
|
9b9a7537f1 | ||
|
|
56d8c65008 | ||
|
|
fc24db7d59 | ||
|
|
0867dcc6da | ||
|
|
b9f57edb92 | ||
|
|
7ebbba3cf2 | ||
|
|
5188734c8b | ||
|
|
4e476a75ca | ||
|
|
7a037a3254 | ||
|
|
f6b44ac905 | ||
|
|
deecc128be | ||
|
|
18f2b5824e | ||
|
|
45084bab70 | ||
|
|
6cdbe38717 | ||
|
|
fe0d0c2590 | ||
|
|
44f61c7c91 | ||
|
|
af8b7d6824 | ||
|
|
170583f8fe | ||
|
|
648a35e4d7 | ||
|
|
99849063d9 | ||
|
|
5b22007a1e | ||
|
|
80cf89c9d5 | ||
|
|
45d37ea957 | ||
|
|
f935c38571 | ||
|
|
bfc7c2b8b7 | ||
|
|
08f90de683 | ||
|
|
32748e3182 | ||
|
|
7a280bc7c6 | ||
|
|
f4ae688527 | ||
|
|
ca45d0a278 | ||
|
|
8f66fcf2a6 | ||
|
|
30549ac5af | ||
|
|
06ab14a6c9 | ||
|
|
72e3570dc4 | ||
|
|
724169b059 | ||
|
|
6e58f5eebb | ||
|
|
96b3d7cc2f | ||
|
|
74c9eada47 | ||
|
|
9d65889824 | ||
|
|
98451c8fc7 | ||
|
|
6b47eef4cd | ||
|
|
158ec65e57 | ||
|
|
c4bbd7c545 | ||
|
|
cb323eb715 | ||
|
|
1aa23aa14a | ||
|
|
41abc19a97 | ||
|
|
99e03dde06 | ||
|
|
9d73c5475e | ||
|
|
25ae9a045f | ||
|
|
dabe66819d | ||
|
|
4ad7b97764 | ||
|
|
083aeb42ec | ||
|
|
a13d1d0692 | ||
|
|
c12645ab4d | ||
|
|
116c99e55c | ||
|
|
43b65d6b2e | ||
|
|
bc7a0e46b7 | ||
|
|
455962bd94 | ||
|
|
86c515bcb8 | ||
|
|
f09067a142 | ||
|
|
8ce24df304 | ||
|
|
279a59d019 | ||
|
|
d88e286696 | ||
|
|
d07aa2a72d | ||
|
|
bf30872439 | ||
|
|
12dc4eb460 | ||
|
|
ab67d1ff35 | ||
|
|
86809d8bf8 | ||
|
|
85adf70394 | ||
|
|
290e7f3835 | ||
|
|
e9e0eed871 | ||
|
|
4ab0f632b7 | ||
|
|
1a33834b3f | ||
|
|
1d367a58d5 | ||
|
|
ec4ce66bcc | ||
|
|
4a0f5eadc2 | ||
|
|
f955f8786b | ||
|
|
38f6e49d00 | ||
|
|
4394d832b8 | ||
|
|
9e68380814 | ||
|
|
c8bb95a852 | ||
|
|
9c2de84ee4 | ||
|
|
d4b1c2c883 | ||
|
|
550d9e1c87 | ||
|
|
3f687a7233 | ||
|
|
c1369ca0ff | ||
|
|
76c8bce7b4 | ||
|
|
e0ae9b8e4f | ||
|
|
9de573c15e | ||
|
|
716c23fed3 | ||
|
|
8e22bd06e9 | ||
|
|
b78929745b | ||
|
|
6986f2186c | ||
|
|
3743ac18f5 | ||
|
|
13f1d40ca3 | ||
|
|
63f28097bd | ||
|
|
ff7fec1de4 | ||
|
|
f65b2850a2 | ||
|
|
3a999358b9 | ||
|
|
d3e195df9f | ||
|
|
dbf8e634d9 | ||
|
|
2b810ca32d | ||
|
|
0adfc45d36 | ||
|
|
2a54d9b889 | ||
|
|
4150c31b98 | ||
|
|
803e8f2bff | ||
|
|
b215345f11 | ||
|
|
98d35e0412 | ||
|
|
437e8e4d25 | ||
|
|
e05085ddf0 | ||
|
|
495b5de38c | ||
|
|
c2b89dd199 | ||
|
|
f15b1e5dfc | ||
|
|
e308a1b5d9 | ||
|
|
1e698440f9 | ||
|
|
98bf7e5387 | ||
|
|
e1f8ac59cb | ||
|
|
0a2b6b71ca | ||
|
|
5daae7b47c | ||
|
|
4951fc9b80 | ||
|
|
0bf8182728 | ||
|
|
a512d82793 | ||
|
|
4f305a8705 | ||
|
|
46daa4cb79 | ||
|
|
485363d7a2 | ||
|
|
c8d2d9d4fd | ||
|
|
0880213342 | ||
|
|
8d4fb74173 | ||
|
|
f10adb3383 | ||
|
|
3be33820d9 | ||
|
|
83f638c64f | ||
|
|
35d77a3925 | ||
|
|
a33da0c750 | ||
|
|
cd2350140d | ||
|
|
bd125b249b | ||
|
|
33b9a15f73 | ||
|
|
0e55fc7f7e | ||
|
|
fc0bbc57cb | ||
|
|
bc46149573 | ||
|
|
be8598bcaa | ||
|
|
ace1936a78 | ||
|
|
e5649d03b2 | ||
|
|
426d6bae3f | ||
|
|
5d35df65f3 | ||
|
|
f91f5e3cba | ||
|
|
1c66f8c6ca | ||
|
|
a42902af0d | ||
|
|
a871055d96 | ||
|
|
64fcb9595c | ||
|
|
9d9cff977d | ||
|
|
5724f37ea6 | ||
|
|
3ca93b49ca | ||
|
|
b2248818dd | ||
|
|
a702fbbe86 | ||
|
|
1002e502d5 | ||
|
|
17a8adae05 | ||
|
|
4fca050623 | ||
|
|
f371c176b6 | ||
|
|
1c6772f383 | ||
|
|
7d2f28084b | ||
|
|
923b8d7444 | ||
|
|
49e4eb3ef3 | ||
|
|
2c481c0d15 | ||
|
|
f2e854c935 | ||
|
|
8045e0bfaf | ||
|
|
40e176ec28 | ||
|
|
1ab135e989 | ||
|
|
99c14ae9e0 | ||
|
|
00d859734d | ||
|
|
de4efff558 | ||
|
|
2ed3f4aa40 | ||
|
|
ba59c8ee66 | ||
|
|
fb97e22dee | ||
|
|
b0e7283d2b | ||
|
|
e341564eee | ||
|
|
730006977c | ||
|
|
ea26a5722a | ||
|
|
62742d92d2 | ||
|
|
50b9983534 | ||
|
|
34ea8302ef | ||
|
|
a8fd0bbdef | ||
|
|
99e6243e25 | ||
|
|
0f6c767143 | ||
|
|
eb0b932c44 | ||
|
|
1e4be0f3fe | ||
|
|
47caa1b118 | ||
|
|
641609e187 | ||
|
|
006fb6a611 | ||
|
|
a0e4ee1d00 | ||
|
|
27d770c759 | ||
|
|
fe39b5120b | ||
|
|
74df27befd | ||
|
|
c92a1e430d | ||
|
|
642d22578f | ||
|
|
140ec1b380 | ||
|
|
17090c177e | ||
|
|
d2721cbd4b | ||
|
|
f80509c886 | ||
|
|
6c85796abe | ||
|
|
e82cbbf117 | ||
|
|
bacdc97d90 | ||
|
|
1e43c70adf | ||
|
|
16bb6e90a9 | ||
|
|
cf6882c579 | ||
|
|
858d0b3361 | ||
|
|
208a5433b7 | ||
|
|
9a7c9604fe | ||
|
|
f44fa4df31 | ||
|
|
68a985a09f | ||
|
|
6b329aaa58 | ||
|
|
80798ae0e6 | ||
|
|
cd61e2c92d | ||
|
|
c943510828 | ||
|
|
9291d330dd | ||
|
|
a856d9e2f2 | ||
|
|
41f9436465 | ||
|
|
5233c14725 | ||
|
|
3d42d949af | ||
|
|
aec1dc770b | ||
|
|
f335c17f0d | ||
|
|
dfe2c466bc | ||
|
|
bb924531ef | ||
|
|
6e24b6fa88 | ||
|
|
d9a7c197fc | ||
|
|
14d30bae9d | ||
|
|
97ef1904bb | ||
|
|
0856aea069 | ||
|
|
73e9253cb7 | ||
|
|
663e97db97 | ||
|
|
8b64266602 | ||
|
|
e697cc5ec8 | ||
|
|
2c4d5a42d8 | ||
|
|
2f2ec23a53 | ||
|
|
3ad72f39a6 | ||
|
|
88a074dfa9 | ||
|
|
8c5f956dfe | ||
|
|
505eed8767 | ||
|
|
cc9a3d3551 | ||
|
|
70b743e364 | ||
|
|
6307b7f278 | ||
|
|
ed2ee7ad90 | ||
|
|
26789f4187 | ||
|
|
923fc20ca5 | ||
|
|
6808a37b69 | ||
|
|
f996e65a4d | ||
|
|
0168f6b9b7 | ||
|
|
e6b8643124 | ||
|
|
69a92712ef | ||
|
|
93804cec3f | ||
|
|
8cd430efc7 | ||
|
|
8175c946f2 | ||
|
|
0eb70b6208 | ||
|
|
5ac3378479 | ||
|
|
356b208f33 | ||
|
|
924f03595b | ||
|
|
90595968b2 | ||
|
|
96a59ca429 | ||
|
|
b76d6cd987 | ||
|
|
98b7224c65 | ||
|
|
ece73f63c8 | ||
|
|
b281ae9583 | ||
|
|
6af7e1edd2 | ||
|
|
f04ca2bdde | ||
|
|
9007d8c484 | ||
|
|
d8a7e7a39d | ||
|
|
ee13581475 | ||
|
|
8c86a3013a | ||
|
|
8aab81acc7 | ||
|
|
8707190d16 | ||
|
|
aa72cab84f | ||
|
|
29607a8617 | ||
|
|
2239ab9019 | ||
|
|
86ea8a8e26 | ||
|
|
bfa8d8e691 | ||
|
|
dc788b14eb | ||
|
|
228a87038e | ||
|
|
d11ee2a4cf | ||
|
|
b38c940dd3 | ||
|
|
6a22d355eb | ||
|
|
a3a46fb0bb | ||
|
|
452339a1f0 | ||
|
|
5e93f1e8bd | ||
|
|
2e4023cfd6 | ||
|
|
12c748534e | ||
|
|
8275bdbc25 | ||
|
|
0ffaf99e10 | ||
|
|
b1b76feeb0 | ||
|
|
265616ff80 | ||
|
|
89c65f6226 | ||
|
|
6634b36f4a | ||
|
|
6c433d83fc | ||
|
|
a9d77a0005 | ||
|
|
efb35b1aa7 | ||
|
|
6f9d21fc21 | ||
|
|
f7df03f0c0 | ||
|
|
e92ff43e09 | ||
|
|
592a52ef0b | ||
|
|
4e19066e26 | ||
|
|
8e39e83c38 | ||
|
|
78900defad | ||
|
|
d0b78e7c73 | ||
|
|
d3675f269d | ||
|
|
64d25f416a | ||
|
|
3f8b38a1ce | ||
|
|
5979acc31e | ||
|
|
7397927b77 | ||
|
|
0d67282c00 | ||
|
|
f9939023c6 | ||
|
|
922d76dace | ||
|
|
02bf674539 | ||
|
|
4ad315b3f7 | ||
|
|
342fa0465c | ||
|
|
2866badc04 | ||
|
|
60194d8db8 | ||
|
|
165745bb7d | ||
|
|
fc4f3eebb6 | ||
|
|
8a6bd97ec2 | ||
|
|
c4ee48ad1e | ||
|
|
3e5aa77ff5 | ||
|
|
b45b0f6a2a | ||
|
|
fc77c0fdf1 | ||
|
|
bf1e96a303 | ||
|
|
86ba3e9bf4 | ||
|
|
d0fb1fc0f1 | ||
|
|
8bf4939ac1 | ||
|
|
75a653a5c7 | ||
|
|
e713ffa52d | ||
|
|
abc3efc16e | ||
|
|
25e20f9299 | ||
|
|
f2effc4253 | ||
|
|
fb03dba60c | ||
|
|
68e57d5ec4 | ||
|
|
a1691a77cf | ||
|
|
aa5651a464 | ||
|
|
0a79fb6196 | ||
|
|
7e108d707a | ||
|
|
fb850fb040 | ||
|
|
95292b8213 | ||
|
|
e59dca4bec | ||
|
|
86e47c722d | ||
|
|
5a1127d2b9 | ||
|
|
b1d4df4309 | ||
|
|
f6f70776d9 | ||
|
|
570777db7e | ||
|
|
b06b42f0e9 | ||
|
|
8a170a7e16 | ||
|
|
5e95634173 | ||
|
|
84cc3490a1 | ||
|
|
ee724d372d | ||
|
|
711578d0a2 | ||
|
|
1956416fb6 | ||
|
|
1ec1f4f75f | ||
|
|
0fb192d79e | ||
|
|
19847652a9 | ||
|
|
d807334141 | ||
|
|
a08a233484 | ||
|
|
7fc9676385 | ||
|
|
7d8cdc79f2 | ||
|
|
6938299b72 | ||
|
|
6794091919 | ||
|
|
6631ebc12a | ||
|
|
efc800c223 | ||
|
|
6756b6d741 | ||
|
|
3138e36137 | ||
|
|
b3b1c18ff5 | ||
|
|
1c9cd46d88 | ||
|
|
f4074fb939 | ||
|
|
7d286337ce | ||
|
|
81f92b8b0f | ||
|
|
01337ddcbf | ||
|
|
3fb545f869 | ||
|
|
04e59482b5 | ||
|
|
50acd26804 | ||
|
|
1bc8e698b4 | ||
|
|
46b8b67fed | ||
|
|
84a653b5d5 | ||
|
|
dee7212531 | ||
|
|
50b0593648 | ||
|
|
4c786406c3 | ||
|
|
6fed12e9ad | ||
|
|
11216350c4 | ||
|
|
577dbdeb7c | ||
|
|
b75f77b31c | ||
|
|
2776e1b127 | ||
|
|
562cc4352b | ||
|
|
013d5a99a8 | ||
|
|
fbe7bb90c2 | ||
|
|
660326af42 | ||
|
|
80aa2dfc73 | ||
|
|
316d8f252a | ||
|
|
28a39d8dfc | ||
|
|
3fc6c194e5 | ||
|
|
0e85b40eba | ||
|
|
d479c8d44e | ||
|
|
6b6e677f8b | ||
|
|
45c9914efa | ||
|
|
a4299cab89 | ||
|
|
67e4ee4aca | ||
|
|
85750c9453 | ||
|
|
d8b4b3f2cf | ||
|
|
fedaef07eb | ||
|
|
9ae9a82cca | ||
|
|
37fec1821f | ||
|
|
c4e69bc036 | ||
|
|
7dd14282c6 | ||
|
|
7e72cc3d1b | ||
|
|
8e2da52d82 | ||
|
|
e38c861cb5 | ||
|
|
b6a571a90c | ||
|
|
26949d8083 | ||
|
|
1c80c2108d | ||
|
|
e1fc44d44a | ||
|
|
32e5c484ab | ||
|
|
bf8589f9ec | ||
|
|
b65d9e5a46 | ||
|
|
5aa4714d05 | ||
|
|
dd8500052e | ||
|
|
2d68f44a40 | ||
|
|
39ec90895e | ||
|
|
3d1d41e36b | ||
|
|
46dfc75724 | ||
|
|
906915e058 | ||
|
|
85418c678d | ||
|
|
81e06c45d3 | ||
|
|
645fbf0231 | ||
|
|
5d10dba3c7 | ||
|
|
78806c081b | ||
|
|
3f659ccf7d | ||
|
|
6cd52a3cdc | ||
|
|
a1f675419a | ||
|
|
9087c1b4e1 | ||
|
|
9418d55a2f | ||
|
|
87076df23f | ||
|
|
aba9603021 | ||
|
|
086aa3134b | ||
|
|
010691f3e5 | ||
|
|
12d9099543 | ||
|
|
c9b7f8d901 | ||
|
|
0372f82f2e | ||
|
|
18be9afbe1 | ||
|
|
36f5618376 | ||
|
|
03542368d7 | ||
|
|
36a02d1658 | ||
|
|
c65182b568 | ||
|
|
e5bd824506 | ||
|
|
10fbb610c3 | ||
|
|
b8ab58a196 | ||
|
|
fa59b5ba6c | ||
|
|
67d47abde2 | ||
|
|
042ff18e65 | ||
|
|
3008952240 | ||
|
|
aef28c7128 | ||
|
|
9c5dfe65c9 | ||
|
|
b12cdd673b | ||
|
|
8247580372 | ||
|
|
d7c7537aa9 | ||
|
|
ad05a1d4fa | ||
|
|
7bd819ad96 | ||
|
|
64b472d6bc | ||
|
|
3d7a906cce | ||
|
|
8c4a5a957a | ||
|
|
21874473cc | ||
|
|
8aa99b34d6 | ||
|
|
6759164c82 | ||
|
|
2445377d0a | ||
|
|
99b6393fbc | ||
|
|
7e049f3270 | ||
|
|
79d1e6b932 | ||
|
|
3403e3ea5c | ||
|
|
c3a3aa9c0d | ||
|
|
89c9ff144c | ||
|
|
82e6334832 | ||
|
|
dd70aa8969 | ||
|
|
4cf190239e | ||
|
|
cb4ba51eee | ||
|
|
4d03867550 | ||
|
|
02294e3afc | ||
|
|
f68aa1d931 | ||
|
|
fb390359e1 | ||
|
|
764c64b615 | ||
|
|
a167770848 | ||
|
|
551743abec | ||
|
|
68a596707b | ||
|
|
47da2d657e | ||
|
|
43372c94a8 | ||
|
|
0643274c68 | ||
|
|
395b471700 | ||
|
|
a040d56fdc | ||
|
|
3c8a65c540 | ||
|
|
2f38643473 | ||
|
|
906a2e05eb | ||
|
|
3390e24534 | ||
|
|
dc28b892bc | ||
|
|
3023cd139d | ||
|
|
127135e1cb | ||
|
|
aa4e115f80 | ||
|
|
90f204b833 | ||
|
|
77427a8ec4 | ||
|
|
eafcdb01c2 | ||
|
|
3f58086aa1 | ||
|
|
d36a4dbdbe | ||
|
|
6879aa96e2 | ||
|
|
fc219cc7f3 | ||
|
|
787f9362eb | ||
|
|
231ac5305f | ||
|
|
4bd79e370f | ||
|
|
5c0cd63da0 | ||
|
|
5a31543538 | ||
|
|
1b88bf8690 | ||
|
|
6db01e67c9 | ||
|
|
f7cb6576b3 | ||
|
|
4a71749d1c | ||
|
|
d13f457344 | ||
|
|
ab41401643 | ||
|
|
592b93ce2b | ||
|
|
5dc7b90a1c | ||
|
|
b1e0203527 | ||
|
|
774dde5767 | ||
|
|
c322cd8b48 | ||
|
|
3dcdd52eb7 | ||
|
|
fe17d97a05 | ||
|
|
b71a34b476 | ||
|
|
ea71c86f69 | ||
|
|
550fe605bd | ||
|
|
109dd97016 | ||
|
|
715f0a10f3 | ||
|
|
d01caea53e | ||
|
|
a9cab31a1f | ||
|
|
d0968e87d7 | ||
|
|
6417f780e6 | ||
|
|
fd4d76de93 | ||
|
|
cdf8663bd3 | ||
|
|
bf9050757b | ||
|
|
bce99da6e5 | ||
|
|
234925c850 | ||
|
|
c9b7a5f84c | ||
|
|
cfaeaa370a | ||
|
|
8cd5a89d6a | ||
|
|
090f8d5cc9 | ||
|
|
29ae8cfa7f | ||
|
|
338184712d | ||
|
|
b2c510d73e | ||
|
|
e8b649d315 | ||
|
|
6a5d5511f1 | ||
|
|
ec99ac3a3b | ||
|
|
58cfd8bc42 | ||
|
|
f676f12e02 | ||
|
|
393fe43090 | ||
|
|
296a50a6d2 | ||
|
|
b1ee3d8793 | ||
|
|
d930fdae78 | ||
|
|
993fbfe5a8 | ||
|
|
af546ef0f1 | ||
|
|
2b60e81a52 | ||
|
|
11418459b8 | ||
|
|
3b8b8e47da | ||
|
|
ae302d49ef | ||
|
|
0153229d3c | ||
|
|
eeb74dd6fd | ||
|
|
ecc62a3ba9 | ||
|
|
aca60465f0 | ||
|
|
57bdca37fc | ||
|
|
6409397770 | ||
|
|
974044adf0 | ||
|
|
59aeeda786 | ||
|
|
b6bf8d992f | ||
|
|
82cb966863 | ||
|
|
9d31edc67a | ||
|
|
1b4f4bdd6d | ||
|
|
c9a879d329 | ||
|
|
ea69b1be00 | ||
|
|
2a8b3a87ea | ||
|
|
52783734ce | ||
|
|
0e1b390ec6 | ||
|
|
ab49bdf82f | ||
|
|
25e7051271 | ||
|
|
85c0a72ae8 | ||
|
|
7315f9adfc | ||
|
|
af1aee4ac3 | ||
|
|
d6ff7eef88 | ||
|
|
7706b4dbc3 | ||
|
|
3561ecd2b7 | ||
|
|
74c7ff7fdf | ||
|
|
a68ce353ad | ||
|
|
0d168f3445 | ||
|
|
073e04a3cc | ||
|
|
591dc048d4 | ||
|
|
bfe8d3b37a | ||
|
|
9695dba415 | ||
|
|
6a48f0502e | ||
|
|
4a8d6592ab | ||
|
|
434aebd641 | ||
|
|
c124c38e70 | ||
|
|
e6d60bb124 | ||
|
|
99afb88957 | ||
|
|
085ea2028c | ||
|
|
0e7c600cf7 | ||
|
|
3f3532defe | ||
|
|
a0ffc88e47 | ||
|
|
11bfaf0e91 | ||
|
|
189093d548 | ||
|
|
34eb89b1b6 | ||
|
|
b725d242d3 | ||
|
|
568e88314b | ||
|
|
70e94205d1 | ||
|
|
aaad952782 | ||
|
|
dc27911174 | ||
|
|
42678ae8e1 | ||
|
|
b6341992f6 | ||
|
|
17fd0f96df | ||
|
|
0b039e8d8c | ||
|
|
eab8a6d96b | ||
|
|
9f72bf117e | ||
|
|
058e9436f0 | ||
|
|
ebd8fe0e4f | ||
|
|
7970f5f691 | ||
|
|
0689ca86c5 | ||
|
|
5783e0f182 | ||
|
|
e190aa8d03 | ||
|
|
15bac02cbf | ||
|
|
7fdacce9a2 | ||
|
|
78ea179e9a | ||
|
|
2cfc3fadb2 | ||
|
|
60cdee2ea6 | ||
|
|
a17ac6860d | ||
|
|
ef24466702 | ||
|
|
de78027b0e | ||
|
|
28c40a0ad2 | ||
|
|
230bbb2239 | ||
|
|
bda2913a32 | ||
|
|
78daaf6e5b | ||
|
|
8939167a39 | ||
|
|
b48c6111d9 | ||
|
|
de1e919b96 | ||
|
|
35a96027f1 | ||
|
|
8e12d71580 | ||
|
|
7a36bfa2ff | ||
|
|
dfeac0cb7f | ||
|
|
dfec780157 | ||
|
|
4cc93324f8 | ||
|
|
bf8833eae6 | ||
|
|
00028010ee | ||
|
|
a681a76c4b | ||
|
|
cce812ff11 | ||
|
|
ccb4bd595c | ||
|
|
dc7eedcd8f | ||
|
|
40faa78929 | ||
|
|
eb53db00d0 | ||
|
|
658ef39736 | ||
|
|
0e90bcb2ef | ||
|
|
a020fce2d7 | ||
|
|
525ccf7aba | ||
|
|
ebcfc16f63 | ||
|
|
4b825efffb | ||
|
|
71871016d2 | ||
|
|
c07ef23d5b | ||
|
|
cbe884ad63 | ||
|
|
1dd703b337 | ||
|
|
bcb1d98191 | ||
|
|
1f5486e8cc | ||
|
|
1371be6f2c | ||
|
|
b0d8ed6248 | ||
|
|
379f4585e3 | ||
|
|
45a71981c8 | ||
|
|
ac3409f644 | ||
|
|
11c33f81dd | ||
|
|
641cc860d8 | ||
|
|
2029bd6e8a | ||
|
|
1ca5e1218d | ||
|
|
887c704f63 | ||
|
|
567dc74cd7 | ||
|
|
53606d5055 | ||
|
|
19a6c21c44 | ||
|
|
3a4b11e4b3 | ||
|
|
d981574ab2 | ||
|
|
eb817739b9 | ||
|
|
8972f17638 | ||
|
|
4d997d9473 | ||
|
|
e79c86c7b6 | ||
|
|
96bb86536f | ||
|
|
2daf6c8b3f | ||
|
|
24b46158de | ||
|
|
63d336ba28 | ||
|
|
8dd3a92671 | ||
|
|
8e8897c70d | ||
|
|
eac513411e | ||
|
|
98f9274085 | ||
|
|
884dd9fc3b | ||
|
|
04c564936a | ||
|
|
957138960a | ||
|
|
58a0297915 | ||
|
|
61bf536207 | ||
|
|
4136a7b372 | ||
|
|
b3afc9961d | ||
|
|
27e2e042da | ||
|
|
cc659bad30 | ||
|
|
776931c0ad | ||
|
|
e22b4b6fe1 | ||
|
|
cad534bffe | ||
|
|
3b44adb6fa | ||
|
|
cc0bd1473f | ||
|
|
58124d27bf | ||
|
|
9ad118814a | ||
|
|
270f8dd093 | ||
|
|
fde0ae4781 | ||
|
|
b87ce8c75d | ||
|
|
e372c87836 | ||
|
|
f8478d7742 | ||
|
|
a3d6923fec | ||
|
|
7bfa22187d | ||
|
|
7a8affae32 | ||
|
|
e48dd7c4c4 | ||
|
|
6d298c1746 | ||
|
|
4fe9ab92e0 | ||
|
|
f2ac4cdc64 | ||
|
|
be902401d1 | ||
|
|
020e93bca2 | ||
|
|
61dfdc2258 | ||
|
|
a98e3c8eab | ||
|
|
58a90ff706 | ||
|
|
d0bf12f3d4 | ||
|
|
af16785bbf | ||
|
|
18f6e83a2c | ||
|
|
147d97421b | ||
|
|
dcf2feb7aa | ||
|
|
e63e940b96 | ||
|
|
68b0fb83ee | ||
|
|
f15e5f060a | ||
|
|
e2a403441f | ||
|
|
9b3665b9c6 | ||
|
|
f4c8bf9206 | ||
|
|
c033db9c31 | ||
|
|
9300526f49 | ||
|
|
b6dd1f7f2d | ||
|
|
1e6eddca04 | ||
|
|
19ca0d7dd7 | ||
|
|
7fcb5d1c0c | ||
|
|
dd995ec28a | ||
|
|
af99de3535 | ||
|
|
3567cc2fb0 | ||
|
|
5e9d9bd264 | ||
|
|
62447c1bb9 | ||
|
|
a86434c9a2 | ||
|
|
b7b70299f7 | ||
|
|
eb02e0aec1 | ||
|
|
7816e53db7 | ||
|
|
7ae08e572a | ||
|
|
47d15d8adb | ||
|
|
0dac0e87e4 | ||
|
|
b290099d5b | ||
|
|
f6b1a41a0b | ||
|
|
e529af04cf | ||
|
|
6c365dd949 | ||
|
|
32f15fc557 | ||
|
|
6f06af1d5f | ||
|
|
a91f99faab | ||
|
|
17a87b23a1 | ||
|
|
d4b5ec2ae2 | ||
|
|
1bcaf09855 | ||
|
|
fa3842b2b4 | ||
|
|
ea72256e56 | ||
|
|
be5828cb3e | ||
|
|
104b72505c | ||
|
|
23728a0686 | ||
|
|
eaaa856b13 | ||
|
|
01e81d02b5 | ||
|
|
9ae8d0a512 | ||
|
|
da16322fb8 | ||
|
|
a6d39afc26 | ||
|
|
1b6a124f54 | ||
|
|
8aff6bf8f3 | ||
|
|
21115752aa | ||
|
|
025c2b88b9 | ||
|
|
655f9d7c97 | ||
|
|
03b5e36114 | ||
|
|
9c78c1e9ca | ||
|
|
136186a526 | ||
|
|
1f6b02a671 | ||
|
|
76a0518d52 | ||
|
|
a688a69af6 | ||
|
|
b890ffc5f3 | ||
|
|
b98558107e | ||
|
|
3e360ad0fb | ||
|
|
a7ced407f8 | ||
|
|
45a1e137d1 | ||
|
|
a44e0d81cc | ||
|
|
749fc6699c | ||
|
|
d7bb147a23 | ||
|
|
08b0b254f2 | ||
|
|
5414a4c3f1 | ||
|
|
7944ab329d | ||
|
|
8ca14a434c | ||
|
|
e24ba68fec | ||
|
|
b205326782 | ||
|
|
daa1960dff | ||
|
|
894c7f6972 | ||
|
|
737dcc76b8 | ||
|
|
b42f17916b | ||
|
|
2c6ce6fd07 | ||
|
|
f243f4b8ea | ||
|
|
02e881ebc0 | ||
|
|
7b8f10a14e | ||
|
|
d995c31b7b | ||
|
|
45154cb55c | ||
|
|
48067b10cd | ||
|
|
f24c40d780 | ||
|
|
f4edd36744 | ||
|
|
a202a6abdc | ||
|
|
c25adfe28a | ||
|
|
822c6cc45c | ||
|
|
c48b44d06a | ||
|
|
21a59fbd35 | ||
|
|
cdcf940d12 | ||
|
|
6cefc44fb6 | ||
|
|
8308f159ad | ||
|
|
e5ff8f2164 | ||
|
|
a000252c8a | ||
|
|
9a42c580cf | ||
|
|
6e0a7a742e | ||
|
|
026bac121f | ||
|
|
8a26b817f7 | ||
|
|
528a4435a9 | ||
|
|
9b791bb7a7 | ||
|
|
58eb0b1292 | ||
|
|
39eda4a4be | ||
|
|
87a6445ff6 | ||
|
|
b7e6d33d48 | ||
|
|
52e9cd20ee | ||
|
|
b34bd5d988 | ||
|
|
58d5e7bc58 | ||
|
|
1e09bfbb80 | ||
|
|
91fa1c066c | ||
|
|
10d49b55d1 | ||
|
|
67d0bf6a27 | ||
|
|
d36cbde7aa | ||
|
|
016f4088d4 | ||
|
|
c2d1bbb4be | ||
|
|
bc6a01444a | ||
|
|
24f0902194 | ||
|
|
19fd6a10dd | ||
|
|
a45a5296e4 | ||
|
|
1459bb15c1 | ||
|
|
ce5f3c463b | ||
|
|
04ebbf35e7 | ||
|
|
ba1d6e30e2 | ||
|
|
e9ac8933f9 | ||
|
|
ec96b7f878 | ||
|
|
ba7d429178 | ||
|
|
79be2089be | ||
|
|
ea2305de47 | ||
|
|
635b2c35a8 | ||
|
|
afdc4bb3f1 | ||
|
|
3d49ec72ba | ||
|
|
520f818adc | ||
|
|
20b054472e | ||
|
|
21a70c5655 | ||
|
|
08270f5d6d | ||
|
|
9923be2f1d | ||
|
|
5df57c95d4 | ||
|
|
d8c91466d0 | ||
|
|
37b2e370fe | ||
|
|
98dfba0640 | ||
|
|
574c9084c2 | ||
|
|
9862ae8e7a | ||
|
|
7bc4e63209 | ||
|
|
ad40150cfa | ||
|
|
970530d99f | ||
|
|
c6ae989b82 | ||
|
|
a0ebf354b1 | ||
|
|
97ecbc792e | ||
|
|
5934a59489 | ||
|
|
ee18046581 | ||
|
|
1e4e38c02f | ||
|
|
7a3038eceb | ||
|
|
7d2f86b72e | ||
|
|
3aaa319505 | ||
|
|
f4075f0a51 | ||
|
|
59643a8d52 | ||
|
|
baf2a880e4 | ||
|
|
d71e8320c7 | ||
|
|
3b3d924f40 | ||
|
|
60701bc007 | ||
|
|
5ca3e184e0 | ||
|
|
2903d1a0a6 | ||
|
|
5f0f1802a4 | ||
|
|
5be56d9b00 | ||
|
|
d3fd1b5045 | ||
|
|
4615e87483 | ||
|
|
fb9b72deea | ||
|
|
5cc873a6bb | ||
|
|
26745496a5 | ||
|
|
4537725852 | ||
|
|
676bda22ae | ||
|
|
87028e5f35 | ||
|
|
912083d20f | ||
|
|
4b6044dade | ||
|
|
871d3b537d | ||
|
|
01f8a514a1 | ||
|
|
7dbd77d2a4 | ||
|
|
94dde53c21 | ||
|
|
e47eaa3ac3 | ||
|
|
4060299204 | ||
|
|
70a99927cd | ||
|
|
688cee9463 | ||
|
|
fa3e300c37 | ||
|
|
9dbad050fd | ||
|
|
87202115d0 | ||
|
|
abc4cb444a | ||
|
|
78744840eb | ||
|
|
46cfe45cf6 | ||
|
|
91ff41a9d9 | ||
|
|
3869da9d06 | ||
|
|
2530691347 | ||
|
|
c65de27e9d | ||
|
|
9bb715fcb2 | ||
|
|
8c4f39ab49 | ||
|
|
e5206091dd | ||
|
|
e53c6fe64c | ||
|
|
b893b24d88 | ||
|
|
c7caa55223 | ||
|
|
c675019ef3 | ||
|
|
f2fea0a402 | ||
|
|
aeafd631d5 | ||
|
|
aa442632ea | ||
|
|
2a4b5f88a7 | ||
|
|
0e36010937 | ||
|
|
8a06825871 | ||
|
|
ce2f1bcde6 | ||
|
|
c59826471c | ||
|
|
9c8f64f602 | ||
|
|
d4342a77c8 | ||
|
|
054d2296c4 | ||
|
|
ae116981ef | ||
|
|
becb886ab7 | ||
|
|
3502ac9263 | ||
|
|
c64cb94b7d | ||
|
|
dffb28816d | ||
|
|
6e521f07c4 | ||
|
|
c05313aefc | ||
|
|
db0ca1cb9b | ||
|
|
584a73255a | ||
|
|
3a66385059 | ||
|
|
7e4f83c2f5 | ||
|
|
9971c82e99 | ||
|
|
b418525b3b | ||
|
|
f5c28107d4 | ||
|
|
793f928e6e | ||
|
|
a8fb2e37d5 | ||
|
|
11fed9394a | ||
|
|
bcdd0c66c0 | ||
|
|
e988215d8a | ||
|
|
f616643335 | ||
|
|
816f93222b | ||
|
|
9e85b1240a | ||
|
|
c5059b4568 | ||
|
|
2ae304dcf5 | ||
|
|
ffc15aa57a | ||
|
|
c9c02daff7 | ||
|
|
f6f3e2377e | ||
|
|
ed3226e3a0 | ||
|
|
8f5200e79d | ||
|
|
2753552997 | ||
|
|
a061ea7125 | ||
|
|
068d57f382 | ||
|
|
c76a635414 | ||
|
|
1b5f6b4683 | ||
|
|
891adf3397 | ||
|
|
190e7e76d3 | ||
|
|
eb47d5139f | ||
|
|
288910e67e | ||
|
|
1a0feb4f19 | ||
|
|
1cba7d3e1d | ||
|
|
0ff99b716c | ||
|
|
a537e785fb | ||
|
|
dbbc5ea127 | ||
|
|
3e6713d16c | ||
|
|
a86aa8034d | ||
|
|
c292ecd54b | ||
|
|
7bfdfd3c38 | ||
|
|
3f154e3a53 | ||
|
|
06ba8372d0 | ||
|
|
eae9b66eed | ||
|
|
4caca9b06a | ||
|
|
de772d7ddd | ||
|
|
707472ba27 | ||
|
|
46265debc3 | ||
|
|
14d221bf9c | ||
|
|
f4f871002b | ||
|
|
bb92d4aa54 | ||
|
|
acc7253ca1 | ||
|
|
f1002b5f9d | ||
|
|
84cea8e3d6 | ||
|
|
05a0f6fa62 | ||
|
|
2ab2f070b4 | ||
|
|
103de2011b | ||
|
|
3cec93e2f1 | ||
|
|
a23d7ee6e2 | ||
|
|
42afbcf655 | ||
|
|
c61167bdfa | ||
|
|
642cefb84f | ||
|
|
1223aa7c70 | ||
|
|
0d90977917 | ||
|
|
7e57e85e75 | ||
|
|
8f518954aa | ||
|
|
26144815d2 | ||
|
|
21d07be7df | ||
|
|
04f69f0853 | ||
|
|
0678c81959 | ||
|
|
70a88088f2 | ||
|
|
f8e8c756ab | ||
|
|
6e26fa6e3c | ||
|
|
b54d6e785d | ||
|
|
5d2e22cd86 | ||
|
|
ccd1d3f460 | ||
|
|
964486a819 | ||
|
|
9080f1d2f0 | ||
|
|
aab8dfcde5 | ||
|
|
1633b74ab2 | ||
|
|
bafd67be36 | ||
|
|
8f1d7d081d | ||
|
|
66bfcd1d45 | ||
|
|
d89438a15f | ||
|
|
d77fa5245d | ||
|
|
67115dd8d8 | ||
|
|
cefe125304 | ||
|
|
5a98b3d0bb |
67
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
67
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
name: 🐞 Bug Report
|
||||||
|
description: Use this form if you think you found a bug / Verwende dieses Formular, wenn Du denkst, dass Du einen Fehler gefunden hast.
|
||||||
|
labels: bug
|
||||||
|
body:
|
||||||
|
|
||||||
|
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thank you for taking your time to report a bug.
|
||||||
|
|
||||||
|
Before you proceed, please check:
|
||||||
|
- [ ] Do you use the [latest version](https://github.com/jomjol/AI-on-the-edge-device/releases)?
|
||||||
|
- [ ] Is there already another similar issue? Check for open and closed [Issue](https://github.com/jomjol/AI-on-the-edge-device/issues) and [Discussions](https://github.com/jomjol/AI-on-the-edge-device/discussions).
|
||||||
|
- [ ] Are instructions in the [README](https://github.com/jomjol/AI-on-the-edge-device/tree/master#readme) solving your issue?
|
||||||
|
- [ ] Are instructions in the [Wiki](https://github.com/jomjol/AI-on-the-edge-device/wiki) solving your issue?
|
||||||
|
|
||||||
|
Du darfst gerne auch in Deutsch schreiben!
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: The Problem
|
||||||
|
description: A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: Version
|
||||||
|
description: Which version are you using? (See menu `System > Info`).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: Logfile
|
||||||
|
description: Add the logfile (See menu `System > Log Viewer`) to help explain your problem. This will be automatically formatted into code, so no need to format it on your side.
|
||||||
|
render: shell
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Expected Behavior
|
||||||
|
description: A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Screenshots
|
||||||
|
description: If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Additional Context
|
||||||
|
description: Add any other context about the problem here.
|
||||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: 💬 Open a new Discussion
|
||||||
|
url: https://github.com/jomjol/AI-on-the-edge-device/discussions/categories/q-a
|
||||||
|
about: Use this form if you have a question or need help setting your AI-on-the-edge-device it up
|
||||||
23
.github/ISSUE_TEMPLATE/feature.yaml
vendored
Normal file
23
.github/ISSUE_TEMPLATE/feature.yaml
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
name: 💡 Feature Request
|
||||||
|
description: Use this form if you have an idea or wish for a new feature
|
||||||
|
labels: enhancement
|
||||||
|
body:
|
||||||
|
|
||||||
|
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thank you for taking your time to document your idea.
|
||||||
|
|
||||||
|
Before you proceed, please check:
|
||||||
|
- [ ] Is there already another similar issue open or closed? Check for open and closed [Issue](https://github.com/jomjol/AI-on-the-edge-device/issues) and [Discussions](https://github.com/jomjol/AI-on-the-edge-device/discussions).
|
||||||
|
- [ ] Is there already another similar [discussion](https://github.com/jomjol/AI-on-the-edge-device/discussions) ongoing?
|
||||||
|
|
||||||
|
Please be aware that we might decline your request if we have a reason for it!
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: The Feature
|
||||||
|
description: A clear and concise description of what the new feature should do. If possible, refer to other projects or add a mockup.
|
||||||
19
.github/ISSUE_TEMPLATE/training.yaml
vendored
Normal file
19
.github/ISSUE_TEMPLATE/training.yaml
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
name: 📖 New Training Data
|
||||||
|
description: Use this form if you collected images and want to provide them for training the model
|
||||||
|
title: New Training Data
|
||||||
|
labels: new training data
|
||||||
|
assignees: jomjol
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Before you proceed, please check:
|
||||||
|
- [ ] Did you make sure we don't have similar data yet in the training data? (see [Digital Counters](https://jomjol.github.io/neural-network-digital-counter-readout/) resp. [Analog Needles](https://jomjol.github.io/neural-network-analog-needle-readout))
|
||||||
|
- [ ] Did you follow the guideline as documented in the [Wiki](https://github.com/jomjol/AI-on-the-edge-device/wiki/ROI-Configuration)? We will only be able to accept the files if they fulfill the rules!
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: Files
|
||||||
|
description: You can drag & drop your **zipped** images here
|
||||||
8
.github/ISSUE_TEMPLATE/x_plain.yaml
vendored
Normal file
8
.github/ISSUE_TEMPLATE/x_plain.yaml
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
name: 🗒️ Blank Form
|
||||||
|
description: Use this form if none of the others fit. Please only use none of the others fit!
|
||||||
|
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Issue
|
||||||
|
|
||||||
98
.github/label-commenter-config.yml
vendored
Normal file
98
.github/label-commenter-config.yml
vendored
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
# Reply Bot Configuration
|
||||||
|
# See https://github.com/peaceiris/actions-label-commenter
|
||||||
|
# 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:
|
||||||
|
#######################################################################
|
||||||
|
# Bot Response: Logfile
|
||||||
|
#######################################################################
|
||||||
|
- name: bot-reply Logfile
|
||||||
|
labeled:
|
||||||
|
issue:
|
||||||
|
body: |
|
||||||
|
Please provide a logfile!
|
||||||
|
Make sure to first enable the `DEBUG` level in `Settings->Configuration->Debug->Logfile Log Level`!
|
||||||
|
Then wait until the issue arises again.
|
||||||
|
When you copy the log into here, please make sure to use **Fenced code blocks** by wrapping it into separate lines with ` ``` `, see https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-and-highlighting-code-blocks#fenced-code-blocks
|
||||||
|
discussion:
|
||||||
|
body: |
|
||||||
|
Please provide a logfile!
|
||||||
|
Make sure to first enable the `DEBUG` level in `Settings->Configuration->Debug->Logfile Log Level`!
|
||||||
|
Then wait until the issue arises again.
|
||||||
|
When you copy the log into here, please make sure to use **Fenced code blocks** by wrapping it into separate lines with ` ``` `, see https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-and-highlighting-code-blocks#fenced-code-blocks
|
||||||
|
|
||||||
|
|
||||||
|
#######################################################################
|
||||||
|
# Bot Response: Web Console
|
||||||
|
#######################################################################
|
||||||
|
- name: bot-reply Web Console
|
||||||
|
labeled:
|
||||||
|
issue:
|
||||||
|
body: |
|
||||||
|
You can use the [Web Console](https://jomjol.github.io/AI-on-the-edge-device/index.html) to get USB log from the device.
|
||||||
|
The USB log contains more information about the startup and operation of the device than the normal Web UI log
|
||||||
|
When you copy the log into herm, please make sure to use **Fenced code blocks** by wrapping it into separate lines with ` ``` `, see https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-and-highlighting-code-blocks#fenced-code-blocks
|
||||||
|
discussion:
|
||||||
|
body: |
|
||||||
|
You can use the [Web Console](https://jomjol.github.io/AI-on-the-edge-device/index.html) to get USB log from the device.
|
||||||
|
The USB log contains more information about the startup and operation of the device than the normal Web UI log
|
||||||
|
When you copy the log into herm, please make sure to use **Fenced code blocks** by wrapping it into separate lines with ` ``` `, see https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-and-highlighting-code-blocks#fenced-code-blocks
|
||||||
|
|
||||||
|
|
||||||
|
#######################################################################
|
||||||
|
# Bot Response: Properly Format Code
|
||||||
|
#######################################################################
|
||||||
|
- name: bot-reply Properly Format Code
|
||||||
|
labeled:
|
||||||
|
issue:
|
||||||
|
body: |
|
||||||
|
Please make sure to use **Fenced code blocks** by wrapping it into separate lines with ` ``` `, see https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-and-highlighting-code-blocks#fenced-code-blocks
|
||||||
|
This makes your code or log much easier to read!
|
||||||
|
discussion:
|
||||||
|
body: |
|
||||||
|
Please make sure to use **Fenced code blocks** by wrapping it into separate lines with ` ``` `, see https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-and-highlighting-code-blocks#fenced-code-blocks
|
||||||
|
This makes your code or log much easier to read!
|
||||||
|
|
||||||
|
|
||||||
|
#######################################################################
|
||||||
|
# Bot Response: Web Installer
|
||||||
|
#######################################################################
|
||||||
|
- name: bot-reply Web Installer
|
||||||
|
labeled:
|
||||||
|
issue:
|
||||||
|
body: |
|
||||||
|
You can use the [Web Installer](https://jomjol.github.io/AI-on-the-edge-device/index.html) install the firmware onto the ESP32.
|
||||||
|
discussion:
|
||||||
|
body: |
|
||||||
|
You can use the [Web Installer](https://jomjol.github.io/AI-on-the-edge-device/index.html) install the firmware onto the ESP32.
|
||||||
|
|
||||||
|
|
||||||
|
#######################################################################
|
||||||
|
# Bot Response: Rolling Build
|
||||||
|
#######################################################################
|
||||||
|
- name: bot-reply Rolling Build
|
||||||
|
labeled:
|
||||||
|
issue:
|
||||||
|
body: |
|
||||||
|
You can try the latest [Automatic Build](https://github.com/jomjol/AI-on-the-edge-device/actions/workflows/build.yaml?query=branch%3Arolling+event%3Apush) of the the `rolling` or any other branch. It might already contain a fix for your issue.
|
||||||
|
See the [documentation](https://jomjol.github.io/AI-on-the-edge-device-docs/rolling-installation) for additional information.
|
||||||
|
discussion:
|
||||||
|
body: |
|
||||||
|
You can try the latest [Automatic Build](https://github.com/jomjol/AI-on-the-edge-device/actions/workflows/build.yaml?query=branch%3Arolling+event%3Apush) of the the `rolling` or any other branch. It might already contain a fix for your issue.
|
||||||
|
See the [documentation](https://jomjol.github.io/AI-on-the-edge-device-docs/rolling-installation) for additional information.
|
||||||
|
|
||||||
|
|
||||||
|
#######################################################################
|
||||||
|
# Bot Response: Show Trained Digits/Pointers
|
||||||
|
#######################################################################
|
||||||
|
- name: bot-reply Show Trained Digits/Pointers
|
||||||
|
labeled:
|
||||||
|
issue:
|
||||||
|
body: |
|
||||||
|
See [Digital Digits](https://jomjol.github.io/neural-network-digital-counter-readout) resp. [Analogue Pointers](https://jomjol.github.io/neural-network-analog-needle-readout) for an overview of all trained data.
|
||||||
|
If your type is not contained it can be added to our training material, see [here](https://jomjol.github.io/AI-on-the-edge-device-docs/collect-new-images/).
|
||||||
|
discussion:
|
||||||
|
body: |
|
||||||
|
See [Digital Digits](https://jomjol.github.io/neural-network-digital-counter-readout) resp. [Analogue Pointers](https://jomjol.github.io/neural-network-analog-needle-readout) for an overview of all trained data.
|
||||||
|
If your type is not contained it can be added to our training material, see [here](https://jomjol.github.io/AI-on-the-edge-device-docs/collect-new-images/).
|
||||||
418
.github/workflows/build.yaml
vendored
Normal file
418
.github/workflows/build.yaml
vendored
Normal file
@@ -0,0 +1,418 @@
|
|||||||
|
name: Build and Pack
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
#########################################################################################
|
||||||
|
## Build Firmware
|
||||||
|
#########################################################################################
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- id: skip_check
|
||||||
|
uses: fkirc/skip-duplicate-actions@v5
|
||||||
|
with:
|
||||||
|
concurrent_skipping: same_content_newer
|
||||||
|
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Set Variables
|
||||||
|
id: vars
|
||||||
|
run: |
|
||||||
|
echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Update PIP cache on every commit
|
||||||
|
uses: actions/cache@v3.2.3
|
||||||
|
with:
|
||||||
|
path: ~/.cache/pip
|
||||||
|
key: pip-${{ github.run_id }}
|
||||||
|
restore-keys: pip # This matches above key as it is only used as a prefix. it the restores the nearest cache, see https://github.com/restore-keys:/blob/main/tips-and-workarounds.md#update-a-cache
|
||||||
|
|
||||||
|
- name: Update PlatformIO cache on every commit
|
||||||
|
uses: actions/cache@v3.2.3
|
||||||
|
with:
|
||||||
|
path: ~/.platformio
|
||||||
|
key: platformio-${{ github.run_id }}
|
||||||
|
restore-keys: platformio # This matches above key as it is only used as a prefix. it the restores the nearest cache, see https://github.com/restore-keys:/blob/main/tips-and-workarounds.md#update-a-cache
|
||||||
|
|
||||||
|
- name: Update Build cache on every commit
|
||||||
|
uses: actions/cache@v3.2.3
|
||||||
|
with:
|
||||||
|
path: ./code/.pio/
|
||||||
|
key: build-${{ github.run_id }}
|
||||||
|
restore-keys: build # This matches above key as it is only used as a prefix. it the restores the nearest cache, see https://github.com/restore-keys:/blob/main/tips-and-workarounds.md#update-a-cache
|
||||||
|
|
||||||
|
- name: Update generated-files cache on every commit
|
||||||
|
uses: actions/cache@v3.2.3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
./code/.pio/build/esp32cam/firmware.bin
|
||||||
|
./code/.pio/build/esp32cam/partitions.bin
|
||||||
|
./code/.pio/build/esp32cam/bootloader.bin
|
||||||
|
./html/*
|
||||||
|
key: generated-files-${{ github.run_id }}
|
||||||
|
restore-keys: generated-files # This matches above key as it is only used as a prefix. it the restores the nearest cache, see https://github.com/restore-keys:/blob/main/tips-and-workarounds.md#update-a-cache
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: '3.10'
|
||||||
|
|
||||||
|
- name: Install PlatformIO
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install --upgrade platformio
|
||||||
|
|
||||||
|
- name: Build Firmware
|
||||||
|
#run: echo "Testing... ${{ github.ref_name }}, ${{ steps.vars.outputs.sha_short }}" > ./sd-card/html/version.txt; mkdir -p ./code/.pio/build/esp32cam/; cd ./code/.pio/build/esp32cam/; echo "${{ steps.vars.outputs.sha_short }}" > firmware.bin; cp firmware.bin partitions.bin; cp firmware.bin bootloader.bin # Testing
|
||||||
|
run: cd code; platformio run --environment esp32cam
|
||||||
|
|
||||||
|
- name: Prepare Web UI (copy data from repo and update hashes in all files)
|
||||||
|
run: |
|
||||||
|
rm -rf ./html
|
||||||
|
mkdir html
|
||||||
|
cp ./sd-card/html/* ./html/
|
||||||
|
cd html; find . -type f -exec sed -i 's/$COMMIT_HASH/${{ steps.vars.outputs.sha_short }}/g' {} \;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#########################################################################################
|
||||||
|
## Pack for Update
|
||||||
|
#########################################################################################
|
||||||
|
pack-for-update:
|
||||||
|
# New OTA concept
|
||||||
|
# update__version.zip file with following content:
|
||||||
|
# - /firmware.bin
|
||||||
|
# - (optional) /html/*
|
||||||
|
# - (optional) /config/*.tfl
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: build
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Update generated-files cache on every commit
|
||||||
|
uses: actions/cache@v3.2.3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
./code/.pio/build/esp32cam/firmware.bin
|
||||||
|
./code/.pio/build/esp32cam/partitions.bin
|
||||||
|
./code/.pio/build/esp32cam/bootloader.bin
|
||||||
|
./html/*
|
||||||
|
key: generated-files-${{ github.run_id }}
|
||||||
|
restore-keys: generated-files # This matches above key as it is only used as a prefix. it the restores the nearest cache, see https://github.com/restore-keys:/blob/main/tips-and-workarounds.md#update-a-cache
|
||||||
|
|
||||||
|
- name: Update update cache on every commit
|
||||||
|
uses: actions/cache@v3.2.3
|
||||||
|
with:
|
||||||
|
path: update
|
||||||
|
key: update-${{ github.run_id }}
|
||||||
|
restore-keys: update # This matches above key as it is only used as a prefix. it the restores the nearest cache, see https://github.com/restore-keys:/blob/main/tips-and-workarounds.md#update-a-cache
|
||||||
|
|
||||||
|
- name: Set Variables
|
||||||
|
id: vars
|
||||||
|
run: |
|
||||||
|
echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
||||||
|
echo "branch=$(echo ${{ github.ref_name }} | tr / __)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Prepare update__*.zip artifact
|
||||||
|
run: |
|
||||||
|
rm -rf ./update
|
||||||
|
mkdir -p ./update
|
||||||
|
cp "./code/.pio/build/esp32cam/firmware.bin" "update/firmware.bin"
|
||||||
|
|
||||||
|
- name: Add Web UI to update
|
||||||
|
run: cp -r ./html ./update/
|
||||||
|
|
||||||
|
- name: Add CNN to update
|
||||||
|
run: |
|
||||||
|
rm -rf ./update/config/
|
||||||
|
mkdir -p ./update/config/
|
||||||
|
cp ./sd-card/config/*.tfl ./update/config/ 2>/dev/null || true
|
||||||
|
cp ./sd-card/config/*.tflite ./update/config/ 2>/dev/null || true
|
||||||
|
|
||||||
|
- name: Upload update as update.zip artifact (Firmware + Web UI + CNN)
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: "AI-on-the-edge-device__update__${{ steps.vars.outputs.branch }}_(${{ steps.vars.outputs.sha_short }})"
|
||||||
|
path: ./update/*
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#########################################################################################
|
||||||
|
## Pack for Remote Setup
|
||||||
|
#########################################################################################
|
||||||
|
pack-for-remote_setup:
|
||||||
|
# New Remote Setup concept
|
||||||
|
# remote_setup__version.zip file with following content:
|
||||||
|
# - /firmware.bin
|
||||||
|
# - /html/*
|
||||||
|
# - /config/*
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: build
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Update generated-files cache on every commit
|
||||||
|
uses: actions/cache@v3.2.3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
./code/.pio/build/esp32cam/firmware.bin
|
||||||
|
./code/.pio/build/esp32cam/partitions.bin
|
||||||
|
./code/.pio/build/esp32cam/bootloader.bin
|
||||||
|
./html/*
|
||||||
|
key: generated-files-${{ github.run_id }}
|
||||||
|
restore-keys: generated-files # This matches above key as it is only used as a prefix. it the restores the nearest cache, see https://github.com/restore-keys:/blob/main/tips-and-workarounds.md#update-a-cache
|
||||||
|
|
||||||
|
- name: Update remote_setup cache on every commit
|
||||||
|
uses: actions/cache@v3.2.3
|
||||||
|
with:
|
||||||
|
path: remote_setup
|
||||||
|
key: remote_setup-${{ github.run_id }}
|
||||||
|
restore-keys: remote_setup # This matches above key as it is only used as a prefix. it the restores the nearest cache, see https://github.com/restore-keys:/blob/main/tips-and-workarounds.md#update-a-cache
|
||||||
|
|
||||||
|
- name: Set Variables
|
||||||
|
id: vars
|
||||||
|
run: |
|
||||||
|
echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
||||||
|
echo "branch=$(echo ${{ github.ref_name }} | tr / __)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Prepare remote_setup__*.zip artifact
|
||||||
|
run: |
|
||||||
|
rm -rf ./remote_setup
|
||||||
|
mkdir -p ./remote_setup
|
||||||
|
|
||||||
|
- name: Add Web UI to remote_setup
|
||||||
|
run: cp -r ./html ./remote_setup/
|
||||||
|
|
||||||
|
- name: Add whole config folder to remote_setup
|
||||||
|
run: |
|
||||||
|
rm -rf ./remote_setup/config/
|
||||||
|
mkdir -p ./remote_setup/config/
|
||||||
|
cp ./sd-card/config/* ./remote_setup/config/ 2>/dev/null || true
|
||||||
|
|
||||||
|
- name: Upload remote_setup as remote_setup.zip artifact (Firmware + Web UI + Config)
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: "AI-on-the-edge-device__remote-setup__${{ steps.vars.outputs.branch }}_(${{ steps.vars.outputs.sha_short }})"
|
||||||
|
path: ./remote_setup/*
|
||||||
|
|
||||||
|
|
||||||
|
#########################################################################################
|
||||||
|
## Pack for a fresh install (USB flashing) (manual_setup)
|
||||||
|
#########################################################################################
|
||||||
|
pack-for-manual_setup:
|
||||||
|
# creates old style binaries for fresh installation (backward compatible to wiki)
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: build
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Update generated-files cache on every commit
|
||||||
|
uses: actions/cache@v3.2.3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
./code/.pio/build/esp32cam/firmware.bin
|
||||||
|
./code/.pio/build/esp32cam/partitions.bin
|
||||||
|
./code/.pio/build/esp32cam/bootloader.bin
|
||||||
|
./html/*
|
||||||
|
key: generated-files-${{ github.run_id }}
|
||||||
|
restore-keys: generated-files # This matches above key as it is only used as a prefix. it the restores the nearest cache, see https://github.com/restore-keys:/blob/main/tips-and-workarounds.md#update-a-cache
|
||||||
|
|
||||||
|
- name: Update manual_setup cache on every commit
|
||||||
|
uses: actions/cache@v3.2.3
|
||||||
|
with:
|
||||||
|
path: manual_setup
|
||||||
|
key: manual_setup-${{ github.run_id }}
|
||||||
|
restore-keys: manual_setup # This matches above key as it is only used as a prefix. it the restores the nearest cache, see https://github.com/restore-keys:/blob/main/tips-and-workarounds.md#update-a-cache
|
||||||
|
|
||||||
|
- name: Set Variables
|
||||||
|
id: vars
|
||||||
|
run: |
|
||||||
|
echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
||||||
|
echo "branch=$(echo ${{ github.ref_name }} | tr / __)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Prepare manual_setup__*.zip artifact
|
||||||
|
run: |
|
||||||
|
rm -rf manual_setup
|
||||||
|
mkdir -p manual_setup
|
||||||
|
rm -rf manual_setup/*.zip
|
||||||
|
rm -rf release
|
||||||
|
mkdir -p release
|
||||||
|
# copy builds to manual_setup folder
|
||||||
|
cp -f "./code/.pio/build/esp32cam/firmware.bin" "manual_setup/firmware.bin"
|
||||||
|
cp -f "./code/.pio/build/esp32cam/bootloader.bin" "manual_setup/bootloader.bin"
|
||||||
|
cp -f "./code/.pio/build/esp32cam/partitions.bin" "manual_setup/partitions.bin"
|
||||||
|
rm -rf ./sd-card/html
|
||||||
|
cp -r ./html ./sd-card/ # Overwrite the Web UI with the preprocessed files
|
||||||
|
cd sd-card; zip -r ../manual_setup/sd-card.zip *; cd ..
|
||||||
|
cd ./manual_setup
|
||||||
|
|
||||||
|
- name: Upload manual_setup.zip artifact (Firmware + Bootloader + Partitions + Web UI)
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: "AI-on-the-edge-device__manual-setup__${{ steps.vars.outputs.branch }}_(${{ steps.vars.outputs.sha_short }})"
|
||||||
|
path: ./manual_setup
|
||||||
|
|
||||||
|
|
||||||
|
#########################################################################################
|
||||||
|
## Prepare and create release
|
||||||
|
#########################################################################################
|
||||||
|
release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [pack-for-update, pack-for-manual_setup, pack-for-remote_setup]
|
||||||
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
|
||||||
|
# Sets permissions of the GITHUB_TOKEN to allow updating the branches
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pages: write
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Update update cache on every commit
|
||||||
|
uses: actions/cache@v3.2.3
|
||||||
|
with:
|
||||||
|
path: update
|
||||||
|
key: update-${{ github.run_id }}
|
||||||
|
restore-keys: update # This matches above key as it is only used as a prefix. it the restores the nearest cache, see https://github.com/restore-keys:/blob/main/tips-and-workarounds.md#update-a-cache
|
||||||
|
|
||||||
|
- name: Update remote_setup cache on every commit
|
||||||
|
uses: actions/cache@v3.2.3
|
||||||
|
with:
|
||||||
|
path: remote_setup
|
||||||
|
key: remote_setup-${{ github.run_id }}
|
||||||
|
restore-keys: remote_setup # This matches above key as it is only used as a prefix. it the restores the nearest cache, see https://github.com/restore-keys:/blob/main/tips-and-workarounds.md#update-a-cache
|
||||||
|
|
||||||
|
- name: Update manual_setup cache on every commit
|
||||||
|
uses: actions/cache@v3.2.3
|
||||||
|
with:
|
||||||
|
path: manual_setup
|
||||||
|
key: manual_setup-${{ github.run_id }}
|
||||||
|
restore-keys: manual_setup # This matches above key as it is only used as a prefix. it the restores the nearest cache, see https://github.com/restore-keys:/blob/main/tips-and-workarounds.md#update-a-cache
|
||||||
|
|
||||||
|
- name: Set Variables
|
||||||
|
id: vars
|
||||||
|
run: |
|
||||||
|
echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
||||||
|
echo "branch=$(echo ${{ github.ref_name }} | tr / __)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Prepare artifacts for release
|
||||||
|
run: |
|
||||||
|
rm -rf release
|
||||||
|
mkdir -p release
|
||||||
|
|
||||||
|
# create AI-on-the-edge-device__update__*.zip like "AI-on-the-edge-device__update__v13.0.5.zip"
|
||||||
|
cd ./update
|
||||||
|
zip -r ../release/AI-on-the-edge-device__update__${{ steps.vars.outputs.branch }}.zip .
|
||||||
|
|
||||||
|
# create AI-on-the-edge-device__manual-setup__*.zip like "AI-on-the-edge-device__manual-setup__v13.0.5.zip"
|
||||||
|
cd ../manual_setup
|
||||||
|
zip -r ../release/AI-on-the-edge-device__manual-setup__${{ steps.vars.outputs.branch }}.zip .
|
||||||
|
|
||||||
|
# create AI-on-the-edge-device__remote-setup__*.zip like "AI-on-the-edge-device__remote-setup__v13.0.5.zip"
|
||||||
|
cd ../remote_setup
|
||||||
|
zip -r ../release/AI-on-the-edge-device__remote-setup__${{ steps.vars.outputs.branch }}.zip .
|
||||||
|
|
||||||
|
# extract the version used in next step
|
||||||
|
- id: get_version
|
||||||
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
uses: battila7/get-version-action@v2
|
||||||
|
|
||||||
|
# # the changelog [unreleased] will now be changed to the release version
|
||||||
|
# - name: Update changelog
|
||||||
|
# uses: thomaseizinger/keep-a-changelog-new-release@v1
|
||||||
|
# if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
# with:
|
||||||
|
# changelogPath: Changelog.md
|
||||||
|
# version: ${{ steps.get_version.outputs.version-without-v }}
|
||||||
|
|
||||||
|
# the release notes will be extracted from changelog
|
||||||
|
- name: Extract release notes
|
||||||
|
id: extract-release-notes
|
||||||
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
uses: ffurrer2/extract-release-notes@v1
|
||||||
|
with:
|
||||||
|
changelog_file: Changelog.md
|
||||||
|
|
||||||
|
# Releases should only be created on master by tagging the last commit.
|
||||||
|
# all artifacts in firmware folder pushed to the release
|
||||||
|
- name: Release
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
|
# Note:
|
||||||
|
# If you get the error "Resource not accessible by integration",
|
||||||
|
# The access rights are not sufficient, see
|
||||||
|
# https://github.com/softprops/action-gh-release/issues/232#issuecomment-1131379440
|
||||||
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
with:
|
||||||
|
name: ${{ steps.get_version.outputs.version-without-v }}
|
||||||
|
body: ${{ steps.extract-release-notes.outputs.release_notes }}
|
||||||
|
files: |
|
||||||
|
release/*
|
||||||
|
|
||||||
|
# # Commit&Push Changelog to master branch. Must be manually merged back to rolling
|
||||||
|
# - name: Commit changes and push changes
|
||||||
|
# if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
# run: |
|
||||||
|
# git config user.name github-actions
|
||||||
|
# git config user.email github-actions@github.com
|
||||||
|
# git add Changelog.md
|
||||||
|
# git commit Changelog.md -m "Update Changelog.md for ${{github.event.inputs.versionIncrement}} release"
|
||||||
|
# git push origin HEAD:master
|
||||||
|
|
||||||
|
|
||||||
|
#########################################################################################
|
||||||
|
## Update the Web Installer on a release
|
||||||
|
#########################################################################################
|
||||||
|
# This is the same as in the update-webinstaller.yml
|
||||||
|
update-web-installer:
|
||||||
|
needs: [release]
|
||||||
|
environment:
|
||||||
|
name: github-pages
|
||||||
|
url: ${{ steps.deployment.outputs.page_url }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pages: write
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Get version of last release
|
||||||
|
id: last_release
|
||||||
|
uses: InsonusK/get-latest-release@v1.0.1
|
||||||
|
with:
|
||||||
|
myToken: ${{ github.token }}
|
||||||
|
exclude_types: "draft|prerelease"
|
||||||
|
view_top: 1
|
||||||
|
|
||||||
|
- name: Add binary to Web Installer and update manifest
|
||||||
|
run: |
|
||||||
|
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
|
||||||
|
- name: Setup Pages
|
||||||
|
uses: actions/configure-pages@v2
|
||||||
|
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-pages-artifact@v1
|
||||||
|
with:
|
||||||
|
path: 'docs'
|
||||||
|
|
||||||
|
- name: Deploy to GitHub Pages
|
||||||
|
id: deployment
|
||||||
|
uses: actions/deploy-pages@v1
|
||||||
30
.github/workflows/clear_cache.yml
vendored
Normal file
30
.github/workflows/clear_cache.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
name: Clear cache
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
actions: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
clear-cache:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Clear cache
|
||||||
|
uses: actions/github-script@v6
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
console.log("About to clear")
|
||||||
|
const caches = await github.rest.actions.getActionsCacheList({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
})
|
||||||
|
for (const cache of caches.data.actions_caches) {
|
||||||
|
console.log(cache)
|
||||||
|
github.rest.actions.deleteActionsCacheById({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
cache_id: cache.id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
console.log("Clear completed")
|
||||||
61
.github/workflows/manual-update-webinstaller.yml
vendored
Normal file
61
.github/workflows/manual-update-webinstaller.yml
vendored
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
# 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
|
||||||
|
|
||||||
|
name: Manual Web Installer Update
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch: # Run on manual trigger
|
||||||
|
# push:
|
||||||
|
# branches:
|
||||||
|
# - rolling
|
||||||
|
# paths:
|
||||||
|
# - docs # The path filter somehow does not work, so lets run it on every change to rolling
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
manually-update-web-installer:
|
||||||
|
environment:
|
||||||
|
name: github-pages
|
||||||
|
url: ${{ steps.deployment.outputs.page_url }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pages: write
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Get version of last release
|
||||||
|
id: last_release
|
||||||
|
uses: InsonusK/get-latest-release@v1.0.1
|
||||||
|
with:
|
||||||
|
myToken: ${{ github.token }}
|
||||||
|
exclude_types: "release"
|
||||||
|
view_top: 1
|
||||||
|
|
||||||
|
- name: Add binary to Web Installer and update manifest
|
||||||
|
run: |
|
||||||
|
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
|
||||||
|
|
||||||
|
- name: Setup Pages
|
||||||
|
uses: actions/configure-pages@v2
|
||||||
|
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-pages-artifact@v1
|
||||||
|
with:
|
||||||
|
path: 'docs'
|
||||||
|
|
||||||
|
- name: Deploy to GitHub Pages
|
||||||
|
id: deployment
|
||||||
|
uses: actions/deploy-pages@v1
|
||||||
|
|
||||||
79
.github/workflows/reply-bot.yml
vendored
Normal file
79
.github/workflows/reply-bot.yml
vendored
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
# Reply Bot
|
||||||
|
# It uses the configuration in .github/label-commenter-config.yml
|
||||||
|
# See https://github.com/peaceiris/actions-label-commenter
|
||||||
|
|
||||||
|
name: Reply-Bot
|
||||||
|
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types: [labeled]
|
||||||
|
discussion:
|
||||||
|
types: [labeled]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
issues: write
|
||||||
|
pull-requests: write
|
||||||
|
discussions: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
comment:
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
|
||||||
|
####################################################################
|
||||||
|
## Remove labels again (issues only)
|
||||||
|
## Make sure to also add the reply message to .github/label-commenter-config.yml!
|
||||||
|
## This currently seems no longer to work due to changes on the actions-cool/issues-helper!
|
||||||
|
####################################################################
|
||||||
|
# - name: Remove 'Logfile' label again (issues only)
|
||||||
|
# if: github.event.label.name == 'bot-reply Logfile'
|
||||||
|
# uses: actions-cool/issues-helper@v3
|
||||||
|
# with:
|
||||||
|
# actions: 'remove-labels'
|
||||||
|
# labels: 'bot-reply Logfile'
|
||||||
|
#
|
||||||
|
# - name: Remove 'Web Console' label again (issues only)
|
||||||
|
# if: github.event.label.name == 'bot-reply Web Console'
|
||||||
|
# uses: actions-cool/issues-helper@v3
|
||||||
|
# with:
|
||||||
|
# actions: 'remove-labels'
|
||||||
|
# labels: 'bot-reply Web Console'
|
||||||
|
#
|
||||||
|
# - name: Remove 'Properly Format Code' label again (issues only)
|
||||||
|
# if: github.event.label.name == 'bot-reply Properly Format Code'
|
||||||
|
# uses: actions-cool/issues-helper@v3
|
||||||
|
# with:
|
||||||
|
# actions: 'remove-labels'
|
||||||
|
# labels: 'bot-reply Properly Format Code'
|
||||||
|
#
|
||||||
|
# - name: Remove 'Web Installer' label again (issues only)
|
||||||
|
# if: github.event.label.name == 'bot-reply Web Installer'
|
||||||
|
# uses: actions-cool/issues-helper@v3
|
||||||
|
# with:
|
||||||
|
# actions: 'remove-labels'
|
||||||
|
# labels: 'bot-reply Web Installer'
|
||||||
|
#
|
||||||
|
# - name: Remove 'Rolling Build' label again (issues only)
|
||||||
|
# if: github.event.label.name == 'bot-reply Rolling Build'
|
||||||
|
# uses: actions-cool/issues-helper@v3
|
||||||
|
# with:
|
||||||
|
# actions: 'remove-labels'
|
||||||
|
# labels: 'bot-reply Rolling Build'
|
||||||
|
#
|
||||||
|
# - name: Remove 'Show Trained Digits/Pointers' label again (issues only)
|
||||||
|
# if: github.event.label.name == 'bot-reply Show Trained Digits/Pointers'
|
||||||
|
# uses: actions-cool/issues-helper@v3
|
||||||
|
# with:
|
||||||
|
# actions: 'remove-labels'
|
||||||
|
# labels: 'bot-reply Show Trained Digits/Pointers'
|
||||||
|
|
||||||
|
####################################################################
|
||||||
|
## Write the response
|
||||||
|
####################################################################
|
||||||
|
- name: Write Response
|
||||||
|
uses: peaceiris/actions-label-commenter@c2d00660c86f2b9ed0fb35b372c451558eba85b3
|
||||||
|
with:
|
||||||
|
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
13
.gitignore
vendored
13
.gitignore
vendored
@@ -2,17 +2,24 @@
|
|||||||
.pio/
|
.pio/
|
||||||
.vscode/
|
.vscode/
|
||||||
.code-workspace
|
.code-workspace
|
||||||
.helper/
|
|
||||||
/sd-card/htm./.vscode/
|
/sd-card/htm./.vscode/
|
||||||
|
/code/build
|
||||||
|
/code/.helper
|
||||||
|
/sd-card/html/debug/
|
||||||
|
/firmware/
|
||||||
|
version.txt
|
||||||
|
/dist/
|
||||||
|
/dist_release/
|
||||||
|
/dist_old_ota
|
||||||
CMakeLists.txt.user
|
CMakeLists.txt.user
|
||||||
CMakeCache.txt
|
CMakeCache.txt
|
||||||
CMakeFiles
|
CMakeFiles
|
||||||
CMakeScripts
|
CMakeScripts
|
||||||
Testing
|
|
||||||
Makefile
|
Makefile
|
||||||
cmake_install.cmake
|
cmake_install.cmake
|
||||||
install_manifest.txt
|
install_manifest.txt
|
||||||
compile_commands.json
|
compile_commands.json
|
||||||
CTestTestfile.cmake
|
CTestTestfile.cmake
|
||||||
_deps
|
_deps
|
||||||
|
code/edgeAI.code-workspace
|
||||||
|
.DS_Store
|
||||||
|
|||||||
9
.gitmodules
vendored
Normal file
9
.gitmodules
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[submodule "code/components/esp32-camera"]
|
||||||
|
path = code/components/esp32-camera
|
||||||
|
url = https://github.com/espressif/esp32-camera.git
|
||||||
|
[submodule "code/components/esp-nn"]
|
||||||
|
path = code/components/esp-nn
|
||||||
|
url = https://github.com/espressif/esp-nn.git
|
||||||
|
[submodule "code/components/tflite-micro-esp-examples"]
|
||||||
|
path = code/components/tflite-micro-esp-examples
|
||||||
|
url = https://github.com/espressif/tflite-micro-esp-examples.git
|
||||||
814
Changelog.md
814
Changelog.md
@@ -1,6 +1,814 @@
|
|||||||
# Versions
|
## [14.0.2] - 2023-02-05
|
||||||
|
|
||||||
##### 0.1.0 (2020-08-07)
|
**Stabilization and Improved User Experience**
|
||||||
|
|
||||||
* Initial Version
|
Thanks to over 80 Pull Requests from 6 contributors, we can anounce another great release with many many improvements and new features:
|
||||||
|
|
||||||
|
### Update Procedure
|
||||||
|
|
||||||
|
Update Procedure see [online documentation](https://jomjol.github.io/AI-on-the-edge-device-docs/Installation/#update-ota-over-the-air)
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
For a full list of changes see [Full list of changes](https://github.com/jomjol/AI-on-the-edge-device/compare/v13.0.8...v14.0.0)
|
||||||
|
|
||||||
|
#### Added
|
||||||
|
|
||||||
|
- [1877](https://github.com/jomjol/AI-on-the-edge-device/pull/1877) Show WIFI signal text labels / Log RSSI value to logfile
|
||||||
|
- [1671](https://github.com/jomjol/AI-on-the-edge-device/pull/1671) Added experimental support for WLAN 802.11k und 802.11v (Mesh-Support)
|
||||||
|
- Web UI caching of static files
|
||||||
|
- Added various debug tools
|
||||||
|
- [1798](https://github.com/jomjol/AI-on-the-edge-device/pull/1798) Add error handling for memory intensive tasks
|
||||||
|
- [1784](https://github.com/jomjol/AI-on-the-edge-device/pull/1784) Add option to disable brownout detector
|
||||||
|
- Added full web browser based installation mode (including initial setup of SD-card) - see [WebInstaller](https://jomjol.github.io/AI-on-the-edge-device/index.html)
|
||||||
|
- Added [Demo Mode](https://jomjol.github.io/AI-on-the-edge-device-docs/Demo-Mode)
|
||||||
|
- [1648](https://github.com/jomjol/AI-on-the-edge-device/pull/1648) Added trigger to start a flow by [REST](https://jomjol.github.io/AI-on-the-edge-device-docs/REST-API) API or [MQTT](https://jomjol.github.io/AI-on-the-edge-device-docs/MQTT-API/)
|
||||||
|
- Show special images during steps `Initializing` and `Take Image` as the current camera image might be incomplete or outdated
|
||||||
|
|
||||||
|
#### Changed
|
||||||
|
|
||||||
|
- Migrated documentation (Wiki) to <https://jomjol.github.io/AI-on-the-edge-device-docs>. Please help us to make it even better.
|
||||||
|
- New OTA Update page with progress indication
|
||||||
|
- Various memory optimizations
|
||||||
|
- Cleanup code/Web UI
|
||||||
|
- Updated models
|
||||||
|
- [1809](https://github.com/jomjol/AI-on-the-edge-device/pull/1809) Store preprocessed image with ROI to RAM
|
||||||
|
- Better log messages on some errors/issues
|
||||||
|
- [1742](https://github.com/jomjol/AI-on-the-edge-device/pull/1742) Replace alert boxes with overlay info boxes
|
||||||
|
- Improve log message when web UI is installed incomplete
|
||||||
|
- [1676](https://github.com/jomjol/AI-on-the-edge-device/pull/1676) Improve NTP handling
|
||||||
|
- HTML: improved user informations (info boxes, error hints, ...)
|
||||||
|
- [1904](https://github.com/jomjol/AI-on-the-edge-device/pull/1904) Removed newlines in JSON and replaced all whitespaces where there was more than one
|
||||||
|
|
||||||
|
#### Fixed
|
||||||
|
|
||||||
|
- Fixed many many things
|
||||||
|
- [1509](https://github.com/jomjol/AI-on-the-edge-device/pull/1509) Protect `wifi.ini` from beeing deleted.
|
||||||
|
- [1530](https://github.com/jomjol/AI-on-the-edge-device/pull/1530) Homeassistant `Problem Sensor`
|
||||||
|
- [1518](https://github.com/jomjol/AI-on-the-edge-device/pull/1518) JSON Strings
|
||||||
|
- [1817](https://github.com/jomjol/AI-on-the-edge-device/pull/1817) DataGraph: datafiles sorted -> newest on top
|
||||||
|
|
||||||
|
#### Removed
|
||||||
|
|
||||||
|
- n.a.
|
||||||
|
|
||||||
|
## [13.0.8] - 2022-12-19
|
||||||
|
|
||||||
|
**Home Assistant MQTT Discovery Support**
|
||||||
|
|
||||||
|
### Update Procedure see [online documentation](https://jomjol.github.io/AI-on-the-edge-device-docs/Installation/#update-ota-over-the-air)
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Implementation of [Home Assistant MQTT Discovery](https://www.home-assistant.io/integrations/mqtt/#mqtt-discovery)
|
||||||
|
- Improved ROIs configuration: locked ROI geometry, equidistant delta x
|
||||||
|
- Improved OTA Update mechanism (only working after installation for next update)
|
||||||
|
- Added data logging in `/log/data` - One day per file and each measurement is on one line
|
||||||
|
- Format: csv - comma separated
|
||||||
|
- Content: `time`, `name-of-number`, `raw-value`, `return-value`, `pre-value`, `change-rate`, `change-absolute`, `error-text`, `cnn-digital`, `cnn-analog`
|
||||||
|
- Show graph of values direct in the user interface (thanks to [@rdmueller](https://github.com/rdmueller))
|
||||||
|
|
||||||
|
- Using new data logging (see above)
|
||||||
|
- Possibility to choose different values and switch between different numbers (if present)
|
||||||
|
|
||||||
|
Note: You need to activate data logging for this feature to work, see above!
|
||||||
|
- PreValue is now contained in `/json` ([#1154](https://github.com/jomjol/AI-on-the-edge-device/issues/1154))
|
||||||
|
- SD card info into the `System>Info` menu (thanks to [@Slider007](https://github.com/Slider0007))
|
||||||
|
- Version check (Firmware vs. Web UI)
|
||||||
|
- Various minor new features
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Updated tflite (`dig-cont_0600_s3.tflite`)
|
||||||
|
- Updated OTA functionality (more robust, but not fully bullet prove yet)
|
||||||
|
- Updated Espressif library to `espressif32@v5.2.0`
|
||||||
|
- [#1176](https://github.com/jomjol/AI-on-the-edge-device/discussions/1176) accept minor negative values (-0.2) if extended resolution is enabled
|
||||||
|
- [#1143](https://github.com/jomjol/AI-on-the-edge-device/issues/1143) added config parameter `AnalogDigitalTransitionStart`. It can setup very early and very late digit transition starts.
|
||||||
|
- New version of `dig-class100` (v1.4.0): added images of heliowatt powermeter
|
||||||
|
- NEW v13.0.2: Update Tool "Logfile downloader and combiner" to handle the new csv file format.
|
||||||
|
- NEW v13.0.2: MQTT: Added MQTT topic `status` (Digitalization Status), Timezone to MQTT topic `timestamp`.#
|
||||||
|
- NEW v13.0.2: Logging: Disable heap logs by default, cleanup
|
||||||
|
- NEW v13.0.7:
|
||||||
|
- log NTP server name
|
||||||
|
- Improved log messages
|
||||||
|
- Various preparations for next release
|
||||||
|
- **NEW v13.0.8**:
|
||||||
|
- Continue booting on PSRAM issues, Web UI will show an error
|
||||||
|
- Updated models
|
||||||
|
- Various UI enhancements
|
||||||
|
- Various internal improvements
|
||||||
|
- Show uptime in log
|
||||||
|
- Show uptime and round on overview page
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- [#1116](https://github.com/jomjol/AI-on-the-edge-device/issues/1116) precision problem at setting prevalue
|
||||||
|
- [#1119](https://github.com/jomjol/AI-on-the-edge-device/issues/1119) renamed `firmware.bin` not working in OTA
|
||||||
|
- [#1143](https://github.com/jomjol/AI-on-the-edge-device/issues/1143) changed postprocess for `analog->digit` (lowest digit processing)
|
||||||
|
- [#1280](https://github.com/jomjol/AI-on-the-edge-device/issues/1280) check ROIs name for unsupported characters
|
||||||
|
- [#983](https://github.com/jomjol/AI-on-the-edge-device/issues/983) old log files did not get deleted
|
||||||
|
- Failed NTP time sync during startup gets now retried every round if needed
|
||||||
|
- Whitespaces and `=` in MQTT and InfluxDB passwords
|
||||||
|
- Various minor fixes and improvements
|
||||||
|
- NEW v13.0.2: Corrected Version comparison between firmware and Web UI.
|
||||||
|
- NEW v13.0.3: Re-updated build environment to v5.2.0 (from accidental downgrad to v4.4.0)
|
||||||
|
- NEW v13.0.4: Fix for reboot in case of MQTT not used
|
||||||
|
- NEW v13.0.5: No reboot in case of missing NTP-connection
|
||||||
|
- NEW v13.0.7:
|
||||||
|
- Prevent autoreboot on cam framebuffer init error
|
||||||
|
- Properly protect `wlan.ini` against deletion
|
||||||
|
- Fixed various MQTT topic content issues
|
||||||
|
- Fix Digit detected as 10 (<https://github.com/jomjol/AI-on-the-edge-device/pull/1525>)
|
||||||
|
- Fix frozen time in datafile on error
|
||||||
|
- Various minor fixes
|
||||||
|
- **NEW v13.0.8**:
|
||||||
|
- Fix Rate Problem ([#1578](https://github.com/jomjol/AI-on-the-edge-device/issues/1578), [#1572](https://github.com/jomjol/AI-on-the-edge-device/issues/1572))
|
||||||
|
- Stabilized MQTT
|
||||||
|
- Fixed redundant calls in OTA
|
||||||
|
- Block REST API calls till resource is ready
|
||||||
|
- Fixed number renaming ([#1635](https://github.com/jomjol/AI-on-the-edge-device/issues/1635))
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- n.a.
|
||||||
|
|
||||||
|
## [12.0.1] 2022-09-29
|
||||||
|
|
||||||
|
Improve **u**ser e**x**perience
|
||||||
|
|
||||||
|
:bangbang: The release breaks a few things in ota update :bangbang:
|
||||||
|
|
||||||
|
**Make sure to read the instructions below carfully!**.
|
||||||
|
|
||||||
|
1. Backup your configuration (use the `System > Backup/Restore` page)!
|
||||||
|
2. You should update to `11.3.1` before you update to this release. All other migrations are not tested.
|
||||||
|
Rolling newer than `11.3.1` can also be used, but no guaranty.
|
||||||
|
3. Upload and update the `firmware.bin` file from this release. **but do not reboot**
|
||||||
|
4. Upload the `html-from-11.3.1.zip` in html upload and update the web interface.
|
||||||
|
5. Now you can reboot.
|
||||||
|
|
||||||
|
If anything breaks you can try to
|
||||||
|
1\. Call `http://<IP>/ota?task=update&file=firmware.bin` resp. `http://<IP>/ota?task=update&file=html.zip` if the upload successed but the extraction failed.
|
||||||
|
1\. Use the initial_esp32_setup.zip ( <https://github.com/jomjol/AI-on-the-edge-device/wiki/Installation> ) as alternative.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Automatic release creation
|
||||||
|
- Newest firmware of rolling branch now automatically build and provided in [Github Actions Output](https://github.com/jomjol/AI-on-the-edge-device/actions) (developers only)
|
||||||
|
- [#1068](https://github.com/jomjol/AI-on-the-edge-device/issues/1068) New update mechanism:
|
||||||
|
- Handling of all files (`zip`, `tfl`, `tflite`, `bin`) within in one common update interface
|
||||||
|
- Using the `update.zip` from the [Release page](https://github.com/jomjol/AI-on-the-edge-device/releases)
|
||||||
|
- Status (`upload`, `processing`, ...) displayed on Web Interface
|
||||||
|
- Automatical detection and suggestion for reboot where needed (Web Interface uupdates only need a page refresh)
|
||||||
|
- :bangbang: Best for OTA use Firefox. Chrome works with warnings. Safari stuck in upload.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Integrated version info better shown on the Info page and in the log
|
||||||
|
- Updated menu
|
||||||
|
- Update used libraries (`tflite`, `esp32-cam`, `esp-nn`, as of 20220924)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- [#1092](https://github.com/jomjol/AI-on-the-edge-device/issues/1092) censor passwords in log outputs
|
||||||
|
- [#1029](https://github.com/jomjol/AI-on-the-edge-device/issues/1029) wrong change of `checkDigitConsistency` now working like releases before `11.3.1`
|
||||||
|
- Spelling corrections (**[cristianmitran](https://github.com/cristianmitran)**)
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- Remove the folder `/firmware` from GitHub repository.
|
||||||
|
If you want to get the latest `firmware.bin` and `html.zip` files, please download from the automated [build action](https://github.com/jomjol/AI-on-the-edge-device/actions) or [release page](https://github.com/jomjol/AI-on-the-edge-device/releases)
|
||||||
|
|
||||||
|
## [11.3.1](https://github.com/jomjol/AI-on-the-edge-device/releases/tag/v11.3.1), 2022-09-17
|
||||||
|
|
||||||
|
Intermediate Digits
|
||||||
|
|
||||||
|
- **ATTENTION**:
|
||||||
|
|
||||||
|
- first update the `firmware.bin` and ensure that the new version is running
|
||||||
|
|
||||||
|
- Only afterwards update the `html.zip`
|
||||||
|
|
||||||
|
- Otherwise the downwards compatibility of the new counter clockwise feature is not given and you end in a reboot loop, that needs manual flashing!
|
||||||
|
|
||||||
|
|
||||||
|
- **NEW v11.3.1**: corrected corrupted asset `firmware.bin`
|
||||||
|
- Increased precision (more than 6-7 digits)
|
||||||
|
- Implements Counter Clockwise Analog Pointers
|
||||||
|
- Improved post processing algorithm
|
||||||
|
- Debugging: intensive use of testcases
|
||||||
|
- MQTT: improved handling, extended logging, automated reconnect
|
||||||
|
- HTML: Backup Option for Configuration
|
||||||
|
- HTML: Improved Reboot
|
||||||
|
- HTML: Update WebUI (Reboot, Infos, CPU Temp, RSSI)
|
||||||
|
- This version is largely also based on the work of **[caco3](https://github.com/caco3)**, **[adellafave](https://github.com/adellafave)**, **[haverland](https://github.com/haverland)**, **[stefanbode](https://github.com/stefanbode)**, **[PLCHome](https://github.com/PLCHome)**
|
||||||
|
|
||||||
|
## [11.2.0](https://github.com/jomjol/AI-on-the-edge-device/releases/tag/v11.2.0), 2022-08-28
|
||||||
|
|
||||||
|
Intermediate Digits
|
||||||
|
|
||||||
|
- Updated Tensorflow / TFlite to newest tflite (version as of 2022-07-27)
|
||||||
|
|
||||||
|
- Updated analog neural network file (`ana-cont_11.3.0_s2.tflite` - default, `ana-class100_0120_s1_q.tflite`)
|
||||||
|
|
||||||
|
- Updated digital neural network file (`dig-cont_0570_s3.tflite` - default, `dig-class100_0120_s2_q.tflite`)
|
||||||
|
|
||||||
|
- Added automated filtering of tflite-file in the graphical configuration (thanks to @**[caco3](https://github.com/caco3)**)
|
||||||
|
|
||||||
|
- Updated consistency algorithm & test cases
|
||||||
|
|
||||||
|
- HTML: added favicon and system name, Improved reboot dialog (thanks to @**[caco3](https://github.com/caco3)**)
|
||||||
|
|
||||||
|
## [11.1.1](https://github.com/jomjol/AI-on-the-edge-device/releases/tag/v11.1.1), 2022-08-22
|
||||||
|
|
||||||
|
Intermediate Digits
|
||||||
|
|
||||||
|
- New and improved consistency check (especially with analog and digital counters mixed)
|
||||||
|
- Bug Fix: digital counter algorithm
|
||||||
|
|
||||||
|
## [11.0.1](https://github.com/jomjol/AI-on-the-edge-device/releases/tag/v11.0.1), 2022-08-18
|
||||||
|
|
||||||
|
Intermediate Digits
|
||||||
|
|
||||||
|
- **NEW v11.0.1**: Bug Fix InfluxDB configuration (only update of html.zip necessary)
|
||||||
|
|
||||||
|
- Implementation of new CNN types to detect intermediate values of digits with rolling numbers
|
||||||
|
|
||||||
|
- By default the old algo (0, 1, ..., 9, "N") is active (due to the limited types of digits trained so far)
|
||||||
|
- Activation can be done by selection a tflite file with the new trained model in the 'config.ini'
|
||||||
|
- **Details can be found in the [wiki](https://github.com/jomjol/AI-on-the-edge-device/wiki/Neural-Network-Types)** (different types, trained image types, naming convention)
|
||||||
|
|
||||||
|
- Updated neural network files (and adaption to new naming convention)
|
||||||
|
|
||||||
|
- Published a tool to download and combine log files - **Thanks to **
|
||||||
|
|
||||||
|
- Files see ['/tools/logfile-tool'](tbd), How-to see [wiki](https://github.com/jomjol/AI-on-the-edge-device/wiki/Gasmeter-Log-Downloader)
|
||||||
|
|
||||||
|
- Bug Fix: InfluxDB enabling in grahic configuration
|
||||||
|
|
||||||
|
## [10.6.2](https://github.com/jomjol/AI-on-the-edge-device/releases/tag/v10.6.2), 2022-07-24
|
||||||
|
|
||||||
|
Stability Increase
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **NEW 10.6.2**: ignore hidden files in model selection (configuration page)
|
||||||
|
|
||||||
|
- **NEW 10.6.1**: Revoke esp32cam & tflite update
|
||||||
|
|
||||||
|
- **NEW 10.6.1**: Bug Fix: tflite-filename with ".", HTML spelling error
|
||||||
|
|
||||||
|
- IndluxDB: direct injection into InfluxDB - thanks to **[wetneb](https://github.com/wetneb)**
|
||||||
|
|
||||||
|
- MQTT: implemented "Retain Flag" and extend with absolute Change (in addition to rate)
|
||||||
|
|
||||||
|
- `config.ini`: removal of modelsize (readout from tflite)
|
||||||
|
|
||||||
|
- Updated analog neural network file (`ana1000s2.tflite`) & digital neural network file (`dig1400s2q.tflite`)
|
||||||
|
|
||||||
|
- TFMicro/Lite: Update (espressif Version 20220716)
|
||||||
|
|
||||||
|
- Updated esp32cam (v20220716)
|
||||||
|
|
||||||
|
- ESP-IDF: Update to 4.4
|
||||||
|
|
||||||
|
- Internal update (CNN algorithm optimizations, reparation for new neural network type)
|
||||||
|
|
||||||
|
- Bug Fix: no time with fixed IP, Postprocessing, MQTT
|
||||||
|
|
||||||
|
## [10.5.2](https://github.com/jomjol/AI-on-the-edge-device/releases/tag/v10.5.2), 2022-02-22
|
||||||
|
|
||||||
|
Stability Increase
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- NEW 10.5.2: Bug Fix: wrong `firmware.bin` (no rate update)
|
||||||
|
- NEW 10.5.1: Bug Fix: wrong return value, rate value & PreValue status, HTML: SSID & IP were not displayed
|
||||||
|
- MQTT: changed wifi naming to "wifiRSSI"
|
||||||
|
- HTML: check selectable values for consistency
|
||||||
|
- Refactoring of check postprocessing consistency (e.g. max rate, negative rate, ...)
|
||||||
|
- Bug Fix: corrected error in "Check Consistency Increase"
|
||||||
|
|
||||||
|
## [10.4.0](https://github.com/jomjol/AI-on-the-edge-device/releases/tag/v10.4.0), 2022-02-12
|
||||||
|
|
||||||
|
Stability Increase
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Graphical configuration: select available neural network files (_.tfl,_.tflite) from drop down menu
|
||||||
|
- OTA-update: add option to upload tfl / tflite files to the correct location (`/config/`)
|
||||||
|
- In the future the new files will also be copied to the `firmware` directory of the repository
|
||||||
|
- Added Wifi RSSI to MQTT information
|
||||||
|
- Updated analog neural network file (`ana-s3-q-20220105.tflite`)
|
||||||
|
- Updated digital neural network file (`dig-s1-q-20220102.tflite`)
|
||||||
|
- Updated build environment to `Espressif 3.5.0`
|
||||||
|
|
||||||
|
## [10.3.0] - (2022-01-29)
|
||||||
|
|
||||||
|
Stability Increase
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Implemented LED flash dimming (`LEDIntensity`).
|
||||||
|
Remark: as auto illumination in the camera is used, this is rather for energy saving. It will not help reducing reflections
|
||||||
|
- Additional camera parameters: saturation, contrast (although not too much impact yet)
|
||||||
|
- Some readings will have removable "N"s that can not be removed automatically and are handled with an "error" --> no return value in the field "value" anymore (still reported back via field "raw value")
|
||||||
|
- Updated esp32 camera hardware driver
|
||||||
|
- Bug fix: MQTT, HTML improvements
|
||||||
|
|
||||||
|
**ATTENTION: The new ESP32 camera hardware driver is much more stable on newer OV2640 versions (no or much less reboots) but seems to be not fully compatible with older versions.**
|
||||||
|
|
||||||
|
If you have problem with stalled systems you can try the following
|
||||||
|
|
||||||
|
- Update the parameter `ImageQuality` to `12` instead of current value `5` (manually in the `config.ini`)
|
||||||
|
|
||||||
|
- If this is not helping, you might need to update your hardware or stay with version 9.2
|
||||||
|
|
||||||
|
## [10.2.0] - (2022-01-14)
|
||||||
|
|
||||||
|
Stability Increase
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Due to the updated camera driver, the image looks different and a new setup might be needed
|
||||||
|
|
||||||
|
- Update reference image
|
||||||
|
- Update Alignment marks
|
||||||
|
|
||||||
|
- Reduce reboot due to camera problems
|
||||||
|
|
||||||
|
- Update esp32-camera to new version (master as of 2022-01-09)
|
||||||
|
|
||||||
|
## [10.1.1] - (2022-01-12)
|
||||||
|
|
||||||
|
Stability Increase
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Bug Fix MQTT problem
|
||||||
|
- Issue:
|
||||||
|
- Changing from v9.x to 10.x the MQTT-parameter "Topic" was renamed into "MainTopic" to address multiple number meters. This renaming should have been done automatically in the background within the graphical configuration, but was not working. Instead the parameter "Topic" was deleted and "MainTopic" was set to disabled and "undefined".
|
||||||
|
- ToDo
|
||||||
|
- Update the `html.zip`
|
||||||
|
- If old `config.ini` available: copy it to `/config`, open the graphical configuration and save it again.
|
||||||
|
- If old `config.ini` not available: reset the parameter "MainTopic" within the `config.ini` manually
|
||||||
|
- Reboot
|
||||||
|
|
||||||
|
## [10.1.0] - (2022-01-09)
|
||||||
|
|
||||||
|
Stability Increase
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Reduce ESP32 frequency to 160MHz
|
||||||
|
|
||||||
|
- Update tflite (new source: <https://github.com/espressif/tflite-micro-esp-examples>)
|
||||||
|
|
||||||
|
- Update analog neural network (ana-s3-q-20220105.tflite)
|
||||||
|
|
||||||
|
- Update digital neural network (dig-s1-q-20220102.tflite)
|
||||||
|
|
||||||
|
- Increased web-server buffers
|
||||||
|
|
||||||
|
- bug fix: compiler compatibility
|
||||||
|
|
||||||
|
## [10.0.2] - (2022-01-01)
|
||||||
|
|
||||||
|
Stability Increase
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- NEW v10.0.2: Corrected JSON error
|
||||||
|
|
||||||
|
- Updated compiler toolchain to ESP-IDF 4.3
|
||||||
|
|
||||||
|
- Removal of memory leak
|
||||||
|
|
||||||
|
- Improved error handling during startup (check PSRAM and camera with remark in logfile)
|
||||||
|
|
||||||
|
- MQTT: implemented raw value additionally, removal of regex contrain
|
||||||
|
|
||||||
|
- Normalized Parameter `MaxRateValue` to "change per minute"
|
||||||
|
|
||||||
|
- HTML: improved input handling
|
||||||
|
|
||||||
|
- Corrected error handling: in case of error the old value, rate, timestamp are not transmitted any more
|
||||||
|
|
||||||
|
## [9.2.0] - (2021-12-02)
|
||||||
|
|
||||||
|
External Illumination
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Direct JSON access: `http://IP-ADRESS/json`
|
||||||
|
- Error message in log file in case camera error during startup
|
||||||
|
- Upgrade analog CNN to v9.1.0
|
||||||
|
- Upgrade digital CNN to v13.3.0 (added new images)
|
||||||
|
- html: support of different ports
|
||||||
|
|
||||||
|
## [9.1.1] - External Illumination (2021-11-16)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- NEW 9.1.1 bug fix: LED implemenetation
|
||||||
|
- External LEDs: change control mode (resolve bug with more than 2 LEDs)
|
||||||
|
- Additional info into log file
|
||||||
|
- Bug fix: decimal shift, html, log file
|
||||||
|
|
||||||
|
## [9.0.0] - External Illumination (2021-10-23)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Implementation of external illumination to adjust positioning, brightness and color of the illumination now set individually
|
||||||
|
- Technical details can be found in the wiki: <https://github.com/jomjol/AI-on-the-edge-device/wiki/External-LED>
|
||||||
|
<img src="https://raw.githubusercontent.com/jomjol/ai-on-the-edge-device/master/images/intern_vs_external.jpg" width="500">
|
||||||
|
- New housing published for external LEDs and small clearing: <https://www.thingiverse.com/thing:5028229>
|
||||||
|
|
||||||
|
## [8.5.0] - Multi Meter Support (2021-10-07)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Upgrade digital CNN to v13.1.0 (added new images)
|
||||||
|
- bug fix: wlan password with space, double digit output
|
||||||
|
|
||||||
|
## [8.4.0] - Multi Meter Support (2021-09-25)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- License change (remove MIT license, remark see below)
|
||||||
|
|
||||||
|
- html: show hostname in title and main page
|
||||||
|
|
||||||
|
- configuration:
|
||||||
|
|
||||||
|
- moved setting `ExtendedResolution` to individual number settings
|
||||||
|
- New parameter `IgnoreLeadingNaN` (delete leading NaN's specifically)
|
||||||
|
- **ATTENTION**: update of the `config.ini` needed (open, adjust `ExtendedResolution`, save)
|
||||||
|
|
||||||
|
- Bug fixing (html, images of recognized numbers)
|
||||||
|
|
||||||
|
**ATTENTION: LICENSE CHANGE - removal of MIT License.**
|
||||||
|
|
||||||
|
- Currently no licence published - copyright belongs to author
|
||||||
|
|
||||||
|
- If you are interested in a commercial usage or dedicated versions please contact the developer
|
||||||
|
- no limits to private usage
|
||||||
|
|
||||||
|
## [8.3.0] - Multi Meter Support (2021-09-12)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Upgrade digital CNN to v12.1.0 (added new images)
|
||||||
|
- Dedicated NaN handling, internal refactoring (CNN-Handling)
|
||||||
|
- HTML: confirmation after config.ini update
|
||||||
|
- Bug fixing
|
||||||
|
|
||||||
|
## [8.2.0] - Multi Meter Support (2021-08-24)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Improve server responsiveness
|
||||||
|
|
||||||
|
|
||||||
|
- Flow status and prevalue status in overview
|
||||||
|
- Improved prevalue handling
|
||||||
|
|
||||||
|
## [8.1.0] - Multi Meter Support (2021-08-12)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- GPIO: using the general mqtt main topic for GPIO
|
||||||
|
|
||||||
|
|
||||||
|
- Upgrade digital CNN to v12.0.0 (added new images)
|
||||||
|
- Update tfmicro to new master (2021-08-07)
|
||||||
|
- Bug fix: remove text in mqtt value, remove connect limit in wlan reconnet
|
||||||
|
|
||||||
|
## [8.0.5] - Multi Meter Support (2021-08-01)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- NEW 8.0.5: bug fix: saving prevalue
|
||||||
|
|
||||||
|
|
||||||
|
- NEW 8.0.4: bug fix: load config.ini after upgrade
|
||||||
|
- NEW 8.0.3: bug fix: reboot during `config.ini` handling, html error
|
||||||
|
- NEW 8.0.2: saving roundes prevalue, bug fix html server
|
||||||
|
- NEW 8.0.1: bug fix: html handling of parameter `FixedExposure` and `ImageSize`
|
||||||
|
- Dual / multi meter support (more than 1 number to be recognized)
|
||||||
|
This is implemented with the feature "number" on the ROI definition as well as selected options
|
||||||
|
- MQTT: standardization of the naming - including new topics (`json`, `freeMem`, `uptime`)c
|
||||||
|
- Preparation for extended GPIO support (thanks to Zwerk2k) - not tested and fully functional yet
|
||||||
|
- Bug fixing: html server, memory leak, MQTT connect, hostname, turn of flash LED
|
||||||
|
|
||||||
|
<span style="color: red;">**ATTENTION: the configuration and prevalue files are modified automatically and will not be backward compatible!**</span>
|
||||||
|
|
||||||
|
## [7.1.2] MQTT-Update - (2021-06-17)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- NEW: 7.1.2: bug fix setting hostname, Flash-LED not off during reboot
|
||||||
|
|
||||||
|
|
||||||
|
- NEW: 7.1.1: bug fix wlan password with "=" (again)
|
||||||
|
|
||||||
|
- MQTT error message: changes "no error", send retain flag
|
||||||
|
|
||||||
|
- Update wlan handling to esp-idf 4.1
|
||||||
|
|
||||||
|
- Upgrade digital CNN to v8.7.0 (added new images)
|
||||||
|
|
||||||
|
- Bug fix: MQTT, WLAN, LED-Controll, GPIO usage, fixed IP, calculation flow rate
|
||||||
|
|
||||||
|
## [7.0.1] MQTT-Update - (2021-05-13)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- NEW: 7.0.1: bug fix wlan password with "="
|
||||||
|
|
||||||
|
|
||||||
|
- Upgrade digital CNN to v8.5.0 (added new images)
|
||||||
|
|
||||||
|
- New MQTT topics: flow rate (units/minute), time stamp (last correct read readout)
|
||||||
|
|
||||||
|
- Update MQTT/Error topic to " " in case no error (instead of empty string)
|
||||||
|
|
||||||
|
- Portrait or landscape image orientation in rotated image (avoid cropping)
|
||||||
|
|
||||||
|
## [6.7.2] Image Processing in Memory - (2021-05-01)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- NEW 6.7.2: Updated html for setup modus - remove reboot on edit configuration)
|
||||||
|
|
||||||
|
|
||||||
|
- NEW 6.7.1: Improved stability of camera (back to v6.6.1) - remove black strips and areas
|
||||||
|
|
||||||
|
- Upgrade digital CNN to v8.3.0 (added new type of digits)
|
||||||
|
|
||||||
|
- Internal update: TFlite (v2.5), esp32cam, startup sequence
|
||||||
|
|
||||||
|
- Rollback to espressif v2.1.0, as v3.2.0 shows unstable reboot
|
||||||
|
|
||||||
|
- Bugfix: WLan-passwords, reset of hostname
|
||||||
|
|
||||||
|
## [6.6.1] Image Processing in Memory - (2021-04-05)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- NEW 6.6.1: failed SD card initialization indicated by fast blinking LED at startup
|
||||||
|
|
||||||
|
|
||||||
|
- Improved SD-card handling (increase compatibility with more type of cards)
|
||||||
|
|
||||||
|
## [6.5.0] Image Processing in Memory - (2021-03-25)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Upgrade digital CNN to v8.2.0 (added new type of digits)
|
||||||
|
|
||||||
|
|
||||||
|
- Supporting alignment structures in ROI definition
|
||||||
|
- Bug fixing: definition of hostname in `config.ini`
|
||||||
|
|
||||||
|
## [6.4.0] Image Processing in Memory - (2021-03-20)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Additional alignment marks for settings the ROIs (analog and digit)
|
||||||
|
|
||||||
|
|
||||||
|
- Upgrade analog CNN to v7.0.0 (added new type of pointer)
|
||||||
|
|
||||||
|
## [6.3.1] Image Processing in Memory - (2021-03-16)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- NEW: 6.3.1: bug fixing in initial edit reference image and `config.ini` (Spelling error in `InitialRotate`)
|
||||||
|
|
||||||
|
|
||||||
|
- Initial setup mode: bug fixing, error correction
|
||||||
|
- Bug-fixing
|
||||||
|
|
||||||
|
## [6.2.2] Image Processing in Memory - (2021-03-10)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- NEW 6.2.2: bug fixing
|
||||||
|
|
||||||
|
|
||||||
|
- NEW 6.2.1: Changed brightness and contrast to default if not enabled (resolves to bright images)
|
||||||
|
|
||||||
|
- Determination of fixed illumination settings during startup - speed up of 5s in each run
|
||||||
|
|
||||||
|
- Update digital CNN to v8.1.1 (additional digital images trained)
|
||||||
|
|
||||||
|
- Extended error message in MQTT error message
|
||||||
|
|
||||||
|
- Image brightness is now adjustable
|
||||||
|
|
||||||
|
- Bug fixing: minor topics
|
||||||
|
|
||||||
|
## [6.1.0] Image Processing in Memory - (2021-01-20)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Disabling of analog / digital counters in configuration
|
||||||
|
|
||||||
|
|
||||||
|
- Improved Alignment Algorithm (`AlignmentAlgo` = `Default`, `Accurate` , `Fast`)
|
||||||
|
- Analog counters: `ExtendedResolution` (last digit is extended by sub comma value of CNN)
|
||||||
|
- `config.ini`: additional parameter `hostname` (additional to wlan.ini)
|
||||||
|
- Switching of GPIO12/13 via http-interface: `/GPIO?GPIO=12&Status=high/low`
|
||||||
|
- Bug fixing: html configuration page, wlan password ("=" now possible)
|
||||||
|
|
||||||
|
## [6.0.0] Image Processing in Memory - (2021-01-02)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **Major change**: image processing fully in memory - no need of SD card buffer anymore
|
||||||
|
|
||||||
|
- Need to limit camera resolution to VGA (due to memory limits)
|
||||||
|
|
||||||
|
|
||||||
|
- MQTT: Last Will Testament (LWT) implemented: "connection lost" in case of connection lost to `TopicError`
|
||||||
|
- Disabled `CheckDigitIncreaseConsistency` in default configuration - must now be explicit enabled if needed
|
||||||
|
- Update digital CNN to v7.2.1 (additional digital images trained)
|
||||||
|
- Setting of arbitrary time server in `config.ini`
|
||||||
|
- Option for fixed IP-, DNS-Settings in `wlan.ini`
|
||||||
|
- Increased stability (internal image and camera handling)
|
||||||
|
- Bug fixing: edit digits, handling PreValue, html-bugs
|
||||||
|
|
||||||
|
## [5.0.0] Setup Modus - (2020-12-06)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Implementation of initial setup modus for fresh installation
|
||||||
|
|
||||||
|
|
||||||
|
- Code restructuring (full compatibility between pure ESP-IDF and Platformio w/ espressif)
|
||||||
|
|
||||||
|
## [4.1.1] Configuration editor - (2020-12-02)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Bug fixing: internal improvement of file handling (reduce not responding)
|
||||||
|
|
||||||
|
## [4.1.0] Configuration editor - (2020-11-30)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Implementation of configuration editor (including basic and expert mode)
|
||||||
|
|
||||||
|
|
||||||
|
- Adjustable time zone to adjust to local time setting (incl. daylight saving time)
|
||||||
|
|
||||||
|
- MQTT: additional topic for error reporting
|
||||||
|
|
||||||
|
- standardized access to current logfile via `http://IP-ADRESS/logfileact`
|
||||||
|
|
||||||
|
- Update digital CNN to v7.2.0, analog CNN to 6.3.0
|
||||||
|
|
||||||
|
- Bug fixing: truncation error, CheckDigitConsistency & PreValue implementation
|
||||||
|
|
||||||
|
## [4.0.0] Tflite Core - (2020-11-15)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Implementation of rolling log-files
|
||||||
|
|
||||||
|
|
||||||
|
- Update Tflite-Core to master@20201108 (v2.4)
|
||||||
|
|
||||||
|
- Bug-fixing for reducing reboots
|
||||||
|
|
||||||
|
## [3.1.0] MQTT-Client - (2020-10-26)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Update digital CNN to v6.5.0 and HTML (Info to hostname, IP, ssid)
|
||||||
|
|
||||||
|
- New implementation of "checkDigitConsistency" also for digits
|
||||||
|
|
||||||
|
- MQTT-Adapter: user and password for sign in MQTT-Broker
|
||||||
|
|
||||||
|
## [3.0.0] MQTT-Client (2020-10-14)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Implementation of MQTT Client
|
||||||
|
|
||||||
|
|
||||||
|
- Improved Version Control
|
||||||
|
- bug-fixing
|
||||||
|
|
||||||
|
## [2.2.1] Version Control (2020-09-27)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Bug-Fixing (hostname in wlan.ini and error handling inside flow)
|
||||||
|
|
||||||
|
## \[2.2.0| Version Control (2020-09-27)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Integrated automated versioning system (menu: SYSTEM --> INFO)
|
||||||
|
|
||||||
|
|
||||||
|
- Update Build-System to PlatformIO - Espressif 32 v2.0.0 (ESP-IDF 4.1)
|
||||||
|
|
||||||
|
## [2.1.0] Decimal Shift, Chrome & Edge (2020-09-25)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Implementation of Decimal Shift
|
||||||
|
|
||||||
|
|
||||||
|
- Update default CNN for digits to v6.4.0
|
||||||
|
|
||||||
|
- Improvement HTML
|
||||||
|
|
||||||
|
- Support for Chrome and Edge
|
||||||
|
|
||||||
|
- Reduce logging to minimum - extended logging on demand
|
||||||
|
|
||||||
|
- Implementation of hostname in wlan.ini (`hostname = "HOSTNAME")`
|
||||||
|
|
||||||
|
- Bug fixing, code corrections
|
||||||
|
|
||||||
|
## [2.0.0] Layout update (2020-09-12)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Update to **new and modern layout**
|
||||||
|
- Support for Chrome improved
|
||||||
|
- Improved robustness: improved error handling in auto flow reduces spontaneous reboots
|
||||||
|
- File server: Option for "DELETE ALL"
|
||||||
|
- WLan: support of spaces in SSID and password
|
||||||
|
- Reference Image: Option for mirror image, option for image update on the fly
|
||||||
|
- additional parameter in `wasserzaehler.html?noerror=true` to suppress an potential error message
|
||||||
|
- bug fixing
|
||||||
|
|
||||||
|
## [1.1.3](2020-09-09)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **Bug in configuration of analog ROIs corrected** - correction in v.1.0.2 did not work properly
|
||||||
|
|
||||||
|
|
||||||
|
- Improved update page for the web server (`/html` can be updated via a zip-file, which is provided in `/firmware/html.zip`)
|
||||||
|
- Improved Chrome support
|
||||||
|
|
||||||
|
## [1.1.0](2020-09-06)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Implementation of "delete complete directory"
|
||||||
|
**Attention: beside the `firmware.bin`, also the content of `/html` needs to be updated!**
|
||||||
|
|
||||||
|
## [1.0.2](2020-09-06)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Bug in configuration of analog ROIs corrected
|
||||||
|
|
||||||
|
|
||||||
|
- minor bug correction
|
||||||
|
|
||||||
|
## [1.0.1](2020-09-05)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- preValue.ini Bug corrected
|
||||||
|
|
||||||
|
|
||||||
|
- minor bug correction
|
||||||
|
|
||||||
|
## [1.0.0](2020-09-04)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **First usable version** - compatible to previous project (<https://github.com/jomjol/water-meter-system-complete>)
|
||||||
|
|
||||||
|
|
||||||
|
- NEW:
|
||||||
|
- no docker container for CNN calculation necessary
|
||||||
|
- web based configuration editor on board
|
||||||
|
|
||||||
|
## [0.1.0](2020-08-07)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Initial Version
|
||||||
|
|
||||||
|
|
||||||
|
[14.0.0]: https://github.com/jomjol/AI-on-the-edge-device/compare/v13.0.8...v14.0.2
|
||||||
|
[13.0.8]: https://github.com/jomjol/AI-on-the-edge-device/compare/v12.0.1...v13.0.8
|
||||||
|
[13.0.7]: https://github.com/jomjol/AI-on-the-edge-device/compare/v12.0.1...v13.0.7
|
||||||
|
[13.0.5]: https://github.com/jomjol/AI-on-the-edge-device/compare/v12.0.1...v13.0.5
|
||||||
|
[13.0.4]: https://github.com/jomjol/AI-on-the-edge-device/compare/v12.0.1...v13.0.4
|
||||||
|
[13.0.1]: https://github.com/jomjol/AI-on-the-edge-device/compare/v12.0.1...v13.0.1
|
||||||
|
[12.0.1]: https://github.com/jomjol/AI-on-the-edge-device/compare/v11.3.1...v12.0.1
|
||||||
|
[11.4.3]: https://github.com/haverland/AI-on-the-edge-device/compare/v10.6.2...v11.4.3
|
||||||
|
[11.4.2]: https://github.com/haverland/AI-on-the-edge-device/compare/v10.6.2...v11.4.2
|
||||||
|
[11.3.9]: https://github.com/haverland/AI-on-the-edge-device/compare/v10.6.2...v11.3.9
|
||||||
|
|||||||
280
FeatureRequest.md
Normal file
280
FeatureRequest.md
Normal file
@@ -0,0 +1,280 @@
|
|||||||
|
## Feature Requests
|
||||||
|
|
||||||
|
**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.
|
||||||
|
|
||||||
|
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!
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
____
|
||||||
|
#### #34 implement state and Roi for water leak detection
|
||||||
|
for example see Roi on the next picture..
|
||||||
|

|
||||||
|
in case of position change between the measurments set this state to true, if there is no change set it back to false.
|
||||||
|
In a defined time window this movement can lead into an alarm state / water leak..
|
||||||
|
haveing this state in the mqtt broker can trigger functions like closing the ater pipe walve and so on...
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### #33 Implement MATTER protocoll
|
||||||
|
|
||||||
|
* see [#1404](https://github.com/jomjol/AI-on-the-edge-device/issues/1404)
|
||||||
|
|
||||||
|
#### #32 Add feature to correct misinterpreted value
|
||||||
|
|
||||||
|
* If a value is misinterpreted, the user can manually correct the value.
|
||||||
|
* The misinterpreted ROIs would be saved in a "training data" -folder on the SD-card
|
||||||
|
* Stretch goal: make sending of saved training data as easy as pushing a button =)
|
||||||
|
|
||||||
|
#### #31 Implement InfluxDB v2.x interface
|
||||||
|
|
||||||
|
* Currently only InfluxDB v1.x is supportet, extend to v2.x
|
||||||
|
* Remark: interface has changed
|
||||||
|
* see [#1160](https://github.com/jomjol/AI-on-the-edge-device/issues/1160)
|
||||||
|
|
||||||
|
#### #30 Support meter clock over
|
||||||
|
|
||||||
|
* In case of meter clocking over, that is, reaching its max. value and starting over from 0,
|
||||||
|
accept the new value and calculate correctly the difference.
|
||||||
|
(see line 739 onwards in ClassFlowPostProcessing.cpp)
|
||||||
|
|
||||||
|
#### ~~#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~~
|
||||||
|
|
||||||
|
#### #28 Improved error handling for ROIs
|
||||||
|
|
||||||
|
* In case a ROI is out of the image, there is no error message, but a non sense image is used
|
||||||
|
* Implement a error message for wrong configuratioin of ROI
|
||||||
|
|
||||||
|
#### #27 Use Homie Spec for Mqtt binding
|
||||||
|
|
||||||
|
* Use the standardized Home Protocol for the Mqtt binding
|
||||||
|
* https://homieiot.github.io/
|
||||||
|
|
||||||
|
#### #26 Changes behaviour for "N" replacement
|
||||||
|
|
||||||
|
* in case the higher digits has already increased by minium 1 - don't set the "N" to the last value, but to "0"
|
||||||
|
* https://github.com/jomjol/AI-on-the-edge-device/issues/792
|
||||||
|
|
||||||
|
|
||||||
|
#### #25 Trigger Measurement via MQTT
|
||||||
|
|
||||||
|
* https://github.com/jomjol/AI-on-the-edge-device/issues/727
|
||||||
|
|
||||||
|
|
||||||
|
#### #24 Show Mqtt state directly in Webserver
|
||||||
|
|
||||||
|
* Show MQTT log in Web page. E.g. connection established or failed to connect...
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### #23 CPU Temp and Mqtt values
|
||||||
|
|
||||||
|
* Show the CPU Temp directly in Webpage. Also add the value to MQTT sending
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### ~~#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~~
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### #21 Extended "CheckDigitalConsistency" Logik
|
||||||
|
|
||||||
|
* https://github.com/jomjol/AI-on-the-edge-device/issues/590
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### #20 Deep sleep and push mode
|
||||||
|
|
||||||
|
* Let the device be normally in deep sleep state, and wake it up periodically to collect data and push it via MQTT or HTTP post.
|
||||||
|
* Support ESP-NOW to reduce the overhead of connecting to wifi and mqtt
|
||||||
|
* the above should enable battery powered applications
|
||||||
|
|
||||||
|
* An other way to set deep sleep would be to enable it in a specific period (at night).
|
||||||
|
|
||||||
|
|
||||||
|
#### #19 Extended log informations
|
||||||
|
|
||||||
|
* https://github.com/jomjol/AI-on-the-edge-device/issues/580
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### ~~#18 Document WLAN-strength in web page~~
|
||||||
|
|
||||||
|
* ~~https://github.com/jomjol/AI-on-the-edge-device/issues/563~~
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### ~~#17 Direct InfluxDB connection~~
|
||||||
|
|
||||||
|
* ~~Done in v10.6.0~~
|
||||||
|
|
||||||
|
|
||||||
|
#### #16 Serial Communication
|
||||||
|
|
||||||
|
* https://github.com/jomjol/AI-on-the-edge-device/issues/512
|
||||||
|
* Send the readout value via RX/TX interface with a dedicated TAG
|
||||||
|
* Make dedicated communication FlowModule
|
||||||
|
* Modification of RX/TX communication
|
||||||
|
* Configuration interfache
|
||||||
|
|
||||||
|
|
||||||
|
#### #15 Calibration for FishEye image
|
||||||
|
|
||||||
|
* https://github.com/jomjol/AI-on-the-edge-device/issues/507
|
||||||
|
|
||||||
|
1. The development of such a correction algorithm with the libraries, that are available for the ESP32 environment.
|
||||||
|
2. New module for integration of the flow into the image processing flow.
|
||||||
|
3. Extension of the configuration (config.ini) and html-pages
|
||||||
|
4. Parameter adjustment and testing for every different fish-eye module
|
||||||
|
5. Maintenance for further updates / modules, ...
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### ~~#14 Backup and restore option for configuration~~- implemented v11.3.1
|
||||||
|
|
||||||
|
* ~~https://github.com/jomjol/AI-on-the-edge-device/issues/459~~
|
||||||
|
|
||||||
|
* ~~Implement a zip file compression for store and restore~~
|
||||||
|
|
||||||
|
* ~~Update the html to handle it~~
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### #13 Manage non linear gauge without CNN re-training
|
||||||
|
|
||||||
|
* https://github.com/jomjol/AI-on-the-edge-device/issues/443
|
||||||
|
|
||||||
|
* Implement a look up table for non linear analog meters
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### ~~#12 Less reboots due to memory leakage~~
|
||||||
|
|
||||||
|
* ~~Issue: #414 & #425 #430~~
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### #11 MQTT - configurable payload
|
||||||
|
|
||||||
|
* https://github.com/jomjol/AI-on-the-edge-device/issues/344
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### #10 Improve and bug fix logging of images
|
||||||
|
|
||||||
|
* https://github.com/jomjol/AI-on-the-edge-device/issues/307
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### #9 Basic auth for the UI
|
||||||
|
|
||||||
|
* https://github.com/jomjol/AI-on-the-edge-device/issues/283
|
||||||
|
|
||||||
|
* Implementation of an authentication mechanism.
|
||||||
|
|
||||||
|
#### #8 MQTT configurable readout intervall
|
||||||
|
|
||||||
|
Make the readout intervall configurable via MQTT.
|
||||||
|
|
||||||
|
* Change the mqtt part to receive and process input and not only sending
|
||||||
|
|
||||||
|
#### #7 Extended Error Handling
|
||||||
|
|
||||||
|
Check different types of error (e.g. tflite not availabe) and generate an error on the html page.
|
||||||
|
|
||||||
|
To do:
|
||||||
|
|
||||||
|
* Make a list of "important" errors
|
||||||
|
* Implement a checking algo
|
||||||
|
* Extend the firmware and html page for the error handling
|
||||||
|
|
||||||
|
#### ~~#6 Check for double ROI names~~ - implemented v8.0.0
|
||||||
|
|
||||||
|
~~Check during configuration, that ROI names are unique.~~
|
||||||
|
|
||||||
|
~~To do:~~
|
||||||
|
|
||||||
|
* ~~Implementation of ROI name checking in html code before saving analog or digital ROIs~~
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### #5 Configurable decimal separator (point or comma)
|
||||||
|
|
||||||
|
Decimal separator configurable for different systems
|
||||||
|
|
||||||
|
To do:
|
||||||
|
|
||||||
|
* Implementation of decimal point into postprocessing module
|
||||||
|
* Extension of configuration
|
||||||
|
* Adaption of the html configuration to implement shifting
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### ~~#4 Initial Shifting and Rotation~~ - implemented v7.0.0
|
||||||
|
|
||||||
|
* ~~https://github.com/jomjol/AI-on-the-edge-device/issues/123~~
|
||||||
|
|
||||||
|
~~Implementation of a shifting additional to the initial rotation of the raw camera input~~
|
||||||
|
|
||||||
|
~~To do:~~
|
||||||
|
|
||||||
|
* ~~Implementation of shifting~~
|
||||||
|
* ~~Extension of configuration~~
|
||||||
|
* ~~Adaption of the html configuration to implement shifting~~
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### ~~#3 Allow grouping of digits to multiple reading values~~ - implemented v8.0.0
|
||||||
|
|
||||||
|
* ~~https://github.com/jomjol/AI-on-the-edge-device/issues/123~~
|
||||||
|
|
||||||
|
~~Implementation of two different independent readouts in one setup~~
|
||||||
|
|
||||||
|
~~To do:~~
|
||||||
|
|
||||||
|
* ~~Extend the configuration, setting and processing flow for two independend readouts~~
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
____
|
||||||
|
|
||||||
|
#### #2 MQTT-controll with callback
|
||||||
|
* https://github.com/jomjol/AI-on-the-edge-device/issues/105
|
||||||
|
|
||||||
|
Extend the MQTT client to also enable callbacks for configuration setting
|
||||||
|
|
||||||
|
To do:
|
||||||
|
|
||||||
|
* implement callback for receiving information and override `config.ini` settings
|
||||||
|
|
||||||
|
* change configuration management to handle online updates (currently changes need a restart)
|
||||||
|
|
||||||
|
* think about the startup, as there the default config is loaded
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
____
|
||||||
|
|
||||||
|
#### ~~#1 Optional GPIO for external flash/lighting~~ - implemented (v8.0.0)
|
||||||
|
|
||||||
|
* ~~https://github.com/jomjol/AI-on-the-edge-device/issues/133~~
|
||||||
|
|
||||||
|
~~Implementation of an an extrnal flash / lightning through GPIOs.~~
|
||||||
|
|
||||||
|
* ~~available GPIOs: 12 & 13 (currently in use for html switching)~~
|
||||||
|
|
||||||
|
~~To do:~~
|
||||||
|
|
||||||
|
* ~~Implementation of a software module for external light source (e.g. WS8132 LED controller, ...)~~
|
||||||
|
* ~~Update of the camera module to use the external light instead of the internal flash light~~
|
||||||
|
* ~~Adopt the configuration algorithm with a configurable light source~~
|
||||||
184
README.md
184
README.md
@@ -1,108 +1,102 @@
|
|||||||
# AI-on-the-edge-device
|
# Welcome to the AI-on-the-edge-device
|
||||||
|
<img src="images/icon/watermeter.svg" width="100px">
|
||||||
|
|
||||||
This is an example of Artificial Intelligence (AI) calculations on a very cheap hardware.
|
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**.
|
||||||
|
|
||||||
### Details on **function**, **installation** and **configuration** can be found on the **[Wiki Page](https://github.com/jomjol/AI-on-the-edge-device/wiki)**
|
This projects allows you to digitalize your **analoge** water, gas, power and other meters using cheap and easily available hardware.
|
||||||
|
|
||||||
A 3d-printable housing can be found here: https://www.thingiverse.com/thing:4571627
|
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.
|
||||||
|
|
||||||
|
<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
|
||||||
|
- Full integration into Homeassistant
|
||||||
|
- Support for Influx DB 1
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
<img src="https://raw.githubusercontent.com/jomjol/AI-on-the-edge-device/master/images/idea.jpg" width="600">
|
||||||
|
|
||||||
|
## Impressions
|
||||||
|
### AI-on-the-edge-device on a Water Meter
|
||||||
<img src="https://raw.githubusercontent.com/jomjol/AI-on-the-edge-device/master/images/watermeter_all.jpg" width="200"><img src="https://raw.githubusercontent.com/jomjol/AI-on-the-edge-device/master/images/main.jpg" width="200"><img src="https://raw.githubusercontent.com/jomjol/AI-on-the-edge-device/master/images/size.png" width="200">
|
<img src="https://raw.githubusercontent.com/jomjol/AI-on-the-edge-device/master/images/watermeter_all.jpg" width="200"><img src="https://raw.githubusercontent.com/jomjol/AI-on-the-edge-device/master/images/main.jpg" width="200"><img src="https://raw.githubusercontent.com/jomjol/AI-on-the-edge-device/master/images/size.png" width="200">
|
||||||
|
|
||||||
|
### Web Interface (Water Meter)
|
||||||
<img src="https://raw.githubusercontent.com/jomjol/AI-on-the-edge-device/master/images/watermeter.jpg" width="600">
|
<img src="https://raw.githubusercontent.com/jomjol/AI-on-the-edge-device/master/images/watermeter.jpg" width="600">
|
||||||
<img src="https://raw.githubusercontent.com/jomjol/AI-on-the-edge-device/master/images/edit_reference.jpg" width="600">
|
|
||||||
|
### AI-on-the-edge-device on a Electrical Power Meter
|
||||||
|
<img src="https://raw.githubusercontent.com/jomjol/AI-on-the-edge-device/master/images/powermeter.jpg" width="600">
|
||||||
|
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
## Change log
|
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)
|
||||||
|
|
||||||
|
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).
|
||||||
|
|
||||||
|
### 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).
|
||||||
|
|
||||||
|
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)
|
||||||
|
- Flash Tool from Espressif
|
||||||
|
- ESPtool (Command Line Tool)
|
||||||
|
|
||||||
|
See the [Docu](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.
|
||||||
|
|
||||||
|
## Casing
|
||||||
|
|
||||||
|
A 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).
|
||||||
|
|
||||||
|
## 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).
|
||||||
|
|
||||||
|
<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).
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
## 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/)
|
||||||
|
|
||||||
|
## 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)
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
### Known Issues
|
|
||||||
|
|
||||||
* spontaneous reboot, especially in case of intensive web server access (improved since v2.1.0)
|
|
||||||
|
|
||||||
------
|
|
||||||
|
|
||||||
**General remark:** Beside the `firmware.bin`, typically also the content of `/html` needs to be updated!
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
##### Rolling - (2020-09-25)
|
|
||||||
|
|
||||||
* based on v2.1.0 (2020-09-25)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
##### 2.1.0 Layout update (2020-09-25)
|
|
||||||
|
|
||||||
* Implementation of Decimal Shift
|
|
||||||
|
|
||||||
* Update default CNN for digits to v6.4.0
|
|
||||||
|
|
||||||
* Improvement HTML
|
|
||||||
|
|
||||||
* Support for Chrome and Firefox
|
|
||||||
|
|
||||||
* Reduce logging to minimum - extended logging on demand
|
|
||||||
|
|
||||||
* Implementation of hostname in wlan.ini (`hostname = "HOSTNAME")`
|
|
||||||
|
|
||||||
* Bug fixing, code corrections
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
##### 2.0.0 Layout update (2020-09-12)
|
|
||||||
|
|
||||||
* Update to **new and modern layout**
|
|
||||||
* Support for Chrome improved
|
|
||||||
* Improved robustness: improved error handling in auto flow reduces spontaneous reboots
|
|
||||||
* File server: Option for "DELETE ALL"
|
|
||||||
* WLan: support of spaces in SSID and password
|
|
||||||
* Reference Image: Option for mirror image, option for image update on the fly
|
|
||||||
* additional parameter in `wasserzaehler.html?noerror=true` to suppress an potential error message
|
|
||||||
* bug fixing
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
##### 1.1.3 (2020-09-09)
|
|
||||||
|
|
||||||
* **Bug in configuration of analog ROIs corrected** - correction in v.1.0.2 did not work properly
|
|
||||||
* Improved update page for the web server (`/html` can be updated via a zip-file, which is provided in `/firmware/html.zip`)
|
|
||||||
* Improved Chrome support
|
|
||||||
|
|
||||||
##### 1.1.0 (2020-09-06)
|
|
||||||
|
|
||||||
* Implementation of "delete complete directory"
|
|
||||||
**Attention: beside the `firmware.bin`, also the content of `/html` needs to be updated!**
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
##### 1.0.2 (2020-09-06)
|
|
||||||
|
|
||||||
* Bug in configuration of analog ROIs corrected
|
|
||||||
* minor bug correction
|
|
||||||
|
|
||||||
##### 1.0.1 (2020-09-05)
|
|
||||||
|
|
||||||
* preValue.ini Bug corrected
|
|
||||||
* minor bug correction
|
|
||||||
|
|
||||||
##### 1.0.0 (2020-09-04)
|
|
||||||
|
|
||||||
* **First usable version** - compatible to previous project (https://github.com/jomjol/water-meter-system-complete)
|
|
||||||
* NEW:
|
|
||||||
* no docker container for CNN calculation necessary
|
|
||||||
* web based configuration editor on board
|
|
||||||
|
|
||||||
##### 0.1.0 (2020-08-07)
|
|
||||||
|
|
||||||
* Initial Version
|
|
||||||
|
|
||||||
|
|
||||||
#### [Full Changelog](Changelog.md)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Solved topics
|
|
||||||
|
|
||||||
* n.a.
|
|
||||||
11
code/.gitignore
vendored
11
code/.gitignore
vendored
@@ -3,4 +3,13 @@
|
|||||||
.vscode/c_cpp_properties.json
|
.vscode/c_cpp_properties.json
|
||||||
.vscode/launch.json
|
.vscode/launch.json
|
||||||
.vscode/ipch
|
.vscode/ipch
|
||||||
.helper
|
version.cpp
|
||||||
|
sdkconfig.esp32cam
|
||||||
|
sdkconfig.esp32cam-dev
|
||||||
|
sdkconfig.esp32cam-debug
|
||||||
|
sdkconfig.esp32cam-board-rev3
|
||||||
|
sdkconfig.esp32cam-cpu-freq-240
|
||||||
|
sdkconfig.esp32cam-board-rev3-cpu-freq-240
|
||||||
|
sdkconfig.esp32cam-dev-himem
|
||||||
|
sdkconfig.esp32cam-dev-task-analysis
|
||||||
|
sdkconfig.esp32cam-no-softap
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
copy "C:\Users\Muell\Documents\Programmieren\GitHub\AI-on-the-edge-device\code\.pio\build\esp32cam\firmware.bin" "C:\Users\Muell\Documents\Programmieren\GitHub\AI-on-the-edge-device\firmware\firmware.bin"
|
copy "..\..\code\.pio\build\esp32cam\firmware.bin" "..\..\firmware\firmware.bin"
|
||||||
copy "C:\Users\Muell\Documents\Programmieren\GitHub\AI-on-the-edge-device\code\.pio\build\esp32cam\bootloader.bin" "C:\Users\Muell\Documents\Programmieren\GitHub\AI-on-the-edge-device\firmware\bootloader.bin"
|
copy "..\..\code\.pio\build\esp32cam\bootloader.bin" "..\..\firmware\bootloader.bin"
|
||||||
copy "C:\Users\Muell\Documents\Programmieren\GitHub\AI-on-the-edge-device\code\.pio\build\esp32cam\partitions.bin" "C:\Users\Muell\Documents\Programmieren\GitHub\AI-on-the-edge-device\firmware\partitions.bin"
|
copy "..\..\code\.pio\build\esp32cam\partitions.bin" "..\..\firmware\partitions.bin"
|
||||||
1
code/.helper/makezip.bat
Normal file
1
code/.helper/makezip.bat
Normal file
@@ -0,0 +1 @@
|
|||||||
|
powershell Compress-Archive -Path "..\..\sd-card\html\*.*" -DestinationPath "..\..\firmware\html.zip"
|
||||||
@@ -1,8 +1,22 @@
|
|||||||
cmake_minimum_required(VERSION 3.16.0)
|
cmake_minimum_required(VERSION 3.16.0)
|
||||||
|
|
||||||
list(APPEND EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)
|
list(APPEND EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common components/tflite-micro-esp-examples/components/tflite-lib)
|
||||||
|
|
||||||
set(PROJECT_VER "0.0.9.3")
|
ADD_CUSTOM_COMMAND(
|
||||||
|
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/version.cpp
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/_version.cpp
|
||||||
|
COMMAND ${CMAKE_COMMAND} -P
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/version.cmake)
|
||||||
|
|
||||||
|
if(EXISTS "${SDKCONFIG}.defaults")
|
||||||
|
if(EXISTS "sdkconfig.defaults")
|
||||||
|
set(SDKCONFIG_DEFAULTS "${SDKCONFIG}.defaults;sdkconfig.defaults")
|
||||||
|
message(STATUS "-- Using defaults: ${SDKCONFIG_DEFAULTS} + sdkconfig.defaults")
|
||||||
|
else()
|
||||||
|
set(SDKCONFIG_DEFAULTS "${SDKCONFIG}.defaults")
|
||||||
|
endif()
|
||||||
|
message(STATUS "-- Using defaults: ${SDKCONFIG_DEFAULTS}")
|
||||||
|
endif()
|
||||||
|
|
||||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||||
project(esp32cam-server-only)
|
project(AI-on-the-edge)
|
||||||
|
|||||||
62
code/README.md
Normal file
62
code/README.md
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
# Build
|
||||||
|
|
||||||
|
## Preparations
|
||||||
|
```
|
||||||
|
git clone https://github.com/jomjol/AI-on-the-edge-device.git
|
||||||
|
cd AI-on-the-edge-device
|
||||||
|
git checkout rolling
|
||||||
|
git submodule update --init
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build and Flash within terminal
|
||||||
|
See further down to build it within an IDE.
|
||||||
|
### Compile
|
||||||
|
```
|
||||||
|
cd code
|
||||||
|
platformio run --environment esp32cam
|
||||||
|
```
|
||||||
|
|
||||||
|
### Upload
|
||||||
|
```
|
||||||
|
pio run --target upload --upload-port /dev/ttyUSB0
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively you also can set the UART device in `platformio.ini`, eg. `upload_port = /dev/ttyUSB0`
|
||||||
|
|
||||||
|
### Monitor UART Log
|
||||||
|
```
|
||||||
|
pio device monitor -p /dev/ttyUSB0
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build and Flash with Visual Code IDE
|
||||||
|
|
||||||
|
- Download and install VS Code
|
||||||
|
- https://code.visualstudio.com/Download
|
||||||
|
- Install the VS Code platform io plugin
|
||||||
|
- <img src="https://raw.githubusercontent.com/jomjol/ai-on-the-edge-device/master/images/platformio_plugin.jpg" width="200" align="middle">
|
||||||
|
- Check for error messages, maybe you need to manually add some python libraries
|
||||||
|
- e.g. in my Ubuntu a python3-env was missing: `sudo apt-get install python3-venv`
|
||||||
|
- git clone this project
|
||||||
|
- in Linux:
|
||||||
|
|
||||||
|
```
|
||||||
|
git clone https://github.com/jomjol/AI-on-the-edge-device.git
|
||||||
|
cd AI-on-the-edge-device
|
||||||
|
git checkout rolling
|
||||||
|
git submodule update --init
|
||||||
|
```
|
||||||
|
|
||||||
|
- in VS code, open the `AI-on-the-edge-device/code`
|
||||||
|
- from terminal: `cd AI-on-the-edge-device/code && code .`
|
||||||
|
- open a pio terminal (click on the terminal sign in the bottom menu bar)
|
||||||
|
- make sure you are in the `code` directory
|
||||||
|
- To build, type `platformio run --environment esp32cam`
|
||||||
|
- or use the graphical interface:
|
||||||
|
<img src="https://raw.githubusercontent.com/jomjol/ai-on-the-edge-device/master/images/platformio_build.jpg" width="200" align="middle">
|
||||||
|
- the build artifacts are stored in `code/.pio/build/esp32cam/`
|
||||||
|
- Connect the device and type `pio device monitor`. There you will see your device and can copy the name to the next instruction
|
||||||
|
- Add `upload_port = you_device_port` to the `platformio.ini` file
|
||||||
|
- make sure an sd card with the contents of the `sd_card` folder is inserted and you have changed the wifi details
|
||||||
|
- `pio run --target erase` to erase the flash
|
||||||
|
- `pio run --target upload` this will upload the `bootloader.bin, partitions.bin,firmware.bin` from the `code/.pio/build/esp32cam/` folder.
|
||||||
|
- `pio device monitor` to observe the logs via uart
|
||||||
1
code/components/esp-nn
Submodule
1
code/components/esp-nn
Submodule
Submodule code/components/esp-nn added at 6b3ef8e226
1
code/components/esp32-camera
Submodule
1
code/components/esp32-camera
Submodule
Submodule code/components/esp32-camera added at 5c8349f4cf
7
code/components/jomjol_configfile/CMakeLists.txt
Normal file
7
code/components/jomjol_configfile/CMakeLists.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
FILE(GLOB_RECURSE app_sources ${CMAKE_CURRENT_SOURCE_DIR}/*.*)
|
||||||
|
|
||||||
|
idf_component_register(SRCS ${app_sources}
|
||||||
|
INCLUDE_DIRS "."
|
||||||
|
REQUIRES jomjol_logfile)
|
||||||
|
|
||||||
|
|
||||||
86
code/components/jomjol_configfile/configFile.cpp
Normal file
86
code/components/jomjol_configfile/configFile.cpp
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
#include <string.h>
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
|
||||||
|
#include "Helper.h"
|
||||||
|
#include "configFile.h"
|
||||||
|
#include <esp_log.h>
|
||||||
|
|
||||||
|
#include "../../include/defines.h"
|
||||||
|
|
||||||
|
static const char *TAG = "CONFIG";
|
||||||
|
|
||||||
|
ConfigFile::ConfigFile(std::string filePath)
|
||||||
|
{
|
||||||
|
std::string config = FormatFileName(filePath);
|
||||||
|
pFile = fopen(config.c_str(), "r");
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigFile::~ConfigFile()
|
||||||
|
{
|
||||||
|
fclose(pFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ConfigFile::isNewParagraph(std::string input)
|
||||||
|
{
|
||||||
|
if ((input[0] == '[') || ((input[0] == ';') && (input[1] == '[')))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ConfigFile::GetNextParagraph(std::string& aktparamgraph, bool &disabled, bool &eof)
|
||||||
|
{
|
||||||
|
while (getNextLine(&aktparamgraph, disabled, eof) && !isNewParagraph(aktparamgraph));
|
||||||
|
|
||||||
|
if (isNewParagraph(aktparamgraph))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ConfigFile::getNextLine(std::string *rt, bool &disabled, bool &eof)
|
||||||
|
{
|
||||||
|
eof = false;
|
||||||
|
char zw[1024] = "";
|
||||||
|
if (pFile == NULL)
|
||||||
|
{
|
||||||
|
*rt = "";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fgets(zw, 1024, pFile))
|
||||||
|
{
|
||||||
|
ESP_LOGD(TAG, "%s", zw);
|
||||||
|
if ((strlen(zw) == 0) && feof(pFile))
|
||||||
|
{
|
||||||
|
*rt = "";
|
||||||
|
eof = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
*rt = "";
|
||||||
|
eof = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*rt = zw;
|
||||||
|
*rt = trim(*rt);
|
||||||
|
while ((zw[0] == ';' || zw[0] == '#' || (rt->size() == 0)) && !(zw[1] == '['))
|
||||||
|
{
|
||||||
|
fgets(zw, 1024, pFile);
|
||||||
|
ESP_LOGD(TAG, "%s", zw);
|
||||||
|
if (feof(pFile))
|
||||||
|
{
|
||||||
|
*rt = "";
|
||||||
|
eof = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*rt = zw;
|
||||||
|
*rt = trim(*rt);
|
||||||
|
}
|
||||||
|
|
||||||
|
disabled = ((*rt)[0] == ';');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
23
code/components/jomjol_configfile/configFile.h
Normal file
23
code/components/jomjol_configfile/configFile.h
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef CONFIGFILE_H
|
||||||
|
#define CONFIGFILE_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class ConfigFile {
|
||||||
|
public:
|
||||||
|
ConfigFile(std::string filePath);
|
||||||
|
~ConfigFile();
|
||||||
|
|
||||||
|
bool isNewParagraph(std::string input);
|
||||||
|
bool GetNextParagraph(std::string& aktparamgraph, bool &disabled, bool &eof);
|
||||||
|
bool getNextLine(std::string* rt, bool &disabled, bool &eof);
|
||||||
|
bool ConfigFileExists(){return pFile;};
|
||||||
|
|
||||||
|
private:
|
||||||
|
FILE* pFile;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //CONFIGFILE_H
|
||||||
9
code/components/jomjol_controlGPIO/CMakeLists.txt
Normal file
9
code/components/jomjol_controlGPIO/CMakeLists.txt
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
FILE(GLOB_RECURSE app_sources ${CMAKE_CURRENT_SOURCE_DIR}/*.*)
|
||||||
|
|
||||||
|
list(APPEND EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)
|
||||||
|
|
||||||
|
idf_component_register(SRCS ${app_sources}
|
||||||
|
INCLUDE_DIRS "." "../../include"
|
||||||
|
REQUIRES esp_http_server jomjol_logfile jomjol_configfile jomjol_mqtt jomjol_flowcontroll)
|
||||||
|
|
||||||
|
|
||||||
132
code/components/jomjol_controlGPIO/Color.cpp
Normal file
132
code/components/jomjol_controlGPIO/Color.cpp
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
#include "Color.h"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Int -> fixed point
|
||||||
|
int up( int x ) { return x * 255; }
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int iRgbSqrt( int num ) {
|
||||||
|
// https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Binary_numeral_system_.28base_2.29
|
||||||
|
assert( "sqrt input should be non-negative" && num >= 0 );
|
||||||
|
assert( "sqrt input should no exceed 16 bits" && num <= 0xFFFF );
|
||||||
|
int res = 0;
|
||||||
|
int bit = 1 << 16;
|
||||||
|
while ( bit > num )
|
||||||
|
bit >>= 2;
|
||||||
|
while ( bit != 0 ) {
|
||||||
|
if ( num >= res + bit ) {
|
||||||
|
num -= res + bit;
|
||||||
|
res = ( res >> 1 ) + bit;
|
||||||
|
} else
|
||||||
|
res >>= 1;
|
||||||
|
bit >>= 2;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rgb::Rgb( Hsv y ) {
|
||||||
|
// https://stackoverflow.com/questions/24152553/hsv-to-rgb-and-back-without-floating-point-math-in-python
|
||||||
|
// greyscale
|
||||||
|
if( y.s == 0 ) {
|
||||||
|
r = g = b = y.v;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int region = y.h / 43;
|
||||||
|
const int remainder = ( y.h - ( region * 43 ) ) * 6;
|
||||||
|
|
||||||
|
const int p = ( y.v * ( 255 - y.s ) ) >> 8;
|
||||||
|
const int q = ( y.v * ( 255 - ( ( y.s * remainder ) >> 8 ) ) ) >> 8;
|
||||||
|
const int t = ( y.v * ( 255 - ( ( y.s * (255 -remainder ) ) >> 8 ) ) ) >> 8;
|
||||||
|
|
||||||
|
switch( region ) {
|
||||||
|
case 0: r = y.v; g = t; b = p; break;
|
||||||
|
case 1: r = q; g = y.v; b = p; break;
|
||||||
|
case 2: r = p; g = y.v; b = t; break;
|
||||||
|
case 3: r = p; g = q; b = y.v; break;
|
||||||
|
case 4: r = t; g = p; b = y.v; break;
|
||||||
|
case 5: r = y.v; g = p; b = q; break;
|
||||||
|
default: __builtin_trap();
|
||||||
|
}
|
||||||
|
|
||||||
|
a = y.a;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rgb& Rgb::operator=( Hsv hsv ) {
|
||||||
|
Rgb r{ hsv };
|
||||||
|
swap( r );
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rgb Rgb::operator+( Rgb in ) const {
|
||||||
|
auto copy = *this;
|
||||||
|
copy += in;
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rgb& Rgb::operator+=( Rgb in ) {
|
||||||
|
unsigned int red = r + in.r;
|
||||||
|
r = ( red < 255 ) ? red : 255;
|
||||||
|
unsigned int green = g + in.g;
|
||||||
|
g = ( green < 255 ) ? green : 255;
|
||||||
|
unsigned int blue = b + in.b;
|
||||||
|
b = ( blue < 255 ) ? blue : 255;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rgb& Rgb::blend( Rgb in ) {
|
||||||
|
unsigned int inAlpha = in.a * ( 255 - a );
|
||||||
|
unsigned int alpha = a + inAlpha;
|
||||||
|
r = iRgbSqrt( ( ( r * r * a ) + ( in.r * in.r * inAlpha ) ) / alpha );
|
||||||
|
g = iRgbSqrt( ( ( g * g * a ) + ( in.g * in.g * inAlpha ) ) / alpha );
|
||||||
|
b = iRgbSqrt( ( ( b * b * a ) + ( in.b * in.b * inAlpha ) ) / alpha );
|
||||||
|
a = alpha;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t IRAM_ATTR Rgb::getGrb( int idx ) {
|
||||||
|
switch ( idx ) {
|
||||||
|
case 0: return g;
|
||||||
|
case 1: return r;
|
||||||
|
case 2: return b;
|
||||||
|
}
|
||||||
|
__builtin_unreachable();
|
||||||
|
}
|
||||||
|
|
||||||
|
Hsv::Hsv( Rgb r ) {
|
||||||
|
int min = std::min( r.r, std::min( r.g, r.b ) );
|
||||||
|
int max = std::max( r.r, std::max( r.g, r.b ) );
|
||||||
|
int chroma = max - min;
|
||||||
|
|
||||||
|
v = max;
|
||||||
|
if ( chroma == 0 ) {
|
||||||
|
h = s = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
s = up( chroma ) / max;
|
||||||
|
int hh;
|
||||||
|
if ( max == r.r )
|
||||||
|
hh = ( up( int( r.g ) - int( r.b ) ) ) / chroma / 6;
|
||||||
|
else if ( max == r.g )
|
||||||
|
hh = 255 / 3 + ( up( int( r.b ) - int( r.r ) ) ) / chroma / 6;
|
||||||
|
else
|
||||||
|
hh = 2 * 255 / 3 + ( up( int( r.r ) - int( r.g ) ) ) / chroma / 6;
|
||||||
|
|
||||||
|
if ( hh < 0 )
|
||||||
|
hh += 255;
|
||||||
|
h = hh;
|
||||||
|
|
||||||
|
a = r.a;
|
||||||
|
}
|
||||||
|
|
||||||
|
Hsv& Hsv::operator=( Rgb rgb ) {
|
||||||
|
Hsv h{ rgb };
|
||||||
|
swap( h );
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
74
code/components/jomjol_controlGPIO/Color.h
Normal file
74
code/components/jomjol_controlGPIO/Color.h
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef COLOR_H
|
||||||
|
#define COLOR_H
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include "esp_attr.h"
|
||||||
|
union Hsv;
|
||||||
|
|
||||||
|
union Rgb {
|
||||||
|
struct __attribute__ ((packed)) {
|
||||||
|
uint8_t r, g, b, a;
|
||||||
|
};
|
||||||
|
uint32_t value;
|
||||||
|
|
||||||
|
Rgb( uint8_t r = 0, uint8_t g = 0, uint8_t b = 0, uint8_t a = 255 ) : r( r ), g( g ), b( b ), a( a ) {}
|
||||||
|
Rgb( Hsv c );
|
||||||
|
Rgb& operator=( Rgb rgb ) { swap( rgb ); return *this; }
|
||||||
|
Rgb& operator=( Hsv hsv );
|
||||||
|
Rgb operator+( Rgb in ) const;
|
||||||
|
Rgb& operator+=( Rgb in );
|
||||||
|
bool operator==( Rgb in ) const { return in.value == value; }
|
||||||
|
Rgb& blend( Rgb in );
|
||||||
|
void swap( Rgb& o ) { value = o.value; }
|
||||||
|
void linearize() {
|
||||||
|
r = channelGamma(r);
|
||||||
|
g = channelGamma(g);
|
||||||
|
b = channelGamma(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t IRAM_ATTR getGrb( int idx );
|
||||||
|
|
||||||
|
void stretchChannels( uint8_t maxR, uint8_t maxG, uint8_t maxB ) {
|
||||||
|
r = stretch( r, maxR );
|
||||||
|
g = stretch( g, maxG );
|
||||||
|
b = stretch( b, maxB );
|
||||||
|
}
|
||||||
|
|
||||||
|
void stretchChannelsEvenly( uint8_t max ) {
|
||||||
|
stretchChannels( max, max, max );
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint8_t stretch( int value, uint8_t max ) {
|
||||||
|
return ( value * max ) >> 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t channelGamma( int channel ) {
|
||||||
|
/* The optimal gamma correction is x^2.8. However, this is expensive to
|
||||||
|
* compute. Therefore, we use x^3 for gamma correction. Also, we add a
|
||||||
|
* bias as the WS2812 LEDs do not turn on for values less than 4. */
|
||||||
|
if (channel == 0)
|
||||||
|
return channel;
|
||||||
|
channel = channel * channel * channel * 251;
|
||||||
|
channel >>= 24;
|
||||||
|
return static_cast< uint8_t >( 4 + channel );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
union Hsv {
|
||||||
|
struct __attribute__ ((packed)) {
|
||||||
|
uint8_t h, s, v, a;
|
||||||
|
};
|
||||||
|
uint32_t value;
|
||||||
|
|
||||||
|
Hsv( uint8_t h, uint8_t s = 0, uint8_t v = 0, uint8_t a = 255 ) : h( h ), s( s ), v( v ), a( a ) {}
|
||||||
|
Hsv( Rgb r );
|
||||||
|
Hsv& operator=( Hsv h ) { swap( h ); return *this; }
|
||||||
|
Hsv& operator=( Rgb rgb );
|
||||||
|
bool operator==( Hsv in ) const { return in.value == value; }
|
||||||
|
void swap( Hsv& o ) { value = o.value; }
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //COLOR_H
|
||||||
63
code/components/jomjol_controlGPIO/SmartLeds.cpp
Normal file
63
code/components/jomjol_controlGPIO/SmartLeds.cpp
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
#include "SmartLeds.h"
|
||||||
|
|
||||||
|
IsrCore SmartLed::_interruptCore = CoreCurrent;
|
||||||
|
intr_handle_t SmartLed::_interruptHandle = NULL;
|
||||||
|
|
||||||
|
SmartLed*& IRAM_ATTR SmartLed::ledForChannel( int channel ) {
|
||||||
|
static SmartLed* table[8] = { nullptr };
|
||||||
|
assert( channel < 8 );
|
||||||
|
return table[ channel ];
|
||||||
|
}
|
||||||
|
|
||||||
|
void IRAM_ATTR SmartLed::interruptHandler(void*) {
|
||||||
|
for (int channel = 0; channel != 8; channel++) {
|
||||||
|
auto self = ledForChannel( channel );
|
||||||
|
|
||||||
|
if ( RMT.int_st.val & (1 << (24 + channel ) ) ) { // tx_thr_event
|
||||||
|
if ( self )
|
||||||
|
self->copyRmtHalfBlock();
|
||||||
|
RMT.int_clr.val |= 1 << ( 24 + channel );
|
||||||
|
} else if ( RMT.int_st.val & ( 1 << (3 * channel ) ) ) { // tx_end
|
||||||
|
if ( self )
|
||||||
|
xSemaphoreGiveFromISR( self->_finishedFlag, nullptr );
|
||||||
|
RMT.int_clr.val |= 1 << ( 3 * channel );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void IRAM_ATTR SmartLed::copyRmtHalfBlock() {
|
||||||
|
int offset = detail::MAX_PULSES * _halfIdx;
|
||||||
|
_halfIdx = !_halfIdx;
|
||||||
|
int len = 3 - _componentPosition + 3 * ( _count - 1 );
|
||||||
|
len = std::min( len, detail::MAX_PULSES / 8 );
|
||||||
|
|
||||||
|
if ( !len ) {
|
||||||
|
for ( int i = 0; i < detail::MAX_PULSES; i++) {
|
||||||
|
RMTMEM.chan[ _channel].data32[i + offset ].val = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int i;
|
||||||
|
for ( i = 0; i != len && _pixelPosition != _count; i++ ) {
|
||||||
|
uint8_t val = _buffer[ _pixelPosition ].getGrb( _componentPosition );
|
||||||
|
for ( int j = 0; j != 8; j++, val <<= 1 ) {
|
||||||
|
int bit = val >> 7;
|
||||||
|
int idx = i * 8 + offset + j;
|
||||||
|
RMTMEM.chan[ _channel ].data32[ idx ].val = _bitToRmt[ bit & 0x01 ].value;
|
||||||
|
}
|
||||||
|
if ( _pixelPosition == _count - 1 && _componentPosition == 2 ) {
|
||||||
|
RMTMEM.chan[ _channel ].data32[ i * 8 + offset + 7 ].duration1 =
|
||||||
|
_timing.TRS / ( detail::RMT_DURATION_NS * detail::DIVIDER );
|
||||||
|
}
|
||||||
|
|
||||||
|
_componentPosition++;
|
||||||
|
if ( _componentPosition == 3 ) {
|
||||||
|
_componentPosition = 0;
|
||||||
|
_pixelPosition++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for ( i *= 8; i != detail::MAX_PULSES; i++ ) {
|
||||||
|
RMTMEM.chan[ _channel ].data32[ i + offset ].val = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
535
code/components/jomjol_controlGPIO/SmartLeds.h
Normal file
535
code/components/jomjol_controlGPIO/SmartLeds.h
Normal file
@@ -0,0 +1,535 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef SMARTLEDS_H
|
||||||
|
#define SMARTLEDS_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A C++ driver for the WS2812 LEDs using the RMT peripheral on the ESP32.
|
||||||
|
*
|
||||||
|
* Jan "yaqwsx" Mrázek <email@honzamrazek.cz>
|
||||||
|
*
|
||||||
|
* Based on the work by Martin F. Falatic - https://github.com/FozzTexx/ws2812-demo
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#if defined ( ARDUINO )
|
||||||
|
extern "C" { // ...someone forgot to put in the includes...
|
||||||
|
#include "esp32-hal.h"
|
||||||
|
#include "esp_intr_alloc.h"
|
||||||
|
#include "esp_ipc.h"
|
||||||
|
#include "driver/gpio.h"
|
||||||
|
#include "driver/periph_ctrl.h"
|
||||||
|
#include "freertos/semphr.h"
|
||||||
|
#include "soc/rmt_struct.h"
|
||||||
|
#include <driver/spi_master.h>
|
||||||
|
#include "esp_idf_version.h"
|
||||||
|
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL( 4, 0, 0 )
|
||||||
|
#include "soc/dport_reg.h"
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#elif defined ( ESP_PLATFORM )
|
||||||
|
extern "C" { // ...someone forgot to put in the includes...
|
||||||
|
#include <esp_intr_alloc.h>
|
||||||
|
#include <esp_ipc.h>
|
||||||
|
#include <driver/gpio.h>
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/semphr.h>
|
||||||
|
#include <soc/dport_reg.h>
|
||||||
|
#include <soc/gpio_sig_map.h>
|
||||||
|
#include <soc/rmt_struct.h>
|
||||||
|
#include <driver/spi_master.h>
|
||||||
|
}
|
||||||
|
#include <stdio.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "Color.h"
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
struct TimingParams {
|
||||||
|
uint32_t T0H;
|
||||||
|
uint32_t T1H;
|
||||||
|
uint32_t T0L;
|
||||||
|
uint32_t T1L;
|
||||||
|
uint32_t TRS;
|
||||||
|
};
|
||||||
|
|
||||||
|
union RmtPulsePair {
|
||||||
|
struct {
|
||||||
|
int duration0:15;
|
||||||
|
int level0:1;
|
||||||
|
int duration1:15;
|
||||||
|
int level1:1;
|
||||||
|
};
|
||||||
|
uint32_t value;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const int DIVIDER = 4; // 8 still seems to work, but timings become marginal
|
||||||
|
static const int MAX_PULSES = 32; // A channel has a 64 "pulse" buffer - we use half per pass
|
||||||
|
static const double RMT_DURATION_NS = 12.5; // minimum time of a single RMT duration based on clock ns
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
using LedType = detail::TimingParams;
|
||||||
|
|
||||||
|
static const LedType LED_WS2812 = { 350, 700, 800, 600, 50000 };
|
||||||
|
static const LedType LED_WS2812B = { 400, 850, 850, 400, 50100 };
|
||||||
|
static const LedType LED_SK6812 = { 300, 600, 900, 600, 80000 };
|
||||||
|
static const LedType LED_WS2813 = { 350, 800, 350, 350, 300000 };
|
||||||
|
|
||||||
|
enum BufferType { SingleBuffer = 0, DoubleBuffer };
|
||||||
|
|
||||||
|
enum IsrCore { CoreFirst = 0, CoreSecond = 1, CoreCurrent = 2};
|
||||||
|
|
||||||
|
class SmartLed {
|
||||||
|
public:
|
||||||
|
// The RMT interrupt must not run on the same core as WiFi interrupts, otherwise SmartLeds
|
||||||
|
// can't fill the RMT buffer fast enough, resulting in rendering artifacts.
|
||||||
|
// Usually, that means you have to set isrCore == CoreSecond.
|
||||||
|
//
|
||||||
|
// If you use anything other than CoreCurrent, the FreeRTOS scheduler MUST be already running,
|
||||||
|
// so you can't use it if you define SmartLed as global variable.
|
||||||
|
SmartLed( const LedType& type, int count, int pin, int channel = 0, BufferType doubleBuffer = SingleBuffer, IsrCore isrCore = CoreCurrent)
|
||||||
|
: _timing( type ),
|
||||||
|
_channel( channel ),
|
||||||
|
_count( count ),
|
||||||
|
_firstBuffer( new Rgb[ count ] ),
|
||||||
|
_secondBuffer( doubleBuffer ? new Rgb[ count ] : nullptr ),
|
||||||
|
_finishedFlag( xSemaphoreCreateBinary() )
|
||||||
|
{
|
||||||
|
assert( channel >= 0 && channel < 8 );
|
||||||
|
assert( ledForChannel( channel ) == nullptr );
|
||||||
|
|
||||||
|
xSemaphoreGive( _finishedFlag );
|
||||||
|
|
||||||
|
DPORT_SET_PERI_REG_MASK( DPORT_PERIP_CLK_EN_REG, DPORT_RMT_CLK_EN );
|
||||||
|
DPORT_CLEAR_PERI_REG_MASK( DPORT_PERIP_RST_EN_REG, DPORT_RMT_RST );
|
||||||
|
|
||||||
|
PIN_FUNC_SELECT( GPIO_PIN_MUX_REG[ pin ], 2 );
|
||||||
|
gpio_set_direction( static_cast< gpio_num_t >( pin ), GPIO_MODE_OUTPUT );
|
||||||
|
gpio_matrix_out( static_cast< gpio_num_t >( pin ), RMT_SIG_OUT0_IDX + _channel, 0, 0 );
|
||||||
|
initChannel( _channel );
|
||||||
|
|
||||||
|
RMT.tx_lim_ch[ _channel ].limit = detail::MAX_PULSES;
|
||||||
|
RMT.int_ena.val |= 1 << ( 24 + _channel );
|
||||||
|
RMT.int_ena.val |= 1 << ( 3 * _channel );
|
||||||
|
|
||||||
|
_bitToRmt[ 0 ].level0 = 1;
|
||||||
|
_bitToRmt[ 0 ].level1 = 0;
|
||||||
|
_bitToRmt[ 0 ].duration0 = _timing.T0H / ( detail::RMT_DURATION_NS * detail::DIVIDER );
|
||||||
|
_bitToRmt[ 0 ].duration1 = _timing.T0L / ( detail::RMT_DURATION_NS * detail::DIVIDER );
|
||||||
|
|
||||||
|
_bitToRmt[ 1 ].level0 = 1;
|
||||||
|
_bitToRmt[ 1 ].level1 = 0;
|
||||||
|
_bitToRmt[ 1 ].duration0 = _timing.T1H / ( detail::RMT_DURATION_NS * detail::DIVIDER );
|
||||||
|
_bitToRmt[ 1 ].duration1 = _timing.T1L / ( detail::RMT_DURATION_NS * detail::DIVIDER );
|
||||||
|
|
||||||
|
if ( !anyAlive() ) {
|
||||||
|
_interruptCore = isrCore;
|
||||||
|
if(isrCore != CoreCurrent) {
|
||||||
|
ESP_ERROR_CHECK(esp_ipc_call_blocking(isrCore, registerInterrupt, NULL));
|
||||||
|
} else {
|
||||||
|
registerInterrupt(NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ledForChannel( channel ) = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
~SmartLed() {
|
||||||
|
ledForChannel( _channel ) = nullptr;
|
||||||
|
if ( !anyAlive() ) {
|
||||||
|
if(_interruptCore != CoreCurrent) {
|
||||||
|
ESP_ERROR_CHECK(esp_ipc_call_blocking(_interruptCore, unregisterInterrupt, NULL));
|
||||||
|
} else {
|
||||||
|
unregisterInterrupt(NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vSemaphoreDelete( _finishedFlag );
|
||||||
|
}
|
||||||
|
|
||||||
|
Rgb& operator[]( int idx ) {
|
||||||
|
return _firstBuffer[ idx ];
|
||||||
|
}
|
||||||
|
|
||||||
|
const Rgb& operator[]( int idx ) const {
|
||||||
|
return _firstBuffer[ idx ];
|
||||||
|
}
|
||||||
|
|
||||||
|
void show() {
|
||||||
|
_buffer = _firstBuffer.get();
|
||||||
|
startTransmission();
|
||||||
|
swapBuffers();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool wait( TickType_t timeout = portMAX_DELAY ) {
|
||||||
|
if( xSemaphoreTake( _finishedFlag, timeout ) == pdTRUE ) {
|
||||||
|
xSemaphoreGive( _finishedFlag );
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int size() const {
|
||||||
|
return _count;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rgb *begin() { return _firstBuffer.get(); }
|
||||||
|
const Rgb *begin() const { return _firstBuffer.get(); }
|
||||||
|
const Rgb *cbegin() const { return _firstBuffer.get(); }
|
||||||
|
|
||||||
|
Rgb *end() { return _firstBuffer.get() + _count; }
|
||||||
|
const Rgb *end() const { return _firstBuffer.get() + _count; }
|
||||||
|
const Rgb *cend() const { return _firstBuffer.get() + _count; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
static intr_handle_t _interruptHandle;
|
||||||
|
static IsrCore _interruptCore;
|
||||||
|
|
||||||
|
static void initChannel( int channel ) {
|
||||||
|
RMT.apb_conf.fifo_mask = 1; //enable memory access, instead of FIFO mode.
|
||||||
|
RMT.apb_conf.mem_tx_wrap_en = 1; //wrap around when hitting end of buffer
|
||||||
|
RMT.conf_ch[ channel ].conf0.div_cnt = detail::DIVIDER;
|
||||||
|
RMT.conf_ch[ channel ].conf0.mem_size = 1;
|
||||||
|
RMT.conf_ch[ channel ].conf0.carrier_en = 0;
|
||||||
|
RMT.conf_ch[ channel ].conf0.carrier_out_lv = 1;
|
||||||
|
RMT.conf_ch[ channel ].conf0.mem_pd = 0;
|
||||||
|
|
||||||
|
RMT.conf_ch[ channel ].conf1.rx_en = 0;
|
||||||
|
RMT.conf_ch[ channel ].conf1.mem_owner = 0;
|
||||||
|
RMT.conf_ch[ channel ].conf1.tx_conti_mode = 0; //loop back mode.
|
||||||
|
RMT.conf_ch[ channel ].conf1.ref_always_on = 1; // use apb clock: 80M
|
||||||
|
RMT.conf_ch[ channel ].conf1.idle_out_en = 1;
|
||||||
|
RMT.conf_ch[ channel ].conf1.idle_out_lv = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void registerInterrupt(void *) {
|
||||||
|
ESP_ERROR_CHECK(esp_intr_alloc( ETS_RMT_INTR_SOURCE, 0, interruptHandler, nullptr, &_interruptHandle));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void unregisterInterrupt(void*) {
|
||||||
|
esp_intr_free( _interruptHandle );
|
||||||
|
}
|
||||||
|
|
||||||
|
static SmartLed*& IRAM_ATTR ledForChannel( int channel );
|
||||||
|
static void IRAM_ATTR interruptHandler( void* );
|
||||||
|
|
||||||
|
void IRAM_ATTR copyRmtHalfBlock();
|
||||||
|
|
||||||
|
void swapBuffers() {
|
||||||
|
if ( _secondBuffer )
|
||||||
|
_firstBuffer.swap( _secondBuffer );
|
||||||
|
}
|
||||||
|
|
||||||
|
void startTransmission() {
|
||||||
|
// Invalid use of the library
|
||||||
|
if( xSemaphoreTake( _finishedFlag, 0 ) != pdTRUE )
|
||||||
|
abort();
|
||||||
|
|
||||||
|
_pixelPosition = _componentPosition = _halfIdx = 0;
|
||||||
|
copyRmtHalfBlock();
|
||||||
|
if ( _pixelPosition < _count )
|
||||||
|
copyRmtHalfBlock();
|
||||||
|
|
||||||
|
RMT.conf_ch[ _channel ].conf1.mem_rd_rst = 1;
|
||||||
|
RMT.conf_ch[ _channel ].conf1.tx_start = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool anyAlive() {
|
||||||
|
for ( int i = 0; i != 8; i++ )
|
||||||
|
if ( ledForChannel( i ) != nullptr ) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const LedType& _timing;
|
||||||
|
int _channel;
|
||||||
|
detail::RmtPulsePair _bitToRmt[ 2 ];
|
||||||
|
int _count;
|
||||||
|
std::unique_ptr< Rgb[] > _firstBuffer;
|
||||||
|
std::unique_ptr< Rgb[] > _secondBuffer;
|
||||||
|
Rgb *_buffer;
|
||||||
|
|
||||||
|
xSemaphoreHandle _finishedFlag;
|
||||||
|
|
||||||
|
int _pixelPosition;
|
||||||
|
int _componentPosition;
|
||||||
|
int _halfIdx;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Apa102 {
|
||||||
|
public:
|
||||||
|
struct ApaRgb {
|
||||||
|
ApaRgb( uint8_t r = 0, uint8_t g = 0, uint32_t b = 0, uint32_t v = 0xFF )
|
||||||
|
: v( 0xE0 | v ), b( b ), g( g ), r( r )
|
||||||
|
{}
|
||||||
|
|
||||||
|
ApaRgb& operator=( const Rgb& o ) {
|
||||||
|
r = o.r;
|
||||||
|
g = o.g;
|
||||||
|
b = o.b;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
ApaRgb& operator=( const Hsv& o ) {
|
||||||
|
*this = Rgb{ o };
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t v, b, g, r;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const int FINAL_FRAME_SIZE = 4;
|
||||||
|
static const int TRANS_COUNT = 2 + 8;
|
||||||
|
|
||||||
|
Apa102( int count, int clkpin, int datapin, BufferType doubleBuffer = SingleBuffer )
|
||||||
|
: _count( count ),
|
||||||
|
_firstBuffer( new ApaRgb[ count ] ),
|
||||||
|
_secondBuffer( doubleBuffer ? new ApaRgb[ count ] : nullptr ),
|
||||||
|
_initFrame( 0 )
|
||||||
|
{
|
||||||
|
spi_bus_config_t buscfg;
|
||||||
|
memset( &buscfg, 0, sizeof( buscfg ) );
|
||||||
|
buscfg.mosi_io_num = datapin;
|
||||||
|
buscfg.miso_io_num = -1;
|
||||||
|
buscfg.sclk_io_num = clkpin;
|
||||||
|
buscfg.quadwp_io_num = -1;
|
||||||
|
buscfg.quadhd_io_num = -1;
|
||||||
|
buscfg.max_transfer_sz = 65535;
|
||||||
|
|
||||||
|
spi_device_interface_config_t devcfg;
|
||||||
|
memset( &devcfg, 0, sizeof( devcfg ) );
|
||||||
|
devcfg.clock_speed_hz = 1000000;
|
||||||
|
devcfg.mode = 0;
|
||||||
|
devcfg.spics_io_num = -1;
|
||||||
|
devcfg.queue_size = TRANS_COUNT;
|
||||||
|
devcfg.pre_cb = nullptr;
|
||||||
|
|
||||||
|
auto ret = spi_bus_initialize( HSPI_HOST, &buscfg, 1 );
|
||||||
|
assert( ret == ESP_OK );
|
||||||
|
|
||||||
|
ret = spi_bus_add_device( HSPI_HOST, &devcfg, &_spi );
|
||||||
|
assert( ret == ESP_OK );
|
||||||
|
|
||||||
|
std::fill_n( _finalFrame, FINAL_FRAME_SIZE, 0xFFFFFFFF );
|
||||||
|
}
|
||||||
|
|
||||||
|
~Apa102() {
|
||||||
|
// ToDo
|
||||||
|
}
|
||||||
|
|
||||||
|
ApaRgb& operator[]( int idx ) {
|
||||||
|
return _firstBuffer[ idx ];
|
||||||
|
}
|
||||||
|
|
||||||
|
const ApaRgb& operator[]( int idx ) const {
|
||||||
|
return _firstBuffer[ idx ];
|
||||||
|
}
|
||||||
|
|
||||||
|
void show() {
|
||||||
|
_buffer = _firstBuffer.get();
|
||||||
|
startTransmission();
|
||||||
|
swapBuffers();
|
||||||
|
}
|
||||||
|
|
||||||
|
void wait() {
|
||||||
|
for ( int i = 0; i != _transCount; i++ ) {
|
||||||
|
spi_transaction_t *t;
|
||||||
|
spi_device_get_trans_result( _spi, &t, portMAX_DELAY );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
void swapBuffers() {
|
||||||
|
if ( _secondBuffer )
|
||||||
|
_firstBuffer.swap( _secondBuffer );
|
||||||
|
}
|
||||||
|
|
||||||
|
void startTransmission() {
|
||||||
|
for ( int i = 0; i != TRANS_COUNT; i++ ) {
|
||||||
|
_transactions[ i ].cmd = 0;
|
||||||
|
_transactions[ i ].addr = 0;
|
||||||
|
_transactions[ i ].flags = 0;
|
||||||
|
_transactions[ i ].rxlength = 0;
|
||||||
|
_transactions[ i ].rx_buffer = nullptr;
|
||||||
|
}
|
||||||
|
// Init frame
|
||||||
|
_transactions[ 0 ].length = 32;
|
||||||
|
_transactions[ 0 ].tx_buffer = &_initFrame;
|
||||||
|
spi_device_queue_trans( _spi, _transactions + 0, portMAX_DELAY );
|
||||||
|
// Data
|
||||||
|
_transactions[ 1 ].length = 32 * _count;
|
||||||
|
_transactions[ 1 ].tx_buffer = _buffer;
|
||||||
|
spi_device_queue_trans( _spi, _transactions + 1, portMAX_DELAY );
|
||||||
|
_transCount = 2;
|
||||||
|
// End frame
|
||||||
|
for ( int i = 0; i != 1 + _count / 32 / FINAL_FRAME_SIZE; i++ ) {
|
||||||
|
_transactions[ 2 + i ].length = 32 * FINAL_FRAME_SIZE;
|
||||||
|
_transactions[ 2 + i ].tx_buffer = _finalFrame;
|
||||||
|
spi_device_queue_trans( _spi, _transactions + 2 + i, portMAX_DELAY );
|
||||||
|
_transCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
spi_device_handle_t _spi;
|
||||||
|
int _count;
|
||||||
|
std::unique_ptr< ApaRgb[] > _firstBuffer, _secondBuffer;
|
||||||
|
ApaRgb *_buffer;
|
||||||
|
|
||||||
|
spi_transaction_t _transactions[ TRANS_COUNT ];
|
||||||
|
int _transCount;
|
||||||
|
|
||||||
|
uint32_t _initFrame;
|
||||||
|
uint32_t _finalFrame[ FINAL_FRAME_SIZE ];
|
||||||
|
};
|
||||||
|
|
||||||
|
class LDP8806 {
|
||||||
|
public:
|
||||||
|
struct LDP8806_GRB {
|
||||||
|
|
||||||
|
LDP8806_GRB( uint8_t g_7bit = 0, uint8_t r_7bit = 0, uint32_t b_7bit = 0 )
|
||||||
|
: g( g_7bit ), r( r_7bit ), b( b_7bit )
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
LDP8806_GRB& operator=( const Rgb& o ) {
|
||||||
|
//Convert 8->7bit colour
|
||||||
|
r = ( o.r * 127 / 256 ) | 0x80;
|
||||||
|
g = ( o.g * 127 / 256 ) | 0x80;
|
||||||
|
b = ( o.b * 127 / 256 ) | 0x80;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
LDP8806_GRB& operator=( const Hsv& o ) {
|
||||||
|
*this = Rgb{ o };
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t g, r, b;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const int LED_FRAME_SIZE_BYTES = sizeof( LDP8806_GRB );
|
||||||
|
static const int LATCH_FRAME_SIZE_BYTES = 3;
|
||||||
|
static const int TRANS_COUNT_MAX = 20;//Arbitrary, supports up to 600 LED
|
||||||
|
|
||||||
|
LDP8806( int count, int clkpin, int datapin, BufferType doubleBuffer = SingleBuffer, uint32_t clock_speed_hz = 2000000 )
|
||||||
|
: _count( count ),
|
||||||
|
_firstBuffer( new LDP8806_GRB[ count ] ),
|
||||||
|
_secondBuffer( doubleBuffer ? new LDP8806_GRB[ count ] : nullptr ),
|
||||||
|
// one 'latch'/start-of-data mark frame for every 32 leds
|
||||||
|
_latchFrames( ( count + 31 ) / 32 )
|
||||||
|
{
|
||||||
|
spi_bus_config_t buscfg;
|
||||||
|
memset( &buscfg, 0, sizeof( buscfg ) );
|
||||||
|
buscfg.mosi_io_num = datapin;
|
||||||
|
buscfg.miso_io_num = -1;
|
||||||
|
buscfg.sclk_io_num = clkpin;
|
||||||
|
buscfg.quadwp_io_num = -1;
|
||||||
|
buscfg.quadhd_io_num = -1;
|
||||||
|
buscfg.max_transfer_sz = 65535;
|
||||||
|
|
||||||
|
spi_device_interface_config_t devcfg;
|
||||||
|
memset( &devcfg, 0, sizeof( devcfg ) );
|
||||||
|
devcfg.clock_speed_hz = clock_speed_hz;
|
||||||
|
devcfg.mode = 0;
|
||||||
|
devcfg.spics_io_num = -1;
|
||||||
|
devcfg.queue_size = TRANS_COUNT_MAX;
|
||||||
|
devcfg.pre_cb = nullptr;
|
||||||
|
|
||||||
|
auto ret = spi_bus_initialize( HSPI_HOST, &buscfg, 1 );
|
||||||
|
assert( ret == ESP_OK );
|
||||||
|
|
||||||
|
ret = spi_bus_add_device( HSPI_HOST, &devcfg, &_spi );
|
||||||
|
assert( ret == ESP_OK );
|
||||||
|
|
||||||
|
std::fill_n( _latchBuffer, LATCH_FRAME_SIZE_BYTES, 0x0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
~LDP8806() {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
|
LDP8806_GRB& operator[]( int idx ) {
|
||||||
|
return _firstBuffer[ idx ];
|
||||||
|
}
|
||||||
|
|
||||||
|
const LDP8806_GRB& operator[]( int idx ) const {
|
||||||
|
return _firstBuffer[ idx ];
|
||||||
|
}
|
||||||
|
|
||||||
|
void show() {
|
||||||
|
_buffer = _firstBuffer.get();
|
||||||
|
startTransmission();
|
||||||
|
swapBuffers();
|
||||||
|
}
|
||||||
|
|
||||||
|
void wait() {
|
||||||
|
while ( _transCount-- ) {
|
||||||
|
spi_transaction_t *t;
|
||||||
|
spi_device_get_trans_result( _spi, &t, portMAX_DELAY );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
void swapBuffers() {
|
||||||
|
if ( _secondBuffer )
|
||||||
|
_firstBuffer.swap( _secondBuffer );
|
||||||
|
}
|
||||||
|
|
||||||
|
void startTransmission() {
|
||||||
|
_transCount = 0;
|
||||||
|
for ( int i = 0; i != TRANS_COUNT_MAX; i++ ) {
|
||||||
|
_transactions[ i ].cmd = 0;
|
||||||
|
_transactions[ i ].addr = 0;
|
||||||
|
_transactions[ i ].flags = 0;
|
||||||
|
_transactions[ i ].rxlength = 0;
|
||||||
|
_transactions[ i ].rx_buffer = nullptr;
|
||||||
|
}
|
||||||
|
// LED Data
|
||||||
|
_transactions[ 0 ].length = ( LED_FRAME_SIZE_BYTES * 8 ) * _count;
|
||||||
|
_transactions[ 0 ].tx_buffer = _buffer;
|
||||||
|
spi_device_queue_trans( _spi, _transactions + _transCount, portMAX_DELAY );
|
||||||
|
_transCount++;
|
||||||
|
|
||||||
|
// 'latch'/start-of-data marker frames
|
||||||
|
for ( int i = 0; i < _latchFrames; i++ ) {
|
||||||
|
_transactions[ _transCount ].length = ( LATCH_FRAME_SIZE_BYTES * 8 );
|
||||||
|
_transactions[ _transCount ].tx_buffer = _latchBuffer;
|
||||||
|
spi_device_queue_trans( _spi, _transactions + _transCount, portMAX_DELAY );
|
||||||
|
_transCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
spi_device_handle_t _spi;
|
||||||
|
int _count;
|
||||||
|
std::unique_ptr< LDP8806_GRB[] > _firstBuffer, _secondBuffer;
|
||||||
|
LDP8806_GRB *_buffer;
|
||||||
|
|
||||||
|
spi_transaction_t _transactions[ TRANS_COUNT_MAX ];
|
||||||
|
int _transCount;
|
||||||
|
|
||||||
|
int _latchFrames;
|
||||||
|
uint8_t _latchBuffer[ LATCH_FRAME_SIZE_BYTES ];
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //SMARTLEDS_H
|
||||||
705
code/components/jomjol_controlGPIO/server_GPIO.cpp
Normal file
705
code/components/jomjol_controlGPIO/server_GPIO.cpp
Normal file
@@ -0,0 +1,705 @@
|
|||||||
|
#include <string>
|
||||||
|
#include <functional>
|
||||||
|
#include "string.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include "esp_system.h"
|
||||||
|
#include "esp_event.h"
|
||||||
|
|
||||||
|
#include "server_tflite.h"
|
||||||
|
|
||||||
|
#include "esp_log.h"
|
||||||
|
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "../../include/defines.h"
|
||||||
|
|
||||||
|
#include "server_GPIO.h"
|
||||||
|
|
||||||
|
#include "ClassLogFile.h"
|
||||||
|
#include "configFile.h"
|
||||||
|
#include "Helper.h"
|
||||||
|
#ifdef ENABLE_MQTT
|
||||||
|
#include "interface_mqtt.h"
|
||||||
|
#endif //ENABLE_MQTT
|
||||||
|
|
||||||
|
|
||||||
|
static const char *TAG = "GPIO";
|
||||||
|
QueueHandle_t gpio_queue_handle = NULL;
|
||||||
|
|
||||||
|
GpioPin::GpioPin(gpio_num_t gpio, const char* name, gpio_pin_mode_t mode, gpio_int_type_t interruptType, uint8_t dutyResolution, std::string mqttTopic, bool httpEnable)
|
||||||
|
{
|
||||||
|
_gpio = gpio;
|
||||||
|
_name = name;
|
||||||
|
_mode = mode;
|
||||||
|
_interruptType = interruptType;
|
||||||
|
_mqttTopic = mqttTopic;
|
||||||
|
}
|
||||||
|
|
||||||
|
GpioPin::~GpioPin()
|
||||||
|
{
|
||||||
|
ESP_LOGD(TAG,"reset GPIO pin %d", _gpio);
|
||||||
|
if (_interruptType != GPIO_INTR_DISABLE) {
|
||||||
|
//hook isr handler for specific gpio pin
|
||||||
|
gpio_isr_handler_remove(_gpio);
|
||||||
|
}
|
||||||
|
gpio_reset_pin(_gpio);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void IRAM_ATTR gpio_isr_handler(void* arg)
|
||||||
|
{
|
||||||
|
GpioResult gpioResult;
|
||||||
|
gpioResult.gpio = *(gpio_num_t*) arg;
|
||||||
|
gpioResult.value = gpio_get_level(gpioResult.gpio);
|
||||||
|
BaseType_t ContextSwitchRequest = pdFALSE;
|
||||||
|
|
||||||
|
xQueueSendToBackFromISR(gpio_queue_handle,(void*)&gpioResult,&ContextSwitchRequest);
|
||||||
|
|
||||||
|
if(ContextSwitchRequest){
|
||||||
|
taskYIELD();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void gpioHandlerTask(void *arg) {
|
||||||
|
ESP_LOGD(TAG,"start interrupt task");
|
||||||
|
while(1){
|
||||||
|
if(uxQueueMessagesWaiting(gpio_queue_handle)){
|
||||||
|
while(uxQueueMessagesWaiting(gpio_queue_handle)){
|
||||||
|
GpioResult gpioResult;
|
||||||
|
xQueueReceive(gpio_queue_handle,(void*)&gpioResult,10);
|
||||||
|
ESP_LOGD(TAG,"gpio: %d state: %d", gpioResult.gpio, gpioResult.value);
|
||||||
|
((GpioHandler*)arg)->gpioInterrupt(&gpioResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
((GpioHandler*)arg)->taskHandler();
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GpioPin::gpioInterrupt(int value) {
|
||||||
|
#ifdef ENABLE_MQTT
|
||||||
|
if (_mqttTopic != "") {
|
||||||
|
ESP_LOGD(TAG, "gpioInterrupt %s %d", _mqttTopic.c_str(), value);
|
||||||
|
|
||||||
|
MQTTPublish(_mqttTopic, value ? "true" : "false");
|
||||||
|
}
|
||||||
|
#endif //ENABLE_MQTT
|
||||||
|
currentState = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GpioPin::init()
|
||||||
|
{
|
||||||
|
gpio_config_t io_conf;
|
||||||
|
//set interrupt
|
||||||
|
io_conf.intr_type = _interruptType;
|
||||||
|
//set as output mode
|
||||||
|
io_conf.mode = (_mode == GPIO_PIN_MODE_OUTPUT) || (_mode == GPIO_PIN_MODE_BUILT_IN_FLASH_LED) ? gpio_mode_t::GPIO_MODE_OUTPUT : gpio_mode_t::GPIO_MODE_INPUT;
|
||||||
|
//bit mask of the pins that you want to set,e.g.GPIO18/19
|
||||||
|
io_conf.pin_bit_mask = (1ULL << _gpio);
|
||||||
|
//set pull-down mode
|
||||||
|
io_conf.pull_down_en = _mode == GPIO_PIN_MODE_INPUT_PULLDOWN ? gpio_pulldown_t::GPIO_PULLDOWN_ENABLE : gpio_pulldown_t::GPIO_PULLDOWN_DISABLE;
|
||||||
|
//set pull-up mode
|
||||||
|
io_conf.pull_up_en = _mode == GPIO_PIN_MODE_INPUT_PULLDOWN ? gpio_pullup_t::GPIO_PULLUP_ENABLE : gpio_pullup_t::GPIO_PULLUP_DISABLE;
|
||||||
|
//configure GPIO with the given settings
|
||||||
|
gpio_config(&io_conf);
|
||||||
|
|
||||||
|
// if (_interruptType != GPIO_INTR_DISABLE) { // ohne GPIO_PIN_MODE_EXTERNAL_FLASH_WS281X, wenn das genutzt wird, dann soll auch der Handler hier nicht initialisiert werden, da das dann über SmartLED erfolgt.
|
||||||
|
if ((_interruptType != GPIO_INTR_DISABLE) && (_interruptType != GPIO_PIN_MODE_EXTERNAL_FLASH_WS281X)) {
|
||||||
|
//hook isr handler for specific gpio pin
|
||||||
|
ESP_LOGD(TAG, "GpioPin::init add isr handler for GPIO %d", _gpio);
|
||||||
|
gpio_isr_handler_add(_gpio, gpio_isr_handler, (void*)&_gpio);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef ENABLE_MQTT
|
||||||
|
if ((_mqttTopic != "") && ((_mode == GPIO_PIN_MODE_OUTPUT) || (_mode == GPIO_PIN_MODE_OUTPUT_PWM) || (_mode == GPIO_PIN_MODE_BUILT_IN_FLASH_LED))) {
|
||||||
|
std::function<bool(std::string, char*, int)> f = std::bind(&GpioPin::handleMQTT, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
|
||||||
|
MQTTregisterSubscribeFunction(_mqttTopic, f);
|
||||||
|
}
|
||||||
|
#endif //ENABLE_MQTT
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GpioPin::getValue(std::string* errorText)
|
||||||
|
{
|
||||||
|
if ((_mode != GPIO_PIN_MODE_INPUT) && (_mode != GPIO_PIN_MODE_INPUT_PULLUP) && (_mode != GPIO_PIN_MODE_INPUT_PULLDOWN)) {
|
||||||
|
(*errorText) = "GPIO is not in input mode";
|
||||||
|
}
|
||||||
|
|
||||||
|
return gpio_get_level(_gpio) == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GpioPin::setValue(bool value, gpio_set_source setSource, std::string* errorText)
|
||||||
|
{
|
||||||
|
ESP_LOGD(TAG, "GpioPin::setValue %d", value);
|
||||||
|
|
||||||
|
if ((_mode != GPIO_PIN_MODE_OUTPUT) && (_mode != GPIO_PIN_MODE_OUTPUT_PWM) && (_mode != GPIO_PIN_MODE_BUILT_IN_FLASH_LED)) {
|
||||||
|
(*errorText) = "GPIO is not in output mode";
|
||||||
|
} else {
|
||||||
|
gpio_set_level(_gpio, value);
|
||||||
|
|
||||||
|
#ifdef ENABLE_MQTT
|
||||||
|
if ((_mqttTopic != "") && (setSource != GPIO_SET_SOURCE_MQTT)) {
|
||||||
|
MQTTPublish(_mqttTopic, value ? "true" : "false");
|
||||||
|
}
|
||||||
|
#endif //ENABLE_MQTT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GpioPin::publishState() {
|
||||||
|
int newState = gpio_get_level(_gpio);
|
||||||
|
if (newState != currentState) {
|
||||||
|
ESP_LOGD(TAG,"publish state of GPIO %d new state %d", _gpio, newState);
|
||||||
|
#ifdef ENABLE_MQTT
|
||||||
|
MQTTPublish(_mqttTopic, newState ? "true" : "false");
|
||||||
|
#endif //ENABLE_MQTT
|
||||||
|
currentState = newState;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef ENABLE_MQTT
|
||||||
|
bool GpioPin::handleMQTT(std::string, char* data, int data_len) {
|
||||||
|
ESP_LOGD(TAG, "GpioPin::handleMQTT data %.*s", data_len, data);
|
||||||
|
|
||||||
|
std::string dataStr(data, data_len);
|
||||||
|
dataStr = toLower(dataStr);
|
||||||
|
std::string errorText = "";
|
||||||
|
if ((dataStr == "true") || (dataStr == "1")) {
|
||||||
|
setValue(true, GPIO_SET_SOURCE_MQTT, &errorText);
|
||||||
|
} else if ((dataStr == "false") || (dataStr == "0")) {
|
||||||
|
setValue(false, GPIO_SET_SOURCE_MQTT, &errorText);
|
||||||
|
} else {
|
||||||
|
errorText = "wrong value ";
|
||||||
|
errorText.append(data, data_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errorText != "") {
|
||||||
|
ESP_LOGE(TAG, "%s", errorText.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
return (errorText == "");
|
||||||
|
}
|
||||||
|
#endif //ENABLE_MQTT
|
||||||
|
|
||||||
|
esp_err_t callHandleHttpRequest(httpd_req_t *req)
|
||||||
|
{
|
||||||
|
ESP_LOGD(TAG,"callHandleHttpRequest");
|
||||||
|
|
||||||
|
GpioHandler *gpioHandler = (GpioHandler*)req->user_ctx;
|
||||||
|
return gpioHandler->handleHttpRequest(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
void taskGpioHandler(void *pvParameter)
|
||||||
|
{
|
||||||
|
ESP_LOGD(TAG,"taskGpioHandler");
|
||||||
|
((GpioHandler*)pvParameter)->init();
|
||||||
|
}
|
||||||
|
|
||||||
|
GpioHandler::GpioHandler(std::string configFile, httpd_handle_t httpServer)
|
||||||
|
{
|
||||||
|
ESP_LOGI(TAG,"start GpioHandler");
|
||||||
|
_configFile = configFile;
|
||||||
|
_httpServer = httpServer;
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "register GPIO Uri");
|
||||||
|
registerGpioUri();
|
||||||
|
}
|
||||||
|
|
||||||
|
GpioHandler::~GpioHandler() {
|
||||||
|
if (gpioMap != NULL) {
|
||||||
|
clear();
|
||||||
|
delete gpioMap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GpioHandler::init()
|
||||||
|
{
|
||||||
|
// TickType_t xDelay = 60000 / portTICK_PERIOD_MS;
|
||||||
|
// ESP_LOGD(TAG, "wait before start %ldms", (long) xDelay);
|
||||||
|
// vTaskDelay( xDelay );
|
||||||
|
|
||||||
|
ESP_LOGD(TAG, "*************** Start GPIOHandler_Init *****************");
|
||||||
|
|
||||||
|
if (gpioMap == NULL) {
|
||||||
|
gpioMap = new std::map<gpio_num_t, GpioPin*>();
|
||||||
|
} else {
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "read GPIO config and init GPIO");
|
||||||
|
if (!readConfig()) {
|
||||||
|
clear();
|
||||||
|
delete gpioMap;
|
||||||
|
gpioMap = NULL;
|
||||||
|
ESP_LOGI(TAG, "GPIO init completed, handler is disabled");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for(std::map<gpio_num_t, GpioPin*>::iterator it = gpioMap->begin(); it != gpioMap->end(); ++it) {
|
||||||
|
it->second->init();
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef ENABLE_MQTT
|
||||||
|
std::function<void()> f = std::bind(&GpioHandler::handleMQTTconnect, this);
|
||||||
|
MQTTregisterConnectFunction("gpio-handler", f);
|
||||||
|
#endif //ENABLE_MQTT
|
||||||
|
|
||||||
|
if (xHandleTaskGpio == NULL) {
|
||||||
|
gpio_queue_handle = xQueueCreate(10,sizeof(GpioResult));
|
||||||
|
BaseType_t xReturned = xTaskCreate(&gpioHandlerTask, "gpio_int", 3 * 1024, (void *)this, tskIDLE_PRIORITY + 4, &xHandleTaskGpio);
|
||||||
|
if(xReturned == pdPASS ) {
|
||||||
|
ESP_LOGD(TAG, "xHandletaskGpioHandler started");
|
||||||
|
} else {
|
||||||
|
ESP_LOGD(TAG, "xHandletaskGpioHandler not started %d ", (int)xHandleTaskGpio);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "GPIO init completed, is enabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
void GpioHandler::taskHandler() {
|
||||||
|
if (gpioMap != NULL) {
|
||||||
|
for(std::map<gpio_num_t, GpioPin*>::iterator it = gpioMap->begin(); it != gpioMap->end(); ++it) {
|
||||||
|
if ((it->second->getInterruptType() == GPIO_INTR_DISABLE))
|
||||||
|
it->second->publishState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef ENABLE_MQTT
|
||||||
|
void GpioHandler::handleMQTTconnect()
|
||||||
|
{
|
||||||
|
if (gpioMap != NULL) {
|
||||||
|
for(std::map<gpio_num_t, GpioPin*>::iterator it = gpioMap->begin(); it != gpioMap->end(); ++it) {
|
||||||
|
if ((it->second->getMode() == GPIO_PIN_MODE_INPUT) || (it->second->getMode() == GPIO_PIN_MODE_INPUT_PULLDOWN) || (it->second->getMode() == GPIO_PIN_MODE_INPUT_PULLUP))
|
||||||
|
it->second->publishState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif //ENABLE_MQTT
|
||||||
|
|
||||||
|
void GpioHandler::deinit() {
|
||||||
|
#ifdef ENABLE_MQTT
|
||||||
|
MQTTunregisterConnectFunction("gpio-handler");
|
||||||
|
#endif //ENABLE_MQTT
|
||||||
|
clear();
|
||||||
|
if (xHandleTaskGpio != NULL) {
|
||||||
|
vTaskDelete(xHandleTaskGpio);
|
||||||
|
xHandleTaskGpio = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GpioHandler::gpioInterrupt(GpioResult* gpioResult) {
|
||||||
|
if ((gpioMap != NULL) && (gpioMap->find(gpioResult->gpio) != gpioMap->end())) {
|
||||||
|
(*gpioMap)[gpioResult->gpio]->gpioInterrupt(gpioResult->value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GpioHandler::readConfig()
|
||||||
|
{
|
||||||
|
if (!gpioMap->empty())
|
||||||
|
clear();
|
||||||
|
|
||||||
|
ConfigFile configFile = ConfigFile(_configFile);
|
||||||
|
|
||||||
|
std::vector<std::string> splitted;
|
||||||
|
std::string line = "";
|
||||||
|
bool disabledLine = false;
|
||||||
|
bool eof = false;
|
||||||
|
gpio_num_t gpioExtLED = (gpio_num_t) 0;
|
||||||
|
|
||||||
|
// ESP_LOGD(TAG, "readConfig - Start 1");
|
||||||
|
|
||||||
|
while ((!configFile.GetNextParagraph(line, disabledLine, eof) || (line.compare("[GPIO]") != 0)) && !eof) {}
|
||||||
|
if (eof)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// ESP_LOGD(TAG, "readConfig - Start 2 line: %s, disabbledLine: %d", line.c_str(), (int) disabledLine);
|
||||||
|
|
||||||
|
|
||||||
|
_isEnabled = !disabledLine;
|
||||||
|
|
||||||
|
if (!_isEnabled)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// ESP_LOGD(TAG, "readConfig - Start 3");
|
||||||
|
|
||||||
|
#ifdef ENABLE_MQTT
|
||||||
|
// std::string mainTopicMQTT = "";
|
||||||
|
std::string mainTopicMQTT = GetMQTTMainTopic();
|
||||||
|
if (mainTopicMQTT.length() > 0)
|
||||||
|
{
|
||||||
|
mainTopicMQTT = mainTopicMQTT + "/GPIO";
|
||||||
|
ESP_LOGD(TAG, "MAINTOPICMQTT found");
|
||||||
|
}
|
||||||
|
#endif // ENABLE_MQTT
|
||||||
|
bool registerISR = false;
|
||||||
|
while (configFile.getNextLine(&line, disabledLine, eof) && !configFile.isNewParagraph(line))
|
||||||
|
{
|
||||||
|
splitted = ZerlegeZeile(line);
|
||||||
|
// const std::regex pieces_regex("IO([0-9]{1,2})");
|
||||||
|
// std::smatch pieces_match;
|
||||||
|
// if (std::regex_match(splitted[0], pieces_match, pieces_regex) && (pieces_match.size() == 2))
|
||||||
|
// {
|
||||||
|
// std::string gpioStr = pieces_match[1];
|
||||||
|
ESP_LOGD(TAG, "conf param %s", toUpper(splitted[0]).c_str());
|
||||||
|
if (toUpper(splitted[0]) == "MAINTOPICMQTT") {
|
||||||
|
// ESP_LOGD(TAG, "MAINTOPICMQTT found");
|
||||||
|
// mainTopicMQTT = splitted[1];
|
||||||
|
} else if ((splitted[0].rfind("IO", 0) == 0) && (splitted.size() >= 6))
|
||||||
|
{
|
||||||
|
ESP_LOGI(TAG,"Enable GP%s in %s mode", splitted[0].c_str(), splitted[1].c_str());
|
||||||
|
std::string gpioStr = splitted[0].substr(2, 2);
|
||||||
|
gpio_num_t gpioNr = (gpio_num_t)atoi(gpioStr.c_str());
|
||||||
|
gpio_pin_mode_t pinMode = resolvePinMode(toLower(splitted[1]));
|
||||||
|
gpio_int_type_t intType = resolveIntType(toLower(splitted[2]));
|
||||||
|
uint16_t dutyResolution = (uint8_t)atoi(splitted[3].c_str());
|
||||||
|
#ifdef ENABLE_MQTT
|
||||||
|
bool mqttEnabled = toLower(splitted[4]) == "true";
|
||||||
|
#endif // ENABLE_MQTT
|
||||||
|
bool httpEnabled = toLower(splitted[5]) == "true";
|
||||||
|
char gpioName[100];
|
||||||
|
if (splitted.size() >= 7) {
|
||||||
|
strcpy(gpioName, trim(splitted[6]).c_str());
|
||||||
|
} else {
|
||||||
|
sprintf(gpioName, "GPIO%d", gpioNr);
|
||||||
|
}
|
||||||
|
#ifdef ENABLE_MQTT
|
||||||
|
std::string mqttTopic = mqttEnabled ? (mainTopicMQTT + "/" + gpioName) : "";
|
||||||
|
#else // ENABLE_MQTT
|
||||||
|
std::string mqttTopic = "";
|
||||||
|
#endif // ENABLE_MQTT
|
||||||
|
GpioPin* gpioPin = new GpioPin(gpioNr, gpioName, pinMode, intType,dutyResolution, mqttTopic, httpEnabled);
|
||||||
|
(*gpioMap)[gpioNr] = gpioPin;
|
||||||
|
|
||||||
|
if (pinMode == GPIO_PIN_MODE_EXTERNAL_FLASH_WS281X)
|
||||||
|
{
|
||||||
|
ESP_LOGD(TAG, "Set WS2812 to GPIO %d", gpioNr);
|
||||||
|
gpioExtLED = gpioNr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (intType != GPIO_INTR_DISABLE) {
|
||||||
|
registerISR = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (toUpper(splitted[0]) == "LEDNUMBERS")
|
||||||
|
{
|
||||||
|
LEDNumbers = stoi(splitted[1]);
|
||||||
|
}
|
||||||
|
if (toUpper(splitted[0]) == "LEDCOLOR")
|
||||||
|
{
|
||||||
|
uint8_t _r, _g, _b;
|
||||||
|
_r = stoi(splitted[1]);
|
||||||
|
_g = stoi(splitted[2]);
|
||||||
|
_b = stoi(splitted[3]);
|
||||||
|
|
||||||
|
LEDColor = Rgb{_r, _g, _b};
|
||||||
|
}
|
||||||
|
if (toUpper(splitted[0]) == "LEDTYPE")
|
||||||
|
{
|
||||||
|
if (splitted[1] == "WS2812")
|
||||||
|
LEDType = LED_WS2812;
|
||||||
|
if (splitted[1] == "WS2812B")
|
||||||
|
LEDType = LED_WS2812B;
|
||||||
|
if (splitted[1] == "SK6812")
|
||||||
|
LEDType = LED_SK6812;
|
||||||
|
if (splitted[1] == "WS2813")
|
||||||
|
LEDType = LED_WS2813;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (registerISR) {
|
||||||
|
//install gpio isr service
|
||||||
|
gpio_install_isr_service(ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_IRAM);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gpioExtLED > 0)
|
||||||
|
{
|
||||||
|
// LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Startsequence 06"); // Nremove
|
||||||
|
// vTaskDelay( xDelay );
|
||||||
|
// xDelay = 5000 / portTICK_PERIOD_MS;
|
||||||
|
// ESP_LOGD(TAG, "main: sleep for: %ldms", (long) xDelay);
|
||||||
|
|
||||||
|
// SmartLed leds( LED_WS2812, 2, GPIO_NUM_12, 0, DoubleBuffer );
|
||||||
|
|
||||||
|
|
||||||
|
// leds[ 0 ] = Rgb{ 255, 0, 0 };
|
||||||
|
// leds[ 1 ] = Rgb{ 255, 255, 255 };
|
||||||
|
// leds.show();
|
||||||
|
// SmartLed leds = new SmartLed(LEDType, LEDNumbers, gpioExtLED, 0, DoubleBuffer);
|
||||||
|
// _SmartLED = new SmartLed( LED_WS2812, 2, GPIO_NUM_12, 0, DoubleBuffer );
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GpioHandler::clear()
|
||||||
|
{
|
||||||
|
ESP_LOGD(TAG, "GpioHandler::clear");
|
||||||
|
|
||||||
|
if (gpioMap != NULL) {
|
||||||
|
for(std::map<gpio_num_t, GpioPin*>::iterator it = gpioMap->begin(); it != gpioMap->end(); ++it) {
|
||||||
|
delete it->second;
|
||||||
|
}
|
||||||
|
gpioMap->clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// gpio_uninstall_isr_service(); can't uninstall, isr service is used by camera
|
||||||
|
}
|
||||||
|
|
||||||
|
void GpioHandler::registerGpioUri()
|
||||||
|
{
|
||||||
|
ESP_LOGI(TAG, "server_GPIO - Registering URI handlers");
|
||||||
|
|
||||||
|
httpd_uri_t camuri = { };
|
||||||
|
camuri.method = HTTP_GET;
|
||||||
|
camuri.uri = "/GPIO";
|
||||||
|
camuri.handler = callHandleHttpRequest;
|
||||||
|
camuri.user_ctx = (void*)this;
|
||||||
|
httpd_register_uri_handler(_httpServer, &camuri);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t GpioHandler::handleHttpRequest(httpd_req_t *req)
|
||||||
|
{
|
||||||
|
ESP_LOGD(TAG, "handleHttpRequest");
|
||||||
|
|
||||||
|
if (gpioMap == NULL) {
|
||||||
|
std::string resp_str = "GPIO handler not initialized";
|
||||||
|
httpd_resp_send(req, resp_str.c_str(), resp_str.length());
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
LogFile.WriteHeapInfo("handler_switch_GPIO - Start");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "handler_switch_GPIO");
|
||||||
|
char _query[200];
|
||||||
|
char _valueGPIO[30];
|
||||||
|
char _valueStatus[30];
|
||||||
|
std::string gpio, status;
|
||||||
|
|
||||||
|
if (httpd_req_get_url_query_str(req, _query, 200) == ESP_OK) {
|
||||||
|
ESP_LOGD(TAG, "Query: %s", _query);
|
||||||
|
|
||||||
|
if (httpd_query_key_value(_query, "GPIO", _valueGPIO, 30) == ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGD(TAG, "GPIO is found %s", _valueGPIO);
|
||||||
|
gpio = std::string(_valueGPIO);
|
||||||
|
} else {
|
||||||
|
std::string resp_str = "GPIO No is not defined";
|
||||||
|
httpd_resp_send(req, resp_str.c_str(), resp_str.length());
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
if (httpd_query_key_value(_query, "Status", _valueStatus, 30) == ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGD(TAG, "Status is found %s", _valueStatus);
|
||||||
|
status = std::string(_valueStatus);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const char* resp_str = "Error in call. Use /GPIO?GPIO=12&Status=high";
|
||||||
|
httpd_resp_send(req, resp_str, strlen(resp_str));
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
status = toUpper(status);
|
||||||
|
if ((status != "HIGH") && (status != "LOW") && (status != "TRUE") && (status != "FALSE") && (status != "0") && (status != "1") && (status != ""))
|
||||||
|
{
|
||||||
|
std::string zw = "Status not valid: " + status;
|
||||||
|
httpd_resp_sendstr_chunk(req, zw.c_str());
|
||||||
|
httpd_resp_sendstr_chunk(req, NULL);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int gpionum = stoi(gpio);
|
||||||
|
|
||||||
|
// frei: 16; 12-15; 2; 4 // nur 12 und 13 funktionieren 2: reboot, 4: BlitzLED, 15: PSRAM, 14/15: DMA für SDKarte ???
|
||||||
|
gpio_num_t gpio_num = resolvePinNr(gpionum);
|
||||||
|
if (gpio_num == GPIO_NUM_NC)
|
||||||
|
{
|
||||||
|
std::string zw = "GPIO" + std::to_string(gpionum) + " unsupported - only 12 & 13 free";
|
||||||
|
httpd_resp_sendstr_chunk(req, zw.c_str());
|
||||||
|
httpd_resp_sendstr_chunk(req, NULL);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gpioMap->count(gpio_num) == 0) {
|
||||||
|
char resp_str [30];
|
||||||
|
sprintf(resp_str, "GPIO%d is not registred", gpio_num);
|
||||||
|
httpd_resp_send(req, resp_str, strlen(resp_str));
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status == "")
|
||||||
|
{
|
||||||
|
std::string resp_str = "";
|
||||||
|
status = (*gpioMap)[gpio_num]->getValue(&resp_str) ? "HIGH" : "LOW";
|
||||||
|
if (resp_str == "") {
|
||||||
|
resp_str = status;
|
||||||
|
}
|
||||||
|
httpd_resp_sendstr_chunk(req, resp_str.c_str());
|
||||||
|
httpd_resp_sendstr_chunk(req, NULL);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::string resp_str = "";
|
||||||
|
(*gpioMap)[gpio_num]->setValue((status == "HIGH") || (status == "TRUE") || (status == "1"), GPIO_SET_SOURCE_HTTP, &resp_str);
|
||||||
|
if (resp_str == "") {
|
||||||
|
resp_str = "GPIO" + std::to_string(gpionum) + " switched to " + status;
|
||||||
|
}
|
||||||
|
httpd_resp_sendstr_chunk(req, resp_str.c_str());
|
||||||
|
httpd_resp_sendstr_chunk(req, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
};
|
||||||
|
|
||||||
|
void GpioHandler::flashLightEnable(bool value)
|
||||||
|
{
|
||||||
|
ESP_LOGD(TAG, "GpioHandler::flashLightEnable %s", value ? "true" : "false");
|
||||||
|
|
||||||
|
if (gpioMap != NULL) {
|
||||||
|
for(std::map<gpio_num_t, GpioPin*>::iterator it = gpioMap->begin(); it != gpioMap->end(); ++it)
|
||||||
|
{
|
||||||
|
if (it->second->getMode() == GPIO_PIN_MODE_BUILT_IN_FLASH_LED) //|| (it->second->getMode() == GPIO_PIN_MODE_EXTERNAL_FLASH_PWM) || (it->second->getMode() == GPIO_PIN_MODE_EXTERNAL_FLASH_WS281X))
|
||||||
|
{
|
||||||
|
std::string resp_str = "";
|
||||||
|
it->second->setValue(value, GPIO_SET_SOURCE_INTERNAL, &resp_str);
|
||||||
|
|
||||||
|
if (resp_str == "") {
|
||||||
|
ESP_LOGD(TAG, "Flash light pin GPIO %d switched to %s", (int)it->first, (value ? "on" : "off"));
|
||||||
|
} else {
|
||||||
|
ESP_LOGE(TAG, "Can't set flash light pin GPIO %d. Error: %s", (int)it->first, resp_str.c_str());
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
if (it->second->getMode() == GPIO_PIN_MODE_EXTERNAL_FLASH_WS281X)
|
||||||
|
{
|
||||||
|
#ifdef __LEDGLOBAL
|
||||||
|
if (leds_global == NULL) {
|
||||||
|
ESP_LOGI(TAG, "init SmartLed: LEDNumber=%d, GPIO=%d", LEDNumbers, (int)it->second->getGPIO());
|
||||||
|
leds_global = new SmartLed( LEDType, LEDNumbers, it->second->getGPIO(), 0, DoubleBuffer );
|
||||||
|
} else {
|
||||||
|
// wait until we can update: https://github.com/RoboticsBrno/SmartLeds/issues/10#issuecomment-386921623
|
||||||
|
leds_global->wait();
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
SmartLed leds( LEDType, LEDNumbers, it->second->getGPIO(), 0, DoubleBuffer );
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (value)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < LEDNumbers; ++i)
|
||||||
|
#ifdef __LEDGLOBAL
|
||||||
|
(*leds_global)[i] = LEDColor;
|
||||||
|
#else
|
||||||
|
leds[i] = LEDColor;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (int i = 0; i < LEDNumbers; ++i)
|
||||||
|
#ifdef __LEDGLOBAL
|
||||||
|
(*leds_global)[i] = Rgb{0, 0, 0};
|
||||||
|
#else
|
||||||
|
leds[i] = Rgb{0, 0, 0};
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#ifdef __LEDGLOBAL
|
||||||
|
leds_global->show();
|
||||||
|
#else
|
||||||
|
leds.show();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gpio_num_t GpioHandler::resolvePinNr(uint8_t pinNr)
|
||||||
|
{
|
||||||
|
switch(pinNr) {
|
||||||
|
case 0:
|
||||||
|
return GPIO_NUM_0;
|
||||||
|
case 1:
|
||||||
|
return GPIO_NUM_1;
|
||||||
|
case 3:
|
||||||
|
return GPIO_NUM_3;
|
||||||
|
case 4:
|
||||||
|
return GPIO_NUM_4;
|
||||||
|
case 12:
|
||||||
|
return GPIO_NUM_12;
|
||||||
|
case 13:
|
||||||
|
return GPIO_NUM_13;
|
||||||
|
default:
|
||||||
|
return GPIO_NUM_NC;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gpio_pin_mode_t GpioHandler::resolvePinMode(std::string input)
|
||||||
|
{
|
||||||
|
if( input == "disabled" ) return GPIO_PIN_MODE_DISABLED;
|
||||||
|
if( input == "input" ) return GPIO_PIN_MODE_INPUT;
|
||||||
|
if( input == "input-pullup" ) return GPIO_PIN_MODE_INPUT_PULLUP;
|
||||||
|
if( input == "input-pulldown" ) return GPIO_PIN_MODE_INPUT_PULLDOWN;
|
||||||
|
if( input == "output" ) return GPIO_PIN_MODE_OUTPUT;
|
||||||
|
if( input == "built-in-led" ) return GPIO_PIN_MODE_BUILT_IN_FLASH_LED;
|
||||||
|
if( input == "output-pwm" ) return GPIO_PIN_MODE_OUTPUT_PWM;
|
||||||
|
if( input == "external-flash-pwm" ) return GPIO_PIN_MODE_EXTERNAL_FLASH_PWM;
|
||||||
|
if( input == "external-flash-ws281x" ) return GPIO_PIN_MODE_EXTERNAL_FLASH_WS281X;
|
||||||
|
|
||||||
|
return GPIO_PIN_MODE_DISABLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
gpio_int_type_t GpioHandler::resolveIntType(std::string input)
|
||||||
|
{
|
||||||
|
if( input == "disabled" ) return GPIO_INTR_DISABLE;
|
||||||
|
if( input == "rising-edge" ) return GPIO_INTR_POSEDGE;
|
||||||
|
if( input == "falling-edge" ) return GPIO_INTR_NEGEDGE;
|
||||||
|
if( input == "rising-and-falling" ) return GPIO_INTR_ANYEDGE ;
|
||||||
|
if( input == "low-level-trigger" ) return GPIO_INTR_LOW_LEVEL;
|
||||||
|
if( input == "high-level-trigger" ) return GPIO_INTR_HIGH_LEVEL;
|
||||||
|
|
||||||
|
|
||||||
|
return GPIO_INTR_DISABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static GpioHandler *gpioHandler = NULL;
|
||||||
|
|
||||||
|
void gpio_handler_create(httpd_handle_t server)
|
||||||
|
{
|
||||||
|
if (gpioHandler == NULL)
|
||||||
|
gpioHandler = new GpioHandler(CONFIG_FILE, server);
|
||||||
|
}
|
||||||
|
|
||||||
|
void gpio_handler_init()
|
||||||
|
{
|
||||||
|
if (gpioHandler != NULL) {
|
||||||
|
gpioHandler->init();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void gpio_handler_deinit() {
|
||||||
|
if (gpioHandler != NULL) {
|
||||||
|
gpioHandler->deinit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void gpio_handler_destroy()
|
||||||
|
{
|
||||||
|
if (gpioHandler != NULL) {
|
||||||
|
gpio_handler_deinit();
|
||||||
|
delete gpioHandler;
|
||||||
|
gpioHandler = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GpioHandler* gpio_handler_get()
|
||||||
|
{
|
||||||
|
return gpioHandler;
|
||||||
|
}
|
||||||
|
|
||||||
114
code/components/jomjol_controlGPIO/server_GPIO.h
Normal file
114
code/components/jomjol_controlGPIO/server_GPIO.h
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef SERVER_GPIO_H
|
||||||
|
#define SERVER_GPIO_H
|
||||||
|
|
||||||
|
#include <esp_log.h>
|
||||||
|
|
||||||
|
#include <esp_http_server.h>
|
||||||
|
#include <map>
|
||||||
|
#include "driver/gpio.h"
|
||||||
|
|
||||||
|
#include "SmartLeds.h"
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
GPIO_PIN_MODE_DISABLED = 0x0,
|
||||||
|
GPIO_PIN_MODE_INPUT = 0x1,
|
||||||
|
GPIO_PIN_MODE_INPUT_PULLUP = 0x2,
|
||||||
|
GPIO_PIN_MODE_INPUT_PULLDOWN = 0x3,
|
||||||
|
GPIO_PIN_MODE_OUTPUT = 0x4,
|
||||||
|
GPIO_PIN_MODE_BUILT_IN_FLASH_LED = 0x5,
|
||||||
|
GPIO_PIN_MODE_OUTPUT_PWM = 0x6,
|
||||||
|
GPIO_PIN_MODE_EXTERNAL_FLASH_PWM = 0x7,
|
||||||
|
GPIO_PIN_MODE_EXTERNAL_FLASH_WS281X = 0x8,
|
||||||
|
} gpio_pin_mode_t;
|
||||||
|
|
||||||
|
struct GpioResult {
|
||||||
|
gpio_num_t gpio;
|
||||||
|
int value;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
GPIO_SET_SOURCE_INTERNAL = 0,
|
||||||
|
GPIO_SET_SOURCE_MQTT = 1,
|
||||||
|
GPIO_SET_SOURCE_HTTP = 2,
|
||||||
|
} gpio_set_source;
|
||||||
|
|
||||||
|
class GpioPin {
|
||||||
|
public:
|
||||||
|
GpioPin(gpio_num_t gpio, const char* name, gpio_pin_mode_t mode, gpio_int_type_t interruptType, uint8_t dutyResolution, std::string mqttTopic, bool httpEnable);
|
||||||
|
~GpioPin();
|
||||||
|
|
||||||
|
void init();
|
||||||
|
bool getValue(std::string* errorText);
|
||||||
|
void setValue(bool value, gpio_set_source setSource, std::string* errorText);
|
||||||
|
#ifdef ENABLE_MQTT
|
||||||
|
bool handleMQTT(std::string, char* data, int data_len);
|
||||||
|
#endif //ENABLE_MQTT
|
||||||
|
void publishState();
|
||||||
|
void gpioInterrupt(int value);
|
||||||
|
gpio_int_type_t getInterruptType() { return _interruptType; }
|
||||||
|
gpio_pin_mode_t getMode() { return _mode; }
|
||||||
|
gpio_num_t getGPIO(){return _gpio;};
|
||||||
|
|
||||||
|
private:
|
||||||
|
gpio_num_t _gpio;
|
||||||
|
const char* _name;
|
||||||
|
gpio_pin_mode_t _mode;
|
||||||
|
gpio_int_type_t _interruptType;
|
||||||
|
std::string _mqttTopic;
|
||||||
|
int currentState = -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
esp_err_t callHandleHttpRequest(httpd_req_t *req);
|
||||||
|
void taskGpioHandler(void *pvParameter);
|
||||||
|
|
||||||
|
class GpioHandler {
|
||||||
|
public:
|
||||||
|
GpioHandler(std::string configFile, httpd_handle_t httpServer);
|
||||||
|
~GpioHandler();
|
||||||
|
|
||||||
|
void init();
|
||||||
|
void deinit();
|
||||||
|
void registerGpioUri();
|
||||||
|
esp_err_t handleHttpRequest(httpd_req_t *req);
|
||||||
|
void taskHandler();
|
||||||
|
void gpioInterrupt(GpioResult* gpioResult);
|
||||||
|
void flashLightEnable(bool value);
|
||||||
|
bool isEnabled() { return _isEnabled; }
|
||||||
|
#ifdef ENABLE_MQTT
|
||||||
|
void handleMQTTconnect();
|
||||||
|
#endif //ENABLE_MQTT
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string _configFile;
|
||||||
|
httpd_handle_t _httpServer;
|
||||||
|
std::map<gpio_num_t, GpioPin*> *gpioMap = NULL;
|
||||||
|
TaskHandle_t xHandleTaskGpio = NULL;
|
||||||
|
bool _isEnabled = false;
|
||||||
|
|
||||||
|
int LEDNumbers = 2;
|
||||||
|
Rgb LEDColor = Rgb{ 255, 255, 255 };
|
||||||
|
LedType LEDType = LED_WS2812;
|
||||||
|
#ifdef __LEDGLOBAL
|
||||||
|
SmartLed *leds_global = NULL;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool readConfig();
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
gpio_num_t resolvePinNr(uint8_t pinNr);
|
||||||
|
gpio_pin_mode_t resolvePinMode(std::string input);
|
||||||
|
gpio_int_type_t resolveIntType(std::string input);
|
||||||
|
};
|
||||||
|
|
||||||
|
void gpio_handler_create(httpd_handle_t server);
|
||||||
|
void gpio_handler_init();
|
||||||
|
void gpio_handler_deinit();
|
||||||
|
void gpio_handler_destroy();
|
||||||
|
GpioHandler* gpio_handler_get();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endif //SERVER_GPIO_H
|
||||||
|
|
||||||
9
code/components/jomjol_controlcamera/CMakeLists.txt
Normal file
9
code/components/jomjol_controlcamera/CMakeLists.txt
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
FILE(GLOB_RECURSE app_sources ${CMAKE_CURRENT_SOURCE_DIR}/*.*)
|
||||||
|
|
||||||
|
list(APPEND EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)
|
||||||
|
|
||||||
|
idf_component_register(SRCS ${app_sources}
|
||||||
|
INCLUDE_DIRS "."
|
||||||
|
REQUIRES esp32-camera esp_http_server jomjol_logfile jomjol_image_proc nvs_flash jomjol_fileserver_ota jomjol_controlGPIO)
|
||||||
|
|
||||||
|
|
||||||
737
code/components/jomjol_controlcamera/ClassControllCamera.cpp
Normal file
737
code/components/jomjol_controlcamera/ClassControllCamera.cpp
Normal file
@@ -0,0 +1,737 @@
|
|||||||
|
#include "ClassControllCamera.h"
|
||||||
|
#include "ClassLogFile.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include "driver/gpio.h"
|
||||||
|
#include "esp_timer.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
|
||||||
|
#include "Helper.h"
|
||||||
|
#include "CImageBasis.h"
|
||||||
|
|
||||||
|
#include "server_ota.h"
|
||||||
|
#include "server_GPIO.h"
|
||||||
|
|
||||||
|
#include "../../include/defines.h"
|
||||||
|
|
||||||
|
#include <esp_event.h>
|
||||||
|
#include <esp_log.h>
|
||||||
|
#include <esp_system.h>
|
||||||
|
#include <nvs_flash.h>
|
||||||
|
#include <sys/param.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
|
||||||
|
#include "esp_camera.h"
|
||||||
|
|
||||||
|
#include "driver/ledc.h"
|
||||||
|
#include "server_tflite.h"
|
||||||
|
|
||||||
|
static const char *TAG = "CAM";
|
||||||
|
|
||||||
|
static camera_config_t camera_config = {
|
||||||
|
.pin_pwdn = CAM_PIN_PWDN,
|
||||||
|
.pin_reset = CAM_PIN_RESET,
|
||||||
|
.pin_xclk = CAM_PIN_XCLK,
|
||||||
|
.pin_sscb_sda = CAM_PIN_SIOD,
|
||||||
|
.pin_sscb_scl = CAM_PIN_SIOC,
|
||||||
|
|
||||||
|
.pin_d7 = CAM_PIN_D7,
|
||||||
|
.pin_d6 = CAM_PIN_D6,
|
||||||
|
.pin_d5 = CAM_PIN_D5,
|
||||||
|
.pin_d4 = CAM_PIN_D4,
|
||||||
|
.pin_d3 = CAM_PIN_D3,
|
||||||
|
.pin_d2 = CAM_PIN_D2,
|
||||||
|
.pin_d1 = CAM_PIN_D1,
|
||||||
|
.pin_d0 = CAM_PIN_D0,
|
||||||
|
.pin_vsync = CAM_PIN_VSYNC,
|
||||||
|
.pin_href = CAM_PIN_HREF,
|
||||||
|
.pin_pclk = CAM_PIN_PCLK,
|
||||||
|
|
||||||
|
//XCLK 20MHz or 10MHz for OV2640 double FPS (Experimental)
|
||||||
|
.xclk_freq_hz = 20000000, // Orginal value
|
||||||
|
// .xclk_freq_hz = 5000000, // Test to get rid of the image errors !!!! Hangs in version 9.2 !!!!
|
||||||
|
.ledc_timer = LEDC_TIMER_0,
|
||||||
|
.ledc_channel = LEDC_CHANNEL_0,
|
||||||
|
|
||||||
|
.pixel_format = PIXFORMAT_JPEG, //YUV422,GRAYSCALE,RGB565,JPEG
|
||||||
|
.frame_size = FRAMESIZE_VGA, //QQVGA-UXGA Do not use sizes above QVGA when not JPEG
|
||||||
|
// .frame_size = FRAMESIZE_UXGA, //QQVGA-UXGA Do not use sizes above QVGA when not JPEG
|
||||||
|
.jpeg_quality = 12, //0-63 lower number means higher quality
|
||||||
|
.fb_count = 1, //if more than one, i2s runs in continuous mode. Use only with JPEG
|
||||||
|
.fb_location = CAMERA_FB_IN_PSRAM, /*!< The location where the frame buffer will be allocated */
|
||||||
|
.grab_mode = CAMERA_GRAB_LATEST, // only from new esp32cam version
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
CCamera Camera;
|
||||||
|
|
||||||
|
uint8_t *demoImage = NULL; // Buffer holding the demo image in bytes
|
||||||
|
|
||||||
|
#define DEMO_IMAGE_SIZE 30000 // Max size of demo image in bytes
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
httpd_req_t *req;
|
||||||
|
size_t len;
|
||||||
|
} jpg_chunking_t;
|
||||||
|
|
||||||
|
|
||||||
|
bool CCamera::testCamera(void) {
|
||||||
|
bool success;
|
||||||
|
camera_fb_t *fb = esp_camera_fb_get();
|
||||||
|
if (fb) {
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_camera_fb_return(fb);
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void CCamera::ledc_init(void)
|
||||||
|
{
|
||||||
|
#ifdef USE_PWM_LEDFLASH
|
||||||
|
|
||||||
|
// Prepare and then apply the LEDC PWM timer configuration
|
||||||
|
ledc_timer_config_t ledc_timer = { };
|
||||||
|
|
||||||
|
ledc_timer.speed_mode = LEDC_MODE;
|
||||||
|
ledc_timer.timer_num = LEDC_TIMER;
|
||||||
|
ledc_timer.duty_resolution = LEDC_DUTY_RES;
|
||||||
|
ledc_timer.freq_hz = LEDC_FREQUENCY; // Set output frequency at 5 kHz
|
||||||
|
ledc_timer.clk_cfg = LEDC_AUTO_CLK;
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer));
|
||||||
|
|
||||||
|
// Prepare and then apply the LEDC PWM channel configuration
|
||||||
|
ledc_channel_config_t ledc_channel = { };
|
||||||
|
|
||||||
|
ledc_channel.speed_mode = LEDC_MODE;
|
||||||
|
ledc_channel.channel = LEDC_CHANNEL;
|
||||||
|
ledc_channel.timer_sel = LEDC_TIMER;
|
||||||
|
ledc_channel.intr_type = LEDC_INTR_DISABLE;
|
||||||
|
ledc_channel.gpio_num = LEDC_OUTPUT_IO;
|
||||||
|
ledc_channel.duty = 0; // Set duty to 0%
|
||||||
|
ledc_channel.hpoint = 0;
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel));
|
||||||
|
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static size_t jpg_encode_stream(void * arg, size_t index, const void* data, size_t len){
|
||||||
|
jpg_chunking_t *j = (jpg_chunking_t *)arg;
|
||||||
|
if(!index){
|
||||||
|
j->len = 0;
|
||||||
|
}
|
||||||
|
if(httpd_resp_send_chunk(j->req, (const char *)data, len) != ESP_OK){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
j->len += len;
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool CCamera::SetBrightnessContrastSaturation(int _brightness, int _contrast, int _saturation)
|
||||||
|
{
|
||||||
|
bool result = false;
|
||||||
|
sensor_t * s = esp_camera_sensor_get();
|
||||||
|
if (_brightness > -100)
|
||||||
|
_brightness = min(2, max(-2, _brightness));
|
||||||
|
if (_contrast > -100)
|
||||||
|
_contrast = min(2, max(-2, _contrast));
|
||||||
|
if (_saturation > -100)
|
||||||
|
_saturation = min(2, max(-2, _saturation));
|
||||||
|
|
||||||
|
if (_saturation > -100)
|
||||||
|
s->set_saturation(s, _saturation);
|
||||||
|
if (_contrast > -100)
|
||||||
|
s->set_contrast(s, _contrast);
|
||||||
|
if (_brightness > -100)
|
||||||
|
s->set_brightness(s, _brightness);
|
||||||
|
|
||||||
|
if ((_brightness != brightness) && (_brightness > -100))
|
||||||
|
result = true;
|
||||||
|
if ((_contrast != contrast) && (_contrast > -100))
|
||||||
|
result = true;
|
||||||
|
if ((_saturation != saturation) && (_saturation > -100))
|
||||||
|
result = true;
|
||||||
|
|
||||||
|
if (_brightness > -100)
|
||||||
|
brightness = _brightness;
|
||||||
|
if (_contrast > -100)
|
||||||
|
contrast = _contrast;
|
||||||
|
if (_saturation > -100)
|
||||||
|
saturation = _saturation;
|
||||||
|
|
||||||
|
if (result && isFixedExposure)
|
||||||
|
EnableAutoExposure(waitbeforepicture_org);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void CCamera::SetQualitySize(int qual, framesize_t resol)
|
||||||
|
{
|
||||||
|
sensor_t * s = esp_camera_sensor_get();
|
||||||
|
s->set_quality(s, qual);
|
||||||
|
s->set_framesize(s, resol);
|
||||||
|
ActualResolution = resol;
|
||||||
|
ActualQuality = qual;
|
||||||
|
|
||||||
|
if (resol == FRAMESIZE_QVGA)
|
||||||
|
{
|
||||||
|
image_height = 240;
|
||||||
|
image_width = 320;
|
||||||
|
}
|
||||||
|
if (resol == FRAMESIZE_VGA)
|
||||||
|
{
|
||||||
|
image_height = 480;
|
||||||
|
image_width = 640;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void CCamera::EnableAutoExposure(int flash_duration)
|
||||||
|
{
|
||||||
|
ESP_LOGD(TAG, "EnableAutoExposure");
|
||||||
|
LEDOnOff(true);
|
||||||
|
if (flash_duration > 0)
|
||||||
|
LightOnOff(true);
|
||||||
|
const TickType_t xDelay = flash_duration / portTICK_PERIOD_MS;
|
||||||
|
vTaskDelay( xDelay );
|
||||||
|
|
||||||
|
camera_fb_t * fb = esp_camera_fb_get();
|
||||||
|
esp_camera_fb_return(fb);
|
||||||
|
fb = esp_camera_fb_get();
|
||||||
|
if (!fb) {
|
||||||
|
ESP_LOGE(TAG, "Camera Capture Failed");
|
||||||
|
LEDOnOff(false);
|
||||||
|
LightOnOff(false);
|
||||||
|
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Capture Failed (Procedure 'EnableAutoExposure') --> Reboot! "
|
||||||
|
"Check that your camera module is working and connected properly.");
|
||||||
|
//doReboot();
|
||||||
|
}
|
||||||
|
esp_camera_fb_return(fb);
|
||||||
|
|
||||||
|
sensor_t * s = esp_camera_sensor_get();
|
||||||
|
s->set_gain_ctrl(s, 0);
|
||||||
|
s->set_exposure_ctrl(s, 0);
|
||||||
|
|
||||||
|
|
||||||
|
LEDOnOff(false);
|
||||||
|
LightOnOff(false);
|
||||||
|
isFixedExposure = true;
|
||||||
|
waitbeforepicture_org = flash_duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
esp_err_t CCamera::CaptureToBasisImage(CImageBasis *_Image, int delay)
|
||||||
|
{
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
LogFile.WriteHeapInfo("CCamera::CaptureToBasisImage - Start");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
_Image->EmptyImage(); //Delete previous stored raw image -> black image
|
||||||
|
|
||||||
|
LEDOnOff(true);
|
||||||
|
|
||||||
|
if (delay > 0)
|
||||||
|
{
|
||||||
|
LightOnOff(true);
|
||||||
|
const TickType_t xDelay = delay / portTICK_PERIOD_MS;
|
||||||
|
vTaskDelay( xDelay );
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
LogFile.WriteHeapInfo("CCamera::CaptureToBasisImage - After LightOn");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
camera_fb_t * fb = esp_camera_fb_get();
|
||||||
|
esp_camera_fb_return(fb);
|
||||||
|
fb = esp_camera_fb_get();
|
||||||
|
if (!fb) {
|
||||||
|
LEDOnOff(false);
|
||||||
|
LightOnOff(false);
|
||||||
|
|
||||||
|
ESP_LOGE(TAG, "CaptureToBasisImage: Capture Failed");
|
||||||
|
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "is not working anymore (CCamera::CaptureToBasisImage) - most probably caused by a hardware problem (instablility, ...). "
|
||||||
|
"System will reboot.");
|
||||||
|
doReboot();
|
||||||
|
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (demoMode) { // Use images stored on SD-Card instead of camera image
|
||||||
|
/* Replace Framebuffer with image from SD-Card */
|
||||||
|
loadNextDemoImage(fb);
|
||||||
|
}
|
||||||
|
|
||||||
|
CImageBasis* _zwImage = new CImageBasis();
|
||||||
|
_zwImage->LoadFromMemory(fb->buf, fb->len);
|
||||||
|
esp_camera_fb_return(fb);
|
||||||
|
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
LogFile.WriteHeapInfo("CCamera::CaptureToBasisImage - After fb_get");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
LEDOnOff(false);
|
||||||
|
|
||||||
|
if (delay > 0)
|
||||||
|
LightOnOff(false);
|
||||||
|
|
||||||
|
// TickType_t xDelay = 1000 / portTICK_PERIOD_MS;
|
||||||
|
// vTaskDelay( xDelay ); // wait for power to recover
|
||||||
|
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
LogFile.WriteHeapInfo("CCamera::CaptureToBasisImage - After LoadFromMemory");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
stbi_uc* p_target;
|
||||||
|
stbi_uc* p_source;
|
||||||
|
int channels = 3;
|
||||||
|
int width = image_width;
|
||||||
|
int height = image_height;
|
||||||
|
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
std::string _zw = "Targetimage: " + std::to_string((int) _Image->rgb_image) + " Size: " + std::to_string(_Image->width) + ", " + std::to_string(_Image->height);
|
||||||
|
_zw = _zw + " _zwImage: " + std::to_string((int) _zwImage->rgb_image) + " Size: " + std::to_string(_zwImage->width) + ", " + std::to_string(_zwImage->height);
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, _zw);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
for (int x = 0; x < width; ++x)
|
||||||
|
for (int y = 0; y < height; ++y)
|
||||||
|
{
|
||||||
|
p_target = _Image->rgb_image + (channels * (y * width + x));
|
||||||
|
p_source = _zwImage->rgb_image + (channels * (y * width + x));
|
||||||
|
p_target[0] = p_source[0];
|
||||||
|
p_target[1] = p_source[1];
|
||||||
|
p_target[2] = p_source[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
delete _zwImage;
|
||||||
|
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
LogFile.WriteHeapInfo("CCamera::CaptureToBasisImage - Done");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
esp_err_t CCamera::CaptureToFile(std::string nm, int delay)
|
||||||
|
{
|
||||||
|
string ftype;
|
||||||
|
|
||||||
|
LEDOnOff(true); // Switched off to save power !
|
||||||
|
|
||||||
|
if (delay > 0)
|
||||||
|
{
|
||||||
|
LightOnOff(true);
|
||||||
|
const TickType_t xDelay = delay / portTICK_PERIOD_MS;
|
||||||
|
vTaskDelay( xDelay );
|
||||||
|
}
|
||||||
|
|
||||||
|
camera_fb_t * fb = esp_camera_fb_get();
|
||||||
|
esp_camera_fb_return(fb);
|
||||||
|
fb = esp_camera_fb_get();
|
||||||
|
if (!fb) {
|
||||||
|
ESP_LOGE(TAG, "CaptureToFile: Camera Capture Failed");
|
||||||
|
LEDOnOff(false);
|
||||||
|
LightOnOff(false);
|
||||||
|
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Capture Failed (CCamera::CaptureToFile) --> Reboot! "
|
||||||
|
"Check that your camera module is working and connected properly.");
|
||||||
|
//doReboot();
|
||||||
|
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
LEDOnOff(false);
|
||||||
|
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
ESP_LOGD(TAG, "w %d, h %d, size %d", fb->width, fb->height, fb->len);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
nm = FormatFileName(nm);
|
||||||
|
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
ESP_LOGD(TAG, "Save Camera to: %s", nm.c_str());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ftype = toUpper(getFileType(nm));
|
||||||
|
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
ESP_LOGD(TAG, "Filetype: %s", ftype.c_str());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
uint8_t * buf = NULL;
|
||||||
|
size_t buf_len = 0;
|
||||||
|
bool converted = false;
|
||||||
|
|
||||||
|
if (ftype.compare("BMP") == 0)
|
||||||
|
{
|
||||||
|
frame2bmp(fb, &buf, &buf_len);
|
||||||
|
converted = true;
|
||||||
|
}
|
||||||
|
if (ftype.compare("JPG") == 0)
|
||||||
|
{
|
||||||
|
if(fb->format != PIXFORMAT_JPEG){
|
||||||
|
bool jpeg_converted = frame2jpg(fb, ActualQuality, &buf, &buf_len);
|
||||||
|
converted = true;
|
||||||
|
if(!jpeg_converted){
|
||||||
|
ESP_LOGE(TAG, "JPEG compression failed");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buf_len = fb->len;
|
||||||
|
buf = fb->buf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE * fp = fopen(nm.c_str(), "wb");
|
||||||
|
if (fp == NULL) /* If an error occurs during the file creation */
|
||||||
|
{
|
||||||
|
fprintf(stderr, "fopen() failed for '%s'\n", nm.c_str());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fwrite(buf, sizeof(uint8_t), buf_len, fp);
|
||||||
|
fclose(fp);
|
||||||
|
}
|
||||||
|
if (converted)
|
||||||
|
free(buf);
|
||||||
|
|
||||||
|
esp_camera_fb_return(fb);
|
||||||
|
|
||||||
|
if (delay > 0)
|
||||||
|
{
|
||||||
|
LightOnOff(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
esp_err_t CCamera::CaptureToHTTP(httpd_req_t *req, int delay)
|
||||||
|
{
|
||||||
|
camera_fb_t * fb = NULL;
|
||||||
|
esp_err_t res = ESP_OK;
|
||||||
|
size_t fb_len = 0;
|
||||||
|
int64_t fr_start = esp_timer_get_time();
|
||||||
|
|
||||||
|
|
||||||
|
LEDOnOff(true);
|
||||||
|
|
||||||
|
if (delay > 0)
|
||||||
|
{
|
||||||
|
LightOnOff(true);
|
||||||
|
const TickType_t xDelay = delay / portTICK_PERIOD_MS;
|
||||||
|
vTaskDelay( xDelay );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fb = esp_camera_fb_get();
|
||||||
|
esp_camera_fb_return(fb);
|
||||||
|
fb = esp_camera_fb_get();
|
||||||
|
if (!fb) {
|
||||||
|
ESP_LOGE(TAG, "Camera capture failed");
|
||||||
|
LEDOnOff(false);
|
||||||
|
LightOnOff(false);
|
||||||
|
httpd_resp_send_500(req);
|
||||||
|
// doReboot();
|
||||||
|
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
LEDOnOff(false);
|
||||||
|
|
||||||
|
res = httpd_resp_set_type(req, "image/jpeg");
|
||||||
|
if(res == ESP_OK){
|
||||||
|
res = httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=raw.jpg");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(res == ESP_OK){
|
||||||
|
if (demoMode) { // Use images stored on SD-Card instead of camera image
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Using Demo image!");
|
||||||
|
/* Replace Framebuffer with image from SD-Card */
|
||||||
|
loadNextDemoImage(fb);
|
||||||
|
|
||||||
|
res = httpd_resp_send(req, (const char *)fb->buf, fb->len);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if(fb->format == PIXFORMAT_JPEG){
|
||||||
|
fb_len = fb->len;
|
||||||
|
res = httpd_resp_send(req, (const char *)fb->buf, fb->len);
|
||||||
|
} else {
|
||||||
|
jpg_chunking_t jchunk = {req, 0};
|
||||||
|
res = frame2jpg_cb(fb, 80, jpg_encode_stream, &jchunk)?ESP_OK:ESP_FAIL;
|
||||||
|
httpd_resp_send_chunk(req, NULL, 0);
|
||||||
|
fb_len = jchunk.len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
esp_camera_fb_return(fb);
|
||||||
|
int64_t fr_end = esp_timer_get_time();
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "JPG: %uKB %ums", (uint32_t)(fb_len/1024), (uint32_t)((fr_end - fr_start)/1000));
|
||||||
|
|
||||||
|
if (delay > 0)
|
||||||
|
{
|
||||||
|
LightOnOff(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void CCamera::LightOnOff(bool status)
|
||||||
|
{
|
||||||
|
GpioHandler* gpioHandler = gpio_handler_get();
|
||||||
|
if ((gpioHandler != NULL) && (gpioHandler->isEnabled())) {
|
||||||
|
ESP_LOGD(TAG, "Use gpioHandler flashLigh");
|
||||||
|
gpioHandler->flashLightEnable(status);
|
||||||
|
} else {
|
||||||
|
#ifdef USE_PWM_LEDFLASH
|
||||||
|
if (status)
|
||||||
|
{
|
||||||
|
ESP_LOGD(TAG, "Internal Flash-LED turn on with PWM %d", led_intensity);
|
||||||
|
ESP_ERROR_CHECK(ledc_set_duty(LEDC_MODE, LEDC_CHANNEL, led_intensity));
|
||||||
|
// Update duty to apply the new value
|
||||||
|
ESP_ERROR_CHECK(ledc_update_duty(LEDC_MODE, LEDC_CHANNEL));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ESP_LOGD(TAG, "Internal Flash-LED turn off PWM");
|
||||||
|
ESP_ERROR_CHECK(ledc_set_duty(LEDC_MODE, LEDC_CHANNEL, 0));
|
||||||
|
ESP_ERROR_CHECK(ledc_update_duty(LEDC_MODE, LEDC_CHANNEL));
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
// Init the GPIO
|
||||||
|
gpio_pad_select_gpio(FLASH_GPIO);
|
||||||
|
// Set the GPIO as a push/pull output
|
||||||
|
gpio_set_direction(FLASH_GPIO, GPIO_MODE_OUTPUT);
|
||||||
|
|
||||||
|
if (status)
|
||||||
|
gpio_set_level(FLASH_GPIO, 1);
|
||||||
|
else
|
||||||
|
gpio_set_level(FLASH_GPIO, 0);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void CCamera::LEDOnOff(bool status)
|
||||||
|
{
|
||||||
|
// Init the GPIO
|
||||||
|
gpio_pad_select_gpio(BLINK_GPIO);
|
||||||
|
/* Set the GPIO as a push/pull output */
|
||||||
|
gpio_set_direction(BLINK_GPIO, GPIO_MODE_OUTPUT);
|
||||||
|
|
||||||
|
if (!status)
|
||||||
|
gpio_set_level(BLINK_GPIO, 1);
|
||||||
|
else
|
||||||
|
gpio_set_level(BLINK_GPIO, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void CCamera::GetCameraParameter(httpd_req_t *req, int &qual, framesize_t &resol)
|
||||||
|
{
|
||||||
|
char _query[100];
|
||||||
|
char _qual[10];
|
||||||
|
char _size[10];
|
||||||
|
|
||||||
|
resol = ActualResolution;
|
||||||
|
qual = ActualQuality;
|
||||||
|
|
||||||
|
|
||||||
|
if (httpd_req_get_url_query_str(req, _query, 100) == ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGD(TAG, "Query: %s", _query);
|
||||||
|
if (httpd_query_key_value(_query, "size", _size, 10) == ESP_OK)
|
||||||
|
{
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
ESP_LOGD(TAG, "Size: %s", _size);
|
||||||
|
#endif
|
||||||
|
if (strcmp(_size, "QVGA") == 0)
|
||||||
|
resol = FRAMESIZE_QVGA; // 320x240
|
||||||
|
if (strcmp(_size, "VGA") == 0)
|
||||||
|
resol = FRAMESIZE_VGA; // 640x480
|
||||||
|
if (strcmp(_size, "SVGA") == 0)
|
||||||
|
resol = FRAMESIZE_SVGA; // 800x600
|
||||||
|
if (strcmp(_size, "XGA") == 0)
|
||||||
|
resol = FRAMESIZE_XGA; // 1024x768
|
||||||
|
if (strcmp(_size, "SXGA") == 0)
|
||||||
|
resol = FRAMESIZE_SXGA; // 1280x1024
|
||||||
|
if (strcmp(_size, "UXGA") == 0)
|
||||||
|
resol = FRAMESIZE_UXGA; // 1600x1200
|
||||||
|
}
|
||||||
|
if (httpd_query_key_value(_query, "quality", _qual, 10) == ESP_OK)
|
||||||
|
{
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
ESP_LOGD(TAG, "Quality: %s", _qual);
|
||||||
|
#endif
|
||||||
|
qual = atoi(_qual);
|
||||||
|
|
||||||
|
if (qual > 63)
|
||||||
|
qual = 63;
|
||||||
|
if (qual < 0)
|
||||||
|
qual = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
framesize_t CCamera::TextToFramesize(const char * _size)
|
||||||
|
{
|
||||||
|
if (strcmp(_size, "QVGA") == 0)
|
||||||
|
return FRAMESIZE_QVGA; // 320x240
|
||||||
|
if (strcmp(_size, "VGA") == 0)
|
||||||
|
return FRAMESIZE_VGA; // 640x480
|
||||||
|
if (strcmp(_size, "SVGA") == 0)
|
||||||
|
return FRAMESIZE_SVGA; // 800x600
|
||||||
|
if (strcmp(_size, "XGA") == 0)
|
||||||
|
return FRAMESIZE_XGA; // 1024x768
|
||||||
|
if (strcmp(_size, "SXGA") == 0)
|
||||||
|
return FRAMESIZE_SXGA; // 1280x1024
|
||||||
|
if (strcmp(_size, "UXGA") == 0)
|
||||||
|
return FRAMESIZE_UXGA; // 1600x1200
|
||||||
|
return ActualResolution;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
CCamera::CCamera()
|
||||||
|
{
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
ESP_LOGD(TAG, "CreateClassCamera");
|
||||||
|
#endif
|
||||||
|
brightness = -5;
|
||||||
|
contrast = -5;
|
||||||
|
saturation = -5;
|
||||||
|
isFixedExposure = false;
|
||||||
|
|
||||||
|
ledc_init();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
esp_err_t CCamera::InitCam()
|
||||||
|
{
|
||||||
|
ESP_LOGD(TAG, "Init Camera");
|
||||||
|
ActualQuality = camera_config.jpeg_quality;
|
||||||
|
ActualResolution = camera_config.frame_size;
|
||||||
|
//initialize the camera
|
||||||
|
esp_camera_deinit(); // De-init in case it was already initialized
|
||||||
|
esp_err_t err = esp_camera_init(&camera_config);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "Camera Init Failed");
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
CameraInitSuccessful = true;
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void CCamera::SetLEDIntensity(float _intrel)
|
||||||
|
{
|
||||||
|
_intrel = min(_intrel, (float) 100);
|
||||||
|
_intrel = max(_intrel, (float) 0);
|
||||||
|
_intrel = _intrel / 100;
|
||||||
|
led_intensity = (int) (_intrel * 8191);
|
||||||
|
ESP_LOGD(TAG, "Set led_intensity to %d of 8191", led_intensity);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool CCamera::getCameraInitSuccessful()
|
||||||
|
{
|
||||||
|
return CameraInitSuccessful;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<std::string> demoFiles;
|
||||||
|
|
||||||
|
void CCamera::useDemoMode()
|
||||||
|
{
|
||||||
|
char line[50];
|
||||||
|
|
||||||
|
FILE *fd = fopen("/sdcard/demo/files.txt", "r");
|
||||||
|
if (!fd) {
|
||||||
|
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Can not start Demo mode, the folder '/sdcard/demo/' does not contain the needed files!");
|
||||||
|
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "See Details on https://jomjol.github.io/AI-on-the-edge-device-docs/Demo-Mode!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
demoImage = (uint8_t*)malloc(DEMO_IMAGE_SIZE);
|
||||||
|
if (demoImage == NULL) {
|
||||||
|
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Unable to acquire required memory for demo image!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (fgets(line, sizeof(line), fd) != NULL) {
|
||||||
|
line[strlen(line) - 1] = '\0';
|
||||||
|
demoFiles.push_back(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(fd);
|
||||||
|
|
||||||
|
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Using Demo mode (" + std::to_string(demoFiles.size()) +
|
||||||
|
" files) instead of real camera image!");
|
||||||
|
|
||||||
|
for (auto file : demoFiles) {
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, file);
|
||||||
|
}
|
||||||
|
|
||||||
|
demoMode = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool CCamera::loadNextDemoImage(camera_fb_t *fb) {
|
||||||
|
char filename[50];
|
||||||
|
int readBytes;
|
||||||
|
long fileSize;
|
||||||
|
|
||||||
|
snprintf(filename, sizeof(filename), "/sdcard/demo/%s", demoFiles[getCountFlowRounds() % demoFiles.size()].c_str());
|
||||||
|
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Using " + std::string(filename) + " as demo image");
|
||||||
|
|
||||||
|
/* Inject saved image */
|
||||||
|
|
||||||
|
FILE * fp = fopen(filename, "rb");
|
||||||
|
if (!fp) {
|
||||||
|
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to read file: " + std::string(filename) +"!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fileSize = GetFileSize(filename);
|
||||||
|
if (fileSize > DEMO_IMAGE_SIZE) {
|
||||||
|
char buf[100];
|
||||||
|
snprintf(buf, sizeof(buf), "Demo Image (%d bytes) is larger than provided buffer (%d bytes)!",
|
||||||
|
(int)fileSize, DEMO_IMAGE_SIZE);
|
||||||
|
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, std::string(buf));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
readBytes = fread(demoImage, 1, DEMO_IMAGE_SIZE, fp);
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "read " + std::to_string(readBytes) + " bytes");
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
fb->buf = demoImage; // Update pointer
|
||||||
|
fb->len = readBytes;
|
||||||
|
// ToDo do we also need to set height, width, format and timestamp?
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
long CCamera::GetFileSize(std::string filename)
|
||||||
|
{
|
||||||
|
struct stat stat_buf;
|
||||||
|
long rc = stat(filename.c_str(), &stat_buf);
|
||||||
|
return rc == 0 ? stat_buf.st_size : -1;
|
||||||
|
}
|
||||||
62
code/components/jomjol_controlcamera/ClassControllCamera.h
Normal file
62
code/components/jomjol_controlcamera/ClassControllCamera.h
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef CLASSCONTROLLCAMERA_H
|
||||||
|
#define CLASSCONTROLLCAMERA_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include "freertos/queue.h"
|
||||||
|
#include "freertos/event_groups.h"
|
||||||
|
|
||||||
|
#include "esp_camera.h"
|
||||||
|
#include <string>
|
||||||
|
#include <esp_http_server.h>
|
||||||
|
#include "CImageBasis.h"
|
||||||
|
#include "../../include/defines.h"
|
||||||
|
|
||||||
|
class CCamera {
|
||||||
|
protected:
|
||||||
|
int ActualQuality;
|
||||||
|
framesize_t ActualResolution;
|
||||||
|
int brightness, contrast, saturation;
|
||||||
|
bool isFixedExposure;
|
||||||
|
int waitbeforepicture_org;
|
||||||
|
int led_intensity = 4095;
|
||||||
|
|
||||||
|
void ledc_init(void);
|
||||||
|
bool CameraInitSuccessful = false;
|
||||||
|
bool demoMode = false;
|
||||||
|
|
||||||
|
bool loadNextDemoImage(camera_fb_t *fb);
|
||||||
|
long GetFileSize(std::string filename);
|
||||||
|
|
||||||
|
public:
|
||||||
|
int image_height, image_width;
|
||||||
|
|
||||||
|
CCamera();
|
||||||
|
esp_err_t InitCam();
|
||||||
|
|
||||||
|
void LightOnOff(bool status);
|
||||||
|
void LEDOnOff(bool status);
|
||||||
|
esp_err_t CaptureToHTTP(httpd_req_t *req, int delay = 0);
|
||||||
|
void SetQualitySize(int qual, framesize_t resol);
|
||||||
|
bool SetBrightnessContrastSaturation(int _brightness, int _contrast, int _saturation);
|
||||||
|
void GetCameraParameter(httpd_req_t *req, int &qual, framesize_t &resol);
|
||||||
|
void SetLEDIntensity(float _intrel);
|
||||||
|
bool testCamera(void);
|
||||||
|
void EnableAutoExposure(int flash_duration);
|
||||||
|
bool getCameraInitSuccessful();
|
||||||
|
void useDemoMode(void);
|
||||||
|
|
||||||
|
|
||||||
|
framesize_t TextToFramesize(const char * text);
|
||||||
|
|
||||||
|
esp_err_t CaptureToFile(std::string nm, int delay = 0);
|
||||||
|
esp_err_t CaptureToBasisImage(CImageBasis *_Image, int delay = 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
extern CCamera Camera;
|
||||||
|
|
||||||
|
#endif
|
||||||
290
code/components/jomjol_controlcamera/server_camera.cpp
Normal file
290
code/components/jomjol_controlcamera/server_camera.cpp
Normal file
@@ -0,0 +1,290 @@
|
|||||||
|
#include "server_camera.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include "string.h"
|
||||||
|
|
||||||
|
#include "esp_camera.h"
|
||||||
|
#include "ClassControllCamera.h"
|
||||||
|
|
||||||
|
#include "ClassLogFile.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
|
||||||
|
#include "../../include/defines.h"
|
||||||
|
|
||||||
|
static const char *TAG = "server_cam";
|
||||||
|
|
||||||
|
|
||||||
|
void PowerResetCamera(){
|
||||||
|
|
||||||
|
ESP_LOGD(TAG, "Resetting camera by power down line");
|
||||||
|
gpio_config_t conf;
|
||||||
|
conf.intr_type = GPIO_INTR_DISABLE;
|
||||||
|
conf.pin_bit_mask = 1LL << GPIO_NUM_32;
|
||||||
|
conf.mode = GPIO_MODE_OUTPUT;
|
||||||
|
conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
||||||
|
conf.pull_up_en = GPIO_PULLUP_DISABLE;
|
||||||
|
gpio_config(&conf);
|
||||||
|
|
||||||
|
// carefull, logic is inverted compared to reset pin
|
||||||
|
gpio_set_level(GPIO_NUM_32, 1);
|
||||||
|
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||||
|
gpio_set_level(GPIO_NUM_32, 0);
|
||||||
|
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
esp_err_t handler_lightOn(httpd_req_t *req)
|
||||||
|
{
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
LogFile.WriteHeapInfo("handler_lightOn - Start");
|
||||||
|
ESP_LOGD(TAG, "handler_lightOn uri: %s", req->uri);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (Camera.getCameraInitSuccessful())
|
||||||
|
{
|
||||||
|
Camera.LightOnOff(true);
|
||||||
|
const char* resp_str = (const char*) req->user_ctx;
|
||||||
|
httpd_resp_send(req, resp_str, strlen(resp_str));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
httpd_resp_send_err(req, HTTPD_403_FORBIDDEN, "Camera not initialized: REST API /lighton not available!");
|
||||||
|
return ESP_ERR_NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
LogFile.WriteHeapInfo("handler_lightOn - Done");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
esp_err_t handler_lightOff(httpd_req_t *req)
|
||||||
|
{
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
LogFile.WriteHeapInfo("handler_lightOff - Start");
|
||||||
|
ESP_LOGD(TAG, "handler_lightOff uri: %s", req->uri);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (Camera.getCameraInitSuccessful())
|
||||||
|
{
|
||||||
|
Camera.LightOnOff(false);
|
||||||
|
const char* resp_str = (const char*) req->user_ctx;
|
||||||
|
httpd_resp_send(req, resp_str, strlen(resp_str));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
httpd_resp_send_err(req, HTTPD_403_FORBIDDEN, "Camera not initialized: REST API /lightoff not available!");
|
||||||
|
return ESP_ERR_NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
LogFile.WriteHeapInfo("handler_lightOff - Done");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
esp_err_t handler_capture(httpd_req_t *req)
|
||||||
|
{
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
LogFile.WriteHeapInfo("handler_capture - Start");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (Camera.getCameraInitSuccessful())
|
||||||
|
{
|
||||||
|
int quality;
|
||||||
|
framesize_t res;
|
||||||
|
|
||||||
|
Camera.GetCameraParameter(req, quality, res);
|
||||||
|
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
ESP_LOGD(TAG, "Size: %d, Quality: %d", res, quality);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Camera.SetQualitySize(quality, res);
|
||||||
|
|
||||||
|
esp_err_t result;
|
||||||
|
result = Camera.CaptureToHTTP(req);
|
||||||
|
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
LogFile.WriteHeapInfo("handler_capture - Done");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
httpd_resp_send_err(req, HTTPD_403_FORBIDDEN, "Camera not initialized: REST API /capture not available!");
|
||||||
|
return ESP_ERR_NOT_FOUND;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
esp_err_t handler_capture_with_light(httpd_req_t *req)
|
||||||
|
{
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
LogFile.WriteHeapInfo("handler_capture_with_light - Start");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (Camera.getCameraInitSuccessful())
|
||||||
|
{
|
||||||
|
char _query[100];
|
||||||
|
char _delay[10];
|
||||||
|
|
||||||
|
int quality;
|
||||||
|
framesize_t res;
|
||||||
|
int delay = 2500;
|
||||||
|
|
||||||
|
if (httpd_req_get_url_query_str(req, _query, 100) == ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGD(TAG, "Query: %s", _query);
|
||||||
|
if (httpd_query_key_value(_query, "delay", _delay, 10) == ESP_OK)
|
||||||
|
{
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
ESP_LOGD(TAG, "Delay: %s", _delay);
|
||||||
|
#endif
|
||||||
|
delay = atoi(_delay);
|
||||||
|
|
||||||
|
if (delay < 0)
|
||||||
|
delay = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Camera.GetCameraParameter(req, quality, res);
|
||||||
|
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
ESP_LOGD(TAG, "Size: %d, Quality: %d", res, quality);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Camera.SetQualitySize(quality, res);
|
||||||
|
Camera.LightOnOff(true);
|
||||||
|
const TickType_t xDelay = delay / portTICK_PERIOD_MS;
|
||||||
|
vTaskDelay( xDelay );
|
||||||
|
|
||||||
|
esp_err_t result;
|
||||||
|
result = Camera.CaptureToHTTP(req);
|
||||||
|
|
||||||
|
Camera.LightOnOff(false);
|
||||||
|
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
LogFile.WriteHeapInfo("handler_capture_with_light - Done");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
httpd_resp_send_err(req, HTTPD_403_FORBIDDEN, "Camera not initialized: REST API /capture_with_flashlight not available!");
|
||||||
|
return ESP_ERR_NOT_FOUND;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
esp_err_t handler_capture_save_to_file(httpd_req_t *req)
|
||||||
|
{
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
LogFile.WriteHeapInfo("handler_capture_save_to_file - Start");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (Camera.getCameraInitSuccessful())
|
||||||
|
{
|
||||||
|
char _query[100];
|
||||||
|
char _delay[10];
|
||||||
|
int delay = 0;
|
||||||
|
char filename[100];
|
||||||
|
std::string fn = "/sdcard/";
|
||||||
|
|
||||||
|
|
||||||
|
int quality;
|
||||||
|
framesize_t res;
|
||||||
|
|
||||||
|
if (httpd_req_get_url_query_str(req, _query, 100) == ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGD(TAG, "Query: %s", _query);
|
||||||
|
if (httpd_query_key_value(_query, "filename", filename, 100) == ESP_OK)
|
||||||
|
{
|
||||||
|
fn.append(filename);
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
ESP_LOGD(TAG, "Filename: %s", fn.c_str());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
else
|
||||||
|
fn.append("noname.jpg");
|
||||||
|
|
||||||
|
if (httpd_query_key_value(_query, "delay", _delay, 10) == ESP_OK)
|
||||||
|
{
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
ESP_LOGD(TAG, "Delay: %s", _delay);
|
||||||
|
#endif
|
||||||
|
delay = atoi(_delay);
|
||||||
|
|
||||||
|
if (delay < 0)
|
||||||
|
delay = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
fn.append("noname.jpg");
|
||||||
|
|
||||||
|
Camera.GetCameraParameter(req, quality, res);
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
ESP_LOGD(TAG, "Size: %d, Quality: %d", res, quality);
|
||||||
|
#endif
|
||||||
|
Camera.SetQualitySize(quality, res);
|
||||||
|
|
||||||
|
esp_err_t result;
|
||||||
|
result = Camera.CaptureToFile(fn, delay);
|
||||||
|
|
||||||
|
const char* resp_str = (const char*) fn.c_str();
|
||||||
|
httpd_resp_send(req, resp_str, strlen(resp_str));
|
||||||
|
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
LogFile.WriteHeapInfo("handler_capture_save_to_file - Done");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
httpd_resp_send_err(req, HTTPD_403_FORBIDDEN, "Camera not initialized: REST API /save not available!");
|
||||||
|
return ESP_ERR_NOT_FOUND;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void register_server_camera_uri(httpd_handle_t server)
|
||||||
|
{
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
ESP_LOGI(TAG, "server_part_camera - Registering URI handlers");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
httpd_uri_t camuri = { };
|
||||||
|
camuri.method = HTTP_GET;
|
||||||
|
|
||||||
|
camuri.uri = "/lighton";
|
||||||
|
camuri.handler = handler_lightOn;
|
||||||
|
camuri.user_ctx = (void*) "Light On";
|
||||||
|
httpd_register_uri_handler(server, &camuri);
|
||||||
|
|
||||||
|
camuri.uri = "/lightoff";
|
||||||
|
camuri.handler = handler_lightOff;
|
||||||
|
camuri.user_ctx = (void*) "Light Off";
|
||||||
|
httpd_register_uri_handler(server, &camuri);
|
||||||
|
|
||||||
|
camuri.uri = "/capture";
|
||||||
|
camuri.handler = handler_capture;
|
||||||
|
camuri.user_ctx = NULL;
|
||||||
|
httpd_register_uri_handler(server, &camuri);
|
||||||
|
|
||||||
|
camuri.uri = "/capture_with_flashlight";
|
||||||
|
camuri.handler = handler_capture_with_light;
|
||||||
|
camuri.user_ctx = NULL;
|
||||||
|
httpd_register_uri_handler(server, &camuri);
|
||||||
|
|
||||||
|
camuri.uri = "/save";
|
||||||
|
camuri.handler = handler_capture_save_to_file;
|
||||||
|
camuri.user_ctx = NULL;
|
||||||
|
httpd_register_uri_handler(server, &camuri);
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
#ifndef JOMJOL_CONTROLCAMERA_H
|
#ifndef JOMJOL_CONTROLCAMERA_H
|
||||||
#define JOMJOL_CONTROLCAMERA_H
|
#define JOMJOL_CONTROLCAMERA_H
|
||||||
|
|
||||||
@@ -7,8 +9,6 @@
|
|||||||
|
|
||||||
//#include "ClassControllCamera.h"
|
//#include "ClassControllCamera.h"
|
||||||
|
|
||||||
static const char *TAGPARTCAMERA = "server_camera";
|
|
||||||
|
|
||||||
void register_server_camera_uri(httpd_handle_t server);
|
void register_server_camera_uri(httpd_handle_t server);
|
||||||
|
|
||||||
void PowerResetCamera();
|
void PowerResetCamera();
|
||||||
7
code/components/jomjol_fileserver_ota/CMakeLists.txt
Normal file
7
code/components/jomjol_fileserver_ota/CMakeLists.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
FILE(GLOB_RECURSE app_sources ${CMAKE_CURRENT_SOURCE_DIR}/*.*)
|
||||||
|
|
||||||
|
idf_component_register(SRCS ${app_sources}
|
||||||
|
INCLUDE_DIRS "." "../../include"
|
||||||
|
REQUIRES tflite-lib esp_http_server app_update esp_http_client nvs_flash jomjol_tfliteclass jomjol_flowcontroll spiffs jomjol_helper jomjol_controlGPIO miniz)
|
||||||
|
|
||||||
|
|
||||||
1212
code/components/jomjol_fileserver_ota/server_file.cpp
Normal file
1212
code/components/jomjol_fileserver_ota/server_file.cpp
Normal file
File diff suppressed because it is too large
Load Diff
21
code/components/jomjol_fileserver_ota/server_file.h
Normal file
21
code/components/jomjol_fileserver_ota/server_file.h
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef SERVERFILE_H
|
||||||
|
#define SERVERFILE_H
|
||||||
|
|
||||||
|
#include <esp_http_server.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
void register_server_file_uri(httpd_handle_t server, const char *base_path);
|
||||||
|
|
||||||
|
void unzip(std::string _in_zip_file, std::string _target_directory);
|
||||||
|
std::string unzip_new(std::string _in_zip_file, std::string _target_zip, std::string _target_bin, std::string _main = "/sdcard/", bool _initial_setup = false);
|
||||||
|
|
||||||
|
|
||||||
|
void delete_all_in_directory(std::string _directory);
|
||||||
|
|
||||||
|
esp_err_t get_tflite_file_handler(httpd_req_t *req);
|
||||||
|
esp_err_t get_data_file_handler(httpd_req_t *req);
|
||||||
|
esp_err_t get_numbers_file_handler(httpd_req_t *req);
|
||||||
|
|
||||||
|
#endif //SERVERFILE_H
|
||||||
@@ -5,35 +5,65 @@
|
|||||||
#include <sys/param.h>
|
#include <sys/param.h>
|
||||||
#include <sys/unistd.h>
|
#include <sys/unistd.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "esp_err.h"
|
#include "esp_err.h"
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
|
|
||||||
|
#include "Helper.h"
|
||||||
|
|
||||||
#include "esp_http_server.h"
|
#include "esp_http_server.h"
|
||||||
|
|
||||||
|
#include "../../include/defines.h"
|
||||||
static const char *TAG = "serverhelp";
|
|
||||||
|
|
||||||
#define SCRATCH_BUFSIZE 8192
|
|
||||||
char scratch[SCRATCH_BUFSIZE];
|
|
||||||
|
|
||||||
|
|
||||||
#define IS_FILE_EXT(filename, ext) \
|
static const char *TAG = "SERVER HELP";
|
||||||
(strcasecmp(&filename[strlen(filename) - sizeof(ext) + 1], ext) == 0)
|
|
||||||
|
char scratch[SERVER_HELPER_SCRATCH_BUFSIZE];
|
||||||
|
|
||||||
|
|
||||||
esp_err_t send_file(httpd_req_t *req, std::string filename, struct stat * file_stat)
|
bool endsWith(std::string const &str, std::string const &suffix) {
|
||||||
|
if (str.length() < suffix.length()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return str.compare(str.length() - suffix.length(), suffix.length(), suffix) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
esp_err_t send_file(httpd_req_t *req, std::string filename)
|
||||||
{
|
{
|
||||||
FILE *fd = fopen(filename.c_str(), "r");
|
FILE *fd = fopen(filename.c_str(), "r");
|
||||||
if (!fd) {
|
if (!fd) {
|
||||||
ESP_LOGE(TAG, "Failed to read existing file : %s", filename.c_str());
|
ESP_LOGE(TAG, "Failed to read file: %s", filename.c_str());
|
||||||
/* Respond with 500 Internal Server Error */
|
/* Respond with 404 Error */
|
||||||
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to read existing file");
|
httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, get404());
|
||||||
return ESP_FAIL;
|
return ESP_FAIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGI(TAG, "Sending file : %s (%ld bytes)...", filename.c_str(), file_stat->st_size);
|
ESP_LOGD(TAG, "Sending file: %s ...", filename.c_str());
|
||||||
|
// httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
|
||||||
|
|
||||||
|
/* For all files with the following file extention tell
|
||||||
|
the webbrowser to cache them for 24h */
|
||||||
|
if (endsWith(filename, ".html") ||
|
||||||
|
endsWith(filename, ".htm") ||
|
||||||
|
endsWith(filename, ".css") ||
|
||||||
|
endsWith(filename, ".js") ||
|
||||||
|
endsWith(filename, ".map") ||
|
||||||
|
endsWith(filename, ".jpg") ||
|
||||||
|
endsWith(filename, ".jpeg") ||
|
||||||
|
endsWith(filename, ".ico") ||
|
||||||
|
endsWith(filename, ".png")) {
|
||||||
|
httpd_resp_set_hdr(req, "Cache-Control", "max-age=86400");
|
||||||
|
}
|
||||||
|
|
||||||
set_content_type_from_file(req, filename.c_str());
|
set_content_type_from_file(req, filename.c_str());
|
||||||
|
|
||||||
/* Retrieve the pointer to scratch buffer for temporary storage */
|
/* Retrieve the pointer to scratch buffer for temporary storage */
|
||||||
@@ -41,7 +71,7 @@ esp_err_t send_file(httpd_req_t *req, std::string filename, struct stat * file_
|
|||||||
size_t chunksize;
|
size_t chunksize;
|
||||||
do {
|
do {
|
||||||
/* Read file in chunks into the scratch buffer */
|
/* Read file in chunks into the scratch buffer */
|
||||||
chunksize = fread(chunk, 1, SCRATCH_BUFSIZE, fd);
|
chunksize = fread(chunk, 1, SERVER_HELPER_SCRATCH_BUFSIZE, fd);
|
||||||
|
|
||||||
/* Send the buffer contents as HTTP response chunk */
|
/* Send the buffer contents as HTTP response chunk */
|
||||||
if (httpd_resp_send_chunk(req, chunk, chunksize) != ESP_OK) {
|
if (httpd_resp_send_chunk(req, chunk, chunksize) != ESP_OK) {
|
||||||
@@ -59,12 +89,13 @@ esp_err_t send_file(httpd_req_t *req, std::string filename, struct stat * file_
|
|||||||
|
|
||||||
/* Close file after sending complete */
|
/* Close file after sending complete */
|
||||||
fclose(fd);
|
fclose(fd);
|
||||||
ESP_LOGI(TAG, "File sending complete");
|
ESP_LOGD(TAG, "File sending complete");
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Copies the full path into destination buffer and returns
|
/* Copies the full path into destination buffer and returns
|
||||||
* pointer to path (skipping the preceding base path) */
|
* pointer to path (skipping the preceding base path) */
|
||||||
const char* get_path_from_uri(char *dest, const char *base_path, const char *uri, size_t destsize)
|
const char* get_path_from_uri(char *dest, const char *base_path, const char *uri, size_t destsize)
|
||||||
@@ -104,8 +135,14 @@ esp_err_t set_content_type_from_file(httpd_req_t *req, const char *filename)
|
|||||||
return httpd_resp_set_type(req, "text/html");
|
return httpd_resp_set_type(req, "text/html");
|
||||||
} else if (IS_FILE_EXT(filename, ".jpeg")) {
|
} else if (IS_FILE_EXT(filename, ".jpeg")) {
|
||||||
return httpd_resp_set_type(req, "image/jpeg");
|
return httpd_resp_set_type(req, "image/jpeg");
|
||||||
|
} else if (IS_FILE_EXT(filename, ".jpg")) {
|
||||||
|
return httpd_resp_set_type(req, "image/jpeg");
|
||||||
} else if (IS_FILE_EXT(filename, ".ico")) {
|
} else if (IS_FILE_EXT(filename, ".ico")) {
|
||||||
return httpd_resp_set_type(req, "image/x-icon");
|
return httpd_resp_set_type(req, "image/x-icon");
|
||||||
|
} else if (IS_FILE_EXT(filename, ".js")) {
|
||||||
|
return httpd_resp_set_type(req, "text/javascript");
|
||||||
|
} else if (IS_FILE_EXT(filename, ".css")) {
|
||||||
|
return httpd_resp_set_type(req, "text/css");
|
||||||
}
|
}
|
||||||
/* This is a limited set only */
|
/* This is a limited set only */
|
||||||
/* For any other type always set as plain text */
|
/* For any other type always set as plain text */
|
||||||
@@ -1,3 +1,8 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef SERVERHELP_H
|
||||||
|
#define SERVERHELP_H
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
//#include <sys/param.h>
|
//#include <sys/param.h>
|
||||||
#include "esp_http_server.h"
|
#include "esp_http_server.h"
|
||||||
@@ -5,6 +10,8 @@
|
|||||||
|
|
||||||
const char* get_path_from_uri(char *dest, const char *base_path, const char *uri, size_t destsize);
|
const char* get_path_from_uri(char *dest, const char *base_path, const char *uri, size_t destsize);
|
||||||
|
|
||||||
esp_err_t send_file(httpd_req_t *req, std::string filename, struct stat * file_stat);
|
esp_err_t send_file(httpd_req_t *req, std::string filename);
|
||||||
|
|
||||||
esp_err_t set_content_type_from_file(httpd_req_t *req, const char *filename);
|
esp_err_t set_content_type_from_file(httpd_req_t *req, const char *filename);
|
||||||
|
|
||||||
|
#endif //SERVERHELP_H
|
||||||
686
code/components/jomjol_fileserver_ota/server_ota.cpp
Normal file
686
code/components/jomjol_fileserver_ota/server_ota.cpp
Normal file
@@ -0,0 +1,686 @@
|
|||||||
|
#include "server_ota.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include "string.h"
|
||||||
|
|
||||||
|
#include <esp_int_wdt.h>
|
||||||
|
#include <esp_task_wdt.h>
|
||||||
|
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include "esp_system.h"
|
||||||
|
#include "esp_event.h"
|
||||||
|
#include "esp_event.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include <esp_ota_ops.h>
|
||||||
|
#include "esp_http_client.h"
|
||||||
|
#include "esp_flash_partitions.h"
|
||||||
|
#include "esp_partition.h"
|
||||||
|
#include <nvs.h>
|
||||||
|
#include "nvs_flash.h"
|
||||||
|
#include "driver/gpio.h"
|
||||||
|
// #include "protocol_examples_common.h"
|
||||||
|
#include "errno.h"
|
||||||
|
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#include "server_tflite.h"
|
||||||
|
#include "server_file.h"
|
||||||
|
#include "server_GPIO.h"
|
||||||
|
#ifdef ENABLE_MQTT
|
||||||
|
#include "interface_mqtt.h"
|
||||||
|
#endif //ENABLE_MQTT
|
||||||
|
#include "ClassControllCamera.h"
|
||||||
|
#include "connect_wlan.h"
|
||||||
|
|
||||||
|
|
||||||
|
#include "ClassLogFile.h"
|
||||||
|
|
||||||
|
#include "Helper.h"
|
||||||
|
#include "../../include/defines.h"
|
||||||
|
|
||||||
|
/*an ota data write buffer ready to write to the flash*/
|
||||||
|
static char ota_write_data[SERVER_OTA_SCRATCH_BUFSIZE + 1] = { 0 };
|
||||||
|
|
||||||
|
static const char *TAG = "OTA";
|
||||||
|
|
||||||
|
esp_err_t handler_reboot(httpd_req_t *req);
|
||||||
|
static bool ota_update_task(std::string fn);
|
||||||
|
|
||||||
|
std::string _file_name_update;
|
||||||
|
bool initial_setup = false;
|
||||||
|
|
||||||
|
|
||||||
|
static void infinite_loop(void)
|
||||||
|
{
|
||||||
|
int i = 0;
|
||||||
|
ESP_LOGI(TAG, "When a new firmware is available on the server, press the reset button to download it");
|
||||||
|
while(1) {
|
||||||
|
ESP_LOGI(TAG, "Waiting for a new firmware... %d", ++i);
|
||||||
|
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void task_do_Update_ZIP(void *pvParameter)
|
||||||
|
{
|
||||||
|
std::string filetype = toUpper(getFileType(_file_name_update));
|
||||||
|
|
||||||
|
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "File: " + _file_name_update + " Filetype: " + filetype);
|
||||||
|
|
||||||
|
|
||||||
|
if (filetype == "ZIP")
|
||||||
|
{
|
||||||
|
std::string in, out, outbin, zw, retfirmware;
|
||||||
|
|
||||||
|
out = "/sdcard/html";
|
||||||
|
outbin = "/sdcard/firmware";
|
||||||
|
|
||||||
|
retfirmware = unzip_new(_file_name_update, out+"/", outbin+"/", "/sdcard/", initial_setup);
|
||||||
|
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Files unzipped.");
|
||||||
|
|
||||||
|
if (retfirmware.length() > 0)
|
||||||
|
{
|
||||||
|
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Found firmware.bin");
|
||||||
|
ota_update_task(retfirmware);
|
||||||
|
}
|
||||||
|
|
||||||
|
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Trigger reboot due to firmware update.");
|
||||||
|
doRebootOTA();
|
||||||
|
} else if (filetype == "BIN")
|
||||||
|
{
|
||||||
|
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Do firmware update - file: " + _file_name_update);
|
||||||
|
ota_update_task(_file_name_update);
|
||||||
|
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Trigger reboot due to firmware update.");
|
||||||
|
doRebootOTA();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Only ZIP-Files support for update during startup!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void CheckUpdate()
|
||||||
|
{
|
||||||
|
FILE *pfile;
|
||||||
|
if ((pfile = fopen("/sdcard/update.txt", "r")) == NULL)
|
||||||
|
{
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "No update triggered.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char zw[1024] = "";
|
||||||
|
fgets(zw, 1024, pfile);
|
||||||
|
_file_name_update = std::string(zw);
|
||||||
|
if (fgets(zw, 1024, pfile))
|
||||||
|
{
|
||||||
|
std::string _szw = std::string(zw);
|
||||||
|
if (_szw == "init")
|
||||||
|
{
|
||||||
|
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Inital Setup triggered.");
|
||||||
|
initial_setup = true; }
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(pfile);
|
||||||
|
DeleteFile("/sdcard/update.txt"); // Prevent Boot Loop!!!
|
||||||
|
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Update during boot triggered - Update File: " + _file_name_update);
|
||||||
|
|
||||||
|
|
||||||
|
xTaskCreate(&task_do_Update_ZIP, "task_do_Update_ZIP", configMINIMAL_STACK_SIZE * 35, NULL, tskIDLE_PRIORITY+1, NULL);
|
||||||
|
while(1) { // wait until reboot within task_do_Update_ZIP
|
||||||
|
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool ota_update_task(std::string fn)
|
||||||
|
{
|
||||||
|
esp_err_t err;
|
||||||
|
/* update handle : set by esp_ota_begin(), must be freed via esp_ota_end() */
|
||||||
|
esp_ota_handle_t update_handle = 0 ;
|
||||||
|
const esp_partition_t *update_partition = NULL;
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Starting OTA update");
|
||||||
|
|
||||||
|
const esp_partition_t *configured = esp_ota_get_boot_partition();
|
||||||
|
const esp_partition_t *running = esp_ota_get_running_partition();
|
||||||
|
|
||||||
|
if (configured != running) {
|
||||||
|
ESP_LOGW(TAG, "Configured OTA boot partition at offset 0x%08x, but running from offset 0x%08x",
|
||||||
|
configured->address, running->address);
|
||||||
|
ESP_LOGW(TAG, "(This can happen if either the OTA boot data or preferred boot image become somehow corrupted.)");
|
||||||
|
}
|
||||||
|
ESP_LOGI(TAG, "Running partition type %d subtype %d (offset 0x%08x)",
|
||||||
|
running->type, running->subtype, running->address);
|
||||||
|
|
||||||
|
|
||||||
|
update_partition = esp_ota_get_next_update_partition(NULL);
|
||||||
|
ESP_LOGI(TAG, "Writing to partition subtype %d at offset 0x%x",
|
||||||
|
update_partition->subtype, update_partition->address);
|
||||||
|
// assert(update_partition != NULL);
|
||||||
|
|
||||||
|
int binary_file_length = 0;
|
||||||
|
|
||||||
|
// deal with all receive packet
|
||||||
|
bool image_header_was_checked = false;
|
||||||
|
|
||||||
|
int data_read;
|
||||||
|
|
||||||
|
FILE* f = fopen(fn.c_str(), "rb"); // previously only "r
|
||||||
|
|
||||||
|
if (f == NULL) { // File does not exist
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
data_read = fread(ota_write_data, 1, SERVER_OTA_SCRATCH_BUFSIZE, f);
|
||||||
|
|
||||||
|
while (data_read > 0) {
|
||||||
|
if (data_read < 0) {
|
||||||
|
ESP_LOGE(TAG, "Error: SSL data read error");
|
||||||
|
return false;
|
||||||
|
} else if (data_read > 0) {
|
||||||
|
if (image_header_was_checked == false) {
|
||||||
|
esp_app_desc_t new_app_info;
|
||||||
|
if (data_read > sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t)) {
|
||||||
|
// check current version with downloading
|
||||||
|
memcpy(&new_app_info, &ota_write_data[sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t)], sizeof(esp_app_desc_t));
|
||||||
|
ESP_LOGI(TAG, "New firmware version: %s", new_app_info.version);
|
||||||
|
|
||||||
|
esp_app_desc_t running_app_info;
|
||||||
|
if (esp_ota_get_partition_description(running, &running_app_info) == ESP_OK) {
|
||||||
|
ESP_LOGI(TAG, "Running firmware version: %s", running_app_info.version);
|
||||||
|
}
|
||||||
|
|
||||||
|
const esp_partition_t* last_invalid_app = esp_ota_get_last_invalid_partition();
|
||||||
|
esp_app_desc_t invalid_app_info;
|
||||||
|
if (esp_ota_get_partition_description(last_invalid_app, &invalid_app_info) == ESP_OK) {
|
||||||
|
ESP_LOGI(TAG, "Last invalid firmware version: %s", invalid_app_info.version);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check current version with last invalid partition
|
||||||
|
if (last_invalid_app != NULL) {
|
||||||
|
if (memcmp(invalid_app_info.version, new_app_info.version, sizeof(new_app_info.version)) == 0) {
|
||||||
|
ESP_LOGW(TAG, "New version is the same as invalid version.");
|
||||||
|
ESP_LOGW(TAG, "Previously, there was an attempt to launch the firmware with %s version, but it failed.", invalid_app_info.version);
|
||||||
|
ESP_LOGW(TAG, "The firmware has been rolled back to the previous version.");
|
||||||
|
infinite_loop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
if (memcmp(new_app_info.version, running_app_info.version, sizeof(new_app_info.version)) == 0) {
|
||||||
|
ESP_LOGW(TAG, "Current running version is the same as a new. We will not continue the update.");
|
||||||
|
infinite_loop();
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
image_header_was_checked = true;
|
||||||
|
|
||||||
|
err = esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &update_handle);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "esp_ota_begin failed (%s)", esp_err_to_name(err));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ESP_LOGI(TAG, "esp_ota_begin succeeded");
|
||||||
|
} else {
|
||||||
|
ESP_LOGE(TAG, "received package is not fit len");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = esp_ota_write( update_handle, (const void *)ota_write_data, data_read);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
binary_file_length += data_read;
|
||||||
|
ESP_LOGD(TAG, "Written image length %d", binary_file_length);
|
||||||
|
} else if (data_read == 0) {
|
||||||
|
//
|
||||||
|
// * As esp_http_client_read never returns negative error code, we rely on
|
||||||
|
// * `errno` to check for underlying transport connectivity closure if any
|
||||||
|
//
|
||||||
|
if (errno == ECONNRESET || errno == ENOTCONN) {
|
||||||
|
ESP_LOGE(TAG, "Connection closed, errno = %d", errno);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data_read = fread(ota_write_data, 1, SERVER_OTA_SCRATCH_BUFSIZE, f);
|
||||||
|
}
|
||||||
|
fclose(f);
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Total Write binary data length: %d", binary_file_length);
|
||||||
|
|
||||||
|
err = esp_ota_end(update_handle);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
if (err == ESP_ERR_OTA_VALIDATE_FAILED) {
|
||||||
|
ESP_LOGE(TAG, "Image validation failed, image is corrupted");
|
||||||
|
}
|
||||||
|
ESP_LOGE(TAG, "esp_ota_end failed (%s)!", esp_err_to_name(err));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = esp_ota_set_boot_partition(update_partition);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "esp_ota_set_boot_partition failed (%s)!", esp_err_to_name(err));
|
||||||
|
|
||||||
|
}
|
||||||
|
// ESP_LOGI(TAG, "Prepare to restart system!");
|
||||||
|
// esp_restart();
|
||||||
|
|
||||||
|
return true ;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void print_sha256 (const uint8_t *image_hash, const char *label)
|
||||||
|
{
|
||||||
|
char hash_print[HASH_LEN * 2 + 1];
|
||||||
|
hash_print[HASH_LEN * 2] = 0;
|
||||||
|
for (int i = 0; i < HASH_LEN; ++i) {
|
||||||
|
sprintf(&hash_print[i * 2], "%02x", image_hash[i]);
|
||||||
|
}
|
||||||
|
ESP_LOGI(TAG, "%s: %s", label, hash_print);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool diagnostic(void)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void CheckOTAUpdate(void)
|
||||||
|
{
|
||||||
|
ESP_LOGI(TAG, "Start CheckOTAUpdateCheck...");
|
||||||
|
|
||||||
|
uint8_t sha_256[HASH_LEN] = { 0 };
|
||||||
|
esp_partition_t partition;
|
||||||
|
|
||||||
|
// get sha256 digest for the partition table
|
||||||
|
partition.address = ESP_PARTITION_TABLE_OFFSET;
|
||||||
|
partition.size = ESP_PARTITION_TABLE_MAX_LEN;
|
||||||
|
partition.type = ESP_PARTITION_TYPE_DATA;
|
||||||
|
esp_partition_get_sha256(&partition, sha_256);
|
||||||
|
print_sha256(sha_256, "SHA-256 for the partition table: ");
|
||||||
|
|
||||||
|
// get sha256 digest for bootloader
|
||||||
|
partition.address = ESP_BOOTLOADER_OFFSET;
|
||||||
|
partition.size = ESP_PARTITION_TABLE_OFFSET;
|
||||||
|
partition.type = ESP_PARTITION_TYPE_APP;
|
||||||
|
esp_partition_get_sha256(&partition, sha_256);
|
||||||
|
print_sha256(sha_256, "SHA-256 for bootloader: ");
|
||||||
|
|
||||||
|
// get sha256 digest for running partition
|
||||||
|
esp_partition_get_sha256(esp_ota_get_running_partition(), sha_256);
|
||||||
|
print_sha256(sha_256, "SHA-256 for current firmware: ");
|
||||||
|
|
||||||
|
const esp_partition_t *running = esp_ota_get_running_partition();
|
||||||
|
esp_ota_img_states_t ota_state;
|
||||||
|
esp_err_t res_stat_partition = esp_ota_get_state_partition(running, &ota_state);
|
||||||
|
switch (res_stat_partition)
|
||||||
|
{
|
||||||
|
case ESP_OK:
|
||||||
|
ESP_LOGD(TAG, "CheckOTAUpdate Partition: ESP_OK");
|
||||||
|
if (esp_ota_get_state_partition(running, &ota_state) == ESP_OK) {
|
||||||
|
if (ota_state == ESP_OTA_IMG_PENDING_VERIFY) {
|
||||||
|
// run diagnostic function ...
|
||||||
|
bool diagnostic_is_ok = diagnostic();
|
||||||
|
if (diagnostic_is_ok) {
|
||||||
|
ESP_LOGI(TAG, "Diagnostics completed successfully! Continuing execution...");
|
||||||
|
esp_ota_mark_app_valid_cancel_rollback();
|
||||||
|
} else {
|
||||||
|
ESP_LOGE(TAG, "Diagnostics failed! Start rollback to the previous version...");
|
||||||
|
esp_ota_mark_app_invalid_rollback_and_reboot();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ESP_ERR_INVALID_ARG:
|
||||||
|
ESP_LOGD(TAG, "CheckOTAUpdate Partition: ESP_ERR_INVALID_ARG");
|
||||||
|
break;
|
||||||
|
case ESP_ERR_NOT_SUPPORTED:
|
||||||
|
ESP_LOGD(TAG, "CheckOTAUpdate Partition: ESP_ERR_NOT_SUPPORTED");
|
||||||
|
break;
|
||||||
|
case ESP_ERR_NOT_FOUND:
|
||||||
|
ESP_LOGD(TAG, "CheckOTAUpdate Partition: ESP_ERR_NOT_FOUND");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (esp_ota_get_state_partition(running, &ota_state) == ESP_OK) {
|
||||||
|
if (ota_state == ESP_OTA_IMG_PENDING_VERIFY) {
|
||||||
|
// run diagnostic function ...
|
||||||
|
bool diagnostic_is_ok = diagnostic();
|
||||||
|
if (diagnostic_is_ok) {
|
||||||
|
ESP_LOGI(TAG, "Diagnostics completed successfully! Continuing execution...");
|
||||||
|
esp_ota_mark_app_valid_cancel_rollback();
|
||||||
|
} else {
|
||||||
|
ESP_LOGE(TAG, "Diagnostics failed! Start rollback to the previous version...");
|
||||||
|
esp_ota_mark_app_invalid_rollback_and_reboot();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
esp_err_t handler_ota_update(httpd_req_t *req)
|
||||||
|
{
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
LogFile.WriteHeapInfo("handler_ota_update - Start");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "handler_ota_update");
|
||||||
|
char _query[200];
|
||||||
|
char _filename[100];
|
||||||
|
char _valuechar[30];
|
||||||
|
std::string fn = "/sdcard/firmware/";
|
||||||
|
bool _file_del = false;
|
||||||
|
std::string _task = "";
|
||||||
|
|
||||||
|
if (httpd_req_get_url_query_str(req, _query, 200) == ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGD(TAG, "Query: %s", _query);
|
||||||
|
|
||||||
|
if (httpd_query_key_value(_query, "task", _valuechar, 30) == ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGD(TAG, "task is found: %s", _valuechar);
|
||||||
|
_task = std::string(_valuechar);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (httpd_query_key_value(_query, "file", _filename, 100) == ESP_OK)
|
||||||
|
{
|
||||||
|
fn.append(_filename);
|
||||||
|
ESP_LOGD(TAG, "File: %s", fn.c_str());
|
||||||
|
}
|
||||||
|
if (httpd_query_key_value(_query, "delete", _filename, 100) == ESP_OK)
|
||||||
|
{
|
||||||
|
fn.append(_filename);
|
||||||
|
_file_del = true;
|
||||||
|
ESP_LOGD(TAG, "Delete Default File: %s", fn.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_task.compare("emptyfirmwaredir") == 0)
|
||||||
|
{
|
||||||
|
ESP_LOGD(TAG, "Start empty directory /firmware");
|
||||||
|
delete_all_in_directory("/sdcard/firmware");
|
||||||
|
std::string zw = "firmware directory deleted - v2\n";
|
||||||
|
ESP_LOGD(TAG, "%s", zw.c_str());
|
||||||
|
printf("Ausgabe: %s\n", zw.c_str());
|
||||||
|
|
||||||
|
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
|
||||||
|
httpd_resp_send(req, zw.c_str(), strlen(zw.c_str()));
|
||||||
|
/* Respond with an empty chunk to signal HTTP response completion */
|
||||||
|
httpd_resp_send_chunk(req, NULL, 0);
|
||||||
|
|
||||||
|
ESP_LOGD(TAG, "Done empty directory /firmware");
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_task.compare("update") == 0)
|
||||||
|
{
|
||||||
|
std::string filetype = toUpper(getFileType(fn));
|
||||||
|
if (filetype.length() == 0)
|
||||||
|
{
|
||||||
|
std::string zw = "Update failed - no file specified (zip, bin, tfl, tlite)";
|
||||||
|
httpd_resp_sendstr_chunk(req, zw.c_str());
|
||||||
|
httpd_resp_sendstr_chunk(req, NULL);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if ((filetype == "TFLITE") || (filetype == "TFL"))
|
||||||
|
{
|
||||||
|
std::string out = "/sdcard/config/" + getFileFullFileName(fn);
|
||||||
|
DeleteFile(out);
|
||||||
|
CopyFile(fn, out);
|
||||||
|
DeleteFile(fn);
|
||||||
|
|
||||||
|
const char* resp_str = "Neural Network File copied.";
|
||||||
|
httpd_resp_sendstr_chunk(req, resp_str);
|
||||||
|
httpd_resp_sendstr_chunk(req, NULL);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if ((filetype == "ZIP") || (filetype == "BIN"))
|
||||||
|
{
|
||||||
|
FILE *pfile;
|
||||||
|
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Update for reboot.");
|
||||||
|
pfile = fopen("/sdcard/update.txt", "w");
|
||||||
|
fwrite(fn.c_str(), fn.length(), 1, pfile);
|
||||||
|
fclose(pfile);
|
||||||
|
|
||||||
|
std::string zw = "reboot\n";
|
||||||
|
httpd_resp_sendstr_chunk(req, zw.c_str());
|
||||||
|
httpd_resp_sendstr_chunk(req, NULL);
|
||||||
|
ESP_LOGD(TAG, "Send reboot");
|
||||||
|
return ESP_OK;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
if (filetype == "BIN")
|
||||||
|
{
|
||||||
|
const char* resp_str;
|
||||||
|
|
||||||
|
KillTFliteTasks();
|
||||||
|
gpio_handler_deinit();
|
||||||
|
if (ota_update_task(fn))
|
||||||
|
{
|
||||||
|
std::string zw = "reboot\n";
|
||||||
|
httpd_resp_sendstr_chunk(req, zw.c_str());
|
||||||
|
httpd_resp_sendstr_chunk(req, NULL);
|
||||||
|
ESP_LOGD(TAG, "Send reboot");
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
resp_str = "Error during Firmware Update!!!\nPlease check output of console.";
|
||||||
|
httpd_resp_send(req, resp_str, strlen(resp_str));
|
||||||
|
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
LogFile.WriteHeapInfo("handler_ota_update - Done");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
std::string zw = "Update failed - no valid file specified (zip, bin, tfl, tlite)!";
|
||||||
|
httpd_resp_sendstr_chunk(req, zw.c_str());
|
||||||
|
httpd_resp_sendstr_chunk(req, NULL);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (_task.compare("unziphtml") == 0)
|
||||||
|
{
|
||||||
|
ESP_LOGD(TAG, "Task unziphtml");
|
||||||
|
std::string in, out, zw;
|
||||||
|
|
||||||
|
in = "/sdcard/firmware/html.zip";
|
||||||
|
out = "/sdcard/html";
|
||||||
|
|
||||||
|
delete_all_in_directory(out);
|
||||||
|
|
||||||
|
unzip(in, out+"/");
|
||||||
|
zw = "Web Interface Update Successfull!\nNo reboot necessary";
|
||||||
|
httpd_resp_send(req, zw.c_str(), strlen(zw.c_str()));
|
||||||
|
httpd_resp_sendstr_chunk(req, NULL);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_file_del)
|
||||||
|
{
|
||||||
|
ESP_LOGD(TAG, "Delete !! _file_del: %s", fn.c_str());
|
||||||
|
struct stat file_stat;
|
||||||
|
int _result = stat(fn.c_str(), &file_stat);
|
||||||
|
ESP_LOGD(TAG, "Ergebnis %d\n", _result);
|
||||||
|
if (_result == 0) {
|
||||||
|
ESP_LOGD(TAG, "Deleting file: %s", fn.c_str());
|
||||||
|
/* Delete file */
|
||||||
|
unlink(fn.c_str());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ESP_LOGD(TAG, "File does not exist: %s", fn.c_str());
|
||||||
|
}
|
||||||
|
/* Respond with an empty chunk to signal HTTP response completion */
|
||||||
|
std::string zw = "file deleted\n";
|
||||||
|
ESP_LOGD(TAG, "%s", zw.c_str());
|
||||||
|
httpd_resp_send(req, zw.c_str(), strlen(zw.c_str()));
|
||||||
|
httpd_resp_send_chunk(req, NULL, 0);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
string zw = "ota without parameter - should not be the case!";
|
||||||
|
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
|
||||||
|
httpd_resp_send(req, zw.c_str(), strlen(zw.c_str()));
|
||||||
|
httpd_resp_send_chunk(req, NULL, 0);
|
||||||
|
|
||||||
|
ESP_LOGE(TAG, "ota without parameter - should not be the case!");
|
||||||
|
|
||||||
|
/*
|
||||||
|
const char* resp_str;
|
||||||
|
|
||||||
|
KillTFliteTasks();
|
||||||
|
gpio_handler_deinit();
|
||||||
|
if (ota_update_task(fn))
|
||||||
|
{
|
||||||
|
resp_str = "Firmware Update Successfull! You can restart now.";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
resp_str = "Error during Firmware Update!!! Please check console output.";
|
||||||
|
}
|
||||||
|
|
||||||
|
httpd_resp_send(req, resp_str, strlen(resp_str));
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
LogFile.WriteHeapInfo("handler_ota_update - Done");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void hard_restart()
|
||||||
|
{
|
||||||
|
esp_task_wdt_init(1,true);
|
||||||
|
esp_task_wdt_add(NULL);
|
||||||
|
while(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void task_reboot(void *KillAutoFlow)
|
||||||
|
{
|
||||||
|
// write a reboot, to identify a reboot by purpouse
|
||||||
|
FILE* pfile = fopen("/sdcard/reboot.txt", "w");
|
||||||
|
std::string _s_zw= "reboot";
|
||||||
|
fwrite(_s_zw.c_str(), strlen(_s_zw.c_str()), 1, pfile);
|
||||||
|
fclose(pfile);
|
||||||
|
|
||||||
|
vTaskDelay(3000 / portTICK_PERIOD_MS);
|
||||||
|
|
||||||
|
if ((bool)KillAutoFlow) {
|
||||||
|
KillTFliteTasks(); // Kill autoflow task if executed in extra task, if not don't kill parent task
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stop service tasks */
|
||||||
|
#ifdef ENABLE_MQTT
|
||||||
|
MQTTdestroy_client(true);
|
||||||
|
#endif //ENABLE_MQTT
|
||||||
|
gpio_handler_destroy();
|
||||||
|
esp_camera_deinit();
|
||||||
|
WIFIDestroy();
|
||||||
|
|
||||||
|
vTaskDelay(3000 / portTICK_PERIOD_MS);
|
||||||
|
esp_restart(); // Reset type: CPU reset (Reset both CPUs)
|
||||||
|
|
||||||
|
vTaskDelay(5000 / portTICK_PERIOD_MS);
|
||||||
|
hard_restart(); // Reset type: System reset (Triggered by watchdog), if esp_restart stalls (WDT needs to be activated)
|
||||||
|
|
||||||
|
ESP_LOGE(TAG, "Reboot failed!");
|
||||||
|
vTaskDelete(NULL); //Delete this task if it comes to this point
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void doReboot()
|
||||||
|
{
|
||||||
|
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Reboot triggered by Software (5s).");
|
||||||
|
LogFile.WriteToFile(ESP_LOG_WARN, TAG, "Reboot in 5sec");
|
||||||
|
|
||||||
|
BaseType_t xReturned = xTaskCreate(&task_reboot, "task_reboot", configMINIMAL_STACK_SIZE * 3, (void*) true, 10, NULL);
|
||||||
|
if( xReturned != pdPASS )
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "task_reboot not created -> force reboot without killing flow");
|
||||||
|
task_reboot((void*) false);
|
||||||
|
}
|
||||||
|
vTaskDelay(10000 / portTICK_PERIOD_MS); // Prevent serving web client fetch response until system is shuting down
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void doRebootOTA()
|
||||||
|
{
|
||||||
|
LogFile.WriteToFile(ESP_LOG_WARN, TAG, "Reboot in 5sec");
|
||||||
|
|
||||||
|
esp_camera_deinit();
|
||||||
|
|
||||||
|
vTaskDelay(5000 / portTICK_PERIOD_MS);
|
||||||
|
esp_restart(); // Reset type: CPU reset (Reset both CPUs)
|
||||||
|
|
||||||
|
vTaskDelay(5000 / portTICK_PERIOD_MS);
|
||||||
|
hard_restart(); // Reset type: System reset (Triggered by watchdog), if esp_restart stalls (WDT needs to be activated)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
esp_err_t handler_reboot(httpd_req_t *req)
|
||||||
|
{
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
LogFile.WriteHeapInfo("handler_reboot - Start");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "handler_reboot");
|
||||||
|
ESP_LOGI(TAG, "!!! System will restart within 5 sec!!!");
|
||||||
|
|
||||||
|
std::string response =
|
||||||
|
"<html><head><script>"
|
||||||
|
"function m(h) {"
|
||||||
|
"document.getElementById('t').innerHTML=h;"
|
||||||
|
"setInterval(function (){h +='.'; document.getElementById('t').innerHTML=h;"
|
||||||
|
"fetch('reboot_page.html',{mode: 'no-cors'}).then(r=>{parent.location.href=('index.html');})}, 1000);"
|
||||||
|
"}</script></head></html><body style='font-family: arial'><h3 id=t></h3>"
|
||||||
|
"<script>m('Rebooting!<br>The page will automatically reload in around 25..60s.<br><br>');</script>"
|
||||||
|
"</body></html>";
|
||||||
|
|
||||||
|
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
|
||||||
|
httpd_resp_send(req, response.c_str(), strlen(response.c_str()));
|
||||||
|
|
||||||
|
doReboot();
|
||||||
|
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
LogFile.WriteHeapInfo("handler_reboot - Done");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void register_server_ota_sdcard_uri(httpd_handle_t server)
|
||||||
|
{
|
||||||
|
ESP_LOGI(TAG, "Registering URI handlers");
|
||||||
|
|
||||||
|
httpd_uri_t camuri = { };
|
||||||
|
camuri.method = HTTP_GET;
|
||||||
|
camuri.uri = "/ota";
|
||||||
|
camuri.handler = handler_ota_update;
|
||||||
|
camuri.user_ctx = (void*) "Do OTA";
|
||||||
|
httpd_register_uri_handler(server, &camuri);
|
||||||
|
|
||||||
|
camuri.method = HTTP_GET;
|
||||||
|
camuri.uri = "/reboot";
|
||||||
|
camuri.handler = handler_reboot;
|
||||||
|
camuri.user_ctx = (void*) "Reboot";
|
||||||
|
httpd_register_uri_handler(server, &camuri);
|
||||||
|
|
||||||
|
}
|
||||||
20
code/components/jomjol_fileserver_ota/server_ota.h
Normal file
20
code/components/jomjol_fileserver_ota/server_ota.h
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef SERVEROTA_H
|
||||||
|
#define SERVEROTA_H
|
||||||
|
|
||||||
|
#include <esp_log.h>
|
||||||
|
|
||||||
|
#include <esp_http_server.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
|
||||||
|
void register_server_ota_sdcard_uri(httpd_handle_t server);
|
||||||
|
void CheckOTAUpdate();
|
||||||
|
void doReboot();
|
||||||
|
void doRebootOTA();
|
||||||
|
void hard_restart();
|
||||||
|
void CheckUpdate();
|
||||||
|
|
||||||
|
#endif //SERVEROTA_H
|
||||||
7
code/components/jomjol_flowcontroll/CMakeLists.txt
Normal file
7
code/components/jomjol_flowcontroll/CMakeLists.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
FILE(GLOB_RECURSE app_sources ${CMAKE_CURRENT_SOURCE_DIR}/*.*)
|
||||||
|
|
||||||
|
idf_component_register(SRCS ${app_sources}
|
||||||
|
INCLUDE_DIRS "."
|
||||||
|
REQUIRES jomjol_tfliteclass jomjol_helper jomjol_controlcamera jomjol_mqtt jomjol_influxdb jomjol_fileserver_ota jomjol_image_proc jomjol_wlan)
|
||||||
|
|
||||||
|
|
||||||
119
code/components/jomjol_flowcontroll/ClassFlow.cpp
Normal file
119
code/components/jomjol_flowcontroll/ClassFlow.cpp
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
#include "ClassFlow.h"
|
||||||
|
#include <fstream>
|
||||||
|
#include <string>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string.h>
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "../../include/defines.h"
|
||||||
|
|
||||||
|
static const char *TAG = "CLASS";
|
||||||
|
|
||||||
|
|
||||||
|
void ClassFlow::SetInitialParameter(void)
|
||||||
|
{
|
||||||
|
ListFlowControll = NULL;
|
||||||
|
previousElement = NULL;
|
||||||
|
disabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ClassFlow::isNewParagraph(string input)
|
||||||
|
{
|
||||||
|
if ((input[0] == '[') || ((input[0] == ';') && (input[1] == '[')))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ClassFlow::GetNextParagraph(FILE* pfile, string& aktparamgraph)
|
||||||
|
{
|
||||||
|
while (getNextLine(pfile, &aktparamgraph) && !isNewParagraph(aktparamgraph));
|
||||||
|
|
||||||
|
if (isNewParagraph(aktparamgraph))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ClassFlow::ClassFlow(void)
|
||||||
|
{
|
||||||
|
SetInitialParameter();
|
||||||
|
}
|
||||||
|
|
||||||
|
ClassFlow::ClassFlow(std::vector<ClassFlow*> * lfc)
|
||||||
|
{
|
||||||
|
SetInitialParameter();
|
||||||
|
ListFlowControll = lfc;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClassFlow::ClassFlow(std::vector<ClassFlow*> * lfc, ClassFlow *_prev)
|
||||||
|
{
|
||||||
|
SetInitialParameter();
|
||||||
|
ListFlowControll = lfc;
|
||||||
|
previousElement = _prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ClassFlow::ReadParameter(FILE* pfile, string &aktparamgraph)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ClassFlow::doFlow(string time)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
string ClassFlow::getHTMLSingleStep(string host){
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
string ClassFlow::getReadout()
|
||||||
|
{
|
||||||
|
return string();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ClassFlow::GetParameterName(std::string _input)
|
||||||
|
{
|
||||||
|
string _param;
|
||||||
|
int _pospunkt = _input.find_first_of(".");
|
||||||
|
if (_pospunkt > -1)
|
||||||
|
{
|
||||||
|
_param = _input.substr(_pospunkt+1, _input.length() - _pospunkt - 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_param = _input;
|
||||||
|
}
|
||||||
|
// ESP_LOGD(TAG, "Parameter: %s, Pospunkt: %d", _param.c_str(), _pospunkt);
|
||||||
|
return _param;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool ClassFlow::getNextLine(FILE* pfile, string *rt)
|
||||||
|
{
|
||||||
|
char zw[1024];
|
||||||
|
if (pfile == NULL)
|
||||||
|
{
|
||||||
|
*rt = "";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!fgets(zw, 1024, pfile))
|
||||||
|
{
|
||||||
|
*rt = "";
|
||||||
|
ESP_LOGD(TAG, "END OF FILE");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ESP_LOGD(TAG, "%s", zw);
|
||||||
|
*rt = zw;
|
||||||
|
*rt = trim(*rt);
|
||||||
|
while ((zw[0] == ';' || zw[0] == '#' || (rt->size() == 0)) && !(zw[1] == '['))
|
||||||
|
{
|
||||||
|
*rt = "";
|
||||||
|
if (!fgets(zw, 1024, pfile))
|
||||||
|
return false;
|
||||||
|
ESP_LOGD(TAG, "%s", zw);
|
||||||
|
*rt = zw;
|
||||||
|
*rt = trim(*rt);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@@ -1,36 +1,48 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef CLASSFLOW_H
|
||||||
|
#define CLASSFLOW_H
|
||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "Helper.h"
|
#include "Helper.h"
|
||||||
#include "CFindTemplate.h"
|
#include "CImageBasis.h"
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
struct HTMLInfo
|
struct HTMLInfo
|
||||||
{
|
{
|
||||||
float val;
|
float val;
|
||||||
|
CImageBasis *image = NULL;
|
||||||
|
CImageBasis *image_org = NULL;
|
||||||
std::string filename;
|
std::string filename;
|
||||||
|
std::string filename_org;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
class ClassFlow
|
class ClassFlow
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
std::vector<string> ZerlegeZeile(string input);
|
|
||||||
bool isNewParagraph(string input);
|
bool isNewParagraph(string input);
|
||||||
bool GetNextParagraph(FILE* pfile, string& aktparamgraph);
|
bool GetNextParagraph(FILE* pfile, string& aktparamgraph);
|
||||||
bool getNextLine(FILE* pfile, string* rt);
|
bool getNextLine(FILE* pfile, string* rt);
|
||||||
|
|
||||||
std::vector<ClassFlow*>* ListFlowControll;
|
std::vector<ClassFlow*>* ListFlowControll;
|
||||||
|
ClassFlow *previousElement;
|
||||||
|
|
||||||
virtual void SetInitialParameter(void);
|
virtual void SetInitialParameter(void);
|
||||||
|
|
||||||
|
std::string GetParameterName(std::string _input);
|
||||||
|
|
||||||
|
bool disabled;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ClassFlow(void);
|
ClassFlow(void);
|
||||||
ClassFlow(std::vector<ClassFlow*> * lfc);
|
ClassFlow(std::vector<ClassFlow*> * lfc);
|
||||||
|
ClassFlow(std::vector<ClassFlow*> * lfc, ClassFlow *_prev);
|
||||||
|
|
||||||
virtual bool ReadParameter(FILE* pfile, string &aktparamgraph);
|
virtual bool ReadParameter(FILE* pfile, string &aktparamgraph);
|
||||||
virtual bool doFlow(string time);
|
virtual bool doFlow(string time);
|
||||||
virtual string getHTMLSingleStep(string host);
|
virtual string getHTMLSingleStep(string host);
|
||||||
@@ -39,3 +51,4 @@ public:
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#endif //CLASSFLOW_H
|
||||||
376
code/components/jomjol_flowcontroll/ClassFlowAlignment.cpp
Normal file
376
code/components/jomjol_flowcontroll/ClassFlowAlignment.cpp
Normal file
@@ -0,0 +1,376 @@
|
|||||||
|
#include "ClassFlowAlignment.h"
|
||||||
|
#include "ClassFlowMakeImage.h"
|
||||||
|
#include "ClassFlow.h"
|
||||||
|
#include "server_tflite.h"
|
||||||
|
|
||||||
|
#include "CRotateImage.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
|
||||||
|
|
||||||
|
#include "ClassLogFile.h"
|
||||||
|
#include "../../include/defines.h"
|
||||||
|
|
||||||
|
|
||||||
|
static const char *TAG = "ALIGN";
|
||||||
|
|
||||||
|
// #define DEBUG_DETAIL_ON
|
||||||
|
|
||||||
|
|
||||||
|
void ClassFlowAlignment::SetInitialParameter(void)
|
||||||
|
{
|
||||||
|
initalrotate = 0;
|
||||||
|
anz_ref = 0;
|
||||||
|
initialmirror = false;
|
||||||
|
use_antialiasing = false;
|
||||||
|
initialflip = false;
|
||||||
|
SaveAllFiles = false;
|
||||||
|
namerawimage = "/sdcard/img_tmp/raw.jpg";
|
||||||
|
FileStoreRefAlignment = "/sdcard/config/align.txt";
|
||||||
|
ListFlowControll = NULL;
|
||||||
|
AlignAndCutImage = NULL;
|
||||||
|
ImageBasis = NULL;
|
||||||
|
ImageTMP = NULL;
|
||||||
|
#ifdef ALGROI_LOAD_FROM_MEM_AS_JPG
|
||||||
|
AlgROI = (ImageData*)heap_caps_malloc(sizeof(ImageData), MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM);
|
||||||
|
#endif
|
||||||
|
previousElement = NULL;
|
||||||
|
disabled = false;
|
||||||
|
SAD_criteria = 0.05;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ClassFlowAlignment::ClassFlowAlignment(std::vector<ClassFlow*>* lfc)
|
||||||
|
{
|
||||||
|
SetInitialParameter();
|
||||||
|
ListFlowControll = lfc;
|
||||||
|
|
||||||
|
for (int i = 0; i < ListFlowControll->size(); ++i)
|
||||||
|
{
|
||||||
|
if (((*ListFlowControll)[i])->name().compare("ClassFlowMakeImage") == 0)
|
||||||
|
{
|
||||||
|
ImageBasis = ((ClassFlowMakeImage*) (*ListFlowControll)[i])->rawImage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ImageBasis) // the function take pictures does not exist --> must be created first ONLY FOR TEST PURPOSES
|
||||||
|
{
|
||||||
|
ESP_LOGD(TAG, "CImageBasis had to be created");
|
||||||
|
ImageBasis = new CImageBasis(namerawimage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool ClassFlowAlignment::ReadParameter(FILE* pfile, string& aktparamgraph)
|
||||||
|
{
|
||||||
|
std::vector<string> splitted;
|
||||||
|
int suchex = 40;
|
||||||
|
int suchey = 40;
|
||||||
|
int alg_algo = 0;
|
||||||
|
|
||||||
|
|
||||||
|
aktparamgraph = trim(aktparamgraph);
|
||||||
|
|
||||||
|
if (aktparamgraph.size() == 0)
|
||||||
|
if (!this->GetNextParagraph(pfile, aktparamgraph))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (aktparamgraph.compare("[Alignment]") != 0) //Paragraph does not fit MakeImage
|
||||||
|
return false;
|
||||||
|
|
||||||
|
while (this->getNextLine(pfile, &aktparamgraph) && !this->isNewParagraph(aktparamgraph))
|
||||||
|
{
|
||||||
|
splitted = ZerlegeZeile(aktparamgraph);
|
||||||
|
if ((toUpper(splitted[0]) == "FLIPIMAGESIZE") && (splitted.size() > 1))
|
||||||
|
{
|
||||||
|
if (toUpper(splitted[1]) == "TRUE")
|
||||||
|
initialflip = true;
|
||||||
|
}
|
||||||
|
if ((toUpper(splitted[0]) == "INITIALMIRROR") && (splitted.size() > 1))
|
||||||
|
{
|
||||||
|
if (toUpper(splitted[1]) == "TRUE")
|
||||||
|
initialmirror = true;
|
||||||
|
}
|
||||||
|
if (((toUpper(splitted[0]) == "INITALROTATE") || (toUpper(splitted[0]) == "INITIALROTATE")) && (splitted.size() > 1))
|
||||||
|
{
|
||||||
|
this->initalrotate = std::stod(splitted[1]);
|
||||||
|
}
|
||||||
|
if ((toUpper(splitted[0]) == "SEARCHFIELDX") && (splitted.size() > 1))
|
||||||
|
{
|
||||||
|
suchex = std::stod(splitted[1]);
|
||||||
|
}
|
||||||
|
if ((toUpper(splitted[0]) == "SEARCHFIELDY") && (splitted.size() > 1))
|
||||||
|
{
|
||||||
|
suchey = std::stod(splitted[1]);
|
||||||
|
}
|
||||||
|
if ((toUpper(splitted[0]) == "ANTIALIASING") && (splitted.size() > 1))
|
||||||
|
{
|
||||||
|
if (toUpper(splitted[1]) == "TRUE")
|
||||||
|
use_antialiasing = true;
|
||||||
|
}
|
||||||
|
if ((splitted.size() == 3) && (anz_ref < 2))
|
||||||
|
{
|
||||||
|
References[anz_ref].image_file = FormatFileName("/sdcard" + splitted[0]);
|
||||||
|
References[anz_ref].target_x = std::stod(splitted[1]);
|
||||||
|
References[anz_ref].target_y = std::stod(splitted[2]);
|
||||||
|
anz_ref++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((toUpper(splitted[0]) == "SAVEALLFILES") && (splitted.size() > 1))
|
||||||
|
{
|
||||||
|
if (toUpper(splitted[1]) == "TRUE")
|
||||||
|
SaveAllFiles = true;
|
||||||
|
}
|
||||||
|
if ((toUpper(splitted[0]) == "ALIGNMENTALGO") && (splitted.size() > 1))
|
||||||
|
{
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
std::string zw2 = "Alignment mode selected: " + splitted[1];
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, zw2);
|
||||||
|
#endif
|
||||||
|
if (toUpper(splitted[1]) == "HIGHACCURACY")
|
||||||
|
alg_algo = 1;
|
||||||
|
if (toUpper(splitted[1]) == "FAST")
|
||||||
|
alg_algo = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < anz_ref; ++i)
|
||||||
|
{
|
||||||
|
References[i].search_x = suchex;
|
||||||
|
References[i].search_y = suchey;
|
||||||
|
References[i].fastalg_SAD_criteria = SAD_criteria;
|
||||||
|
References[i].alignment_algo = alg_algo;
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
std::string zw2 = "Alignment mode written: " + std::to_string(alg_algo);
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, zw2);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
LoadReferenceAlignmentValues();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
string ClassFlowAlignment::getHTMLSingleStep(string host)
|
||||||
|
{
|
||||||
|
string result;
|
||||||
|
|
||||||
|
result = "<p>Rotated Image: </p> <p><img src=\"" + host + "/img_tmp/rot.jpg\"></p>\n";
|
||||||
|
result = result + "<p>Found Alignment: </p> <p><img src=\"" + host + "/img_tmp/rot_roi.jpg\"></p>\n";
|
||||||
|
result = result + "<p>Aligned Image: </p> <p><img src=\"" + host + "/img_tmp/alg.jpg\"></p>\n";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool ClassFlowAlignment::doFlow(string time)
|
||||||
|
{
|
||||||
|
#ifdef ALGROI_LOAD_FROM_MEM_AS_JPG
|
||||||
|
if (!AlgROI) // AlgROI needs to be allocated before ImageTMP to avoid heap fragmentation
|
||||||
|
{
|
||||||
|
AlgROI = (ImageData*)heap_caps_realloc(AlgROI, sizeof(ImageData), MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM);
|
||||||
|
if (!AlgROI)
|
||||||
|
{
|
||||||
|
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Can't allocate AlgROI");
|
||||||
|
LogFile.WriteHeapInfo("ClassFlowAlignment-doFlow");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AlgROI)
|
||||||
|
{
|
||||||
|
ImageBasis->writeToMemoryAsJPG((ImageData*)AlgROI, 90);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!ImageTMP)
|
||||||
|
{
|
||||||
|
ImageTMP = new CImageBasis(ImageBasis);
|
||||||
|
if (!ImageTMP)
|
||||||
|
{
|
||||||
|
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Can't allocate ImageTMP -> Exec this round aborted!");
|
||||||
|
LogFile.WriteHeapInfo("ClassFlowAlignment-doFlow");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delete AlignAndCutImage;
|
||||||
|
AlignAndCutImage = new CAlignAndCutImage(ImageBasis, ImageTMP);
|
||||||
|
if (!AlignAndCutImage)
|
||||||
|
{
|
||||||
|
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Can't allocate AlignAndCutImage -> Exec this round aborted!");
|
||||||
|
LogFile.WriteHeapInfo("ClassFlowAlignment-doFlow");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
CRotateImage rt(AlignAndCutImage, ImageTMP, initialflip);
|
||||||
|
if (initialflip)
|
||||||
|
{
|
||||||
|
int _zw = ImageBasis->height;
|
||||||
|
ImageBasis->height = ImageBasis->width;
|
||||||
|
ImageBasis->width = _zw;
|
||||||
|
|
||||||
|
_zw = ImageTMP->width;
|
||||||
|
ImageTMP->width = ImageTMP->height;
|
||||||
|
ImageTMP->height = _zw;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (initialmirror)
|
||||||
|
{
|
||||||
|
ESP_LOGD(TAG, "do mirror");
|
||||||
|
rt.Mirror();
|
||||||
|
|
||||||
|
if (SaveAllFiles)
|
||||||
|
AlignAndCutImage->SaveToFile(FormatFileName("/sdcard/img_tmp/mirror.jpg"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((initalrotate != 0) || initialflip)
|
||||||
|
{
|
||||||
|
if (use_antialiasing)
|
||||||
|
rt.RotateAntiAliasing(initalrotate);
|
||||||
|
else
|
||||||
|
rt.Rotate(initalrotate);
|
||||||
|
|
||||||
|
if (SaveAllFiles)
|
||||||
|
AlignAndCutImage->SaveToFile(FormatFileName("/sdcard/img_tmp/rot.jpg"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!AlignAndCutImage->Align(&References[0], &References[1]))
|
||||||
|
{
|
||||||
|
SaveReferenceAlignmentValues();
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef ALGROI_LOAD_FROM_MEM_AS_JPG
|
||||||
|
if (AlgROI) {
|
||||||
|
DrawRef(ImageTMP);
|
||||||
|
tfliteflow.DigitalDrawROI(ImageTMP);
|
||||||
|
tfliteflow.AnalogDrawROI(ImageTMP);
|
||||||
|
ImageTMP->writeToMemoryAsJPG((ImageData*)AlgROI, 90);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (SaveAllFiles)
|
||||||
|
{
|
||||||
|
AlignAndCutImage->SaveToFile(FormatFileName("/sdcard/img_tmp/alg.jpg"));
|
||||||
|
ImageTMP->SaveToFile(FormatFileName("/sdcard/img_tmp/alg_roi.jpg"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// must be deleted to have memory space for loading tflite
|
||||||
|
delete ImageTMP;
|
||||||
|
ImageTMP = NULL;
|
||||||
|
|
||||||
|
LoadReferenceAlignmentValues();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ClassFlowAlignment::SaveReferenceAlignmentValues()
|
||||||
|
{
|
||||||
|
FILE* pFile;
|
||||||
|
std::string zwtime, zwvalue;
|
||||||
|
|
||||||
|
pFile = fopen(FileStoreRefAlignment.c_str(), "w");
|
||||||
|
|
||||||
|
if (strlen(zwtime.c_str()) == 0)
|
||||||
|
{
|
||||||
|
time_t rawtime;
|
||||||
|
struct tm* timeinfo;
|
||||||
|
char buffer[80];
|
||||||
|
|
||||||
|
time(&rawtime);
|
||||||
|
timeinfo = localtime(&rawtime);
|
||||||
|
|
||||||
|
strftime(buffer, 80, "%Y-%m-%dT%H:%M:%S", timeinfo);
|
||||||
|
zwtime = std::string(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
fputs(zwtime.c_str(), pFile);
|
||||||
|
fputs("\n", pFile);
|
||||||
|
|
||||||
|
zwvalue = std::to_string(References[0].fastalg_x) + "\t" + std::to_string(References[0].fastalg_y);
|
||||||
|
zwvalue = zwvalue + "\t" +std::to_string(References[0].fastalg_SAD)+ "\t" +std::to_string(References[0].fastalg_min);
|
||||||
|
zwvalue = zwvalue + "\t" +std::to_string(References[0].fastalg_max)+ "\t" +std::to_string(References[0].fastalg_avg);
|
||||||
|
fputs(zwvalue.c_str(), pFile);
|
||||||
|
fputs("\n", pFile);
|
||||||
|
|
||||||
|
zwvalue = std::to_string(References[1].fastalg_x) + "\t" + std::to_string(References[1].fastalg_y);
|
||||||
|
zwvalue = zwvalue + "\t" +std::to_string(References[1].fastalg_SAD)+ "\t" +std::to_string(References[1].fastalg_min);
|
||||||
|
zwvalue = zwvalue + "\t" +std::to_string(References[1].fastalg_max)+ "\t" +std::to_string(References[1].fastalg_avg);
|
||||||
|
fputs(zwvalue.c_str(), pFile);
|
||||||
|
fputs("\n", pFile);
|
||||||
|
|
||||||
|
fclose(pFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool ClassFlowAlignment::LoadReferenceAlignmentValues(void)
|
||||||
|
{
|
||||||
|
FILE* pFile;
|
||||||
|
char zw[1024];
|
||||||
|
string zwvalue;
|
||||||
|
std::vector<string> splitted;
|
||||||
|
|
||||||
|
|
||||||
|
pFile = fopen(FileStoreRefAlignment.c_str(), "r");
|
||||||
|
if (pFile == NULL)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
fgets(zw, 1024, pFile);
|
||||||
|
ESP_LOGD(TAG, "%s", zw);
|
||||||
|
|
||||||
|
fgets(zw, 1024, pFile);
|
||||||
|
splitted = ZerlegeZeile(std::string(zw), " \t");
|
||||||
|
if (splitted.size() < 6)
|
||||||
|
{
|
||||||
|
fclose(pFile);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
References[0].fastalg_x = stoi(splitted[0]);
|
||||||
|
References[0].fastalg_y = stoi(splitted[1]);
|
||||||
|
References[0].fastalg_SAD = stof(splitted[2]);
|
||||||
|
References[0].fastalg_min = stoi(splitted[3]);
|
||||||
|
References[0].fastalg_max = stoi(splitted[4]);
|
||||||
|
References[0].fastalg_avg = stof(splitted[5]);
|
||||||
|
|
||||||
|
fgets(zw, 1024, pFile);
|
||||||
|
splitted = ZerlegeZeile(std::string(zw));
|
||||||
|
if (splitted.size() < 6)
|
||||||
|
{
|
||||||
|
fclose(pFile);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
References[1].fastalg_x = stoi(splitted[0]);
|
||||||
|
References[1].fastalg_y = stoi(splitted[1]);
|
||||||
|
References[1].fastalg_SAD = stof(splitted[2]);
|
||||||
|
References[1].fastalg_min = stoi(splitted[3]);
|
||||||
|
References[1].fastalg_max = stoi(splitted[4]);
|
||||||
|
References[1].fastalg_avg = stof(splitted[5]);
|
||||||
|
|
||||||
|
fclose(pFile);
|
||||||
|
|
||||||
|
|
||||||
|
/*#ifdef DEBUG_DETAIL_ON
|
||||||
|
std::string _zw = "\tLoadReferences[0]\tx,y:\t" + std::to_string(References[0].fastalg_x) + "\t" + std::to_string(References[0].fastalg_x);
|
||||||
|
_zw = _zw + "\tSAD, min, max, avg:\t" + std::to_string(References[0].fastalg_SAD) + "\t" + std::to_string(References[0].fastalg_min);
|
||||||
|
_zw = _zw + "\t" + std::to_string(References[0].fastalg_max) + "\t" + std::to_string(References[0].fastalg_avg);
|
||||||
|
LogFile.WriteToDedicatedFile("/sdcard/alignment.txt", _zw);
|
||||||
|
_zw = "\tLoadReferences[1]\tx,y:\t" + std::to_string(References[1].fastalg_x) + "\t" + std::to_string(References[1].fastalg_x);
|
||||||
|
_zw = _zw + "\tSAD, min, max, avg:\t" + std::to_string(References[1].fastalg_SAD) + "\t" + std::to_string(References[1].fastalg_min);
|
||||||
|
_zw = _zw + "\t" + std::to_string(References[1].fastalg_max) + "\t" + std::to_string(References[1].fastalg_avg);
|
||||||
|
LogFile.WriteToDedicatedFile("/sdcard/alignment.txt", _zw);
|
||||||
|
#endif*/
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ClassFlowAlignment::DrawRef(CImageBasis *_zw)
|
||||||
|
{
|
||||||
|
if (_zw->ImageOkay())
|
||||||
|
{
|
||||||
|
_zw->drawRect(References[0].target_x, References[0].target_y, References[0].width, References[0].height, 255, 0, 0, 2);
|
||||||
|
_zw->drawRect(References[1].target_x, References[1].target_y, References[1].width, References[1].height, 255, 0, 0, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
54
code/components/jomjol_flowcontroll/ClassFlowAlignment.h
Normal file
54
code/components/jomjol_flowcontroll/ClassFlowAlignment.h
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef CLASSFLOWALIGNMENT_H
|
||||||
|
#define CLASSFLOWALIGNMENT_H
|
||||||
|
|
||||||
|
#include "ClassFlow.h"
|
||||||
|
#include "Helper.h"
|
||||||
|
#include "CAlignAndCutImage.h"
|
||||||
|
#include "CFindTemplate.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
class ClassFlowAlignment :
|
||||||
|
public ClassFlow
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
float initalrotate;
|
||||||
|
bool initialmirror;
|
||||||
|
bool initialflip;
|
||||||
|
bool use_antialiasing;
|
||||||
|
RefInfo References[2];
|
||||||
|
int anz_ref;
|
||||||
|
string namerawimage;
|
||||||
|
bool SaveAllFiles;
|
||||||
|
CAlignAndCutImage *AlignAndCutImage;
|
||||||
|
std::string FileStoreRefAlignment;
|
||||||
|
float SAD_criteria;
|
||||||
|
|
||||||
|
void SetInitialParameter(void);
|
||||||
|
bool LoadReferenceAlignmentValues(void);
|
||||||
|
void SaveReferenceAlignmentValues();
|
||||||
|
|
||||||
|
public:
|
||||||
|
CImageBasis *ImageBasis, *ImageTMP;
|
||||||
|
#ifdef ALGROI_LOAD_FROM_MEM_AS_JPG
|
||||||
|
ImageData *AlgROI;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ClassFlowAlignment(std::vector<ClassFlow*>* lfc);
|
||||||
|
|
||||||
|
CAlignAndCutImage* GetAlignAndCutImage(){return AlignAndCutImage;};
|
||||||
|
|
||||||
|
void DrawRef(CImageBasis *_zw);
|
||||||
|
|
||||||
|
bool ReadParameter(FILE* pfile, string& aktparamgraph);
|
||||||
|
bool doFlow(string time);
|
||||||
|
string getHTMLSingleStep(string host);
|
||||||
|
string name(){return "ClassFlowAlignment";};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif //CLASSFLOWALIGNMENT_H
|
||||||
970
code/components/jomjol_flowcontroll/ClassFlowCNNGeneral.cpp
Normal file
970
code/components/jomjol_flowcontroll/ClassFlowCNNGeneral.cpp
Normal file
@@ -0,0 +1,970 @@
|
|||||||
|
#include "ClassFlowCNNGeneral.h"
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sstream> // std::stringstream
|
||||||
|
|
||||||
|
#include "CTfLiteClass.h"
|
||||||
|
#include "ClassLogFile.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "../../include/defines.h"
|
||||||
|
|
||||||
|
static const char* TAG = "CNN";
|
||||||
|
|
||||||
|
//#ifdef CONFIG_HEAP_TRACING_STANDALONE
|
||||||
|
#ifdef HEAP_TRACING_CLASS_FLOW_CNN_GENERAL_DO_ALING_AND_CUT
|
||||||
|
#include <esp_heap_trace.h>
|
||||||
|
#define NUM_RECORDS 300
|
||||||
|
static heap_trace_record_t trace_record[NUM_RECORDS]; // This buffer must be in internal RAM
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
ClassFlowCNNGeneral::ClassFlowCNNGeneral(ClassFlowAlignment *_flowalign, t_CNNType _cnntype) : ClassFlowImage(NULL, TAG)
|
||||||
|
{
|
||||||
|
string cnnmodelfile = "";
|
||||||
|
modelxsize = 1;
|
||||||
|
modelysize = 1;
|
||||||
|
CNNGoodThreshold = 0.0;
|
||||||
|
ListFlowControll = NULL;
|
||||||
|
previousElement = NULL;
|
||||||
|
SaveAllFiles = false;
|
||||||
|
disabled = false;
|
||||||
|
isLogImageSelect = false;
|
||||||
|
CNNType = AutoDetect;
|
||||||
|
CNNType = _cnntype;
|
||||||
|
flowpostalignment = _flowalign;
|
||||||
|
logfileRetentionInDays = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
string ClassFlowCNNGeneral::getReadout(int _analog = 0, bool _extendedResolution, int prev, float _before_narrow_Analog, float analogDigitalTransitionStart)
|
||||||
|
{
|
||||||
|
string result = "";
|
||||||
|
|
||||||
|
if (GENERAL[_analog]->ROI.size() == 0)
|
||||||
|
return result;
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "getReadout _analog=" + std::to_string(_analog) + ", _extendedResolution=" + std::to_string(_extendedResolution) + ", prev=" + std::to_string(prev));
|
||||||
|
|
||||||
|
if (CNNType == Analogue || CNNType == Analogue100)
|
||||||
|
{
|
||||||
|
float number = GENERAL[_analog]->ROI[GENERAL[_analog]->ROI.size() - 1]->result_float;
|
||||||
|
int result_after_decimal_point = ((int) floor(number * 10) + 10) % 10;
|
||||||
|
|
||||||
|
prev = PointerEvalAnalogNew(GENERAL[_analog]->ROI[GENERAL[_analog]->ROI.size() - 1]->result_float, prev);
|
||||||
|
// LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "getReadout(analog) number=" + std::to_string(number) + ", result_after_decimal_point=" + std::to_string(result_after_decimal_point) + ", prev=" + std::to_string(prev));
|
||||||
|
result = std::to_string(prev);
|
||||||
|
|
||||||
|
if (_extendedResolution && (CNNType != Digital))
|
||||||
|
result = result + std::to_string(result_after_decimal_point);
|
||||||
|
|
||||||
|
for (int i = GENERAL[_analog]->ROI.size() - 2; i >= 0; --i)
|
||||||
|
{
|
||||||
|
prev = PointerEvalAnalogNew(GENERAL[_analog]->ROI[i]->result_float, prev);
|
||||||
|
result = std::to_string(prev) + result;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CNNType == Digital)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < GENERAL[_analog]->ROI.size(); ++i)
|
||||||
|
{
|
||||||
|
if (GENERAL[_analog]->ROI[i]->result_klasse >= 10)
|
||||||
|
result = result + "N";
|
||||||
|
else
|
||||||
|
result = result + std::to_string(GENERAL[_analog]->ROI[i]->result_klasse);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((CNNType == DoubleHyprid10) || (CNNType == Digital100))
|
||||||
|
{
|
||||||
|
|
||||||
|
float number = GENERAL[_analog]->ROI[GENERAL[_analog]->ROI.size() - 1]->result_float;
|
||||||
|
if (number >= 0) // NaN?
|
||||||
|
{
|
||||||
|
if (_extendedResolution) // is only set if it is the first digit (no analogue before!)
|
||||||
|
{
|
||||||
|
int result_after_decimal_point = ((int) floor(number * 10)) % 10;
|
||||||
|
int result_before_decimal_point = ((int) floor(number)) % 10;
|
||||||
|
|
||||||
|
result = std::to_string(result_before_decimal_point) + std::to_string(result_after_decimal_point);
|
||||||
|
prev = result_before_decimal_point;
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "getReadout(dig100-ext) result_before_decimal_point=" + std::to_string(result_before_decimal_point) + ", result_after_decimal_point=" + std::to_string(result_after_decimal_point) + ", prev=" + std::to_string(prev));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (_before_narrow_Analog >= 0)
|
||||||
|
prev = PointerEvalHybridNew(GENERAL[_analog]->ROI[GENERAL[_analog]->ROI.size() - 1]->result_float, _before_narrow_Analog, prev, true, analogDigitalTransitionStart);
|
||||||
|
else
|
||||||
|
prev = PointerEvalHybridNew(GENERAL[_analog]->ROI[GENERAL[_analog]->ROI.size() - 1]->result_float, prev, prev);
|
||||||
|
result = std::to_string(prev);
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "getReadout(dig100) prev=" + std::to_string(prev));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = "N";
|
||||||
|
if (_extendedResolution && (CNNType != Digital))
|
||||||
|
result = "NN";
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = GENERAL[_analog]->ROI.size() - 2; i >= 0; --i)
|
||||||
|
{
|
||||||
|
if (GENERAL[_analog]->ROI[i]->result_float >= 0)
|
||||||
|
{
|
||||||
|
prev = PointerEvalHybridNew(GENERAL[_analog]->ROI[i]->result_float, GENERAL[_analog]->ROI[i+1]->result_float, prev);
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "getReadout#PointerEvalHybridNew()= " + std::to_string(prev));
|
||||||
|
result = std::to_string(prev) + result;
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "getReadout#result= " + result);
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
prev = -1;
|
||||||
|
result = "N" + result;
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "getReadout(result_float<0 /'N') result_float=" + std::to_string(GENERAL[_analog]->ROI[i]->result_float));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int ClassFlowCNNGeneral::PointerEvalHybridNew(float number, float number_of_predecessors, int eval_predecessors, bool Analog_Predecessors, float digitalAnalogTransitionStart)
|
||||||
|
{
|
||||||
|
int result;
|
||||||
|
int result_after_decimal_point = ((int) floor(number * 10)) % 10;
|
||||||
|
int result_before_decimal_point = ((int) floor(number) + 10) % 10;
|
||||||
|
|
||||||
|
if (eval_predecessors < 0)
|
||||||
|
{
|
||||||
|
if ((result_after_decimal_point <= Digital_Uncertainty * 10) || (result_after_decimal_point >= Digital_Uncertainty * 10)) // Band around the digit --> Rounding, as digit reaches inaccuracy in the frame
|
||||||
|
result = (int) (round(number) + 10) % 10;
|
||||||
|
else
|
||||||
|
result = (int) ((int) trunc(number) + 10) % 10;
|
||||||
|
|
||||||
|
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));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Analog_Predecessors)
|
||||||
|
{
|
||||||
|
result = PointerEvalAnalogToDigitNew(number, number_of_predecessors, eval_predecessors, digitalAnalogTransitionStart);
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "PointerEvalHybridNew - Analog predecessor, evaluation over PointerEvalAnalogNew = " + 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));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((number_of_predecessors >= Digital_Transition_Area_Predecessor ) && (number_of_predecessors <= (10.0 - Digital_Transition_Area_Predecessor)))
|
||||||
|
{
|
||||||
|
// no digit change, because predecessor is far enough away (0+/-DigitalTransitionRangePredecessor) --> number is rounded
|
||||||
|
if ((result_after_decimal_point <= DigitalBand) || (result_after_decimal_point >= (10-DigitalBand))) // Band around the digit --> Round off, as digit reaches inaccuracy in the frame
|
||||||
|
result = ((int) round(number) + 10) % 10;
|
||||||
|
else
|
||||||
|
result = ((int) trunc(number) + 10) % 10;
|
||||||
|
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "PointerEvalHybridNew - NO analogue predecessor, no change of digits, as pre-decimal point far enough away = " + 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));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eval_predecessors <= 1) // Zero crossing at the predecessor has taken place (! evaluation via Prev_value and not number!) --> round up here (2.8 --> 3, but also 3.1 --> 3)
|
||||||
|
{
|
||||||
|
// We simply assume that the current digit after the zero crossing of the predecessor
|
||||||
|
// has passed through at least half (x.5)
|
||||||
|
if (result_after_decimal_point > 5)
|
||||||
|
// The current digit does not yet have a zero crossing, but the predecessor does..
|
||||||
|
result = (result_before_decimal_point + 1) % 10;
|
||||||
|
else
|
||||||
|
// Act. digit and predecessor have zero crossing
|
||||||
|
result = result_before_decimal_point % 10;
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "PointerEvalHybridNew - NO analogue predecessor, zero crossing has taken placen = " + 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));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// remains only >= 9.x --> no zero crossing yet --> 2.8 --> 2,
|
||||||
|
// and from 9.7(DigitalTransitionRangeLead) 3.1 --> 2
|
||||||
|
// everything >=x.4 can be considered as current number in transition. With 9.x predecessor the current
|
||||||
|
// number can still be x.6 - x.7.
|
||||||
|
// Preceding (else - branch) does not already happen from 9.
|
||||||
|
if (Digital_Transition_Area_Forward>=number_of_predecessors || result_after_decimal_point >= 4)
|
||||||
|
// The current digit, like the previous digit, does not yet have a zero crossing.
|
||||||
|
result = result_before_decimal_point % 10;
|
||||||
|
else
|
||||||
|
// current digit precedes the smaller digit (9.x). So already >=x.0 while the previous digit has not yet
|
||||||
|
// has no zero crossing. Therefore, it is reduced by 1.
|
||||||
|
result = (result_before_decimal_point - 1 + 10) % 10;
|
||||||
|
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "PointerEvalHybridNew - O analogue predecessor, >= 9.5 --> no zero crossing yet = " + 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) + " result_after_decimal_point = " + std::to_string(result_after_decimal_point));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int ClassFlowCNNGeneral::PointerEvalAnalogToDigitNew(float number, float numeral_preceder, int eval_predecessors, float analogDigitalTransitionStart)
|
||||||
|
{
|
||||||
|
int result;
|
||||||
|
int result_after_decimal_point = ((int) floor(number * 10)) % 10;
|
||||||
|
int result_before_decimal_point = ((int) floor(number) + 10) % 10;
|
||||||
|
bool roundedUp = false;
|
||||||
|
|
||||||
|
// Within the digital inequalities
|
||||||
|
if ((result_after_decimal_point >= (10-Digital_Uncertainty * 10)) // Band around the digit --> Round off, as digit reaches inaccuracy in the frame
|
||||||
|
|| (eval_predecessors <= 4 && result_after_decimal_point>=6)) { // or digit runs after (analogue =0..4, digit >=6)
|
||||||
|
result = (int) (round(number) + 10) % 10;
|
||||||
|
roundedUp = true;
|
||||||
|
// before/ after decimal point, because we adjust the number based on the uncertainty.
|
||||||
|
result_after_decimal_point = ((int) floor(result * 10)) % 10;
|
||||||
|
result_before_decimal_point = ((int) floor(result) + 10) % 10;
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "PointerEvalAnalogToDigitNew - Digital Uncertainty - Result = " + std::to_string(result) +
|
||||||
|
" number: " + std::to_string(number) + " numeral_preceder: " + std::to_string(numeral_preceder) +
|
||||||
|
" erg before comma: " + std::to_string(result_before_decimal_point) +
|
||||||
|
" erg after comma: " + std::to_string(result_after_decimal_point));
|
||||||
|
} else {
|
||||||
|
result = (int) ((int) trunc(number) + 10) % 10;
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "PointerEvalAnalogToDigitNew - NO digital Uncertainty - Result = " + std::to_string(result) +
|
||||||
|
" number: " + std::to_string(number) + " numeral_preceder = " + std::to_string(numeral_preceder));
|
||||||
|
}
|
||||||
|
|
||||||
|
// No zero crossing has taken place.
|
||||||
|
// Only eval_predecessors used because numeral_preceder could be wrong here.
|
||||||
|
// numeral_preceder<=0.1 & eval_predecessors=9 corresponds to analogue was reset because of previous analogue that are not yet at 0.
|
||||||
|
if ((eval_predecessors>=6 && (numeral_preceder>analogDigitalTransitionStart || numeral_preceder<=0.2) && roundedUp))
|
||||||
|
{
|
||||||
|
result = ((result_before_decimal_point+10) - 1) % 10;
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "PointerEvalAnalogToDigitNew - Nulldurchgang noch nicht stattgefunden = " + std::to_string(result) +
|
||||||
|
" number: " + std::to_string(number) +
|
||||||
|
" numeral_preceder = " + std::to_string(numeral_preceder) +
|
||||||
|
" eerg after comma = " + std::to_string(result_after_decimal_point));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int ClassFlowCNNGeneral::PointerEvalAnalogNew(float number, int numeral_preceder)
|
||||||
|
{
|
||||||
|
float number_min, number_max;
|
||||||
|
int result;
|
||||||
|
|
||||||
|
if (numeral_preceder == -1)
|
||||||
|
{
|
||||||
|
result = (int) floor(number);
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "PointerEvalAnalogNew - No predecessor - Result = " + std::to_string(result) +
|
||||||
|
" number: " + std::to_string(number) + " numeral_preceder = " + std::to_string(numeral_preceder) + " Analog_error = " + std::to_string(Analog_error));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
number_min = number - Analog_error / 10.0;
|
||||||
|
number_max = number + Analog_error / 10.0;
|
||||||
|
|
||||||
|
if ((int) floor(number_max) - (int) floor(number_min) != 0)
|
||||||
|
{
|
||||||
|
if (numeral_preceder <= Analog_error)
|
||||||
|
{
|
||||||
|
result = ((int) floor(number_max) + 10) % 10;
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "PointerEvalAnalogNew - number ambiguous, correction upwards - result = " + std::to_string(result) +
|
||||||
|
" number: " + std::to_string(number) + " numeral_preceder = " + std::to_string(numeral_preceder) + " Analog_error = " + std::to_string(Analog_error));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (numeral_preceder >= 10 - Analog_error)
|
||||||
|
{
|
||||||
|
result = ((int) floor(number_min) + 10) % 10;
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "PointerEvalAnalogNew - number ambiguous, downward correction - result = " + std::to_string(result) +
|
||||||
|
" number: " + std::to_string(number) + " numeral_preceder = " + std::to_string(numeral_preceder) + " Analog_error = " + std::to_string(Analog_error));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
result = ((int) floor(number) + 10) % 10;
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "PointerEvalAnalogNew - number unambiguous, no correction necessary - result = " + std::to_string(result) +
|
||||||
|
" number: " + std::to_string(number) + " numeral_preceder = " + std::to_string(numeral_preceder) + " Analog_error = " + std::to_string(Analog_error));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool ClassFlowCNNGeneral::ReadParameter(FILE* pfile, string& aktparamgraph)
|
||||||
|
{
|
||||||
|
std::vector<string> splitted;
|
||||||
|
|
||||||
|
aktparamgraph = trim(aktparamgraph);
|
||||||
|
|
||||||
|
if (aktparamgraph.size() == 0)
|
||||||
|
if (!this->GetNextParagraph(pfile, aktparamgraph))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
|
||||||
|
if ((toUpper(aktparamgraph) != "[ANALOG]") && (toUpper(aktparamgraph) != ";[ANALOG]")
|
||||||
|
&& (toUpper(aktparamgraph) != "[DIGIT]") && (toUpper(aktparamgraph) != ";[DIGIT]")
|
||||||
|
&& (toUpper(aktparamgraph) != "[DIGITS]") && (toUpper(aktparamgraph) != ";[DIGITS]")
|
||||||
|
) // Paragraph passt nicht
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (aktparamgraph[0] == ';')
|
||||||
|
{
|
||||||
|
disabled = true;
|
||||||
|
while (getNextLine(pfile, &aktparamgraph) && !isNewParagraph(aktparamgraph));
|
||||||
|
ESP_LOGD(TAG, "[Analog/Digit] is disabled!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
while (this->getNextLine(pfile, &aktparamgraph) && !this->isNewParagraph(aktparamgraph))
|
||||||
|
{
|
||||||
|
splitted = ZerlegeZeile(aktparamgraph);
|
||||||
|
if ((toUpper(splitted[0]) == "LOGIMAGELOCATION") && (splitted.size() > 1))
|
||||||
|
{
|
||||||
|
this->LogImageLocation = "/sdcard" + splitted[1];
|
||||||
|
this->isLogImage = true;
|
||||||
|
}
|
||||||
|
if ((toUpper(splitted[0]) == "LOGIMAGESELECT") && (splitted.size() > 1))
|
||||||
|
{
|
||||||
|
LogImageSelect = splitted[1];
|
||||||
|
isLogImageSelect = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((toUpper(splitted[0]) == "LOGFILERETENTIONINDAYS") && (splitted.size() > 1))
|
||||||
|
{
|
||||||
|
this->logfileRetentionInDays = std::stoi(splitted[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((toUpper(splitted[0]) == "MODEL") && (splitted.size() > 1))
|
||||||
|
{
|
||||||
|
this->cnnmodelfile = splitted[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((toUpper(splitted[0]) == "CNNGOODTHRESHOLD") && (splitted.size() > 1))
|
||||||
|
{
|
||||||
|
CNNGoodThreshold = std::stof(splitted[1]);
|
||||||
|
}
|
||||||
|
if (splitted.size() >= 5)
|
||||||
|
{
|
||||||
|
general* _analog = GetGENERAL(splitted[0], true);
|
||||||
|
roi* neuroi = _analog->ROI[_analog->ROI.size()-1];
|
||||||
|
neuroi->posx = std::stoi(splitted[1]);
|
||||||
|
neuroi->posy = std::stoi(splitted[2]);
|
||||||
|
neuroi->deltax = std::stoi(splitted[3]);
|
||||||
|
neuroi->deltay = std::stoi(splitted[4]);
|
||||||
|
neuroi->CCW = false;
|
||||||
|
if (splitted.size() >= 6)
|
||||||
|
{
|
||||||
|
neuroi->CCW = toUpper(splitted[5]) == "TRUE";
|
||||||
|
}
|
||||||
|
neuroi->result_float = -1;
|
||||||
|
neuroi->image = NULL;
|
||||||
|
neuroi->image_org = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((toUpper(splitted[0]) == "SAVEALLFILES") && (splitted.size() > 1))
|
||||||
|
{
|
||||||
|
if (toUpper(splitted[1]) == "TRUE")
|
||||||
|
SaveAllFiles = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!getNetworkParameter())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
|
||||||
|
for (int _ana = 0; _ana < GENERAL.size(); ++_ana)
|
||||||
|
for (int i = 0; i < GENERAL[_ana]->ROI.size(); ++i)
|
||||||
|
{
|
||||||
|
GENERAL[_ana]->ROI[i]->image = new CImageBasis(modelxsize, modelysize, modelchannel);
|
||||||
|
GENERAL[_ana]->ROI[i]->image_org = new CImageBasis(GENERAL[_ana]->ROI[i]->deltax, GENERAL[_ana]->ROI[i]->deltay, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
general* ClassFlowCNNGeneral::FindGENERAL(string _name_number)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < GENERAL.size(); ++i)
|
||||||
|
if (GENERAL[i]->name == _name_number)
|
||||||
|
return GENERAL[i];
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
general* ClassFlowCNNGeneral::GetGENERAL(string _name, bool _create = true)
|
||||||
|
{
|
||||||
|
string _analog, _roi;
|
||||||
|
int _pospunkt = _name.find_first_of(".");
|
||||||
|
|
||||||
|
if (_pospunkt > -1)
|
||||||
|
{
|
||||||
|
_analog = _name.substr(0, _pospunkt);
|
||||||
|
_roi = _name.substr(_pospunkt+1, _name.length() - _pospunkt - 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_analog = "default";
|
||||||
|
_roi = _name;
|
||||||
|
}
|
||||||
|
|
||||||
|
general *_ret = NULL;
|
||||||
|
|
||||||
|
for (int i = 0; i < GENERAL.size(); ++i)
|
||||||
|
if (GENERAL[i]->name == _analog)
|
||||||
|
_ret = GENERAL[i];
|
||||||
|
|
||||||
|
if (!_create) // not found and should not be created
|
||||||
|
return _ret;
|
||||||
|
|
||||||
|
if (_ret == NULL)
|
||||||
|
{
|
||||||
|
_ret = new general;
|
||||||
|
_ret->name = _analog;
|
||||||
|
GENERAL.push_back(_ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
roi* neuroi = new roi;
|
||||||
|
neuroi->name = _roi;
|
||||||
|
|
||||||
|
_ret->ROI.push_back(neuroi);
|
||||||
|
|
||||||
|
ESP_LOGD(TAG, "GetGENERAL - GENERAL %s - roi %s - CCW: %d", _analog.c_str(), _roi.c_str(), neuroi->CCW);
|
||||||
|
|
||||||
|
return _ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
string ClassFlowCNNGeneral::getHTMLSingleStep(string host)
|
||||||
|
{
|
||||||
|
string result, zw;
|
||||||
|
std::vector<HTMLInfo*> htmlinfo;
|
||||||
|
|
||||||
|
result = "<p>Found ROIs: </p> <p><img src=\"" + host + "/img_tmp/alg_roi.jpg\"></p>\n";
|
||||||
|
result = result + "Analog Pointers: <p> ";
|
||||||
|
|
||||||
|
htmlinfo = GetHTMLInfo();
|
||||||
|
for (int i = 0; i < htmlinfo.size(); ++i)
|
||||||
|
{
|
||||||
|
std::stringstream stream;
|
||||||
|
stream << std::fixed << std::setprecision(1) << htmlinfo[i]->val;
|
||||||
|
zw = stream.str();
|
||||||
|
|
||||||
|
result = result + "<img src=\"" + host + "/img_tmp/" + htmlinfo[i]->filename + "\"> " + zw;
|
||||||
|
delete htmlinfo[i];
|
||||||
|
}
|
||||||
|
htmlinfo.clear();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool ClassFlowCNNGeneral::doFlow(string time)
|
||||||
|
{
|
||||||
|
|
||||||
|
#ifdef HEAP_TRACING_CLASS_FLOW_CNN_GENERAL_DO_ALING_AND_CUT
|
||||||
|
//register a buffer to record the memory trace
|
||||||
|
ESP_ERROR_CHECK( heap_trace_init_standalone(trace_record, NUM_RECORDS) );
|
||||||
|
// start tracing
|
||||||
|
ESP_ERROR_CHECK( heap_trace_start(HEAP_TRACE_LEAKS) );
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (disabled)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (!doAlignAndCut(time)){
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "doFlow after alignment");
|
||||||
|
|
||||||
|
doNeuralNetwork(time);
|
||||||
|
|
||||||
|
RemoveOldLogs();
|
||||||
|
|
||||||
|
#ifdef HEAP_TRACING_CLASS_FLOW_CNN_GENERAL_DO_ALING_AND_CUT
|
||||||
|
ESP_ERROR_CHECK( heap_trace_stop() );
|
||||||
|
heap_trace_dump();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool ClassFlowCNNGeneral::doAlignAndCut(string time)
|
||||||
|
{
|
||||||
|
if (disabled)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
CAlignAndCutImage *caic = flowpostalignment->GetAlignAndCutImage();
|
||||||
|
|
||||||
|
for (int _ana = 0; _ana < GENERAL.size(); ++_ana)
|
||||||
|
for (int i = 0; i < GENERAL[_ana]->ROI.size(); ++i)
|
||||||
|
{
|
||||||
|
ESP_LOGD(TAG, "General %d - Align&Cut", i);
|
||||||
|
|
||||||
|
caic->CutAndSave(GENERAL[_ana]->ROI[i]->posx, GENERAL[_ana]->ROI[i]->posy, GENERAL[_ana]->ROI[i]->deltax, GENERAL[_ana]->ROI[i]->deltay, GENERAL[_ana]->ROI[i]->image_org);
|
||||||
|
if (SaveAllFiles)
|
||||||
|
{
|
||||||
|
if (GENERAL[_ana]->name == "default")
|
||||||
|
GENERAL[_ana]->ROI[i]->image_org->SaveToFile(FormatFileName("/sdcard/img_tmp/" + GENERAL[_ana]->ROI[i]->name + ".jpg"));
|
||||||
|
else
|
||||||
|
GENERAL[_ana]->ROI[i]->image_org->SaveToFile(FormatFileName("/sdcard/img_tmp/" + GENERAL[_ana]->name + "_" + GENERAL[_ana]->ROI[i]->name + ".jpg"));
|
||||||
|
}
|
||||||
|
|
||||||
|
GENERAL[_ana]->ROI[i]->image_org->Resize(modelxsize, modelysize, GENERAL[_ana]->ROI[i]->image);
|
||||||
|
if (SaveAllFiles)
|
||||||
|
{
|
||||||
|
if (GENERAL[_ana]->name == "default")
|
||||||
|
GENERAL[_ana]->ROI[i]->image->SaveToFile(FormatFileName("/sdcard/img_tmp/" + GENERAL[_ana]->ROI[i]->name + ".jpg"));
|
||||||
|
else
|
||||||
|
GENERAL[_ana]->ROI[i]->image->SaveToFile(FormatFileName("/sdcard/img_tmp/" + GENERAL[_ana]->name + "_" + GENERAL[_ana]->ROI[i]->name + ".jpg"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ClassFlowCNNGeneral::DrawROI(CImageBasis *_zw)
|
||||||
|
{
|
||||||
|
if (_zw->ImageOkay())
|
||||||
|
{
|
||||||
|
if (CNNType == Analogue || CNNType == Analogue100)
|
||||||
|
{
|
||||||
|
int r = 0;
|
||||||
|
int g = 255;
|
||||||
|
int b = 0;
|
||||||
|
|
||||||
|
for (int _ana = 0; _ana < GENERAL.size(); ++_ana)
|
||||||
|
for (int i = 0; i < GENERAL[_ana]->ROI.size(); ++i)
|
||||||
|
{
|
||||||
|
_zw->drawRect(GENERAL[_ana]->ROI[i]->posx, GENERAL[_ana]->ROI[i]->posy, GENERAL[_ana]->ROI[i]->deltax, GENERAL[_ana]->ROI[i]->deltay, r, g, b, 1);
|
||||||
|
_zw->drawEllipse( (int) (GENERAL[_ana]->ROI[i]->posx + GENERAL[_ana]->ROI[i]->deltax/2), (int) (GENERAL[_ana]->ROI[i]->posy + GENERAL[_ana]->ROI[i]->deltay/2), (int) (GENERAL[_ana]->ROI[i]->deltax/2), (int) (GENERAL[_ana]->ROI[i]->deltay/2), r, g, b, 2);
|
||||||
|
_zw->drawLine((int) (GENERAL[_ana]->ROI[i]->posx + GENERAL[_ana]->ROI[i]->deltax/2), (int) GENERAL[_ana]->ROI[i]->posy, (int) (GENERAL[_ana]->ROI[i]->posx + GENERAL[_ana]->ROI[i]->deltax/2), (int) (GENERAL[_ana]->ROI[i]->posy + GENERAL[_ana]->ROI[i]->deltay), r, g, b, 2);
|
||||||
|
_zw->drawLine((int) GENERAL[_ana]->ROI[i]->posx, (int) (GENERAL[_ana]->ROI[i]->posy + GENERAL[_ana]->ROI[i]->deltay/2), (int) GENERAL[_ana]->ROI[i]->posx + GENERAL[_ana]->ROI[i]->deltax, (int) (GENERAL[_ana]->ROI[i]->posy + GENERAL[_ana]->ROI[i]->deltay/2), r, g, b, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (int _dig = 0; _dig < GENERAL.size(); ++_dig)
|
||||||
|
for (int i = 0; i < GENERAL[_dig]->ROI.size(); ++i)
|
||||||
|
_zw->drawRect(GENERAL[_dig]->ROI[i]->posx, GENERAL[_dig]->ROI[i]->posy, GENERAL[_dig]->ROI[i]->deltax, GENERAL[_dig]->ROI[i]->deltay, 0, 0, (255 - _dig*100), 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool ClassFlowCNNGeneral::getNetworkParameter()
|
||||||
|
{
|
||||||
|
if (disabled)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
CTfLiteClass *tflite = new CTfLiteClass;
|
||||||
|
string zwcnn = "/sdcard" + cnnmodelfile;
|
||||||
|
zwcnn = FormatFileName(zwcnn);
|
||||||
|
ESP_LOGD(TAG, "%s", zwcnn.c_str());
|
||||||
|
if (!tflite->LoadModel(zwcnn)) {
|
||||||
|
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Can't load tflite model " + cnnmodelfile + " -> Init aborted!");
|
||||||
|
LogFile.WriteHeapInfo("getNetworkParameter-LoadModel");
|
||||||
|
delete tflite;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tflite->MakeAllocate()) {
|
||||||
|
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Can't allocate tflite model -> Init aborted!");
|
||||||
|
LogFile.WriteHeapInfo("getNetworkParameter-MakeAllocate");
|
||||||
|
delete tflite;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CNNType == AutoDetect)
|
||||||
|
{
|
||||||
|
tflite->GetInputDimension(false);
|
||||||
|
modelxsize = tflite->ReadInputDimenstion(0);
|
||||||
|
modelysize = tflite->ReadInputDimenstion(1);
|
||||||
|
modelchannel = tflite->ReadInputDimenstion(2);
|
||||||
|
|
||||||
|
int _anzoutputdimensions = tflite->GetAnzOutPut();
|
||||||
|
switch (_anzoutputdimensions)
|
||||||
|
{
|
||||||
|
case 2:
|
||||||
|
CNNType = Analogue;
|
||||||
|
ESP_LOGD(TAG, "TFlite-Type set to Analogue");
|
||||||
|
break;
|
||||||
|
case 10:
|
||||||
|
CNNType = DoubleHyprid10;
|
||||||
|
ESP_LOGD(TAG, "TFlite-Type set to DoubleHyprid10");
|
||||||
|
break;
|
||||||
|
case 11:
|
||||||
|
CNNType = Digital;
|
||||||
|
ESP_LOGD(TAG, "TFlite-Type set to Digital");
|
||||||
|
break;
|
||||||
|
/* case 20:
|
||||||
|
CNNType = DigitalHyprid10;
|
||||||
|
ESP_LOGD(TAG, "TFlite-Type set to DigitalHyprid10");
|
||||||
|
break;
|
||||||
|
*/
|
||||||
|
// case 22:
|
||||||
|
// CNNType = DigitalHyprid;
|
||||||
|
// ESP_LOGD(TAG, "TFlite-Type set to DigitalHyprid");
|
||||||
|
// break;
|
||||||
|
case 100:
|
||||||
|
if (modelxsize==32 && modelysize == 32) {
|
||||||
|
CNNType = Analogue100;
|
||||||
|
ESP_LOGD(TAG, "TFlite-Type set to Analogue100");
|
||||||
|
} else {
|
||||||
|
CNNType = Digital100;
|
||||||
|
ESP_LOGD(TAG, "TFlite-Type set to Digital");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "tflite does not fit the firmware (outout_dimension=" + std::to_string(_anzoutputdimensions) + ")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delete tflite;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool ClassFlowCNNGeneral::doNeuralNetwork(string time)
|
||||||
|
{
|
||||||
|
if (disabled)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
string logPath = CreateLogFolder(time);
|
||||||
|
|
||||||
|
CTfLiteClass *tflite = new CTfLiteClass;
|
||||||
|
string zwcnn = "/sdcard" + cnnmodelfile;
|
||||||
|
zwcnn = FormatFileName(zwcnn);
|
||||||
|
ESP_LOGD(TAG, "%s", zwcnn.c_str());
|
||||||
|
|
||||||
|
if (!tflite->LoadModel(zwcnn)) {
|
||||||
|
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Can't load tflite model " + cnnmodelfile + " -> Exec aborted this round!");
|
||||||
|
LogFile.WriteHeapInfo("doNeuralNetwork-LoadModel");
|
||||||
|
delete tflite;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tflite->MakeAllocate()) {
|
||||||
|
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Can't allocate tfilte model -> Exec aborted this round!");
|
||||||
|
LogFile.WriteHeapInfo("doNeuralNetwork-MakeAllocate");
|
||||||
|
delete tflite;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int n = 0; n < GENERAL.size(); ++n) // For each NUMBER
|
||||||
|
{
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Processing Number '" + GENERAL[n]->name + "'");
|
||||||
|
for (int roi = 0; roi < GENERAL[n]->ROI.size(); ++roi) // For each ROI
|
||||||
|
{
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "ROI #" + std::to_string(roi) + " - TfLite");
|
||||||
|
//ESP_LOGD(TAG, "General %d - TfLite", i);
|
||||||
|
|
||||||
|
switch (CNNType) {
|
||||||
|
case Analogue:
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "CNN Type: Analogue");
|
||||||
|
{
|
||||||
|
float f1, f2;
|
||||||
|
f1 = 0; f2 = 0;
|
||||||
|
|
||||||
|
tflite->LoadInputImageBasis(GENERAL[n]->ROI[roi]->image);
|
||||||
|
tflite->Invoke();
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "After Invoke");
|
||||||
|
|
||||||
|
f1 = tflite->GetOutputValue(0);
|
||||||
|
f2 = tflite->GetOutputValue(1);
|
||||||
|
float result = fmod(atan2(f1, f2) / (M_PI * 2) + 2, 1);
|
||||||
|
|
||||||
|
if(GENERAL[n]->ROI[roi]->CCW)
|
||||||
|
GENERAL[n]->ROI[roi]->result_float = 10 - (result * 10);
|
||||||
|
else
|
||||||
|
GENERAL[n]->ROI[roi]->result_float = result * 10;
|
||||||
|
|
||||||
|
ESP_LOGD(TAG, "General result (Analog)%i - CCW: %d - %f", roi, GENERAL[n]->ROI[roi]->CCW, GENERAL[n]->ROI[roi]->result_float);
|
||||||
|
if (isLogImage)
|
||||||
|
LogImage(logPath, GENERAL[n]->ROI[roi]->name, &GENERAL[n]->ROI[roi]->result_float, NULL, time, GENERAL[n]->ROI[roi]->image_org);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case Digital:
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "CNN Type: Digital");
|
||||||
|
{
|
||||||
|
GENERAL[n]->ROI[roi]->result_klasse = 0;
|
||||||
|
GENERAL[n]->ROI[roi]->result_klasse = tflite->GetClassFromImageBasis(GENERAL[n]->ROI[roi]->image);
|
||||||
|
ESP_LOGD(TAG, "General result (Digit)%i: %d", roi, GENERAL[n]->ROI[roi]->result_klasse);
|
||||||
|
|
||||||
|
if (isLogImage)
|
||||||
|
{
|
||||||
|
string _imagename = GENERAL[n]->name + "_" + GENERAL[n]->ROI[roi]->name;
|
||||||
|
if (isLogImageSelect)
|
||||||
|
{
|
||||||
|
if (LogImageSelect.find(GENERAL[n]->ROI[roi]->name) != std::string::npos)
|
||||||
|
LogImage(logPath, _imagename, NULL, &GENERAL[n]->ROI[roi]->result_klasse, time, GENERAL[n]->ROI[roi]->image_org);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogImage(logPath, _imagename, NULL, &GENERAL[n]->ROI[roi]->result_klasse, time, GENERAL[n]->ROI[roi]->image_org);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
|
||||||
|
case DoubleHyprid10:
|
||||||
|
{
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "CNN Type: DoubleHyprid10");
|
||||||
|
int _num, _numplus, _numminus;
|
||||||
|
float _val, _valplus, _valminus;
|
||||||
|
float _fit;
|
||||||
|
float _result_save_file;
|
||||||
|
|
||||||
|
tflite->LoadInputImageBasis(GENERAL[n]->ROI[roi]->image);
|
||||||
|
tflite->Invoke();
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "After Invoke");
|
||||||
|
|
||||||
|
_num = tflite->GetOutClassification(0, 9);
|
||||||
|
_numplus = (_num + 1) % 10;
|
||||||
|
_numminus = (_num - 1 + 10) % 10;
|
||||||
|
|
||||||
|
_val = tflite->GetOutputValue(_num);
|
||||||
|
_valplus = tflite->GetOutputValue(_numplus);
|
||||||
|
_valminus = tflite->GetOutputValue(_numminus);
|
||||||
|
|
||||||
|
float result = _num;
|
||||||
|
|
||||||
|
if (_valplus > _valminus)
|
||||||
|
{
|
||||||
|
result = result + _valplus / (_valplus + _val);
|
||||||
|
_fit = _val + _valplus;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = result - _valminus / (_val + _valminus);
|
||||||
|
_fit = _val + _valminus;
|
||||||
|
|
||||||
|
}
|
||||||
|
if (result >= 10)
|
||||||
|
result = result - 10;
|
||||||
|
if (result < 0)
|
||||||
|
result = result + 10;
|
||||||
|
|
||||||
|
string zw = "_num (p, m): " + to_string(_num) + " " + to_string(_numplus) + " " + to_string(_numminus);
|
||||||
|
zw = zw + " _val (p, m): " + to_string(_val) + " " + to_string(_valplus) + " " + to_string(_valminus);
|
||||||
|
zw = zw + " result: " + to_string(result) + " _fit: " + to_string(_fit);
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, zw);
|
||||||
|
|
||||||
|
|
||||||
|
_result_save_file = result;
|
||||||
|
|
||||||
|
if (_fit < CNNGoodThreshold)
|
||||||
|
{
|
||||||
|
GENERAL[n]->ROI[roi]->isReject = true;
|
||||||
|
result = -1;
|
||||||
|
_result_save_file+= 100; // In case fit is not sufficient, the result should still be saved with "-10x.y".
|
||||||
|
string zw = "Value Rejected due to Threshold (Fit: " + to_string(_fit) + ", Threshold: " + to_string(CNNGoodThreshold) + ")";
|
||||||
|
LogFile.WriteToFile(ESP_LOG_WARN, TAG, zw);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GENERAL[n]->ROI[roi]->isReject = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
GENERAL[n]->ROI[roi]->result_float = result;
|
||||||
|
ESP_LOGD(TAG, "Result General(Analog)%i: %f", roi, GENERAL[n]->ROI[roi]->result_float);
|
||||||
|
|
||||||
|
if (isLogImage)
|
||||||
|
{
|
||||||
|
string _imagename = GENERAL[n]->name + "_" + GENERAL[n]->ROI[roi]->name;
|
||||||
|
if (isLogImageSelect)
|
||||||
|
{
|
||||||
|
if (LogImageSelect.find(GENERAL[n]->ROI[roi]->name) != std::string::npos)
|
||||||
|
LogImage(logPath, _imagename, &_result_save_file, NULL, time, GENERAL[n]->ROI[roi]->image_org);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogImage(logPath, _imagename, &_result_save_file, NULL, time, GENERAL[n]->ROI[roi]->image_org);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Digital100:
|
||||||
|
case Analogue100:
|
||||||
|
{
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "CNN Type: Digital100 or Analogue100");
|
||||||
|
int _num;
|
||||||
|
float _result_save_file;
|
||||||
|
|
||||||
|
tflite->LoadInputImageBasis(GENERAL[n]->ROI[roi]->image);
|
||||||
|
tflite->Invoke();
|
||||||
|
|
||||||
|
_num = tflite->GetOutClassification();
|
||||||
|
|
||||||
|
if(GENERAL[n]->ROI[roi]->CCW)
|
||||||
|
GENERAL[n]->ROI[roi]->result_float = 10 - ((float)_num / 10.0);
|
||||||
|
else
|
||||||
|
GENERAL[n]->ROI[roi]->result_float = (float)_num / 10.0;
|
||||||
|
|
||||||
|
_result_save_file = GENERAL[n]->ROI[roi]->result_float;
|
||||||
|
|
||||||
|
|
||||||
|
GENERAL[n]->ROI[roi]->isReject = false;
|
||||||
|
|
||||||
|
ESP_LOGD(TAG, "Result General(Analog)%i - CCW: %d - %f", roi, GENERAL[n]->ROI[roi]->CCW, GENERAL[n]->ROI[roi]->result_float);
|
||||||
|
|
||||||
|
if (isLogImage)
|
||||||
|
{
|
||||||
|
string _imagename = GENERAL[n]->name + "_" + GENERAL[n]->ROI[roi]->name;
|
||||||
|
if (isLogImageSelect)
|
||||||
|
{
|
||||||
|
if (LogImageSelect.find(GENERAL[n]->ROI[roi]->name) != std::string::npos)
|
||||||
|
LogImage(logPath, _imagename, &_result_save_file, NULL, time, GENERAL[n]->ROI[roi]->image_org);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogImage(logPath, _imagename, &_result_save_file, NULL, time, GENERAL[n]->ROI[roi]->image_org);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delete tflite;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool ClassFlowCNNGeneral::isExtendedResolution(int _number)
|
||||||
|
{
|
||||||
|
if (!(CNNType == Digital))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<HTMLInfo*> ClassFlowCNNGeneral::GetHTMLInfo()
|
||||||
|
{
|
||||||
|
std::vector<HTMLInfo*> result;
|
||||||
|
|
||||||
|
for (int _ana = 0; _ana < GENERAL.size(); ++_ana)
|
||||||
|
for (int i = 0; i < GENERAL[_ana]->ROI.size(); ++i)
|
||||||
|
{
|
||||||
|
ESP_LOGD(TAG, "Image: %d", (int) GENERAL[_ana]->ROI[i]->image);
|
||||||
|
if (GENERAL[_ana]->ROI[i]->image)
|
||||||
|
{
|
||||||
|
if (GENERAL[_ana]->name == "default")
|
||||||
|
GENERAL[_ana]->ROI[i]->image->SaveToFile(FormatFileName("/sdcard/img_tmp/" + GENERAL[_ana]->ROI[i]->name + ".jpg"));
|
||||||
|
else
|
||||||
|
GENERAL[_ana]->ROI[i]->image->SaveToFile(FormatFileName("/sdcard/img_tmp/" + GENERAL[_ana]->name + "_" + GENERAL[_ana]->ROI[i]->name + ".jpg"));
|
||||||
|
}
|
||||||
|
|
||||||
|
HTMLInfo *zw = new HTMLInfo;
|
||||||
|
if (GENERAL[_ana]->name == "default")
|
||||||
|
{
|
||||||
|
zw->filename = GENERAL[_ana]->ROI[i]->name + ".jpg";
|
||||||
|
zw->filename_org = GENERAL[_ana]->ROI[i]->name + ".jpg";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
zw->filename = GENERAL[_ana]->name + "_" + GENERAL[_ana]->ROI[i]->name + ".jpg";
|
||||||
|
zw->filename_org = GENERAL[_ana]->name + "_" + GENERAL[_ana]->ROI[i]->name + ".jpg";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CNNType == Digital)
|
||||||
|
zw->val = GENERAL[_ana]->ROI[i]->result_klasse;
|
||||||
|
else
|
||||||
|
zw->val = GENERAL[_ana]->ROI[i]->result_float;
|
||||||
|
zw->image = GENERAL[_ana]->ROI[i]->image;
|
||||||
|
zw->image_org = GENERAL[_ana]->ROI[i]->image_org;
|
||||||
|
|
||||||
|
result.push_back(zw);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int ClassFlowCNNGeneral::getNumberGENERAL()
|
||||||
|
{
|
||||||
|
return GENERAL.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
string ClassFlowCNNGeneral::getNameGENERAL(int _analog)
|
||||||
|
{
|
||||||
|
if (_analog < GENERAL.size())
|
||||||
|
return GENERAL[_analog]->name;
|
||||||
|
|
||||||
|
return "GENERAL DOES NOT EXIST";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
general* ClassFlowCNNGeneral::GetGENERAL(int _analog)
|
||||||
|
{
|
||||||
|
if (_analog < GENERAL.size())
|
||||||
|
return GENERAL[_analog];
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ClassFlowCNNGeneral::UpdateNameNumbers(std::vector<std::string> *_name_numbers)
|
||||||
|
{
|
||||||
|
for (int _dig = 0; _dig < GENERAL.size(); _dig++)
|
||||||
|
{
|
||||||
|
std::string _name = GENERAL[_dig]->name;
|
||||||
|
bool found = false;
|
||||||
|
for (int i = 0; i < (*_name_numbers).size(); ++i)
|
||||||
|
{
|
||||||
|
if ((*_name_numbers)[i] == _name)
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
if (!found)
|
||||||
|
(*_name_numbers).push_back(_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
string ClassFlowCNNGeneral::getReadoutRawString(int _analog)
|
||||||
|
{
|
||||||
|
string rt = "";
|
||||||
|
|
||||||
|
if (_analog >= GENERAL.size() || GENERAL[_analog]==NULL || GENERAL[_analog]->ROI.size() == 0)
|
||||||
|
return rt;
|
||||||
|
|
||||||
|
for (int i = 0; i < GENERAL[_analog]->ROI.size(); ++i)
|
||||||
|
{
|
||||||
|
if (CNNType == Analogue || CNNType == Analogue100)
|
||||||
|
{
|
||||||
|
rt = rt + "," + RundeOutput(GENERAL[_analog]->ROI[i]->result_float, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CNNType == Digital)
|
||||||
|
{
|
||||||
|
if (GENERAL[_analog]->ROI[i]->result_klasse == 10)
|
||||||
|
rt = rt + ",N";
|
||||||
|
else
|
||||||
|
rt = rt + "," + RundeOutput(GENERAL[_analog]->ROI[i]->result_klasse, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((CNNType == DoubleHyprid10) || (CNNType == Digital100))
|
||||||
|
{
|
||||||
|
rt = rt + "," + RundeOutput(GENERAL[_analog]->ROI[i]->result_float, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rt;
|
||||||
|
}
|
||||||
|
|
||||||
88
code/components/jomjol_flowcontroll/ClassFlowCNNGeneral.h
Normal file
88
code/components/jomjol_flowcontroll/ClassFlowCNNGeneral.h
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef CLASSFLOWCNNGENERAL_H
|
||||||
|
#define CLASSFLOWCNNGENERAL_H
|
||||||
|
|
||||||
|
#include"ClassFlowDefineTypes.h"
|
||||||
|
#include "ClassFlowAlignment.h"
|
||||||
|
|
||||||
|
|
||||||
|
enum t_CNNType {
|
||||||
|
AutoDetect,
|
||||||
|
Analogue,
|
||||||
|
Analogue100,
|
||||||
|
Digital,
|
||||||
|
DigitalHyprid10,
|
||||||
|
DoubleHyprid10,
|
||||||
|
Digital100,
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
class ClassFlowCNNGeneral :
|
||||||
|
public ClassFlowImage
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
t_CNNType CNNType;
|
||||||
|
std::vector<general*> GENERAL;
|
||||||
|
float CNNGoodThreshold;
|
||||||
|
|
||||||
|
//moved to define.h
|
||||||
|
//float Analog_error = 3.0;
|
||||||
|
//float AnalogToDigtalFehler = 0.8;
|
||||||
|
//float Digital_Uncertainty = 0.2;
|
||||||
|
//int DigitalBand = 3;
|
||||||
|
//float Digital_Transition_Range_Predecessor = 2;
|
||||||
|
//float Digital_Transition_Area_Predecessor = 0.7; // 9.3 - 0.7
|
||||||
|
//float Digital_Transition_Area_Forward = 9.7; // Pre-run zero crossing only happens from approx. 9.7 onwards
|
||||||
|
|
||||||
|
string cnnmodelfile;
|
||||||
|
int modelxsize, modelysize, modelchannel;
|
||||||
|
bool isLogImageSelect;
|
||||||
|
string LogImageSelect;
|
||||||
|
ClassFlowAlignment* flowpostalignment;
|
||||||
|
|
||||||
|
bool SaveAllFiles;
|
||||||
|
|
||||||
|
int PointerEvalAnalogNew(float zahl, int numeral_preceder);
|
||||||
|
int PointerEvalAnalogToDigitNew(float zahl, float numeral_preceder, int eval_predecessors, float analogDigitalTransitionStart);
|
||||||
|
int PointerEvalHybridNew(float zahl, float number_of_predecessors, int eval_predecessors, bool Analog_Predecessors = false, float analogDigitalTransitionStart=9.2);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool doNeuralNetwork(string time);
|
||||||
|
bool doAlignAndCut(string time);
|
||||||
|
|
||||||
|
bool getNetworkParameter();
|
||||||
|
|
||||||
|
public:
|
||||||
|
ClassFlowCNNGeneral(ClassFlowAlignment *_flowalign, t_CNNType _cnntype = AutoDetect);
|
||||||
|
|
||||||
|
bool ReadParameter(FILE* pfile, string& aktparamgraph);
|
||||||
|
bool doFlow(string time);
|
||||||
|
|
||||||
|
string getHTMLSingleStep(string host);
|
||||||
|
string getReadout(int _analog, bool _extendedResolution = false, int prev = -1, float _before_narrow_Analog = -1, float analogDigitalTransitionStart=9.2);
|
||||||
|
|
||||||
|
string getReadoutRawString(int _analog);
|
||||||
|
|
||||||
|
void DrawROI(CImageBasis *_zw);
|
||||||
|
|
||||||
|
std::vector<HTMLInfo*> GetHTMLInfo();
|
||||||
|
|
||||||
|
int getNumberGENERAL();
|
||||||
|
general* GetGENERAL(int _analog);
|
||||||
|
general* GetGENERAL(string _name, bool _create);
|
||||||
|
general* FindGENERAL(string _name_number);
|
||||||
|
string getNameGENERAL(int _analog);
|
||||||
|
|
||||||
|
bool isExtendedResolution(int _number = 0);
|
||||||
|
|
||||||
|
void UpdateNameNumbers(std::vector<std::string> *_name_numbers);
|
||||||
|
|
||||||
|
t_CNNType getCNNType(){return CNNType;};
|
||||||
|
|
||||||
|
string name(){return "ClassFlowCNNGeneral";};
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
908
code/components/jomjol_flowcontroll/ClassFlowControll.cpp
Normal file
908
code/components/jomjol_flowcontroll/ClassFlowControll.cpp
Normal file
@@ -0,0 +1,908 @@
|
|||||||
|
#include "ClassFlowControll.h"
|
||||||
|
|
||||||
|
#include "connect_wlan.h"
|
||||||
|
#include "read_wlanini.h"
|
||||||
|
|
||||||
|
#include "freertos/task.h"
|
||||||
|
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
#include <dirent.h>
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "ClassLogFile.h"
|
||||||
|
#include "time_sntp.h"
|
||||||
|
#include "Helper.h"
|
||||||
|
#include "server_ota.h"
|
||||||
|
#ifdef ENABLE_MQTT
|
||||||
|
#include "interface_mqtt.h"
|
||||||
|
#include "server_mqtt.h"
|
||||||
|
#endif //ENABLE_MQTT
|
||||||
|
|
||||||
|
#include "server_help.h"
|
||||||
|
#include "../../include/defines.h"
|
||||||
|
|
||||||
|
static const char* TAG = "CTRL";
|
||||||
|
|
||||||
|
//#define DEBUG_DETAIL_ON
|
||||||
|
|
||||||
|
|
||||||
|
std::string ClassFlowControll::doSingleStep(std::string _stepname, std::string _host){
|
||||||
|
std::string _classname = "";
|
||||||
|
std::string result = "";
|
||||||
|
|
||||||
|
ESP_LOGD(TAG, "Step %s start", _stepname.c_str());
|
||||||
|
|
||||||
|
if ((_stepname.compare("[MakeImage]") == 0) || (_stepname.compare(";[MakeImage]") == 0)){
|
||||||
|
_classname = "ClassFlowMakeImage";
|
||||||
|
}
|
||||||
|
if ((_stepname.compare("[Alignment]") == 0) || (_stepname.compare(";[Alignment]") == 0)){
|
||||||
|
_classname = "ClassFlowAlignment";
|
||||||
|
}
|
||||||
|
if ((_stepname.compare(0, 7, "[Digits") == 0) || (_stepname.compare(0, 8, ";[Digits") == 0)) {
|
||||||
|
_classname = "ClassFlowCNNGeneral";
|
||||||
|
}
|
||||||
|
if ((_stepname.compare("[Analog]") == 0) || (_stepname.compare(";[Analog]") == 0)){
|
||||||
|
_classname = "ClassFlowCNNGeneral";
|
||||||
|
}
|
||||||
|
#ifdef ENABLE_MQTT
|
||||||
|
if ((_stepname.compare("[MQTT]") == 0) || (_stepname.compare(";[MQTT]") == 0)){
|
||||||
|
_classname = "ClassFlowMQTT";
|
||||||
|
}
|
||||||
|
#endif //ENABLE_MQTT
|
||||||
|
|
||||||
|
#ifdef ENABLE_INFLUXDB
|
||||||
|
if ((_stepname.compare("[InfluxDB]") == 0) || (_stepname.compare(";[InfluxDB]") == 0)){
|
||||||
|
_classname = "ClassFlowInfluxDB";
|
||||||
|
}
|
||||||
|
#endif //ENABLE_INFLUXDB
|
||||||
|
|
||||||
|
for (int i = 0; i < FlowControll.size(); ++i)
|
||||||
|
if (FlowControll[i]->name().compare(_classname) == 0){
|
||||||
|
if (!(FlowControll[i]->name().compare("ClassFlowMakeImage") == 0)) // if it is a MakeImage, the image does not need to be included, this happens automatically with the html query.
|
||||||
|
FlowControll[i]->doFlow("");
|
||||||
|
result = FlowControll[i]->getHTMLSingleStep(_host);
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGD(TAG, "Step %s end", _stepname.c_str());
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::string ClassFlowControll::TranslateAktstatus(std::string _input)
|
||||||
|
{
|
||||||
|
if (_input.compare("ClassFlowMakeImage") == 0)
|
||||||
|
return ("Take Image");
|
||||||
|
if (_input.compare("ClassFlowAlignment") == 0)
|
||||||
|
return ("Aligning");
|
||||||
|
if (_input.compare("ClassFlowCNNGeneral") == 0)
|
||||||
|
return ("Digitalization of ROIs");
|
||||||
|
#ifdef ENABLE_MQTT
|
||||||
|
if (_input.compare("ClassFlowMQTT") == 0)
|
||||||
|
return ("Sending MQTT");
|
||||||
|
#endif //ENABLE_MQTT
|
||||||
|
#ifdef ENABLE_INFLUXDB
|
||||||
|
if (_input.compare("ClassFlowInfluxDB") == 0)
|
||||||
|
return ("Sending InfluxDB");
|
||||||
|
#endif //ENABLE_INFLUXDB
|
||||||
|
if (_input.compare("ClassFlowPostProcessing") == 0)
|
||||||
|
return ("Post-Processing");
|
||||||
|
if (_input.compare("ClassFlowWriteList") == 0)
|
||||||
|
return ("Writing List");
|
||||||
|
|
||||||
|
return "Unkown Status";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<HTMLInfo*> ClassFlowControll::GetAllDigital()
|
||||||
|
{
|
||||||
|
if (flowdigit)
|
||||||
|
{
|
||||||
|
ESP_LOGD(TAG, "ClassFlowControll::GetAllDigital - flowdigit != NULL");
|
||||||
|
return flowdigit->GetHTMLInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<HTMLInfo*> empty;
|
||||||
|
return empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<HTMLInfo*> ClassFlowControll::GetAllAnalog()
|
||||||
|
{
|
||||||
|
if (flowanalog)
|
||||||
|
return flowanalog->GetHTMLInfo();
|
||||||
|
|
||||||
|
std::vector<HTMLInfo*> empty;
|
||||||
|
return empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
t_CNNType ClassFlowControll::GetTypeDigital()
|
||||||
|
{
|
||||||
|
if (flowdigit)
|
||||||
|
return flowdigit->getCNNType();
|
||||||
|
|
||||||
|
return t_CNNType::None;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
t_CNNType ClassFlowControll::GetTypeAnalog()
|
||||||
|
{
|
||||||
|
if (flowanalog)
|
||||||
|
return flowanalog->getCNNType();
|
||||||
|
|
||||||
|
return t_CNNType::None;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef ALGROI_LOAD_FROM_MEM_AS_JPG
|
||||||
|
void ClassFlowControll::DigitalDrawROI(CImageBasis *_zw)
|
||||||
|
{
|
||||||
|
if (flowdigit)
|
||||||
|
flowdigit->DrawROI(_zw);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ClassFlowControll::AnalogDrawROI(CImageBasis *_zw)
|
||||||
|
{
|
||||||
|
if (flowanalog)
|
||||||
|
flowanalog->DrawROI(_zw);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef ENABLE_MQTT
|
||||||
|
string ClassFlowControll::GetMQTTMainTopic()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < FlowControll.size(); ++i)
|
||||||
|
if (FlowControll[i]->name().compare("ClassFlowMQTT") == 0)
|
||||||
|
return ((ClassFlowMQTT*) (FlowControll[i]))->GetMQTTMainTopic();
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ClassFlowControll::StartMQTTService() {
|
||||||
|
/* Start the MQTT service */
|
||||||
|
for (int i = 0; i < FlowControll.size(); ++i) {
|
||||||
|
if (FlowControll[i]->name().compare("ClassFlowMQTT") == 0) {
|
||||||
|
return ((ClassFlowMQTT*) (FlowControll[i]))->Start(AutoIntervall);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif //ENABLE_MQTT
|
||||||
|
|
||||||
|
|
||||||
|
void ClassFlowControll::SetInitialParameter(void)
|
||||||
|
{
|
||||||
|
AutoStart = false;
|
||||||
|
SetupModeActive = false;
|
||||||
|
AutoIntervall = 10; // Minutes
|
||||||
|
flowdigit = NULL;
|
||||||
|
flowanalog = NULL;
|
||||||
|
flowpostprocessing = NULL;
|
||||||
|
disabled = false;
|
||||||
|
aktRunNr = 0;
|
||||||
|
aktstatus = "Flow task not yet created";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool ClassFlowControll::isAutoStart(long &_intervall)
|
||||||
|
{
|
||||||
|
_intervall = AutoIntervall * 60 * 1000; // AutoInterval: minutes -> ms
|
||||||
|
return AutoStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ClassFlow* ClassFlowControll::CreateClassFlow(std::string _type)
|
||||||
|
{
|
||||||
|
ClassFlow* cfc = NULL;
|
||||||
|
|
||||||
|
_type = trim(_type);
|
||||||
|
|
||||||
|
if (toUpper(_type).compare("[MAKEIMAGE]") == 0)
|
||||||
|
{
|
||||||
|
cfc = new ClassFlowMakeImage(&FlowControll);
|
||||||
|
flowmakeimage = (ClassFlowMakeImage*) cfc;
|
||||||
|
}
|
||||||
|
if (toUpper(_type).compare("[ALIGNMENT]") == 0)
|
||||||
|
{
|
||||||
|
cfc = new ClassFlowAlignment(&FlowControll);
|
||||||
|
flowalignment = (ClassFlowAlignment*) cfc;
|
||||||
|
}
|
||||||
|
if (toUpper(_type).compare("[ANALOG]") == 0)
|
||||||
|
{
|
||||||
|
cfc = new ClassFlowCNNGeneral(flowalignment);
|
||||||
|
flowanalog = (ClassFlowCNNGeneral*) cfc;
|
||||||
|
}
|
||||||
|
if (toUpper(_type).compare(0, 7, "[DIGITS") == 0)
|
||||||
|
{
|
||||||
|
cfc = new ClassFlowCNNGeneral(flowalignment);
|
||||||
|
flowdigit = (ClassFlowCNNGeneral*) cfc;
|
||||||
|
}
|
||||||
|
#ifdef ENABLE_MQTT
|
||||||
|
if (toUpper(_type).compare("[MQTT]") == 0)
|
||||||
|
cfc = new ClassFlowMQTT(&FlowControll);
|
||||||
|
#endif //ENABLE_MQTT
|
||||||
|
#ifdef ENABLE_INFLUXDB
|
||||||
|
if (toUpper(_type).compare("[INFLUXDB]") == 0)
|
||||||
|
cfc = new ClassFlowInfluxDB(&FlowControll);
|
||||||
|
#endif //ENABLE_INFLUXDB
|
||||||
|
if (toUpper(_type).compare("[WRITELIST]") == 0)
|
||||||
|
cfc = new ClassFlowWriteList(&FlowControll);
|
||||||
|
|
||||||
|
if (toUpper(_type).compare("[POSTPROCESSING]") == 0)
|
||||||
|
{
|
||||||
|
cfc = new ClassFlowPostProcessing(&FlowControll, flowanalog, flowdigit);
|
||||||
|
flowpostprocessing = (ClassFlowPostProcessing*) cfc;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cfc) // Attached only if it is not [AutoTimer], because this is for FlowControll
|
||||||
|
FlowControll.push_back(cfc);
|
||||||
|
|
||||||
|
if (toUpper(_type).compare("[AUTOTIMER]") == 0)
|
||||||
|
cfc = this;
|
||||||
|
|
||||||
|
if (toUpper(_type).compare("[DATALOGGING]") == 0)
|
||||||
|
cfc = this;
|
||||||
|
|
||||||
|
if (toUpper(_type).compare("[DEBUG]") == 0)
|
||||||
|
cfc = this;
|
||||||
|
|
||||||
|
if (toUpper(_type).compare("[SYSTEM]") == 0)
|
||||||
|
cfc = this;
|
||||||
|
|
||||||
|
return cfc;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ClassFlowControll::InitFlow(std::string config)
|
||||||
|
{
|
||||||
|
aktstatus = "Initialization";
|
||||||
|
//#ifdef ENABLE_MQTT
|
||||||
|
//MQTTPublish(mqttServer_getMainTopic() + "/" + "status", "Initialization", false); // Right now, not possible -> MQTT Service is going to be started later
|
||||||
|
//#endif //ENABLE_MQTT
|
||||||
|
|
||||||
|
string line;
|
||||||
|
flowpostprocessing = NULL;
|
||||||
|
|
||||||
|
ClassFlow* cfc;
|
||||||
|
FILE* pFile;
|
||||||
|
config = FormatFileName(config);
|
||||||
|
pFile = fopen(config.c_str(), "r");
|
||||||
|
|
||||||
|
line = "";
|
||||||
|
|
||||||
|
char zw[1024];
|
||||||
|
if (pFile != NULL)
|
||||||
|
{
|
||||||
|
fgets(zw, 1024, pFile);
|
||||||
|
ESP_LOGD(TAG, "%s", zw);
|
||||||
|
line = std::string(zw);
|
||||||
|
}
|
||||||
|
|
||||||
|
while ((line.size() > 0) && !(feof(pFile)))
|
||||||
|
{
|
||||||
|
cfc = CreateClassFlow(line);
|
||||||
|
if (cfc)
|
||||||
|
{
|
||||||
|
ESP_LOGD(TAG, "Start ReadParameter (%s)", line.c_str());
|
||||||
|
cfc->ReadParameter(pFile, line);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
line = "";
|
||||||
|
if (fgets(zw, 1024, pFile) && !feof(pFile))
|
||||||
|
{
|
||||||
|
ESP_LOGD(TAG, "Read: %s", zw);
|
||||||
|
line = std::string(zw);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(pFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::string* ClassFlowControll::getActStatus()
|
||||||
|
{
|
||||||
|
return &aktstatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ClassFlowControll::setActStatus(std::string _aktstatus)
|
||||||
|
{
|
||||||
|
aktstatus = _aktstatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ClassFlowControll::doFlowMakeImageOnly(string time)
|
||||||
|
{
|
||||||
|
std::string zw_time;
|
||||||
|
|
||||||
|
for (int i = 0; i < FlowControll.size(); ++i)
|
||||||
|
{
|
||||||
|
if (FlowControll[i]->name() == "ClassFlowMakeImage") {
|
||||||
|
zw_time = getCurrentTimeString("%H:%M:%S");
|
||||||
|
std::string flowStatus = TranslateAktstatus(FlowControll[i]->name());
|
||||||
|
aktstatus = flowStatus + " (" + zw_time + ")";
|
||||||
|
#ifdef ENABLE_MQTT
|
||||||
|
MQTTPublish(mqttServer_getMainTopic() + "/" + "status", flowStatus, false);
|
||||||
|
#endif //ENABLE_MQTT
|
||||||
|
|
||||||
|
FlowControll[i]->doFlow(time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool ClassFlowControll::doFlow(string time)
|
||||||
|
{
|
||||||
|
bool result = true;
|
||||||
|
std::string zw_time;
|
||||||
|
int repeat = 0;
|
||||||
|
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
LogFile.WriteHeapInfo("ClassFlowControll::doFlow - Start");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Check if we have a valid date/time and if not restart the NTP client */
|
||||||
|
/* if (! getTimeIsSet()) {
|
||||||
|
LogFile.WriteToFile(ESP_LOG_WARN, TAG, "Time not set, restarting NTP Client!");
|
||||||
|
restartNtpClient();
|
||||||
|
}*/
|
||||||
|
|
||||||
|
//checkNtpStatus(0);
|
||||||
|
|
||||||
|
for (int i = 0; i < FlowControll.size(); ++i)
|
||||||
|
{
|
||||||
|
zw_time = getCurrentTimeString("%H:%M:%S");
|
||||||
|
std::string flowStatus = TranslateAktstatus(FlowControll[i]->name());
|
||||||
|
aktstatus = flowStatus + " (" + zw_time + ")";
|
||||||
|
//LogFile.WriteToFile(ESP_LOG_INFO, TAG, aktstatus);
|
||||||
|
#ifdef ENABLE_MQTT
|
||||||
|
MQTTPublish(mqttServer_getMainTopic() + "/" + "status", flowStatus, false);
|
||||||
|
#endif //ENABLE_MQTT
|
||||||
|
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
string zw = "FlowControll.doFlow - " + FlowControll[i]->name();
|
||||||
|
LogFile.WriteHeapInfo(zw);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!FlowControll[i]->doFlow(time)){
|
||||||
|
repeat++;
|
||||||
|
LogFile.WriteToFile(ESP_LOG_WARN, TAG, "Fehler im vorheriger Schritt - wird zum " + to_string(repeat) + ". Mal wiederholt");
|
||||||
|
if (i) i -= 1; // vPrevious step must be repeated (probably take pictures)
|
||||||
|
result = false;
|
||||||
|
if (repeat > 5) {
|
||||||
|
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Wiederholung 5x nicht erfolgreich --> reboot");
|
||||||
|
doReboot();
|
||||||
|
//Step was repeated 5x --> reboot
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
LogFile.WriteHeapInfo("ClassFlowControll::doFlow");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
zw_time = getCurrentTimeString("%H:%M:%S");
|
||||||
|
std::string flowStatus = "Flow finished";
|
||||||
|
aktstatus = flowStatus + " (" + zw_time + ")";
|
||||||
|
//LogFile.WriteToFile(ESP_LOG_INFO, TAG, aktstatus);
|
||||||
|
#ifdef ENABLE_MQTT
|
||||||
|
MQTTPublish(mqttServer_getMainTopic() + "/" + "status", flowStatus, false);
|
||||||
|
#endif //ENABLE_MQTT
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
string ClassFlowControll::getReadoutAll(int _type)
|
||||||
|
{
|
||||||
|
std::string out = "";
|
||||||
|
if (flowpostprocessing)
|
||||||
|
{
|
||||||
|
std::vector<NumberPost*> *numbers = flowpostprocessing->GetNumbers();
|
||||||
|
|
||||||
|
for (int i = 0; i < (*numbers).size(); ++i)
|
||||||
|
{
|
||||||
|
out = out + (*numbers)[i]->name + "\t";
|
||||||
|
switch (_type) {
|
||||||
|
case READOUT_TYPE_VALUE:
|
||||||
|
out = out + (*numbers)[i]->ReturnValue;
|
||||||
|
break;
|
||||||
|
case READOUT_TYPE_PREVALUE:
|
||||||
|
if (flowpostprocessing->PreValueUse)
|
||||||
|
{
|
||||||
|
if ((*numbers)[i]->PreValueOkay)
|
||||||
|
out = out + (*numbers)[i]->ReturnPreValue;
|
||||||
|
else
|
||||||
|
out = out + "PreValue too old";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
out = out + "PreValue deactivated";
|
||||||
|
break;
|
||||||
|
case READOUT_TYPE_RAWVALUE:
|
||||||
|
out = out + (*numbers)[i]->ReturnRawValue;
|
||||||
|
break;
|
||||||
|
case READOUT_TYPE_ERROR:
|
||||||
|
out = out + (*numbers)[i]->ErrorMessageText;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (i < (*numbers).size()-1)
|
||||||
|
out = out + "\r\n";
|
||||||
|
}
|
||||||
|
// ESP_LOGD(TAG, "OUT: %s", out.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
string ClassFlowControll::getReadout(bool _rawvalue = false, bool _noerror = false)
|
||||||
|
{
|
||||||
|
if (flowpostprocessing)
|
||||||
|
return flowpostprocessing->getReadoutParam(_rawvalue, _noerror);
|
||||||
|
|
||||||
|
string zw = "";
|
||||||
|
string result = "";
|
||||||
|
|
||||||
|
for (int i = 0; i < FlowControll.size(); ++i)
|
||||||
|
{
|
||||||
|
zw = FlowControll[i]->getReadout();
|
||||||
|
if (zw.length() > 0)
|
||||||
|
{
|
||||||
|
if (result.length() == 0)
|
||||||
|
result = zw;
|
||||||
|
else
|
||||||
|
result = result + "\t" + zw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
string ClassFlowControll::GetPrevalue(std::string _number)
|
||||||
|
{
|
||||||
|
if (flowpostprocessing)
|
||||||
|
{
|
||||||
|
return flowpostprocessing->GetPreValue(_number);
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::string("");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::string ClassFlowControll::UpdatePrevalue(std::string _newvalue, std::string _numbers, bool _extern)
|
||||||
|
{
|
||||||
|
float zw;
|
||||||
|
char* p;
|
||||||
|
|
||||||
|
_newvalue = trim(_newvalue);
|
||||||
|
// ESP_LOGD(TAG, "Input UpdatePreValue: %s", _newvalue.c_str());
|
||||||
|
|
||||||
|
if (_newvalue.compare("0.0") == 0)
|
||||||
|
{
|
||||||
|
zw = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
zw = strtof(_newvalue.c_str(), &p);
|
||||||
|
if (zw == 0)
|
||||||
|
return "- Error in String to Value Conversion!!! Must be of format value=123.456";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (flowpostprocessing)
|
||||||
|
{
|
||||||
|
flowpostprocessing->SetPreValue(zw, _numbers, _extern);
|
||||||
|
return _newvalue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::string();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool ClassFlowControll::ReadParameter(FILE* pfile, string& aktparamgraph)
|
||||||
|
{
|
||||||
|
std::vector<string> splitted;
|
||||||
|
|
||||||
|
aktparamgraph = trim(aktparamgraph);
|
||||||
|
|
||||||
|
if (aktparamgraph.size() == 0)
|
||||||
|
if (!this->GetNextParagraph(pfile, aktparamgraph))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
|
||||||
|
if ((toUpper(aktparamgraph).compare("[AUTOTIMER]") != 0) && (toUpper(aktparamgraph).compare("[DEBUG]") != 0) &&
|
||||||
|
(toUpper(aktparamgraph).compare("[SYSTEM]") != 0 && (toUpper(aktparamgraph).compare("[DATALOGGING]") != 0))) // Paragraph passt nicht zu MakeImage
|
||||||
|
return false;
|
||||||
|
|
||||||
|
while (this->getNextLine(pfile, &aktparamgraph) && !this->isNewParagraph(aktparamgraph))
|
||||||
|
{
|
||||||
|
splitted = ZerlegeZeile(aktparamgraph, " =");
|
||||||
|
if ((toUpper(splitted[0]) == "AUTOSTART") && (splitted.size() > 1))
|
||||||
|
{
|
||||||
|
if (toUpper(splitted[1]) == "TRUE")
|
||||||
|
{
|
||||||
|
AutoStart = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((toUpper(splitted[0]) == "INTERVALL") && (splitted.size() > 1))
|
||||||
|
{
|
||||||
|
AutoIntervall = std::stof(splitted[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((toUpper(splitted[0]) == "DATALOGACTIVE") && (splitted.size() > 1))
|
||||||
|
{
|
||||||
|
if (toUpper(splitted[1]) == "TRUE")
|
||||||
|
{
|
||||||
|
LogFile.SetDataLogToSD(true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
LogFile.SetDataLogToSD(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((toUpper(splitted[0]) == "DATALOGRETENTIONINDAYS") && (splitted.size() > 1))
|
||||||
|
{
|
||||||
|
LogFile.SetDataLogRetention(std::stoi(splitted[1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((toUpper(splitted[0]) == "LOGFILE") && (splitted.size() > 1))
|
||||||
|
{
|
||||||
|
/* matches esp_log_level_t */
|
||||||
|
if ((toUpper(splitted[1]) == "TRUE") || (toUpper(splitted[1]) == "2"))
|
||||||
|
{
|
||||||
|
LogFile.setLogLevel(ESP_LOG_WARN);
|
||||||
|
}
|
||||||
|
else if ((toUpper(splitted[1]) == "FALSE") || (toUpper(splitted[1]) == "0") || (toUpper(splitted[1]) == "1"))
|
||||||
|
{
|
||||||
|
LogFile.setLogLevel(ESP_LOG_ERROR);
|
||||||
|
}
|
||||||
|
else if (toUpper(splitted[1]) == "3")
|
||||||
|
{
|
||||||
|
LogFile.setLogLevel(ESP_LOG_INFO);
|
||||||
|
}
|
||||||
|
else if (toUpper(splitted[1]) == "4")
|
||||||
|
{
|
||||||
|
LogFile.setLogLevel(ESP_LOG_DEBUG);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((toUpper(splitted[0]) == "LOGFILERETENTIONINDAYS") && (splitted.size() > 1))
|
||||||
|
{
|
||||||
|
LogFile.SetLogFileRetention(std::stoi(splitted[1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TimeServer and TimeZone got already read from the config, see setupTime () */
|
||||||
|
|
||||||
|
if ((toUpper(splitted[0]) == "RSSITHREASHOLD") && (splitted.size() > 1))
|
||||||
|
{
|
||||||
|
if (ChangeRSSIThreashold(WLAN_CONFIG_FILE, atoi(splitted[1].c_str())))
|
||||||
|
{
|
||||||
|
// reboot necessary so that the new wlan.ini is also used !!!
|
||||||
|
fclose(pfile);
|
||||||
|
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Rebooting to activate new RSSITHREASHOLD ...");
|
||||||
|
esp_restart();
|
||||||
|
hard_restart();
|
||||||
|
doReboot();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((toUpper(splitted[0]) == "HOSTNAME") && (splitted.size() > 1))
|
||||||
|
{
|
||||||
|
if (ChangeHostName(WLAN_CONFIG_FILE, splitted[1]))
|
||||||
|
{
|
||||||
|
// reboot necessary so that the new wlan.ini is also used !!!
|
||||||
|
fclose(pfile);
|
||||||
|
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Rebooting to activate new HOSTNAME...");
|
||||||
|
esp_restart();
|
||||||
|
hard_restart();
|
||||||
|
doReboot();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((toUpper(splitted[0]) == "SETUPMODE") && (splitted.size() > 1))
|
||||||
|
{
|
||||||
|
if (toUpper(splitted[1]) == "TRUE")
|
||||||
|
{
|
||||||
|
SetupModeActive = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int ClassFlowControll::CleanTempFolder() {
|
||||||
|
const char* folderPath = "/sdcard/img_tmp";
|
||||||
|
|
||||||
|
ESP_LOGD(TAG, "Clean up temporary folder to avoid damage of sdcard sectors: %s", folderPath);
|
||||||
|
DIR *dir = opendir(folderPath);
|
||||||
|
if (!dir) {
|
||||||
|
ESP_LOGE(TAG, "Failed to stat dir: %s", folderPath);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct dirent *entry;
|
||||||
|
int deleted = 0;
|
||||||
|
while ((entry = readdir(dir)) != NULL) {
|
||||||
|
std::string path = string(folderPath) + "/" + entry->d_name;
|
||||||
|
if (entry->d_type == DT_REG) {
|
||||||
|
if (unlink(path.c_str()) == 0) {
|
||||||
|
deleted ++;
|
||||||
|
} else {
|
||||||
|
ESP_LOGE(TAG, "can't delete file: %s", path.c_str());
|
||||||
|
}
|
||||||
|
} else if (entry->d_type == DT_DIR) {
|
||||||
|
deleted += removeFolder(path.c_str(), TAG);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
closedir(dir);
|
||||||
|
ESP_LOGD(TAG, "%d files deleted", deleted);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
esp_err_t ClassFlowControll::SendRawJPG(httpd_req_t *req)
|
||||||
|
{
|
||||||
|
return flowmakeimage != NULL ? flowmakeimage->SendRawJPG(req) : ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
esp_err_t ClassFlowControll::GetJPGStream(std::string _fn, httpd_req_t *req)
|
||||||
|
{
|
||||||
|
ESP_LOGD(TAG, "ClassFlowControll::GetJPGStream %s", _fn.c_str());
|
||||||
|
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
LogFile.WriteHeapInfo("ClassFlowControll::GetJPGStream - Start");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
CImageBasis *_send = NULL;
|
||||||
|
esp_err_t result = ESP_FAIL;
|
||||||
|
bool _sendDelete = false;
|
||||||
|
|
||||||
|
if (_fn == "alg.jpg") {
|
||||||
|
if (flowalignment && flowalignment->ImageBasis->ImageOkay()) {
|
||||||
|
_send = flowalignment->ImageBasis;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "ClassFlowControll::GetJPGStream: alg.jpg cannot be served");
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (_fn == "alg_roi.jpg") {
|
||||||
|
#ifdef ALGROI_LOAD_FROM_MEM_AS_JPG // no CImageBasis needed to create alg_roi.jpg (ca. 790kB less RAM)
|
||||||
|
if (aktstatus.find("Initialization (delayed)") != -1) {
|
||||||
|
FILE* file = fopen("/sdcard/html/Flowstate_initialization_delayed.jpg", "rb");
|
||||||
|
|
||||||
|
if (!file) {
|
||||||
|
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "File /sdcard/html/Flowstate_initialization_delayed.jpg not found");
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
fseek(file, 0, SEEK_END);
|
||||||
|
long fileSize = ftell(file); /* how long is the file ? */
|
||||||
|
fseek(file, 0, SEEK_SET); /* reset */
|
||||||
|
|
||||||
|
unsigned char* fileBuffer = (unsigned char*) malloc(fileSize);
|
||||||
|
|
||||||
|
if (!fileBuffer) {
|
||||||
|
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "ClassFlowControll::GetJPGStream: Not enough memory to create fileBuffer: " + std::to_string(fileSize));
|
||||||
|
fclose(file);
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
fread(fileBuffer, fileSize, 1, file);
|
||||||
|
fclose(file);
|
||||||
|
|
||||||
|
httpd_resp_set_type(req, "image/jpeg");
|
||||||
|
result = httpd_resp_send(req, (const char *)fileBuffer, fileSize);
|
||||||
|
delete fileBuffer;
|
||||||
|
}
|
||||||
|
else if (aktstatus.find("Initialization") != -1) {
|
||||||
|
FILE* file = fopen("/sdcard/html/Flowstate_initialization.jpg", "rb");
|
||||||
|
|
||||||
|
if (!file) {
|
||||||
|
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "File /sdcard/html/Flowstate_initialization.jpg not found");
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
fseek(file, 0, SEEK_END);
|
||||||
|
long fileSize = ftell(file); /* how long is the file ? */
|
||||||
|
fseek(file, 0, SEEK_SET); /* reset */
|
||||||
|
|
||||||
|
unsigned char* fileBuffer = (unsigned char*) malloc(fileSize);
|
||||||
|
|
||||||
|
if (!fileBuffer) {
|
||||||
|
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "ClassFlowControll::GetJPGStream: Not enough memory to create fileBuffer: " + std::to_string(fileSize));
|
||||||
|
fclose(file);
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
fread(fileBuffer, fileSize, 1, file);
|
||||||
|
fclose(file);
|
||||||
|
|
||||||
|
httpd_resp_set_type(req, "image/jpeg");
|
||||||
|
result = httpd_resp_send(req, (const char *)fileBuffer, fileSize);
|
||||||
|
delete fileBuffer;
|
||||||
|
}
|
||||||
|
else if (aktstatus.find("Take Image") != -1) {
|
||||||
|
if (flowalignment && flowalignment->AlgROI) {
|
||||||
|
FILE* file = fopen("/sdcard/html/Flowstate_take_image.jpg", "rb");
|
||||||
|
|
||||||
|
if (!file) {
|
||||||
|
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "File /sdcard/html/Flowstate_take_image.jpg not found");
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
fseek(file, 0, SEEK_END);
|
||||||
|
flowalignment->AlgROI->size = ftell(file); /* how long is the file ? */
|
||||||
|
fseek(file, 0, SEEK_SET); /* reset */
|
||||||
|
|
||||||
|
if (flowalignment->AlgROI->size > MAX_JPG_SIZE) {
|
||||||
|
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "File /sdcard/html/Flowstate_take_image.jpg (" + std::to_string(flowalignment->AlgROI->size) +
|
||||||
|
") > allocated buffer (" + std::to_string(MAX_JPG_SIZE) + ")");
|
||||||
|
fclose(file);
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
fread(flowalignment->AlgROI->data, flowalignment->AlgROI->size, 1, file);
|
||||||
|
fclose(file);
|
||||||
|
|
||||||
|
httpd_resp_set_type(req, "image/jpeg");
|
||||||
|
result = httpd_resp_send(req, (const char *)flowalignment->AlgROI->data, flowalignment->AlgROI->size);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "ClassFlowControll::GetJPGStream: alg_roi.jpg cannot be served -> alg.jpg is going to be served!");
|
||||||
|
if (flowalignment && flowalignment->ImageBasis->ImageOkay()) {
|
||||||
|
_send = flowalignment->ImageBasis;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
httpd_resp_send(req, NULL, 0);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (flowalignment && flowalignment->AlgROI) {
|
||||||
|
httpd_resp_set_type(req, "image/jpeg");
|
||||||
|
result = httpd_resp_send(req, (const char *)flowalignment->AlgROI->data, flowalignment->AlgROI->size);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "ClassFlowControll::GetJPGStream: alg_roi.jpg cannot be served -> alg.jpg is going to be served!");
|
||||||
|
if (flowalignment && flowalignment->ImageBasis->ImageOkay()) {
|
||||||
|
_send = flowalignment->ImageBasis;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
httpd_resp_send(req, NULL, 0);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if (!flowalignment) {
|
||||||
|
ESP_LOGD(TAG, "ClassFloDControll::GetJPGStream: FlowAlignment is not (yet) initialized. Interrupt serving!");
|
||||||
|
httpd_resp_send(req, NULL, 0);
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
_send = new CImageBasis(flowalignment->ImageBasis);
|
||||||
|
|
||||||
|
if (_send->ImageOkay()) {
|
||||||
|
if (flowalignment) flowalignment->DrawRef(_send);
|
||||||
|
if (flowdigit) flowdigit->DrawROI(_send);
|
||||||
|
if (flowanalog) flowanalog->DrawROI(_send);
|
||||||
|
_sendDelete = true; // delete temporary _send element after sending
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
LogFile.WriteToFile(ESP_LOG_WARN, TAG, "ClassFlowControll::GetJPGStream: Not enough memory to create alg_roi.jpg -> alg.jpg is going to be served!");
|
||||||
|
|
||||||
|
if (flowalignment && flowalignment->ImageBasis->ImageOkay()) {
|
||||||
|
_send = flowalignment->ImageBasis;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
httpd_resp_send(req, NULL, 0);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
std::vector<HTMLInfo*> htmlinfo;
|
||||||
|
|
||||||
|
htmlinfo = GetAllDigital();
|
||||||
|
ESP_LOGD(TAG, "After getClassFlowControll::GetAllDigital");
|
||||||
|
|
||||||
|
for (int i = 0; i < htmlinfo.size(); ++i)
|
||||||
|
{
|
||||||
|
if (_fn == htmlinfo[i]->filename)
|
||||||
|
{
|
||||||
|
if (htmlinfo[i]->image)
|
||||||
|
_send = htmlinfo[i]->image;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_fn == htmlinfo[i]->filename_org)
|
||||||
|
{
|
||||||
|
if (htmlinfo[i]->image_org)
|
||||||
|
_send = htmlinfo[i]->image_org;
|
||||||
|
}
|
||||||
|
delete htmlinfo[i];
|
||||||
|
}
|
||||||
|
htmlinfo.clear();
|
||||||
|
|
||||||
|
if (!_send)
|
||||||
|
{
|
||||||
|
htmlinfo = GetAllAnalog();
|
||||||
|
ESP_LOGD(TAG, "After getClassFlowControll::GetAllAnalog");
|
||||||
|
|
||||||
|
for (int i = 0; i < htmlinfo.size(); ++i)
|
||||||
|
{
|
||||||
|
if (_fn == htmlinfo[i]->filename)
|
||||||
|
{
|
||||||
|
if (htmlinfo[i]->image)
|
||||||
|
_send = htmlinfo[i]->image;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_fn == htmlinfo[i]->filename_org)
|
||||||
|
{
|
||||||
|
if (htmlinfo[i]->image_org)
|
||||||
|
_send = htmlinfo[i]->image_org;
|
||||||
|
}
|
||||||
|
delete htmlinfo[i];
|
||||||
|
}
|
||||||
|
htmlinfo.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
LogFile.WriteHeapInfo("ClassFlowControll::GetJPGStream - before send");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (_send)
|
||||||
|
{
|
||||||
|
ESP_LOGD(TAG, "Sending file: %s ...", _fn.c_str());
|
||||||
|
set_content_type_from_file(req, _fn.c_str());
|
||||||
|
result = _send->SendJPGtoHTTP(req);
|
||||||
|
/* Respond with an empty chunk to signal HTTP response completion */
|
||||||
|
httpd_resp_send_chunk(req, NULL, 0);
|
||||||
|
ESP_LOGD(TAG, "File sending complete");
|
||||||
|
|
||||||
|
if (_sendDelete)
|
||||||
|
delete _send;
|
||||||
|
|
||||||
|
_send = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
LogFile.WriteHeapInfo("ClassFlowControll::GetJPGStream - done");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
string ClassFlowControll::getNumbersName()
|
||||||
|
{
|
||||||
|
return flowpostprocessing->getNumbersName();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
string ClassFlowControll::getJSON()
|
||||||
|
{
|
||||||
|
return flowpostprocessing->GetJSON();
|
||||||
|
}
|
||||||
94
code/components/jomjol_flowcontroll/ClassFlowControll.h
Normal file
94
code/components/jomjol_flowcontroll/ClassFlowControll.h
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef CLASSFLOWCONTROLL_H
|
||||||
|
#define CLASSFLOWCONTROLL_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "ClassFlow.h"
|
||||||
|
#include "ClassFlowMakeImage.h"
|
||||||
|
#include "ClassFlowAlignment.h"
|
||||||
|
#include "ClassFlowCNNGeneral.h"
|
||||||
|
#include "ClassFlowPostProcessing.h"
|
||||||
|
#ifdef ENABLE_MQTT
|
||||||
|
#include "ClassFlowMQTT.h"
|
||||||
|
#endif //ENABLE_MQTT
|
||||||
|
#ifdef ENABLE_INFLUXDB
|
||||||
|
#include "ClassFlowInfluxDB.h"
|
||||||
|
#endif //ENABLE_INFLUXDB
|
||||||
|
#include "ClassFlowCNNGeneral.h"
|
||||||
|
#include "ClassFlowWriteList.h"
|
||||||
|
|
||||||
|
class ClassFlowControll :
|
||||||
|
public ClassFlow
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
std::vector<ClassFlow*> FlowControll;
|
||||||
|
ClassFlowPostProcessing* flowpostprocessing;
|
||||||
|
ClassFlowAlignment* flowalignment;
|
||||||
|
ClassFlowCNNGeneral* flowanalog;
|
||||||
|
ClassFlowCNNGeneral* flowdigit;
|
||||||
|
// ClassFlowDigit* flowdigit;
|
||||||
|
ClassFlowMakeImage* flowmakeimage;
|
||||||
|
ClassFlow* CreateClassFlow(std::string _type);
|
||||||
|
|
||||||
|
bool AutoStart;
|
||||||
|
float AutoIntervall;
|
||||||
|
bool SetupModeActive;
|
||||||
|
void SetInitialParameter(void);
|
||||||
|
std::string aktstatus;
|
||||||
|
int aktRunNr;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void InitFlow(std::string config);
|
||||||
|
bool doFlow(string time);
|
||||||
|
void doFlowMakeImageOnly(string time);
|
||||||
|
bool getStatusSetupModus(){return SetupModeActive;};
|
||||||
|
string getReadout(bool _rawvalue, bool _noerror);
|
||||||
|
string getReadoutAll(int _type);
|
||||||
|
string UpdatePrevalue(std::string _newvalue, std::string _numbers, bool _extern);
|
||||||
|
string GetPrevalue(std::string _number = "");
|
||||||
|
bool ReadParameter(FILE* pfile, string& aktparamgraph);
|
||||||
|
string getJSON();
|
||||||
|
string getNumbersName();
|
||||||
|
|
||||||
|
string TranslateAktstatus(std::string _input);
|
||||||
|
|
||||||
|
#ifdef ENABLE_MQTT
|
||||||
|
string GetMQTTMainTopic();
|
||||||
|
#endif //ENABLE_MQTT
|
||||||
|
|
||||||
|
#ifdef ALGROI_LOAD_FROM_MEM_AS_JPG
|
||||||
|
void DigitalDrawROI(CImageBasis *_zw);
|
||||||
|
void AnalogDrawROI(CImageBasis *_zw);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
esp_err_t GetJPGStream(std::string _fn, httpd_req_t *req);
|
||||||
|
esp_err_t SendRawJPG(httpd_req_t *req);
|
||||||
|
|
||||||
|
std::string doSingleStep(std::string _stepname, std::string _host);
|
||||||
|
|
||||||
|
bool isAutoStart(long &_intervall);
|
||||||
|
|
||||||
|
std::string* getActStatus();
|
||||||
|
void setActStatus(std::string _aktstatus);
|
||||||
|
|
||||||
|
std::vector<HTMLInfo*> GetAllDigital();
|
||||||
|
std::vector<HTMLInfo*> GetAllAnalog();
|
||||||
|
|
||||||
|
t_CNNType GetTypeDigital();
|
||||||
|
t_CNNType GetTypeAnalog();
|
||||||
|
|
||||||
|
#ifdef ENABLE_MQTT
|
||||||
|
bool StartMQTTService();
|
||||||
|
#endif //ENABLE_MQTT
|
||||||
|
|
||||||
|
int CleanTempFolder();
|
||||||
|
|
||||||
|
string name(){return "ClassFlowControll";};
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
63
code/components/jomjol_flowcontroll/ClassFlowDefineTypes.h
Normal file
63
code/components/jomjol_flowcontroll/ClassFlowDefineTypes.h
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef CLASSFLOWDEFINETYPES_H
|
||||||
|
#define CLASSFLOWDEFINETYPES_H
|
||||||
|
|
||||||
|
#include "ClassFlowImage.h"
|
||||||
|
|
||||||
|
struct roi {
|
||||||
|
int posx, posy, deltax, deltay;
|
||||||
|
float result_float;
|
||||||
|
int result_klasse;
|
||||||
|
bool isReject, CCW;
|
||||||
|
string name;
|
||||||
|
CImageBasis *image, *image_org;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct general {
|
||||||
|
string name;
|
||||||
|
std::vector<roi*> ROI;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum t_RateType {
|
||||||
|
AbsoluteChange,
|
||||||
|
RateChange
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct NumberPost {
|
||||||
|
float MaxRateValue;
|
||||||
|
bool useMaxRateValue;
|
||||||
|
t_RateType RateType;
|
||||||
|
bool ErrorMessage;
|
||||||
|
bool PreValueOkay;
|
||||||
|
bool AllowNegativeRates;
|
||||||
|
bool checkDigitIncreaseConsistency;
|
||||||
|
time_t lastvalue;
|
||||||
|
string timeStamp;
|
||||||
|
double FlowRateAct; // m3 / min
|
||||||
|
double PreValue; // last value that was read out well
|
||||||
|
double Value; // last value read out, incl. corrections
|
||||||
|
string ReturnRateValue; // return value rate
|
||||||
|
string ReturnChangeAbsolute; // return value rate
|
||||||
|
string ReturnRawValue; // Raw value (with N & leading 0)
|
||||||
|
string ReturnValue; // corrected return value, if necessary with error message
|
||||||
|
string ReturnPreValue; // corrected return value without error message
|
||||||
|
string ErrorMessageText; // Error message for consistency check
|
||||||
|
int AnzahlAnalog;
|
||||||
|
int AnzahlDigital;
|
||||||
|
int DecimalShift;
|
||||||
|
int DecimalShiftInitial;
|
||||||
|
float AnalogDigitalTransitionStart; // When is the digit > x.1, i.e. when does it start to tilt?
|
||||||
|
int Nachkomma;
|
||||||
|
|
||||||
|
bool isExtendedResolution;
|
||||||
|
|
||||||
|
general *digit_roi;
|
||||||
|
general *analog_roi;
|
||||||
|
|
||||||
|
string name;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
139
code/components/jomjol_flowcontroll/ClassFlowImage.cpp
Normal file
139
code/components/jomjol_flowcontroll/ClassFlowImage.cpp
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
#include "ClassFlowImage.h"
|
||||||
|
#include <string>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
#include <dirent.h>
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "time_sntp.h"
|
||||||
|
#include "ClassLogFile.h"
|
||||||
|
#include "CImageBasis.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "../../include/defines.h"
|
||||||
|
|
||||||
|
static const char* TAG = "IMG";
|
||||||
|
|
||||||
|
ClassFlowImage::ClassFlowImage(const char* logTag)
|
||||||
|
{
|
||||||
|
this->logTag = logTag;
|
||||||
|
isLogImage = false;
|
||||||
|
disabled = false;
|
||||||
|
this->logfileRetentionInDays = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClassFlowImage::ClassFlowImage(std::vector<ClassFlow*> * lfc, const char* logTag) : ClassFlow(lfc)
|
||||||
|
{
|
||||||
|
this->logTag = logTag;
|
||||||
|
isLogImage = false;
|
||||||
|
disabled = false;
|
||||||
|
this->logfileRetentionInDays = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClassFlowImage::ClassFlowImage(std::vector<ClassFlow*> * lfc, ClassFlow *_prev, const char* logTag) : ClassFlow(lfc, _prev)
|
||||||
|
{
|
||||||
|
this->logTag = logTag;
|
||||||
|
isLogImage = false;
|
||||||
|
disabled = false;
|
||||||
|
this->logfileRetentionInDays = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
string ClassFlowImage::CreateLogFolder(string time) {
|
||||||
|
if (!isLogImage)
|
||||||
|
return "";
|
||||||
|
|
||||||
|
string logPath = LogImageLocation + "/" + time.LOGFILE_TIME_FORMAT_DATE_EXTR + "/" + time.LOGFILE_TIME_FORMAT_HOUR_EXTR;
|
||||||
|
isLogImage = mkdir_r(logPath.c_str(), S_IRWXU) == 0;
|
||||||
|
if (!isLogImage) {
|
||||||
|
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Can't create log folder for analog images. Path " + logPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return logPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClassFlowImage::LogImage(string logPath, string name, float *resultFloat, int *resultInt, string time, CImageBasis *_img) {
|
||||||
|
if (!isLogImage)
|
||||||
|
return;
|
||||||
|
|
||||||
|
|
||||||
|
char buf[10];
|
||||||
|
|
||||||
|
if (resultFloat != NULL) {
|
||||||
|
if (*resultFloat < 0)
|
||||||
|
sprintf(buf, "N.N_");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sprintf(buf, "%.1f_", *resultFloat);
|
||||||
|
if (strcmp(buf, "10.0_") == 0)
|
||||||
|
sprintf(buf, "0.0_");
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (resultInt != NULL) {
|
||||||
|
sprintf(buf, "%d_", *resultInt);
|
||||||
|
} else {
|
||||||
|
buf[0] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
string nm = logPath + "/" + buf + name + "_" + time + ".jpg";
|
||||||
|
nm = FormatFileName(nm);
|
||||||
|
string output = "/sdcard/img_tmp/" + name + ".jpg";
|
||||||
|
output = FormatFileName(output);
|
||||||
|
ESP_LOGD(logTag, "save to file: %s", nm.c_str());
|
||||||
|
_img->SaveToFile(nm);
|
||||||
|
// CopyFile(output, nm);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClassFlowImage::RemoveOldLogs()
|
||||||
|
{
|
||||||
|
if (!isLogImage)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ESP_LOGD(TAG, "remove old images");
|
||||||
|
if (logfileRetentionInDays == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
time_t rawtime;
|
||||||
|
struct tm* timeinfo;
|
||||||
|
char cmpfilename[30];
|
||||||
|
|
||||||
|
time(&rawtime);
|
||||||
|
rawtime = addDays(rawtime, -logfileRetentionInDays + 1);
|
||||||
|
timeinfo = localtime(&rawtime);
|
||||||
|
//ESP_LOGD(TAG, "ImagefileRetentionInDays: %d", logfileRetentionInDays);
|
||||||
|
|
||||||
|
strftime(cmpfilename, 30, LOGFILE_TIME_FORMAT, timeinfo);
|
||||||
|
//ESP_LOGD(TAG, "file name to compare: %s", cmpfilename);
|
||||||
|
string folderName = string(cmpfilename).LOGFILE_TIME_FORMAT_DATE_EXTR;
|
||||||
|
|
||||||
|
DIR *dir = opendir(LogImageLocation.c_str());
|
||||||
|
if (!dir) {
|
||||||
|
ESP_LOGE(TAG, "Failed to stat dir: %s", LogImageLocation.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct dirent *entry;
|
||||||
|
int deleted = 0;
|
||||||
|
int notDeleted = 0;
|
||||||
|
while ((entry = readdir(dir)) != NULL) {
|
||||||
|
string folderPath = LogImageLocation + "/" + entry->d_name;
|
||||||
|
if (entry->d_type == DT_DIR) {
|
||||||
|
//ESP_LOGD(TAG, "Compare %s to %s", entry->d_name, folderName.c_str());
|
||||||
|
if ((strlen(entry->d_name) == folderName.length()) && (strcmp(entry->d_name, folderName.c_str()) < 0)) {
|
||||||
|
removeFolder(folderPath.c_str(), logTag);
|
||||||
|
deleted++;
|
||||||
|
} else {
|
||||||
|
notDeleted ++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ESP_LOGD(TAG, "Image folder deleted: %d | Image folder not deleted: %d", deleted, notDeleted);
|
||||||
|
closedir(dir);
|
||||||
|
}
|
||||||
|
|
||||||
30
code/components/jomjol_flowcontroll/ClassFlowImage.h
Normal file
30
code/components/jomjol_flowcontroll/ClassFlowImage.h
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef CLASSFLOWIMAGE_H
|
||||||
|
#define CLASSFLOWIMAGE_H
|
||||||
|
|
||||||
|
#include "ClassFlow.h"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
class ClassFlowImage : public ClassFlow
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
string LogImageLocation;
|
||||||
|
bool isLogImage;
|
||||||
|
unsigned short logfileRetentionInDays;
|
||||||
|
const char* logTag;
|
||||||
|
|
||||||
|
string CreateLogFolder(string time);
|
||||||
|
void LogImage(string logPath, string name, float *resultFloat, int *resultInt, string time, CImageBasis *_img);
|
||||||
|
|
||||||
|
|
||||||
|
public:
|
||||||
|
ClassFlowImage(const char* logTag);
|
||||||
|
ClassFlowImage(std::vector<ClassFlow*> * lfc, const char* logTag);
|
||||||
|
ClassFlowImage(std::vector<ClassFlow*> * lfc, ClassFlow *_prev, const char* logTag);
|
||||||
|
|
||||||
|
void RemoveOldLogs();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //CLASSFLOWIMAGE_H
|
||||||
172
code/components/jomjol_flowcontroll/ClassFlowInfluxDB.cpp
Normal file
172
code/components/jomjol_flowcontroll/ClassFlowInfluxDB.cpp
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
#ifdef ENABLE_INFLUXDB
|
||||||
|
#include <sstream>
|
||||||
|
#include "ClassFlowInfluxDB.h"
|
||||||
|
#include "Helper.h"
|
||||||
|
#include "connect_wlan.h"
|
||||||
|
|
||||||
|
#include "time_sntp.h"
|
||||||
|
#include "interface_influxdb.h"
|
||||||
|
|
||||||
|
#include "ClassFlowPostProcessing.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "../../include/defines.h"
|
||||||
|
|
||||||
|
#include "ClassLogFile.h"
|
||||||
|
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
static const char* TAG = "class_flow_influxDb";
|
||||||
|
|
||||||
|
void ClassFlowInfluxDB::SetInitialParameter(void)
|
||||||
|
{
|
||||||
|
uri = "";
|
||||||
|
database = "";
|
||||||
|
measurement = "";
|
||||||
|
|
||||||
|
OldValue = "";
|
||||||
|
flowpostprocessing = NULL;
|
||||||
|
user = "";
|
||||||
|
password = "";
|
||||||
|
previousElement = NULL;
|
||||||
|
ListFlowControll = NULL;
|
||||||
|
disabled = false;
|
||||||
|
InfluxDBenable = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClassFlowInfluxDB::ClassFlowInfluxDB()
|
||||||
|
{
|
||||||
|
SetInitialParameter();
|
||||||
|
}
|
||||||
|
|
||||||
|
ClassFlowInfluxDB::ClassFlowInfluxDB(std::vector<ClassFlow*>* lfc)
|
||||||
|
{
|
||||||
|
SetInitialParameter();
|
||||||
|
|
||||||
|
ListFlowControll = lfc;
|
||||||
|
for (int i = 0; i < ListFlowControll->size(); ++i)
|
||||||
|
{
|
||||||
|
if (((*ListFlowControll)[i])->name().compare("ClassFlowPostProcessing") == 0)
|
||||||
|
{
|
||||||
|
flowpostprocessing = (ClassFlowPostProcessing*) (*ListFlowControll)[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ClassFlowInfluxDB::ClassFlowInfluxDB(std::vector<ClassFlow*>* lfc, ClassFlow *_prev)
|
||||||
|
{
|
||||||
|
SetInitialParameter();
|
||||||
|
|
||||||
|
previousElement = _prev;
|
||||||
|
ListFlowControll = lfc;
|
||||||
|
|
||||||
|
for (int i = 0; i < ListFlowControll->size(); ++i)
|
||||||
|
{
|
||||||
|
if (((*ListFlowControll)[i])->name().compare("ClassFlowPostProcessing") == 0)
|
||||||
|
{
|
||||||
|
flowpostprocessing = (ClassFlowPostProcessing*) (*ListFlowControll)[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool ClassFlowInfluxDB::ReadParameter(FILE* pfile, string& aktparamgraph)
|
||||||
|
{
|
||||||
|
std::vector<string> splitted;
|
||||||
|
|
||||||
|
aktparamgraph = trim(aktparamgraph);
|
||||||
|
|
||||||
|
if (aktparamgraph.size() == 0)
|
||||||
|
if (!this->GetNextParagraph(pfile, aktparamgraph))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (toUpper(aktparamgraph).compare("[INFLUXDB]") != 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
while (this->getNextLine(pfile, &aktparamgraph) && !this->isNewParagraph(aktparamgraph))
|
||||||
|
{
|
||||||
|
ESP_LOGD(TAG, "while loop reading line: %s", aktparamgraph.c_str());
|
||||||
|
splitted = ZerlegeZeile(aktparamgraph);
|
||||||
|
if ((toUpper(splitted[0]) == "USER") && (splitted.size() > 1))
|
||||||
|
{
|
||||||
|
this->user = splitted[1];
|
||||||
|
}
|
||||||
|
if ((toUpper(splitted[0]) == "PASSWORD") && (splitted.size() > 1))
|
||||||
|
{
|
||||||
|
this->password = splitted[1];
|
||||||
|
}
|
||||||
|
if ((toUpper(splitted[0]) == "URI") && (splitted.size() > 1))
|
||||||
|
{
|
||||||
|
this->uri = splitted[1];
|
||||||
|
}
|
||||||
|
if (((toUpper(splitted[0]) == "MEASUREMENT")) && (splitted.size() > 1))
|
||||||
|
{
|
||||||
|
this->measurement = splitted[1];
|
||||||
|
}
|
||||||
|
if (((toUpper(splitted[0]) == "DATABASE")) && (splitted.size() > 1))
|
||||||
|
{
|
||||||
|
this->database = splitted[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((uri.length() > 0) && (database.length() > 0) && (measurement.length() > 0))
|
||||||
|
{
|
||||||
|
// ESP_LOGD(TAG, "Init InfluxDB with uri: %s, measurement: %s, user: %s, password: %s", uri.c_str(), measurement.c_str(), user.c_str(), password.c_str());
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Init InfluxDB with uri: " + uri + ", measurement: " + measurement + ", user: " + user + ", password: " + password);
|
||||||
|
InfluxDBInit(uri, database, measurement, user, password);
|
||||||
|
InfluxDBenable = true;
|
||||||
|
} else {
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "InfluxDB init skipped as we are missing some parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
string ClassFlowInfluxDB::GetInfluxDBMeasurement()
|
||||||
|
{
|
||||||
|
return measurement;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool ClassFlowInfluxDB::doFlow(string zwtime)
|
||||||
|
{
|
||||||
|
if (!InfluxDBenable)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
std::string result;
|
||||||
|
std::string resulterror = "";
|
||||||
|
std::string resultraw = "";
|
||||||
|
std::string resultrate = "";
|
||||||
|
std::string resulttimestamp = "";
|
||||||
|
string zw = "";
|
||||||
|
string namenumber = "";
|
||||||
|
|
||||||
|
if (flowpostprocessing)
|
||||||
|
{
|
||||||
|
std::vector<NumberPost*>* NUMBERS = flowpostprocessing->GetNumbers();
|
||||||
|
|
||||||
|
for (int i = 0; i < (*NUMBERS).size(); ++i)
|
||||||
|
{
|
||||||
|
result = (*NUMBERS)[i]->ReturnValue;
|
||||||
|
resultraw = (*NUMBERS)[i]->ReturnRawValue;
|
||||||
|
resulterror = (*NUMBERS)[i]->ErrorMessageText;
|
||||||
|
resultrate = (*NUMBERS)[i]->ReturnRateValue;
|
||||||
|
resulttimestamp = (*NUMBERS)[i]->timeStamp;
|
||||||
|
|
||||||
|
namenumber = (*NUMBERS)[i]->name;
|
||||||
|
if (namenumber == "default")
|
||||||
|
namenumber = "value";
|
||||||
|
else
|
||||||
|
namenumber = namenumber + "/value";
|
||||||
|
|
||||||
|
if (result.length() > 0 && resulttimestamp.length() > 0)
|
||||||
|
InfluxDBPublish(namenumber, result, resulttimestamp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OldValue = result;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //ENABLE_INFLUXDB
|
||||||
39
code/components/jomjol_flowcontroll/ClassFlowInfluxDB.h
Normal file
39
code/components/jomjol_flowcontroll/ClassFlowInfluxDB.h
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
#ifdef ENABLE_INFLUXDB
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef CLASSFINFLUXDB_H
|
||||||
|
#define CLASSFINFLUXDB_H
|
||||||
|
|
||||||
|
#include "ClassFlow.h"
|
||||||
|
|
||||||
|
#include "ClassFlowPostProcessing.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class ClassFlowInfluxDB :
|
||||||
|
public ClassFlow
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
std::string uri, database, measurement;
|
||||||
|
std::string OldValue;
|
||||||
|
ClassFlowPostProcessing* flowpostprocessing;
|
||||||
|
std::string user, password;
|
||||||
|
bool InfluxDBenable;
|
||||||
|
|
||||||
|
void SetInitialParameter(void);
|
||||||
|
|
||||||
|
public:
|
||||||
|
ClassFlowInfluxDB();
|
||||||
|
ClassFlowInfluxDB(std::vector<ClassFlow*>* lfc);
|
||||||
|
ClassFlowInfluxDB(std::vector<ClassFlow*>* lfc, ClassFlow *_prev);
|
||||||
|
|
||||||
|
string GetInfluxDBMeasurement();
|
||||||
|
|
||||||
|
bool ReadParameter(FILE* pfile, string& aktparamgraph);
|
||||||
|
bool doFlow(string time);
|
||||||
|
string name(){return "ClassFlowInfluxDB";};
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //CLASSFINFLUXDB_H
|
||||||
|
#endif //ENABLE_INFLUXDB
|
||||||
306
code/components/jomjol_flowcontroll/ClassFlowMQTT.cpp
Normal file
306
code/components/jomjol_flowcontroll/ClassFlowMQTT.cpp
Normal file
@@ -0,0 +1,306 @@
|
|||||||
|
#ifdef ENABLE_MQTT
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include "ClassFlowMQTT.h"
|
||||||
|
#include "Helper.h"
|
||||||
|
#include "connect_wlan.h"
|
||||||
|
#include "ClassLogFile.h"
|
||||||
|
|
||||||
|
#include "time_sntp.h"
|
||||||
|
#include "interface_mqtt.h"
|
||||||
|
#include "ClassFlowPostProcessing.h"
|
||||||
|
#include "ClassFlowControll.h"
|
||||||
|
|
||||||
|
#include "server_mqtt.h"
|
||||||
|
|
||||||
|
#include <time.h>
|
||||||
|
#include "../../include/defines.h"
|
||||||
|
|
||||||
|
static const char *TAG = "MQTT";
|
||||||
|
|
||||||
|
extern const char* libfive_git_version(void);
|
||||||
|
extern const char* libfive_git_revision(void);
|
||||||
|
extern const char* libfive_git_branch(void);
|
||||||
|
|
||||||
|
|
||||||
|
void ClassFlowMQTT::SetInitialParameter(void)
|
||||||
|
{
|
||||||
|
uri = "";
|
||||||
|
topic = "";
|
||||||
|
topicError = "";
|
||||||
|
topicRate = "";
|
||||||
|
topicTimeStamp = "";
|
||||||
|
maintopic = hostname;
|
||||||
|
|
||||||
|
topicUptime = "";
|
||||||
|
topicFreeMem = "";
|
||||||
|
|
||||||
|
clientname = "AIOTED-" + getMac();
|
||||||
|
|
||||||
|
OldValue = "";
|
||||||
|
flowpostprocessing = NULL;
|
||||||
|
user = "";
|
||||||
|
password = "";
|
||||||
|
SetRetainFlag = 0;
|
||||||
|
previousElement = NULL;
|
||||||
|
ListFlowControll = NULL;
|
||||||
|
disabled = false;
|
||||||
|
keepAlive = 25*60;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClassFlowMQTT::ClassFlowMQTT()
|
||||||
|
{
|
||||||
|
SetInitialParameter();
|
||||||
|
}
|
||||||
|
|
||||||
|
ClassFlowMQTT::ClassFlowMQTT(std::vector<ClassFlow*>* lfc)
|
||||||
|
{
|
||||||
|
SetInitialParameter();
|
||||||
|
|
||||||
|
ListFlowControll = lfc;
|
||||||
|
for (int i = 0; i < ListFlowControll->size(); ++i)
|
||||||
|
{
|
||||||
|
if (((*ListFlowControll)[i])->name().compare("ClassFlowPostProcessing") == 0)
|
||||||
|
{
|
||||||
|
flowpostprocessing = (ClassFlowPostProcessing*) (*ListFlowControll)[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ClassFlowMQTT::ClassFlowMQTT(std::vector<ClassFlow*>* lfc, ClassFlow *_prev)
|
||||||
|
{
|
||||||
|
SetInitialParameter();
|
||||||
|
|
||||||
|
previousElement = _prev;
|
||||||
|
ListFlowControll = lfc;
|
||||||
|
|
||||||
|
for (int i = 0; i < ListFlowControll->size(); ++i)
|
||||||
|
{
|
||||||
|
if (((*ListFlowControll)[i])->name().compare("ClassFlowPostProcessing") == 0)
|
||||||
|
{
|
||||||
|
flowpostprocessing = (ClassFlowPostProcessing*) (*ListFlowControll)[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool ClassFlowMQTT::ReadParameter(FILE* pfile, string& aktparamgraph)
|
||||||
|
{
|
||||||
|
std::vector<string> splitted;
|
||||||
|
|
||||||
|
aktparamgraph = trim(aktparamgraph);
|
||||||
|
|
||||||
|
if (aktparamgraph.size() == 0)
|
||||||
|
if (!this->GetNextParagraph(pfile, aktparamgraph))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (toUpper(aktparamgraph).compare("[MQTT]") != 0) // Paragraph does not fit MakeImage
|
||||||
|
return false;
|
||||||
|
|
||||||
|
while (this->getNextLine(pfile, &aktparamgraph) && !this->isNewParagraph(aktparamgraph))
|
||||||
|
{
|
||||||
|
splitted = ZerlegeZeile(aktparamgraph);
|
||||||
|
if ((toUpper(splitted[0]) == "USER") && (splitted.size() > 1))
|
||||||
|
{
|
||||||
|
this->user = splitted[1];
|
||||||
|
}
|
||||||
|
if ((toUpper(splitted[0]) == "PASSWORD") && (splitted.size() > 1))
|
||||||
|
{
|
||||||
|
this->password = splitted[1];
|
||||||
|
}
|
||||||
|
if ((toUpper(splitted[0]) == "URI") && (splitted.size() > 1))
|
||||||
|
{
|
||||||
|
this->uri = splitted[1];
|
||||||
|
}
|
||||||
|
if ((toUpper(splitted[0]) == "SETRETAINFLAG") && (splitted.size() > 1))
|
||||||
|
{
|
||||||
|
if (toUpper(splitted[1]) == "TRUE") {
|
||||||
|
SetRetainFlag = 1;
|
||||||
|
setMqtt_Server_Retain(SetRetainFlag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((toUpper(splitted[0]) == "HOMEASSISTANTDISCOVERY") && (splitted.size() > 1))
|
||||||
|
{
|
||||||
|
if (toUpper(splitted[1]) == "TRUE")
|
||||||
|
SetHomeassistantDiscoveryEnabled(true);
|
||||||
|
}
|
||||||
|
if ((toUpper(splitted[0]) == "METERTYPE") && (splitted.size() > 1)) {
|
||||||
|
/* Use meter type for the device class
|
||||||
|
Make sure it is a listed one on https://developers.home-assistant.io/docs/core/entity/sensor/#available-device-classes */
|
||||||
|
if (toUpper(splitted[1]) == "WATER_M3") {
|
||||||
|
mqttServer_setMeterType("water", "m³", "h", "m³/h");
|
||||||
|
}
|
||||||
|
else if (toUpper(splitted[1]) == "WATER_L") {
|
||||||
|
mqttServer_setMeterType("water", "L", "h", "L/h");
|
||||||
|
}
|
||||||
|
else if (toUpper(splitted[1]) == "WATER_FT3") {
|
||||||
|
mqttServer_setMeterType("water", "ft³", "m", "ft³/m"); // Minutes
|
||||||
|
}
|
||||||
|
else if (toUpper(splitted[1]) == "WATER_GAL") {
|
||||||
|
mqttServer_setMeterType("water", "gal", "h", "gal/h");
|
||||||
|
}
|
||||||
|
else if (toUpper(splitted[1]) == "GAS_M3") {
|
||||||
|
mqttServer_setMeterType("gas", "m³", "h", "m³/h");
|
||||||
|
}
|
||||||
|
else if (toUpper(splitted[1]) == "GAS_FT3") {
|
||||||
|
mqttServer_setMeterType("gas", "ft³", "m", "ft³/m"); // Minutes
|
||||||
|
}
|
||||||
|
else if (toUpper(splitted[1]) == "ENERGY_WH") {
|
||||||
|
mqttServer_setMeterType("energy", "Wh", "h", "W");
|
||||||
|
}
|
||||||
|
else if (toUpper(splitted[1]) == "ENERGY_KWH") {
|
||||||
|
mqttServer_setMeterType("energy", "kWh", "h", "kW");
|
||||||
|
}
|
||||||
|
else if (toUpper(splitted[1]) == "ENERGY_MWH") {
|
||||||
|
mqttServer_setMeterType("energy", "MWh", "h", "MW");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((toUpper(splitted[0]) == "CLIENTID") && (splitted.size() > 1))
|
||||||
|
{
|
||||||
|
this->clientname = splitted[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (((toUpper(splitted[0]) == "TOPIC") || (toUpper(splitted[0]) == "MAINTOPIC")) && (splitted.size() > 1))
|
||||||
|
{
|
||||||
|
maintopic = splitted[1];
|
||||||
|
mqttServer_setMainTopic(maintopic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Note:
|
||||||
|
* Originally, we started the MQTT client here.
|
||||||
|
* How ever we need the interval parameter from the ClassFlowControll, but that only gets started later.
|
||||||
|
* To work around this, we delay the start and trigger it from ClassFlowControll::ReadParameter() */
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
string ClassFlowMQTT::GetMQTTMainTopic()
|
||||||
|
{
|
||||||
|
return maintopic;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool ClassFlowMQTT::Start(float AutoIntervall)
|
||||||
|
{
|
||||||
|
roundInterval = AutoIntervall; // Minutes
|
||||||
|
keepAlive = roundInterval * 60 * 2.5; // Seconds, make sure it is greater thatn 2 rounds!
|
||||||
|
|
||||||
|
std::stringstream stream;
|
||||||
|
stream << std::fixed << std::setprecision(1) << "Digitizer interval is " << roundInterval <<
|
||||||
|
" minutes => setting MQTT LWT timeout to " << ((float)keepAlive/60) << " minutes.";
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, stream.str());
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (!MQTTConfigCheck) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (MQTT_Init() == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool ClassFlowMQTT::doFlow(string zwtime)
|
||||||
|
{
|
||||||
|
std::string result;
|
||||||
|
std::string resulterror = "";
|
||||||
|
std::string resultraw = "";
|
||||||
|
std::string resultpre = "";
|
||||||
|
std::string resultrate = ""; // Always Unit / Minute
|
||||||
|
std::string resultRatePerTimeUnit = ""; // According to selection
|
||||||
|
std::string resulttimestamp = "";
|
||||||
|
std::string resultchangabs = "";
|
||||||
|
string zw = "";
|
||||||
|
string namenumber = "";
|
||||||
|
|
||||||
|
publishSystemData();
|
||||||
|
|
||||||
|
if (flowpostprocessing && getMQTTisConnected())
|
||||||
|
{
|
||||||
|
std::vector<NumberPost*>* NUMBERS = flowpostprocessing->GetNumbers();
|
||||||
|
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Publishing MQTT topics...");
|
||||||
|
|
||||||
|
for (int i = 0; i < (*NUMBERS).size(); ++i)
|
||||||
|
{
|
||||||
|
result = (*NUMBERS)[i]->ReturnValue;
|
||||||
|
resultraw = (*NUMBERS)[i]->ReturnRawValue;
|
||||||
|
resultpre = (*NUMBERS)[i]->ReturnPreValue;
|
||||||
|
resulterror = (*NUMBERS)[i]->ErrorMessageText;
|
||||||
|
resultrate = (*NUMBERS)[i]->ReturnRateValue; // Unit per minutes
|
||||||
|
resultchangabs = (*NUMBERS)[i]->ReturnChangeAbsolute; // Units per round
|
||||||
|
resulttimestamp = (*NUMBERS)[i]->timeStamp;
|
||||||
|
|
||||||
|
namenumber = (*NUMBERS)[i]->name;
|
||||||
|
if (namenumber == "default")
|
||||||
|
namenumber = maintopic + "/";
|
||||||
|
else
|
||||||
|
namenumber = maintopic + "/" + namenumber + "/";
|
||||||
|
|
||||||
|
|
||||||
|
if (result.length() > 0)
|
||||||
|
MQTTPublish(namenumber + "value", result, SetRetainFlag);
|
||||||
|
|
||||||
|
if (resulterror.length() > 0)
|
||||||
|
MQTTPublish(namenumber + "error", resulterror, SetRetainFlag);
|
||||||
|
|
||||||
|
if (resultrate.length() > 0) {
|
||||||
|
MQTTPublish(namenumber + "rate", resultrate, SetRetainFlag);
|
||||||
|
|
||||||
|
std::string resultRatePerTimeUnit;
|
||||||
|
if (getTimeUnit() == "h") { // Need conversion to be per hour
|
||||||
|
resultRatePerTimeUnit = resultRatePerTimeUnit = to_string((*NUMBERS)[i]->FlowRateAct * 60); // per minutes => per hour
|
||||||
|
}
|
||||||
|
else { // Keep per minute
|
||||||
|
resultRatePerTimeUnit = resultrate;
|
||||||
|
}
|
||||||
|
MQTTPublish(namenumber + "rate_per_time_unit", resultRatePerTimeUnit, SetRetainFlag);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resultchangabs.length() > 0) {
|
||||||
|
MQTTPublish(namenumber + "changeabsolut", resultchangabs, SetRetainFlag); // Legacy API
|
||||||
|
MQTTPublish(namenumber + "rate_per_digitalization_round", resultchangabs, SetRetainFlag);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resultraw.length() > 0)
|
||||||
|
MQTTPublish(namenumber + "raw", resultraw, SetRetainFlag);
|
||||||
|
|
||||||
|
if (resulttimestamp.length() > 0)
|
||||||
|
MQTTPublish(namenumber + "timestamp", resulttimestamp, SetRetainFlag);
|
||||||
|
|
||||||
|
std::string json = flowpostprocessing->getJsonFromNumber(i, "\n");
|
||||||
|
MQTTPublish(namenumber + "json", json, SetRetainFlag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Disabled because this is no longer a use case */
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// for (int i = 0; i < ListFlowControll->size(); ++i)
|
||||||
|
// {
|
||||||
|
// zw = (*ListFlowControll)[i]->getReadout();
|
||||||
|
// if (zw.length() > 0)
|
||||||
|
// {
|
||||||
|
// if (result.length() == 0)
|
||||||
|
// result = zw;
|
||||||
|
// else
|
||||||
|
// result = result + "\t" + zw;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// MQTTPublish(topic, result, SetRetainFlag);
|
||||||
|
// }
|
||||||
|
|
||||||
|
OldValue = result;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endif //ENABLE_MQTT
|
||||||
42
code/components/jomjol_flowcontroll/ClassFlowMQTT.h
Normal file
42
code/components/jomjol_flowcontroll/ClassFlowMQTT.h
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
#ifdef ENABLE_MQTT
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef CLASSFFLOWMQTT_H
|
||||||
|
#define CLASSFFLOWMQTT_H
|
||||||
|
|
||||||
|
#include "ClassFlow.h"
|
||||||
|
|
||||||
|
#include "ClassFlowPostProcessing.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class ClassFlowMQTT :
|
||||||
|
public ClassFlow
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
std::string uri, topic, topicError, clientname, topicRate, topicTimeStamp, topicUptime, topicFreeMem;
|
||||||
|
std::string OldValue;
|
||||||
|
ClassFlowPostProcessing* flowpostprocessing;
|
||||||
|
std::string user, password;
|
||||||
|
int SetRetainFlag;
|
||||||
|
int keepAlive; // Seconds
|
||||||
|
float roundInterval; // Minutes
|
||||||
|
|
||||||
|
std::string maintopic;
|
||||||
|
void SetInitialParameter(void);
|
||||||
|
|
||||||
|
public:
|
||||||
|
ClassFlowMQTT();
|
||||||
|
ClassFlowMQTT(std::vector<ClassFlow*>* lfc);
|
||||||
|
ClassFlowMQTT(std::vector<ClassFlow*>* lfc, ClassFlow *_prev);
|
||||||
|
|
||||||
|
string GetMQTTMainTopic();
|
||||||
|
bool Start(float AutoIntervall);
|
||||||
|
|
||||||
|
bool ReadParameter(FILE* pfile, string& aktparamgraph);
|
||||||
|
bool doFlow(string time);
|
||||||
|
string name(){return "ClassFlowMQTT";};
|
||||||
|
};
|
||||||
|
#endif //CLASSFFLOWMQTT_H
|
||||||
|
#endif //ENABLE_MQTT
|
||||||
255
code/components/jomjol_flowcontroll/ClassFlowMakeImage.cpp
Normal file
255
code/components/jomjol_flowcontroll/ClassFlowMakeImage.cpp
Normal file
@@ -0,0 +1,255 @@
|
|||||||
|
#include "ClassFlowMakeImage.h"
|
||||||
|
#include "Helper.h"
|
||||||
|
#include "ClassLogFile.h"
|
||||||
|
|
||||||
|
#include "CImageBasis.h"
|
||||||
|
#include "ClassControllCamera.h"
|
||||||
|
|
||||||
|
#include "esp_wifi.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "../../include/defines.h"
|
||||||
|
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
// #define DEBUG_DETAIL_ON
|
||||||
|
|
||||||
|
// #define WIFITURNOFF
|
||||||
|
|
||||||
|
static const char* TAG = "flow_make_image";
|
||||||
|
|
||||||
|
esp_err_t ClassFlowMakeImage::camera_capture(){
|
||||||
|
string nm = namerawimage;
|
||||||
|
Camera.CaptureToFile(nm);
|
||||||
|
time(&TimeImageTaken);
|
||||||
|
localtime(&TimeImageTaken);
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClassFlowMakeImage::takePictureWithFlash(int flash_duration)
|
||||||
|
{
|
||||||
|
// in case the image is flipped, it must be reset here //
|
||||||
|
rawImage->width = image_width;
|
||||||
|
rawImage->height = image_height;
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
ESP_LOGD(TAG, "flash_duration: %d", flash_duration);
|
||||||
|
Camera.CaptureToBasisImage(rawImage, flash_duration);
|
||||||
|
time(&TimeImageTaken);
|
||||||
|
localtime(&TimeImageTaken);
|
||||||
|
|
||||||
|
if (SaveAllFiles) rawImage->SaveToFile(namerawimage);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClassFlowMakeImage::SetInitialParameter(void)
|
||||||
|
{
|
||||||
|
waitbeforepicture = 5;
|
||||||
|
isImageSize = false;
|
||||||
|
ImageQuality = -1;
|
||||||
|
TimeImageTaken = 0;
|
||||||
|
ImageQuality = 5;
|
||||||
|
rawImage = NULL;
|
||||||
|
ImageSize = FRAMESIZE_VGA;
|
||||||
|
SaveAllFiles = false;
|
||||||
|
disabled = false;
|
||||||
|
FixedExposure = false;
|
||||||
|
namerawimage = "/sdcard/img_tmp/raw.jpg";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ClassFlowMakeImage::ClassFlowMakeImage(std::vector<ClassFlow*>* lfc) : ClassFlowImage(lfc, TAG)
|
||||||
|
{
|
||||||
|
LogImageLocation = "/log/source";
|
||||||
|
logfileRetentionInDays = 5;
|
||||||
|
SetInitialParameter();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool ClassFlowMakeImage::ReadParameter(FILE* pfile, string& aktparamgraph)
|
||||||
|
{
|
||||||
|
std::vector<string> splitted;
|
||||||
|
|
||||||
|
aktparamgraph = trim(aktparamgraph);
|
||||||
|
int _brightness = -100;
|
||||||
|
int _contrast = -100;
|
||||||
|
int _saturation = -100;
|
||||||
|
|
||||||
|
if (aktparamgraph.size() == 0)
|
||||||
|
if (!this->GetNextParagraph(pfile, aktparamgraph))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (aktparamgraph.compare("[MakeImage]") != 0) // Paragraph does not fit MakeImage
|
||||||
|
return false;
|
||||||
|
|
||||||
|
while (this->getNextLine(pfile, &aktparamgraph) && !this->isNewParagraph(aktparamgraph))
|
||||||
|
{
|
||||||
|
splitted = ZerlegeZeile(aktparamgraph);
|
||||||
|
if ((splitted[0] == "LogImageLocation") && (splitted.size() > 1))
|
||||||
|
{
|
||||||
|
LogImageLocation = "/sdcard" + splitted[1];
|
||||||
|
isLogImage = true;
|
||||||
|
}
|
||||||
|
if ((splitted[0] == "ImageQuality") && (splitted.size() > 1))
|
||||||
|
ImageQuality = std::stod(splitted[1]);
|
||||||
|
|
||||||
|
if ((splitted[0] == "ImageSize") && (splitted.size() > 1))
|
||||||
|
{
|
||||||
|
ImageSize = Camera.TextToFramesize(splitted[1].c_str());
|
||||||
|
isImageSize = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((toUpper(splitted[0]) == "SAVEALLFILES") && (splitted.size() > 1))
|
||||||
|
{
|
||||||
|
if (toUpper(splitted[1]) == "TRUE")
|
||||||
|
SaveAllFiles = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((toUpper(splitted[0]) == "WAITBEFORETAKINGPICTURE") && (splitted.size() > 1))
|
||||||
|
{
|
||||||
|
waitbeforepicture = stoi(splitted[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((toUpper(splitted[0]) == "LOGFILERETENTIONINDAYS") && (splitted.size() > 1))
|
||||||
|
{
|
||||||
|
this->logfileRetentionInDays = std::stoi(splitted[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((toUpper(splitted[0]) == "BRIGHTNESS") && (splitted.size() > 1))
|
||||||
|
{
|
||||||
|
_brightness = stoi(splitted[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((toUpper(splitted[0]) == "CONTRAST") && (splitted.size() > 1))
|
||||||
|
{
|
||||||
|
_contrast = stoi(splitted[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((toUpper(splitted[0]) == "SATURATION") && (splitted.size() > 1))
|
||||||
|
{
|
||||||
|
_saturation = stoi(splitted[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((toUpper(splitted[0]) == "FIXEDEXPOSURE") && (splitted.size() > 1))
|
||||||
|
{
|
||||||
|
if (toUpper(splitted[1]) == "TRUE")
|
||||||
|
FixedExposure = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((toUpper(splitted[0]) == "LEDINTENSITY") && (splitted.size() > 1))
|
||||||
|
{
|
||||||
|
float ledintensity = stof(splitted[1]);
|
||||||
|
ledintensity = min((float) 100, ledintensity);
|
||||||
|
ledintensity = max((float) 0, ledintensity);
|
||||||
|
Camera.SetLEDIntensity(ledintensity);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((toUpper(splitted[0]) == "DEMO") && (splitted.size() > 1))
|
||||||
|
{
|
||||||
|
if (toUpper(splitted[1]) == "TRUE")
|
||||||
|
Camera.useDemoMode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Camera.SetBrightnessContrastSaturation(_brightness, _contrast, _saturation);
|
||||||
|
Camera.SetQualitySize(ImageQuality, ImageSize);
|
||||||
|
|
||||||
|
image_width = Camera.image_width;
|
||||||
|
image_height = Camera.image_height;
|
||||||
|
rawImage = new CImageBasis();
|
||||||
|
rawImage->CreateEmptyImage(image_width, image_height, 3);
|
||||||
|
|
||||||
|
waitbeforepicture_store = waitbeforepicture;
|
||||||
|
if (FixedExposure && (waitbeforepicture > 0))
|
||||||
|
{
|
||||||
|
// ESP_LOGD(TAG, "Fixed Exposure enabled!");
|
||||||
|
int flash_duration = (int) (waitbeforepicture * 1000);
|
||||||
|
Camera.EnableAutoExposure(flash_duration);
|
||||||
|
waitbeforepicture = 0.2;
|
||||||
|
// flash_duration = (int) (waitbeforepicture * 1000);
|
||||||
|
// takePictureWithFlash(flash_duration);
|
||||||
|
// rawImage->SaveToFile("/sdcard/init2.jpg");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
string ClassFlowMakeImage::getHTMLSingleStep(string host)
|
||||||
|
{
|
||||||
|
string result;
|
||||||
|
result = "Raw Image: <br>\n<img src=\"" + host + "/img_tmp/raw.jpg\">\n";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool ClassFlowMakeImage::doFlow(string zwtime)
|
||||||
|
{
|
||||||
|
string logPath = CreateLogFolder(zwtime);
|
||||||
|
|
||||||
|
int flash_duration = (int) (waitbeforepicture * 1000);
|
||||||
|
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
LogFile.WriteHeapInfo("ClassFlowMakeImage::doFlow - Before takePictureWithFlash");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef WIFITURNOFF
|
||||||
|
esp_wifi_stop(); // to save power usage and
|
||||||
|
#endif
|
||||||
|
|
||||||
|
takePictureWithFlash(flash_duration);
|
||||||
|
|
||||||
|
#ifdef WIFITURNOFF
|
||||||
|
esp_wifi_start();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
LogFile.WriteHeapInfo("ClassFlowMakeImage::doFlow - After takePictureWithFlash");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
LogImage(logPath, "raw", NULL, NULL, zwtime, rawImage);
|
||||||
|
|
||||||
|
RemoveOldLogs();
|
||||||
|
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
LogFile.WriteHeapInfo("ClassFlowMakeImage::doFlow - After RemoveOldLogs");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
esp_err_t ClassFlowMakeImage::SendRawJPG(httpd_req_t *req)
|
||||||
|
{
|
||||||
|
int flash_duration = (int) (waitbeforepicture * 1000);
|
||||||
|
time(&TimeImageTaken);
|
||||||
|
localtime(&TimeImageTaken);
|
||||||
|
|
||||||
|
return Camera.CaptureToHTTP(req, flash_duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ImageData* ClassFlowMakeImage::SendRawImage()
|
||||||
|
{
|
||||||
|
CImageBasis *zw = new CImageBasis(rawImage);
|
||||||
|
ImageData *id;
|
||||||
|
int flash_duration = (int) (waitbeforepicture * 1000);
|
||||||
|
Camera.CaptureToBasisImage(zw, flash_duration);
|
||||||
|
time(&TimeImageTaken);
|
||||||
|
localtime(&TimeImageTaken);
|
||||||
|
|
||||||
|
id = zw->writeToMemoryAsJPG();
|
||||||
|
delete zw;
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
time_t ClassFlowMakeImage::getTimeImageTaken()
|
||||||
|
{
|
||||||
|
return TimeImageTaken;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClassFlowMakeImage::~ClassFlowMakeImage(void)
|
||||||
|
{
|
||||||
|
delete rawImage;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,42 +1,55 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "ClassFlow.h"
|
|
||||||
|
#ifndef CLASSFFLOWMAKEIMAGE_H
|
||||||
|
#define CLASSFFLOWMAKEIMAGE_H
|
||||||
|
|
||||||
|
#include "ClassFlowImage.h"
|
||||||
#include "ClassControllCamera.h"
|
#include "ClassControllCamera.h"
|
||||||
|
#include "../../include/defines.h"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
static const char* TAG2 = "example";
|
|
||||||
|
|
||||||
#define BLINK_GPIO GPIO_NUM_4
|
|
||||||
|
|
||||||
#define CAMERA_MODEL_AI_THINKER
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ClassFlowMakeImage :
|
class ClassFlowMakeImage :
|
||||||
public ClassFlow
|
public ClassFlowImage
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
string LogImageLocation;
|
|
||||||
bool isLogImage;
|
|
||||||
float waitbeforepicture;
|
float waitbeforepicture;
|
||||||
|
float waitbeforepicture_store;
|
||||||
framesize_t ImageSize;
|
framesize_t ImageSize;
|
||||||
bool isImageSize;
|
bool isImageSize;
|
||||||
int ImageQuality;
|
int ImageQuality;
|
||||||
time_t TimeImageTaken;
|
time_t TimeImageTaken;
|
||||||
string namerawimage;
|
string namerawimage;
|
||||||
|
int image_height, image_width;
|
||||||
|
bool SaveAllFiles;
|
||||||
|
bool FixedExposure;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void CopyFile(string input, string output);
|
void CopyFile(string input, string output);
|
||||||
|
|
||||||
esp_err_t camera_capture();
|
esp_err_t camera_capture();
|
||||||
void takePictureWithFlash(int flashdauer);
|
void takePictureWithFlash(int flash_duration);
|
||||||
|
|
||||||
|
|
||||||
|
void SetInitialParameter(void);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ClassFlowMakeImage();
|
CImageBasis *rawImage;
|
||||||
|
|
||||||
ClassFlowMakeImage(std::vector<ClassFlow*>* lfc);
|
ClassFlowMakeImage(std::vector<ClassFlow*>* lfc);
|
||||||
|
|
||||||
bool ReadParameter(FILE* pfile, string& aktparamgraph);
|
bool ReadParameter(FILE* pfile, string& aktparamgraph);
|
||||||
bool doFlow(string time);
|
bool doFlow(string time);
|
||||||
string getHTMLSingleStep(string host);
|
string getHTMLSingleStep(string host);
|
||||||
time_t getTimeImageTaken();
|
time_t getTimeImageTaken();
|
||||||
string name(){return "ClassFlowMakeImage";};
|
string name(){return "ClassFlowMakeImage";};
|
||||||
|
|
||||||
|
ImageData* SendRawImage();
|
||||||
|
esp_err_t SendRawJPG(httpd_req_t *req);
|
||||||
|
|
||||||
|
~ClassFlowMakeImage(void);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif //CLASSFFLOWMAKEIMAGE_H
|
||||||
1109
code/components/jomjol_flowcontroll/ClassFlowPostProcessing.cpp
Normal file
1109
code/components/jomjol_flowcontroll/ClassFlowPostProcessing.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,83 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef CLASSFFLOWPOSTPROCESSING_H
|
||||||
|
#define CLASSFFLOWPOSTPROCESSING_H
|
||||||
|
|
||||||
|
#include "ClassFlow.h"
|
||||||
|
#include "ClassFlowMakeImage.h"
|
||||||
|
#include "ClassFlowCNNGeneral.h"
|
||||||
|
#include "ClassFlowDefineTypes.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
|
||||||
|
class ClassFlowPostProcessing :
|
||||||
|
public ClassFlow
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
std::vector<NumberPost*> NUMBERS;
|
||||||
|
bool UpdatePreValueINI;
|
||||||
|
|
||||||
|
int PreValueAgeStartup;
|
||||||
|
bool ErrorMessage;
|
||||||
|
bool IgnoreLeadingNaN; // SPECIAL CASE for User Gustl ???
|
||||||
|
|
||||||
|
|
||||||
|
ClassFlowCNNGeneral* flowAnalog;
|
||||||
|
ClassFlowCNNGeneral* flowDigit;
|
||||||
|
|
||||||
|
|
||||||
|
string FilePreValue;
|
||||||
|
|
||||||
|
ClassFlowMakeImage *flowMakeImage;
|
||||||
|
|
||||||
|
bool LoadPreValue(void);
|
||||||
|
string ShiftDecimal(string in, int _decShift);
|
||||||
|
|
||||||
|
string ErsetzteN(string, double _prevalue);
|
||||||
|
float checkDigitConsistency(double input, int _decilamshift, bool _isanalog, double _preValue);
|
||||||
|
|
||||||
|
void InitNUMBERS();
|
||||||
|
void handleDecimalSeparator(string _decsep, string _value);
|
||||||
|
void handleMaxRateValue(string _decsep, string _value);
|
||||||
|
void handleDecimalExtendedResolution(string _decsep, string _value);
|
||||||
|
void handleMaxRateType(string _decsep, string _value);
|
||||||
|
void handleAnalogDigitalTransitionStart(string _decsep, string _value);
|
||||||
|
void handleAllowNegativeRate(string _decsep, string _value);
|
||||||
|
|
||||||
|
std::string GetStringReadouts(general);
|
||||||
|
|
||||||
|
void WriteDataLog(int _index);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public:
|
||||||
|
bool PreValueUse;
|
||||||
|
|
||||||
|
ClassFlowPostProcessing(std::vector<ClassFlow*>* lfc, ClassFlowCNNGeneral *_analog, ClassFlowCNNGeneral *_digit);
|
||||||
|
virtual ~ClassFlowPostProcessing(){};
|
||||||
|
bool ReadParameter(FILE* pfile, string& aktparamgraph);
|
||||||
|
bool doFlow(string time);
|
||||||
|
string getReadout(int _number);
|
||||||
|
string getReadoutParam(bool _rawValue, bool _noerror, int _number = 0);
|
||||||
|
string getReadoutError(int _number = 0);
|
||||||
|
string getReadoutRate(int _number = 0);
|
||||||
|
string getReadoutTimeStamp(int _number = 0);
|
||||||
|
void SavePreValue();
|
||||||
|
string getJsonFromNumber(int i, std::string _lineend);
|
||||||
|
string GetPreValue(std::string _number = "");
|
||||||
|
void SetPreValue(double zw, string _numbers, bool _extern = false);
|
||||||
|
|
||||||
|
std::string GetJSON(std::string _lineend = "\n");
|
||||||
|
std::string getNumbersName();
|
||||||
|
|
||||||
|
void UpdateNachkommaDecimalShift();
|
||||||
|
|
||||||
|
std::vector<NumberPost*>* GetNumbers(){return &NUMBERS;};
|
||||||
|
|
||||||
|
string name(){return "ClassFlowPostProcessing";};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif //CLASSFFLOWPOSTPROCESSING_H
|
||||||
91
code/components/jomjol_flowcontroll/ClassFlowWriteList.cpp
Normal file
91
code/components/jomjol_flowcontroll/ClassFlowWriteList.cpp
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
#include <sstream>
|
||||||
|
#include "ClassFlowWriteList.h"
|
||||||
|
#include "Helper.h"
|
||||||
|
|
||||||
|
#include "time_sntp.h"
|
||||||
|
#include "../../include/defines.h"
|
||||||
|
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
void ClassFlowWriteList::SetInitialParameter(void)
|
||||||
|
{
|
||||||
|
flowpostprocessing = NULL;
|
||||||
|
previousElement = NULL;
|
||||||
|
ListFlowControll = NULL;
|
||||||
|
disabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClassFlowWriteList::ClassFlowWriteList()
|
||||||
|
{
|
||||||
|
SetInitialParameter();
|
||||||
|
}
|
||||||
|
|
||||||
|
ClassFlowWriteList::ClassFlowWriteList(std::vector<ClassFlow*>* lfc)
|
||||||
|
{
|
||||||
|
SetInitialParameter();
|
||||||
|
|
||||||
|
ListFlowControll = lfc;
|
||||||
|
for (int i = 0; i < ListFlowControll->size(); ++i)
|
||||||
|
{
|
||||||
|
if (((*ListFlowControll)[i])->name().compare("ClassFlowPostProcessing") == 0)
|
||||||
|
{
|
||||||
|
flowpostprocessing = (ClassFlowPostProcessing*) (*ListFlowControll)[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool ClassFlowWriteList::ReadParameter(FILE* pfile, string& aktparamgraph)
|
||||||
|
{
|
||||||
|
std::vector<string> splitted;
|
||||||
|
|
||||||
|
aktparamgraph = trim(aktparamgraph);
|
||||||
|
|
||||||
|
if (aktparamgraph.size() == 0)
|
||||||
|
if (!this->GetNextParagraph(pfile, aktparamgraph))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (toUpper(aktparamgraph).compare("[MQTT]") != 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
while (this->getNextLine(pfile, &aktparamgraph) && !this->isNewParagraph(aktparamgraph))
|
||||||
|
{
|
||||||
|
splitted = ZerlegeZeile(aktparamgraph);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool ClassFlowWriteList::doFlow(string zwtime)
|
||||||
|
{
|
||||||
|
std::string line = "";
|
||||||
|
|
||||||
|
std::string result;
|
||||||
|
std::string resulterror = "";
|
||||||
|
std::string resultraw = "";
|
||||||
|
std::string resultrate = "";
|
||||||
|
std::string resulttimestamp = "";
|
||||||
|
string zw = "";
|
||||||
|
string namenumber = "";
|
||||||
|
|
||||||
|
if (flowpostprocessing)
|
||||||
|
{
|
||||||
|
std::vector<NumberPost*>* NUMBERS = flowpostprocessing->GetNumbers();
|
||||||
|
|
||||||
|
for (int i = 0; i < (*NUMBERS).size(); ++i)
|
||||||
|
{
|
||||||
|
result = (*NUMBERS)[i]->ReturnValue;
|
||||||
|
resultraw = (*NUMBERS)[i]->ReturnRawValue;
|
||||||
|
resulterror = (*NUMBERS)[i]->ErrorMessageText;
|
||||||
|
resultrate = (*NUMBERS)[i]->ReturnRateValue;
|
||||||
|
resulttimestamp = (*NUMBERS)[i]->timeStamp;
|
||||||
|
|
||||||
|
line = line + resulttimestamp + "\t" + resultraw + "\t" + result + "\t" + resultraw + "\t" + resultrate + "\t" + resulttimestamp + "\t";
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
27
code/components/jomjol_flowcontroll/ClassFlowWriteList.h
Normal file
27
code/components/jomjol_flowcontroll/ClassFlowWriteList.h
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef CLASSFFLOWPWRITELIST_H
|
||||||
|
#define CLASSFFLOWPWRITELIST_H
|
||||||
|
|
||||||
|
#include "ClassFlow.h"
|
||||||
|
#include "ClassFlowPostProcessing.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class ClassFlowWriteList :
|
||||||
|
public ClassFlow
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
ClassFlowPostProcessing* flowpostprocessing;
|
||||||
|
void SetInitialParameter(void);
|
||||||
|
|
||||||
|
public:
|
||||||
|
ClassFlowWriteList();
|
||||||
|
ClassFlowWriteList(std::vector<ClassFlow*>* lfc);
|
||||||
|
|
||||||
|
bool ReadParameter(FILE* pfile, string& aktparamgraph);
|
||||||
|
bool doFlow(string time);
|
||||||
|
string name(){return "ClassFlowWriteList";};
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //CLASSFFLOWPWRITELIST_H
|
||||||
7
code/components/jomjol_helper/CMakeLists.txt
Normal file
7
code/components/jomjol_helper/CMakeLists.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
FILE(GLOB_RECURSE app_sources ${CMAKE_CURRENT_SOURCE_DIR}/*.*)
|
||||||
|
|
||||||
|
idf_component_register(SRCS ${app_sources}
|
||||||
|
INCLUDE_DIRS "."
|
||||||
|
REQUIRES tflite-lib jomjol_logfile fatfs sdmmc)
|
||||||
|
|
||||||
|
|
||||||
902
code/components/jomjol_helper/Helper.cpp
Normal file
902
code/components/jomjol_helper/Helper.cpp
Normal file
@@ -0,0 +1,902 @@
|
|||||||
|
//#pragma warning(disable : 4996)
|
||||||
|
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
|
||||||
|
#include "Helper.h"
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#include <iomanip>
|
||||||
|
#include <sstream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
#include <dirent.h>
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <esp_log.h>
|
||||||
|
#include "../../include/defines.h"
|
||||||
|
|
||||||
|
|
||||||
|
#include "ClassLogFile.h"
|
||||||
|
|
||||||
|
#include "esp_vfs_fat.h"
|
||||||
|
|
||||||
|
static const char* TAG = "HELPER";
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
unsigned int systemStatus = 0;
|
||||||
|
|
||||||
|
sdmmc_cid_t SDCardCid;
|
||||||
|
sdmmc_csd_t SDCardCsd;
|
||||||
|
|
||||||
|
|
||||||
|
// #define DEBUG_DETAIL_ON
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
string getESPHeapInfo(){
|
||||||
|
string espInfoResultStr = "";
|
||||||
|
char aMsgBuf[80];
|
||||||
|
|
||||||
|
size_t aFreeHeapSize = heap_caps_get_free_size(MALLOC_CAP_8BIT);
|
||||||
|
|
||||||
|
size_t aFreeSPIHeapSize = heap_caps_get_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM);
|
||||||
|
size_t aFreeInternalHeapSize = heap_caps_get_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
|
||||||
|
|
||||||
|
size_t aHeapLargestFreeBlockSize = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM);
|
||||||
|
size_t aHeapIntLargestFreeBlockSize = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
|
||||||
|
|
||||||
|
size_t aMinFreeHeapSize = heap_caps_get_minimum_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM);
|
||||||
|
size_t aMinFreeInternalHeapSize = heap_caps_get_minimum_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
|
||||||
|
|
||||||
|
|
||||||
|
sprintf(aMsgBuf,"Heap Total: %ld", (long) aFreeHeapSize);
|
||||||
|
espInfoResultStr += string(aMsgBuf);
|
||||||
|
|
||||||
|
sprintf(aMsgBuf," | SPI Free: %ld", (long) aFreeSPIHeapSize);
|
||||||
|
espInfoResultStr += string(aMsgBuf);
|
||||||
|
sprintf(aMsgBuf," | SPI Larg Block: %ld", (long) aHeapLargestFreeBlockSize);
|
||||||
|
espInfoResultStr += string(aMsgBuf);
|
||||||
|
sprintf(aMsgBuf," | SPI Min Free: %ld", (long) aMinFreeHeapSize);
|
||||||
|
espInfoResultStr += string(aMsgBuf);
|
||||||
|
|
||||||
|
sprintf(aMsgBuf," | Int Free: %ld", (long) (aFreeInternalHeapSize));
|
||||||
|
espInfoResultStr += string(aMsgBuf);
|
||||||
|
sprintf(aMsgBuf," | Int Larg Block: %ld", (long) aHeapIntLargestFreeBlockSize);
|
||||||
|
espInfoResultStr += string(aMsgBuf);
|
||||||
|
sprintf(aMsgBuf," | Int Min Free: %ld", (long) (aMinFreeInternalHeapSize));
|
||||||
|
espInfoResultStr += string(aMsgBuf);
|
||||||
|
|
||||||
|
return espInfoResultStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
size_t getESPHeapSize()
|
||||||
|
{
|
||||||
|
return heap_caps_get_free_size(MALLOC_CAP_8BIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
size_t getInternalESPHeapSize()
|
||||||
|
{
|
||||||
|
return heap_caps_get_free_size(MALLOC_CAP_8BIT| MALLOC_CAP_INTERNAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
string getSDCardPartitionSize(){
|
||||||
|
FATFS *fs;
|
||||||
|
uint32_t fre_clust, tot_sect;
|
||||||
|
|
||||||
|
/* Get volume information and free clusters of drive 0 */
|
||||||
|
f_getfree("0:", (DWORD *)&fre_clust, &fs);
|
||||||
|
tot_sect = ((fs->n_fatent - 2) * fs->csize) /1024 /(1024/SDCardCsd.sector_size); //corrected by SD Card sector size (usually 512 bytes) and convert to MB
|
||||||
|
|
||||||
|
//ESP_LOGD(TAG, "%d MB total drive space (Sector size [bytes]: %d)", (int)tot_sect, (int)fs->ssize);
|
||||||
|
|
||||||
|
return std::to_string(tot_sect);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
string getSDCardFreePartitionSpace(){
|
||||||
|
FATFS *fs;
|
||||||
|
uint32_t fre_clust, fre_sect;
|
||||||
|
|
||||||
|
/* Get volume information and free clusters of drive 0 */
|
||||||
|
f_getfree("0:", (DWORD *)&fre_clust, &fs);
|
||||||
|
fre_sect = (fre_clust * fs->csize) / 1024 /(1024/SDCardCsd.sector_size); //corrected by SD Card sector size (usually 512 bytes) and convert to MB
|
||||||
|
|
||||||
|
//ESP_LOGD(TAG, "%d MB free drive space (Sector size [bytes]: %d)", (int)fre_sect, (int)fs->ssize);
|
||||||
|
|
||||||
|
return std::to_string(fre_sect);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
string getSDCardPartitionAllocationSize(){
|
||||||
|
FATFS *fs;
|
||||||
|
uint32_t fre_clust, allocation_size;
|
||||||
|
|
||||||
|
/* Get volume information and free clusters of drive 0 */
|
||||||
|
f_getfree("0:", (DWORD *)&fre_clust, &fs);
|
||||||
|
allocation_size = fs->ssize;
|
||||||
|
|
||||||
|
//ESP_LOGD(TAG, "SD Card Partition Allocation Size: %d bytes", allocation_size);
|
||||||
|
|
||||||
|
return std::to_string(allocation_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void SaveSDCardInfo(sdmmc_card_t* card) {
|
||||||
|
SDCardCid = card->cid;
|
||||||
|
SDCardCsd = card->csd;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
string getSDCardManufacturer(){
|
||||||
|
string SDCardManufacturer = SDCardParseManufacturerIDs(SDCardCid.mfg_id);
|
||||||
|
//ESP_LOGD(TAG, "SD Card Manufacturer: %s", SDCardManufacturer.c_str());
|
||||||
|
|
||||||
|
return (SDCardManufacturer + " (ID: " + std::to_string(SDCardCid.mfg_id) + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
string getSDCardName(){
|
||||||
|
char *SDCardName = SDCardCid.name;
|
||||||
|
//ESP_LOGD(TAG, "SD Card Name: %s", SDCardName);
|
||||||
|
|
||||||
|
return std::string(SDCardName);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
string getSDCardCapacity(){
|
||||||
|
int SDCardCapacity = SDCardCsd.capacity / (1024/SDCardCsd.sector_size) / 1024; // total sectors * sector size --> Byte to MB (1024*1024)
|
||||||
|
//ESP_LOGD(TAG, "SD Card Capacity: %s", std::to_string(SDCardCapacity).c_str());
|
||||||
|
|
||||||
|
return std::to_string(SDCardCapacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
string getSDCardSectorSize(){
|
||||||
|
int SDCardSectorSize = SDCardCsd.sector_size;
|
||||||
|
//ESP_LOGD(TAG, "SD Card Sector Size: %s bytes", std::to_string(SDCardSectorSize).c_str());
|
||||||
|
|
||||||
|
return std::to_string(SDCardSectorSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
void memCopyGen(uint8_t* _source, uint8_t* _target, int _size)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _size; ++i)
|
||||||
|
*(_target + i) = *(_source + i);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::string FormatFileName(std::string input)
|
||||||
|
{
|
||||||
|
#ifdef ISWINDOWS_TRUE
|
||||||
|
input.erase(0, 1);
|
||||||
|
std::string os = "/";
|
||||||
|
std::string ns = "\\";
|
||||||
|
FindReplace(input, os, ns);
|
||||||
|
#endif
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::size_t file_size(const std::string& file_name) {
|
||||||
|
std::ifstream file(file_name.c_str(),std::ios::in | std::ios::binary);
|
||||||
|
if (!file) return 0;
|
||||||
|
file.seekg (0, std::ios::end);
|
||||||
|
return static_cast<std::size_t>(file.tellg());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void FindReplace(std::string& line, std::string& oldString, std::string& newString) {
|
||||||
|
const size_t oldSize = oldString.length();
|
||||||
|
|
||||||
|
// do nothing if line is shorter than the string to find
|
||||||
|
if (oldSize > line.length()) return;
|
||||||
|
|
||||||
|
const size_t newSize = newString.length();
|
||||||
|
for (size_t pos = 0; ; pos += newSize) {
|
||||||
|
// Locate the substring to replace
|
||||||
|
pos = line.find(oldString, pos);
|
||||||
|
if (pos == std::string::npos) return;
|
||||||
|
if (oldSize == newSize) {
|
||||||
|
// if they're same size, use std::string::replace
|
||||||
|
line.replace(pos, oldSize, newString);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// if not same size, replace by erasing and inserting
|
||||||
|
line.erase(pos, oldSize);
|
||||||
|
line.insert(pos, newString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool MakeDir(std::string _what)
|
||||||
|
{
|
||||||
|
int mk_ret = mkdir(_what.c_str(), 0775);
|
||||||
|
if (mk_ret)
|
||||||
|
{
|
||||||
|
ESP_LOGD(TAG, "error with mkdir %s ret %d", _what.c_str(), mk_ret);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool ctype_space(const char c, string adddelimiter)
|
||||||
|
{
|
||||||
|
if (c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == 11)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (adddelimiter.find(c) != string::npos)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
string trim(string istring, string adddelimiter)
|
||||||
|
{
|
||||||
|
bool trimmed = false;
|
||||||
|
|
||||||
|
if (ctype_space(istring[istring.length() - 1], adddelimiter))
|
||||||
|
{
|
||||||
|
istring.erase(istring.length() - 1);
|
||||||
|
trimmed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctype_space(istring[0], adddelimiter))
|
||||||
|
{
|
||||||
|
istring.erase(0, 1);
|
||||||
|
trimmed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((trimmed == false) || (istring.size() == 0))
|
||||||
|
{
|
||||||
|
return istring;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return trim(istring, adddelimiter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
size_t findDelimiterPos(string input, string delimiter)
|
||||||
|
{
|
||||||
|
size_t pos = std::string::npos;
|
||||||
|
size_t zw;
|
||||||
|
string akt_del;
|
||||||
|
|
||||||
|
for (int anz = 0; anz < delimiter.length(); ++anz)
|
||||||
|
{
|
||||||
|
akt_del = delimiter[anz];
|
||||||
|
if ((zw = input.find(akt_del)) != std::string::npos)
|
||||||
|
{
|
||||||
|
if (pos != std::string::npos)
|
||||||
|
{
|
||||||
|
if (zw < pos)
|
||||||
|
pos = zw;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
pos = zw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool RenameFile(string from, string to)
|
||||||
|
{
|
||||||
|
// ESP_LOGI(logTag, "Deleting file: %s", fn.c_str());
|
||||||
|
/* Delete file */
|
||||||
|
FILE* fpSourceFile = fopen(from.c_str(), "rb");
|
||||||
|
if (!fpSourceFile) // Sourcefile existiert nicht sonst gibt es einen Fehler beim Kopierversuch!
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "DeleteFile: File %s existiert nicht!", from.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
fclose(fpSourceFile);
|
||||||
|
|
||||||
|
rename(from.c_str(), to.c_str());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool FileExists(string filename)
|
||||||
|
{
|
||||||
|
FILE* fpSourceFile = fopen(filename.c_str(), "rb");
|
||||||
|
if (!fpSourceFile) // Sourcefile existiert nicht sonst gibt es einen Fehler beim Kopierversuch!
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
fclose(fpSourceFile);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool DeleteFile(string fn)
|
||||||
|
{
|
||||||
|
// ESP_LOGI(logTag, "Deleting file: %s", fn.c_str());
|
||||||
|
/* Delete file */
|
||||||
|
FILE* fpSourceFile = fopen(fn.c_str(), "rb");
|
||||||
|
if (!fpSourceFile) // Sourcefile existiert nicht sonst gibt es einen Fehler beim Kopierversuch!
|
||||||
|
{
|
||||||
|
ESP_LOGD(TAG, "DeleteFile: File %s existiert nicht!", fn.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
fclose(fpSourceFile);
|
||||||
|
|
||||||
|
unlink(fn.c_str());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool CopyFile(string input, string output)
|
||||||
|
{
|
||||||
|
input = FormatFileName(input);
|
||||||
|
output = FormatFileName(output);
|
||||||
|
|
||||||
|
if (toUpper(input).compare(WLAN_CONFIG_FILE) == 0)
|
||||||
|
{
|
||||||
|
ESP_LOGD(TAG, "wlan.ini kann nicht kopiert werden!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
char cTemp;
|
||||||
|
FILE* fpSourceFile = fopen(input.c_str(), "rb");
|
||||||
|
if (!fpSourceFile) // Sourcefile existiert nicht sonst gibt es einen Fehler beim Kopierversuch!
|
||||||
|
{
|
||||||
|
ESP_LOGD(TAG, "File %s existiert nicht!", input.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE* fpTargetFile = fopen(output.c_str(), "wb");
|
||||||
|
|
||||||
|
// Code Section
|
||||||
|
|
||||||
|
// Read From The Source File - "Copy"
|
||||||
|
while (fread(&cTemp, 1, 1, fpSourceFile) == 1)
|
||||||
|
{
|
||||||
|
// Write To The Target File - "Paste"
|
||||||
|
fwrite(&cTemp, 1, 1, fpTargetFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close The Files
|
||||||
|
fclose(fpSourceFile);
|
||||||
|
fclose(fpTargetFile);
|
||||||
|
ESP_LOGD(TAG, "File copied: %s to %s", input.c_str(), output.c_str());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
string getFileFullFileName(string filename)
|
||||||
|
{
|
||||||
|
size_t lastpos = filename.find_last_of('/');
|
||||||
|
|
||||||
|
if (lastpos == string::npos)
|
||||||
|
return "";
|
||||||
|
|
||||||
|
// ESP_LOGD(TAG, "Last position: %d", lastpos);
|
||||||
|
|
||||||
|
string zw = filename.substr(lastpos + 1, filename.size() - lastpos);
|
||||||
|
|
||||||
|
return zw;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
string getDirectory(string filename)
|
||||||
|
{
|
||||||
|
size_t lastpos = filename.find('/');
|
||||||
|
|
||||||
|
if (lastpos == string::npos)
|
||||||
|
lastpos = filename.find('\\');
|
||||||
|
|
||||||
|
if (lastpos == string::npos)
|
||||||
|
return "";
|
||||||
|
|
||||||
|
// ESP_LOGD(TAG, "Directory: %d", lastpos);
|
||||||
|
|
||||||
|
string zw = filename.substr(0, lastpos - 1);
|
||||||
|
return zw;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
string getFileType(string filename)
|
||||||
|
{
|
||||||
|
size_t lastpos = filename.rfind(".", filename.length());
|
||||||
|
size_t neu_pos;
|
||||||
|
while ((neu_pos = filename.find(".", lastpos + 1)) > -1)
|
||||||
|
{
|
||||||
|
lastpos = neu_pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastpos == string::npos)
|
||||||
|
return "";
|
||||||
|
|
||||||
|
string zw = filename.substr(lastpos + 1, filename.size() - lastpos);
|
||||||
|
zw = toUpper(zw);
|
||||||
|
|
||||||
|
return zw;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* recursive mkdir */
|
||||||
|
int mkdir_r(const char *dir, const mode_t mode) {
|
||||||
|
char tmp[FILE_PATH_MAX];
|
||||||
|
char *p = NULL;
|
||||||
|
struct stat sb;
|
||||||
|
size_t len;
|
||||||
|
|
||||||
|
/* copy path */
|
||||||
|
len = strnlen (dir, FILE_PATH_MAX);
|
||||||
|
if (len == 0 || len == FILE_PATH_MAX) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
memcpy (tmp, dir, len);
|
||||||
|
tmp[len] = '\0';
|
||||||
|
|
||||||
|
/* remove trailing slash */
|
||||||
|
if(tmp[len - 1] == '/') {
|
||||||
|
tmp[len - 1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
/* check if path exists and is a directory */
|
||||||
|
if (stat (tmp, &sb) == 0) {
|
||||||
|
if (S_ISDIR (sb.st_mode)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* recursive mkdir */
|
||||||
|
for(p = tmp + 1; *p; p++) {
|
||||||
|
if(*p == '/') {
|
||||||
|
*p = 0;
|
||||||
|
/* test path */
|
||||||
|
if (stat(tmp, &sb) != 0) {
|
||||||
|
/* path does not exist - create directory */
|
||||||
|
if (mkdir(tmp, mode) < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
} else if (!S_ISDIR(sb.st_mode)) {
|
||||||
|
/* not a directory */
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
*p = '/';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* test path */
|
||||||
|
if (stat(tmp, &sb) != 0) {
|
||||||
|
/* path does not exist - create directory */
|
||||||
|
if (mkdir(tmp, mode) < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
} else if (!S_ISDIR(sb.st_mode)) {
|
||||||
|
/* not a directory */
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
string toUpper(string in)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < in.length(); ++i)
|
||||||
|
in[i] = toupper(in[i]);
|
||||||
|
|
||||||
|
return in;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
string toLower(string in)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < in.length(); ++i)
|
||||||
|
in[i] = tolower(in[i]);
|
||||||
|
|
||||||
|
return in;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// CPU Temp
|
||||||
|
extern "C" uint8_t temprature_sens_read();
|
||||||
|
float temperatureRead()
|
||||||
|
{
|
||||||
|
return (temprature_sens_read() - 32) / 1.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
time_t addDays(time_t startTime, int days) {
|
||||||
|
struct tm* tm = localtime(&startTime);
|
||||||
|
tm->tm_mday += days;
|
||||||
|
return mktime(tm);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int removeFolder(const char* folderPath, const char* logTag) {
|
||||||
|
//ESP_LOGD(logTag, "Delete content in path %s", folderPath);
|
||||||
|
|
||||||
|
DIR *dir = opendir(folderPath);
|
||||||
|
if (!dir) {
|
||||||
|
ESP_LOGE(logTag, "Failed to stat dir: %s", folderPath);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct dirent *entry;
|
||||||
|
int deleted = 0;
|
||||||
|
while ((entry = readdir(dir)) != NULL) {
|
||||||
|
std::string path = string(folderPath) + "/" + entry->d_name;
|
||||||
|
if (entry->d_type == DT_REG) {
|
||||||
|
//ESP_LOGD(logTag, "Delete file %s", path.c_str());
|
||||||
|
if (unlink(path.c_str()) == 0) {
|
||||||
|
deleted ++;
|
||||||
|
} else {
|
||||||
|
ESP_LOGE(logTag, "can't delete file: %s", path.c_str());
|
||||||
|
}
|
||||||
|
} else if (entry->d_type == DT_DIR) {
|
||||||
|
deleted += removeFolder(path.c_str(), logTag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
closedir(dir);
|
||||||
|
if (rmdir(folderPath) != 0) {
|
||||||
|
ESP_LOGE(logTag, "can't delete folder: %s", folderPath);
|
||||||
|
}
|
||||||
|
ESP_LOGD(logTag, "%d files in folder %s deleted.", deleted, folderPath);
|
||||||
|
|
||||||
|
return deleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<string> HelperZerlegeZeile(std::string input, std::string _delimiter = "")
|
||||||
|
{
|
||||||
|
std::vector<string> Output;
|
||||||
|
std::string delimiter = " =,";
|
||||||
|
if (_delimiter.length() > 0){
|
||||||
|
delimiter = _delimiter;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ZerlegeZeile(input, delimiter);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<string> ZerlegeZeile(std::string input, std::string delimiter)
|
||||||
|
{
|
||||||
|
std::vector<string> Output;
|
||||||
|
|
||||||
|
input = trim(input, delimiter);
|
||||||
|
|
||||||
|
/* The input can have multiple formats:
|
||||||
|
* - key = value
|
||||||
|
* - key = value1 value2 value3 ...
|
||||||
|
* - key value1 value2 value3 ...
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
* - ImageSize = VGA
|
||||||
|
* - IO0 = input disabled 10 false false
|
||||||
|
* - main.dig1 28 144 55 100 false
|
||||||
|
*
|
||||||
|
* This causes issues eg. if a password key has a whitespace or equal sign in its value.
|
||||||
|
* As a workaround and to not break any legacy usage, we enforce to only use the
|
||||||
|
* equal sign, if the key is "password"
|
||||||
|
*/
|
||||||
|
if (input.find("password") != string::npos) { // Line contains a password, use the equal sign as the only delimiter and only split on first occurrence
|
||||||
|
size_t pos = input.find("=");
|
||||||
|
Output.push_back(trim(input.substr(0, pos), ""));
|
||||||
|
Output.push_back(trim(input.substr(pos +1, string::npos), ""));
|
||||||
|
}
|
||||||
|
else { // Legacy Mode
|
||||||
|
size_t pos = findDelimiterPos(input, delimiter);
|
||||||
|
std::string token;
|
||||||
|
while (pos != std::string::npos) {
|
||||||
|
token = input.substr(0, pos);
|
||||||
|
token = trim(token, delimiter);
|
||||||
|
Output.push_back(token);
|
||||||
|
input.erase(0, pos + 1);
|
||||||
|
input = trim(input, delimiter);
|
||||||
|
pos = findDelimiterPos(input, delimiter);
|
||||||
|
}
|
||||||
|
Output.push_back(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Output;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::string ReplaceString(std::string subject, const std::string& search,
|
||||||
|
const std::string& replace) {
|
||||||
|
size_t pos = 0;
|
||||||
|
while ((pos = subject.find(search, pos)) != std::string::npos) {
|
||||||
|
subject.replace(pos, search.length(), replace);
|
||||||
|
pos += replace.length();
|
||||||
|
}
|
||||||
|
return subject;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Source: https://git.kernel.org/pub/scm/utils/mmc/mmc-utils.git/tree/lsmmc.c */
|
||||||
|
/* SD Card Manufacturer Database */
|
||||||
|
struct SDCard_Manufacturer_database {
|
||||||
|
string type;
|
||||||
|
int id;
|
||||||
|
string manufacturer;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/* Source: https://git.kernel.org/pub/scm/utils/mmc/mmc-utils.git/tree/lsmmc.c */
|
||||||
|
/* SD Card Manufacturer Database */
|
||||||
|
struct SDCard_Manufacturer_database database[] = {
|
||||||
|
{
|
||||||
|
.type = "sd",
|
||||||
|
.id = 0x01,
|
||||||
|
.manufacturer = "Panasonic",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.type = "sd",
|
||||||
|
.id = 0x02,
|
||||||
|
.manufacturer = "Toshiba/Kingston/Viking",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.type = "sd",
|
||||||
|
.id = 0x03,
|
||||||
|
.manufacturer = "SanDisk",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.type = "sd",
|
||||||
|
.id = 0x08,
|
||||||
|
.manufacturer = "Silicon Power",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.type = "sd",
|
||||||
|
.id = 0x18,
|
||||||
|
.manufacturer = "Infineon",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.type = "sd",
|
||||||
|
.id = 0x1b,
|
||||||
|
.manufacturer = "Transcend/Samsung",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.type = "sd",
|
||||||
|
.id = 0x1c,
|
||||||
|
.manufacturer = "Transcend",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.type = "sd",
|
||||||
|
.id = 0x1d,
|
||||||
|
.manufacturer = "Corsair/AData",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.type = "sd",
|
||||||
|
.id = 0x1e,
|
||||||
|
.manufacturer = "Transcend",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.type = "sd",
|
||||||
|
.id = 0x1f,
|
||||||
|
.manufacturer = "Kingston",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.type = "sd",
|
||||||
|
.id = 0x27,
|
||||||
|
.manufacturer = "Delkin/Phison",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.type = "sd",
|
||||||
|
.id = 0x28,
|
||||||
|
.manufacturer = "Lexar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.type = "sd",
|
||||||
|
.id = 0x30,
|
||||||
|
.manufacturer = "SanDisk",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.type = "sd",
|
||||||
|
.id = 0x31,
|
||||||
|
.manufacturer = "Silicon Power",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.type = "sd",
|
||||||
|
.id = 0x33,
|
||||||
|
.manufacturer = "STMicroelectronics",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.type = "sd",
|
||||||
|
.id = 0x41,
|
||||||
|
.manufacturer = "Kingston",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.type = "sd",
|
||||||
|
.id = 0x6f,
|
||||||
|
.manufacturer = "STMicroelectronics",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.type = "sd",
|
||||||
|
.id = 0x74,
|
||||||
|
.manufacturer = "Transcend",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.type = "sd",
|
||||||
|
.id = 0x76,
|
||||||
|
.manufacturer = "Patriot",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.type = "sd",
|
||||||
|
.id = 0x82,
|
||||||
|
.manufacturer = "Gobe/Sony",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.type = "sd",
|
||||||
|
.id = 0x89,
|
||||||
|
.manufacturer = "Unknown",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/* Parse SD Card Manufacturer Database */
|
||||||
|
string SDCardParseManufacturerIDs(int id)
|
||||||
|
{
|
||||||
|
unsigned int id_cnt = sizeof(database) / sizeof(struct SDCard_Manufacturer_database);
|
||||||
|
string ret_val = "";
|
||||||
|
|
||||||
|
for (int i = 0; i < id_cnt; i++) {
|
||||||
|
if (database[i].id == id) {
|
||||||
|
return database[i].manufacturer;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ret_val = "ID unknown (not in DB)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret_val;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
string RundeOutput(double _in, int _anzNachkomma)
|
||||||
|
{
|
||||||
|
std::stringstream stream;
|
||||||
|
int _zw = _in;
|
||||||
|
// ESP_LOGD(TAG, "AnzNachkomma: %d", _anzNachkomma);
|
||||||
|
|
||||||
|
if (_anzNachkomma < 0) {
|
||||||
|
_anzNachkomma = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_anzNachkomma > 0)
|
||||||
|
{
|
||||||
|
stream << std::fixed << std::setprecision(_anzNachkomma) << _in;
|
||||||
|
return stream.str();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
stream << _zw;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return stream.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
string getMac(void) {
|
||||||
|
uint8_t macInt[6];
|
||||||
|
char macFormated[6*2 + 5 + 1]; // AA:BB:CC:DD:EE:FF
|
||||||
|
|
||||||
|
esp_read_mac(macInt, ESP_MAC_WIFI_STA);
|
||||||
|
sprintf(macFormated, "%02X:%02X:%02X:%02X:%02X:%02X", macInt[0], macInt[1], macInt[2], macInt[3], macInt[4], macInt[5]);
|
||||||
|
|
||||||
|
return macFormated;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void setSystemStatusFlag(SystemStatusFlag_t flag) {
|
||||||
|
systemStatus = systemStatus | flag; // set bit
|
||||||
|
|
||||||
|
char buf[20];
|
||||||
|
snprintf(buf, sizeof(buf), "0x%08X", getSystemStatus());
|
||||||
|
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "New System Status: " + std::string(buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void clearSystemStatusFlag(SystemStatusFlag_t flag) {
|
||||||
|
systemStatus = systemStatus | ~flag; // clear bit
|
||||||
|
|
||||||
|
char buf[20];
|
||||||
|
snprintf(buf, sizeof(buf), "0x%08X", getSystemStatus());
|
||||||
|
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "New System Status: " + std::string(buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int getSystemStatus(void) {
|
||||||
|
return systemStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool isSetSystemStatusFlag(SystemStatusFlag_t flag) {
|
||||||
|
//ESP_LOGE(TAG, "Flag (0x%08X) is set (0x%08X): %d", flag, systemStatus , ((systemStatus & flag) == flag));
|
||||||
|
|
||||||
|
if ((systemStatus & flag) == flag) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
time_t getUpTime(void) {
|
||||||
|
return (uint32_t)(esp_timer_get_time()/1000/1000); // in seconds
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
string getResetReason(void) {
|
||||||
|
std::string reasonText;
|
||||||
|
|
||||||
|
switch(esp_reset_reason()) {
|
||||||
|
case ESP_RST_POWERON: reasonText = "Power-on event (or reset button)"; break; //!< Reset due to power-on event
|
||||||
|
case ESP_RST_EXT: reasonText = "External pin"; break; //!< Reset by external pin (not applicable for ESP32)
|
||||||
|
case ESP_RST_SW: reasonText = "Via esp_restart"; break; //!< Software reset via esp_restart
|
||||||
|
case ESP_RST_PANIC: reasonText = "Exception/panic"; break; //!< Software reset due to exception/panic
|
||||||
|
case ESP_RST_INT_WDT: reasonText = "Interrupt watchdog"; break; //!< Reset (software or hardware) due to interrupt watchdog
|
||||||
|
case ESP_RST_TASK_WDT: reasonText = "Task watchdog"; break; //!< Reset due to task watchdog
|
||||||
|
case ESP_RST_WDT: reasonText = "Other watchdogs"; break; //!< Reset due to other watchdogs
|
||||||
|
case ESP_RST_DEEPSLEEP: reasonText = "Exiting deep sleep mode"; break; //!< Reset after exiting deep sleep mode
|
||||||
|
case ESP_RST_BROWNOUT: reasonText = "Brownout"; break; //!< Brownout reset (software or hardware)
|
||||||
|
case ESP_RST_SDIO: reasonText = "SDIO"; break; //!< Reset over SDIO
|
||||||
|
|
||||||
|
case ESP_RST_UNKNOWN: //!< Reset reason can not be determined
|
||||||
|
default:
|
||||||
|
reasonText = "Unknown";
|
||||||
|
}
|
||||||
|
return reasonText;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current uptime formated ad xxf xxh xxm [xxs]
|
||||||
|
*/
|
||||||
|
std::string getFormatedUptime(bool compact) {
|
||||||
|
char buf[20];
|
||||||
|
#pragma GCC diagnostic ignored "-Wformat-truncation"
|
||||||
|
|
||||||
|
int uptime = getUpTime(); // in seconds
|
||||||
|
|
||||||
|
int days = int(floor(uptime / (3600*24)));
|
||||||
|
int hours = int(floor((uptime - days * 3600*24) / (3600)));
|
||||||
|
int minutes = int(floor((uptime - days * 3600*24 - hours * 3600) / (60)));
|
||||||
|
int seconds = uptime - days * 3600*24 - hours * 3600 - minutes * 60;
|
||||||
|
|
||||||
|
if (compact) {
|
||||||
|
snprintf(buf, sizeof(buf), "%dd%02dh%02dm%02ds", days, hours, minutes, seconds);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
snprintf(buf, sizeof(buf), "%3dd %02dh %02dm %02ds", days, hours, minutes, seconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::string(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const char* get404(void) {
|
||||||
|
return
|
||||||
|
"<pre>\n\n\n\n"
|
||||||
|
" _\n"
|
||||||
|
" .__(.)< ( oh oh! This page does not exist! )\n"
|
||||||
|
" \\___)\n"
|
||||||
|
"\n\n"
|
||||||
|
" You could try your <a href=index.html target=_parent>luck</a> here!</pre>\n"
|
||||||
|
"<script>document.cookie = \"page=overview.html\"</script>"; // Make sure we load the overview page
|
||||||
|
}
|
||||||
96
code/components/jomjol_helper/Helper.h
Normal file
96
code/components/jomjol_helper/Helper.h
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef HELPER_H
|
||||||
|
#define HELPER_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <fstream>
|
||||||
|
#include <vector>
|
||||||
|
#include "sdmmc_cmd.h"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
std::string FormatFileName(std::string input);
|
||||||
|
std::size_t file_size(const std::string& file_name);
|
||||||
|
void FindReplace(std::string& line, std::string& oldString, std::string& newString);
|
||||||
|
|
||||||
|
bool CopyFile(string input, string output);
|
||||||
|
bool DeleteFile(string fn);
|
||||||
|
bool RenameFile(string from, string to);
|
||||||
|
bool MakeDir(std::string _what);
|
||||||
|
bool FileExists(string filename);
|
||||||
|
|
||||||
|
|
||||||
|
string RundeOutput(double _in, int _anzNachkomma);
|
||||||
|
|
||||||
|
size_t findDelimiterPos(string input, string delimiter);
|
||||||
|
//string trim(string istring);
|
||||||
|
string trim(string istring, string adddelimiter = "");
|
||||||
|
bool ctype_space(const char c, string adddelimiter);
|
||||||
|
|
||||||
|
string getFileType(string filename);
|
||||||
|
string getFileFullFileName(string filename);
|
||||||
|
string getDirectory(string filename);
|
||||||
|
|
||||||
|
|
||||||
|
int mkdir_r(const char *dir, const mode_t mode);
|
||||||
|
int removeFolder(const char* folderPath, const char* logTag);
|
||||||
|
|
||||||
|
string toLower(string in);
|
||||||
|
string toUpper(string in);
|
||||||
|
|
||||||
|
float temperatureRead();
|
||||||
|
|
||||||
|
time_t addDays(time_t startTime, int days);
|
||||||
|
|
||||||
|
void memCopyGen(uint8_t* _source, uint8_t* _target, int _size);
|
||||||
|
|
||||||
|
std::vector<string> HelperZerlegeZeile(std::string input, std::string _delimiter);
|
||||||
|
std::vector<std::string> ZerlegeZeile(std::string input, std::string delimiter = " =, \t");
|
||||||
|
|
||||||
|
///////////////////////////
|
||||||
|
size_t getInternalESPHeapSize();
|
||||||
|
size_t getESPHeapSize();
|
||||||
|
string getESPHeapInfo();
|
||||||
|
|
||||||
|
/////////////////////////////
|
||||||
|
string getSDCardPartitionSize();
|
||||||
|
string getSDCardFreePartitionSpace();
|
||||||
|
string getSDCardPartitionAllocationSize();
|
||||||
|
|
||||||
|
void SaveSDCardInfo(sdmmc_card_t* card);
|
||||||
|
string SDCardParseManufacturerIDs(int);
|
||||||
|
string getSDCardManufacturer();
|
||||||
|
string getSDCardName();
|
||||||
|
string getSDCardCapacity();
|
||||||
|
string getSDCardSectorSize();
|
||||||
|
|
||||||
|
string getMac(void);
|
||||||
|
|
||||||
|
|
||||||
|
/* Error bit fields
|
||||||
|
One bit per error
|
||||||
|
Make sure it matches https://jomjol.github.io/AI-on-the-edge-device-docs/Error-Codes */
|
||||||
|
enum SystemStatusFlag_t { // One bit per error
|
||||||
|
// First Byte
|
||||||
|
SYSTEM_STATUS_PSRAM_BAD = 1 << 0, // 1, Critical Error
|
||||||
|
SYSTEM_STATUS_HEAP_TOO_SMALL = 1 << 1, // 2, Critical Error
|
||||||
|
SYSTEM_STATUS_CAM_BAD = 1 << 2, // 4, Critical Error
|
||||||
|
|
||||||
|
// Second Byte
|
||||||
|
SYSTEM_STATUS_CAM_FB_BAD = 1 << (0+8), // 8, Flow still might work
|
||||||
|
SYSTEM_STATUS_NTP_BAD = 1 << (1+8), // 9, Flow will work but time will be wrong
|
||||||
|
};
|
||||||
|
|
||||||
|
void setSystemStatusFlag(SystemStatusFlag_t flag);
|
||||||
|
void clearSystemStatusFlag(SystemStatusFlag_t flag);
|
||||||
|
int getSystemStatus(void);
|
||||||
|
bool isSetSystemStatusFlag(SystemStatusFlag_t flag);
|
||||||
|
|
||||||
|
time_t getUpTime(void);
|
||||||
|
string getResetReason(void);
|
||||||
|
std::string getFormatedUptime(bool compact);
|
||||||
|
|
||||||
|
const char* get404(void);
|
||||||
|
|
||||||
|
#endif //HELPER_H
|
||||||
178
code/components/jomjol_helper/esp_sys.cpp
Normal file
178
code/components/jomjol_helper/esp_sys.cpp
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
#include "../../include/defines.h"
|
||||||
|
|
||||||
|
#ifdef DEBUG_ENABLE_SYSINFO
|
||||||
|
|
||||||
|
#include "esp_sys.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
|
||||||
|
#include "esp_chip_info.h"
|
||||||
|
|
||||||
|
|
||||||
|
void Restart() {
|
||||||
|
esp_restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
//source : https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/misc_system_api.html#_CPPv416esp_chip_model_t
|
||||||
|
|
||||||
|
//https://github.com/espressif/esp-idf/blob/8464186e67e34b417621df6b6f1f289a6c60b859/components/esp_hw_support/include/esp_chip_info.h
|
||||||
|
/*
|
||||||
|
typedef enum {
|
||||||
|
CHIP_ESP32 = 1, //!< ESP32
|
||||||
|
CHIP_ESP32S2 = 2, //!< ESP32-S2
|
||||||
|
CHIP_ESP32S3 = 9, //!< ESP32-S3
|
||||||
|
CHIP_ESP32C3 = 5, //!< ESP32-C3
|
||||||
|
CHIP_ESP32H4 = 6, //!< ESP32-H4
|
||||||
|
CHIP_ESP32C2 = 12, //!< ESP32-C2
|
||||||
|
CHIP_ESP32C6 = 13, //!< ESP32-C6
|
||||||
|
CHIP_ESP32H2 = 16, //!< ESP32-H2
|
||||||
|
CHIP_POSIX_LINUX = 999, //!< The code is running on POSIX/Linux simulator
|
||||||
|
} esp_chip_model_t;
|
||||||
|
*/
|
||||||
|
|
||||||
|
char* GetChipModel(){
|
||||||
|
esp_chip_info_t chipInfo;
|
||||||
|
esp_chip_info(&chipInfo);
|
||||||
|
switch((int)chipInfo.model) {
|
||||||
|
case 0 : return (char*)"ESP8266";
|
||||||
|
case (int)esp_chip_model_t::CHIP_ESP32 : return (char*)"ESP32";
|
||||||
|
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0)
|
||||||
|
case (int)esp_chip_model_t::CHIP_ESP32S2 : return (char*)"ESP32-S2";
|
||||||
|
case (int)esp_chip_model_t::CHIP_ESP32S3 : return (char*)"ESP32-S3";
|
||||||
|
case (int)esp_chip_model_t::CHIP_ESP32C3 : return (char*)"ESP32-C3";
|
||||||
|
case 6 : return (char*)"ESP32-H4";
|
||||||
|
case 12 : return (char*)"ESP32-C2";
|
||||||
|
case 13 : return (char*)"ESP32-C6";
|
||||||
|
//case (int)esp_chip_model_t::CHIP_ESP32H4 : return (char*)"ESP32-H4";
|
||||||
|
//case (int)esp_chip_model_t::CHIP_ESP32C2 : return (char*)"ESP32-C2";
|
||||||
|
//case (int)esp_chip_model_t::CHIP_ESP32C6 : return (char*)"ESP32-C6";
|
||||||
|
//case (int)esp_chip_model_t::CHIP_ESP32H2 : return (char*)"ESP32-H2";
|
||||||
|
case 16 : return (char*)"ESP32-H2";
|
||||||
|
//case (int)esp_chip_model_t::CHIP_POSIX_LINUX : return (char*)"CHIP_POSIX_LINUX";
|
||||||
|
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
return (char*)"Chip Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t GetChipCoreCount() {
|
||||||
|
esp_chip_info_t chipInfo;
|
||||||
|
esp_chip_info(&chipInfo);
|
||||||
|
return chipInfo.cores;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t GetChipRevision() {
|
||||||
|
esp_chip_info_t chipInfo;
|
||||||
|
esp_chip_info(&chipInfo);
|
||||||
|
return chipInfo.revision;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t GetChipfeatures() {
|
||||||
|
esp_chip_info_t chipInfo;
|
||||||
|
esp_chip_info(&chipInfo);
|
||||||
|
return chipInfo.features;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
uint32_t GetFreeHeap() {
|
||||||
|
return esp_get_free_heap_size();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t GetLeastHeapFreeSinceBoot() {
|
||||||
|
return esp_get_minimum_free_heap_size();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::string get_device_info()
|
||||||
|
{
|
||||||
|
esp_chip_info_t chip_info;
|
||||||
|
esp_chip_info(&chip_info);
|
||||||
|
|
||||||
|
std::string espInfoResultStr = "";
|
||||||
|
char aMsgBuf[40];
|
||||||
|
|
||||||
|
espInfoResultStr += "Device Info:";
|
||||||
|
espInfoResultStr += "---------------\n";
|
||||||
|
espInfoResultStr += "Chip Model: " + std::string(GetChipModel()) +"\n";
|
||||||
|
sprintf(aMsgBuf,"Chip Revision: %d\n", chip_info.revision);
|
||||||
|
espInfoResultStr += std::string(aMsgBuf);
|
||||||
|
sprintf(aMsgBuf,"CPU Cores: %d\n", chip_info.cores);
|
||||||
|
espInfoResultStr += std::string(aMsgBuf);
|
||||||
|
sprintf(aMsgBuf,"Flash Memory: %dMB\n", spi_flash_get_chip_size()/(1024*1024));
|
||||||
|
espInfoResultStr += std::string(aMsgBuf);
|
||||||
|
if(chip_info.features & CHIP_FEATURE_WIFI_BGN)
|
||||||
|
//espInfoResultStr += "Base MAC: " + std::string(getMac()) +"\n";
|
||||||
|
espInfoResultStr += "ESP-IDF version: " + std::string(esp_get_idf_version()) +"\n";
|
||||||
|
if((chip_info.features & CHIP_FEATURE_WIFI_BGN) || (chip_info.features & CHIP_FEATURE_BT) ||
|
||||||
|
(chip_info.features & CHIP_FEATURE_BLE) || (chip_info.features & CHIP_FEATURE_EMB_FLASH))
|
||||||
|
{
|
||||||
|
espInfoResultStr += "Characteristics:\n";
|
||||||
|
if(chip_info.features & CHIP_FEATURE_WIFI_BGN)
|
||||||
|
espInfoResultStr += " WiFi 2.4GHz\n";
|
||||||
|
if(chip_info.features & CHIP_FEATURE_BT)
|
||||||
|
espInfoResultStr += " Bluetooth Classic\n";
|
||||||
|
if(chip_info.features & CHIP_FEATURE_BLE)
|
||||||
|
espInfoResultStr += " Bluetooth Low Energy\n";
|
||||||
|
if(chip_info.features & CHIP_FEATURE_EMB_FLASH)
|
||||||
|
espInfoResultStr += " Embedded Flash memory\n";
|
||||||
|
else
|
||||||
|
espInfoResultStr += " External Flash memory\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef USE_HIMEM_IF_AVAILABLE
|
||||||
|
sprintf(aMsgBuf,"spiram size %u\n", esp_spiram_get_size());
|
||||||
|
espInfoResultStr += std::string(aMsgBuf);
|
||||||
|
sprintf(aMsgBuf,"himem free %u\n", esp_himem_get_free_size());
|
||||||
|
espInfoResultStr += std::string(aMsgBuf);
|
||||||
|
sprintf(aMsgBuf,"himem phys %u\n", esp_himem_get_phys_size());
|
||||||
|
espInfoResultStr += std::string(aMsgBuf);
|
||||||
|
sprintf(aMsgBuf,"himem reserved %u\n", esp_himem_reserved_area_size());
|
||||||
|
espInfoResultStr += std::string(aMsgBuf);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return espInfoResultStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
size_t getFreeMemoryInternal(){ //Current Free Memory
|
||||||
|
return heap_caps_get_free_size(MALLOC_CAP_8BIT) - heap_caps_get_free_size(MALLOC_CAP_SPIRAM);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t getFreeMemorySPIRAM(){ //Current Free Memory
|
||||||
|
return heap_caps_get_free_size(MALLOC_CAP_SPIRAM);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
size_t getLargestFreeBlockInternal(){ //Largest Free Block
|
||||||
|
return heap_caps_get_largest_free_block(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t getLargestFreeBlockSPIRAM(){ //Largest Free Block
|
||||||
|
return heap_caps_get_largest_free_block(MALLOC_CAP_SPIRAM);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
size_t getMinEverFreeMemInternal(){ //Min. Ever Free Size
|
||||||
|
return heap_caps_get_minimum_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t getMinEverFreeMemSPIRAM(){ //Min. Ever Free Size
|
||||||
|
return heap_caps_get_minimum_free_size(MALLOC_CAP_SPIRAM);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef USE_HIMEM_IF_AVAILABLE
|
||||||
|
size_t getHimemTotSpace(){
|
||||||
|
return esp_himem_get_phys_size();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t getHimemFreeSpace(){
|
||||||
|
return esp_himem_get_free_size();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t getHimemReservedArea(){
|
||||||
|
return esp_himem_reserved_area_size();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif //DEBUG_ENABLE_SYSINFO
|
||||||
54
code/components/jomjol_helper/esp_sys.h
Normal file
54
code/components/jomjol_helper/esp_sys.h
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../../include/defines.h"
|
||||||
|
|
||||||
|
#ifdef DEBUG_ENABLE_SYSINFO
|
||||||
|
|
||||||
|
#ifndef ESP_SYS_H
|
||||||
|
#define ESP_SYS_H
|
||||||
|
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
|
||||||
|
// Device libraries (ESP-IDF)
|
||||||
|
#include <esp_system.h>
|
||||||
|
#include <esp_spi_flash.h>
|
||||||
|
#include <esp_heap_caps.h>
|
||||||
|
|
||||||
|
// for esp_spiram_get_size
|
||||||
|
extern "C" {
|
||||||
|
#include <esp32/spiram.h>
|
||||||
|
#ifdef USE_HIMEM_IF_AVAILABLE
|
||||||
|
#include <esp32/himem.h>
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void Restart();
|
||||||
|
char *GetChipModel();
|
||||||
|
uint8_t GetChipCoreCount();
|
||||||
|
uint16_t GetChipRevision();
|
||||||
|
uint32_t GetChipfeatures();
|
||||||
|
uint32_t GetFreeHeap();
|
||||||
|
uint32_t GetLeastHeapFreeSinceBoot();
|
||||||
|
|
||||||
|
std::string get_device_info();
|
||||||
|
|
||||||
|
size_t getFreeMemoryInternal();
|
||||||
|
size_t getFreeMemorySPIRAM();
|
||||||
|
size_t getLargestFreeBlockInternal();
|
||||||
|
size_t getLargestFreeBlockSPIRAM();
|
||||||
|
size_t getMinEverFreeMemInternal();
|
||||||
|
size_t getMinEverFreeMemSPIRAM();
|
||||||
|
#ifdef USE_HIMEM_IF_AVAILABLE
|
||||||
|
size_t getHimemTotSpace();
|
||||||
|
size_t getHimemFreeSpace();
|
||||||
|
size_t getHimemReservedArea();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#endif //ESP_SYS_H
|
||||||
|
|
||||||
|
#endif // DEBUG_ENABLE_SYSINFO
|
||||||
115
code/components/jomjol_helper/himem_memory_check.cpp
Normal file
115
code/components/jomjol_helper/himem_memory_check.cpp
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
|
||||||
|
// need [env:esp32cam-dev-himem]
|
||||||
|
//CONFIG_SPIRAM_BANKSWITCH_ENABLE=y
|
||||||
|
//CONFIG_SPIRAM_BANKSWITCH_RESERVE=4
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#include "../../include/defines.h"
|
||||||
|
|
||||||
|
#ifdef DEBUG_HIMEM_MEMORY_CHECK
|
||||||
|
|
||||||
|
#include "himem_memory_check.h"
|
||||||
|
|
||||||
|
//source adapted from : https://github.com/espressif/esp-idf/blob/master/examples/system/himem/main/himem_example_main.c
|
||||||
|
|
||||||
|
|
||||||
|
//Fill memory with pseudo-random data generated from the given seed.
|
||||||
|
//Fills the memory in 32-bit words for speed.
|
||||||
|
static void fill_mem_seed(int seed, void *mem, int len)
|
||||||
|
{
|
||||||
|
uint32_t *p = (uint32_t *)mem;
|
||||||
|
unsigned int rseed = seed ^ 0xa5a5a5a5;
|
||||||
|
for (int i = 0; i < len / 4; i++) {
|
||||||
|
*p++ = rand_r(&rseed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check the memory filled by fill_mem_seed. Returns true if the data matches the data
|
||||||
|
//that fill_mem_seed wrote (when given the same seed).
|
||||||
|
//Returns true if there's a match, false when the region differs from what should be there.
|
||||||
|
static bool check_mem_seed(int seed, void *mem, int len, int phys_addr)
|
||||||
|
{
|
||||||
|
uint32_t *p = (uint32_t *)mem;
|
||||||
|
unsigned int rseed = seed ^ 0xa5a5a5a5;
|
||||||
|
for (int i = 0; i < len / 4; i++) {
|
||||||
|
uint32_t ex = rand_r(&rseed);
|
||||||
|
if (ex != *p) {
|
||||||
|
//printf("check_mem_seed: %x has 0x%08"PRIx32" expected 0x%08"PRIx32"\n", phys_addr+((char*)p-(char*)mem), *p, ex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Allocate a himem region, fill it with data, check it and release it.
|
||||||
|
static bool test_region(int check_size, int seed)
|
||||||
|
{
|
||||||
|
esp_himem_handle_t mh; //Handle for the address space we're using
|
||||||
|
esp_himem_rangehandle_t rh; //Handle for the actual RAM.
|
||||||
|
bool ret = true;
|
||||||
|
|
||||||
|
//Allocate the memory we're going to check.
|
||||||
|
ESP_ERROR_CHECK(esp_himem_alloc(check_size, &mh));
|
||||||
|
//Allocate a block of address range
|
||||||
|
ESP_ERROR_CHECK(esp_himem_alloc_map_range(ESP_HIMEM_BLKSZ, &rh));
|
||||||
|
for (int i = 0; i < check_size; i += ESP_HIMEM_BLKSZ) {
|
||||||
|
uint32_t *ptr = NULL;
|
||||||
|
//Map in block, write pseudo-random data, unmap block.
|
||||||
|
ESP_ERROR_CHECK(esp_himem_map(mh, rh, i, 0, ESP_HIMEM_BLKSZ, 0, (void**)&ptr));
|
||||||
|
fill_mem_seed(i ^ seed, ptr, ESP_HIMEM_BLKSZ); //
|
||||||
|
ESP_ERROR_CHECK(esp_himem_unmap(rh, ptr, ESP_HIMEM_BLKSZ));
|
||||||
|
}
|
||||||
|
vTaskDelay(5); //give the OS some time to do things so the task watchdog doesn't bark
|
||||||
|
for (int i = 0; i < check_size; i += ESP_HIMEM_BLKSZ) {
|
||||||
|
uint32_t *ptr;
|
||||||
|
//Map in block, check against earlier written pseudo-random data, unmap block.
|
||||||
|
ESP_ERROR_CHECK(esp_himem_map(mh, rh, i, 0, ESP_HIMEM_BLKSZ, 0, (void**)&ptr));
|
||||||
|
if (!check_mem_seed(i ^ seed, ptr, ESP_HIMEM_BLKSZ, i)) {
|
||||||
|
//printf("Error in block %d\n", i / ESP_HIMEM_BLKSZ);
|
||||||
|
ret = false;
|
||||||
|
}
|
||||||
|
ESP_ERROR_CHECK(esp_himem_unmap(rh, ptr, ESP_HIMEM_BLKSZ));
|
||||||
|
if (!ret) break; //don't check rest of blocks if error occurred
|
||||||
|
}
|
||||||
|
//Okay, all done!
|
||||||
|
ESP_ERROR_CHECK(esp_himem_free(mh));
|
||||||
|
ESP_ERROR_CHECK(esp_himem_free_map_range(rh));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::string himem_memory_check()
|
||||||
|
{
|
||||||
|
size_t memcnt=esp_himem_get_phys_size();
|
||||||
|
size_t memfree=esp_himem_get_free_size();
|
||||||
|
|
||||||
|
std::string espInfoResultStr = "";
|
||||||
|
char aMsgBuf[40];
|
||||||
|
|
||||||
|
espInfoResultStr += "Running HIMEM memory check";
|
||||||
|
|
||||||
|
sprintf(aMsgBuf,"Himem has %dKiB of memory", (int)memcnt/1024);
|
||||||
|
espInfoResultStr += std::string(aMsgBuf);
|
||||||
|
|
||||||
|
sprintf(aMsgBuf,"%dKiB of which is free", (int)memfree/1024);
|
||||||
|
espInfoResultStr += std::string(aMsgBuf);
|
||||||
|
|
||||||
|
espInfoResultStr += "\n please wait ....\n";
|
||||||
|
|
||||||
|
//running memory checks
|
||||||
|
//assert(test_region(memfree, 0xaaaa));
|
||||||
|
|
||||||
|
if(test_region(memfree, 0xaaaa)) {
|
||||||
|
espInfoResultStr += "Himem check Failed!\n";
|
||||||
|
} else {
|
||||||
|
espInfoResultStr += "Himem check Done!\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return espInfoResultStr;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endif // DEBUG_HIMEM_MEMORY_CHECK
|
||||||
41
code/components/jomjol_helper/himem_memory_check.h
Normal file
41
code/components/jomjol_helper/himem_memory_check.h
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
|
||||||
|
// need [env:esp32cam-dev-himem]
|
||||||
|
//CONFIG_SPIRAM_BANKSWITCH_ENABLE=y
|
||||||
|
//CONFIG_SPIRAM_BANKSWITCH_RESERVE=4
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../../include/defines.h"
|
||||||
|
|
||||||
|
#ifdef DEBUG_HIMEM_MEMORY_CHECK
|
||||||
|
|
||||||
|
#ifndef HIMEM_MEMORY_CHECK_H
|
||||||
|
#define HIMEM_MEMORY_CHECK_H
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//source : //source : https://github.com/espressif/esp-idf/blob/master/examples/system/himem/main/himem_example_main.c
|
||||||
|
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include "freertos/queue.h"
|
||||||
|
#include "esp_system.h"
|
||||||
|
#include "esp_heap_caps.h"
|
||||||
|
#include "esp32/himem.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include "esp32/himem.h"
|
||||||
|
|
||||||
|
|
||||||
|
std::string himem_memory_check();
|
||||||
|
|
||||||
|
#endif //HIMEM_MEMORY_CHECK_H
|
||||||
|
|
||||||
|
#endif // DEBUG_HIMEM_MEMORY_CHECK
|
||||||
87
code/components/jomjol_helper/perfmon.c
Normal file
87
code/components/jomjol_helper/perfmon.c
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
//source : https://github.com/Carbon225/esp32-perfmon
|
||||||
|
|
||||||
|
#include "../../include/defines.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
ESP32 CPU usage monitor
|
||||||
|
Gives you a rough idea of how the Xtensa cores are utilized.
|
||||||
|
|
||||||
|
Works by attaching idle hooks and measuring how often they get called. The core usage is calculated: usage% = idle ticks since last measurement / expected idle ticks if core were idle * 100%. The expected idle tick count was measured by running an empty program.
|
||||||
|
|
||||||
|
Limitations:
|
||||||
|
Should only be used for user information, not in logic that needs accurate values
|
||||||
|
New IDF versions could optimize performance and therefore introduce an error to usage estimation.
|
||||||
|
When one core is at 100% the other might report a negative value
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
#include "perfmon.h"
|
||||||
|
Call perfmon_start() once
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef DEBUG_ENABLE_PERFMON
|
||||||
|
|
||||||
|
#include "perfmon.h"
|
||||||
|
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include "esp_freertos_hooks.h"
|
||||||
|
#include "sdkconfig.h"
|
||||||
|
|
||||||
|
#include "esp_log.h"
|
||||||
|
static const char *TAG = "perfmon";
|
||||||
|
|
||||||
|
static uint64_t idle0Calls = 0;
|
||||||
|
static uint64_t idle1Calls = 0;
|
||||||
|
|
||||||
|
#if defined(CONFIG_ESP32_DEFAULT_CPU_FREQ_240)
|
||||||
|
static const uint64_t MaxIdleCalls = 1855000;
|
||||||
|
#elif defined(CONFIG_ESP32_DEFAULT_CPU_FREQ_160)
|
||||||
|
static const uint64_t MaxIdleCalls = 1233100;
|
||||||
|
#else
|
||||||
|
#error "Unsupported CPU frequency"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static bool idle_task_0()
|
||||||
|
{
|
||||||
|
idle0Calls += 1;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool idle_task_1()
|
||||||
|
{
|
||||||
|
idle1Calls += 1;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void perfmon_task(void *args)
|
||||||
|
{
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
float idle0 = idle0Calls;
|
||||||
|
float idle1 = idle1Calls;
|
||||||
|
idle0Calls = 0;
|
||||||
|
idle1Calls = 0;
|
||||||
|
|
||||||
|
int cpu0 = 100.f - idle0 / MaxIdleCalls * 100.f;
|
||||||
|
int cpu1 = 100.f - idle1 / MaxIdleCalls * 100.f;
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Core 0 at %d%%", cpu0);
|
||||||
|
ESP_LOGI(TAG, "Core 1 at %d%%", cpu1);
|
||||||
|
// TODO configurable delay
|
||||||
|
vTaskDelay(5000 / portTICK_PERIOD_MS);
|
||||||
|
}
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t perfmon_start()
|
||||||
|
{
|
||||||
|
ESP_ERROR_CHECK(esp_register_freertos_idle_hook_for_cpu(idle_task_0, 0));
|
||||||
|
ESP_ERROR_CHECK(esp_register_freertos_idle_hook_for_cpu(idle_task_1, 1));
|
||||||
|
// TODO calculate optimal stack size
|
||||||
|
xTaskCreate(perfmon_task, "perfmon", 2048, NULL, 1, NULL);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endif // DEBUG_ENABLE_PERFMON
|
||||||
24
code/components/jomjol_helper/perfmon.h
Normal file
24
code/components/jomjol_helper/perfmon.h
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
|
||||||
|
#include "../../include/defines.h"
|
||||||
|
|
||||||
|
#ifdef DEBUG_ENABLE_PERFMON
|
||||||
|
|
||||||
|
#ifndef COMPONENTS_PERFMON_INCLUDE_PERFMON_H_
|
||||||
|
#define COMPONENTS_PERFMON_INCLUDE_PERFMON_H_
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "esp_err.h"
|
||||||
|
|
||||||
|
esp_err_t perfmon_start();
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* COMPONENTS_PERFMON_INCLUDE_PERFMON_H_ */
|
||||||
|
|
||||||
|
#endif //DEBUG_ENABLE_PERFMON
|
||||||
209
code/components/jomjol_image_proc/CAlignAndCutImage.cpp
Normal file
209
code/components/jomjol_image_proc/CAlignAndCutImage.cpp
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
#include "CAlignAndCutImage.h"
|
||||||
|
#include "CRotateImage.h"
|
||||||
|
#include "ClassLogFile.h"
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <esp_log.h>
|
||||||
|
#include "../../include/defines.h"
|
||||||
|
|
||||||
|
static const char* TAG = "c_align_and_cut_image";
|
||||||
|
|
||||||
|
CAlignAndCutImage::CAlignAndCutImage(CImageBasis *_org, CImageBasis *_temp)
|
||||||
|
{
|
||||||
|
rgb_image = _org->rgb_image;
|
||||||
|
channels = _org->channels;
|
||||||
|
width = _org->width;
|
||||||
|
height = _org->height;
|
||||||
|
bpp = _org->bpp;
|
||||||
|
externalImage = true;
|
||||||
|
|
||||||
|
islocked = false;
|
||||||
|
|
||||||
|
ImageTMP = _temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CAlignAndCutImage::GetRefSize(int *ref_dx, int *ref_dy)
|
||||||
|
{
|
||||||
|
ref_dx[0] = t0_dx;
|
||||||
|
ref_dy[0] = t0_dy;
|
||||||
|
ref_dx[1] = t1_dx;
|
||||||
|
ref_dy[1] = t1_dy;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CAlignAndCutImage::Align(RefInfo *_temp1, RefInfo *_temp2)
|
||||||
|
{
|
||||||
|
int dx, dy;
|
||||||
|
int r0_x, r0_y, r1_x, r1_y;
|
||||||
|
bool isSimilar1, isSimilar2;
|
||||||
|
|
||||||
|
CFindTemplate* ft = new CFindTemplate(rgb_image, channels, width, height, bpp);
|
||||||
|
|
||||||
|
r0_x = _temp1->target_x;
|
||||||
|
r0_y = _temp1->target_y;
|
||||||
|
ESP_LOGD(TAG, "Before ft->FindTemplate(_temp1); %s", _temp1->image_file.c_str());
|
||||||
|
isSimilar1 = ft->FindTemplate(_temp1);
|
||||||
|
_temp1->width = ft->tpl_width;
|
||||||
|
_temp1->height = ft->tpl_height;
|
||||||
|
|
||||||
|
r1_x = _temp2->target_x;
|
||||||
|
r1_y = _temp2->target_y;
|
||||||
|
ESP_LOGD(TAG, "Before ft->FindTemplate(_temp2); %s", _temp2->image_file.c_str());
|
||||||
|
isSimilar2 = ft->FindTemplate(_temp2);
|
||||||
|
_temp2->width = ft->tpl_width;
|
||||||
|
_temp2->height = ft->tpl_height;
|
||||||
|
|
||||||
|
delete ft;
|
||||||
|
|
||||||
|
|
||||||
|
dx = _temp1->target_x - _temp1->found_x;
|
||||||
|
dy = _temp1->target_y - _temp1->found_y;
|
||||||
|
|
||||||
|
r0_x += dx;
|
||||||
|
r0_y += dy;
|
||||||
|
|
||||||
|
r1_x += dx;
|
||||||
|
r1_y += dy;
|
||||||
|
|
||||||
|
float w_org, w_ist, d_winkel;
|
||||||
|
|
||||||
|
w_org = atan2(_temp2->found_y - _temp1->found_y, _temp2->found_x - _temp1->found_x);
|
||||||
|
w_ist = atan2(r1_y - r0_y, r1_x - r0_x);
|
||||||
|
|
||||||
|
d_winkel = (w_ist - w_org) * 180 / M_PI;
|
||||||
|
|
||||||
|
/*#ifdef DEBUG_DETAIL_ON
|
||||||
|
std::string zw = "\tdx:\t" + std::to_string(dx) + "\tdy:\t" + std::to_string(dy) + "\td_winkel:\t" + std::to_string(d_winkel);
|
||||||
|
zw = zw + "\tt1_x_y:\t" + std::to_string(_temp1->found_x) + "\t" + std::to_string(_temp1->found_y);
|
||||||
|
zw = zw + "\tpara1_found_min_avg_max_SAD:\t" + std::to_string(_temp1->fastalg_min) + "\t" + std::to_string(_temp1->fastalg_avg) + "\t" + std::to_string(_temp1->fastalg_max) + "\t"+ std::to_string(_temp1->fastalg_SAD);
|
||||||
|
zw = zw + "\tt2_x_y:\t" + std::to_string(_temp2->found_x) + "\t" + std::to_string(_temp2->found_y);
|
||||||
|
zw = zw + "\tpara2_found_min_avg_max:\t" + std::to_string(_temp2->fastalg_min) + "\t" + std::to_string(_temp2->fastalg_avg) + "\t" + std::to_string(_temp2->fastalg_max) + "\t"+ std::to_string(_temp2->fastalg_SAD);
|
||||||
|
LogFile.WriteToDedicatedFile("/sdcard/alignment.txt", zw);
|
||||||
|
#endif*/
|
||||||
|
|
||||||
|
CRotateImage rt(this, ImageTMP);
|
||||||
|
rt.Translate(dx, dy);
|
||||||
|
rt.Rotate(d_winkel, _temp1->target_x, _temp1->target_y);
|
||||||
|
ESP_LOGD(TAG, "Alignment: dx %d - dy %d - rot %f", dx, dy, d_winkel);
|
||||||
|
|
||||||
|
return (isSimilar1 && isSimilar2);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void CAlignAndCutImage::CutAndSave(std::string _template1, int x1, int y1, int dx, int dy)
|
||||||
|
{
|
||||||
|
|
||||||
|
int x2, y2;
|
||||||
|
|
||||||
|
x2 = x1 + dx;
|
||||||
|
y2 = y1 + dy;
|
||||||
|
x2 = std::min(x2, width - 1);
|
||||||
|
y2 = std::min(y2, height - 1);
|
||||||
|
|
||||||
|
dx = x2 - x1;
|
||||||
|
dy = y2 - y1;
|
||||||
|
|
||||||
|
int memsize = dx * dy * channels;
|
||||||
|
uint8_t* odata = (unsigned char*) GET_MEMORY(memsize);
|
||||||
|
|
||||||
|
stbi_uc* p_target;
|
||||||
|
stbi_uc* p_source;
|
||||||
|
|
||||||
|
RGBImageLock();
|
||||||
|
|
||||||
|
for (int x = x1; x < x2; ++x)
|
||||||
|
for (int y = y1; y < y2; ++y)
|
||||||
|
{
|
||||||
|
p_target = odata + (channels * ((y - y1) * dx + (x - x1)));
|
||||||
|
p_source = rgb_image + (channels * (y * width + x));
|
||||||
|
for (int _channels = 0; _channels < channels; ++_channels)
|
||||||
|
p_target[_channels] = p_source[_channels];
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef STBI_ONLY_JPEG
|
||||||
|
stbi_write_jpg(_template1.c_str(), dx, dy, channels, odata, 100);
|
||||||
|
#else
|
||||||
|
stbi_write_bmp(_template1.c_str(), dx, dy, channels, odata);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
RGBImageRelease();
|
||||||
|
|
||||||
|
stbi_image_free(odata);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CAlignAndCutImage::CutAndSave(int x1, int y1, int dx, int dy, CImageBasis *_target)
|
||||||
|
{
|
||||||
|
int x2, y2;
|
||||||
|
|
||||||
|
x2 = x1 + dx;
|
||||||
|
y2 = y1 + dy;
|
||||||
|
x2 = std::min(x2, width - 1);
|
||||||
|
y2 = std::min(y2, height - 1);
|
||||||
|
|
||||||
|
dx = x2 - x1;
|
||||||
|
dy = y2 - y1;
|
||||||
|
|
||||||
|
if ((_target->height != dy) || (_target->width != dx) || (_target->channels != channels))
|
||||||
|
{
|
||||||
|
ESP_LOGD(TAG, "CAlignAndCutImage::CutAndSave - Image size does not match!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t* odata = _target->RGBImageLock();
|
||||||
|
RGBImageLock();
|
||||||
|
|
||||||
|
stbi_uc* p_target;
|
||||||
|
stbi_uc* p_source;
|
||||||
|
|
||||||
|
for (int x = x1; x < x2; ++x)
|
||||||
|
for (int y = y1; y < y2; ++y)
|
||||||
|
{
|
||||||
|
p_target = odata + (channels * ((y - y1) * dx + (x - x1)));
|
||||||
|
p_source = rgb_image + (channels * (y * width + x));
|
||||||
|
for (int _channels = 0; _channels < channels; ++_channels)
|
||||||
|
p_target[_channels] = p_source[_channels];
|
||||||
|
}
|
||||||
|
|
||||||
|
RGBImageRelease();
|
||||||
|
_target->RGBImageRelease();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
CImageBasis* CAlignAndCutImage::CutAndSave(int x1, int y1, int dx, int dy)
|
||||||
|
{
|
||||||
|
int x2, y2;
|
||||||
|
|
||||||
|
x2 = x1 + dx;
|
||||||
|
y2 = y1 + dy;
|
||||||
|
x2 = std::min(x2, width - 1);
|
||||||
|
y2 = std::min(y2, height - 1);
|
||||||
|
|
||||||
|
dx = x2 - x1;
|
||||||
|
dy = y2 - y1;
|
||||||
|
|
||||||
|
int memsize = dx * dy * channels;
|
||||||
|
uint8_t* odata = (unsigned char*)GET_MEMORY(memsize);
|
||||||
|
|
||||||
|
stbi_uc* p_target;
|
||||||
|
stbi_uc* p_source;
|
||||||
|
|
||||||
|
RGBImageLock();
|
||||||
|
|
||||||
|
for (int x = x1; x < x2; ++x)
|
||||||
|
for (int y = y1; y < y2; ++y)
|
||||||
|
{
|
||||||
|
p_target = odata + (channels * ((y - y1) * dx + (x - x1)));
|
||||||
|
p_source = rgb_image + (channels * (y * width + x));
|
||||||
|
for (int _channels = 0; _channels < channels; ++_channels)
|
||||||
|
p_target[_channels] = p_source[_channels];
|
||||||
|
}
|
||||||
|
|
||||||
|
CImageBasis* rs = new CImageBasis(odata, channels, dx, dy, bpp);
|
||||||
|
RGBImageRelease();
|
||||||
|
rs->SetIndepended();
|
||||||
|
return rs;
|
||||||
|
}
|
||||||
27
code/components/jomjol_image_proc/CAlignAndCutImage.h
Normal file
27
code/components/jomjol_image_proc/CAlignAndCutImage.h
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef CALIGNANDCUTIMAGE_H
|
||||||
|
#define CALIGNANDCUTIMAGE_H
|
||||||
|
|
||||||
|
#include "CImageBasis.h"
|
||||||
|
#include "CFindTemplate.h"
|
||||||
|
|
||||||
|
|
||||||
|
class CAlignAndCutImage : public CImageBasis
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
int t0_dx, t0_dy, t1_dx, t1_dy;
|
||||||
|
CImageBasis *ImageTMP;
|
||||||
|
CAlignAndCutImage(std::string _image) : CImageBasis(_image) {ImageTMP = NULL;};
|
||||||
|
CAlignAndCutImage(uint8_t* _rgb_image, int _channels, int _width, int _height, int _bpp) : CImageBasis(_rgb_image, _channels, _width, _height, _bpp) {ImageTMP = NULL;};
|
||||||
|
CAlignAndCutImage(CImageBasis *_org, CImageBasis *_temp);
|
||||||
|
|
||||||
|
bool Align(RefInfo *_temp1, RefInfo *_temp2);
|
||||||
|
// void Align(std::string _template1, int x1, int y1, std::string _template2, int x2, int y2, int deltax = 40, int deltay = 40, std::string imageROI = "");
|
||||||
|
void CutAndSave(std::string _template1, int x1, int y1, int dx, int dy);
|
||||||
|
CImageBasis* CutAndSave(int x1, int y1, int dx, int dy);
|
||||||
|
void CutAndSave(int x1, int y1, int dx, int dy, CImageBasis *_target);
|
||||||
|
void GetRefSize(int *ref_dx, int *ref_dy);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //CALIGNANDCUTIMAGE_H
|
||||||
206
code/components/jomjol_image_proc/CFindTemplate.cpp
Normal file
206
code/components/jomjol_image_proc/CFindTemplate.cpp
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
#include "CFindTemplate.h"
|
||||||
|
|
||||||
|
#include "ClassLogFile.h"
|
||||||
|
#include "Helper.h"
|
||||||
|
#include "../../include/defines.h"
|
||||||
|
|
||||||
|
#include <esp_log.h>
|
||||||
|
|
||||||
|
static const char* TAG = "C FIND TEMPL";
|
||||||
|
|
||||||
|
// #define DEBUG_DETAIL_ON
|
||||||
|
|
||||||
|
|
||||||
|
bool CFindTemplate::FindTemplate(RefInfo *_ref)
|
||||||
|
{
|
||||||
|
uint8_t* rgb_template;
|
||||||
|
|
||||||
|
if (file_size(_ref->image_file.c_str()) == 0) {
|
||||||
|
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, _ref->image_file + " is empty!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
rgb_template = stbi_load(_ref->image_file.c_str(), &tpl_width, &tpl_height, &tpl_bpp, channels);
|
||||||
|
|
||||||
|
if (rgb_template == NULL) {
|
||||||
|
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to load " + _ref->image_file + "! Is it corrupted?");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ESP_LOGD(TAG, "FindTemplate 01");
|
||||||
|
|
||||||
|
int ow, ow_start, ow_stop;
|
||||||
|
int oh, oh_start, oh_stop;
|
||||||
|
|
||||||
|
if (_ref->search_x == 0)
|
||||||
|
{
|
||||||
|
_ref->search_x = width;
|
||||||
|
_ref->found_x = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_ref->search_y == 0)
|
||||||
|
{
|
||||||
|
_ref->search_y = height;
|
||||||
|
_ref->found_y = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ow_start = _ref->target_x - _ref->search_x;
|
||||||
|
ow_start = std::max(ow_start, 0);
|
||||||
|
ow_stop = _ref->target_x + _ref->search_x;
|
||||||
|
if ((ow_stop + tpl_width) > width)
|
||||||
|
ow_stop = width - tpl_width;
|
||||||
|
ow = ow_stop - ow_start + 1;
|
||||||
|
|
||||||
|
oh_start = _ref->target_y - _ref->search_y;
|
||||||
|
oh_start = std::max(oh_start, 0);
|
||||||
|
oh_stop = _ref->target_y + _ref->search_y;
|
||||||
|
if ((oh_stop + tpl_height) > height)
|
||||||
|
oh_stop = height - tpl_height;
|
||||||
|
oh = oh_stop - oh_start + 1;
|
||||||
|
|
||||||
|
float avg, SAD;
|
||||||
|
int min, max;
|
||||||
|
bool isSimilar = false;
|
||||||
|
|
||||||
|
// ESP_LOGD(TAG, "FindTemplate 02");
|
||||||
|
|
||||||
|
if ((_ref->alignment_algo == 2) && (_ref->fastalg_x > -1) && (_ref->fastalg_y > -1)) // für Testzwecke immer Berechnen
|
||||||
|
{
|
||||||
|
isSimilar = CalculateSimularities(rgb_template, _ref->fastalg_x, _ref->fastalg_y, ow, oh, min, avg, max, SAD, _ref->fastalg_SAD, _ref->fastalg_SAD_criteria);
|
||||||
|
/*#ifdef DEBUG_DETAIL_ON
|
||||||
|
std::string zw = "\t" + _ref->image_file + "\tt1_x_y:\t" + std::to_string(_ref->fastalg_x) + "\t" + std::to_string(_ref->fastalg_y);
|
||||||
|
zw = zw + "\tpara1_found_min_avg_max_SAD:\t" + std::to_string(min) + "\t" + std::to_string(avg) + "\t" + std::to_string(max) + "\t"+ std::to_string(SAD);
|
||||||
|
LogFile.WriteToDedicatedFile("/sdcard/alignment.txt", zw);
|
||||||
|
#endif*/
|
||||||
|
}
|
||||||
|
|
||||||
|
// ESP_LOGD(TAG, "FindTemplate 03");
|
||||||
|
|
||||||
|
|
||||||
|
if (isSimilar)
|
||||||
|
{
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Use FastAlignment sucessfull");
|
||||||
|
#endif
|
||||||
|
_ref->found_x = _ref->fastalg_x;
|
||||||
|
_ref->found_y = _ref->fastalg_y;
|
||||||
|
|
||||||
|
stbi_image_free(rgb_template);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ESP_LOGD(TAG, "FindTemplate 04");
|
||||||
|
|
||||||
|
|
||||||
|
double aktSAD;
|
||||||
|
double minSAD = pow(tpl_width * tpl_height * 255, 2);
|
||||||
|
|
||||||
|
RGBImageLock();
|
||||||
|
|
||||||
|
// ESP_LOGD(TAG, "FindTemplate 05");
|
||||||
|
int xouter, youter, tpl_x, tpl_y, _ch;
|
||||||
|
int _anzchannels = channels;
|
||||||
|
if (_ref->alignment_algo == 0) // 0 = "Default" (nur R-Kanal)
|
||||||
|
_anzchannels = 1;
|
||||||
|
|
||||||
|
for (xouter = ow_start; xouter <= ow_stop; xouter++)
|
||||||
|
for (youter = oh_start; youter <= oh_stop; ++youter)
|
||||||
|
{
|
||||||
|
aktSAD = 0;
|
||||||
|
for (tpl_x = 0; tpl_x < tpl_width; tpl_x++)
|
||||||
|
for (tpl_y = 0; tpl_y < tpl_height; tpl_y++)
|
||||||
|
{
|
||||||
|
stbi_uc* p_org = rgb_image + (channels * ((youter + tpl_y) * width + (xouter + tpl_x)));
|
||||||
|
stbi_uc* p_tpl = rgb_template + (channels * (tpl_y * tpl_width + tpl_x));
|
||||||
|
for (_ch = 0; _ch < _anzchannels; ++_ch)
|
||||||
|
{
|
||||||
|
aktSAD += pow(p_tpl[_ch] - p_org[_ch], 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (aktSAD < minSAD)
|
||||||
|
{
|
||||||
|
minSAD = aktSAD;
|
||||||
|
_ref->found_x = xouter;
|
||||||
|
_ref->found_y = youter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ESP_LOGD(TAG, "FindTemplate 06");
|
||||||
|
|
||||||
|
|
||||||
|
if (_ref->alignment_algo == 2)
|
||||||
|
CalculateSimularities(rgb_template, _ref->found_x, _ref->found_y, ow, oh, min, avg, max, SAD, _ref->fastalg_SAD, _ref->fastalg_SAD_criteria);
|
||||||
|
|
||||||
|
|
||||||
|
// ESP_LOGD(TAG, "FindTemplate 07");
|
||||||
|
|
||||||
|
_ref->fastalg_x = _ref->found_x;
|
||||||
|
_ref->fastalg_y = _ref->found_y;
|
||||||
|
_ref->fastalg_min = min;
|
||||||
|
_ref->fastalg_avg = avg;
|
||||||
|
_ref->fastalg_max = max;
|
||||||
|
_ref->fastalg_SAD = SAD;
|
||||||
|
|
||||||
|
|
||||||
|
/*#ifdef DEBUG_DETAIL_ON
|
||||||
|
std::string zw = "\t" + _ref->image_file + "\tt1_x_y:\t" + std::to_string(_ref->fastalg_x) + "\t" + std::to_string(_ref->fastalg_y);
|
||||||
|
zw = zw + "\tpara1_found_min_avg_max_SAD:\t" + std::to_string(min) + "\t" + std::to_string(avg) + "\t" + std::to_string(max) + "\t"+ std::to_string(SAD);
|
||||||
|
LogFile.WriteToDedicatedFile("/sdcard/alignment.txt", zw);
|
||||||
|
#endif*/
|
||||||
|
|
||||||
|
RGBImageRelease();
|
||||||
|
stbi_image_free(rgb_template);
|
||||||
|
|
||||||
|
// ESP_LOGD(TAG, "FindTemplate 08");
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool CFindTemplate::CalculateSimularities(uint8_t* _rgb_tmpl, int _startx, int _starty, int _sizex, int _sizey, int &min, float &avg, int &max, float &SAD, float _SADold, float _SADcrit)
|
||||||
|
{
|
||||||
|
int dif;
|
||||||
|
int minDif = 255;
|
||||||
|
int maxDif = -255;
|
||||||
|
double avgDifSum = 0;
|
||||||
|
long int anz = 0;
|
||||||
|
double aktSAD = 0;
|
||||||
|
|
||||||
|
int xouter, youter, _ch;
|
||||||
|
|
||||||
|
for (xouter = 0; xouter <= _sizex; xouter++)
|
||||||
|
for (youter = 0; youter <= _sizey; ++youter)
|
||||||
|
{
|
||||||
|
stbi_uc* p_org = rgb_image + (channels * ((youter + _starty) * width + (xouter + _startx)));
|
||||||
|
stbi_uc* p_tpl = _rgb_tmpl + (channels * (youter * tpl_width + xouter));
|
||||||
|
for (_ch = 0; _ch < channels; ++_ch)
|
||||||
|
{
|
||||||
|
dif = p_tpl[_ch] - p_org[_ch];
|
||||||
|
aktSAD += pow(p_tpl[_ch] - p_org[_ch], 2);
|
||||||
|
if (dif < minDif) minDif = dif;
|
||||||
|
if (dif > maxDif) maxDif = dif;
|
||||||
|
avgDifSum += dif;
|
||||||
|
anz++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
avg = avgDifSum / anz;
|
||||||
|
min = minDif;
|
||||||
|
max = maxDif;
|
||||||
|
SAD = sqrt(aktSAD) / anz;
|
||||||
|
|
||||||
|
float _SADdif = abs(SAD - _SADold);
|
||||||
|
|
||||||
|
ESP_LOGD(TAG, "Anzahl %ld, avgDifSum %fd, avg %f, SAD_neu: %fd, _SAD_old: %f, _SAD_crit:%f", anz, avgDifSum, avg, SAD, _SADold, _SADdif);
|
||||||
|
|
||||||
|
if (_SADdif <= _SADcrit)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
42
code/components/jomjol_image_proc/CFindTemplate.h
Normal file
42
code/components/jomjol_image_proc/CFindTemplate.h
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef CFINDTEMPLATE_H
|
||||||
|
#define CFINDTEMPLATE_H
|
||||||
|
|
||||||
|
#include "CImageBasis.h"
|
||||||
|
|
||||||
|
struct RefInfo {
|
||||||
|
std::string image_file;
|
||||||
|
int target_x = 0;
|
||||||
|
int target_y = 0;
|
||||||
|
int width = 0;
|
||||||
|
int height = 0;
|
||||||
|
int found_x;
|
||||||
|
int found_y;
|
||||||
|
int search_x;
|
||||||
|
int search_y;
|
||||||
|
int fastalg_x = -1;
|
||||||
|
int fastalg_y = -1;
|
||||||
|
int fastalg_min = -256;
|
||||||
|
float fastalg_avg = -1;
|
||||||
|
int fastalg_max = -1;
|
||||||
|
float fastalg_SAD = -1;
|
||||||
|
float fastalg_SAD_criteria = -1;
|
||||||
|
int alignment_algo = 0; // 0 = "Default" (nur R-Kanal), 1 = "HighAccuracy" (RGB-Kanal), 2 = "Fast" (1.x RGB, dann isSimilar)
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class CFindTemplate : public CImageBasis
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
int tpl_width, tpl_height, tpl_bpp;
|
||||||
|
CFindTemplate(uint8_t* _rgb_image, int _channels, int _width, int _height, int _bpp) : CImageBasis(_rgb_image, _channels, _width, _height, _bpp) {};
|
||||||
|
|
||||||
|
bool FindTemplate(RefInfo *_ref);
|
||||||
|
|
||||||
|
bool CalculateSimularities(uint8_t* _rgb_tmpl, int _startx, int _starty, int _sizex, int _sizey, int &min, float &avg, int &max, float &SAD, float _SADold, float _SADcrit);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //CFINDTEMPLATE_H
|
||||||
673
code/components/jomjol_image_proc/CImageBasis.cpp
Normal file
673
code/components/jomjol_image_proc/CImageBasis.cpp
Normal file
@@ -0,0 +1,673 @@
|
|||||||
|
#include "CImageBasis.h"
|
||||||
|
#include "Helper.h"
|
||||||
|
#include "ClassLogFile.h"
|
||||||
|
#include "server_ota.h"
|
||||||
|
|
||||||
|
#include <esp_log.h>
|
||||||
|
#include "../../include/defines.h"
|
||||||
|
|
||||||
|
#include "esp_system.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
static const char *TAG = "C IMG BASIS";
|
||||||
|
|
||||||
|
bool jpgFileTooLarge = false; // JPG creation verfication
|
||||||
|
|
||||||
|
|
||||||
|
//#define DEBUG_DETAIL_ON
|
||||||
|
|
||||||
|
|
||||||
|
uint8_t * CImageBasis::RGBImageLock(int _waitmaxsec)
|
||||||
|
{
|
||||||
|
if (islocked)
|
||||||
|
{
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
ESP_LOGD(TAG, "Image is locked: sleep for: %ds", _waitmaxsec);
|
||||||
|
#endif
|
||||||
|
TickType_t xDelay;
|
||||||
|
xDelay = 1000 / portTICK_PERIOD_MS;
|
||||||
|
for (int i = 0; i <= _waitmaxsec; ++i)
|
||||||
|
{
|
||||||
|
vTaskDelay( xDelay );
|
||||||
|
if (!islocked)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (islocked)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
return rgb_image;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void CImageBasis::RGBImageRelease()
|
||||||
|
{
|
||||||
|
islocked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
uint8_t * CImageBasis::RGBImageGet()
|
||||||
|
{
|
||||||
|
return rgb_image;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void writejpghelp(void *context, void *data, int size)
|
||||||
|
{
|
||||||
|
// ESP_LOGD(TAG, "Size all: %d, size %d", ((ImageData*)context)->size, size);
|
||||||
|
ImageData* _zw = (ImageData*) context;
|
||||||
|
uint8_t *voidstart = _zw->data;
|
||||||
|
uint8_t *datastart = (uint8_t*) data;
|
||||||
|
|
||||||
|
if ((_zw->size < MAX_JPG_SIZE)) { // Abort copy to prevent buffer overflow
|
||||||
|
voidstart += _zw->size;
|
||||||
|
|
||||||
|
for (int i = 0; i < size; ++i)
|
||||||
|
*(voidstart + i) = *(datastart + i);
|
||||||
|
|
||||||
|
_zw->size += size;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
jpgFileTooLarge = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ImageData* CImageBasis::writeToMemoryAsJPG(const int quality)
|
||||||
|
{
|
||||||
|
ImageData* ii = new ImageData;
|
||||||
|
|
||||||
|
RGBImageLock();
|
||||||
|
stbi_write_jpg_to_func(writejpghelp, ii, width, height, channels, rgb_image, quality);
|
||||||
|
RGBImageRelease();
|
||||||
|
|
||||||
|
if (jpgFileTooLarge) {
|
||||||
|
jpgFileTooLarge = false;
|
||||||
|
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "writeToMemoryAsJPG: Creation aborted! JPG size > preallocated buffer: " + std::to_string(MAX_JPG_SIZE));
|
||||||
|
}
|
||||||
|
return ii;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void CImageBasis::writeToMemoryAsJPG(ImageData* i, const int quality)
|
||||||
|
{
|
||||||
|
ImageData* ii = new ImageData;
|
||||||
|
|
||||||
|
RGBImageLock();
|
||||||
|
stbi_write_jpg_to_func(writejpghelp, ii, width, height, channels, rgb_image, quality);
|
||||||
|
RGBImageRelease();
|
||||||
|
|
||||||
|
if (jpgFileTooLarge) {
|
||||||
|
jpgFileTooLarge = false;
|
||||||
|
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "writeToMemoryAsJPG: Creation aborted! JPG size > preallocated buffer: " + std::to_string(MAX_JPG_SIZE));
|
||||||
|
}
|
||||||
|
memCopy((uint8_t*) ii, (uint8_t*) i, sizeof(ImageData));
|
||||||
|
delete ii;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct SendJPGHTTP
|
||||||
|
{
|
||||||
|
httpd_req_t *req;
|
||||||
|
esp_err_t res;
|
||||||
|
char buf[HTTP_BUFFER_SENT];
|
||||||
|
int size = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
inline void writejpgtohttphelp(void *context, void *data, int size)
|
||||||
|
{
|
||||||
|
SendJPGHTTP* _send = (SendJPGHTTP*) context;
|
||||||
|
if ((_send->size + size) >= HTTP_BUFFER_SENT) // data no longer fits in buffer
|
||||||
|
{
|
||||||
|
if (httpd_resp_send_chunk(_send->req, _send->buf, _send->size) != ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "File sending failed!");
|
||||||
|
_send->res = ESP_FAIL;
|
||||||
|
}
|
||||||
|
_send->size = 0;
|
||||||
|
}
|
||||||
|
std::memcpy((void*) (&(_send->buf[0]) + _send->size), data, size);
|
||||||
|
_send->size+= size;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
esp_err_t CImageBasis::SendJPGtoHTTP(httpd_req_t *_req, const int quality)
|
||||||
|
{
|
||||||
|
SendJPGHTTP ii;
|
||||||
|
ii.req = _req;
|
||||||
|
ii.res = ESP_OK;
|
||||||
|
ii.size = 0;
|
||||||
|
|
||||||
|
RGBImageLock();
|
||||||
|
stbi_write_jpg_to_func(writejpgtohttphelp, &ii, width, height, channels, rgb_image, quality);
|
||||||
|
|
||||||
|
if (ii.size > 0)
|
||||||
|
{
|
||||||
|
if (httpd_resp_send_chunk(_req, (char*) ii.buf, ii.size) != ESP_OK) //still send the rest
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "File sending failed!");
|
||||||
|
ii.res = ESP_FAIL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RGBImageRelease();
|
||||||
|
|
||||||
|
return ii.res;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool CImageBasis::CopyFromMemory(uint8_t* _source, int _size)
|
||||||
|
{
|
||||||
|
int gr = height * width * channels;
|
||||||
|
if (gr != _size) // Size does not fit
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "Cannot copy image from memory - sizes do not match: should be %d, but is %d", _size, gr);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RGBImageLock();
|
||||||
|
memCopy(_source, rgb_image, _size);
|
||||||
|
RGBImageRelease();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
uint8_t CImageBasis::GetPixelColor(int x, int y, int ch)
|
||||||
|
{
|
||||||
|
stbi_uc* p_source;
|
||||||
|
p_source = rgb_image + (channels * (y * width + x));
|
||||||
|
return p_source[ch];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void CImageBasis::memCopy(uint8_t* _source, uint8_t* _target, int _size)
|
||||||
|
{
|
||||||
|
#ifdef _ESP32_PSRAM
|
||||||
|
for (int i = 0; i < _size; ++i)
|
||||||
|
*(_target + i) = *(_source + i);
|
||||||
|
#else
|
||||||
|
memcpy(_target, _source, _size);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool CImageBasis::isInImage(int x, int y)
|
||||||
|
{
|
||||||
|
if ((x < 0) || (x > width - 1))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if ((y < 0) || (y > height- 1))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void CImageBasis::setPixelColor(int x, int y, int r, int g, int b)
|
||||||
|
{
|
||||||
|
stbi_uc* p_source;
|
||||||
|
|
||||||
|
RGBImageLock();
|
||||||
|
p_source = rgb_image + (channels * (y * width + x));
|
||||||
|
p_source[0] = r;
|
||||||
|
if ( channels > 2)
|
||||||
|
{
|
||||||
|
p_source[1] = g;
|
||||||
|
p_source[2] = b;
|
||||||
|
}
|
||||||
|
RGBImageRelease();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void CImageBasis::drawRect(int x, int y, int dx, int dy, int r, int g, int b, int thickness)
|
||||||
|
{
|
||||||
|
int zwx1, zwx2, zwy1, zwy2;
|
||||||
|
int _x, _y, _thick;
|
||||||
|
|
||||||
|
zwx1 = x - thickness + 1;
|
||||||
|
zwx2 = x + dx + thickness - 1;
|
||||||
|
zwy1 = y;
|
||||||
|
zwy2 = y;
|
||||||
|
|
||||||
|
RGBImageLock();
|
||||||
|
|
||||||
|
for (_thick = 0; _thick < thickness; _thick++)
|
||||||
|
for (_x = zwx1; _x <= zwx2; ++_x)
|
||||||
|
for (_y = zwy1; _y <= zwy2; _y++)
|
||||||
|
if (isInImage(_x, _y))
|
||||||
|
setPixelColor(_x, _y - _thick, r, g, b);
|
||||||
|
|
||||||
|
zwx1 = x - thickness + 1;
|
||||||
|
zwx2 = x + dx + thickness - 1;
|
||||||
|
zwy1 = y + dy;
|
||||||
|
zwy2 = y + dy;
|
||||||
|
for (_thick = 0; _thick < thickness; _thick++)
|
||||||
|
for (_x = zwx1; _x <= zwx2; ++_x)
|
||||||
|
for (_y = zwy1; _y <= zwy2; _y++)
|
||||||
|
if (isInImage(_x, _y))
|
||||||
|
setPixelColor(_x, _y + _thick, r, g, b);
|
||||||
|
|
||||||
|
zwx1 = x;
|
||||||
|
zwx2 = x;
|
||||||
|
zwy1 = y;
|
||||||
|
zwy2 = y + dy;
|
||||||
|
for (_thick = 0; _thick < thickness; _thick++)
|
||||||
|
for (_x = zwx1; _x <= zwx2; ++_x)
|
||||||
|
for (_y = zwy1; _y <= zwy2; _y++)
|
||||||
|
if (isInImage(_x, _y))
|
||||||
|
setPixelColor(_x - _thick, _y, r, g, b);
|
||||||
|
|
||||||
|
zwx1 = x + dx;
|
||||||
|
zwx2 = x + dx;
|
||||||
|
zwy1 = y;
|
||||||
|
zwy2 = y + dy;
|
||||||
|
for (_thick = 0; _thick < thickness; _thick++)
|
||||||
|
for (_x = zwx1; _x <= zwx2; ++_x)
|
||||||
|
for (_y = zwy1; _y <= zwy2; _y++)
|
||||||
|
if (isInImage(_x, _y))
|
||||||
|
setPixelColor(_x + _thick, _y, r, g, b);
|
||||||
|
|
||||||
|
RGBImageRelease();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void CImageBasis::drawLine(int x1, int y1, int x2, int y2, int r, int g, int b, int thickness)
|
||||||
|
{
|
||||||
|
int _x, _y, _thick;
|
||||||
|
int _zwy1, _zwy2;
|
||||||
|
thickness = (thickness-1) / 2;
|
||||||
|
|
||||||
|
RGBImageLock();
|
||||||
|
|
||||||
|
for (_thick = 0; _thick <= thickness; ++_thick)
|
||||||
|
for (_x = x1 - _thick; _x <= x2 + _thick; ++_x)
|
||||||
|
{
|
||||||
|
if (x2 == x1)
|
||||||
|
{
|
||||||
|
_zwy1 = y1;
|
||||||
|
_zwy2 = y2;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_zwy1 = (y2 - y1) * (float)(_x - x1) / (float)(x2 - x1) + y1;
|
||||||
|
_zwy2 = (y2 - y1) * (float)(_x + 1 - x1) / (float)(x2 - x1) + y1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (_y = _zwy1 - _thick; _y <= _zwy2 + _thick; _y++)
|
||||||
|
if (isInImage(_x, _y))
|
||||||
|
setPixelColor(_x, _y, r, g, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
RGBImageRelease();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void CImageBasis::drawEllipse(int x1, int y1, int radx, int rady, int r, int g, int b, int thickness)
|
||||||
|
{
|
||||||
|
float deltarad, aktrad;
|
||||||
|
int _thick, _x, _y;
|
||||||
|
int rad = radx;
|
||||||
|
|
||||||
|
if (rady > radx)
|
||||||
|
rad = rady;
|
||||||
|
|
||||||
|
deltarad = 1 / (4 * M_PI * (rad + thickness - 1));
|
||||||
|
|
||||||
|
RGBImageLock();
|
||||||
|
|
||||||
|
for (aktrad = 0; aktrad <= (2 * M_PI); aktrad += deltarad)
|
||||||
|
for (_thick = 0; _thick < thickness; ++_thick)
|
||||||
|
{
|
||||||
|
_x = sin(aktrad) * (radx + _thick) + x1;
|
||||||
|
_y = cos(aktrad) * (rady + _thick) + y1;
|
||||||
|
if (isInImage(_x, _y))
|
||||||
|
setPixelColor(_x, _y, r, g, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
RGBImageRelease();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void CImageBasis::drawCircle(int x1, int y1, int rad, int r, int g, int b, int thickness)
|
||||||
|
{
|
||||||
|
float deltarad, aktrad;
|
||||||
|
int _thick, _x, _y;
|
||||||
|
|
||||||
|
deltarad = 1 / (4 * M_PI * (rad + thickness - 1));
|
||||||
|
|
||||||
|
RGBImageLock();
|
||||||
|
|
||||||
|
for (aktrad = 0; aktrad <= (2 * M_PI); aktrad += deltarad)
|
||||||
|
for (_thick = 0; _thick < thickness; ++_thick)
|
||||||
|
{
|
||||||
|
_x = sin(aktrad) * (rad + _thick) + x1;
|
||||||
|
_y = cos(aktrad) * (rad + _thick) + y1;
|
||||||
|
if (isInImage(_x, _y))
|
||||||
|
setPixelColor(_x, _y, r, g, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
RGBImageRelease();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
CImageBasis::CImageBasis()
|
||||||
|
{
|
||||||
|
externalImage = false;
|
||||||
|
rgb_image = NULL;
|
||||||
|
width = 0;
|
||||||
|
height = 0;
|
||||||
|
channels = 0;
|
||||||
|
islocked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void CImageBasis::CreateEmptyImage(int _width, int _height, int _channels)
|
||||||
|
{
|
||||||
|
bpp = _channels;
|
||||||
|
width = _width;
|
||||||
|
height = _height;
|
||||||
|
channels = _channels;
|
||||||
|
|
||||||
|
RGBImageLock();
|
||||||
|
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
LogFile.WriteHeapInfo("CreateEmptyImage");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int memsize = width * height * channels;
|
||||||
|
rgb_image = (unsigned char*)GET_MEMORY(memsize);
|
||||||
|
|
||||||
|
if (rgb_image == NULL)
|
||||||
|
{
|
||||||
|
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "CreateEmptyImage: Can't allocate enough memory: " + std::to_string(memsize));
|
||||||
|
LogFile.WriteHeapInfo("CreateEmptyImage");
|
||||||
|
RGBImageRelease();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
stbi_uc* p_source;
|
||||||
|
|
||||||
|
for (int x = 0; x < width; ++x)
|
||||||
|
for (int y = 0; y < height; ++y)
|
||||||
|
{
|
||||||
|
p_source = rgb_image + (channels * (y * width + x));
|
||||||
|
for (int _channels = 0; _channels < channels; ++_channels)
|
||||||
|
p_source[_channels] = (uint8_t) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
RGBImageRelease();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void CImageBasis::EmptyImage()
|
||||||
|
{
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
LogFile.WriteHeapInfo("EmptyImage");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
stbi_uc* p_source;
|
||||||
|
|
||||||
|
RGBImageLock();
|
||||||
|
|
||||||
|
for (int x = 0; x < width; ++x)
|
||||||
|
for (int y = 0; y < height; ++y)
|
||||||
|
{
|
||||||
|
p_source = rgb_image + (channels * (y * width + x));
|
||||||
|
for (int _channels = 0; _channels < channels; ++_channels)
|
||||||
|
p_source[_channels] = (uint8_t) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
RGBImageRelease();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void CImageBasis::LoadFromMemory(stbi_uc *_buffer, int len)
|
||||||
|
{
|
||||||
|
RGBImageLock();
|
||||||
|
|
||||||
|
if (rgb_image)
|
||||||
|
stbi_image_free(rgb_image);
|
||||||
|
|
||||||
|
rgb_image = stbi_load_from_memory(_buffer, len, &width, &height, &channels, 3);
|
||||||
|
bpp = channels;
|
||||||
|
ESP_LOGD(TAG, "Image loaded from memory: %d, %d, %d", width, height, channels);
|
||||||
|
|
||||||
|
if ((width * height * channels) == 0)
|
||||||
|
{
|
||||||
|
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Image with size 0 loaded --> reboot to be done! "
|
||||||
|
"Check that your camera module is working and connected properly.");
|
||||||
|
LogFile.WriteHeapInfo("LoadFromMemory");
|
||||||
|
|
||||||
|
doReboot();
|
||||||
|
}
|
||||||
|
RGBImageRelease();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
CImageBasis::CImageBasis(CImageBasis *_copyfrom)
|
||||||
|
{
|
||||||
|
islocked = false;
|
||||||
|
externalImage = false;
|
||||||
|
channels = _copyfrom->channels;
|
||||||
|
width = _copyfrom->width;
|
||||||
|
height = _copyfrom->height;
|
||||||
|
bpp = _copyfrom->bpp;
|
||||||
|
|
||||||
|
RGBImageLock();
|
||||||
|
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
LogFile.WriteHeapInfo("CImageBasis_copyfrom - Start");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int memsize = width * height * channels;
|
||||||
|
rgb_image = (unsigned char*)GET_MEMORY(memsize);
|
||||||
|
|
||||||
|
if (rgb_image == NULL)
|
||||||
|
{
|
||||||
|
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "CImageBasis-Copyfrom: Can't allocate enough memory: " + std::to_string(memsize));
|
||||||
|
LogFile.WriteHeapInfo("CImageBasis-Copyfrom");
|
||||||
|
RGBImageRelease();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
memCopy(_copyfrom->rgb_image, rgb_image, memsize);
|
||||||
|
RGBImageRelease();
|
||||||
|
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
LogFile.WriteHeapInfo("CImageBasis_copyfrom - done");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
CImageBasis::CImageBasis(int _width, int _height, int _channels)
|
||||||
|
{
|
||||||
|
islocked = false;
|
||||||
|
externalImage = false;
|
||||||
|
channels = _channels;
|
||||||
|
width = _width;
|
||||||
|
height = _height;
|
||||||
|
bpp = _channels;
|
||||||
|
|
||||||
|
RGBImageLock();
|
||||||
|
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
LogFile.WriteHeapInfo("CImageBasis_width,height,ch - Start");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int memsize = width * height * channels;
|
||||||
|
rgb_image = (unsigned char*)GET_MEMORY(memsize);
|
||||||
|
|
||||||
|
if (rgb_image == NULL)
|
||||||
|
{
|
||||||
|
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "CImageBasis-width,height,ch: Can't allocate enough memory: " + std::to_string(memsize));
|
||||||
|
LogFile.WriteHeapInfo("CImageBasis-width,height,ch");
|
||||||
|
RGBImageRelease();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RGBImageRelease();
|
||||||
|
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
LogFile.WriteHeapInfo("CImageBasis_width,height,ch - done");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
CImageBasis::CImageBasis(std::string _image)
|
||||||
|
{
|
||||||
|
islocked = false;
|
||||||
|
channels = 3;
|
||||||
|
externalImage = false;
|
||||||
|
filename = _image;
|
||||||
|
|
||||||
|
if (file_size(_image.c_str()) == 0) {
|
||||||
|
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, _image + " is empty!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RGBImageLock();
|
||||||
|
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
LogFile.WriteHeapInfo("CImageBasis_image - Start");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
rgb_image = stbi_load(_image.c_str(), &width, &height, &bpp, channels);
|
||||||
|
|
||||||
|
if (rgb_image == NULL) {
|
||||||
|
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "CImageBasis-image: Failed to load " + _image + "! Is it corrupted?");
|
||||||
|
LogFile.WriteHeapInfo("CImageBasis-image");
|
||||||
|
RGBImageRelease();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RGBImageRelease();
|
||||||
|
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
std::string zw = "CImageBasis after load " + _image;
|
||||||
|
ESP_LOGD(TAG, "%s", zw.c_str());
|
||||||
|
ESP_LOGD(TAG, "w %d, h %d, b %d, c %d", width, height, bpp, channels);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
LogFile.WriteHeapInfo("CImageBasis_image - done");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool CImageBasis::ImageOkay(){
|
||||||
|
return rgb_image != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
CImageBasis::CImageBasis(uint8_t* _rgb_image, int _channels, int _width, int _height, int _bpp)
|
||||||
|
{
|
||||||
|
islocked = false;
|
||||||
|
rgb_image = _rgb_image;
|
||||||
|
channels = _channels;
|
||||||
|
width = _width;
|
||||||
|
height = _height;
|
||||||
|
bpp = _bpp;
|
||||||
|
externalImage = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void CImageBasis::Contrast(float _contrast) //input range [-100..100]
|
||||||
|
{
|
||||||
|
stbi_uc* p_source;
|
||||||
|
|
||||||
|
float contrast = (_contrast/100) + 1; //convert to decimal & shift range: [0..2]
|
||||||
|
float intercept = 128 * (1 - contrast);
|
||||||
|
|
||||||
|
RGBImageLock();
|
||||||
|
|
||||||
|
|
||||||
|
for (int x = 0; x < width; ++x)
|
||||||
|
for (int y = 0; y < height; ++y)
|
||||||
|
{
|
||||||
|
p_source = rgb_image + (channels * (y * width + x));
|
||||||
|
for (int _channels = 0; _channels < channels; ++_channels)
|
||||||
|
p_source[_channels] = (uint8_t) std::min(255, std::max(0, (int) (p_source[_channels] * contrast + intercept)));
|
||||||
|
}
|
||||||
|
|
||||||
|
RGBImageRelease();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
CImageBasis::~CImageBasis()
|
||||||
|
{
|
||||||
|
RGBImageLock();
|
||||||
|
|
||||||
|
if (!externalImage)
|
||||||
|
stbi_image_free(rgb_image);
|
||||||
|
|
||||||
|
RGBImageRelease();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void CImageBasis::SaveToFile(std::string _imageout)
|
||||||
|
{
|
||||||
|
string typ = getFileType(_imageout);
|
||||||
|
|
||||||
|
RGBImageLock();
|
||||||
|
|
||||||
|
if ((typ == "jpg") || (typ == "JPG")) // CAUTION PROBLEMATIC IN ESP32
|
||||||
|
{
|
||||||
|
stbi_write_jpg(_imageout.c_str(), width, height, channels, rgb_image, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef STBI_ONLY_JPEG
|
||||||
|
if ((typ == "bmp") || (typ == "BMP"))
|
||||||
|
{
|
||||||
|
stbi_write_bmp(_imageout.c_str(), width, height, channels, rgb_image);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
RGBImageRelease();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void CImageBasis::Resize(int _new_dx, int _new_dy)
|
||||||
|
{
|
||||||
|
int memsize = _new_dx * _new_dy * channels;
|
||||||
|
uint8_t* odata = (unsigned char*)GET_MEMORY(memsize);
|
||||||
|
|
||||||
|
RGBImageLock();
|
||||||
|
|
||||||
|
stbir_resize_uint8(rgb_image, width, height, 0, odata, _new_dx, _new_dy, 0, channels);
|
||||||
|
stbi_image_free(rgb_image);
|
||||||
|
|
||||||
|
rgb_image = (unsigned char*)GET_MEMORY(memsize);
|
||||||
|
memCopy(odata, rgb_image, memsize);
|
||||||
|
width = _new_dx;
|
||||||
|
height = _new_dy;
|
||||||
|
stbi_image_free(odata);
|
||||||
|
|
||||||
|
RGBImageRelease();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void CImageBasis::Resize(int _new_dx, int _new_dy, CImageBasis *_target)
|
||||||
|
{
|
||||||
|
if ((_target->height != _new_dy) || (_target->width != _new_dx) || (_target->channels != channels))
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "Resize - Target image size does not fit!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RGBImageLock();
|
||||||
|
|
||||||
|
uint8_t* odata = _target->rgb_image;
|
||||||
|
stbir_resize_uint8(rgb_image, width, height, 0, odata, _new_dx, _new_dy, 0, channels);
|
||||||
|
|
||||||
|
RGBImageRelease();
|
||||||
|
}
|
||||||
|
|
||||||
92
code/components/jomjol_image_proc/CImageBasis.h
Normal file
92
code/components/jomjol_image_proc/CImageBasis.h
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef CIMAGEBASIS_H
|
||||||
|
#define CIMAGEBASIS_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string>
|
||||||
|
#include <esp_http_server.h>
|
||||||
|
|
||||||
|
#include "../../include/defines.h"
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#include "stb_image.h"
|
||||||
|
#include "stb_image_write.h"
|
||||||
|
#include "stb_image_resize.h"
|
||||||
|
|
||||||
|
#include "esp_heap_caps.h"
|
||||||
|
|
||||||
|
struct ImageData
|
||||||
|
{
|
||||||
|
uint8_t data[MAX_JPG_SIZE];
|
||||||
|
size_t size = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class CImageBasis
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
bool externalImage;
|
||||||
|
std::string filename;
|
||||||
|
|
||||||
|
void memCopy(uint8_t* _source, uint8_t* _target, int _size);
|
||||||
|
bool isInImage(int x, int y);
|
||||||
|
|
||||||
|
bool islocked;
|
||||||
|
|
||||||
|
public:
|
||||||
|
uint8_t* rgb_image;
|
||||||
|
int channels;
|
||||||
|
int width, height, bpp;
|
||||||
|
|
||||||
|
uint8_t * RGBImageLock(int _waitmaxsec = 60);
|
||||||
|
void RGBImageRelease();
|
||||||
|
uint8_t * RGBImageGet();
|
||||||
|
|
||||||
|
int getWidth(){return this->width;};
|
||||||
|
int getHeight(){return this->height;};
|
||||||
|
int getChannels(){return this->channels;};
|
||||||
|
void drawRect(int x, int y, int dx, int dy, int r = 255, int g = 255, int b = 255, int thickness = 1);
|
||||||
|
void drawLine(int x1, int y1, int x2, int y2, int r, int g, int b, int thickness = 1);
|
||||||
|
void drawCircle(int x1, int y1, int rad, int r, int g, int b, int thickness = 1);
|
||||||
|
void drawEllipse(int x1, int y1, int radx, int rady, int r, int g, int b, int thickness = 1);
|
||||||
|
|
||||||
|
void setPixelColor(int x, int y, int r, int g, int b);
|
||||||
|
void Contrast(float _contrast);
|
||||||
|
bool ImageOkay();
|
||||||
|
bool CopyFromMemory(uint8_t* _source, int _size);
|
||||||
|
|
||||||
|
void SetIndepended(){externalImage = false;};
|
||||||
|
|
||||||
|
void CreateEmptyImage(int _width, int _height, int _channels);
|
||||||
|
void EmptyImage();
|
||||||
|
|
||||||
|
|
||||||
|
CImageBasis();
|
||||||
|
CImageBasis(std::string _image);
|
||||||
|
CImageBasis(uint8_t* _rgb_image, int _channels, int _width, int _height, int _bpp);
|
||||||
|
CImageBasis(int _width, int _height, int _channels);
|
||||||
|
CImageBasis(CImageBasis *_copyfrom);
|
||||||
|
|
||||||
|
void Resize(int _new_dx, int _new_dy);
|
||||||
|
void Resize(int _new_dx, int _new_dy, CImageBasis *_target);
|
||||||
|
|
||||||
|
void LoadFromMemory(stbi_uc *_buffer, int len);
|
||||||
|
|
||||||
|
ImageData* writeToMemoryAsJPG(const int quality = 90);
|
||||||
|
void writeToMemoryAsJPG(ImageData* ii, const int quality = 90);
|
||||||
|
|
||||||
|
esp_err_t SendJPGtoHTTP(httpd_req_t *req, const int quality = 90);
|
||||||
|
|
||||||
|
uint8_t GetPixelColor(int x, int y, int ch);
|
||||||
|
|
||||||
|
~CImageBasis();
|
||||||
|
|
||||||
|
void SaveToFile(std::string _imageout);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif //CIMAGEBASIS_H
|
||||||
|
|
||||||
7
code/components/jomjol_image_proc/CMakeLists.txt
Normal file
7
code/components/jomjol_image_proc/CMakeLists.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
FILE(GLOB_RECURSE app_sources ${CMAKE_CURRENT_SOURCE_DIR}/*.*)
|
||||||
|
|
||||||
|
idf_component_register(SRCS ${app_sources}
|
||||||
|
INCLUDE_DIRS "."
|
||||||
|
REQUIRES jomjol_helper jomjol_logfile esp_http_server jomjol_fileserver_ota)
|
||||||
|
|
||||||
|
|
||||||
349
code/components/jomjol_image_proc/CRotateImage.cpp
Normal file
349
code/components/jomjol_image_proc/CRotateImage.cpp
Normal file
@@ -0,0 +1,349 @@
|
|||||||
|
#include "CRotateImage.h"
|
||||||
|
|
||||||
|
|
||||||
|
CRotateImage::CRotateImage(CImageBasis *_org, CImageBasis *_temp, bool _flip)
|
||||||
|
{
|
||||||
|
rgb_image = _org->rgb_image;
|
||||||
|
channels = _org->channels;
|
||||||
|
width = _org->width;
|
||||||
|
height = _org->height;
|
||||||
|
bpp = _org->bpp;
|
||||||
|
externalImage = true;
|
||||||
|
ImageTMP = _temp;
|
||||||
|
ImageOrg = _org;
|
||||||
|
islocked = false;
|
||||||
|
doflip = _flip;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CRotateImage::Mirror(){
|
||||||
|
int memsize = width * height * channels;
|
||||||
|
uint8_t* odata;
|
||||||
|
if (ImageTMP)
|
||||||
|
{
|
||||||
|
odata = ImageTMP->RGBImageLock();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
odata = (unsigned char*)GET_MEMORY(memsize);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int x_source, y_source;
|
||||||
|
stbi_uc* p_target;
|
||||||
|
stbi_uc* p_source;
|
||||||
|
|
||||||
|
RGBImageLock();
|
||||||
|
|
||||||
|
for (int x = 0; x < width; ++x)
|
||||||
|
for (int y = 0; y < height; ++y)
|
||||||
|
{
|
||||||
|
p_target = odata + (channels * (y * width + x));
|
||||||
|
|
||||||
|
x_source = width - x;
|
||||||
|
y_source = y;
|
||||||
|
|
||||||
|
p_source = rgb_image + (channels * (y_source * width + x_source));
|
||||||
|
for (int _channels = 0; _channels < channels; ++_channels)
|
||||||
|
p_target[_channels] = p_source[_channels];
|
||||||
|
}
|
||||||
|
|
||||||
|
// memcpy(rgb_image, odata, memsize);
|
||||||
|
memCopy(odata, rgb_image, memsize);
|
||||||
|
if (!ImageTMP)
|
||||||
|
stbi_image_free(odata);
|
||||||
|
|
||||||
|
if (ImageTMP)
|
||||||
|
ImageTMP->RGBImageRelease();
|
||||||
|
|
||||||
|
RGBImageRelease();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CRotateImage::Rotate(float _angle, int _centerx, int _centery)
|
||||||
|
{
|
||||||
|
int org_width, org_height;
|
||||||
|
float m[2][3];
|
||||||
|
|
||||||
|
float x_center = _centerx;
|
||||||
|
float y_center = _centery;
|
||||||
|
_angle = _angle / 180 * M_PI;
|
||||||
|
|
||||||
|
if (doflip)
|
||||||
|
{
|
||||||
|
org_width = width;
|
||||||
|
org_height = height;
|
||||||
|
height = org_width;
|
||||||
|
width = org_height;
|
||||||
|
x_center = x_center - (org_width/2) + (org_height/2);
|
||||||
|
y_center = y_center + (org_width/2) - (org_height/2);
|
||||||
|
if (ImageOrg)
|
||||||
|
{
|
||||||
|
ImageOrg->height = height;
|
||||||
|
ImageOrg->width = width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
org_width = width;
|
||||||
|
org_height = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
m[0][0] = cos(_angle);
|
||||||
|
m[0][1] = sin(_angle);
|
||||||
|
m[0][2] = (1 - m[0][0]) * x_center - m[0][1] * y_center;
|
||||||
|
|
||||||
|
m[1][0] = -m[0][1];
|
||||||
|
m[1][1] = m[0][0];
|
||||||
|
m[1][2] = m[0][1] * x_center + (1 - m[0][0]) * y_center;
|
||||||
|
|
||||||
|
if (doflip)
|
||||||
|
{
|
||||||
|
m[0][2] = m[0][2] + (org_width/2) - (org_height/2);
|
||||||
|
m[1][2] = m[1][2] - (org_width/2) + (org_height/2);
|
||||||
|
}
|
||||||
|
|
||||||
|
int memsize = width * height * channels;
|
||||||
|
uint8_t* odata;
|
||||||
|
if (ImageTMP)
|
||||||
|
{
|
||||||
|
odata = ImageTMP->RGBImageLock();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
odata = (unsigned char*)GET_MEMORY(memsize);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int x_source, y_source;
|
||||||
|
stbi_uc* p_target;
|
||||||
|
stbi_uc* p_source;
|
||||||
|
|
||||||
|
RGBImageLock();
|
||||||
|
|
||||||
|
for (int x = 0; x < width; ++x)
|
||||||
|
for (int y = 0; y < height; ++y)
|
||||||
|
{
|
||||||
|
p_target = odata + (channels * (y * width + x));
|
||||||
|
|
||||||
|
x_source = int(m[0][0] * x + m[0][1] * y);
|
||||||
|
y_source = int(m[1][0] * x + m[1][1] * y);
|
||||||
|
|
||||||
|
x_source += int(m[0][2]);
|
||||||
|
y_source += int(m[1][2]);
|
||||||
|
|
||||||
|
if ((x_source >= 0) && (x_source < org_width) && (y_source >= 0) && (y_source < org_height))
|
||||||
|
{
|
||||||
|
p_source = rgb_image + (channels * (y_source * org_width + x_source));
|
||||||
|
for (int _channels = 0; _channels < channels; ++_channels)
|
||||||
|
p_target[_channels] = p_source[_channels];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (int _channels = 0; _channels < channels; ++_channels)
|
||||||
|
p_target[_channels] = 255;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// memcpy(rgb_image, odata, memsize);
|
||||||
|
memCopy(odata, rgb_image, memsize);
|
||||||
|
|
||||||
|
if (!ImageTMP)
|
||||||
|
{
|
||||||
|
stbi_image_free(odata);
|
||||||
|
}
|
||||||
|
if (ImageTMP)
|
||||||
|
ImageTMP->RGBImageRelease();
|
||||||
|
|
||||||
|
RGBImageRelease();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void CRotateImage::RotateAntiAliasing(float _angle, int _centerx, int _centery)
|
||||||
|
{
|
||||||
|
int org_width, org_height;
|
||||||
|
float m[2][3];
|
||||||
|
|
||||||
|
float x_center = _centerx;
|
||||||
|
float y_center = _centery;
|
||||||
|
_angle = _angle / 180 * M_PI;
|
||||||
|
|
||||||
|
if (doflip)
|
||||||
|
{
|
||||||
|
org_width = width;
|
||||||
|
org_height = height;
|
||||||
|
height = org_width;
|
||||||
|
width = org_height;
|
||||||
|
x_center = x_center - (org_width/2) + (org_height/2);
|
||||||
|
y_center = y_center + (org_width/2) - (org_height/2);
|
||||||
|
if (ImageOrg)
|
||||||
|
{
|
||||||
|
ImageOrg->height = height;
|
||||||
|
ImageOrg->width = width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
org_width = width;
|
||||||
|
org_height = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
m[0][0] = cos(_angle);
|
||||||
|
m[0][1] = sin(_angle);
|
||||||
|
m[0][2] = (1 - m[0][0]) * x_center - m[0][1] * y_center;
|
||||||
|
|
||||||
|
m[1][0] = -m[0][1];
|
||||||
|
m[1][1] = m[0][0];
|
||||||
|
m[1][2] = m[0][1] * x_center + (1 - m[0][0]) * y_center;
|
||||||
|
|
||||||
|
if (doflip)
|
||||||
|
{
|
||||||
|
m[0][2] = m[0][2] + (org_width/2) - (org_height/2);
|
||||||
|
m[1][2] = m[1][2] - (org_width/2) + (org_height/2);
|
||||||
|
}
|
||||||
|
|
||||||
|
int memsize = width * height * channels;
|
||||||
|
uint8_t* odata;
|
||||||
|
if (ImageTMP)
|
||||||
|
{
|
||||||
|
odata = ImageTMP->RGBImageLock();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
odata = (unsigned char*)GET_MEMORY(memsize);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int x_source_1, y_source_1, x_source_2, y_source_2;
|
||||||
|
float x_source, y_source;
|
||||||
|
float quad_ul, quad_ur, quad_ol, quad_or;
|
||||||
|
stbi_uc* p_target;
|
||||||
|
stbi_uc *p_source_ul, *p_source_ur, *p_source_ol, *p_source_or;
|
||||||
|
|
||||||
|
RGBImageLock();
|
||||||
|
|
||||||
|
for (int x = 0; x < width; ++x)
|
||||||
|
for (int y = 0; y < height; ++y)
|
||||||
|
{
|
||||||
|
p_target = odata + (channels * (y * width + x));
|
||||||
|
|
||||||
|
x_source = (m[0][0] * x + m[0][1] * y);
|
||||||
|
y_source = (m[1][0] * x + m[1][1] * y);
|
||||||
|
|
||||||
|
x_source += (m[0][2]);
|
||||||
|
y_source += (m[1][2]);
|
||||||
|
|
||||||
|
x_source_1 = (int)x_source;
|
||||||
|
x_source_2 = x_source_1 + 1;
|
||||||
|
y_source_1 = (int)y_source;
|
||||||
|
y_source_2 = y_source_1 + 1;
|
||||||
|
|
||||||
|
quad_ul = (x_source_2 - x_source) * (y_source_2 - y_source);
|
||||||
|
quad_ur = (1- (x_source_2 - x_source)) * (y_source_2 - y_source);
|
||||||
|
quad_or = (x_source_2 - x_source) * (1-(y_source_2 - y_source));
|
||||||
|
quad_ol = (1- (x_source_2 - x_source)) * (1-(y_source_2 - y_source));
|
||||||
|
|
||||||
|
|
||||||
|
if ((x_source_1 >= 0) && (x_source_2 < org_width) && (y_source_1 >= 0) && (y_source_2 < org_height))
|
||||||
|
{
|
||||||
|
p_source_ul = rgb_image + (channels * (y_source_1 * org_width + x_source_1));
|
||||||
|
p_source_ur = rgb_image + (channels * (y_source_1 * org_width + x_source_2));
|
||||||
|
p_source_or = rgb_image + (channels * (y_source_2 * org_width + x_source_1));
|
||||||
|
p_source_ol = rgb_image + (channels * (y_source_2 * org_width + x_source_2));
|
||||||
|
for (int _channels = 0; _channels < channels; ++_channels)
|
||||||
|
{
|
||||||
|
p_target[_channels] = (int)((float)p_source_ul[_channels] * quad_ul
|
||||||
|
+ (float)p_source_ur[_channels] * quad_ur
|
||||||
|
+ (float)p_source_or[_channels] * quad_or
|
||||||
|
+ (float)p_source_ol[_channels] * quad_ol);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (int _channels = 0; _channels < channels; ++_channels)
|
||||||
|
p_target[_channels] = 255;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// memcpy(rgb_image, odata, memsize);
|
||||||
|
memCopy(odata, rgb_image, memsize);
|
||||||
|
|
||||||
|
if (!ImageTMP)
|
||||||
|
{
|
||||||
|
stbi_image_free(odata);
|
||||||
|
}
|
||||||
|
if (ImageTMP)
|
||||||
|
ImageTMP->RGBImageRelease();
|
||||||
|
|
||||||
|
RGBImageRelease();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void CRotateImage::Rotate(float _angle)
|
||||||
|
{
|
||||||
|
// ESP_LOGD(TAG, "width %d, height %d", width, height);
|
||||||
|
Rotate(_angle, width / 2, height / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CRotateImage::RotateAntiAliasing(float _angle)
|
||||||
|
{
|
||||||
|
// ESP_LOGD(TAG, "width %d, height %d", width, height);
|
||||||
|
RotateAntiAliasing(_angle, width / 2, height / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CRotateImage::Translate(int _dx, int _dy)
|
||||||
|
{
|
||||||
|
int memsize = width * height * channels;
|
||||||
|
uint8_t* odata;
|
||||||
|
if (ImageTMP)
|
||||||
|
{
|
||||||
|
odata = ImageTMP->RGBImageLock();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
odata = (unsigned char*)GET_MEMORY(memsize);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
int x_source, y_source;
|
||||||
|
stbi_uc* p_target;
|
||||||
|
stbi_uc* p_source;
|
||||||
|
|
||||||
|
RGBImageLock();
|
||||||
|
|
||||||
|
for (int x = 0; x < width; ++x)
|
||||||
|
for (int y = 0; y < height; ++y)
|
||||||
|
{
|
||||||
|
p_target = odata + (channels * (y * width + x));
|
||||||
|
|
||||||
|
x_source = x - _dx;
|
||||||
|
y_source = y - _dy;
|
||||||
|
|
||||||
|
if ((x_source >= 0) && (x_source < width) && (y_source >= 0) && (y_source < height))
|
||||||
|
{
|
||||||
|
p_source = rgb_image + (channels * (y_source * width + x_source));
|
||||||
|
for (int _channels = 0; _channels < channels; ++_channels)
|
||||||
|
p_target[_channels] = p_source[_channels];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (int _channels = 0; _channels < channels; ++_channels)
|
||||||
|
p_target[_channels] = 255;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// memcpy(rgb_image, odata, memsize);
|
||||||
|
memCopy(odata, rgb_image, memsize);
|
||||||
|
if (!ImageTMP)
|
||||||
|
{
|
||||||
|
stbi_image_free(odata);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImageTMP)
|
||||||
|
{
|
||||||
|
ImageTMP->RGBImageRelease();
|
||||||
|
}
|
||||||
|
RGBImageRelease();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
28
code/components/jomjol_image_proc/CRotateImage.h
Normal file
28
code/components/jomjol_image_proc/CRotateImage.h
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef CROTATEIMAGE_H
|
||||||
|
#define CROTATEIMAGE_H
|
||||||
|
|
||||||
|
#include "CImageBasis.h"
|
||||||
|
|
||||||
|
|
||||||
|
class CRotateImage: public CImageBasis
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CImageBasis *ImageTMP, *ImageOrg;
|
||||||
|
bool doflip;
|
||||||
|
CRotateImage(std::string _image, bool _flip = false) : CImageBasis(_image) {ImageTMP = NULL; ImageOrg = NULL; doflip = _flip;};
|
||||||
|
CRotateImage(uint8_t* _rgb_image, int _channels, int _width, int _height, int _bpp, bool _flip = false) : CImageBasis(_rgb_image, _channels, _width, _height, _bpp) {ImageTMP = NULL; ImageOrg = NULL; doflip = _flip;};
|
||||||
|
CRotateImage(CImageBasis *_org, CImageBasis *_temp, bool _flip = false);
|
||||||
|
|
||||||
|
void Rotate(float _angle);
|
||||||
|
void RotateAntiAliasing(float _angle);
|
||||||
|
|
||||||
|
void Rotate(float _angle, int _centerx, int _centery);
|
||||||
|
void RotateAntiAliasing(float _angle, int _centerx, int _centery);
|
||||||
|
|
||||||
|
void Translate(int _dx, int _dy);
|
||||||
|
void Mirror();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //CROTATEIMAGE_H
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include "../../include/defines.h"
|
||||||
|
|
||||||
#define STB_IMAGE_IMPLEMENTATION
|
#define STB_IMAGE_IMPLEMENTATION
|
||||||
#include "stb_image.h"
|
#include "stb_image.h"
|
||||||
|
|
||||||
7
code/components/jomjol_influxdb/CMakeLists.txt
Normal file
7
code/components/jomjol_influxdb/CMakeLists.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
135
code/components/jomjol_influxdb/interface_influxdb.cpp
Normal file
135
code/components/jomjol_influxdb/interface_influxdb.cpp
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
#ifdef ENABLE_INFLUXDB
|
||||||
|
#include "interface_influxdb.h"
|
||||||
|
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include <time.h>
|
||||||
|
#include "ClassLogFile.h"
|
||||||
|
#include "esp_http_client.h"
|
||||||
|
#include "../../include/defines.h"
|
||||||
|
|
||||||
|
|
||||||
|
static const char *TAG = "INFLUXDB";
|
||||||
|
|
||||||
|
std::string _influxDBURI;
|
||||||
|
std::string _influxDBDatabase;
|
||||||
|
std::string _influxDBMeasurement;
|
||||||
|
std::string _influxDBUser;
|
||||||
|
std::string _influxDBPassword;
|
||||||
|
|
||||||
|
static esp_err_t http_event_handler(esp_http_client_event_t *evt)
|
||||||
|
{
|
||||||
|
switch(evt->event_id)
|
||||||
|
{
|
||||||
|
case HTTP_EVENT_ERROR:
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "HTTP Client Error encountered");
|
||||||
|
break;
|
||||||
|
case HTTP_EVENT_ON_CONNECTED:
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "HTTP Client Error encountered");
|
||||||
|
ESP_LOGI(TAG, "HTTP Client Connected");
|
||||||
|
break;
|
||||||
|
case HTTP_EVENT_HEADERS_SENT:
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "HTTP Client sent all request headers");
|
||||||
|
break;
|
||||||
|
case HTTP_EVENT_ON_HEADER:
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Header: key=" + std::string(evt->header_key) + ", value=" + std::string(evt->header_value));
|
||||||
|
break;
|
||||||
|
case HTTP_EVENT_ON_DATA:
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "HTTP Client data recevied: len=" + std::to_string(evt->data_len));
|
||||||
|
break;
|
||||||
|
case HTTP_EVENT_ON_FINISH:
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "HTTP Client finished");
|
||||||
|
break;
|
||||||
|
case HTTP_EVENT_DISCONNECTED:
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "HTTP Client Disconnected");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InfluxDBPublish(std::string _key, std::string _content, std::string _timestamp) {
|
||||||
|
char response_buffer[MAX_HTTP_OUTPUT_BUFFER] = {0};
|
||||||
|
esp_http_client_config_t http_config = {
|
||||||
|
.user_agent = "ESP32 Meter reader",
|
||||||
|
.method = HTTP_METHOD_POST,
|
||||||
|
.event_handler = http_event_handler,
|
||||||
|
.buffer_size = MAX_HTTP_OUTPUT_BUFFER,
|
||||||
|
.user_data = response_buffer
|
||||||
|
};
|
||||||
|
|
||||||
|
if (_influxDBUser.length() && _influxDBPassword.length()){
|
||||||
|
http_config.username = _influxDBUser.c_str();
|
||||||
|
http_config.password = _influxDBPassword.c_str();
|
||||||
|
http_config.auth_type = HTTP_AUTH_TYPE_BASIC;
|
||||||
|
}
|
||||||
|
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "InfluxDBPublish - Key: " + _key + ", Content: " + _content + ", Timestamp: " + _timestamp);
|
||||||
|
|
||||||
|
// Format: #define PREVALUE_TIME_FORMAT_OUTPUT "%Y-%m-%dT%H:%M:%S%z"
|
||||||
|
struct tm tm;
|
||||||
|
strptime(_timestamp.c_str(), PREVALUE_TIME_FORMAT_OUTPUT, &tm);
|
||||||
|
time_t t = mktime(&tm); // t is now your desired time_t
|
||||||
|
|
||||||
|
struct tm * ptm;
|
||||||
|
ptm = gmtime ( &t );
|
||||||
|
time_t utc = mktime(ptm);
|
||||||
|
|
||||||
|
// time_t now;
|
||||||
|
// time(&now);
|
||||||
|
char nowTimestamp[21];
|
||||||
|
// pad with zeroes to get nanoseconds
|
||||||
|
// sprintf(nowTimestamp,"%ld000000000", (long) now);
|
||||||
|
// sprintf(nowTimestamp,"%ld000000000", (long) t); // Localtime
|
||||||
|
sprintf(nowTimestamp,"%ld000000000", (long) utc); // UTC
|
||||||
|
|
||||||
|
|
||||||
|
// LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Test Time Conversion - t: " + std::to_string(t) + ", utc: " + std::to_string(utc));
|
||||||
|
// LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Test Time Conversion - now: " + std::to_string(now) + ", timestamp: " + std::to_string(t) + "(correct time not used yet)");
|
||||||
|
|
||||||
|
std::string payload = _influxDBMeasurement + " " + _key + "=" + _content + " " + nowTimestamp;
|
||||||
|
payload.shrink_to_fit();
|
||||||
|
|
||||||
|
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "sending line to influxdb:" + payload);
|
||||||
|
|
||||||
|
|
||||||
|
// use the default retention policy of the database
|
||||||
|
std::string apiURI = _influxDBURI + "/api/v2/write?bucket=" + _influxDBDatabase + "/";
|
||||||
|
apiURI.shrink_to_fit();
|
||||||
|
http_config.url = apiURI.c_str();
|
||||||
|
|
||||||
|
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "API URI: " + apiURI);
|
||||||
|
|
||||||
|
esp_http_client_handle_t http_client = esp_http_client_init(&http_config);
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "client is initialized");
|
||||||
|
|
||||||
|
esp_http_client_set_header(http_client, "Content-Type", "text/plain");
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "header is set");
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(esp_http_client_set_post_field(http_client, payload.c_str(), payload.length()));
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "post payload is set");
|
||||||
|
|
||||||
|
esp_err_t err = ESP_ERROR_CHECK_WITHOUT_ABORT(esp_http_client_perform(http_client));
|
||||||
|
|
||||||
|
if( err == ESP_OK ) {
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "HTTP request was performed");
|
||||||
|
int status_code = esp_http_client_get_status_code(http_client);
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "HTTP status code" + std::to_string(status_code));
|
||||||
|
} else {
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "HTTP request failed");
|
||||||
|
}
|
||||||
|
esp_http_client_cleanup(http_client);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void InfluxDBInit(std::string _uri, std::string _database, std::string _measurement, std::string _user, std::string _password){
|
||||||
|
_influxDBURI = _uri;
|
||||||
|
_influxDBDatabase = _database;
|
||||||
|
_influxDBMeasurement = _measurement;
|
||||||
|
_influxDBUser = _user;
|
||||||
|
_influxDBPassword = _password;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void InfluxDBdestroy() {
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //ENABLE_INFLUXDB
|
||||||
17
code/components/jomjol_influxdb/interface_influxdb.h
Normal file
17
code/components/jomjol_influxdb/interface_influxdb.h
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#ifdef ENABLE_INFLUXDB
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#ifndef INTERFACE_INFLUXDB_H
|
||||||
|
#define INTERFACE_INFLUXDB_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <map>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
void InfluxDBInit(std::string _influxDBURI, std::string _database, std::string _measurement, std::string _user, std::string _password);
|
||||||
|
void InfluxDBdestroy();
|
||||||
|
|
||||||
|
void InfluxDBPublish(std::string _key, std::string _content, std::string _timestamp);
|
||||||
|
|
||||||
|
#endif //INTERFACE_INFLUXDB_H
|
||||||
|
#endif //ENABLE_INFLUXDB
|
||||||
7
code/components/jomjol_logfile/CMakeLists.txt
Normal file
7
code/components/jomjol_logfile/CMakeLists.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
FILE(GLOB_RECURSE app_sources ${CMAKE_CURRENT_SOURCE_DIR}/*.*)
|
||||||
|
|
||||||
|
idf_component_register(SRCS ${app_sources}
|
||||||
|
INCLUDE_DIRS "."
|
||||||
|
REQUIRES jomjol_time_sntp jomjol_helper)
|
||||||
|
|
||||||
|
|
||||||
410
code/components/jomjol_logfile/ClassLogFile.cpp
Normal file
410
code/components/jomjol_logfile/ClassLogFile.cpp
Normal file
@@ -0,0 +1,410 @@
|
|||||||
|
#include "ClassLogFile.h"
|
||||||
|
#include "time_sntp.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
#include <dirent.h>
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "Helper.h"
|
||||||
|
#include "../../include/defines.h"
|
||||||
|
|
||||||
|
static const char *TAG = "LOGFILE";
|
||||||
|
|
||||||
|
ClassLogFile LogFile("/sdcard/log/message", "log_%Y-%m-%d.txt", "/sdcard/log/data", "data_%Y-%m-%d.csv");
|
||||||
|
|
||||||
|
|
||||||
|
void ClassLogFile::WriteHeapInfo(std::string _id)
|
||||||
|
{
|
||||||
|
if (loglevel >= ESP_LOG_DEBUG) {
|
||||||
|
std::string _zw = _id + "\t" + getESPHeapInfo();
|
||||||
|
WriteToFile(ESP_LOG_DEBUG, "HEAP", _zw);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ClassLogFile::WriteToData(std::string _timestamp, std::string _name, std::string _ReturnRawValue, std::string _ReturnValue, std::string _ReturnPreValue, std::string _ReturnRateValue, std::string _ReturnChangeAbsolute, std::string _ErrorMessageText, std::string _digital, std::string _analog)
|
||||||
|
{
|
||||||
|
ESP_LOGD(TAG, "Start WriteToData");
|
||||||
|
time_t rawtime;
|
||||||
|
struct tm* timeinfo;
|
||||||
|
char buffer[30];
|
||||||
|
|
||||||
|
time(&rawtime);
|
||||||
|
timeinfo = localtime(&rawtime);
|
||||||
|
|
||||||
|
strftime(buffer, 30, datafile.c_str(), timeinfo);
|
||||||
|
std::string logpath = dataroot + "/" + buffer;
|
||||||
|
|
||||||
|
FILE* pFile;
|
||||||
|
std::string zwtime;
|
||||||
|
|
||||||
|
ESP_LOGD(TAG, "Datalogfile: %s", logpath.c_str());
|
||||||
|
pFile = fopen(logpath.c_str(), "a+");
|
||||||
|
|
||||||
|
if (pFile!=NULL) {
|
||||||
|
fputs(_timestamp.c_str(), pFile);
|
||||||
|
fputs(",", pFile);
|
||||||
|
fputs(_name.c_str(), pFile);
|
||||||
|
fputs(",", pFile);
|
||||||
|
fputs(_ReturnRawValue.c_str(), pFile);
|
||||||
|
fputs(",", pFile);
|
||||||
|
fputs(_ReturnValue.c_str(), pFile);
|
||||||
|
fputs(",", pFile);
|
||||||
|
fputs(_ReturnPreValue.c_str(), pFile);
|
||||||
|
fputs(",", pFile);
|
||||||
|
fputs(_ReturnRateValue.c_str(), pFile);
|
||||||
|
fputs(",", pFile);
|
||||||
|
fputs(_ReturnChangeAbsolute.c_str(), pFile);
|
||||||
|
fputs(",", pFile);
|
||||||
|
fputs(_ErrorMessageText.c_str(), pFile);
|
||||||
|
fputs(_digital.c_str(), pFile);
|
||||||
|
fputs(_analog.c_str(), pFile);
|
||||||
|
fputs("\n", pFile);
|
||||||
|
|
||||||
|
fclose(pFile);
|
||||||
|
} else {
|
||||||
|
ESP_LOGE(TAG, "Can't open data file %s", logpath.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ClassLogFile::setLogLevel(esp_log_level_t _logLevel){
|
||||||
|
loglevel = _logLevel;
|
||||||
|
|
||||||
|
std::string levelText;
|
||||||
|
|
||||||
|
switch(_logLevel) {
|
||||||
|
case ESP_LOG_WARN:
|
||||||
|
levelText = "WARNING";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ESP_LOG_INFO:
|
||||||
|
levelText = "INFO";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ESP_LOG_DEBUG:
|
||||||
|
levelText = "DEBUG";
|
||||||
|
break;
|
||||||
|
case ESP_LOG_ERROR:
|
||||||
|
default:
|
||||||
|
levelText = "ERROR";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Log Level set to %s", levelText.c_str());
|
||||||
|
|
||||||
|
/*
|
||||||
|
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Test");
|
||||||
|
LogFile.WriteToFile(ESP_LOG_WARN, TAG, "Test");
|
||||||
|
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Test");
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Test");
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ClassLogFile::SetLogFileRetention(unsigned short _LogFileRetentionInDays){
|
||||||
|
logFileRetentionInDays = _LogFileRetentionInDays;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ClassLogFile::SetDataLogRetention(unsigned short _DataLogRetentionInDays){
|
||||||
|
dataLogRetentionInDays = _DataLogRetentionInDays;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ClassLogFile::SetDataLogToSD(bool _doDataLogToSD){
|
||||||
|
doDataLogToSD = _doDataLogToSD;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool ClassLogFile::GetDataLogToSD(){
|
||||||
|
return doDataLogToSD;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static FILE* logFileAppendHandle = NULL;
|
||||||
|
std::string fileNameDate;
|
||||||
|
|
||||||
|
void ClassLogFile::WriteToFile(esp_log_level_t level, std::string tag, std::string message, bool _time)
|
||||||
|
{
|
||||||
|
time_t rawtime;
|
||||||
|
struct tm* timeinfo;
|
||||||
|
std::string fileNameDateNew;
|
||||||
|
|
||||||
|
std::string zwtime;
|
||||||
|
std::string ntpTime = "";
|
||||||
|
|
||||||
|
|
||||||
|
time(&rawtime);
|
||||||
|
timeinfo = localtime(&rawtime);
|
||||||
|
char buf[30];
|
||||||
|
strftime(buf, sizeof(buf), logfile.c_str(), timeinfo);
|
||||||
|
fileNameDateNew = std::string(buf);
|
||||||
|
|
||||||
|
std::replace(message.begin(), message.end(), '\n', ' '); // Replace all newline characters
|
||||||
|
|
||||||
|
if (tag != "") {
|
||||||
|
ESP_LOG_LEVEL(level, tag.c_str(), "%s", message.c_str());
|
||||||
|
message = "[" + tag + "] " + message;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ESP_LOG_LEVEL(level, "", "%s", message.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (level > loglevel) {// Only write to file if loglevel is below threshold
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (_time)
|
||||||
|
{
|
||||||
|
char logLineDate[30];
|
||||||
|
strftime(logLineDate, sizeof(logLineDate), "%Y-%m-%dT%H:%M:%S", timeinfo);
|
||||||
|
ntpTime = std::string(logLineDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string loglevelString;
|
||||||
|
switch(level) {
|
||||||
|
case ESP_LOG_ERROR:
|
||||||
|
loglevelString = "ERR";
|
||||||
|
break;
|
||||||
|
case ESP_LOG_WARN:
|
||||||
|
loglevelString = "WRN";
|
||||||
|
break;
|
||||||
|
case ESP_LOG_INFO:
|
||||||
|
loglevelString = "INF";
|
||||||
|
break;
|
||||||
|
case ESP_LOG_DEBUG:
|
||||||
|
loglevelString = "DBG";
|
||||||
|
break;
|
||||||
|
case ESP_LOG_VERBOSE:
|
||||||
|
loglevelString = "VER";
|
||||||
|
break;
|
||||||
|
case ESP_LOG_NONE:
|
||||||
|
default:
|
||||||
|
loglevelString = "NONE";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string formatedUptime = getFormatedUptime(true);
|
||||||
|
|
||||||
|
std::string fullmessage = "[" + formatedUptime + "] " + ntpTime + "\t<" + loglevelString + ">\t" + message + "\n";
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef KEEP_LOGFILE_OPEN_FOR_APPENDING
|
||||||
|
if (fileNameDateNew != fileNameDate) { // Filename changed
|
||||||
|
// Make sure each day gets its own logfile
|
||||||
|
// Also we need to re-open it in case it needed to get closed for reading
|
||||||
|
std::string logpath = logroot + "/" + fileNameDateNew;
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Opening logfile %s for appending", logpath.c_str());
|
||||||
|
logFileAppendHandle = fopen(logpath.c_str(), "a+");
|
||||||
|
if (logFileAppendHandle==NULL) {
|
||||||
|
ESP_LOGE(TAG, "Can't open log file %s", logpath.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fileNameDate = fileNameDateNew;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
std::string logpath = logroot + "/" + fileNameDateNew;
|
||||||
|
logFileAppendHandle = fopen(logpath.c_str(), "a+");
|
||||||
|
if (logFileAppendHandle==NULL) {
|
||||||
|
ESP_LOGE(TAG, "Can't open log file %s", logpath.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
fputs(fullmessage.c_str(), logFileAppendHandle);
|
||||||
|
|
||||||
|
#ifdef KEEP_LOGFILE_OPEN_FOR_APPENDING
|
||||||
|
fflush(logFileAppendHandle);
|
||||||
|
fsync(fileno(logFileAppendHandle));
|
||||||
|
#else
|
||||||
|
CloseLogFileAppendHandle();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ClassLogFile::CloseLogFileAppendHandle() {
|
||||||
|
if (logFileAppendHandle != NULL) {
|
||||||
|
fclose(logFileAppendHandle);
|
||||||
|
logFileAppendHandle = NULL;
|
||||||
|
fileNameDate = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ClassLogFile::WriteToFile(esp_log_level_t level, std::string tag, std::string message) {
|
||||||
|
LogFile.WriteToFile(level, tag, message, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::string ClassLogFile::GetCurrentFileNameData()
|
||||||
|
{
|
||||||
|
time_t rawtime;
|
||||||
|
struct tm* timeinfo;
|
||||||
|
char buffer[60];
|
||||||
|
|
||||||
|
time(&rawtime);
|
||||||
|
timeinfo = localtime(&rawtime);
|
||||||
|
|
||||||
|
strftime(buffer, 60, datafile.c_str(), timeinfo);
|
||||||
|
std::string logpath = dataroot + "/" + buffer;
|
||||||
|
|
||||||
|
return logpath;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::string ClassLogFile::GetCurrentFileName()
|
||||||
|
{
|
||||||
|
time_t rawtime;
|
||||||
|
struct tm* timeinfo;
|
||||||
|
char buffer[60];
|
||||||
|
|
||||||
|
time(&rawtime);
|
||||||
|
timeinfo = localtime(&rawtime);
|
||||||
|
|
||||||
|
strftime(buffer, 60, logfile.c_str(), timeinfo);
|
||||||
|
std::string logpath = logroot + "/" + buffer;
|
||||||
|
|
||||||
|
return logpath;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ClassLogFile::RemoveOldLogFile()
|
||||||
|
{
|
||||||
|
if (logFileRetentionInDays == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGD(TAG, "Remove old log files");
|
||||||
|
|
||||||
|
time_t rawtime;
|
||||||
|
struct tm* timeinfo;
|
||||||
|
char cmpfilename[30];
|
||||||
|
|
||||||
|
time(&rawtime);
|
||||||
|
rawtime = addDays(rawtime, -logFileRetentionInDays + 1);
|
||||||
|
timeinfo = localtime(&rawtime);
|
||||||
|
//ESP_LOGD(TAG, "logFileRetentionInDays: %d", logFileRetentionInDays);
|
||||||
|
|
||||||
|
|
||||||
|
strftime(cmpfilename, 30, logfile.c_str(), timeinfo);
|
||||||
|
//ESP_LOGD(TAG, "log file name to compare: %s", cmpfilename);
|
||||||
|
|
||||||
|
DIR *dir = opendir(logroot.c_str());
|
||||||
|
if (!dir) {
|
||||||
|
ESP_LOGE(TAG, "Failed to stat dir: %s", logroot.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct dirent *entry;
|
||||||
|
int deleted = 0;
|
||||||
|
int notDeleted = 0;
|
||||||
|
while ((entry = readdir(dir)) != NULL) {
|
||||||
|
if (entry->d_type == DT_REG) {
|
||||||
|
//ESP_LOGD(TAG, "compare log file: %s to %s", entry->d_name, cmpfilename);
|
||||||
|
if ((strlen(entry->d_name) == strlen(cmpfilename)) && (strcmp(entry->d_name, cmpfilename) < 0)) {
|
||||||
|
//ESP_LOGD(TAG, "delete log file: %s", entry->d_name);
|
||||||
|
std::string filepath = logroot + "/" + entry->d_name;
|
||||||
|
if (unlink(filepath.c_str()) == 0) {
|
||||||
|
deleted ++;
|
||||||
|
} else {
|
||||||
|
ESP_LOGE(TAG, "can't delete file: %s", entry->d_name);
|
||||||
|
notDeleted ++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
notDeleted ++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ESP_LOGD(TAG, "log files deleted: %d | files not deleted (incl. leer.txt): %d", deleted, notDeleted);
|
||||||
|
closedir(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ClassLogFile::RemoveOldDataLog()
|
||||||
|
{
|
||||||
|
if (dataLogRetentionInDays == 0 || !doDataLogToSD) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGD(TAG, "Remove old data files");
|
||||||
|
|
||||||
|
time_t rawtime;
|
||||||
|
struct tm* timeinfo;
|
||||||
|
char cmpfilename[30];
|
||||||
|
|
||||||
|
time(&rawtime);
|
||||||
|
rawtime = addDays(rawtime, -dataLogRetentionInDays + 1);
|
||||||
|
timeinfo = localtime(&rawtime);
|
||||||
|
//ESP_LOGD(TAG, "dataLogRetentionInDays: %d", dataLogRetentionInDays);
|
||||||
|
|
||||||
|
strftime(cmpfilename, 30, datafile.c_str(), timeinfo);
|
||||||
|
//ESP_LOGD(TAG, "data file name to compare: %s", cmpfilename);
|
||||||
|
|
||||||
|
DIR *dir = opendir(dataroot.c_str());
|
||||||
|
if (!dir) {
|
||||||
|
ESP_LOGE(TAG, "Failed to stat dir: %s", dataroot.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct dirent *entry;
|
||||||
|
int deleted = 0;
|
||||||
|
int notDeleted = 0;
|
||||||
|
while ((entry = readdir(dir)) != NULL) {
|
||||||
|
if (entry->d_type == DT_REG) {
|
||||||
|
//ESP_LOGD(TAG, "Compare data file: %s to %s", entry->d_name, cmpfilename);
|
||||||
|
if ((strlen(entry->d_name) == strlen(cmpfilename)) && (strcmp(entry->d_name, cmpfilename) < 0)) {
|
||||||
|
//ESP_LOGD(TAG, "delete data file: %s", entry->d_name);
|
||||||
|
std::string filepath = dataroot + "/" + entry->d_name;
|
||||||
|
if (unlink(filepath.c_str()) == 0) {
|
||||||
|
deleted ++;
|
||||||
|
} else {
|
||||||
|
ESP_LOGE(TAG, "can't delete file: %s", entry->d_name);
|
||||||
|
notDeleted ++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
notDeleted ++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ESP_LOGD(TAG, "data files deleted: %d | files not deleted (incl. leer.txt): %d", deleted, notDeleted);
|
||||||
|
closedir(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ClassLogFile::CreateLogDirectories()
|
||||||
|
{
|
||||||
|
MakeDir("/sdcard/log");
|
||||||
|
MakeDir("/sdcard/log/data");
|
||||||
|
MakeDir("/sdcard/log/analog");
|
||||||
|
MakeDir("/sdcard/log/digit");
|
||||||
|
MakeDir("/sdcard/log/message");
|
||||||
|
MakeDir("/sdcard/log/source");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ClassLogFile::ClassLogFile(std::string _logroot, std::string _logfile, std::string _logdatapath, std::string _datafile)
|
||||||
|
{
|
||||||
|
logroot = _logroot;
|
||||||
|
logfile = _logfile;
|
||||||
|
datafile = _datafile;
|
||||||
|
dataroot = _logdatapath;
|
||||||
|
logFileRetentionInDays = 3;
|
||||||
|
dataLogRetentionInDays = 3;
|
||||||
|
doDataLogToSD = true;
|
||||||
|
loglevel = ESP_LOG_INFO;
|
||||||
|
}
|
||||||
52
code/components/jomjol_logfile/ClassLogFile.h
Normal file
52
code/components/jomjol_logfile/ClassLogFile.h
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef CLASSLOGFILE_H
|
||||||
|
#define CLASSLOGFILE_H
|
||||||
|
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include "esp_log.h"
|
||||||
|
|
||||||
|
|
||||||
|
class ClassLogFile
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
std::string logroot;
|
||||||
|
std::string logfile;
|
||||||
|
std::string dataroot;
|
||||||
|
std::string datafile;
|
||||||
|
unsigned short logFileRetentionInDays;
|
||||||
|
unsigned short dataLogRetentionInDays;
|
||||||
|
bool doDataLogToSD;
|
||||||
|
esp_log_level_t loglevel;
|
||||||
|
public:
|
||||||
|
ClassLogFile(std::string _logpath, std::string _logfile, std::string _logdatapath, std::string _datafile);
|
||||||
|
|
||||||
|
void WriteHeapInfo(std::string _id);
|
||||||
|
|
||||||
|
void setLogLevel(esp_log_level_t _logLevel);
|
||||||
|
void SetLogFileRetention(unsigned short _LogFileRetentionInDays);
|
||||||
|
void SetDataLogRetention(unsigned short _DataLogRetentionInDays);
|
||||||
|
void SetDataLogToSD(bool _doDataLogToSD);
|
||||||
|
bool GetDataLogToSD();
|
||||||
|
|
||||||
|
void WriteToFile(esp_log_level_t level, std::string tag, std::string message, bool _time);
|
||||||
|
void WriteToFile(esp_log_level_t level, std::string tag, std::string message);
|
||||||
|
|
||||||
|
void CloseLogFileAppendHandle();
|
||||||
|
|
||||||
|
void CreateLogDirectories();
|
||||||
|
void RemoveOldLogFile();
|
||||||
|
void RemoveOldDataLog();
|
||||||
|
|
||||||
|
// void WriteToData(std::string _ReturnRawValue, std::string _ReturnValue, std::string _ReturnPreValue, std::string _ErrorMessageText, std::string _digital, std::string _analog);
|
||||||
|
void WriteToData(std::string _timestamp, std::string _name, std::string _ReturnRawValue, std::string _ReturnValue, std::string _ReturnPreValue, std::string _ReturnRateValue, std::string _ReturnChangeAbsolute, std::string _ErrorMessageText, std::string _digital, std::string _analog);
|
||||||
|
|
||||||
|
|
||||||
|
std::string GetCurrentFileName();
|
||||||
|
std::string GetCurrentFileNameData();
|
||||||
|
};
|
||||||
|
|
||||||
|
extern ClassLogFile LogFile;
|
||||||
|
|
||||||
|
#endif //CLASSLOGFILE_H
|
||||||
5
code/components/jomjol_mqtt/CMakeLists.txt
Normal file
5
code/components/jomjol_mqtt/CMakeLists.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
FILE(GLOB_RECURSE app_sources ${CMAKE_CURRENT_SOURCE_DIR}/*.*)
|
||||||
|
|
||||||
|
idf_component_register(SRCS ${app_sources}
|
||||||
|
INCLUDE_DIRS "."
|
||||||
|
REQUIRES tflite-lib mqtt jomjol_tfliteclass jomjol_helper jomjol_mqtt jomjol_wlan)
|
||||||
399
code/components/jomjol_mqtt/interface_mqtt.cpp
Normal file
399
code/components/jomjol_mqtt/interface_mqtt.cpp
Normal file
@@ -0,0 +1,399 @@
|
|||||||
|
#ifdef ENABLE_MQTT
|
||||||
|
#include "interface_mqtt.h"
|
||||||
|
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "connect_wlan.h"
|
||||||
|
#include "mqtt_client.h"
|
||||||
|
#include "ClassLogFile.h"
|
||||||
|
#include "server_tflite.h"
|
||||||
|
#include "../../include/defines.h"
|
||||||
|
|
||||||
|
static const char *TAG = "MQTT IF";
|
||||||
|
|
||||||
|
std::map<std::string, std::function<void()>>* connectFunktionMap = NULL;
|
||||||
|
std::map<std::string, std::function<bool(std::string, char*, int)>>* subscribeFunktionMap = NULL;
|
||||||
|
|
||||||
|
int failedOnRound = -1;
|
||||||
|
|
||||||
|
esp_mqtt_event_id_t esp_mqtt_ID = MQTT_EVENT_ANY;
|
||||||
|
// ESP_EVENT_ANY_ID
|
||||||
|
|
||||||
|
bool mqtt_enabled = false;
|
||||||
|
bool mqtt_configOK = false;
|
||||||
|
bool mqtt_initialized = false;
|
||||||
|
bool mqtt_connected = false;
|
||||||
|
|
||||||
|
esp_mqtt_client_handle_t client = NULL;
|
||||||
|
std::string uri, client_id, lwt_topic, lwt_connected, lwt_disconnected, user, password, maintopic;
|
||||||
|
int keepalive, SetRetainFlag;
|
||||||
|
void (*callbackOnConnected)(std::string, int) = NULL;
|
||||||
|
|
||||||
|
|
||||||
|
bool MQTTPublish(std::string _key, std::string _content, int retained_flag)
|
||||||
|
{
|
||||||
|
if (!mqtt_enabled) { // MQTT sevice not started / configured (MQTT_Init not called before)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failedOnRound == getCountFlowRounds()) { // we already failed in this round, do not retry until the next round
|
||||||
|
return true; // Fail quietly
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
LogFile.WriteHeapInfo("MQTT Publish");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
MQTT_Init(); // Re-Init client if not initialized yet/anymore
|
||||||
|
|
||||||
|
if (mqtt_initialized && mqtt_connected) {
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
long long int starttime = esp_timer_get_time();
|
||||||
|
#endif
|
||||||
|
int msg_id = esp_mqtt_client_publish(client, _key.c_str(), _content.c_str(), 0, 1, retained_flag);
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
ESP_LOGD(TAG, "Publish msg_id %d in %lld ms", msg_id, (esp_timer_get_time() - starttime)/1000);
|
||||||
|
#endif
|
||||||
|
if (msg_id == -1) {
|
||||||
|
LogFile.WriteToFile(ESP_LOG_WARN, TAG, "Failed to publish topic '" + _key + "', re-trying...");
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
starttime = esp_timer_get_time();
|
||||||
|
#endif
|
||||||
|
msg_id = esp_mqtt_client_publish(client, _key.c_str(), _content.c_str(), 0, 1, retained_flag);
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
ESP_LOGD(TAG, "Publish msg_id %d in %lld ms", msg_id, (esp_timer_get_time() - starttime)/1000);
|
||||||
|
#endif
|
||||||
|
if (msg_id == -1) {
|
||||||
|
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to publish topic '" + _key + "', skipping all MQTT publishings in this round!");
|
||||||
|
failedOnRound = getCountFlowRounds();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_content.length() > 80) { // Truncate message if too long
|
||||||
|
_content.resize(80);
|
||||||
|
_content.append("..");
|
||||||
|
}
|
||||||
|
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Published topic: " + _key + ", content: " + _content + " (msg_id=" + std::to_string(msg_id) + ")");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Publish skipped. Client not initalized or not connected. (topic: " + _key + ")");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static esp_err_t mqtt_event_handler_cb(esp_mqtt_event_handle_t event) {
|
||||||
|
std::string topic = "";
|
||||||
|
switch (event->event_id) {
|
||||||
|
case MQTT_EVENT_BEFORE_CONNECT:
|
||||||
|
ESP_LOGD(TAG, "MQTT_EVENT_BEFORE_CONNECT");
|
||||||
|
mqtt_initialized = true;
|
||||||
|
break;
|
||||||
|
case MQTT_EVENT_CONNECTED:
|
||||||
|
ESP_LOGD(TAG, "MQTT_EVENT_CONNECTED");
|
||||||
|
mqtt_initialized = true;
|
||||||
|
mqtt_connected = true;
|
||||||
|
MQTTconnected();
|
||||||
|
break;
|
||||||
|
case MQTT_EVENT_DISCONNECTED:
|
||||||
|
ESP_LOGD(TAG, "MQTT_EVENT_DISCONNECTED");
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Disconnected from broker");
|
||||||
|
mqtt_connected = false;
|
||||||
|
break;
|
||||||
|
case MQTT_EVENT_SUBSCRIBED:
|
||||||
|
ESP_LOGD(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id);
|
||||||
|
break;
|
||||||
|
case MQTT_EVENT_UNSUBSCRIBED:
|
||||||
|
ESP_LOGD(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id);
|
||||||
|
break;
|
||||||
|
case MQTT_EVENT_PUBLISHED:
|
||||||
|
ESP_LOGD(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id);
|
||||||
|
break;
|
||||||
|
case MQTT_EVENT_DATA:
|
||||||
|
ESP_LOGD(TAG, "MQTT_EVENT_DATA");
|
||||||
|
ESP_LOGD(TAG, "TOPIC=%.*s", event->topic_len, event->topic);
|
||||||
|
ESP_LOGD(TAG, "DATA=%.*s", event->data_len, event->data);
|
||||||
|
topic.assign(event->topic, event->topic_len);
|
||||||
|
if (subscribeFunktionMap != NULL) {
|
||||||
|
if (subscribeFunktionMap->find(topic) != subscribeFunktionMap->end()) {
|
||||||
|
ESP_LOGD(TAG, "call subcribe function for topic %s", topic.c_str());
|
||||||
|
(*subscribeFunktionMap)[topic](topic, event->data, event->data_len);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ESP_LOGW(TAG, "no handler available\r\n");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MQTT_EVENT_ERROR:
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
ESP_LOGD(TAG, "MQTT_EVENT_ERROR - esp_mqtt_error_codes:");
|
||||||
|
ESP_LOGD(TAG, "error_type:%d", event->error_handle->error_type);
|
||||||
|
ESP_LOGD(TAG, "connect_return_code:%d", event->error_handle->connect_return_code);
|
||||||
|
ESP_LOGD(TAG, "esp_transport_sock_errno:%d", event->error_handle->esp_transport_sock_errno);
|
||||||
|
ESP_LOGD(TAG, "esp_tls_last_esp_err:%d", event->error_handle->esp_tls_last_esp_err);
|
||||||
|
ESP_LOGD(TAG, "esp_tls_stack_err:%d", event->error_handle->esp_tls_stack_err);
|
||||||
|
ESP_LOGD(TAG, "esp_tls_cert_verify_flags:%d", event->error_handle->esp_tls_cert_verify_flags);
|
||||||
|
#endif
|
||||||
|
mqtt_connected = false;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ESP_LOGD(TAG, "Other event id:%d", event->event_id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) {
|
||||||
|
ESP_LOGD(TAG, "Event dispatched from event loop base=%s, event_id=%d", base, event_id);
|
||||||
|
mqtt_event_handler_cb((esp_mqtt_event_handle_t) event_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool MQTT_Configure(std::string _mqttURI, std::string _clientid, std::string _user, std::string _password,
|
||||||
|
std::string _maintopic, std::string _lwt, std::string _lwt_connected, std::string _lwt_disconnected,
|
||||||
|
int _keepalive, int _SetRetainFlag, void *_callbackOnConnected) {
|
||||||
|
if ((_mqttURI.length() == 0) || (_maintopic.length() == 0) || (_clientid.length() == 0))
|
||||||
|
{
|
||||||
|
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Init aborted! Config error (URI, MainTopic or ClientID missing)");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uri = _mqttURI;
|
||||||
|
client_id = _clientid;
|
||||||
|
lwt_topic = _maintopic + "/" + _lwt;
|
||||||
|
lwt_connected = _lwt_connected;
|
||||||
|
lwt_disconnected = _lwt_disconnected;
|
||||||
|
keepalive = _keepalive;
|
||||||
|
SetRetainFlag = _SetRetainFlag;
|
||||||
|
maintopic = _maintopic;
|
||||||
|
callbackOnConnected = ( void (*)(std::string, int) )(_callbackOnConnected);
|
||||||
|
|
||||||
|
if (_user.length() && _password.length()){
|
||||||
|
user = _user;
|
||||||
|
password = _password;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef __HIDE_PASSWORD
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "URI: " + uri + ", clientname: " + client_id + ", user: " + user + ", password: XXXXXXXX, maintopic: "
|
||||||
|
+ maintopic + ", last-will-topic: " + lwt_topic + ", keepAlive: " + std::to_string(keepalive) + ", RetainFlag: " + std::to_string(SetRetainFlag));
|
||||||
|
#else
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "URI: " + uri + ", clientname: " + client_id + ", user: " + user + ", password: " + password + ", maintopic: "
|
||||||
|
+ maintopic + ", last-will-topic: " + lwt_topic + ", keepAlive: " + std::to_string(keepalive) + ", RetainFlag: " + std::to_string(SetRetainFlag));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
mqtt_configOK = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int MQTT_Init() {
|
||||||
|
if (mqtt_initialized) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mqtt_configOK) {
|
||||||
|
mqtt_enabled = true;
|
||||||
|
} else {
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Init called, but client is not yet configured.");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!getWIFIisConnected()) {
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Init called, but WIFI is not yet connected.");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Init");
|
||||||
|
MQTTdestroy_client(false);
|
||||||
|
|
||||||
|
esp_mqtt_client_config_t mqtt_cfg = {
|
||||||
|
.uri = uri.c_str(),
|
||||||
|
.client_id = client_id.c_str(),
|
||||||
|
.lwt_topic = lwt_topic.c_str(),
|
||||||
|
.lwt_msg = lwt_disconnected.c_str(),
|
||||||
|
.lwt_retain = 1,
|
||||||
|
.lwt_msg_len = (int)(lwt_disconnected.length()),
|
||||||
|
.keepalive = keepalive,
|
||||||
|
.disable_auto_reconnect = false, // Reconnection routine active (Default: false)
|
||||||
|
.buffer_size = 1536, // size of MQTT send/receive buffer (Default: 1024)
|
||||||
|
.reconnect_timeout_ms = 15000, // Try to reconnect to broker (Default: 10000ms)
|
||||||
|
.network_timeout_ms = 20000, // Network Timeout (Default: 10000ms)
|
||||||
|
.message_retransmit_timeout = 3000 // Tiem after message resent when broker not acknowledged (QoS1, QoS2)
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
if (user.length() && password.length()){
|
||||||
|
mqtt_cfg.username = user.c_str();
|
||||||
|
mqtt_cfg.password = password.c_str();
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
LogFile.WriteHeapInfo("MQTT Client Init");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
client = esp_mqtt_client_init(&mqtt_cfg);
|
||||||
|
if (client)
|
||||||
|
{
|
||||||
|
esp_err_t ret = esp_mqtt_client_register_event(client, esp_mqtt_ID, mqtt_event_handler, client);
|
||||||
|
if (ret != ESP_OK)
|
||||||
|
{
|
||||||
|
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Could not register event (ret=" + std::to_string(ret) + ")!");
|
||||||
|
mqtt_initialized = false;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUG_DETAIL_ON
|
||||||
|
LogFile.WriteHeapInfo("MQTT Client Start");
|
||||||
|
#endif
|
||||||
|
ret = esp_mqtt_client_start(client);
|
||||||
|
if (ret != ESP_OK)
|
||||||
|
{
|
||||||
|
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Client start failed (retval=" + std::to_string(ret) + ")!");
|
||||||
|
mqtt_initialized = false;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Client started, waiting for established connection...");
|
||||||
|
mqtt_initialized = true;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Init failed, no handle created!");
|
||||||
|
mqtt_initialized = false;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void MQTTdestroy_client(bool _disable = false) {
|
||||||
|
if (client) {
|
||||||
|
if (mqtt_connected) {
|
||||||
|
MQTTdestroySubscribeFunction();
|
||||||
|
esp_mqtt_client_disconnect(client);
|
||||||
|
mqtt_connected = false;
|
||||||
|
}
|
||||||
|
esp_mqtt_client_stop(client);
|
||||||
|
esp_mqtt_client_destroy(client);
|
||||||
|
client = NULL;
|
||||||
|
mqtt_initialized = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_disable) // Disable MQTT service, avoid restart with MQTTPublish
|
||||||
|
mqtt_configOK = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool getMQTTisEnabled() {
|
||||||
|
return mqtt_enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool getMQTTisConnected() {
|
||||||
|
return mqtt_connected;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool mqtt_handler_flow_start(std::string _topic, char* _data, int _data_len) {
|
||||||
|
ESP_LOGD(TAG, "Handler called: topic %s, data %.*s", _topic.c_str(), _data_len, _data);
|
||||||
|
|
||||||
|
if (_data_len > 0) {
|
||||||
|
MQTTCtrlFlowStart(_topic);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void MQTTconnected(){
|
||||||
|
if (mqtt_connected) {
|
||||||
|
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Connected to broker");
|
||||||
|
MQTTPublish(lwt_topic, lwt_connected, true); // Publish "connected" to maintopic/connection
|
||||||
|
|
||||||
|
if (connectFunktionMap != NULL) {
|
||||||
|
for(std::map<std::string, std::function<void()>>::iterator it = connectFunktionMap->begin(); it != connectFunktionMap->end(); ++it) {
|
||||||
|
it->second();
|
||||||
|
ESP_LOGD(TAG, "call connect function %s", it->first.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Subcribe to topics */
|
||||||
|
std::function<bool(std::string topic, char* data, int data_len)> subHandler = mqtt_handler_flow_start;
|
||||||
|
MQTTregisterSubscribeFunction(maintopic + "/ctrl/flow_start", subHandler); // subcribe to maintopic/ctrl/flow_start
|
||||||
|
|
||||||
|
if (subscribeFunktionMap != NULL) {
|
||||||
|
for(std::map<std::string, std::function<bool(std::string, char*, int)>>::iterator it = subscribeFunktionMap->begin(); it != subscribeFunktionMap->end(); ++it) {
|
||||||
|
int msg_id = esp_mqtt_client_subscribe(client, it->first.c_str(), 0);
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "topic " + it->first + " subscribe successful, msg_id=" + std::to_string(msg_id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vTaskDelay(10000 / portTICK_PERIOD_MS); // Delay execution of callback routine after connection got established
|
||||||
|
if (callbackOnConnected) { // Call onConnected callback routine --> mqtt_server
|
||||||
|
callbackOnConnected(maintopic, SetRetainFlag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void MQTTregisterConnectFunction(std::string name, std::function<void()> func){
|
||||||
|
ESP_LOGD(TAG, "MQTTregisteronnectFunction %s\r\n", name.c_str());
|
||||||
|
if (connectFunktionMap == NULL) {
|
||||||
|
connectFunktionMap = new std::map<std::string, std::function<void()>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((*connectFunktionMap)[name] != NULL) {
|
||||||
|
ESP_LOGW(TAG, "connect function %s already registred", name.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
(*connectFunktionMap)[name] = func;
|
||||||
|
|
||||||
|
if (mqtt_connected) {
|
||||||
|
func();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void MQTTunregisterConnectFunction(std::string name){
|
||||||
|
ESP_LOGD(TAG, "unregisterConnnectFunction %s\r\n", name.c_str());
|
||||||
|
if ((connectFunktionMap != NULL) && (connectFunktionMap->find(name) != connectFunktionMap->end())) {
|
||||||
|
connectFunktionMap->erase(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void MQTTregisterSubscribeFunction(std::string topic, std::function<bool(std::string, char*, int)> func){
|
||||||
|
ESP_LOGD(TAG, "registerSubscribeFunction %s", topic.c_str());
|
||||||
|
if (subscribeFunktionMap == NULL) {
|
||||||
|
subscribeFunktionMap = new std::map<std::string, std::function<bool(std::string, char*, int)>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((*subscribeFunktionMap)[topic] != NULL) {
|
||||||
|
ESP_LOGW(TAG, "topic %s already registered for subscription", topic.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
(*subscribeFunktionMap)[topic] = func;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void MQTTdestroySubscribeFunction(){
|
||||||
|
if (subscribeFunktionMap != NULL) {
|
||||||
|
if (mqtt_connected) {
|
||||||
|
for(std::map<std::string, std::function<bool(std::string, char*, int)>>::iterator it = subscribeFunktionMap->begin(); it != subscribeFunktionMap->end(); ++it) {
|
||||||
|
int msg_id = esp_mqtt_client_unsubscribe(client, it->first.c_str());
|
||||||
|
ESP_LOGD(TAG, "topic %s unsubscribe successful, msg_id=%d", it->first.c_str(), msg_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribeFunktionMap->clear();
|
||||||
|
delete subscribeFunktionMap;
|
||||||
|
subscribeFunktionMap = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif //ENABLE_MQTT
|
||||||
30
code/components/jomjol_mqtt/interface_mqtt.h
Normal file
30
code/components/jomjol_mqtt/interface_mqtt.h
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#ifdef ENABLE_MQTT
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef INTERFACE_MQTT_H
|
||||||
|
#define INTERFACE_MQTT_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <map>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
bool MQTT_Configure(std::string _mqttURI, std::string _clientid, std::string _user, std::string _password,
|
||||||
|
std::string _maintopic, std::string _lwt, std::string _lwt_connected, std::string _lwt_disconnected,
|
||||||
|
int _keepalive, int SetRetainFlag, void *callbackOnConnected);
|
||||||
|
int MQTT_Init();
|
||||||
|
void MQTTdestroy_client(bool _disable);
|
||||||
|
|
||||||
|
bool MQTTPublish(std::string _key, std::string _content, int retained_flag = 1); // retained Flag as Standart
|
||||||
|
|
||||||
|
bool getMQTTisEnabled();
|
||||||
|
bool getMQTTisConnected();
|
||||||
|
|
||||||
|
void MQTTregisterConnectFunction(std::string name, std::function<void()> func);
|
||||||
|
void MQTTunregisterConnectFunction(std::string name);
|
||||||
|
void MQTTregisterSubscribeFunction(std::string topic, std::function<bool(std::string, char*, int)> func);
|
||||||
|
void MQTTdestroySubscribeFunction();
|
||||||
|
void MQTTconnected();
|
||||||
|
|
||||||
|
#endif //INTERFACE_MQTT_H
|
||||||
|
#endif //#ENABLE_MQTT
|
||||||
266
code/components/jomjol_mqtt/server_mqtt.cpp
Normal file
266
code/components/jomjol_mqtt/server_mqtt.cpp
Normal file
@@ -0,0 +1,266 @@
|
|||||||
|
#ifdef ENABLE_MQTT
|
||||||
|
#include <string>
|
||||||
|
#include <sstream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "ClassLogFile.h"
|
||||||
|
#include "connect_wlan.h"
|
||||||
|
#include "server_mqtt.h"
|
||||||
|
#include "interface_mqtt.h"
|
||||||
|
#include "time_sntp.h"
|
||||||
|
#include "../../include/defines.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static const char *TAG = "MQTT SERVER";
|
||||||
|
|
||||||
|
|
||||||
|
extern const char* libfive_git_version(void);
|
||||||
|
extern const char* libfive_git_revision(void);
|
||||||
|
extern const char* libfive_git_branch(void);
|
||||||
|
|
||||||
|
std::vector<NumberPost*>* NUMBERS;
|
||||||
|
bool HomeassistantDiscovery = false;
|
||||||
|
std::string meterType = "";
|
||||||
|
std::string valueUnit = "";
|
||||||
|
std::string timeUnit = "";
|
||||||
|
std::string rateUnit = "Unit/Minute";
|
||||||
|
float roundInterval; // Minutes
|
||||||
|
int keepAlive = 0; // Seconds
|
||||||
|
int retainFlag;
|
||||||
|
static std::string maintopic;
|
||||||
|
|
||||||
|
|
||||||
|
void mqttServer_setParameter(std::vector<NumberPost*>* _NUMBERS, int _keepAlive, float _roundInterval) {
|
||||||
|
NUMBERS = _NUMBERS;
|
||||||
|
keepAlive = _keepAlive;
|
||||||
|
roundInterval = _roundInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
void mqttServer_setMeterType(std::string _meterType, std::string _valueUnit, std::string _timeUnit,std::string _rateUnit) {
|
||||||
|
meterType = _meterType;
|
||||||
|
valueUnit = _valueUnit;
|
||||||
|
timeUnit = _timeUnit;
|
||||||
|
rateUnit = _rateUnit;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendHomeAssistantDiscoveryTopic(std::string group, std::string field,
|
||||||
|
std::string name, std::string icon, std::string unit, std::string deviceClass, std::string stateClass, std::string entityCategory) {
|
||||||
|
std::string version = std::string(libfive_git_version());
|
||||||
|
|
||||||
|
if (version == "") {
|
||||||
|
version = std::string(libfive_git_branch()) + " (" + std::string(libfive_git_revision()) + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string topicFull;
|
||||||
|
std::string configTopic;
|
||||||
|
std::string payload;
|
||||||
|
|
||||||
|
configTopic = field;
|
||||||
|
|
||||||
|
if (group != "" && (*NUMBERS).size() > 1) { // There is more than one meter, prepend the group so we can differentiate them
|
||||||
|
configTopic = group + "_" + field;
|
||||||
|
name = group + " " + name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (field == "problem") { // Special binary sensor which is based on error topic
|
||||||
|
topicFull = "homeassistant/binary_sensor/" + maintopic + "/" + configTopic + "/config";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
topicFull = "homeassistant/sensor/" + maintopic + "/" + configTopic + "/config";
|
||||||
|
}
|
||||||
|
|
||||||
|
/* See https://www.home-assistant.io/docs/mqtt/discovery/ */
|
||||||
|
payload = string("{") +
|
||||||
|
"\"~\": \"" + maintopic + "\"," +
|
||||||
|
"\"unique_id\": \"" + maintopic + "-" + configTopic + "\"," +
|
||||||
|
"\"object_id\": \"" + maintopic + "_" + configTopic + "\"," + // This used to generate the Entity ID
|
||||||
|
"\"name\": \"" + name + "\"," +
|
||||||
|
"\"icon\": \"mdi:" + icon + "\",";
|
||||||
|
|
||||||
|
if (group != "") {
|
||||||
|
if (field == "problem") { // Special binary sensor which is based on error topic
|
||||||
|
payload += "\"state_topic\": \"~/" + group + "/error\",";
|
||||||
|
payload += "\"value_template\": \"{{ 'OFF' if 'no error' in value else 'ON'}}\",";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
payload += "\"state_topic\": \"~/" + group + "/" + field + "\",";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (field == "problem") { // Special binary sensor which is based on error topic
|
||||||
|
payload += "\"state_topic\": \"~/error\",";
|
||||||
|
payload += "\"value_template\": \"{{ 'OFF' if 'no error' in value else 'ON'}}\",";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
payload += "\"state_topic\": \"~/" + field + "\",";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unit != "") {
|
||||||
|
payload += "\"unit_of_meas\": \"" + unit + "\",";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deviceClass != "") {
|
||||||
|
payload += "\"device_class\": \"" + deviceClass + "\",";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stateClass != "") {
|
||||||
|
payload += "\"state_class\": \"" + stateClass + "\",";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entityCategory != "") {
|
||||||
|
payload += "\"entity_category\": \"" + entityCategory + "\",";
|
||||||
|
}
|
||||||
|
|
||||||
|
payload +=
|
||||||
|
"\"availability_topic\": \"~/" + std::string(LWT_TOPIC) + "\"," +
|
||||||
|
"\"payload_available\": \"" + LWT_CONNECTED + "\"," +
|
||||||
|
"\"payload_not_available\": \"" + LWT_DISCONNECTED + "\",";
|
||||||
|
|
||||||
|
payload += string("\"device\": {") +
|
||||||
|
"\"identifiers\": [\"" + maintopic + "\"]," +
|
||||||
|
"\"name\": \"" + maintopic + "\"," +
|
||||||
|
"\"model\": \"Meter Digitizer\"," +
|
||||||
|
"\"manufacturer\": \"AI on the Edge Device\"," +
|
||||||
|
"\"sw_version\": \"" + version + "\"," +
|
||||||
|
"\"configuration_url\": \"http://" + *getIPAddress() + "\"" +
|
||||||
|
"}" +
|
||||||
|
"}";
|
||||||
|
|
||||||
|
MQTTPublish(topicFull, payload, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MQTThomeassistantDiscovery() {
|
||||||
|
if (!getMQTTisConnected())
|
||||||
|
return;
|
||||||
|
|
||||||
|
LogFile.WriteToFile(ESP_LOG_INFO, TAG, "MQTT - Sending Homeassistant Discovery Topics (Meter Type: " + meterType + ", Value Unit: " + valueUnit + " , Rate Unit: " + rateUnit + ")...");
|
||||||
|
|
||||||
|
// Group | Field | User Friendly Name | Icon | Unit | Device Class | State Class | Entity Category
|
||||||
|
sendHomeAssistantDiscoveryTopic("", "uptime", "Uptime", "clock-time-eight-outline", "s", "", "", "diagnostic");
|
||||||
|
sendHomeAssistantDiscoveryTopic("", "MAC", "MAC Address", "network-outline", "", "", "", "diagnostic");
|
||||||
|
sendHomeAssistantDiscoveryTopic("", "hostname", "Hostname", "network-outline", "", "", "", "diagnostic");
|
||||||
|
sendHomeAssistantDiscoveryTopic("", "freeMem", "Free Memory", "memory", "B", "", "measurement", "diagnostic");
|
||||||
|
sendHomeAssistantDiscoveryTopic("", "wifiRSSI", "Wi-Fi RSSI", "wifi", "dBm", "signal_strength", "", "diagnostic");
|
||||||
|
sendHomeAssistantDiscoveryTopic("", "CPUtemp", "CPU Temperature", "thermometer", "°C", "temperature", "measurement", "diagnostic");
|
||||||
|
sendHomeAssistantDiscoveryTopic("", "interval", "Interval", "clock-time-eight-outline", "min", "" , "measurement", "diagnostic");
|
||||||
|
sendHomeAssistantDiscoveryTopic("", "IP", "IP", "network-outline", "", "", "", "diagnostic");
|
||||||
|
sendHomeAssistantDiscoveryTopic("", "status", "Status", "list-status", "", "", "", "diagnostic");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
for (int i = 0; i < (*NUMBERS).size(); ++i) {
|
||||||
|
std::string group = (*NUMBERS)[i]->name;
|
||||||
|
if (group == "default") {
|
||||||
|
group = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group | Field | User Friendly Name | Icon | Unit | Device Class | State Class | Entity Category
|
||||||
|
sendHomeAssistantDiscoveryTopic(group, "value", "Value", "gauge", valueUnit, meterType, "total_increasing", "");
|
||||||
|
sendHomeAssistantDiscoveryTopic(group, "raw", "Raw Value", "raw", valueUnit, "", "total_increasing", "diagnostic");
|
||||||
|
sendHomeAssistantDiscoveryTopic(group, "error", "Error", "alert-circle-outline", "", "", "", "diagnostic");
|
||||||
|
/* Not announcing "rate" as it is better to use rate_per_time_unit resp. rate_per_digitalization_round */
|
||||||
|
// sendHomeAssistantDiscoveryTopic(group, "rate", "Rate (Unit/Minute)", "swap-vertical", "", "", "", ""); // Legacy, always Unit per Minute
|
||||||
|
sendHomeAssistantDiscoveryTopic(group, "rate_per_time_unit", "Rate (" + rateUnit + ")", "swap-vertical", rateUnit, "", "", "");
|
||||||
|
sendHomeAssistantDiscoveryTopic(group, "rate_per_digitalization_round", "Change since last digitalization round", "arrow-expand-vertical", valueUnit, "", "measurement", ""); // correctly the Unit is Uint/Interval!
|
||||||
|
sendHomeAssistantDiscoveryTopic(group, "timestamp", "Timestamp", "clock-time-eight-outline", "", "timestamp", "", "diagnostic");
|
||||||
|
sendHomeAssistantDiscoveryTopic(group, "json", "JSON", "code-json", "", "", "", "diagnostic");
|
||||||
|
sendHomeAssistantDiscoveryTopic(group, "problem", "Problem", "alert-outline", "", "problem", "", ""); // Special binary sensor which is based on error topic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void publishSystemData() {
|
||||||
|
if (!getMQTTisConnected())
|
||||||
|
return;
|
||||||
|
|
||||||
|
char tmp_char[50];
|
||||||
|
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Publishing system MQTT topics...");
|
||||||
|
|
||||||
|
sprintf(tmp_char, "%ld", (long)getUpTime());
|
||||||
|
MQTTPublish(maintopic + "/" + "uptime", std::string(tmp_char), retainFlag);
|
||||||
|
|
||||||
|
sprintf(tmp_char, "%lu", (long) getESPHeapSize());
|
||||||
|
MQTTPublish(maintopic + "/" + "freeMem", std::string(tmp_char), retainFlag);
|
||||||
|
|
||||||
|
sprintf(tmp_char, "%d", get_WIFI_RSSI());
|
||||||
|
MQTTPublish(maintopic + "/" + "wifiRSSI", std::string(tmp_char), retainFlag);
|
||||||
|
|
||||||
|
sprintf(tmp_char, "%d", (int)temperatureRead());
|
||||||
|
MQTTPublish(maintopic + "/" + "CPUtemp", std::string(tmp_char), retainFlag);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void publishStaticData() {
|
||||||
|
if (!getMQTTisConnected())
|
||||||
|
return;
|
||||||
|
|
||||||
|
LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Publishing static MQTT topics...");
|
||||||
|
MQTTPublish(maintopic + "/" + "MAC", getMac(), retainFlag);
|
||||||
|
MQTTPublish(maintopic + "/" + "IP", *getIPAddress(), retainFlag);
|
||||||
|
MQTTPublish(maintopic + "/" + "hostname", hostname, retainFlag);
|
||||||
|
|
||||||
|
std::stringstream stream;
|
||||||
|
stream << std::fixed << std::setprecision(1) << roundInterval; // minutes
|
||||||
|
MQTTPublish(maintopic + "/" + "interval", stream.str(), retainFlag);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t sendDiscovery_and_static_Topics(httpd_req_t *req) {
|
||||||
|
if (HomeassistantDiscovery) {
|
||||||
|
MQTThomeassistantDiscovery();
|
||||||
|
}
|
||||||
|
|
||||||
|
publishStaticData();
|
||||||
|
|
||||||
|
const char* resp_str = (const char*) req->user_ctx;
|
||||||
|
httpd_resp_send(req, resp_str, strlen(resp_str));
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GotConnected(std::string maintopic, int retainFlag) {
|
||||||
|
if (HomeassistantDiscovery) {
|
||||||
|
MQTThomeassistantDiscovery();
|
||||||
|
}
|
||||||
|
|
||||||
|
publishStaticData();
|
||||||
|
publishSystemData();
|
||||||
|
}
|
||||||
|
|
||||||
|
void register_server_mqtt_uri(httpd_handle_t server) {
|
||||||
|
httpd_uri_t uri = { };
|
||||||
|
uri.method = HTTP_GET;
|
||||||
|
|
||||||
|
uri.uri = "/mqtt_publish_discovery";
|
||||||
|
uri.handler = sendDiscovery_and_static_Topics;
|
||||||
|
uri.user_ctx = (void*) "MQTT Discovery and Static Topics sent";
|
||||||
|
httpd_register_uri_handler(server, &uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::string getTimeUnit(void) {
|
||||||
|
return timeUnit;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void SetHomeassistantDiscoveryEnabled(bool enabled) {
|
||||||
|
HomeassistantDiscovery = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void setMqtt_Server_Retain(int _retainFlag) {
|
||||||
|
retainFlag = _retainFlag;
|
||||||
|
}
|
||||||
|
|
||||||
|
void mqttServer_setMainTopic( std::string _maintopic) {
|
||||||
|
maintopic = _maintopic;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string mqttServer_getMainTopic() {
|
||||||
|
return maintopic;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //ENABLE_MQTT
|
||||||
26
code/components/jomjol_mqtt/server_mqtt.h
Normal file
26
code/components/jomjol_mqtt/server_mqtt.h
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#ifdef ENABLE_MQTT
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef SERVERMQTT_H
|
||||||
|
#define SERVERMQTT_H
|
||||||
|
|
||||||
|
#include "ClassFlowDefineTypes.h"
|
||||||
|
|
||||||
|
void SetHomeassistantDiscoveryEnabled(bool enabled);
|
||||||
|
void mqttServer_setParameter(std::vector<NumberPost*>* _NUMBERS, int interval, float roundInterval);
|
||||||
|
void mqttServer_setMeterType(std::string meterType, std::string valueUnit, std::string timeUnit,std::string rateUnit);
|
||||||
|
void setMqtt_Server_Retain(int SetRetainFlag);
|
||||||
|
void mqttServer_setMainTopic( std::string maintopic);
|
||||||
|
std::string mqttServer_getMainTopic();
|
||||||
|
|
||||||
|
void register_server_mqtt_uri(httpd_handle_t server);
|
||||||
|
|
||||||
|
void publishSystemData();
|
||||||
|
|
||||||
|
std::string getTimeUnit(void);
|
||||||
|
void GotConnected(std::string maintopic, int SetRetainFlag);
|
||||||
|
|
||||||
|
|
||||||
|
#endif //SERVERMQTT_H
|
||||||
|
#endif //ENABLE_MQTT
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user