mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-06 07:49:52 +03:00
Compare commits
819 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1487f30c43 | ||
|
|
793021573a | ||
|
|
2739712c5b | ||
|
|
9945b8d09f | ||
|
|
ee3fafa066 | ||
|
|
eec3b3be7a | ||
|
|
77ad209ce1 | ||
|
|
8a0152ebe6 | ||
|
|
ce8d8699c3 | ||
|
|
2efb9d18c9 | ||
|
|
9af782c485 | ||
|
|
bc232fcff2 | ||
|
|
287232be5c | ||
|
|
c1058ba06c | ||
|
|
9fe6d1092a | ||
|
|
ada55ffaba | ||
|
|
c70b1c3bbd | ||
|
|
a5708e11ba | ||
|
|
8dfc84eac2 | ||
|
|
e00c30cd4f | ||
|
|
be6bb1de6a | ||
|
|
0bd57973c5 | ||
|
|
39cfa3ab79 | ||
|
|
d36fe1c0bf | ||
|
|
27aa57da3c | ||
|
|
b7bd2be0a5 | ||
|
|
9ad80fc74d | ||
|
|
7fc7c24a20 | ||
|
|
c1ae0e76c8 | ||
|
|
f1f9bacf76 | ||
|
|
4e3eb3aeaa | ||
|
|
18c5aaf598 | ||
|
|
cff60f4ed8 | ||
|
|
9fe54825f8 | ||
|
|
c5f2dba1ef | ||
|
|
b94b3e7e2e | ||
|
|
c75f7b6c7d | ||
|
|
d25ead5208 | ||
|
|
68f09f03f8 | ||
|
|
fc1e009f09 | ||
|
|
a5ef1d16d5 | ||
|
|
e80c2b0814 | ||
|
|
5bdf7978bf | ||
|
|
9fc109eec1 | ||
|
|
854f764b3c | ||
|
|
463c68d08c | ||
|
|
2ddd2401eb | ||
|
|
ff045b1a01 | ||
|
|
7c73e70986 | ||
|
|
8699bd4eb0 | ||
|
|
626c32763f | ||
|
|
56c958a141 | ||
|
|
fd1d4b97a0 | ||
|
|
82579869a4 | ||
|
|
12690eeaf4 | ||
|
|
768bdcaaaa | ||
|
|
1db4a33a1d | ||
|
|
61d11ce440 | ||
|
|
08918a7349 | ||
|
|
fb09386c22 | ||
|
|
d42ae52aff | ||
|
|
271d1fda92 | ||
|
|
392015f3af | ||
|
|
c769fd6d2c | ||
|
|
0f06bfa91c | ||
|
|
dffc4a7c02 | ||
|
|
34201025c3 | ||
|
|
189ea6b23d | ||
|
|
24f2d86059 | ||
|
|
1a08ab6a2b | ||
|
|
5e5e6ff053 | ||
|
|
08204a94d8 | ||
|
|
0eb3df704e | ||
|
|
2eb77b5f97 | ||
|
|
33b6ece55b | ||
|
|
5d3f8e5b69 | ||
|
|
8ebc552cac | ||
|
|
944d86b644 | ||
|
|
8bd2a39d4e | ||
|
|
ed9cad6e39 | ||
|
|
e31330e931 | ||
|
|
49d749e89f | ||
|
|
d3fadd7081 | ||
|
|
31ff0f5aba | ||
|
|
b24a63b992 | ||
|
|
f5ec9e9602 | ||
|
|
b6accb8d02 | ||
|
|
a35486ec24 | ||
|
|
fdaa9a6188 | ||
|
|
daf08e7bd9 | ||
|
|
994e1fc26b | ||
|
|
34b7dd61cf | ||
|
|
ce3c3e0b3e | ||
|
|
92a80c3aaf | ||
|
|
df21c15972 | ||
|
|
b683d1dd21 | ||
|
|
a7d0259b30 | ||
|
|
16779064f4 | ||
|
|
732ad4bc6a | ||
|
|
f40a6f20c6 | ||
|
|
4bf22dd6a5 | ||
|
|
62ae5332de | ||
|
|
12e65279ef | ||
|
|
295b90f49c | ||
|
|
644907e58b | ||
|
|
a8a875f9d5 | ||
|
|
6cd9dfc685 | ||
|
|
80a3007f8b | ||
|
|
ed5f0bc6d5 | ||
|
|
df1109ea39 | ||
|
|
1f7c968d0d | ||
|
|
65cf8005a4 | ||
|
|
7c97aaf735 | ||
|
|
23cfdd9b34 | ||
|
|
b454e87405 | ||
|
|
3d715c45e0 | ||
|
|
fea63b0d52 | ||
|
|
57296e55f2 | ||
|
|
12f0120afd | ||
|
|
303e86a5eb | ||
|
|
52479c408f | ||
|
|
fc8eea91eb | ||
|
|
fe5a6fb568 | ||
|
|
55672cc9de | ||
|
|
6d6291e659 | ||
|
|
257b40c2e4 | ||
|
|
e6b61b7a51 | ||
|
|
f167be37a1 | ||
|
|
4ac2d1a9a7 | ||
|
|
8c602cd058 | ||
|
|
b8f6664176 | ||
|
|
1024dbb61f | ||
|
|
253adfeb45 | ||
|
|
99d7ff0dc7 | ||
|
|
738d9b1d94 | ||
|
|
4319d648aa | ||
|
|
2595b906a5 | ||
|
|
c98e1a629b | ||
|
|
ae7f0445a3 | ||
|
|
8406657906 | ||
|
|
9135635af2 | ||
|
|
25098409df | ||
|
|
8176120bf9 | ||
|
|
5cfb7b4548 | ||
|
|
3f17d74bc6 | ||
|
|
1694a0b41d | ||
|
|
ec8a182aa3 | ||
|
|
22b70ac378 | ||
|
|
40a685aeb2 | ||
|
|
a580998f25 | ||
|
|
afc34fc387 | ||
|
|
45335be4ed | ||
|
|
24162b7350 | ||
|
|
3bb7e3514f | ||
|
|
58b75ee203 | ||
|
|
d32178480d | ||
|
|
38e08be752 | ||
|
|
9573c4ed94 | ||
|
|
56e5e87238 | ||
|
|
a9a2380287 | ||
|
|
5bd9ec963f | ||
|
|
427e8cf11c | ||
|
|
de9c224a23 | ||
|
|
0f799d5922 | ||
|
|
3a8bed6976 | ||
|
|
aadf4b7a53 | ||
|
|
ed7ba17bc7 | ||
|
|
526c36728f | ||
|
|
c7a35ebc58 | ||
|
|
5735ffd222 | ||
|
|
d11508282f | ||
|
|
30e11ad593 | ||
|
|
040954bb70 | ||
|
|
eec0051997 | ||
|
|
fab74a9061 | ||
|
|
8e9edcb673 | ||
|
|
2bd66bf4b6 | ||
|
|
9e423d9769 | ||
|
|
6155645436 | ||
|
|
85a839e86b | ||
|
|
0760e6e021 | ||
|
|
fbd3ebbd4e | ||
|
|
24b8e004ec | ||
|
|
34cafe0d4d | ||
|
|
e319f5e270 | ||
|
|
133decf453 | ||
|
|
541615d405 | ||
|
|
21de630f8e | ||
|
|
8a0e037c60 | ||
|
|
326e7bcc2a | ||
|
|
cc8839ab31 | ||
|
|
fcffa3df5c | ||
|
|
27d0ba0526 | ||
|
|
18d329f3ee | ||
|
|
9e064eb564 | ||
|
|
2764185132 | ||
|
|
79d7142e5f | ||
|
|
2a3838771a | ||
|
|
8d712c47f0 | ||
|
|
a3ccc83cf3 | ||
|
|
fe30b8de8d | ||
|
|
d8671dd114 | ||
|
|
603036a5e9 | ||
|
|
9eb617bcb0 | ||
|
|
82e1b069eb | ||
|
|
1b27e1fd09 | ||
|
|
d99222450c | ||
|
|
ef075787dc | ||
|
|
ec411a7dff | ||
|
|
fd4f649db3 | ||
|
|
0c93f1daa5 | ||
|
|
7da2806ab4 | ||
|
|
f88685833d | ||
|
|
c0e77698aa | ||
|
|
9fd7b2553c | ||
|
|
bb5ca8a804 | ||
|
|
cd8921e78e | ||
|
|
9260db330e | ||
|
|
3b32dcb407 | ||
|
|
549a0302b7 | ||
|
|
4d1a428acf | ||
|
|
cc83dab97b | ||
|
|
65ff765219 | ||
|
|
d5cb5c1c51 | ||
|
|
4974208a65 | ||
|
|
2b6fd41d5a | ||
|
|
e26208a5e9 | ||
|
|
12a545ddbf | ||
|
|
4ad5c7299e | ||
|
|
c04371dfae | ||
|
|
d810494211 | ||
|
|
18dd207d3c | ||
|
|
a34c8661bd | ||
|
|
f9516860e3 | ||
|
|
ded7b547e1 | ||
|
|
77607263a9 | ||
|
|
c55e05e7b2 | ||
|
|
d529cbf269 | ||
|
|
c578154b5e | ||
|
|
bbfdb0ff0e | ||
|
|
48de155201 | ||
|
|
a2cfe00113 | ||
|
|
e0c8557d5c | ||
|
|
d6aa1fb48b | ||
|
|
2190db77ad | ||
|
|
8c2aba8eb1 | ||
|
|
c834c5e43e | ||
|
|
94f268a62d | ||
|
|
1f81ccb686 | ||
|
|
356180dbf9 | ||
|
|
1d025d5b97 | ||
|
|
cf8c5430d1 | ||
|
|
1dde495f61 | ||
|
|
c3650817ef | ||
|
|
3f10523e66 | ||
|
|
ad9e463923 | ||
|
|
5f2859fae2 | ||
|
|
2b03d01a15 | ||
|
|
2366f6ba50 | ||
|
|
10d6728c82 | ||
|
|
778cdaabb6 | ||
|
|
efa4f80b99 | ||
|
|
ae15c7ccd0 | ||
|
|
bf95205af6 | ||
|
|
cb11305416 | ||
|
|
5e0c6a527a | ||
|
|
8540e71145 | ||
|
|
cb2746b741 | ||
|
|
209644d500 | ||
|
|
273dc4b83c | ||
|
|
75574f663b | ||
|
|
462870fe6b | ||
|
|
f365fe5317 | ||
|
|
19eb755157 | ||
|
|
26e4badc1b | ||
|
|
fb00f4eef9 | ||
|
|
2ef85bb9fa | ||
|
|
2dfba3f7d0 | ||
|
|
5259d55f23 | ||
|
|
af237c4fc0 | ||
|
|
13a915e1f4 | ||
|
|
df33a24951 | ||
|
|
e62fc14b6d | ||
|
|
591b8afcb0 | ||
|
|
35ee8c33b3 | ||
|
|
9ca47627d2 | ||
|
|
4c27cb831e | ||
|
|
a3b6656be7 | ||
|
|
b0076cd5da | ||
|
|
b6dbf93de2 | ||
|
|
3f42c4d56b | ||
|
|
55ce551868 | ||
|
|
d16e77bba3 | ||
|
|
c9efd095e7 | ||
|
|
8384344c5a | ||
|
|
cb5f707b2d | ||
|
|
e525552e10 | ||
|
|
78bd0a1b76 | ||
|
|
1316fe9509 | ||
|
|
c45fd23227 | ||
|
|
d6856e8a23 | ||
|
|
5fcad37fb9 | ||
|
|
ab58a3dfd3 | ||
|
|
0fce450418 | ||
|
|
3a92d69c77 | ||
|
|
fa1a372468 | ||
|
|
62b341a614 | ||
|
|
0058632d71 | ||
|
|
339408e12a | ||
|
|
8bffa6b304 | ||
|
|
7c80364bcf | ||
|
|
ac950e0e92 | ||
|
|
8d33a89e39 | ||
|
|
41260a4370 | ||
|
|
7d15a8010d | ||
|
|
606a7e6eec | ||
|
|
83e400fbe9 | ||
|
|
d3ae73e6b2 | ||
|
|
b4b2531e33 | ||
|
|
225e482814 | ||
|
|
446601c6e0 | ||
|
|
9f3e2dbcd3 | ||
|
|
3163a142a9 | ||
|
|
dcb0d5087f | ||
|
|
19743da558 | ||
|
|
08c5fd8f64 | ||
|
|
a28cdaa930 | ||
|
|
3f31962a9a | ||
|
|
651b1b1c50 | ||
|
|
ca2ca972b9 | ||
|
|
215c4e49ca | ||
|
|
d1dbce84c7 | ||
|
|
fb4a0b0ae8 | ||
|
|
a01ee4a48d | ||
|
|
8ed867314d | ||
|
|
abf4eeb39a | ||
|
|
40a85634ef | ||
|
|
a5c41a1cd8 | ||
|
|
c6668e1d6a | ||
|
|
5384abc780 | ||
|
|
444d5fb7c3 | ||
|
|
3b76a020c7 | ||
|
|
804187afb9 | ||
|
|
ef2ed0f4d1 | ||
|
|
68084fc9b5 | ||
|
|
1556bf02ba | ||
|
|
68cb94547e | ||
|
|
ee584375c5 | ||
|
|
ac288b794f | ||
|
|
a1f296b2ae | ||
|
|
9b1e399730 | ||
|
|
dd6b435417 | ||
|
|
3d5c08118c | ||
|
|
d78d4aed9d | ||
|
|
9f34531956 | ||
|
|
d1f3ead8b9 | ||
|
|
a33f17932f | ||
|
|
eb05b83009 | ||
|
|
367c022e6c | ||
|
|
b9af4325c9 | ||
|
|
1ae2a624f7 | ||
|
|
d4c9a3c846 | ||
|
|
0b481a44a1 | ||
|
|
513d0e982e | ||
|
|
2629471a56 | ||
|
|
c1ce4f1b73 | ||
|
|
faa21abe54 | ||
|
|
c9f5b0a2c1 | ||
|
|
ed060a400d | ||
|
|
6c3e5d976c | ||
|
|
443050ae28 | ||
|
|
c9ca395e6d | ||
|
|
c3ac215493 | ||
|
|
10cabd032b | ||
|
|
7b9a04ede1 | ||
|
|
9375fb4d2d | ||
|
|
f9b8ac6f30 | ||
|
|
42ba93bdc1 | ||
|
|
fd5f5d49b7 | ||
|
|
80f4e63850 | ||
|
|
7efa8ffbe0 | ||
|
|
85016d6582 | ||
|
|
06bf2f3427 | ||
|
|
c38832ef06 | ||
|
|
c1fa5c42c4 | ||
|
|
2faa78bc32 | ||
|
|
b5633cd579 | ||
|
|
4275d144ca | ||
|
|
7f794f35a6 | ||
|
|
8d778f902f | ||
|
|
f83f22a6fb | ||
|
|
03b6ebd861 | ||
|
|
306baee94d | ||
|
|
cb1989b2ea | ||
|
|
7ce99cb1fb | ||
|
|
3a36663d94 | ||
|
|
e00eb8e64f | ||
|
|
ba21293c42 | ||
|
|
8574b44d4e | ||
|
|
76901d97d2 | ||
|
|
1cbfc91912 | ||
|
|
c81f579464 | ||
|
|
8898ec9419 | ||
|
|
b60127c3ba | ||
|
|
095255b9b7 | ||
|
|
d36246d4d1 | ||
|
|
19094d47aa | ||
|
|
f41bb3671c | ||
|
|
22c75e6df3 | ||
|
|
24cca67625 | ||
|
|
41443d4efe | ||
|
|
6e08356ff7 | ||
|
|
751410ca58 | ||
|
|
5ab10b7aa6 | ||
|
|
01a15a5c85 | ||
|
|
1e15f65b0d | ||
|
|
0818728c25 | ||
|
|
ee5fd4d0eb | ||
|
|
46f35bc67c | ||
|
|
5ad03facce | ||
|
|
ad3bf294d8 | ||
|
|
6dba452aea | ||
|
|
cff5b8c949 | ||
|
|
aed45968db | ||
|
|
ec85a7ec24 | ||
|
|
2de855e044 | ||
|
|
dfd9fe4d01 | ||
|
|
049f956131 | ||
|
|
b503e2bb90 | ||
|
|
b300181d33 | ||
|
|
02f2389587 | ||
|
|
7f140021aa | ||
|
|
905b52e39d | ||
|
|
8fcfb3d8f7 | ||
|
|
64d46fe8f7 | ||
|
|
7962af0872 | ||
|
|
873b75240c | ||
|
|
9e405c5a5a | ||
|
|
6796962c1e | ||
|
|
df6de21cf4 | ||
|
|
e20434da88 | ||
|
|
9c8677acb9 | ||
|
|
1c3bc98bbb | ||
|
|
02616696dc | ||
|
|
00d66c1c4e | ||
|
|
8cfc540670 | ||
|
|
a4062f5d84 | ||
|
|
edee463ade | ||
|
|
64471e4c0e | ||
|
|
28a7ceb6aa | ||
|
|
e4a899912c | ||
|
|
cc9819b56b | ||
|
|
29f86c9ab9 | ||
|
|
722ca34a18 | ||
|
|
bc39b738e2 | ||
|
|
1ada18ec9a | ||
|
|
6b9dadc0d9 | ||
|
|
cf89a06437 | ||
|
|
4a1ea99ee7 | ||
|
|
3dbf8a0fd1 | ||
|
|
7b5a37a85d | ||
|
|
0b5a7b9f2a | ||
|
|
0f99415bbf | ||
|
|
c9775c1edd | ||
|
|
1f1b571e91 | ||
|
|
21e28e970c | ||
|
|
4f1ef297c7 | ||
|
|
3b30083e7c | ||
|
|
50590f4924 | ||
|
|
fae876d7d2 | ||
|
|
c1dabddf21 | ||
|
|
a8ea6ef4a8 | ||
|
|
196e98b3bd | ||
|
|
a0f1f51cca | ||
|
|
d717b72a9e | ||
|
|
eccece7207 | ||
|
|
499ff0d31e | ||
|
|
ae0599d13d | ||
|
|
1a9cf4ebdc | ||
|
|
386082b747 | ||
|
|
fc02721815 | ||
|
|
529970fb19 | ||
|
|
752ce333ec | ||
|
|
8e844550bf | ||
|
|
dfd2a017c2 | ||
|
|
c40a11504c | ||
|
|
fd81299da1 | ||
|
|
9993a7c739 | ||
|
|
ea38011085 | ||
|
|
2079b7e602 | ||
|
|
101159b9f6 | ||
|
|
1442568790 | ||
|
|
1b27fae844 | ||
|
|
4b6b89f1a0 | ||
|
|
a2422e1f6a | ||
|
|
8ba40003e4 | ||
|
|
ac0fb52ce9 | ||
|
|
031c97a162 | ||
|
|
f7d3939c72 | ||
|
|
20f32db8bc | ||
|
|
4d5f8cc96a | ||
|
|
981d62dc2c | ||
|
|
ffcf0bf2d7 | ||
|
|
7997544fb8 | ||
|
|
7e1f16c865 | ||
|
|
765e6bcd69 | ||
|
|
81b654cc41 | ||
|
|
3fc36b5e50 | ||
|
|
52077cbd07 | ||
|
|
a45f1badba | ||
|
|
f38f4bc85a | ||
|
|
8c03592157 | ||
|
|
e7254bc7f4 | ||
|
|
5d0242b47c | ||
|
|
5997dd1491 | ||
|
|
31a5216ae8 | ||
|
|
5c0c0675a2 | ||
|
|
ba9f16da00 | ||
|
|
110ee59cd1 | ||
|
|
555bf8cb2f | ||
|
|
5f1dddf7e4 | ||
|
|
bf5b40ccf4 | ||
|
|
58cbfbc0df | ||
|
|
06f5e1226f | ||
|
|
af57af46b0 | ||
|
|
3f91377751 | ||
|
|
c4c9ed739f | ||
|
|
2a8d3b8cd6 | ||
|
|
af1292caa0 | ||
|
|
44afa1a30f | ||
|
|
fa184a8f94 | ||
|
|
387c2eebcd | ||
|
|
cd6a0da9f0 | ||
|
|
c10ecb097e | ||
|
|
207953e8d1 | ||
|
|
a449ebd0ea | ||
|
|
2afe50fc9e | ||
|
|
cd564f0c54 | ||
|
|
8eba09fea6 | ||
|
|
6c17d61baf | ||
|
|
6df1f2850f | ||
|
|
288d9b70b7 | ||
|
|
8307b0920c | ||
|
|
1097b519ae | ||
|
|
e7b7002883 | ||
|
|
c934b9e8d9 | ||
|
|
0838d06ec4 | ||
|
|
41666458d9 | ||
|
|
b02207a0d7 | ||
|
|
faafd51e40 | ||
|
|
94cf81c164 | ||
|
|
1cd97cf1ee | ||
|
|
6f097e4613 | ||
|
|
90af88f36d | ||
|
|
862cc2545b | ||
|
|
74c6e54dc5 | ||
|
|
33ac89e202 | ||
|
|
89d591500c | ||
|
|
9bf56b2e6d | ||
|
|
7c3dac3da7 | ||
|
|
6853651c8b | ||
|
|
16a9bc4fae | ||
|
|
70d4bcf097 | ||
|
|
1991ca4128 | ||
|
|
879df338e3 | ||
|
|
29c1169b33 | ||
|
|
362b6a1394 | ||
|
|
f5f5901ad9 | ||
|
|
757bf58992 | ||
|
|
af8e77029e | ||
|
|
94519179df | ||
|
|
527dd870a2 | ||
|
|
b9e57414ce | ||
|
|
e3a644182e | ||
|
|
caadb506d8 | ||
|
|
597f0b147d | ||
|
|
0b9a2483b2 | ||
|
|
6a221e84cc | ||
|
|
7ea7a4b25a | ||
|
|
c494627867 | ||
|
|
5032c37f63 | ||
|
|
f5cd92d567 | ||
|
|
240b7dad32 | ||
|
|
2bac805ebf | ||
|
|
b1a3d6ea20 | ||
|
|
77cfad9ff0 | ||
|
|
2dc1bd71ee | ||
|
|
fcdc4314e3 | ||
|
|
900112a967 | ||
|
|
114a8af467 | ||
|
|
e2c2a12a2b | ||
|
|
222f853616 | ||
|
|
450f5a465c | ||
|
|
ba106eecc0 | ||
|
|
374bd7c5c2 | ||
|
|
31b2005320 | ||
|
|
8ae5570c70 | ||
|
|
96fe9aeb31 | ||
|
|
4a7d69c797 | ||
|
|
7dca77450c | ||
|
|
9bb157bbe6 | ||
|
|
6c7787b2ae | ||
|
|
ec5b5ef79d | ||
|
|
6abd56c7e2 | ||
|
|
1fdeaf8b24 | ||
|
|
ad51870d2c | ||
|
|
8929e15e00 | ||
|
|
3f31e636ec | ||
|
|
5e7e1c30ca | ||
|
|
5cf0d8d204 | ||
|
|
dbbd475934 | ||
|
|
bf9877b528 | ||
|
|
5e6b0f64bd | ||
|
|
509d213536 | ||
|
|
4c789a656b | ||
|
|
2f6edfd505 | ||
|
|
9c2d861b93 | ||
|
|
2c0d4fdcef | ||
|
|
be93627ec0 | ||
|
|
c6c7754735 | ||
|
|
07bdf28350 | ||
|
|
fd49f0372a | ||
|
|
36ca7e09ca | ||
|
|
7a36c5e8cb | ||
|
|
15b97515bb | ||
|
|
4048f58856 | ||
|
|
b0ea3184a4 | ||
|
|
6ab2cc60e7 | ||
|
|
237d631e2c | ||
|
|
0ed46679e2 | ||
|
|
1fded64f2e | ||
|
|
161f782904 | ||
|
|
3fe9296139 | ||
|
|
9698e787b2 | ||
|
|
eb274a94c3 | ||
|
|
18be921c1b | ||
|
|
fddfa47b51 | ||
|
|
c533e91643 | ||
|
|
de2792ffdd | ||
|
|
97de23f521 | ||
|
|
c212520f4f | ||
|
|
dc739b97ab | ||
|
|
2583da8714 | ||
|
|
ddfc9f9dd0 | ||
|
|
7d855326d0 | ||
|
|
a1bb49359f | ||
|
|
f9a176e09c | ||
|
|
24d5ac7656 | ||
|
|
558fa20283 | ||
|
|
c42ead995e | ||
|
|
8584901229 | ||
|
|
a17f8db035 | ||
|
|
6783d5b3e8 | ||
|
|
9223a00270 | ||
|
|
c150c578a6 | ||
|
|
5be1482c20 | ||
|
|
20e301594c | ||
|
|
f90f4279d7 | ||
|
|
9962f16d62 | ||
|
|
cb10663735 | ||
|
|
2ab247131f | ||
|
|
c0a3d03a09 | ||
|
|
baa180cc79 | ||
|
|
dbc59b7c8c | ||
|
|
c4d1058133 | ||
|
|
3a8495ca54 | ||
|
|
dad62613f6 | ||
|
|
9641adce1c | ||
|
|
67693364cc | ||
|
|
d37a5c5713 | ||
|
|
a881431010 | ||
|
|
2866862730 | ||
|
|
c234e70a87 | ||
|
|
61296896bd | ||
|
|
2acb45db47 | ||
|
|
abe0d793d6 | ||
|
|
654403ca3d | ||
|
|
b111e4762a | ||
|
|
8bd796b772 | ||
|
|
84b6611ffd | ||
|
|
58b3f309bd | ||
|
|
0bd4330881 | ||
|
|
68feb0fa30 | ||
|
|
b4e266f128 | ||
|
|
855794dbe8 | ||
|
|
2a38981c67 | ||
|
|
63b4a62fac | ||
|
|
710fd1bc79 | ||
|
|
f5ab7510b4 | ||
|
|
ecd37223e8 | ||
|
|
99992db9ac | ||
|
|
3ecea985ad | ||
|
|
ae1b6fc67b | ||
|
|
020bb628d6 | ||
|
|
22a4338e7a | ||
|
|
1f089ec6f9 | ||
|
|
0f78d4f34d | ||
|
|
8395983106 | ||
|
|
87ce1a6d9b | ||
|
|
ac4eba5b72 | ||
|
|
5e53689a81 | ||
|
|
895eddd8d2 | ||
|
|
a8c7b2bca4 | ||
|
|
6f46382806 | ||
|
|
b8af3e235f | ||
|
|
531c2e7b5d | ||
|
|
f76e6b56b1 | ||
|
|
c388b31b22 | ||
|
|
cbb6fa9fec | ||
|
|
a3300e94a7 | ||
|
|
3c8a00be09 | ||
|
|
36ae55e543 | ||
|
|
8b5d893977 | ||
|
|
bd851f9005 | ||
|
|
208be85304 | ||
|
|
9dd613394c | ||
|
|
62eeca944b | ||
|
|
17d7487423 | ||
|
|
ae589c745b | ||
|
|
4b41180785 | ||
|
|
7748f67b9a | ||
|
|
df9f75a5c9 | ||
|
|
7bd8710eb6 | ||
|
|
32f2c6d341 | ||
|
|
20d8b6400f | ||
|
|
d3b086a675 | ||
|
|
e1ee83b907 | ||
|
|
763a2eaab1 | ||
|
|
5d78f1c814 | ||
|
|
3f99806ddd | ||
|
|
bc0a90ee41 | ||
|
|
3900b8fc94 | ||
|
|
f9a53cf320 | ||
|
|
99467558b4 | ||
|
|
2e25e46f6f | ||
|
|
3a1b7cae67 | ||
|
|
5bc3bd23e2 | ||
|
|
a04f4d784b | ||
|
|
9c423dc886 | ||
|
|
1b0f840d18 | ||
|
|
21eaf70181 | ||
|
|
14f2cd4bee | ||
|
|
917e268ac9 | ||
|
|
41228e894a | ||
|
|
365c95991b | ||
|
|
ba4ebe221c | ||
|
|
3ab5946e38 | ||
|
|
f427288c06 | ||
|
|
398cbadb64 | ||
|
|
38a1c9e9d7 | ||
|
|
5abfdf1177 | ||
|
|
385d2377db | ||
|
|
6655035561 | ||
|
|
755408e6aa | ||
|
|
df70ed0062 | ||
|
|
b893290b62 | ||
|
|
96ff5ed123 | ||
|
|
dcdd8d9e44 | ||
|
|
5c484d58dc | ||
|
|
96bf1a32ee | ||
|
|
ad81f84c02 | ||
|
|
529b46d776 | ||
|
|
bbc5bc8585 | ||
|
|
9c33b5cb8b | ||
|
|
ba7ceca798 | ||
|
|
4824152ae5 | ||
|
|
af935d8124 | ||
|
|
951e706fee | ||
|
|
9f1002df9a | ||
|
|
6ab2135cea | ||
|
|
e6bcdede73 | ||
|
|
8b136ddefe | ||
|
|
ca9ac86fde | ||
|
|
42a4c792ad | ||
|
|
a6b0c74f5f | ||
|
|
09e2945c15 | ||
|
|
a3f05cb08b | ||
|
|
0ab573225d | ||
|
|
4c8fd45908 | ||
|
|
8bb703fafe | ||
|
|
9cdeb7c07a | ||
|
|
8d4b43e9f6 | ||
|
|
d8c298f6fe | ||
|
|
8a16566f53 | ||
|
|
9d3456cb3a | ||
|
|
7310db6426 | ||
|
|
ac08bb6037 | ||
|
|
776f72650f | ||
|
|
0eb96e764c | ||
|
|
ec939dfc2e | ||
|
|
809b6be79c | ||
|
|
34f87a0a21 | ||
|
|
bc4418755e | ||
|
|
d24742facd | ||
|
|
9c16e2008e | ||
|
|
6a7a9636ac | ||
|
|
5e76bfa210 | ||
|
|
4da45a7240 | ||
|
|
3c84a1ced1 | ||
|
|
10c8c2b4a7 | ||
|
|
c19345c345 | ||
|
|
6376ed2361 | ||
|
|
04e43d5d41 | ||
|
|
0ba5d8e2bd | ||
|
|
95b8e79624 | ||
|
|
e9c3f8c6da | ||
|
|
1c4da53e75 | ||
|
|
09a15727c7 | ||
|
|
f1034f4230 | ||
|
|
06e9b8d303 | ||
|
|
b912779ef9 | ||
|
|
ced63a6eb0 | ||
|
|
a5f5d36871 | ||
|
|
bd92345793 | ||
|
|
4c1b66279d | ||
|
|
fcf16eec4f | ||
|
|
508f18c29b | ||
|
|
b165e1c20c | ||
|
|
6173e94cc0 |
24
.github/workflows/github-releases-to-discord.yml
vendored
Normal file
24
.github/workflows/github-releases-to-discord.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
name: 'github-releases-to-discord'
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
github-releases-to-discord:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Github Releases To Discord
|
||||
uses: SethCohen/github-releases-to-discord@v1.13.1
|
||||
with:
|
||||
webhook_url: ${{ secrets.WEBHOOK_URL }}
|
||||
color: '2105893'
|
||||
username: 'Release Changelog'
|
||||
avatar_url: 'https://cdn.discordapp.com/icons/816637840644505620/0b14718532d855c452903851b4f0c9a2.png'
|
||||
content: '||@everyone||'
|
||||
footer_title: 'Changelog'
|
||||
footer_icon_url: 'https://cdn.discordapp.com/icons/816637840644505620/0b14718532d855c452903851b4f0c9a2.png'
|
||||
footer_timestamp: true
|
||||
13
.github/workflows/pre_release.yml
vendored
13
.github/workflows/pre_release.yml
vendored
@@ -12,8 +12,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '18'
|
||||
@@ -33,9 +35,10 @@ jobs:
|
||||
run: |
|
||||
cd interface
|
||||
yarn install
|
||||
yarn run typesafe-i18n --no-watch
|
||||
yarn typesafe-i18n --no-watch
|
||||
sed -i "s/= 'pl'/= 'en'/" ./src/i18n/i18n-util.ts
|
||||
yarn run build
|
||||
yarn build
|
||||
yarn webUI
|
||||
|
||||
- name: Build firmware
|
||||
run: |
|
||||
@@ -45,6 +48,10 @@ jobs:
|
||||
run: |
|
||||
platformio run -e ci_s3
|
||||
|
||||
- name: Build E32V2 firmware
|
||||
run: |
|
||||
platformio run -e ci_16M
|
||||
|
||||
- name: Create a GH Release
|
||||
id: 'automatic_releases'
|
||||
uses: 'marvinpinto/action-automatic-releases@latest'
|
||||
|
||||
4
.github/workflows/sonar_check.yml
vendored
4
.github/workflows/sonar_check.yml
vendored
@@ -12,9 +12,9 @@ jobs:
|
||||
# if: github.repository_owner == 'emsesp'
|
||||
# if: github.repository == 'emsesp/EMS-ESP32'
|
||||
env:
|
||||
BUILD_WRAPPER_OUT_DIR: build_wrapper_output_directory
|
||||
BUILD_WRAPPER_OUT_DIR: bw-output
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
|
||||
- name: Install sonar-scanner and build-wrapper
|
||||
|
||||
9
.github/workflows/tagged_release.yml
vendored
9
.github/workflows/tagged_release.yml
vendored
@@ -11,8 +11,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '18'
|
||||
@@ -28,9 +30,10 @@ jobs:
|
||||
run: |
|
||||
cd interface
|
||||
yarn install
|
||||
yarn run typesafe-i18n --no-watch
|
||||
yarn typesafe-i18n --no-watch
|
||||
sed -i "s/= 'pl'/= 'en'/" ./src/i18n/i18n-util.ts
|
||||
yarn run build
|
||||
yarn build
|
||||
yarn webUI
|
||||
|
||||
- name: Build firmware
|
||||
run: |
|
||||
|
||||
9
.github/workflows/test_release.yml
vendored
9
.github/workflows/test_release.yml
vendored
@@ -12,8 +12,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '18'
|
||||
@@ -33,9 +35,10 @@ jobs:
|
||||
run: |
|
||||
cd interface
|
||||
yarn install
|
||||
yarn run typesafe-i18n --no-watch
|
||||
yarn typesafe-i18n --no-watch
|
||||
sed -i "s/= 'pl'/= 'en'/" ./src/i18n/i18n-util.ts
|
||||
yarn run build
|
||||
yarn build
|
||||
yarn webUI
|
||||
|
||||
- name: Build firmware
|
||||
run: |
|
||||
|
||||
20
.gitignore
vendored
20
.gitignore
vendored
@@ -1,8 +1,5 @@
|
||||
# vscode
|
||||
.vscode/c_cpp_properties.json
|
||||
.vscode/extensions.json
|
||||
.vscode/launch.json
|
||||
# .vscode/settings.json
|
||||
.vscode/*
|
||||
|
||||
# c++ compiling
|
||||
.clang_complete
|
||||
@@ -12,11 +9,11 @@ cppcheck.out.xml
|
||||
# platformio
|
||||
.pio
|
||||
pio_local.ini
|
||||
*_old
|
||||
|
||||
# OS specific
|
||||
.DS_Store
|
||||
*Thumbs.db
|
||||
emsesp
|
||||
|
||||
# web specfic
|
||||
build/
|
||||
@@ -36,12 +33,15 @@ stats.html
|
||||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
yarn.lock
|
||||
interface/analyse.html
|
||||
interface/vite.config.ts.timestamp*
|
||||
|
||||
# scripts
|
||||
test.sh
|
||||
scripts/run.sh
|
||||
scripts/__pycache__
|
||||
/scripts/stackdmp.txt
|
||||
scripts/stackdmp.txt
|
||||
|
||||
# i18n generated files
|
||||
interface/src/i18n/i18n-react.tsx
|
||||
@@ -53,8 +53,8 @@ interface/src/i18n/i18n-util.async.ts
|
||||
# sonar
|
||||
.scannerwork/
|
||||
sonar/
|
||||
build_wrapper_output_directory/
|
||||
bw-output/
|
||||
|
||||
# testing
|
||||
emsesp
|
||||
|
||||
# entity dump results
|
||||
# dump_entities.csv
|
||||
# dump_entities.xls*
|
||||
|
||||
2
.vscode/extensions.json
vendored
2
.vscode/extensions.json
vendored
@@ -2,8 +2,6 @@
|
||||
// See http://go.microsoft.com/fwlink/?LinkId=827846
|
||||
// for the documentation about the extensions.json format
|
||||
"recommendations": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode",
|
||||
"platformio.platformio-ide"
|
||||
],
|
||||
"unwantedRecommendations": [
|
||||
|
||||
46
.vscode/settings.json
vendored
46
.vscode/settings.json
vendored
@@ -1,46 +0,0 @@
|
||||
{
|
||||
"search.exclude": {
|
||||
"**/.yarn": true,
|
||||
"**/.pnp.*": true
|
||||
},
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll": true
|
||||
// "source.organizeImports": true
|
||||
},
|
||||
"eslint.nodePath": "interface/.yarn/sdks",
|
||||
"eslint.workingDirectories": ["interface"],
|
||||
"prettier.prettierPath": "",
|
||||
"typescript.tsdk": "interface/.yarn/sdks/typescript/lib",
|
||||
"typescript.enablePromptUseWorkspaceTsdk": true,
|
||||
"files.associations": {
|
||||
"*.tsx": "typescriptreact",
|
||||
"*.tcc": "cpp",
|
||||
"optional": "cpp",
|
||||
"istream": "cpp",
|
||||
"ostream": "cpp",
|
||||
"ratio": "cpp",
|
||||
"system_error": "cpp",
|
||||
"array": "cpp",
|
||||
"functional": "cpp",
|
||||
"regex": "cpp",
|
||||
"tuple": "cpp",
|
||||
"type_traits": "cpp",
|
||||
"utility": "cpp",
|
||||
"string": "cpp",
|
||||
"string_view": "cpp"
|
||||
},
|
||||
"todo-tree.filtering.excludeGlobs": [
|
||||
"**/vendor/**",
|
||||
"**/node_modules/**",
|
||||
"**/dist/**",
|
||||
"**/bower_components/**",
|
||||
"**/build/**",
|
||||
"**/.vscode/**",
|
||||
"**/.github/**",
|
||||
"**/_output/**",
|
||||
"**/*.min.*",
|
||||
"**/*.map",
|
||||
"**/ArduinoJson/**"
|
||||
],
|
||||
"cSpell.enableFiletypes": ["!cpp"]
|
||||
}
|
||||
18
.vscode/tasks.json
vendored
18
.vscode/tasks.json
vendored
@@ -1,18 +0,0 @@
|
||||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "shell",
|
||||
"label": "build standalone emsesp",
|
||||
"command": "make",
|
||||
"args": [],
|
||||
"problemMatcher": ["$gcc"],
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
127
CHANGELOG.md
127
CHANGELOG.md
@@ -5,7 +5,118 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
# [3.6.1] September 9 2023
|
||||
## [3.6.5] March 23 2024
|
||||
|
||||
## **IMPORTANT! BREAKING CHANGES**
|
||||
|
||||
- The Wifi Tx Power setting in Network Settings will be reset to Auto
|
||||
|
||||
## Added
|
||||
|
||||
- thermostat boost mode and boost time [#1446](https://github.com/emsesp/EMS-ESP32/issues/1446)
|
||||
- heatpump energy meters [#1463](https://github.com/emsesp/EMS-ESP32/issues/1463)
|
||||
- heatpump max power [#1475](https://github.com/emsesp/EMS-ESP32/issues/1475)
|
||||
- checkbox for MQTT-TLS enable [#1474](https://github.com/emsesp/EMS-ESP32/issues/1474)
|
||||
- added SK (Slovak) language. Thanks @misa1515
|
||||
- CPU info [#1497](https://github.com/emsesp/EMS-ESP32/pull/1497)
|
||||
- Show network hostname in Web UI under Network Status
|
||||
- Improved HA Discovery so each section (EMS device, Scheduler, Analog, Temperature, Custom, Shower) have their own section
|
||||
- boiler Bosch C1200W, id 12, [#1536](https://github.com/emsesp/EMS-ESP32/issues/1536)
|
||||
- mixer MM100 telegram 0x2CC [#1554](https://github.com/emsesp/EMS-ESP32/issues/1554)
|
||||
- boiler hpSetDiffPressure [#1563](https://github.com/emsesp/EMS-ESP32/issues/1563)
|
||||
- custom variables [#1423](https://github.com/emsesp/EMS-ESP32/issues/1423)
|
||||
- weather compensation [#1642](https://github.com/emsesp/EMS-ESP32/issues/1642)
|
||||
- env and partitions for DevKitC-1-N32R8 [#1635](https://github.com/emsesp/EMS-ESP32/discussions/1635)
|
||||
- command `restart partitionname` and button long press to start with other partition [#1657](https://github.com/emsesp/EMS-ESP32/issues/1657)
|
||||
- command `set service <mqtt|ota|ntp|ap> <enable|disable>` [#1663](https://github.com/emsesp/EMS-ESP32/issues/1663)
|
||||
|
||||
## Fixed
|
||||
|
||||
- exhaust temperature for some boilers
|
||||
- add back boil2hyst [#1477](https://github.com/emsesp/EMS-ESP32/issues/1477)
|
||||
- subscribed MQTT topics not detecting changes by EMS-ESP [#1494](https://github.com/emsesp/EMS-ESP32/issues/1494)
|
||||
- changed HA name and grouping to be consistent [#1528](https://github.com/emsesp/EMS-ESP32/issues/1528)
|
||||
- MQTT autodiscovery in Domoticz not working [#1360](https://github.com/emsesp/EMS-ESP32/issues/1528)
|
||||
- dhw comfort for new ems+, [#1495](https://github.com/emsesp/EMS-ESP32/issues/1495)
|
||||
- added writeable icon to Web's Custom Entity page for each entity shown in the table
|
||||
- Wifi Tx Power not adjusted [#1614](https://github.com/emsesp/EMS-ESP32/issues/1614)
|
||||
- MQTT discovery of custom entity doesn't consider type of data [#1587](https://github.com/emsesp/EMS-ESP32/issues/1587)
|
||||
- WiFi TxPower wasn't correctly used. Added an 'Auto' setting, which is the default.
|
||||
- dns w/wo IPv6 [#1644](https://github.com/emsesp/EMS-ESP32/issues/1644)
|
||||
|
||||
## Changed
|
||||
|
||||
- HA don't set entity_category to Diagnostic/Configuration for EMS entities [#1459](https://github.com/emsesp/EMS-ESP32/discussions/1459)
|
||||
- upgraded ArduinoJson to 7.0.0 #1538 and then 7.0.2
|
||||
- small changes to the API for analog and temperature sensors
|
||||
- Length of mqtt Broker adress [#1619](https://github.com/emsesp/EMS-ESP32/issues/1619)
|
||||
- C++ optimizations - see <https://github.com/emsesp/EMS-ESP32/pull/1615>
|
||||
- Send MQTT heartbeat immediately after connection [#1628](https://github.com/emsesp/EMS-ESP32/issues/1628)
|
||||
- 16MB partitions with second nvs, larger FS, Coredump, optional factory partition
|
||||
- stop fetching empty telegrams after 5 min
|
||||
|
||||
## [3.6.4] November 24 2023
|
||||
|
||||
## **IMPORTANT! BREAKING CHANGES**
|
||||
|
||||
Writeable Text entities have moved from type `sensor` to `text` in Home Assistant to make them also editable within an HA dashboard. Examples are `datetime`, `holidays`, `switchtime`, `vacations`, `maintenancedate`. You will need to manually remove any old discovery topics from your MQTT broker using an application like MQTT Explorer.
|
||||
|
||||
## Added
|
||||
|
||||
- humidity for ventilation devices
|
||||
- telegrams for RC100H, hc2, etc. (seen on discord, not tested)
|
||||
- names for BC400, GB192i.2, read temperatures for low loss header and heatblock [#1317](https://github.com/emsesp/EMS-ESP32/discussions/1317)
|
||||
- option for `forceheatingoff` [#1262](https://github.com/emsesp/EMS-ESP32/issues/1262)
|
||||
- remote thermostat emulation RC100H for RC3xx [#1278](https://github.com/emsesp/EMS-ESP32/discussions/1278)
|
||||
- shower_data MQTT payload contains the timestamp [#1329](https://github.com/emsesp/EMS-ESP32/issues/1329)
|
||||
- HA discovery for writeable text entities [#1337](https://github.com/emsesp/EMS-ESP32/pull/1377)
|
||||
- autodetect board_profile, store in nvs, add telnet command option, add E32V2
|
||||
- heat pump high res energy counters [#1348, #1349. #1350](https://github.com/emsesp/EMS-ESP32/issues/1348)
|
||||
- optional bssid in network settings
|
||||
- extension module EM100 [#1315](https://github.com/emsesp/EMS-ESP32/discussions/1315)
|
||||
- digital_out with new options for polarity and startup state
|
||||
- added 'system allvalues' command that dumps all the EMS device values, plus sensors and any custom entities
|
||||
|
||||
## Fixed
|
||||
|
||||
- remove command `remoteseltemp`, thermostat accept it only from remote thermostat
|
||||
- shower_data MQTT payload contains the timestamp [#1329](https://github.com/emsesp/EMS-ESP32/issues/1329)
|
||||
- fixed helper text in Web Device Entity dialog box for numerical ranges
|
||||
- MQTT base with paths not working in HA [#1393](https://github.com/emsesp/EMS-ESP32/issues/1393)
|
||||
- set/read thermostat mode for RC100-RC300, [#1440](https://github.com/emsesp/EMS-ESP32/issues/1440) [#1442](https://github.com/emsesp/EMS-ESP32/issues/1442)
|
||||
- some setting commands for ems-boiler have used wrong ems+ telegram in 3.6.3
|
||||
|
||||
## Changed
|
||||
|
||||
- update to platform 6.4.0, arduino 2.0.14 / idf 4.4.6
|
||||
- small changes for arduino 3.0.0 / idf 5.1 compatibility (not backward compatible to platform 6.3.2 and before)
|
||||
- AP start after 10 sec, stay until station/eth connected
|
||||
- tested wifi-all-channel-scan (3.6.3-dev4 a-e), removed again because of connect issues
|
||||
- mqtt disconnect stops queue
|
||||
|
||||
## [3.6.2] October 1 2023
|
||||
|
||||
## **IMPORTANT! BREAKING CHANGES**
|
||||
|
||||
## Added
|
||||
|
||||
- Power entities
|
||||
- Optional input of BSSID for AP connection
|
||||
- Return empty json if no entries in scheduler/custom/analogsensor/temperaturesensor
|
||||
|
||||
## Fixed
|
||||
|
||||
- Wifi full scan to get strongest AP
|
||||
- Add missing dhw tags
|
||||
- Sending a dash/- to the Reset command doesn't return an error [#1308](https://github.com/emsesp/EMS-ESP32/discussions/1308)
|
||||
|
||||
## Changed
|
||||
|
||||
- MQTT queue max 300 messages, check heap and maxAlloc
|
||||
- API call commands are logged as WARN in the log
|
||||
- Reset Command renamed to 'reset' in lowercase in EN
|
||||
|
||||
## [3.6.1] September 9 2023
|
||||
|
||||
## **IMPORTANT! BREAKING CHANGES**
|
||||
|
||||
@@ -13,7 +124,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## Added
|
||||
|
||||
- show WiFi rssi in Network Status Page, show quality as color
|
||||
- Show WiFi rssi in Network Status Page, show quality as color
|
||||
|
||||
## Fixed
|
||||
|
||||
@@ -22,9 +133,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## Changed
|
||||
|
||||
- mqtt free mem check set to 60 kb
|
||||
- small cosmetic changes to Searching in Customization web page
|
||||
- updated to espressif32@6.4.0
|
||||
- MQTT free mem check set to 60 kb
|
||||
- Small cosmetic changes to Searching in Customization web page
|
||||
- Updated to espressif32@6.4.0
|
||||
|
||||
# [3.6.0] August 13 2023
|
||||
|
||||
@@ -36,7 +147,7 @@ There are breaking changes between 3.5.x and earlier versions of 3.6.0. Please r
|
||||
|
||||
## Added
|
||||
|
||||
- Workaround for better Domoticz MQTT intergration? [#904](https://github.com/emsesp/EMS-ESP32/issues/904)
|
||||
- Workaround for better Domoticz MQTT integration? [#904](https://github.com/emsesp/EMS-ESP32/issues/904)
|
||||
- Show MAC address without connecting to network enhancement [#933](https://github.com/emsesp/EMS-ESP32/issues/933)
|
||||
- Warn user in WebUI of unsaved changes [#911](https://github.com/emsesp/EMS-ESP32/issues/911)
|
||||
- Detect old Tado thermostat, device-id 0x19, no entities
|
||||
@@ -175,7 +286,7 @@ There are breaking changes between 3.5.x and earlier versions of 3.6.0. Please r
|
||||
|
||||
- fix Table resizing in WebUI [#519](https://github.com/emsesp/EMS-ESP32/issues/519)
|
||||
- allow larger customization files [#570](https://github.com/emsesp/EMS-ESP32/issues/570)
|
||||
- losing entitiy wwcomfort [#581](https://github.com/emsesp/EMS-ESP32/issues/581)
|
||||
- losing entity wwcomfort [#581](https://github.com/emsesp/EMS-ESP32/issues/581)
|
||||
|
||||
## Changed
|
||||
|
||||
@@ -202,7 +313,7 @@ There are breaking changes between 3.5.x and earlier versions of 3.6.0. Please r
|
||||
|
||||
- WebUI optimizations, updated look&feel and better performance [#124](https://github.com/emsesp/EMS-ESP32/issues/124)
|
||||
- Auto refresh of WebUI after successful firmware upload [#178](https://github.com/emsesp/EMS-ESP32/issues/178)
|
||||
- New Customization Service in WebUI. First feature is the ability to enable/disabled Enitites (device values) from EMS devices [#206](https://github.com/emsesp/EMS-ESP32/issues/206)
|
||||
- New Customization Service in WebUI. First feature is the ability to enable/disabled Entities (device values) from EMS devices [#206](https://github.com/emsesp/EMS-ESP32/issues/206)
|
||||
- Option to disable Telnet Console [#209](https://github.com/emsesp/EMS-ESP32/issues/209)
|
||||
- Added Hide SSID, Max Clients and Preferred Channel to Access Point
|
||||
- Merged in MichaelDvP's changes like Fahrenheit conversion, publish single (for IOBroker) and a few other critical optimizations
|
||||
|
||||
@@ -1,22 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
## [3.6.1]
|
||||
## [3.x]
|
||||
|
||||
## **IMPORTANT! BREAKING CHANGES**
|
||||
|
||||
- `shower_data` MQTT topic shows duration is seconds (was previously a full english sentence)
|
||||
|
||||
## Added
|
||||
|
||||
- show WiFi rssi in Network Status Page, show quality as color
|
||||
|
||||
## Fixed
|
||||
|
||||
- Issue in espMqttClient causing a memory leak when MQTT broker is disconnected due to network unavailability [#1264](https://github.com/emsesp/EMS-ESP32/issues/1264)
|
||||
- Using MQTT enum values correctly formatted in MQTT Discovery [#1280](https://github.com/emsesp/EMS-ESP32/issues/1280)
|
||||
|
||||
## Changed
|
||||
|
||||
- mqtt free mem check set to 60 kb
|
||||
- small cosmetic changes to Searching in Customization web page
|
||||
- updated to espressif32@6.4.0
|
||||
|
||||
4
Makefile
4
Makefile
@@ -42,7 +42,7 @@ DEFINES += -DARDUINOJSON_ENABLE_STD_STRING=1 -DARDUINOJSON_ENABLE_PROGMEM=1 -DAR
|
||||
DEFINES += -DEMSESP_DEBUG -DEMSESP_STANDALONE -DEMSESP_TEST -D__linux__ -DEMC_RX_BUFFER_SIZE=1500
|
||||
DEFINES += $(ARGS)
|
||||
|
||||
DEFAULTS = -DEMSESP_DEFAULT_LOCALE=\"en\" -DEMSESP_DEFAULT_TX_MODE=8 -DEMSESP_DEFAULT_VERSION=\"3.7.0-dev\" -DEMSESP_DEFAULT_BOARD_PROFILE=\"S32\"
|
||||
DEFAULTS = -DEMSESP_DEFAULT_LOCALE=\"en\" -DEMSESP_DEFAULT_TX_MODE=8 -DEMSESP_DEFAULT_VERSION=\"3.6.5-dev\" -DEMSESP_DEFAULT_BOARD_PROFILE=\"S32\"
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
# Sources & Files
|
||||
@@ -81,7 +81,7 @@ CPPFLAGS += -g3
|
||||
CPPFLAGS += -Os
|
||||
|
||||
CFLAGS += $(CPPFLAGS)
|
||||
CFLAGS += -Wall -Wextra -Werror -Wswitch-enum -Wno-unused-parameter -Wno-inconsistent-missing-override -Wno-missing-braces -Wno-unused-lambda-capture
|
||||
CFLAGS += -Wall -Wextra -Werror -Wswitch-enum -Wno-unused-parameter -Wno-inconsistent-missing-override -Wno-missing-braces -Wno-unused-lambda-capture -Wno-sign-compare
|
||||
|
||||
CXXFLAGS += $(CFLAGS) -MMD
|
||||
|
||||
|
||||
2338
dump_entities.csv
2338
dump_entities.csv
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,8 @@
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
nvs, data, nvs, 0x9000, 0x5000,
|
||||
otadata, data, ota, , 0x2000,
|
||||
app0, app, ota_0, , 0x7F0000,
|
||||
app1, app, ota_1, , 0x7F0000,
|
||||
spiffs, data, spiffs, , 64K,
|
||||
nvs, data, nvs, 0x9000, 0x005000,
|
||||
otadata, data, ota, , 0x002000,
|
||||
app0, app, ota_0, , 0x5D0000,
|
||||
app1, app, ota_1, , 0x5D0000,
|
||||
nvs1, data, nvs, , 0x040000,
|
||||
spiffs, data, spiffs, , 0x400000,
|
||||
coredump, data, coredump,, 0x010000,
|
||||
|
9
esp32_partition_16M_factory.csv
Normal file
9
esp32_partition_16M_factory.csv
Normal file
@@ -0,0 +1,9 @@
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
nvs, data, nvs, 0x9000, 0x005000,
|
||||
otadata, data, ota, , 0x002000,
|
||||
boot, app, factory, , 0x280000,
|
||||
app0, app, ota_0, , 0x590000,
|
||||
app1, app, ota_1, , 0x590000,
|
||||
nvs1, data, nvs, , 0x040000,
|
||||
spiffs, data, spiffs, , 0x200000,
|
||||
coredump, data, coredump,, 0x010000,
|
||||
|
8
esp32_partition_32M.csv
Normal file
8
esp32_partition_32M.csv
Normal file
@@ -0,0 +1,8 @@
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
nvs, data, nvs, 0x9000, 0x005000,
|
||||
otadata, data, ota, , 0x002000,
|
||||
app0, app, ota_0, , 0xDD0000,
|
||||
app1, app, ota_1, , 0xDD0000,
|
||||
nvs1, data, nvs, , 0x040000,
|
||||
spiffs, data, spiffs, , 0x400000,
|
||||
coredump, data, coredump,, 0x010000,
|
||||
|
@@ -36,7 +36,6 @@ build_flags =
|
||||
-D FACTORY_MQTT_PORT=1883
|
||||
-D FACTORY_MQTT_USERNAME=\"\"
|
||||
-D FACTORY_MQTT_PASSWORD=\"\"
|
||||
-D FACTORY_MQTT_CLIENT_ID=\"ems-esp\"
|
||||
-D FACTORY_MQTT_KEEP_ALIVE=60
|
||||
-D FACTORY_MQTT_CLEAN_SESSION=false
|
||||
-D FACTORY_MQTT_MAX_TOPIC_LENGTH=128
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"airbnb/hooks",
|
||||
"airbnb-typescript",
|
||||
// "airbnb/hooks",
|
||||
// "airbnb-typescript",
|
||||
"plugin:react/recommended",
|
||||
"plugin:react/jsx-runtime",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
|
||||
File diff suppressed because one or more lines are too long
873
interface/.yarn/releases/yarn-3.4.1.cjs
vendored
873
interface/.yarn/releases/yarn-3.4.1.cjs
vendored
File diff suppressed because one or more lines are too long
893
interface/.yarn/releases/yarn-4.1.1.cjs
vendored
Executable file
893
interface/.yarn/releases/yarn-4.1.1.cjs
vendored
Executable file
File diff suppressed because one or more lines are too long
20
interface/.yarn/sdks/eslint/bin/eslint.js
vendored
20
interface/.yarn/sdks/eslint/bin/eslint.js
vendored
@@ -1,20 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const {existsSync} = require(`fs`);
|
||||
const {createRequire} = require(`module`);
|
||||
const {resolve} = require(`path`);
|
||||
|
||||
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||
|
||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||
const absRequire = createRequire(absPnpApiPath);
|
||||
|
||||
if (existsSync(absPnpApiPath)) {
|
||||
if (!process.versions.pnp) {
|
||||
// Setup the environment to be able to require eslint/bin/eslint.js
|
||||
require(absPnpApiPath).setup();
|
||||
}
|
||||
}
|
||||
|
||||
// Defer to the real eslint/bin/eslint.js your application uses
|
||||
module.exports = absRequire(`eslint/bin/eslint.js`);
|
||||
20
interface/.yarn/sdks/eslint/lib/api.js
vendored
20
interface/.yarn/sdks/eslint/lib/api.js
vendored
@@ -1,20 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const {existsSync} = require(`fs`);
|
||||
const {createRequire} = require(`module`);
|
||||
const {resolve} = require(`path`);
|
||||
|
||||
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||
|
||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||
const absRequire = createRequire(absPnpApiPath);
|
||||
|
||||
if (existsSync(absPnpApiPath)) {
|
||||
if (!process.versions.pnp) {
|
||||
// Setup the environment to be able to require eslint
|
||||
require(absPnpApiPath).setup();
|
||||
}
|
||||
}
|
||||
|
||||
// Defer to the real eslint your application uses
|
||||
module.exports = absRequire(`eslint`);
|
||||
6
interface/.yarn/sdks/eslint/package.json
vendored
6
interface/.yarn/sdks/eslint/package.json
vendored
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"name": "eslint",
|
||||
"version": "8.36.0-sdk",
|
||||
"main": "./lib/api.js",
|
||||
"type": "commonjs"
|
||||
}
|
||||
5
interface/.yarn/sdks/integrations.yml
vendored
5
interface/.yarn/sdks/integrations.yml
vendored
@@ -1,5 +0,0 @@
|
||||
# This file is automatically generated by @yarnpkg/sdks.
|
||||
# Manual changes might be lost!
|
||||
|
||||
integrations:
|
||||
- vscode
|
||||
20
interface/.yarn/sdks/prettier/index.js
vendored
20
interface/.yarn/sdks/prettier/index.js
vendored
@@ -1,20 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const {existsSync} = require(`fs`);
|
||||
const {createRequire} = require(`module`);
|
||||
const {resolve} = require(`path`);
|
||||
|
||||
const relPnpApiPath = "../../../.pnp.cjs";
|
||||
|
||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||
const absRequire = createRequire(absPnpApiPath);
|
||||
|
||||
if (existsSync(absPnpApiPath)) {
|
||||
if (!process.versions.pnp) {
|
||||
// Setup the environment to be able to require prettier/index.js
|
||||
require(absPnpApiPath).setup();
|
||||
}
|
||||
}
|
||||
|
||||
// Defer to the real prettier/index.js your application uses
|
||||
module.exports = absRequire(`prettier/index.js`);
|
||||
6
interface/.yarn/sdks/prettier/package.json
vendored
6
interface/.yarn/sdks/prettier/package.json
vendored
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"name": "prettier",
|
||||
"version": "2.8.7-sdk",
|
||||
"main": "./index.js",
|
||||
"type": "commonjs"
|
||||
}
|
||||
20
interface/.yarn/sdks/typescript/bin/tsc
vendored
20
interface/.yarn/sdks/typescript/bin/tsc
vendored
@@ -1,20 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const {existsSync} = require(`fs`);
|
||||
const {createRequire} = require(`module`);
|
||||
const {resolve} = require(`path`);
|
||||
|
||||
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||
|
||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||
const absRequire = createRequire(absPnpApiPath);
|
||||
|
||||
if (existsSync(absPnpApiPath)) {
|
||||
if (!process.versions.pnp) {
|
||||
// Setup the environment to be able to require typescript/bin/tsc
|
||||
require(absPnpApiPath).setup();
|
||||
}
|
||||
}
|
||||
|
||||
// Defer to the real typescript/bin/tsc your application uses
|
||||
module.exports = absRequire(`typescript/bin/tsc`);
|
||||
20
interface/.yarn/sdks/typescript/bin/tsserver
vendored
20
interface/.yarn/sdks/typescript/bin/tsserver
vendored
@@ -1,20 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const {existsSync} = require(`fs`);
|
||||
const {createRequire} = require(`module`);
|
||||
const {resolve} = require(`path`);
|
||||
|
||||
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||
|
||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||
const absRequire = createRequire(absPnpApiPath);
|
||||
|
||||
if (existsSync(absPnpApiPath)) {
|
||||
if (!process.versions.pnp) {
|
||||
// Setup the environment to be able to require typescript/bin/tsserver
|
||||
require(absPnpApiPath).setup();
|
||||
}
|
||||
}
|
||||
|
||||
// Defer to the real typescript/bin/tsserver your application uses
|
||||
module.exports = absRequire(`typescript/bin/tsserver`);
|
||||
20
interface/.yarn/sdks/typescript/lib/tsc.js
vendored
20
interface/.yarn/sdks/typescript/lib/tsc.js
vendored
@@ -1,20 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const {existsSync} = require(`fs`);
|
||||
const {createRequire} = require(`module`);
|
||||
const {resolve} = require(`path`);
|
||||
|
||||
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||
|
||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||
const absRequire = createRequire(absPnpApiPath);
|
||||
|
||||
if (existsSync(absPnpApiPath)) {
|
||||
if (!process.versions.pnp) {
|
||||
// Setup the environment to be able to require typescript/lib/tsc.js
|
||||
require(absPnpApiPath).setup();
|
||||
}
|
||||
}
|
||||
|
||||
// Defer to the real typescript/lib/tsc.js your application uses
|
||||
module.exports = absRequire(`typescript/lib/tsc.js`);
|
||||
223
interface/.yarn/sdks/typescript/lib/tsserver.js
vendored
223
interface/.yarn/sdks/typescript/lib/tsserver.js
vendored
@@ -1,223 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const {existsSync} = require(`fs`);
|
||||
const {createRequire} = require(`module`);
|
||||
const {resolve} = require(`path`);
|
||||
|
||||
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||
|
||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||
const absRequire = createRequire(absPnpApiPath);
|
||||
|
||||
const moduleWrapper = tsserver => {
|
||||
if (!process.versions.pnp) {
|
||||
return tsserver;
|
||||
}
|
||||
|
||||
const {isAbsolute} = require(`path`);
|
||||
const pnpApi = require(`pnpapi`);
|
||||
|
||||
const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//);
|
||||
const isPortal = str => str.startsWith("portal:/");
|
||||
const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`);
|
||||
|
||||
const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => {
|
||||
return `${locator.name}@${locator.reference}`;
|
||||
}));
|
||||
|
||||
// VSCode sends the zip paths to TS using the "zip://" prefix, that TS
|
||||
// doesn't understand. This layer makes sure to remove the protocol
|
||||
// before forwarding it to TS, and to add it back on all returned paths.
|
||||
|
||||
function toEditorPath(str) {
|
||||
// We add the `zip:` prefix to both `.zip/` paths and virtual paths
|
||||
if (isAbsolute(str) && !str.match(/^\^?(zip:|\/zip\/)/) && (str.match(/\.zip\//) || isVirtual(str))) {
|
||||
// We also take the opportunity to turn virtual paths into physical ones;
|
||||
// this makes it much easier to work with workspaces that list peer
|
||||
// dependencies, since otherwise Ctrl+Click would bring us to the virtual
|
||||
// file instances instead of the real ones.
|
||||
//
|
||||
// We only do this to modules owned by the the dependency tree roots.
|
||||
// This avoids breaking the resolution when jumping inside a vendor
|
||||
// with peer dep (otherwise jumping into react-dom would show resolution
|
||||
// errors on react).
|
||||
//
|
||||
const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str;
|
||||
if (resolved) {
|
||||
const locator = pnpApi.findPackageLocator(resolved);
|
||||
if (locator && (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || isPortal(locator.reference))) {
|
||||
str = resolved;
|
||||
}
|
||||
}
|
||||
|
||||
str = normalize(str);
|
||||
|
||||
if (str.match(/\.zip\//)) {
|
||||
switch (hostInfo) {
|
||||
// Absolute VSCode `Uri.fsPath`s need to start with a slash.
|
||||
// VSCode only adds it automatically for supported schemes,
|
||||
// so we have to do it manually for the `zip` scheme.
|
||||
// The path needs to start with a caret otherwise VSCode doesn't handle the protocol
|
||||
//
|
||||
// Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910
|
||||
//
|
||||
// 2021-10-08: VSCode changed the format in 1.61.
|
||||
// Before | ^zip:/c:/foo/bar.zip/package.json
|
||||
// After | ^/zip//c:/foo/bar.zip/package.json
|
||||
//
|
||||
// 2022-04-06: VSCode changed the format in 1.66.
|
||||
// Before | ^/zip//c:/foo/bar.zip/package.json
|
||||
// After | ^/zip/c:/foo/bar.zip/package.json
|
||||
//
|
||||
// 2022-05-06: VSCode changed the format in 1.68
|
||||
// Before | ^/zip/c:/foo/bar.zip/package.json
|
||||
// After | ^/zip//c:/foo/bar.zip/package.json
|
||||
//
|
||||
case `vscode <1.61`: {
|
||||
str = `^zip:${str}`;
|
||||
} break;
|
||||
|
||||
case `vscode <1.66`: {
|
||||
str = `^/zip/${str}`;
|
||||
} break;
|
||||
|
||||
case `vscode <1.68`: {
|
||||
str = `^/zip${str}`;
|
||||
} break;
|
||||
|
||||
case `vscode`: {
|
||||
str = `^/zip/${str}`;
|
||||
} break;
|
||||
|
||||
// To make "go to definition" work,
|
||||
// We have to resolve the actual file system path from virtual path
|
||||
// and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip)
|
||||
case `coc-nvim`: {
|
||||
str = normalize(resolved).replace(/\.zip\//, `.zip::`);
|
||||
str = resolve(`zipfile:${str}`);
|
||||
} break;
|
||||
|
||||
// Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server)
|
||||
// We have to resolve the actual file system path from virtual path,
|
||||
// everything else is up to neovim
|
||||
case `neovim`: {
|
||||
str = normalize(resolved).replace(/\.zip\//, `.zip::`);
|
||||
str = `zipfile://${str}`;
|
||||
} break;
|
||||
|
||||
default: {
|
||||
str = `zip:${str}`;
|
||||
} break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
function fromEditorPath(str) {
|
||||
switch (hostInfo) {
|
||||
case `coc-nvim`: {
|
||||
str = str.replace(/\.zip::/, `.zip/`);
|
||||
// The path for coc-nvim is in format of /<pwd>/zipfile:/<pwd>/.yarn/...
|
||||
// So in order to convert it back, we use .* to match all the thing
|
||||
// before `zipfile:`
|
||||
return process.platform === `win32`
|
||||
? str.replace(/^.*zipfile:\//, ``)
|
||||
: str.replace(/^.*zipfile:/, ``);
|
||||
} break;
|
||||
|
||||
case `neovim`: {
|
||||
str = str.replace(/\.zip::/, `.zip/`);
|
||||
// The path for neovim is in format of zipfile:///<pwd>/.yarn/...
|
||||
return str.replace(/^zipfile:\/\//, ``);
|
||||
} break;
|
||||
|
||||
case `vscode`:
|
||||
default: {
|
||||
return str.replace(/^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/, process.platform === `win32` ? `` : `/`)
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
// Force enable 'allowLocalPluginLoads'
|
||||
// TypeScript tries to resolve plugins using a path relative to itself
|
||||
// which doesn't work when using the global cache
|
||||
// https://github.com/microsoft/TypeScript/blob/1b57a0395e0bff191581c9606aab92832001de62/src/server/project.ts#L2238
|
||||
// VSCode doesn't want to enable 'allowLocalPluginLoads' due to security concerns but
|
||||
// TypeScript already does local loads and if this code is running the user trusts the workspace
|
||||
// https://github.com/microsoft/vscode/issues/45856
|
||||
const ConfiguredProject = tsserver.server.ConfiguredProject;
|
||||
const {enablePluginsWithOptions: originalEnablePluginsWithOptions} = ConfiguredProject.prototype;
|
||||
ConfiguredProject.prototype.enablePluginsWithOptions = function() {
|
||||
this.projectService.allowLocalPluginLoads = true;
|
||||
return originalEnablePluginsWithOptions.apply(this, arguments);
|
||||
};
|
||||
|
||||
// And here is the point where we hijack the VSCode <-> TS communications
|
||||
// by adding ourselves in the middle. We locate everything that looks
|
||||
// like an absolute path of ours and normalize it.
|
||||
|
||||
const Session = tsserver.server.Session;
|
||||
const {onMessage: originalOnMessage, send: originalSend} = Session.prototype;
|
||||
let hostInfo = `unknown`;
|
||||
|
||||
Object.assign(Session.prototype, {
|
||||
onMessage(/** @type {string | object} */ message) {
|
||||
const isStringMessage = typeof message === 'string';
|
||||
const parsedMessage = isStringMessage ? JSON.parse(message) : message;
|
||||
|
||||
if (
|
||||
parsedMessage != null &&
|
||||
typeof parsedMessage === `object` &&
|
||||
parsedMessage.arguments &&
|
||||
typeof parsedMessage.arguments.hostInfo === `string`
|
||||
) {
|
||||
hostInfo = parsedMessage.arguments.hostInfo;
|
||||
if (hostInfo === `vscode` && process.env.VSCODE_IPC_HOOK) {
|
||||
const [, major, minor] = (process.env.VSCODE_IPC_HOOK.match(
|
||||
// The RegExp from https://semver.org/ but without the caret at the start
|
||||
/(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/
|
||||
) ?? []).map(Number)
|
||||
|
||||
if (major === 1) {
|
||||
if (minor < 61) {
|
||||
hostInfo += ` <1.61`;
|
||||
} else if (minor < 66) {
|
||||
hostInfo += ` <1.66`;
|
||||
} else if (minor < 68) {
|
||||
hostInfo += ` <1.68`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const processedMessageJSON = JSON.stringify(parsedMessage, (key, value) => {
|
||||
return typeof value === 'string' ? fromEditorPath(value) : value;
|
||||
});
|
||||
|
||||
return originalOnMessage.call(
|
||||
this,
|
||||
isStringMessage ? processedMessageJSON : JSON.parse(processedMessageJSON)
|
||||
);
|
||||
},
|
||||
|
||||
send(/** @type {any} */ msg) {
|
||||
return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => {
|
||||
return typeof value === `string` ? toEditorPath(value) : value;
|
||||
})));
|
||||
}
|
||||
});
|
||||
|
||||
return tsserver;
|
||||
};
|
||||
|
||||
if (existsSync(absPnpApiPath)) {
|
||||
if (!process.versions.pnp) {
|
||||
// Setup the environment to be able to require typescript/lib/tsserver.js
|
||||
require(absPnpApiPath).setup();
|
||||
}
|
||||
}
|
||||
|
||||
// Defer to the real typescript/lib/tsserver.js your application uses
|
||||
module.exports = moduleWrapper(absRequire(`typescript/lib/tsserver.js`));
|
||||
@@ -1,223 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const {existsSync} = require(`fs`);
|
||||
const {createRequire} = require(`module`);
|
||||
const {resolve} = require(`path`);
|
||||
|
||||
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||
|
||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||
const absRequire = createRequire(absPnpApiPath);
|
||||
|
||||
const moduleWrapper = tsserver => {
|
||||
if (!process.versions.pnp) {
|
||||
return tsserver;
|
||||
}
|
||||
|
||||
const {isAbsolute} = require(`path`);
|
||||
const pnpApi = require(`pnpapi`);
|
||||
|
||||
const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//);
|
||||
const isPortal = str => str.startsWith("portal:/");
|
||||
const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`);
|
||||
|
||||
const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => {
|
||||
return `${locator.name}@${locator.reference}`;
|
||||
}));
|
||||
|
||||
// VSCode sends the zip paths to TS using the "zip://" prefix, that TS
|
||||
// doesn't understand. This layer makes sure to remove the protocol
|
||||
// before forwarding it to TS, and to add it back on all returned paths.
|
||||
|
||||
function toEditorPath(str) {
|
||||
// We add the `zip:` prefix to both `.zip/` paths and virtual paths
|
||||
if (isAbsolute(str) && !str.match(/^\^?(zip:|\/zip\/)/) && (str.match(/\.zip\//) || isVirtual(str))) {
|
||||
// We also take the opportunity to turn virtual paths into physical ones;
|
||||
// this makes it much easier to work with workspaces that list peer
|
||||
// dependencies, since otherwise Ctrl+Click would bring us to the virtual
|
||||
// file instances instead of the real ones.
|
||||
//
|
||||
// We only do this to modules owned by the the dependency tree roots.
|
||||
// This avoids breaking the resolution when jumping inside a vendor
|
||||
// with peer dep (otherwise jumping into react-dom would show resolution
|
||||
// errors on react).
|
||||
//
|
||||
const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str;
|
||||
if (resolved) {
|
||||
const locator = pnpApi.findPackageLocator(resolved);
|
||||
if (locator && (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || isPortal(locator.reference))) {
|
||||
str = resolved;
|
||||
}
|
||||
}
|
||||
|
||||
str = normalize(str);
|
||||
|
||||
if (str.match(/\.zip\//)) {
|
||||
switch (hostInfo) {
|
||||
// Absolute VSCode `Uri.fsPath`s need to start with a slash.
|
||||
// VSCode only adds it automatically for supported schemes,
|
||||
// so we have to do it manually for the `zip` scheme.
|
||||
// The path needs to start with a caret otherwise VSCode doesn't handle the protocol
|
||||
//
|
||||
// Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910
|
||||
//
|
||||
// 2021-10-08: VSCode changed the format in 1.61.
|
||||
// Before | ^zip:/c:/foo/bar.zip/package.json
|
||||
// After | ^/zip//c:/foo/bar.zip/package.json
|
||||
//
|
||||
// 2022-04-06: VSCode changed the format in 1.66.
|
||||
// Before | ^/zip//c:/foo/bar.zip/package.json
|
||||
// After | ^/zip/c:/foo/bar.zip/package.json
|
||||
//
|
||||
// 2022-05-06: VSCode changed the format in 1.68
|
||||
// Before | ^/zip/c:/foo/bar.zip/package.json
|
||||
// After | ^/zip//c:/foo/bar.zip/package.json
|
||||
//
|
||||
case `vscode <1.61`: {
|
||||
str = `^zip:${str}`;
|
||||
} break;
|
||||
|
||||
case `vscode <1.66`: {
|
||||
str = `^/zip/${str}`;
|
||||
} break;
|
||||
|
||||
case `vscode <1.68`: {
|
||||
str = `^/zip${str}`;
|
||||
} break;
|
||||
|
||||
case `vscode`: {
|
||||
str = `^/zip/${str}`;
|
||||
} break;
|
||||
|
||||
// To make "go to definition" work,
|
||||
// We have to resolve the actual file system path from virtual path
|
||||
// and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip)
|
||||
case `coc-nvim`: {
|
||||
str = normalize(resolved).replace(/\.zip\//, `.zip::`);
|
||||
str = resolve(`zipfile:${str}`);
|
||||
} break;
|
||||
|
||||
// Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server)
|
||||
// We have to resolve the actual file system path from virtual path,
|
||||
// everything else is up to neovim
|
||||
case `neovim`: {
|
||||
str = normalize(resolved).replace(/\.zip\//, `.zip::`);
|
||||
str = `zipfile://${str}`;
|
||||
} break;
|
||||
|
||||
default: {
|
||||
str = `zip:${str}`;
|
||||
} break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
function fromEditorPath(str) {
|
||||
switch (hostInfo) {
|
||||
case `coc-nvim`: {
|
||||
str = str.replace(/\.zip::/, `.zip/`);
|
||||
// The path for coc-nvim is in format of /<pwd>/zipfile:/<pwd>/.yarn/...
|
||||
// So in order to convert it back, we use .* to match all the thing
|
||||
// before `zipfile:`
|
||||
return process.platform === `win32`
|
||||
? str.replace(/^.*zipfile:\//, ``)
|
||||
: str.replace(/^.*zipfile:/, ``);
|
||||
} break;
|
||||
|
||||
case `neovim`: {
|
||||
str = str.replace(/\.zip::/, `.zip/`);
|
||||
// The path for neovim is in format of zipfile:///<pwd>/.yarn/...
|
||||
return str.replace(/^zipfile:\/\//, ``);
|
||||
} break;
|
||||
|
||||
case `vscode`:
|
||||
default: {
|
||||
return str.replace(/^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/, process.platform === `win32` ? `` : `/`)
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
// Force enable 'allowLocalPluginLoads'
|
||||
// TypeScript tries to resolve plugins using a path relative to itself
|
||||
// which doesn't work when using the global cache
|
||||
// https://github.com/microsoft/TypeScript/blob/1b57a0395e0bff191581c9606aab92832001de62/src/server/project.ts#L2238
|
||||
// VSCode doesn't want to enable 'allowLocalPluginLoads' due to security concerns but
|
||||
// TypeScript already does local loads and if this code is running the user trusts the workspace
|
||||
// https://github.com/microsoft/vscode/issues/45856
|
||||
const ConfiguredProject = tsserver.server.ConfiguredProject;
|
||||
const {enablePluginsWithOptions: originalEnablePluginsWithOptions} = ConfiguredProject.prototype;
|
||||
ConfiguredProject.prototype.enablePluginsWithOptions = function() {
|
||||
this.projectService.allowLocalPluginLoads = true;
|
||||
return originalEnablePluginsWithOptions.apply(this, arguments);
|
||||
};
|
||||
|
||||
// And here is the point where we hijack the VSCode <-> TS communications
|
||||
// by adding ourselves in the middle. We locate everything that looks
|
||||
// like an absolute path of ours and normalize it.
|
||||
|
||||
const Session = tsserver.server.Session;
|
||||
const {onMessage: originalOnMessage, send: originalSend} = Session.prototype;
|
||||
let hostInfo = `unknown`;
|
||||
|
||||
Object.assign(Session.prototype, {
|
||||
onMessage(/** @type {string | object} */ message) {
|
||||
const isStringMessage = typeof message === 'string';
|
||||
const parsedMessage = isStringMessage ? JSON.parse(message) : message;
|
||||
|
||||
if (
|
||||
parsedMessage != null &&
|
||||
typeof parsedMessage === `object` &&
|
||||
parsedMessage.arguments &&
|
||||
typeof parsedMessage.arguments.hostInfo === `string`
|
||||
) {
|
||||
hostInfo = parsedMessage.arguments.hostInfo;
|
||||
if (hostInfo === `vscode` && process.env.VSCODE_IPC_HOOK) {
|
||||
const [, major, minor] = (process.env.VSCODE_IPC_HOOK.match(
|
||||
// The RegExp from https://semver.org/ but without the caret at the start
|
||||
/(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/
|
||||
) ?? []).map(Number)
|
||||
|
||||
if (major === 1) {
|
||||
if (minor < 61) {
|
||||
hostInfo += ` <1.61`;
|
||||
} else if (minor < 66) {
|
||||
hostInfo += ` <1.66`;
|
||||
} else if (minor < 68) {
|
||||
hostInfo += ` <1.68`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const processedMessageJSON = JSON.stringify(parsedMessage, (key, value) => {
|
||||
return typeof value === 'string' ? fromEditorPath(value) : value;
|
||||
});
|
||||
|
||||
return originalOnMessage.call(
|
||||
this,
|
||||
isStringMessage ? processedMessageJSON : JSON.parse(processedMessageJSON)
|
||||
);
|
||||
},
|
||||
|
||||
send(/** @type {any} */ msg) {
|
||||
return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => {
|
||||
return typeof value === `string` ? toEditorPath(value) : value;
|
||||
})));
|
||||
}
|
||||
});
|
||||
|
||||
return tsserver;
|
||||
};
|
||||
|
||||
if (existsSync(absPnpApiPath)) {
|
||||
if (!process.versions.pnp) {
|
||||
// Setup the environment to be able to require typescript/lib/tsserverlibrary.js
|
||||
require(absPnpApiPath).setup();
|
||||
}
|
||||
}
|
||||
|
||||
// Defer to the real typescript/lib/tsserverlibrary.js your application uses
|
||||
module.exports = moduleWrapper(absRequire(`typescript/lib/tsserverlibrary.js`));
|
||||
@@ -1,20 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const {existsSync} = require(`fs`);
|
||||
const {createRequire} = require(`module`);
|
||||
const {resolve} = require(`path`);
|
||||
|
||||
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||
|
||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||
const absRequire = createRequire(absPnpApiPath);
|
||||
|
||||
if (existsSync(absPnpApiPath)) {
|
||||
if (!process.versions.pnp) {
|
||||
// Setup the environment to be able to require typescript/lib/typescript.js
|
||||
require(absPnpApiPath).setup();
|
||||
}
|
||||
}
|
||||
|
||||
// Defer to the real typescript/lib/typescript.js your application uses
|
||||
module.exports = absRequire(`typescript/lib/typescript.js`);
|
||||
6
interface/.yarn/sdks/typescript/package.json
vendored
6
interface/.yarn/sdks/typescript/package.json
vendored
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"name": "typescript",
|
||||
"version": "5.0.2-sdk",
|
||||
"main": "./lib/typescript.js",
|
||||
"type": "commonjs"
|
||||
}
|
||||
@@ -1,14 +1,7 @@
|
||||
plugins:
|
||||
- path: .yarn/plugins/@yarnpkg/plugin-typescript.cjs
|
||||
spec: '@yarnpkg/plugin-typescript'
|
||||
compressionLevel: mixed
|
||||
|
||||
yarnPath: .yarn/releases/yarn-3.4.1.cjs
|
||||
enableGlobalCache: false
|
||||
|
||||
# uing pnp
|
||||
# nodeLinker: pnp
|
||||
|
||||
# use these if not using PnP and have node_modules
|
||||
nodeLinker: node-modules
|
||||
compressionLevel: 0
|
||||
nmMode: hardlinks-local
|
||||
enableGlobalCache: true
|
||||
|
||||
yarnPath: .yarn/releases/yarn-4.1.1.cjs
|
||||
|
||||
@@ -1,78 +1,78 @@
|
||||
{
|
||||
"name": "EMS-ESP",
|
||||
"version": "3.6.0",
|
||||
"version": "3.6.5",
|
||||
"description": "build EMS-ESP WebUI",
|
||||
"homepage": "https://emsesp.github.io/docs",
|
||||
"author": "proddy",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"build-hosted": "vite build --mode hosted",
|
||||
"preview": "vite preview",
|
||||
"preview-standalone": "npm-run-all -p preview typesafe-i18n mock-api",
|
||||
"mock-api": "node --watch ../mock-api ../mock-api/server.js",
|
||||
"standalone": "npm-run-all -p dev typesafe-i18n mock-api",
|
||||
"typesafe-i18n": "typesafe-i18n",
|
||||
"build-hosted": "typesafe-i18n --no-watch && vite build --mode hosted",
|
||||
"preview-standalone": "typesafe-i18n --no-watch && vite build && concurrently -c \"auto\" \"npm:mock-api\" \"vite preview\"",
|
||||
"mock-api": "bun --watch ../mock-api/server.ts",
|
||||
"old_mock-api": "bun --watch ../mock-api/server.js",
|
||||
"standalone": "concurrently -c \"auto\" \"typesafe-i18n\" \"npm:mock-api\" \"vite\"",
|
||||
"old_standalone": "concurrently -c \"auto\" \"typesafe-i18n\" \"npm:old_mock-api\" \"vite\"",
|
||||
"typesafe-i18n": "typesafe-i18n --no-watch",
|
||||
"webUI": "node progmem-generator.js",
|
||||
"format": "prettier --write '**/*.{ts,tsx,js,css,json,md}'",
|
||||
"lint": "eslint . --cache --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@alova/adapter-xhr": "^1.0.1",
|
||||
"@emotion/react": "^11.11.1",
|
||||
"@alova/adapter-xhr": "^1.0.3",
|
||||
"@babel/core": "^7.24.3",
|
||||
"@emotion/react": "^11.11.4",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@mui/icons-material": "^5.14.8",
|
||||
"@mui/material": "^5.14.8",
|
||||
"@preact/compat": "^17.1.2",
|
||||
"@prefresh/vite": "^2.4.1",
|
||||
"@mui/icons-material": "^5.15.14",
|
||||
"@mui/material": "^5.15.14",
|
||||
"@table-library/react-table-library": "4.1.7",
|
||||
"@types/lodash-es": "^4.17.9",
|
||||
"@types/node": "^20.6.0",
|
||||
"@types/react": "^18.2.21",
|
||||
"@types/react-dom": "^18.2.7",
|
||||
"@types/imagemin": "^8.0.5",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^20.11.30",
|
||||
"@types/react": "^18.2.69",
|
||||
"@types/react-dom": "^18.2.22",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"alova": "^2.11.1",
|
||||
"alova": "^2.18.0",
|
||||
"async-validator": "^4.2.5",
|
||||
"history": "^5.3.0",
|
||||
"jwt-decode": "^3.1.2",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"mime-types": "^2.1.35",
|
||||
"preact": "^10.17.1",
|
||||
"react": "latest",
|
||||
"react-dom": "latest",
|
||||
"react-dropzone": "^14.2.3",
|
||||
"react-icons": "^4.11.0",
|
||||
"react-router-dom": "^6.15.0",
|
||||
"react-toastify": "^9.1.3",
|
||||
"react-icons": "^5.0.1",
|
||||
"react-router-dom": "^6.22.3",
|
||||
"react-toastify": "^10.0.5",
|
||||
"sockette": "^2.0.6",
|
||||
"typesafe-i18n": "^5.26.2",
|
||||
"typescript": "^5.2.2"
|
||||
"typescript": "^5.4.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.22.17",
|
||||
"@preact/preset-vite": "^2.5.0",
|
||||
"@types/babel__core": "^7",
|
||||
"@typescript-eslint/eslint-plugin": "^6.6.0",
|
||||
"@typescript-eslint/parser": "^6.6.0",
|
||||
"eslint": "^8.49.0",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
"eslint-config-airbnb-typescript": "^17.1.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-import-resolver-typescript": "^3.6.0",
|
||||
"@preact/compat": "^17.1.2",
|
||||
"@preact/preset-vite": "^2.8.2",
|
||||
"@typescript-eslint/eslint-plugin": "^7.3.1",
|
||||
"@typescript-eslint/parser": "^7.3.1",
|
||||
"concurrently": "^8.2.2",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-import-resolver-typescript": "^3.6.1",
|
||||
"eslint-plugin-autofix": "^1.1.0",
|
||||
"eslint-plugin-import": "^2.28.1",
|
||||
"eslint-plugin-jsx-a11y": "^6.7.1",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-jsx-a11y": "^6.8.0",
|
||||
"eslint-plugin-prettier": "alpha",
|
||||
"eslint-plugin-react": "^7.33.2",
|
||||
"eslint-plugin-react": "^7.34.1",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^3.0.3",
|
||||
"rollup-plugin-visualizer": "^5.9.2",
|
||||
"terser": "^5.19.4",
|
||||
"vite": "^4.4.9",
|
||||
"vite-plugin-svgr": "^3.2.0",
|
||||
"vite-tsconfig-paths": "^4.2.0"
|
||||
"preact": "^10.20.1",
|
||||
"prettier": "^3.2.5",
|
||||
"rollup-plugin-visualizer": "^5.12.0",
|
||||
"terser": "^5.29.2",
|
||||
"vite": "^5.2.4",
|
||||
"vite-plugin-imagemin": "^0.6.1",
|
||||
"vite-tsconfig-paths": "^4.3.2"
|
||||
},
|
||||
"packageManager": "yarn@3.4.1"
|
||||
"packageManager": "yarn@4.1.1"
|
||||
}
|
||||
|
||||
@@ -1,10 +1,32 @@
|
||||
const { readdirSync, existsSync, unlinkSync, readFileSync, createWriteStream } = require('fs');
|
||||
const { resolve, relative, sep } = require('path');
|
||||
var zlib = require('zlib');
|
||||
var mime = require('mime-types');
|
||||
import { readdirSync, existsSync, unlinkSync, readFileSync, createWriteStream } from 'fs';
|
||||
import { resolve, relative, sep } from 'path';
|
||||
import zlib from 'zlib';
|
||||
import mime from 'mime-types';
|
||||
import crypto from 'crypto';
|
||||
|
||||
const ARDUINO_INCLUDES = '#include <Arduino.h>\n\n';
|
||||
const INDENT = ' ';
|
||||
const outputPath = '../lib/framework/WWWData.h';
|
||||
const sourcePath = './dist';
|
||||
const bytesPerLine = 20;
|
||||
var totalSize = 0;
|
||||
|
||||
const generateWWWClass = () =>
|
||||
`typedef std::function<void(const char * uri, const String & contentType, const uint8_t * content, size_t len, const String & hash)> RouteRegistrationHandler;
|
||||
// Total size is ${totalSize} bytes
|
||||
|
||||
class WWWData {
|
||||
${indent}public:
|
||||
${indent.repeat(2)}static void registerRoutes(RouteRegistrationHandler handler) {
|
||||
${fileInfo
|
||||
.map(
|
||||
(file) =>
|
||||
`${indent.repeat(3)}handler("${file.uri}", "${file.mimeType}", ${file.variable}, ${file.size}, "${file.hash}");`
|
||||
)
|
||||
.join('\n')}
|
||||
${indent.repeat(2)}}
|
||||
};
|
||||
`;
|
||||
|
||||
function getFilesSync(dir, files = []) {
|
||||
readdirSync(dir, { withFileTypes: true }).forEach((entry) => {
|
||||
@@ -18,10 +40,6 @@ function getFilesSync(dir, files = []) {
|
||||
return files;
|
||||
}
|
||||
|
||||
// function coherseToBuffer(input) {
|
||||
// return Buffer.isBuffer(input) ? input : Buffer.from(input);
|
||||
// }
|
||||
|
||||
function cleanAndOpen(path) {
|
||||
if (existsSync(path)) {
|
||||
unlinkSync(path);
|
||||
@@ -29,90 +47,68 @@ function cleanAndOpen(path) {
|
||||
return createWriteStream(path, { flags: 'w+' });
|
||||
}
|
||||
|
||||
export default function ProgmemGenerator({ outputPath = './WWWData.h', bytesPerLine = 20 }) {
|
||||
return {
|
||||
name: 'ProgmemGenerator',
|
||||
writeBundle: () => {
|
||||
console.log('Generating ' + outputPath);
|
||||
const includes = ARDUINO_INCLUDES;
|
||||
const indent = INDENT;
|
||||
const fileInfo = [];
|
||||
const writeStream = cleanAndOpen(resolve(outputPath));
|
||||
const writeFile = (relativeFilePath, buffer) => {
|
||||
const variable = 'ESP_REACT_DATA_' + fileInfo.length;
|
||||
const mimeType = mime.lookup(relativeFilePath);
|
||||
var size = 0;
|
||||
writeStream.write('const uint8_t ' + variable + '[] = {');
|
||||
// const zipBuffer = zlib.brotliCompressSync(buffer, { quality: 1 });
|
||||
const zipBuffer = zlib.gzipSync(buffer, { level: 9 });
|
||||
|
||||
try {
|
||||
const writeIncludes = () => {
|
||||
writeStream.write(includes);
|
||||
};
|
||||
// create sha
|
||||
const hashSum = crypto.createHash('sha256');
|
||||
hashSum.update(zipBuffer);
|
||||
const hash = hashSum.digest('hex');
|
||||
|
||||
const writeFile = (relativeFilePath, buffer) => {
|
||||
const variable = 'ESP_REACT_DATA_' + fileInfo.length;
|
||||
const mimeType = mime.lookup(relativeFilePath);
|
||||
var size = 0;
|
||||
writeStream.write('const uint8_t ' + variable + '[] = {');
|
||||
// const zipBuffer = zlib.brotliCompressSync(buffer, { quality: 1 });
|
||||
const zipBuffer = zlib.gzipSync(buffer);
|
||||
zipBuffer.forEach((b) => {
|
||||
if (!(size % bytesPerLine)) {
|
||||
writeStream.write('\n');
|
||||
writeStream.write(indent);
|
||||
}
|
||||
writeStream.write('0x' + ('00' + b.toString(16).toUpperCase()).substr(-2) + ',');
|
||||
size++;
|
||||
});
|
||||
if (size % bytesPerLine) {
|
||||
writeStream.write('\n');
|
||||
}
|
||||
writeStream.write('};\n\n');
|
||||
fileInfo.push({
|
||||
uri: '/' + relativeFilePath.replace(sep, '/'),
|
||||
mimeType,
|
||||
variable,
|
||||
size
|
||||
});
|
||||
};
|
||||
|
||||
const writeFiles = () => {
|
||||
// process static files
|
||||
const buildPath = resolve('build');
|
||||
for (const filePath of getFilesSync(buildPath)) {
|
||||
const readStream = readFileSync(filePath);
|
||||
const relativeFilePath = relative(buildPath, filePath);
|
||||
writeFile(relativeFilePath, readStream);
|
||||
}
|
||||
|
||||
// process assets
|
||||
// const { assets } = compilation;
|
||||
// Object.keys(assets).forEach((relativeFilePath) => {
|
||||
// writeFile(relativeFilePath, coherseToBuffer(assets[relativeFilePath].source()));
|
||||
// });
|
||||
};
|
||||
|
||||
const generateWWWClass = () =>
|
||||
`typedef std::function<void(const String& uri, const String& contentType, const uint8_t * content, size_t len)> RouteRegistrationHandler;
|
||||
|
||||
class WWWData {
|
||||
${indent}public:
|
||||
${indent.repeat(2)}static void registerRoutes(RouteRegistrationHandler handler) {
|
||||
${fileInfo
|
||||
.map((file) => `${indent.repeat(3)}handler("${file.uri}", "${file.mimeType}", ${file.variable}, ${file.size});`)
|
||||
.join('\n')}
|
||||
${indent.repeat(2)}}
|
||||
};
|
||||
`;
|
||||
const writeWWWClass = () => {
|
||||
writeStream.write(generateWWWClass());
|
||||
};
|
||||
|
||||
writeIncludes();
|
||||
writeFiles();
|
||||
writeWWWClass();
|
||||
|
||||
writeStream.on('finish', () => {
|
||||
// callback();
|
||||
});
|
||||
} finally {
|
||||
writeStream.end();
|
||||
}
|
||||
zipBuffer.forEach((b) => {
|
||||
if (!(size % bytesPerLine)) {
|
||||
writeStream.write('\n');
|
||||
writeStream.write(indent);
|
||||
}
|
||||
};
|
||||
writeStream.write('0x' + ('00' + b.toString(16).toUpperCase()).slice(-2) + ',');
|
||||
size++;
|
||||
});
|
||||
|
||||
if (size % bytesPerLine) {
|
||||
writeStream.write('\n');
|
||||
}
|
||||
|
||||
writeStream.write('};\n\n');
|
||||
|
||||
fileInfo.push({
|
||||
uri: '/' + relativeFilePath.replace(sep, '/'),
|
||||
mimeType,
|
||||
variable,
|
||||
size,
|
||||
hash
|
||||
});
|
||||
|
||||
// console.log(relativeFilePath + ' (size ' + size + ' bytes)');
|
||||
totalSize += size;
|
||||
};
|
||||
|
||||
// start
|
||||
console.log('Generating ' + outputPath + ' from ' + sourcePath);
|
||||
const includes = ARDUINO_INCLUDES;
|
||||
const indent = INDENT;
|
||||
const fileInfo = [];
|
||||
const writeStream = cleanAndOpen(resolve(outputPath));
|
||||
|
||||
// includes
|
||||
writeStream.write(includes);
|
||||
|
||||
// process static files
|
||||
const buildPath = resolve(sourcePath);
|
||||
for (const filePath of getFilesSync(buildPath)) {
|
||||
const readStream = readFileSync(filePath);
|
||||
const relativeFilePath = relative(buildPath, filePath);
|
||||
writeFile(relativeFilePath, readStream);
|
||||
}
|
||||
|
||||
// add class
|
||||
writeStream.write(generateWWWClass());
|
||||
|
||||
// end
|
||||
writeStream.end();
|
||||
|
||||
console.log('Total size: ' + totalSize / 1000 + ' KB');
|
||||
|
||||
Binary file not shown.
@@ -4,7 +4,6 @@ import { ToastContainer, Slide } from 'react-toastify';
|
||||
import 'react-toastify/dist/ReactToastify.min.css';
|
||||
|
||||
import { localStorageDetector } from 'typesafe-i18n/detectors';
|
||||
import { FeaturesLoader } from './contexts/features';
|
||||
import type { FC } from 'react';
|
||||
import AppRouting from 'AppRouting';
|
||||
import CustomTheme from 'CustomTheme';
|
||||
@@ -27,9 +26,7 @@ const App: FC = () => {
|
||||
return (
|
||||
<TypesafeI18n locale={detectedLocale}>
|
||||
<CustomTheme>
|
||||
<FeaturesLoader>
|
||||
<AppRouting />
|
||||
</FeaturesLoader>
|
||||
<AppRouting />
|
||||
<ToastContainer
|
||||
position="bottom-left"
|
||||
autoClose={3000}
|
||||
|
||||
@@ -26,6 +26,9 @@ const theme = responsiveFontSizes(
|
||||
},
|
||||
info: {
|
||||
main: '#607d8b' // blueGrey[500]
|
||||
},
|
||||
text: {
|
||||
disabled: '#eee' // white
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -3,7 +3,6 @@ import { Box, Paper, Typography, MenuItem, TextField, Button } from '@mui/materi
|
||||
import { useRequest } from 'alova';
|
||||
import { useContext, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { FeaturesContext } from './contexts/features';
|
||||
import type { ValidateFieldsError } from 'async-validator';
|
||||
|
||||
import type { Locales } from 'i18n/i18n-types';
|
||||
@@ -15,15 +14,16 @@ import { PROJECT_NAME } from 'api/env';
|
||||
import { ValidatedPasswordField, ValidatedTextField } from 'components';
|
||||
import { AuthenticationContext } from 'contexts/authentication';
|
||||
|
||||
import { ReactComponent as DEflag } from 'i18n/DE.svg';
|
||||
import { ReactComponent as FRflag } from 'i18n/FR.svg';
|
||||
import { ReactComponent as GBflag } from 'i18n/GB.svg';
|
||||
import { ReactComponent as ITflag } from 'i18n/IT.svg';
|
||||
import { ReactComponent as NLflag } from 'i18n/NL.svg';
|
||||
import { ReactComponent as NOflag } from 'i18n/NO.svg';
|
||||
import { ReactComponent as PLflag } from 'i18n/PL.svg';
|
||||
import { ReactComponent as SVflag } from 'i18n/SV.svg';
|
||||
import { ReactComponent as TRflag } from 'i18n/TR.svg';
|
||||
import DEflag from 'i18n/DE.svg';
|
||||
import FRflag from 'i18n/FR.svg';
|
||||
import GBflag from 'i18n/GB.svg';
|
||||
import ITflag from 'i18n/IT.svg';
|
||||
import NLflag from 'i18n/NL.svg';
|
||||
import NOflag from 'i18n/NO.svg';
|
||||
import PLflag from 'i18n/PL.svg';
|
||||
import SKflag from 'i18n/SK.svg';
|
||||
import SVflag from 'i18n/SV.svg';
|
||||
import TRflag from 'i18n/TR.svg';
|
||||
import { I18nContext } from 'i18n/i18n-react';
|
||||
import { loadLocaleAsync } from 'i18n/i18n-util.async';
|
||||
import { onEnterCallback, updateValue } from 'utils';
|
||||
@@ -34,8 +34,6 @@ const SignIn: FC = () => {
|
||||
|
||||
const { LL, setLocale, locale } = useContext(I18nContext);
|
||||
|
||||
const { features } = useContext(FeaturesContext);
|
||||
|
||||
const [signInRequest, setSignInRequest] = useState<SignInRequest>({
|
||||
username: '',
|
||||
password: ''
|
||||
@@ -111,43 +109,46 @@ const SignIn: FC = () => {
|
||||
})}
|
||||
>
|
||||
<Typography variant="h4">{PROJECT_NAME}</Typography>
|
||||
<Typography variant="subtitle2">{features.version}</Typography>
|
||||
|
||||
<TextField name="locale" variant="outlined" value={locale} onChange={onLocaleSelected} size="small" select>
|
||||
<MenuItem key="de" value="de">
|
||||
<DEflag style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
<img src={DEflag} style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
DE
|
||||
</MenuItem>
|
||||
<MenuItem key="en" value="en">
|
||||
<GBflag style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
<img src={GBflag} style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
EN
|
||||
</MenuItem>
|
||||
<MenuItem key="fr" value="fr">
|
||||
<FRflag style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
<img src={FRflag} style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
FR
|
||||
</MenuItem>
|
||||
<MenuItem key="it" value="it">
|
||||
<ITflag style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
<img src={ITflag} style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
IT
|
||||
</MenuItem>
|
||||
<MenuItem key="nl" value="nl">
|
||||
<NLflag style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
<img src={NLflag} style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
NL
|
||||
</MenuItem>
|
||||
<MenuItem key="no" value="no">
|
||||
<NOflag style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
<img src={NOflag} style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
NO
|
||||
</MenuItem>
|
||||
<MenuItem key="pl" value="pl">
|
||||
<PLflag style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
<img src={PLflag} style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
PL
|
||||
</MenuItem>
|
||||
<MenuItem key="sk" value="sk">
|
||||
<img src={SKflag} style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
SK
|
||||
</MenuItem>
|
||||
<MenuItem key="sv" value="sv">
|
||||
<SVflag style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
<img src={SVflag} style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
SV
|
||||
</MenuItem>
|
||||
<MenuItem key="tr" value="tr">
|
||||
<TRflag style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
<img src={TRflag} style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
TR
|
||||
</MenuItem>
|
||||
</TextField>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import jwtDecode from 'jwt-decode';
|
||||
import { jwtDecode } from 'jwt-decode';
|
||||
import { ACCESS_TOKEN, alovaInstance } from './endpoints';
|
||||
import type * as H from 'history';
|
||||
import type { Path } from 'react-router-dom';
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import { alovaInstance } from './endpoints';
|
||||
|
||||
import type { Features } from 'types';
|
||||
|
||||
export const readFeatures = () => alovaInstance.Get<Features>('/rest/features');
|
||||
@@ -498,8 +498,8 @@ function createStructureReader(structure, firstId) {
|
||||
key === '__proto__'
|
||||
? '__proto_:r()'
|
||||
: validName.test(key)
|
||||
? key + ':r()'
|
||||
: '[' + JSON.stringify(key) + ']:r()'
|
||||
? key + ':r()'
|
||||
: '[' + JSON.stringify(key) + ']:r()'
|
||||
)
|
||||
.join(',') +
|
||||
'})}'
|
||||
|
||||
@@ -14,20 +14,20 @@ import {
|
||||
} from '@mui/material';
|
||||
import { useState, useContext } from 'react';
|
||||
import type { TypographyProps } from '@mui/material';
|
||||
|
||||
import type { Locales } from 'i18n/i18n-types';
|
||||
import type { FC, ChangeEventHandler } from 'react';
|
||||
import { AuthenticatedContext } from 'contexts/authentication';
|
||||
import DEflag from 'i18n/DE.svg';
|
||||
import FRflag from 'i18n/FR.svg';
|
||||
import GBflag from 'i18n/GB.svg';
|
||||
import ITflag from 'i18n/IT.svg';
|
||||
import NLflag from 'i18n/NL.svg';
|
||||
import NOflag from 'i18n/NO.svg';
|
||||
import PLflag from 'i18n/PL.svg';
|
||||
import SKflag from 'i18n/SK.svg';
|
||||
import SVflag from 'i18n/SV.svg';
|
||||
import TRflag from 'i18n/TR.svg';
|
||||
|
||||
import { ReactComponent as DEflag } from 'i18n/DE.svg';
|
||||
import { ReactComponent as FRflag } from 'i18n/FR.svg';
|
||||
import { ReactComponent as GBflag } from 'i18n/GB.svg';
|
||||
import { ReactComponent as ITflag } from 'i18n/IT.svg';
|
||||
import { ReactComponent as NLflag } from 'i18n/NL.svg';
|
||||
import { ReactComponent as NOflag } from 'i18n/NO.svg';
|
||||
import { ReactComponent as PLflag } from 'i18n/PL.svg';
|
||||
import { ReactComponent as SVflag } from 'i18n/SV.svg';
|
||||
import { ReactComponent as TRflag } from 'i18n/TR.svg';
|
||||
import { I18nContext } from 'i18n/i18n-react';
|
||||
import { loadLocaleAsync } from 'i18n/i18n-util.async';
|
||||
|
||||
@@ -75,39 +75,43 @@ const LayoutAuthMenu: FC = () => {
|
||||
select
|
||||
>
|
||||
<MenuItem key="de" value="de">
|
||||
<DEflag style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
<img src={DEflag} style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
DE
|
||||
</MenuItem>
|
||||
<MenuItem key="en" value="en">
|
||||
<GBflag style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
<img src={GBflag} style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
EN
|
||||
</MenuItem>
|
||||
<MenuItem key="fr" value="fr">
|
||||
<FRflag style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
<img src={FRflag} style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
FR
|
||||
</MenuItem>
|
||||
<MenuItem key="it" value="it">
|
||||
<ITflag style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
<img src={ITflag} style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
IT
|
||||
</MenuItem>
|
||||
<MenuItem key="nl" value="nl">
|
||||
<NLflag style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
<img src={NLflag} style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
NL
|
||||
</MenuItem>
|
||||
<MenuItem key="no" value="no">
|
||||
<NOflag style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
<img src={NOflag} style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
NO
|
||||
</MenuItem>
|
||||
<MenuItem key="pl" value="pl">
|
||||
<PLflag style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
<img src={PLflag} style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
PL
|
||||
</MenuItem>
|
||||
<MenuItem key="sk" value="sk">
|
||||
<img src={SKflag} style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
SK
|
||||
</MenuItem>
|
||||
<MenuItem key="sv" value="sv">
|
||||
<SVflag style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
<img src={SVflag} style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
SV
|
||||
</MenuItem>
|
||||
<MenuItem key="tr" value="tr">
|
||||
<TRflag style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
<img src={TRflag} style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
TR
|
||||
</MenuItem>
|
||||
</TextField>
|
||||
|
||||
@@ -14,7 +14,7 @@ const RouterTabs: FC<RouterTabsProps> = ({ value, children }) => {
|
||||
const theme = useTheme();
|
||||
const smallDown = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
|
||||
const handleTabChange = (event: React.ChangeEvent<HTMLInputElement>, path: string) => {
|
||||
const handleTabChange = (_event: any, path: string) => {
|
||||
navigate(path);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { useMatch, useResolvedPath } from 'react-router-dom';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
export const useRouterTab = () => {
|
||||
const routerTabPath = useResolvedPath(':tab');
|
||||
const routerTabPathMatch = useMatch(routerTabPath.pathname);
|
||||
const loc = useLocation().pathname;
|
||||
const routerTab = loc.substring(0, loc.lastIndexOf('/')) ? loc : false;
|
||||
|
||||
// const routerTabPath = useResolvedPath(':tab');
|
||||
// const routerTabPathMatch = useMatch(routerTabPath.pathname);
|
||||
// const routerTab = routerTabPathMatch?.params?.tab || false;
|
||||
|
||||
const routerTab = routerTabPathMatch?.params?.tab || false;
|
||||
return { routerTab } as const;
|
||||
};
|
||||
|
||||
@@ -50,8 +50,10 @@ const SingleUpload: FC<SingleUploadProps> = ({ onDrop, onCancel, isUploading, pr
|
||||
|
||||
const progressText = () => {
|
||||
if (uploading) {
|
||||
if (progress.total) {
|
||||
return LL.UPLOADING() + ': ' + Math.round((progress.loaded * 100) / progress.total) + '%';
|
||||
if (progress.total && progress.loaded) {
|
||||
return progress.loaded <= progress.total
|
||||
? LL.UPLOADING() + ': ' + Math.round((progress.loaded * 100) / progress.total) + '%'
|
||||
: LL.UPLOADING() + ': ' + Math.round((progress.total * 100) / progress.loaded) + '%';
|
||||
}
|
||||
}
|
||||
return LL.UPLOAD_DROP_TEXT();
|
||||
@@ -83,7 +85,13 @@ const SingleUpload: FC<SingleUploadProps> = ({ onDrop, onCancel, isUploading, pr
|
||||
<Box width="100%" p={2}>
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={progress.total === 0 ? 0 : Math.round((progress.loaded * 100) / progress.total)}
|
||||
value={
|
||||
progress.total === 0 || progress.loaded === 0
|
||||
? 0
|
||||
: progress.loaded <= progress.total
|
||||
? Math.round((progress.loaded * 100) / progress.total)
|
||||
: Math.round((progress.total * 100) / progress.loaded)
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
<Button startIcon={<CancelIcon />} variant="outlined" color="secondary" onClick={onCancel}>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useRequest } from 'alova';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { redirect } from 'react-router-dom';
|
||||
import { toast } from 'react-toastify';
|
||||
import { AuthenticationContext } from './context';
|
||||
import type { FC } from 'react';
|
||||
@@ -15,8 +15,6 @@ import { useI18nContext } from 'i18n/i18n-react';
|
||||
const Authentication: FC<RequiredChildrenProps> = ({ children }) => {
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [initialized, setInitialized] = useState<boolean>(false);
|
||||
const [me, setMe] = useState<Me>();
|
||||
|
||||
@@ -36,11 +34,12 @@ const Authentication: FC<RequiredChildrenProps> = ({ children }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const signOut = (redirect: boolean) => {
|
||||
const signOut = (doRedirect: boolean) => {
|
||||
AuthenticationApi.clearAccessToken();
|
||||
setMe(undefined);
|
||||
if (redirect) {
|
||||
navigate('/');
|
||||
if (doRedirect) {
|
||||
// navigate('/');
|
||||
redirect('/');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import { useRequest } from 'alova';
|
||||
|
||||
import { FeaturesContext } from '.';
|
||||
import type { FC } from 'react';
|
||||
|
||||
import type { RequiredChildrenProps } from 'utils';
|
||||
import * as FeaturesApi from 'api/features';
|
||||
|
||||
const FeaturesLoader: FC<RequiredChildrenProps> = (props) => {
|
||||
const { data: features } = useRequest(FeaturesApi.readFeatures);
|
||||
|
||||
if (features) {
|
||||
return (
|
||||
<FeaturesContext.Provider
|
||||
value={{
|
||||
features
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</FeaturesContext.Provider>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default FeaturesLoader;
|
||||
@@ -1,10 +0,0 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
import type { Features } from 'types';
|
||||
|
||||
export interface FeaturesContextValue {
|
||||
features: Features;
|
||||
}
|
||||
|
||||
const FeaturesContextDefaultValue = {} as FeaturesContextValue;
|
||||
export const FeaturesContext = createContext(FeaturesContextDefaultValue);
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './context';
|
||||
export { default as FeaturesLoader } from './FeaturesLoader';
|
||||
@@ -22,8 +22,12 @@ const AccessPoint: FC = () => {
|
||||
return (
|
||||
<>
|
||||
<RouterTabs value={routerTab}>
|
||||
<Tab value="status" label={LL.STATUS_OF(LL.ACCESS_POINT(1))} />
|
||||
<Tab value="settings" label={LL.SETTINGS_OF(LL.ACCESS_POINT(1))} disabled={!authenticatedContext.me.admin} />
|
||||
<Tab value="/ap/status" label={LL.STATUS_OF(LL.ACCESS_POINT(1))} />
|
||||
<Tab
|
||||
value="/ap/settings"
|
||||
label={LL.SETTINGS_OF(LL.ACCESS_POINT(1))}
|
||||
disabled={!authenticatedContext.me.admin}
|
||||
/>
|
||||
</RouterTabs>
|
||||
<Routes>
|
||||
<Route path="status" element={<APStatusForm />} />
|
||||
@@ -36,7 +40,7 @@ const AccessPoint: FC = () => {
|
||||
</RequireAdmin>
|
||||
}
|
||||
/>
|
||||
<Route path="/*" element={<Navigate replace to="status" />} />
|
||||
<Route path="*" element={<Navigate replace to="/ap/status" />} />
|
||||
</Routes>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -21,8 +21,8 @@ const Mqtt: FC = () => {
|
||||
return (
|
||||
<>
|
||||
<RouterTabs value={routerTab}>
|
||||
<Tab value="status" label={LL.STATUS_OF('MQTT')} />
|
||||
<Tab value="settings" label={LL.SETTINGS_OF('MQTT')} disabled={!authenticatedContext.me.admin} />
|
||||
<Tab value="/mqtt/status" label={LL.STATUS_OF('MQTT')} />
|
||||
<Tab value="/mqtt/settings" label={LL.SETTINGS_OF('MQTT')} disabled={!authenticatedContext.me.admin} />
|
||||
</RouterTabs>
|
||||
<Routes>
|
||||
<Route path="status" element={<MqttStatusForm />} />
|
||||
@@ -34,7 +34,7 @@ const Mqtt: FC = () => {
|
||||
</RequireAdmin>
|
||||
}
|
||||
/>
|
||||
<Route path="/*" element={<Navigate replace to="status" />} />
|
||||
<Route path="*" element={<Navigate replace to="/mqtt/status" />} />
|
||||
</Routes>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -72,6 +72,7 @@ const MqttSettingsForm: FC = () => {
|
||||
name="host"
|
||||
label={LL.ADDRESS_OF(LL.BROKER())}
|
||||
fullWidth
|
||||
multiline
|
||||
variant="outlined"
|
||||
value={data.host}
|
||||
onChange={updateFormValue}
|
||||
@@ -168,20 +169,24 @@ const MqttSettingsForm: FC = () => {
|
||||
<MenuItem value={2}>2</MenuItem>
|
||||
</TextField>
|
||||
</Grid>
|
||||
{data.rootCA !== undefined && (
|
||||
<Grid item xs={12} sm={6}>
|
||||
<ValidatedPasswordField
|
||||
name="rootCA"
|
||||
label={LL.CERT()}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.rootCA}
|
||||
onChange={updateFormValue}
|
||||
margin="normal"
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
{data.enableTLS !== undefined && (
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox name="enableTLS" checked={data.enableTLS} onChange={updateFormValue} />}
|
||||
label={LL.ENABLE_TLS()}
|
||||
/>
|
||||
)}
|
||||
{data.enableTLS === true && (
|
||||
<ValidatedPasswordField
|
||||
name="rootCA"
|
||||
label={LL.CERT()}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.rootCA}
|
||||
onChange={updateFormValue}
|
||||
margin="normal"
|
||||
/>
|
||||
)}
|
||||
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox name="clean_session" checked={data.clean_session} onChange={updateFormValue} />}
|
||||
@@ -269,6 +274,7 @@ const MqttSettingsForm: FC = () => {
|
||||
>
|
||||
<MenuItem value={0}>Home Assistant</MenuItem>
|
||||
<MenuItem value={1}>Domoticz</MenuItem>
|
||||
<MenuItem value={2}>Domoticz (latest)</MenuItem>
|
||||
</TextField>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6} md={4}>
|
||||
|
||||
@@ -69,7 +69,7 @@ const MqttStatusForm: FC = () => {
|
||||
case MqttDisconnectReason.MQTT_NOT_AUTHORIZED:
|
||||
return 'Not authorized';
|
||||
case MqttDisconnectReason.TLS_BAD_FINGERPRINT:
|
||||
return 'TSL fingerprint invalid';
|
||||
return 'TLS fingerprint invalid';
|
||||
default:
|
||||
return 'Unknown';
|
||||
}
|
||||
|
||||
@@ -44,9 +44,13 @@ const NetworkConnection: FC = () => {
|
||||
}}
|
||||
>
|
||||
<RouterTabs value={routerTab}>
|
||||
<Tab value="status" label={LL.STATUS_OF(LL.NETWORK(1))} />
|
||||
<Tab value="scan" label={LL.NETWORK_SCAN()} disabled={!authenticatedContext.me.admin} />
|
||||
<Tab value="settings" label={LL.SETTINGS_OF(LL.NETWORK(1))} disabled={!authenticatedContext.me.admin} />
|
||||
<Tab value="/network/status" label={LL.STATUS_OF(LL.NETWORK(1))} />
|
||||
<Tab value="/network/scan" label={LL.NETWORK_SCAN()} disabled={!authenticatedContext.me.admin} />
|
||||
<Tab
|
||||
value="/network/settings"
|
||||
label={LL.SETTINGS_OF(LL.NETWORK(1))}
|
||||
disabled={!authenticatedContext.me.admin}
|
||||
/>
|
||||
</RouterTabs>
|
||||
<Routes>
|
||||
<Route path="status" element={<NetworkStatusForm />} />
|
||||
@@ -66,7 +70,7 @@ const NetworkConnection: FC = () => {
|
||||
</RequireAdmin>
|
||||
}
|
||||
/>
|
||||
<Route path="/*" element={<Navigate replace to="status" />} />
|
||||
<Route path="*" element={<Navigate replace to="/network/status" />} />
|
||||
</Routes>
|
||||
</WiFiConnectionContext.Provider>
|
||||
);
|
||||
|
||||
@@ -15,8 +15,8 @@ import {
|
||||
ListItemSecondaryAction,
|
||||
ListItemText,
|
||||
Typography,
|
||||
InputAdornment,
|
||||
TextField
|
||||
TextField,
|
||||
MenuItem
|
||||
} from '@mui/material';
|
||||
// eslint-disable-next-line import/named
|
||||
import { updateState, useRequest } from 'alova';
|
||||
@@ -43,7 +43,7 @@ import {
|
||||
} from 'components';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
import { numberValue, updateValueDirty, useRest } from 'utils';
|
||||
import { updateValueDirty, useRest } from 'utils';
|
||||
|
||||
import { validate } from 'validators';
|
||||
import { createNetworkSettingsValidator } from 'validators/network';
|
||||
@@ -82,12 +82,13 @@ const WiFiSettingsForm: FC = () => {
|
||||
if (selectedNetwork) {
|
||||
updateState('networkSettings', (current_data) => ({
|
||||
ssid: selectedNetwork.ssid,
|
||||
password: '',
|
||||
bssid: selectedNetwork.bssid,
|
||||
password: current_data ? current_data.password : '',
|
||||
hostname: current_data?.hostname,
|
||||
static_ip_config: false,
|
||||
enableIPv6: false,
|
||||
bandwidth20: false,
|
||||
tx_power: 20,
|
||||
tx_power: 0,
|
||||
nosleep: false,
|
||||
enableMDNS: true,
|
||||
enableCORS: false,
|
||||
@@ -117,6 +118,12 @@ const WiFiSettingsForm: FC = () => {
|
||||
} catch (errors: any) {
|
||||
setFieldErrors(errors);
|
||||
}
|
||||
deselectNetwork();
|
||||
};
|
||||
|
||||
const setCancel = async () => {
|
||||
deselectNetwork();
|
||||
await loadData();
|
||||
};
|
||||
|
||||
const restart = async () => {
|
||||
@@ -139,10 +146,17 @@ const WiFiSettingsForm: FC = () => {
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
primary={selectedNetwork.ssid}
|
||||
secondary={'Security: ' + networkSecurityMode(selectedNetwork) + ', Ch: ' + selectedNetwork.channel}
|
||||
secondary={
|
||||
'Security: ' +
|
||||
networkSecurityMode(selectedNetwork) +
|
||||
', Ch: ' +
|
||||
selectedNetwork.channel +
|
||||
', bssid: ' +
|
||||
selectedNetwork.bssid
|
||||
}
|
||||
/>
|
||||
<ListItemSecondaryAction>
|
||||
<IconButton onClick={deselectNetwork}>
|
||||
<IconButton onClick={setCancel}>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</ListItemSecondaryAction>
|
||||
@@ -160,6 +174,16 @@ const WiFiSettingsForm: FC = () => {
|
||||
margin="normal"
|
||||
/>
|
||||
)}
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="bssid"
|
||||
label={'BSSID (' + LL.NETWORK_BLANK_BSSID() + ')'}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.bssid}
|
||||
onChange={updateFormValue}
|
||||
margin="normal"
|
||||
/>
|
||||
{(!selectedNetwork || !isNetworkOpen(selectedNetwork)) && (
|
||||
<ValidatedPasswordField
|
||||
fieldErrors={fieldErrors}
|
||||
@@ -172,20 +196,29 @@ const WiFiSettingsForm: FC = () => {
|
||||
margin="normal"
|
||||
/>
|
||||
)}
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
<TextField
|
||||
name="tx_power"
|
||||
label={LL.TX_POWER()}
|
||||
InputProps={{
|
||||
endAdornment: <InputAdornment position="end">dBm</InputAdornment>
|
||||
}}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={numberValue(data.tx_power)}
|
||||
value={data.tx_power}
|
||||
onChange={updateFormValue}
|
||||
type="number"
|
||||
margin="normal"
|
||||
/>
|
||||
select
|
||||
>
|
||||
<MenuItem value={0}>Auto</MenuItem>
|
||||
<MenuItem value={78}>19.5 dBm</MenuItem>
|
||||
<MenuItem value={76}>19 dBm</MenuItem>
|
||||
<MenuItem value={74}>18.5 dBm</MenuItem>
|
||||
<MenuItem value={68}>17 dBm</MenuItem>
|
||||
<MenuItem value={60}>15 dBm</MenuItem>
|
||||
<MenuItem value={52}>13 dBm</MenuItem>
|
||||
<MenuItem value={44}>11 dBm</MenuItem>
|
||||
<MenuItem value={34}>8.5 dBm</MenuItem>
|
||||
<MenuItem value={28}>7 dBm</MenuItem>
|
||||
<MenuItem value={20}>5 dBm</MenuItem>
|
||||
<MenuItem value={8}>2 dBm</MenuItem>
|
||||
</TextField>
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox name="nosleep" checked={data.nosleep} onChange={updateFormValue} />}
|
||||
label={LL.NETWORK_DISABLE_SLEEP()}
|
||||
@@ -226,10 +259,12 @@ const WiFiSettingsForm: FC = () => {
|
||||
margin="normal"
|
||||
/>
|
||||
)}
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox name="enableIPv6" checked={data.enableIPv6} onChange={updateFormValue} />}
|
||||
label={LL.NETWORK_ENABLE_IPV6()}
|
||||
/>
|
||||
{data.enableIPv6 !== undefined && (
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox name="enableIPv6" checked={data.enableIPv6} onChange={updateFormValue} />}
|
||||
label={LL.NETWORK_ENABLE_IPV6()}
|
||||
/>
|
||||
)}
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox name="static_ip_config" checked={data.static_ip_config} onChange={updateFormValue} />}
|
||||
label={LL.NETWORK_FIXED_IP()}
|
||||
@@ -289,14 +324,14 @@ const WiFiSettingsForm: FC = () => {
|
||||
</>
|
||||
)}
|
||||
{restartNeeded && (
|
||||
<MessageBox my={2} level="warning" message={LL.RESTART_TEXT()}>
|
||||
<MessageBox my={2} level="warning" message={LL.RESTART_TEXT(0)}>
|
||||
<Button startIcon={<PowerSettingsNewIcon />} variant="contained" color="error" onClick={restart}>
|
||||
{LL.RESTART()}
|
||||
</Button>
|
||||
</MessageBox>
|
||||
)}
|
||||
|
||||
{!restartNeeded && dirtyFlags && dirtyFlags.length !== 0 && (
|
||||
{!restartNeeded && (selectedNetwork || (dirtyFlags && dirtyFlags.length !== 0)) && (
|
||||
<ButtonRow>
|
||||
<Button
|
||||
startIcon={<CancelIcon />}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import DeviceHubIcon from '@mui/icons-material/DeviceHub';
|
||||
import DnsIcon from '@mui/icons-material/Dns';
|
||||
import GiteIcon from '@mui/icons-material/Gite';
|
||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||
import RouterIcon from '@mui/icons-material/Router';
|
||||
import SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna';
|
||||
@@ -115,6 +116,15 @@ const NetworkStatusForm: FC = () => {
|
||||
<ListItemText primary="Status" secondary={networkStatus(data)} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
<Avatar sx={{ bgcolor: networkStatusHighlight(data, theme) }}>
|
||||
<GiteIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="Hostname" secondary={data.hostname} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
{isWiFi(data) && (
|
||||
<>
|
||||
<ListItem>
|
||||
|
||||
@@ -65,7 +65,9 @@ const WiFiNetworkSelector: FC<WiFiNetworkSelectorProps> = ({ networkList }) => {
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
primary={network.ssid}
|
||||
secondary={'Security: ' + networkSecurityMode(network) + ', Ch: ' + network.channel}
|
||||
secondary={
|
||||
'Security: ' + networkSecurityMode(network) + ', Ch: ' + network.channel + ', bssid: ' + network.bssid
|
||||
}
|
||||
/>
|
||||
<ListItemIcon>
|
||||
<Badge badgeContent={network.rssi + 'dBm'}>
|
||||
|
||||
@@ -20,8 +20,8 @@ const NetworkTime: FC = () => {
|
||||
return (
|
||||
<>
|
||||
<RouterTabs value={routerTab}>
|
||||
<Tab value="status" label={LL.STATUS_OF('NTP')} />
|
||||
<Tab value="settings" label={LL.SETTINGS_OF('NTP')} disabled={!authenticatedContext.me.admin} />
|
||||
<Tab value="/ntp/status" label={LL.STATUS_OF('NTP')} />
|
||||
<Tab value="/ntp/settings" label={LL.SETTINGS_OF('NTP')} disabled={!authenticatedContext.me.admin} />
|
||||
</RouterTabs>
|
||||
<Routes>
|
||||
<Route path="status" element={<NTPStatusForm />} />
|
||||
@@ -33,7 +33,7 @@ const NetworkTime: FC = () => {
|
||||
</RequireAdmin>
|
||||
}
|
||||
/>
|
||||
<Route path="/*" element={<Navigate replace to="status" />} />
|
||||
<Route path="*" element={<Navigate replace to="/ntp/status" />} />
|
||||
</Routes>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -37,7 +37,8 @@ const GenerateToken: FC<GenerateTokenProps> = ({ username, onClose }) => {
|
||||
if (open) {
|
||||
void generateToken();
|
||||
}
|
||||
}, [open, generateToken]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [open]);
|
||||
|
||||
return (
|
||||
<Dialog sx={dialogStyle} onClose={onClose} open={!!username} fullWidth maxWidth="sm">
|
||||
|
||||
@@ -12,7 +12,7 @@ import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-li
|
||||
import { useTheme } from '@table-library/react-table-library/theme';
|
||||
import { useContext, useState } from 'react';
|
||||
|
||||
import { unstable_useBlocker as useBlocker } from 'react-router-dom';
|
||||
import { useBlocker } from 'react-router-dom';
|
||||
import GenerateToken from './GenerateToken';
|
||||
import UserForm from './UserForm';
|
||||
import type { FC } from 'react';
|
||||
|
||||
@@ -17,13 +17,13 @@ const Security: FC = () => {
|
||||
return (
|
||||
<>
|
||||
<RouterTabs value={routerTab}>
|
||||
<Tab value="users" label={LL.MANAGE_USERS()} />
|
||||
<Tab value="settings" label={LL.SETTINGS_OF(LL.SECURITY(1))} />
|
||||
<Tab value="/security/users" label={LL.MANAGE_USERS()} />
|
||||
<Tab value="/security/settings" label={LL.SETTINGS_OF(LL.SECURITY(1))} />
|
||||
</RouterTabs>
|
||||
<Routes>
|
||||
<Route path="users" element={<ManageUsersForm />} />
|
||||
<Route path="settings" element={<SecuritySettingsForm />} />
|
||||
<Route path="/*" element={<Navigate replace to="users" />} />
|
||||
<Route path="*" element={<Navigate replace to="/security/users" />} />
|
||||
</Routes>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -23,10 +23,10 @@ const System: FC = () => {
|
||||
return (
|
||||
<>
|
||||
<RouterTabs value={routerTab}>
|
||||
<Tab value="status" label={LL.STATUS_OF(LL.SYSTEM(1))} />
|
||||
<Tab value="log" label={LL.LOG_OF(LL.SYSTEM(2))} />
|
||||
<Tab value="ota" label={LL.SETTINGS_OF('OTA')} disabled={!me.admin} />
|
||||
<Tab value="upload" label={LL.UPLOAD_DOWNLOAD()} disabled={!me.admin} />
|
||||
<Tab value="/system/status" label={LL.STATUS_OF(LL.SYSTEM(1))} />
|
||||
<Tab value="/system/log" label={LL.LOG_OF(LL.SYSTEM(2))} />
|
||||
<Tab value="/system/ota" label={LL.SETTINGS_OF('OTA')} disabled={!me.admin} />
|
||||
<Tab value="/system/upload" label={LL.UPLOAD_DOWNLOAD()} disabled={!me.admin} />
|
||||
</RouterTabs>
|
||||
<Routes>
|
||||
<Route path="status" element={<SystemStatusForm />} />
|
||||
@@ -47,7 +47,7 @@ const System: FC = () => {
|
||||
</RequireAdmin>
|
||||
}
|
||||
/>
|
||||
<Route path="/*" element={<Navigate replace to="status" />} />
|
||||
<Route path="*" element={<Navigate replace to="/system/status" />} />
|
||||
</Routes>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import AppsIcon from '@mui/icons-material/Apps';
|
||||
import BuildIcon from '@mui/icons-material/Build';
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import DeveloperBoardIcon from '@mui/icons-material/DeveloperBoard';
|
||||
import DevicesIcon from '@mui/icons-material/Devices';
|
||||
import FolderIcon from '@mui/icons-material/Folder';
|
||||
import MemoryIcon from '@mui/icons-material/Memory';
|
||||
@@ -9,7 +10,6 @@ import RefreshIcon from '@mui/icons-material/Refresh';
|
||||
import SdCardAlertIcon from '@mui/icons-material/SdCardAlert';
|
||||
import SdStorageIcon from '@mui/icons-material/SdStorage';
|
||||
import SettingsBackupRestoreIcon from '@mui/icons-material/SettingsBackupRestore';
|
||||
import ShowChartIcon from '@mui/icons-material/ShowChart';
|
||||
import TimerIcon from '@mui/icons-material/Timer';
|
||||
import {
|
||||
Avatar,
|
||||
@@ -29,7 +29,6 @@ import {
|
||||
import { useRequest } from 'alova';
|
||||
import { useContext, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { FeaturesContext } from '../../contexts/features';
|
||||
import RestartMonitor from './RestartMonitor';
|
||||
import SystemStatusVersionDialog from './SystemStatusVersionDialog';
|
||||
import type { FC } from 'react';
|
||||
@@ -54,8 +53,6 @@ const SystemStatusForm: FC = () => {
|
||||
const [restarting, setRestarting] = useState<boolean>();
|
||||
const [versionDialogOpen, setVersionDialogOpen] = useState<boolean>(false);
|
||||
|
||||
const { features } = useContext(FeaturesContext);
|
||||
|
||||
const { send: restartCommand } = useRequest(SystemApi.restart(), {
|
||||
immediate: false
|
||||
});
|
||||
@@ -200,15 +197,6 @@ const SystemStatusForm: FC = () => {
|
||||
</Button>
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
<Avatar>
|
||||
<DevicesIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary={LL.PLATFORM()} secondary={data.esp_platform + ' / ' + data.sdk_version} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
<Avatar>
|
||||
@@ -221,10 +209,33 @@ const SystemStatusForm: FC = () => {
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
<Avatar>
|
||||
<ShowChartIcon />
|
||||
<DevicesIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary={LL.CPU_FREQ()} secondary={data.cpu_freq_mhz + ' MHz'} />
|
||||
<ListItemText primary="SDK" secondary={data.arduino_version + ' / ESP-IDF ' + data.sdk_version} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
<Avatar>
|
||||
<DeveloperBoardIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
primary="CPU"
|
||||
secondary={
|
||||
data.esp_platform +
|
||||
'/' +
|
||||
data.cpu_type +
|
||||
' (rev.' +
|
||||
data.cpu_rev +
|
||||
', ' +
|
||||
(data.cpu_cores == 1 ? 'single-core)' : 'dual-core)') +
|
||||
' @ ' +
|
||||
data.cpu_freq_mhz +
|
||||
' Mhz'
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem>
|
||||
@@ -277,7 +288,9 @@ const SystemStatusForm: FC = () => {
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
primary={LL.APPSIZE()}
|
||||
secondary={formatNumber(data.app_used) + ' KB / ' + formatNumber(data.app_free) + ' KB'}
|
||||
secondary={
|
||||
data.partition + ': ' + formatNumber(data.app_used) + ' KB / ' + formatNumber(data.app_free) + ' KB'
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
@@ -339,7 +352,7 @@ const SystemStatusForm: FC = () => {
|
||||
open={versionDialogOpen}
|
||||
onClose={() => setVersionDialogOpen(false)}
|
||||
version={data.emsesp_version}
|
||||
platform={features.platform}
|
||||
platform={data.esp_platform}
|
||||
/>
|
||||
)}
|
||||
</SectionContent>
|
||||
|
||||
@@ -13,13 +13,13 @@ import * as EMSESP from 'project/api';
|
||||
|
||||
const UploadFileForm: FC = () => {
|
||||
const { LL } = useI18nContext();
|
||||
const [restarting, setRestarting] = useState<boolean>(false);
|
||||
const [restarting, setRestarting] = useState<boolean>();
|
||||
const [md5, setMd5] = useState<string>();
|
||||
|
||||
const { send: getSettings, onSuccess: onSuccessGetSettings } = useRequest(EMSESP.getSettings(), {
|
||||
immediate: false
|
||||
});
|
||||
const { send: getCustomizations, onSuccess: onSuccessgetCustomizations } = useRequest(EMSESP.getCustomizations(), {
|
||||
const { send: getCustomizations, onSuccess: onSuccessGetCustomizations } = useRequest(EMSESP.getCustomizations(), {
|
||||
immediate: false
|
||||
});
|
||||
const { send: getEntities, onSuccess: onSuccessGetEntities } = useRequest(EMSESP.getEntities(), {
|
||||
@@ -28,6 +28,9 @@ const UploadFileForm: FC = () => {
|
||||
const { send: getSchedule, onSuccess: onSuccessGetSchedule } = useRequest(EMSESP.getSchedule(), {
|
||||
immediate: false
|
||||
});
|
||||
const { send: getAPI, onSuccess: onGetAPI } = useRequest((data) => EMSESP.API(data), {
|
||||
immediate: false
|
||||
});
|
||||
|
||||
const {
|
||||
loading: isUploading,
|
||||
@@ -68,23 +71,26 @@ const UploadFileForm: FC = () => {
|
||||
type: 'text/plain'
|
||||
})
|
||||
);
|
||||
anchor.download = 'emsesp_' + endpoint + '.json';
|
||||
anchor.download = 'emsesp_' + endpoint;
|
||||
anchor.click();
|
||||
URL.revokeObjectURL(anchor.href);
|
||||
toast.info(LL.DOWNLOAD_SUCCESSFUL());
|
||||
};
|
||||
|
||||
onSuccessGetSettings((event) => {
|
||||
saveFile(event.data, 'settings');
|
||||
saveFile(event.data, 'settings.json');
|
||||
});
|
||||
onSuccessgetCustomizations((event) => {
|
||||
saveFile(event.data, 'customizations');
|
||||
onSuccessGetCustomizations((event) => {
|
||||
saveFile(event.data, 'customizations.json');
|
||||
});
|
||||
onSuccessGetEntities((event) => {
|
||||
saveFile(event.data, 'entities');
|
||||
saveFile(event.data, 'entities.json');
|
||||
});
|
||||
onSuccessGetSchedule((event) => {
|
||||
saveFile(event.data, 'schedule');
|
||||
saveFile(event.data, 'schedule.json');
|
||||
});
|
||||
onGetAPI((event) => {
|
||||
saveFile(event.data, event.sendArgs[0].device + '_' + event.sendArgs[0].entity + '.txt');
|
||||
});
|
||||
|
||||
const downloadSettings = async () => {
|
||||
@@ -106,7 +112,17 @@ const UploadFileForm: FC = () => {
|
||||
};
|
||||
|
||||
const downloadSchedule = async () => {
|
||||
await getSchedule().catch((error) => {
|
||||
await getSchedule()
|
||||
.catch((error) => {
|
||||
toast.error(error.message);
|
||||
})
|
||||
.finally(() => {
|
||||
toast.info(LL.DOWNLOAD_SUCCESSFUL());
|
||||
});
|
||||
};
|
||||
|
||||
const callAPI = async (device: string, entity: string) => {
|
||||
await getAPI({ device, entity, id: 0 }).catch((error) => {
|
||||
toast.error(error.message);
|
||||
});
|
||||
};
|
||||
@@ -117,7 +133,12 @@ const UploadFileForm: FC = () => {
|
||||
{LL.UPLOAD()}
|
||||
</Typography>
|
||||
<Box mb={2} color="warning.main">
|
||||
<Typography variant="body2">{LL.UPLOAD_TEXT()} </Typography>
|
||||
<Typography variant="body2">
|
||||
{LL.UPLOAD_TEXT()}
|
||||
<br />
|
||||
<br />
|
||||
{LL.RESTART_TEXT(1)}.
|
||||
</Typography>
|
||||
</Box>
|
||||
{md5 && (
|
||||
<Box mb={2}>
|
||||
@@ -127,8 +148,34 @@ const UploadFileForm: FC = () => {
|
||||
<SingleUpload onDrop={startUpload} onCancel={cancelUpload} isUploading={isUploading} progress={progress} />
|
||||
{!isUploading && (
|
||||
<>
|
||||
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
|
||||
{LL.DOWNLOAD(0)}
|
||||
<Typography sx={{ pt: 4, pb: 2 }} variant="h6" color="primary">
|
||||
{LL.DOWNLOAD(0)} {LL.SUPPORT_INFORMATION(1)}
|
||||
</Typography>
|
||||
<Box color="warning.main">
|
||||
<Typography mb={1} variant="body2">
|
||||
{LL.HELP_INFORMATION_4()}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Button
|
||||
startIcon={<DownloadIcon />}
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
onClick={() => callAPI('system', 'info')}
|
||||
>
|
||||
{LL.SUPPORT_INFORMATION(0)}
|
||||
</Button>
|
||||
<Button
|
||||
sx={{ ml: 2 }}
|
||||
startIcon={<DownloadIcon />}
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
onClick={() => callAPI('system', 'allvalues')}
|
||||
>
|
||||
All Values
|
||||
</Button>
|
||||
|
||||
<Typography sx={{ pt: 4, pb: 2 }} variant="h6" color="primary">
|
||||
{LL.DOWNLOAD(0)} {LL.SETTINGS(1)}
|
||||
</Typography>
|
||||
<Box color="warning.main">
|
||||
<Typography mb={1} variant="body2">
|
||||
@@ -140,7 +187,7 @@ const UploadFileForm: FC = () => {
|
||||
</Button>
|
||||
<Box color="warning.main">
|
||||
<Typography mt={2} mb={1} variant="body2">
|
||||
{LL.DOWNLOAD_CUSTOMIZATION_TEXT()}{' '}
|
||||
{LL.DOWNLOAD_CUSTOMIZATION_TEXT()}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Button startIcon={<DownloadIcon />} variant="outlined" color="primary" onClick={downloadCustomizations}>
|
||||
@@ -157,7 +204,7 @@ const UploadFileForm: FC = () => {
|
||||
</Button>
|
||||
<Box color="warning.main">
|
||||
<Typography mt={2} mb={1} variant="body2">
|
||||
{LL.DOWNLOAD_SCHEDULE_TEXT()}{' '}
|
||||
{LL.DOWNLOAD_SCHEDULE_TEXT()}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Button startIcon={<DownloadIcon />} variant="outlined" color="primary" onClick={downloadSchedule}>
|
||||
|
||||
1
interface/src/i18n/SK.svg
Normal file
1
interface/src/i18n/SK.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 85.333 512 341.333"><path fill="#FFF" d="M0 85.337h512v341.326H0z"/><path fill="#0052B4" d="M0 196.641h512v118.717H0z"/><path fill="#D80027" d="M0 315.359h512v111.304H0z"/><path fill="#FFF" d="M129.468 181.799v85.136c0 48.429 63.267 63.267 63.267 63.267S256 315.362 256 266.935v-85.136H129.468z"/><path fill="#D80027" d="M146.126 184.294v81.941c0 5.472 1.215 10.64 3.623 15.485h85.97c2.408-4.844 3.623-10.012 3.623-15.485v-81.941h-93.216z"/><path fill="#FFF" d="M221.301 241.427h-21.425v-14.283h14.284v-14.283h-14.284v-14.284h-14.283v14.284h-14.282v14.283h14.282v14.283h-21.426v14.284h21.426v14.283h14.283v-14.283h21.425z"/><path fill="#0052B4" d="M169.232 301.658c9.204 5.783 18.66 9.143 23.502 10.636 4.842-1.494 14.298-4.852 23.502-10.636 9.282-5.833 15.79-12.506 19.484-19.939a24.878 24.878 0 0 0-14.418-4.583c-1.956 0-3.856.232-5.682.657-3.871-8.796-12.658-14.94-22.884-14.94-10.227 0-19.013 6.144-22.884 14.94a25.048 25.048 0 0 0-5.682-.657 24.88 24.88 0 0 0-14.418 4.583c3.691 7.433 10.198 14.106 19.48 19.939z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -126,6 +126,7 @@ const de: Translation = {
|
||||
BYPASS_TOKEN: 'Zugriffstoken-Autorisierung bei API-Aufrufen umgehen',
|
||||
READONLY: 'Nur-Lese-Modus aktivieren (blockiert alle ausgehenden EMS Tx Write-Befehle)',
|
||||
UNDERCLOCK_CPU: 'CPU-Geschwindigkeit untertakten',
|
||||
HEATINGOFF: 'Heizen ausschalten beim EMS-ESP Start',
|
||||
ENABLE_SHOWER_TIMER: 'Duschtimer aktivieren',
|
||||
ENABLE_SHOWER_ALERT: 'Duschalarm aktivieren',
|
||||
TRIGGER_TIME: 'Auslösezeit',
|
||||
@@ -170,7 +171,6 @@ const de: Translation = {
|
||||
HELP_INFORMATION_3: 'Um neue Funktionen anzufragen oder Fehler zu melden, eröffnen Sie ein Issue auf Github',
|
||||
HELP_INFORMATION_4: 'Bitte laden Sie die System-Details und hängen Sie sie an das Support-Issue an. ',
|
||||
HELP_INFORMATION_5: 'EMS-ESP ist ein freies Open-Source Projekt. Bitte unterstützen Sie die zukünftige Entwicklung mit einem "Star" auf Github!',
|
||||
SUPPORT_INFO: 'Support Info',
|
||||
UPLOAD: 'Hochladen',
|
||||
DOWNLOAD: '{{H|h|h}}erunterladen',
|
||||
ABORTED: 'abgebrochen',
|
||||
@@ -191,16 +191,14 @@ const de: Translation = {
|
||||
THE_LATEST: 'Die neueste',
|
||||
OFFICIAL: 'offizielle',
|
||||
DEVELOPMENT: 'Entwicklungs',
|
||||
RELEASE_IS: 'release ist', // TODO translate
|
||||
RELEASE_IS: 'Release ist',
|
||||
RELEASE_NOTES: 'Versionshinweise',
|
||||
EMS_ESP_VER: 'EMS-ESP Version',
|
||||
PLATFORM: 'Platform (Platform / SDK)',
|
||||
UPTIME: 'System Betriebszeit',
|
||||
CPU_FREQ: 'CPU Frequenz',
|
||||
HEAP: 'freier RAM Speicher (Gesamt / max. Block)',
|
||||
PSRAM: 'PSRAM (Größe / Frei)',
|
||||
FLASH: 'Flash Speicher (Größe / Geschwindigkeit)',
|
||||
APPSIZE: 'Programm (Genutzt / Frei)',
|
||||
APPSIZE: 'Programm (Partition: Genutzt / Frei)',
|
||||
FILESYSTEM: 'Dateisystem (Genutzt / Frei)',
|
||||
BUFFER_SIZE: 'max. Puffergröße',
|
||||
COMPACT: 'Kompakte Darstellung',
|
||||
@@ -282,6 +280,7 @@ const de: Translation = {
|
||||
NETWORK_SCANNER: 'Netzwerk Suche',
|
||||
NETWORK_NO_WIFI: 'Keine WiFi Netzwerke gefunden',
|
||||
NETWORK_BLANK_SSID: 'Freilassen um WiFi zu deaktivieren und ETH zu aktivieren',
|
||||
NETWORK_BLANK_BSSID: 'Freilassen um nur SSID für die Verbindung zu nutzen',
|
||||
TX_POWER: 'Tx Leistung',
|
||||
HOSTNAME: 'Hostname',
|
||||
NETWORK_DISABLE_SLEEP: 'Deaktiviere WiFi Schlafmodus',
|
||||
@@ -308,7 +307,7 @@ const de: Translation = {
|
||||
LEAVE: 'Verlassen',
|
||||
SCHEDULER: 'Planer',
|
||||
SCHEDULER_HELP_1: 'Fügen Sie eigene, geplante Befehle zur Automatisierung hinzu. Vergeben Sie einen Entitätsnamen um die Aktivierung über API/Mqtt zu steuern',
|
||||
SCHEDULER_HELP_2: 'Use 00:00 to trigger once on start-up', // TODO translate
|
||||
SCHEDULER_HELP_2: '00:00 aktiviert einmalige Ausführung am Start',
|
||||
SCHEDULE: 'Zeitplan',
|
||||
TIME: 'Zeit',
|
||||
TIMER: 'Timer',
|
||||
@@ -317,12 +316,20 @@ const de: Translation = {
|
||||
SCHEDULE_TIMER_2: 'jede Minute',
|
||||
SCHEDULE_TIMER_3: 'jede Stunde',
|
||||
CUSTOM_ENTITIES: 'Individuelle Entitäten',
|
||||
ENTITIES_HELP_1: 'Abfrage von Werten auf dem EMS-Bus',
|
||||
ENTITIES_HELP_1: 'Definition eigener EMS-Werte oder dynamischer Variablen',
|
||||
ENTITIES_UPDATED: 'Entitäten gespeichert',
|
||||
WRITEABLE: 'Schreibbar',
|
||||
SHOWING: 'Anzeigen von',
|
||||
SEARCH: 'Suche',
|
||||
CERT: 'TSL Zertifikat (Freilassen um TSL zu deaktivieren)'
|
||||
CERT: 'TLS Zertifikat (Freilassen für unsichere Verbindung)',
|
||||
ENABLE_TLS: 'Aktiviere TLS',
|
||||
ON: 'An',
|
||||
OFF: 'Aus',
|
||||
POLARITY: 'Polarität',
|
||||
ACTIVEHIGH: 'Aktiv Positiv',
|
||||
ACTIVELOW: 'Aktiv Negativ',
|
||||
UNCHANGED: 'Unverändert',
|
||||
ALWAYS: 'Immer'
|
||||
};
|
||||
|
||||
export default de;
|
||||
|
||||
@@ -63,7 +63,7 @@ const en: Translation = {
|
||||
FREQ: 'Frequency',
|
||||
DUTY_CYCLE: 'Duty Cycle',
|
||||
UNIT: 'UoM',
|
||||
STARTVALUE: 'Start value',
|
||||
STARTVALUE: 'Start Value',
|
||||
WARN_GPIO: 'Warning: be careful when assigning a GPIO!',
|
||||
EDIT: 'Edit',
|
||||
SENSOR: 'Sensor',
|
||||
@@ -126,6 +126,7 @@ const en: Translation = {
|
||||
BYPASS_TOKEN: 'Bypass Access Token authorization on API calls',
|
||||
READONLY: 'Enable read-only mode (blocks all outgoing EMS Tx Write commands)',
|
||||
UNDERCLOCK_CPU: 'Underclock CPU speed',
|
||||
HEATINGOFF: 'Start boiler with forced heating off',
|
||||
ENABLE_SHOWER_TIMER: 'Enable Shower Timer',
|
||||
ENABLE_SHOWER_ALERT: 'Enable Shower Alert',
|
||||
TRIGGER_TIME: 'Trigger Time',
|
||||
@@ -168,9 +169,8 @@ const en: Translation = {
|
||||
HELP_INFORMATION_1: 'Visit the online wiki to get instructions on how to configure EMS-ESP',
|
||||
HELP_INFORMATION_2: 'For live community chat join our Discord server',
|
||||
HELP_INFORMATION_3: 'To request a feature or report a bug',
|
||||
HELP_INFORMATION_4: 'remember to download and attach your system information for a faster response when reporting an issue',
|
||||
HELP_INFORMATION_4: 'Remember to download and attach your support information for a faster response when reporting an issue',
|
||||
HELP_INFORMATION_5: 'EMS-ESP is a free and open-source project. Please support its future development by giving it a star on Github!',
|
||||
SUPPORT_INFO: 'Support Info',
|
||||
UPLOAD: 'Upload',
|
||||
DOWNLOAD: '{{D|d|d}}ownload',
|
||||
ABORTED: 'aborted',
|
||||
@@ -196,11 +196,10 @@ const en: Translation = {
|
||||
EMS_ESP_VER: 'EMS-ESP Version',
|
||||
PLATFORM: 'Device (Platform / SDK)',
|
||||
UPTIME: 'System Uptime',
|
||||
CPU_FREQ: 'CPU Frequency',
|
||||
HEAP: 'Heap (Free / Max Alloc)',
|
||||
PSRAM: 'PSRAM (Size / Free)',
|
||||
FLASH: 'Flash Chip (Size / Speed)',
|
||||
APPSIZE: 'Application (Used / Free)',
|
||||
APPSIZE: 'Application (Partition: Used / Free)',
|
||||
FILESYSTEM: 'File System (Used / Free)',
|
||||
BUFFER_SIZE: 'Max Buffer Size',
|
||||
COMPACT: 'Compact',
|
||||
@@ -230,7 +229,7 @@ const en: Translation = {
|
||||
BROKER: 'Broker',
|
||||
CLIENT: 'Client',
|
||||
BASE_TOPIC: 'Base',
|
||||
OPTIONAL: 'Optional',
|
||||
OPTIONAL: 'optional',
|
||||
FORMATTING: 'Formatting',
|
||||
MQTT_FORMAT: 'Topic/Payload Format',
|
||||
MQTT_NEST_1: 'Nested in a single topic',
|
||||
@@ -282,6 +281,7 @@ const en: Translation = {
|
||||
NETWORK_SCANNER: 'Network Scanner',
|
||||
NETWORK_NO_WIFI: 'No WiFi networks found',
|
||||
NETWORK_BLANK_SSID: 'leave blank to disable WiFi and enable ETH',
|
||||
NETWORK_BLANK_BSSID: 'leave blank to use only SSID',
|
||||
TX_POWER: 'Tx Power',
|
||||
HOSTNAME: 'Hostname',
|
||||
NETWORK_DISABLE_SLEEP: 'Disable WiFi Sleep Mode',
|
||||
@@ -317,12 +317,20 @@ const en: Translation = {
|
||||
SCHEDULE_TIMER_2: 'every minute',
|
||||
SCHEDULE_TIMER_3: 'every hour',
|
||||
CUSTOM_ENTITIES: 'Custom Entities',
|
||||
ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus',
|
||||
ENTITIES_HELP_1: 'Define custom EMS entities or dynamic user variables',
|
||||
ENTITIES_UPDATED: 'Entities Updated',
|
||||
WRITEABLE: 'Writeable',
|
||||
SHOWING: 'Showing',
|
||||
SEARCH: 'Search',
|
||||
CERT: 'TSL root certificate (leave blank to disable TSL)'
|
||||
CERT: 'TLS root certificate (leave blank for insecure)',
|
||||
ENABLE_TLS: 'Enable TLS',
|
||||
ON: 'On',
|
||||
OFF: 'Off',
|
||||
POLARITY: 'Polarity',
|
||||
ACTIVEHIGH: 'Active High',
|
||||
ACTIVELOW: 'Active Low',
|
||||
UNCHANGED: 'Unchanged',
|
||||
ALWAYS: 'Always'
|
||||
};
|
||||
|
||||
export default en;
|
||||
|
||||
@@ -126,6 +126,7 @@ const fr: Translation = {
|
||||
BYPASS_TOKEN: 'Contourner l\'autorisation du jeton d\'accès sur les appels API',
|
||||
READONLY: 'Activer le mode lecture uniquement (bloque toutes les commandes EMS sortantes en écriture Tx)',
|
||||
UNDERCLOCK_CPU: 'Underclock du CPU',
|
||||
HEATINGOFF: 'Start boiler with forced heating off', // TODO translate
|
||||
ENABLE_SHOWER_TIMER: 'Activer la minuterie de la douche',
|
||||
ENABLE_SHOWER_ALERT: 'Activer les alertes de durée de douche',
|
||||
TRIGGER_TIME: 'Durée avant déclenchement',
|
||||
@@ -168,9 +169,8 @@ const fr: Translation = {
|
||||
HELP_INFORMATION_1: 'Visitez le wiki en ligne pour obtenir des instructions sur la façon de configurer EMS-ESP.',
|
||||
HELP_INFORMATION_2: 'Pour une discussion en direct avec la communauté, rejoignez notre serveur Discord',
|
||||
HELP_INFORMATION_3: 'Pour demander une fonctionnalité ou signaler un problème',
|
||||
HELP_INFORMATION_4: 'n\'oubliez pas de télécharger et de joindre les informations relatives à votre système pour obtenir une réponse plus rapide lorsque vous signalez un problème',
|
||||
HELP_INFORMATION_4: 'N\'oubliez pas de télécharger et de joindre les informations relatives à votre système pour obtenir une réponse plus rapide lorsque vous signalez un problème',
|
||||
HELP_INFORMATION_5: 'EMS-ESP est un projet libre et open-source. Merci de soutenir son développement futur en lui donnant une étoile sur Github !',
|
||||
SUPPORT_INFO: 'Information de support',
|
||||
UPLOAD: 'Upload',
|
||||
DOWNLOAD: '{{D|d|d}}ownload',
|
||||
ABORTED: 'annulé',
|
||||
@@ -194,13 +194,11 @@ const fr: Translation = {
|
||||
RELEASE_IS: 'release est', // TODO translate
|
||||
RELEASE_NOTES: 'notes de version',
|
||||
EMS_ESP_VER: 'Version EMS-ESP',
|
||||
PLATFORM: 'Appareil (Plateforme / SDK)',
|
||||
UPTIME: 'Durée de fonctionnement du système',
|
||||
CPU_FREQ: 'Fréquence du CPU',
|
||||
HEAP: 'Heap (Libre / Max Allouée)',
|
||||
PSRAM: 'PSRAM (Taille / Libre)',
|
||||
FLASH: 'Flash Chip (Taille / Vitesse)',
|
||||
APPSIZE: 'Application (Utilisée / Libre)',
|
||||
APPSIZE: 'Application (Partition: Utilisée / Libre)',
|
||||
FILESYSTEM: 'File System (Utilisée / Libre)',
|
||||
BUFFER_SIZE: 'Max taille du buffer',
|
||||
COMPACT: 'Compact',
|
||||
@@ -230,7 +228,7 @@ const fr: Translation = {
|
||||
BROKER: 'Broker',
|
||||
CLIENT: 'Client',
|
||||
BASE_TOPIC: 'Base',
|
||||
OPTIONAL: 'Optionnel',
|
||||
OPTIONAL: 'optionnel',
|
||||
FORMATTING: 'Mise en forme',
|
||||
MQTT_FORMAT: 'Format du Topic/Payload',
|
||||
MQTT_NEST_1: 'Englobé dans un topic unique',
|
||||
@@ -282,6 +280,7 @@ const fr: Translation = {
|
||||
NETWORK_SCANNER: 'Scan réseau',
|
||||
NETWORK_NO_WIFI: 'Pas de réseau WiFi trouvé',
|
||||
NETWORK_BLANK_SSID: 'laisser vide pour désactiver le WiFi', // and enable ETH // TODO translate
|
||||
NETWORK_BLANK_BSSID: 'leave blank to use only SSID', // TODO translate
|
||||
TX_POWER: 'Puissance Tx',
|
||||
HOSTNAME: 'Nom d\'hôte',
|
||||
NETWORK_DISABLE_SLEEP: 'Désactiver le mode veille du WiFi',
|
||||
@@ -322,7 +321,15 @@ const fr: Translation = {
|
||||
WRITEABLE: 'Writeable', // TODO translate
|
||||
SHOWING: 'Showing', // TODO translate
|
||||
SEARCH: 'Search', // TODO translate
|
||||
CERT: 'TSL root certificate (leave blank to disable TSL)' // TODO translate
|
||||
CERT: 'TLS root certificate (leave blank for insecure)', // TODO translate
|
||||
ENABLE_TLS: 'Activer TLS',
|
||||
ON: 'On', // TODO translate
|
||||
OFF: 'Off', // TODO translate
|
||||
POLARITY: 'Polarity', // TODO translate
|
||||
ACTIVEHIGH: 'Active High', // TODO translate
|
||||
ACTIVELOW: 'Active Low', // TODO translate
|
||||
UNCHANGED: 'Unchanged', // TODO translate
|
||||
ALWAYS: 'Always' // TODO translate
|
||||
};
|
||||
|
||||
export default fr;
|
||||
|
||||
@@ -128,6 +128,7 @@ const it: Translation = {
|
||||
BYPASS_TOKEN: 'Ignora autorizzazione del token di accesso sulle chiamate API',
|
||||
READONLY: 'Abilita modalità sola-lettura (blocca tutti i comandi di scrittura EMS Tx in uscita)',
|
||||
UNDERCLOCK_CPU: 'Abbassa velocità della CPU',
|
||||
HEATINGOFF: 'Avviamento caldaia con riscaldamento forzato spento',
|
||||
ENABLE_SHOWER_TIMER: 'Abilita timer doccia',
|
||||
ENABLE_SHOWER_ALERT: 'Abilita avviso doccia',
|
||||
TRIGGER_TIME: 'Tempo di avvio',
|
||||
@@ -170,9 +171,8 @@ const it: Translation = {
|
||||
HELP_INFORMATION_1: 'Visita il wiki online per ottenere istruzioni su come configurare EMS-ESP',
|
||||
HELP_INFORMATION_2: 'Per la chat della community dal vivo unisciti al nostro server Discord',
|
||||
HELP_INFORMATION_3: 'Per richiedere una funzionalità o segnalare un errore',
|
||||
HELP_INFORMATION_4: 'ricordati di scaricare e allegare le informazioni del tuo sistema per una risposta più rapida quando segnali un problema',
|
||||
HELP_INFORMATION_4: 'Ricordati di scaricare e allegare le informazioni del tuo sistema per una risposta più rapida quando segnali un problema',
|
||||
HELP_INFORMATION_5: 'EMS-ESP è un progetto gratuito e open-source. Supporta il suo sviluppo futuro assegnandogli una stella su Github!',
|
||||
SUPPORT_INFO: 'Info Supporto',
|
||||
UPLOAD: 'Carica',
|
||||
DOWNLOAD: 'Scarica',
|
||||
ABORTED: 'Annullato',
|
||||
@@ -196,13 +196,11 @@ const it: Translation = {
|
||||
RELEASE_IS: 'rilascio é',
|
||||
RELEASE_NOTES: 'note rilascio',
|
||||
EMS_ESP_VER: 'Versione EMS-ESP',
|
||||
PLATFORM: 'Dispositivo (Piattaforma / SDK)',
|
||||
UPTIME: 'Tempo di attività del sistema',
|
||||
CPU_FREQ: 'Frequenza CPU ',
|
||||
HEAP: 'Heap (Free / Max Alloc)',
|
||||
PSRAM: 'PSRAM (Size / Free)',
|
||||
FLASH: 'Flash Chip (Size / Speed)',
|
||||
APPSIZE: 'Applicazione (Usata / Libera)',
|
||||
APPSIZE: 'Applicazione (Partizione: Usata / Libera)',
|
||||
FILESYSTEM: 'Memoria Sistema (Usata / Libera)',
|
||||
BUFFER_SIZE: 'Max Buffer Size',
|
||||
COMPACT: 'Compact',
|
||||
@@ -232,7 +230,7 @@ const it: Translation = {
|
||||
BROKER: 'Broker',
|
||||
CLIENT: 'Cliente',
|
||||
BASE_TOPIC: 'Base',
|
||||
OPTIONAL: 'Opzionale',
|
||||
OPTIONAL: 'opzionale',
|
||||
FORMATTING: 'Formattazione',
|
||||
MQTT_FORMAT: 'Formato Topic/Payload ',
|
||||
MQTT_NEST_1: 'Inserito in un singolo argomento',
|
||||
@@ -284,6 +282,7 @@ const it: Translation = {
|
||||
NETWORK_SCANNER: 'Scansione Rete',
|
||||
NETWORK_NO_WIFI: 'Nessuana rete WiFi trovata',
|
||||
NETWORK_BLANK_SSID: 'lasciare vuoto per disattivare WiFi',
|
||||
NETWORK_BLANK_BSSID: 'leave blank to use only SSID', // TODO translate
|
||||
TX_POWER: 'Potenza Tx',
|
||||
HOSTNAME: 'Nome ospite',
|
||||
NETWORK_DISABLE_SLEEP: 'Disabilita la modalità sospensione Wi-Fi',
|
||||
@@ -319,12 +318,20 @@ const it: Translation = {
|
||||
SCHEDULE_TIMER_2: 'Ogni minuto',
|
||||
SCHEDULE_TIMER_3: 'Ogni ora',
|
||||
CUSTOM_ENTITIES: 'Entità personalizzate',
|
||||
ENTITIES_HELP_1: 'Recupera entità personalizzate dal BUS EMS',
|
||||
ENTITIES_HELP_1: 'Recupera entità personalizzate dal BUS EMS', // TODO translate
|
||||
ENTITIES_UPDATED: 'Entità aggiornate',
|
||||
WRITEABLE: 'Scrivibile',
|
||||
SHOWING: 'Visualizza',
|
||||
SEARCH: 'Ricerca',
|
||||
CERT: 'TSL root certificate (leave blank to disable TSL)' // TODO translate
|
||||
CERT: 'TLS root certificate (leave blank for insecure)', // TODO translate
|
||||
ENABLE_TLS: 'Abilita TLS',
|
||||
ON: 'On', // TODO translate
|
||||
OFF: 'Off', // TODO translate
|
||||
POLARITY: 'Polarity', // TODO translate
|
||||
ACTIVEHIGH: 'Active High', // TODO translate
|
||||
ACTIVELOW: 'Active Low', // TODO translate
|
||||
UNCHANGED: 'Unchanged', // TODO translate
|
||||
ALWAYS: 'Always' // TODO translate
|
||||
};
|
||||
|
||||
export default it;
|
||||
|
||||
@@ -126,6 +126,7 @@ const nl: Translation = {
|
||||
BYPASS_TOKEN: 'API Access Token authenticatie uitschakelen',
|
||||
READONLY: 'Activeer read-only modus (blokkeert alle outgaande EMS Tx schrijf commandos)',
|
||||
UNDERCLOCK_CPU: 'Underclock CPU snelheid',
|
||||
HEATINGOFF: 'Start boiler with forced heating off', // TODO translate
|
||||
ENABLE_SHOWER_TIMER: 'Activeer Douche Timer (tijdmeting)',
|
||||
ENABLE_SHOWER_ALERT: 'Activeer Douchemelding',
|
||||
TRIGGER_TIME: 'Trigger tijd',
|
||||
@@ -168,9 +169,8 @@ const nl: Translation = {
|
||||
HELP_INFORMATION_1: 'Bezoek de online wiki om instructies te vinden om EMS-ESP te configureren',
|
||||
HELP_INFORMATION_2: 'Voor de live community ga naar de Discord server',
|
||||
HELP_INFORMATION_3: 'Om een nieuwe feature te vragen of een bug te rapporteren',
|
||||
HELP_INFORMATION_4: 'zorg dat je ook je systeem details zijn toevoeged voor een sneller antwoord',
|
||||
HELP_INFORMATION_4: 'Zorg dat je ook je systeem details zijn toevoeged voor een sneller antwoord',
|
||||
HELP_INFORMATION_5: 'EMS-ESP is een gratis en open source project. Steun ons met een Star op Github!',
|
||||
SUPPORT_INFO: 'Support Info',
|
||||
UPLOAD: 'Upload',
|
||||
DOWNLOAD: '{{D|d|d}}ownload',
|
||||
ABORTED: 'afgebroken',
|
||||
@@ -194,13 +194,11 @@ const nl: Translation = {
|
||||
RELEASE_IS: 'release is',
|
||||
RELEASE_NOTES: 'release notes',
|
||||
EMS_ESP_VER: 'EMS-ESP Versie',
|
||||
PLATFORM: 'Apparaat (Platform / SDK)',
|
||||
UPTIME: 'Systeem Uptime',
|
||||
CPU_FREQ: 'CPU Frequency',
|
||||
HEAP: 'Heap (Free / Max Alloc)',
|
||||
PSRAM: 'PSRAM (Size / Free)',
|
||||
FLASH: 'Flash Chip (Size / Speed)',
|
||||
APPSIZE: 'Application (Used / Free)',
|
||||
APPSIZE: 'Application (Partition: Used / Free)',
|
||||
FILESYSTEM: 'File System (Used / Free)',
|
||||
BUFFER_SIZE: 'Max Buffer Size',
|
||||
COMPACT: 'Compact',
|
||||
@@ -230,7 +228,7 @@ const nl: Translation = {
|
||||
BROKER: 'Broker',
|
||||
CLIENT: 'Client',
|
||||
BASE_TOPIC: 'Base',
|
||||
OPTIONAL: 'Optioneel',
|
||||
OPTIONAL: 'optioneel',
|
||||
FORMATTING: 'Formatteren',
|
||||
MQTT_FORMAT: 'Topic/Payload Formattering',
|
||||
MQTT_NEST_1: 'Genest in 1 topic',
|
||||
@@ -282,6 +280,7 @@ const nl: Translation = {
|
||||
NETWORK_SCANNER: 'Netwerk Scanner',
|
||||
NETWORK_NO_WIFI: 'Geen WiFi networken gevonden',
|
||||
NETWORK_BLANK_SSID: 'laat leeg om WiFi uit te schakelen',
|
||||
NETWORK_BLANK_BSSID: 'leave blank to use only SSID', // TODO translate
|
||||
TX_POWER: 'Tx Vermogen',
|
||||
HOSTNAME: 'Hostname',
|
||||
NETWORK_DISABLE_SLEEP: 'WiFi Sleep Mode uitzetten',
|
||||
@@ -317,12 +316,20 @@ const nl: Translation = {
|
||||
SCHEDULE_TIMER_2: 'elke minuut',
|
||||
SCHEDULE_TIMER_3: 'elke huur',
|
||||
CUSTOM_ENTITIES: 'Aangepaste Entiteiten',
|
||||
ENTITIES_HELP_1: 'Aangepaste entiteiten ophalen uit de EMS-bus',
|
||||
ENTITIES_HELP_1: 'Aangepaste entiteiten ophalen uit de EMS-bus', // TODO translate
|
||||
ENTITIES_UPDATED: 'Entiteiten bijgewerkt',
|
||||
WRITEABLE: 'Beschrijfbare',
|
||||
SHOWING: 'Tonen',
|
||||
SEARCH: 'Zoek',
|
||||
CERT: 'TSL rootcertificaat (laat leeg om TSL uit te schakelen)'
|
||||
CERT: 'TLS rootcertificaat (laat leeg om TLS-insecure)', // TODO translate
|
||||
ENABLE_TLS: 'Activeer TLS',
|
||||
ON: 'On', // TODO translate
|
||||
OFF: 'Off', // TODO translate
|
||||
POLARITY: 'Polarity', // TODO translate
|
||||
ACTIVEHIGH: 'Active High', // TODO translate
|
||||
ACTIVELOW: 'Active Low', // TODO translate
|
||||
UNCHANGED: 'Unchanged', // TODO translate
|
||||
ALWAYS: 'Always' // TODO translate
|
||||
};
|
||||
|
||||
export default nl;
|
||||
|
||||
@@ -126,6 +126,7 @@ const no: Translation = {
|
||||
BYPASS_TOKEN: 'Utelat Aksess Token authorisering av API kall',
|
||||
READONLY: 'Aktiver read-only modus (blokker all EMS Tx Skriving)',
|
||||
UNDERCLOCK_CPU: 'Underklokking av prosessorhastighet',
|
||||
HEATINGOFF: 'Start boiler with forced heating off', // TODO translate
|
||||
ENABLE_SHOWER_TIMER: 'Aktiver Dusjtimer',
|
||||
ENABLE_SHOWER_ALERT: 'Aktiver Dusj-varsling',
|
||||
TRIGGER_TIME: 'Aktiveringstid',
|
||||
@@ -168,9 +169,8 @@ const no: Translation = {
|
||||
HELP_INFORMATION_1: 'Besøk wiki for instruksjoner for å konfigurere EMS-ESP',
|
||||
HELP_INFORMATION_2: 'For community-support besøk vår Discord-server',
|
||||
HELP_INFORMATION_3: 'For å be om en ny funksjon eller melde feil',
|
||||
HELP_INFORMATION_4: 'husk å laste ned og legg ved din systeminformasjon for en raskere respons når du rapporterer et problem',
|
||||
HELP_INFORMATION_4: 'Husk å laste ned og legg ved din systeminformasjon for en raskere respons når du rapporterer et problem',
|
||||
HELP_INFORMATION_5: 'EMS-ESP er gratis og åpen kildekode. Bidra til utviklingen ved å gi oss en stjerne på GitHub!',
|
||||
SUPPORT_INFO: 'Supportinfo',
|
||||
UPLOAD: 'Opplasning',
|
||||
DOWNLOAD: '{{N|n|n}}edlasting',
|
||||
ABORTED: 'avbrutt',
|
||||
@@ -194,13 +194,11 @@ const no: Translation = {
|
||||
RELEASE_IS: 'release er',
|
||||
RELEASE_NOTES: 'release notes',
|
||||
EMS_ESP_VER: 'EMS-ESP Version',
|
||||
PLATFORM: 'Enhet (Platform / SDK)',
|
||||
UPTIME: 'System Oppetid',
|
||||
CPU_FREQ: 'CPU Frekvens',
|
||||
HEAP: 'Heap (Ledig / Max Allokert)',
|
||||
PSRAM: 'PSRAM (Størrelse / Ledig)',
|
||||
FLASH: 'Flash Chip (Størrelse / Hastighet)',
|
||||
APPSIZE: 'Applikasjon (Brukt / Ledig)',
|
||||
APPSIZE: 'Applikasjon (Partition: Brukt / Ledig)',
|
||||
FILESYSTEM: 'File System (Brukt / Ledig)',
|
||||
BUFFER_SIZE: 'Max Buffer Størrelse',
|
||||
COMPACT: 'Komprimere',
|
||||
@@ -230,7 +228,7 @@ const no: Translation = {
|
||||
BROKER: 'Broker',
|
||||
CLIENT: 'Client',
|
||||
BASE_TOPIC: 'Base',
|
||||
OPTIONAL: 'Valgfritt',
|
||||
OPTIONAL: 'valgfritt',
|
||||
FORMATTING: 'Formatering',
|
||||
MQTT_FORMAT: 'Topic/Payload Format',
|
||||
MQTT_NEST_1: 'Nestet i en topic',
|
||||
@@ -282,6 +280,7 @@ const no: Translation = {
|
||||
NETWORK_SCANNER: 'Nettverk Scanner',
|
||||
NETWORK_NO_WIFI: 'Ingen trådløse nett funnet',
|
||||
NETWORK_BLANK_SSID: 'la feltet være blankt for å deaktivisere trådløst nettverk', // TODO translate
|
||||
NETWORK_BLANK_BSSID: 'leave blank to use only SSID', // TODO translate
|
||||
TX_POWER: 'Tx Effekt',
|
||||
HOSTNAME: 'Hostname',
|
||||
NETWORK_DISABLE_SLEEP: 'Hindre at trådløst nettverk går i Sleep Mode',
|
||||
@@ -322,7 +321,15 @@ const no: Translation = {
|
||||
WRITEABLE: 'Writeable', // TODO translate
|
||||
SHOWING: 'Showing', // TODO translate
|
||||
SEARCH: 'Search', // TODO translate
|
||||
CERT: 'TSL root certificate (leave blank to disable TSL)' // TODO translate
|
||||
CERT: 'TLS root certificate (leave blank for insecure)', // TODO translate
|
||||
ENABLE_TLS: 'Aktiviser TLS',
|
||||
ON: 'On', // TODO translate
|
||||
OFF: 'Off', // TODO translate
|
||||
POLARITY: 'Polarity', // TODO translate
|
||||
ACTIVEHIGH: 'Active High', // TODO translate
|
||||
ACTIVELOW: 'Active Low', // TODO translate
|
||||
UNCHANGED: 'Unchanged', // TODO translate
|
||||
ALWAYS: 'Always' // TODO translate
|
||||
};
|
||||
|
||||
export default no;
|
||||
|
||||
@@ -53,7 +53,7 @@ const pl: BaseTranslation = {
|
||||
PROBLEM_LOADING: 'Problem z załadowaniem!',
|
||||
ANALOG_SENSOR: '{{u|u||ustawienia u|ustawień u}}rządzeni{{a podłączonego do EMS-ESP|e||a podłączonego do EMS-ESP|a podłączonego do EMS-ESP}}',
|
||||
ANALOG_SENSORS: 'Urządzenia podłączone do EMS-ESP',
|
||||
SETTINGS: 'ustawienia',
|
||||
SETTINGS: 'ustawie{{nia|ń|}}',
|
||||
UPDATED_OF: 'Zaktualizowano {0}.',
|
||||
UPDATE_OF: 'Aktualizacja {0}',
|
||||
REMOVED_OF: 'Usunięto ustawienia {0}.',
|
||||
@@ -126,6 +126,7 @@ const pl: BaseTranslation = {
|
||||
BYPASS_TOKEN: 'Pomiń autoryzację tokenem w wywołaniach API',
|
||||
READONLY: 'Tryb pracy "tylko do odczytu" (blokuje wszystkie komendy zapisu na magistralę EMS)',
|
||||
UNDERCLOCK_CPU: 'Obniż taktowanie CPU',
|
||||
HEATINGOFF: 'Uruchom kocioł z wymuszonym wyłączonym grzaniem',
|
||||
ENABLE_SHOWER_TIMER: 'Aktywuj minutnik prysznica',
|
||||
ENABLE_SHOWER_ALERT: 'Aktywuj alarm prysznica',
|
||||
TRIGGER_TIME: 'Wyzwalaj po czasie',
|
||||
@@ -145,13 +146,13 @@ const pl: BaseTranslation = {
|
||||
MINUTES: 'minut',
|
||||
HOURS: 'godzin',
|
||||
RESTART: 'Restart',
|
||||
RESTART_TEXT: 'Aby zastosować wprowadzone zmiany interfejs EMS-ESP musi zostać zrestartowany.',
|
||||
RESTART_TEXT: 'Aby zastosować wprowadzone zmiany, interfejs EMS-ESP {{musi zostać|zostanie|}} uruchomiony ponowni{{e.|e|}}',
|
||||
RESTART_CONFIRM: 'Na pewno chcesz zrestartować interfejs EMS-ESP?',
|
||||
COMMAND: '{{Komenda|KOMENDA|}}',
|
||||
CUSTOMIZATIONS_RESTART: 'Wszystkie personalizacje zostały usunięte. Restartuję...',
|
||||
CUSTOMIZATIONS_FULL: 'Wybrano za dużo obiektów. Wprowadź zmiany w mniejszych partiach.',
|
||||
CUSTOMIZATIONS_SAVED: 'Personalizacje zostały zapisane.',
|
||||
CUSTOMIZATIONS_HELP_1: 'Wybierz urządzenie EMS, dostosuj opcje lub kliknij by zmienić nazwę encji.',
|
||||
CUSTOMIZATIONS_HELP_1: 'Wybierz urządzenie EMS, a następnie dostosuj opcje lub kliknij na nazwie encji by tę nazwę zmienić',
|
||||
CUSTOMIZATIONS_HELP_2: 'oznacz jako ulubioną',
|
||||
CUSTOMIZATIONS_HELP_3: 'zablokuj akcje zapisu',
|
||||
CUSTOMIZATIONS_HELP_4: 'wyklucz z MQTT i API',
|
||||
@@ -163,14 +164,13 @@ const pl: BaseTranslation = {
|
||||
NAME: '{{Nazwa|nazwa|}}',
|
||||
CUSTOMIZATIONS_RESET: 'Na pewno chcesz usunąć wszystkie personalizacje łącznie z ustawieniami dla czujników temperatury 1-Wire® i urządzeń podłączonych do EMS-ESP?',
|
||||
DEVICE_ENTITIES: 'Encje urządzenia',
|
||||
SUPPORT_INFORMATION: 'Informacje dotyczące wsparcia',
|
||||
SUPPORT_INFORMATION: '{{I|i|}}nformacj{{e|i|}} o systemie',
|
||||
CLICK_HERE: 'Kliknij tu',
|
||||
HELP_INFORMATION_1: 'Aby uzyskać instrukcje dotyczące konfiguracji EMS-ESP skorzystaj z wiki w internecie',
|
||||
HELP_INFORMATION_1: 'Aby uzyskać instrukcje dotyczące konfiguracji EMS-ESP, skorzystaj z wiki w internecie',
|
||||
HELP_INFORMATION_2: 'Aby dołączyć do naszego serwera Discord i komunikować się na żywo ze społecznością',
|
||||
HELP_INFORMATION_3: 'Aby zaproponować nową funkcjonalność lub zgłosić problem',
|
||||
HELP_INFORMATION_4: 'Zgłaszając problem, nie zapomnij dołączyć informacji o swoim systemie!',
|
||||
HELP_INFORMATION_4: 'Zgłaszając problem, nie zapomnij pobrać i dołączyć informacji o swoim systemie!',
|
||||
HELP_INFORMATION_5: 'EMS-ESP jest darmowym projektem typu open-source. Aby go wesprzeć, rozważ przyznanie nam gwiazdki na Github!',
|
||||
SUPPORT_INFO: 'Pobierz informacje',
|
||||
UPLOAD: 'Wysyłanie',
|
||||
DOWNLOAD: '{{P|p||P}}obier{{anie|z||z}}',
|
||||
ABORTED: 'zostało przerwane!',
|
||||
@@ -194,21 +194,19 @@ const pl: BaseTranslation = {
|
||||
RELEASE_IS: 'wydanie to',
|
||||
RELEASE_NOTES: 'lista zmian',
|
||||
EMS_ESP_VER: 'Wersja EMS-ESP',
|
||||
PLATFORM: 'Urządzenie (platforma / SDK)',
|
||||
UPTIME: 'Czas działania systemu',
|
||||
CPU_FREQ: 'Taktowanie CPU',
|
||||
HEAP: 'HEAP (wolne / maksymalny przydział)',
|
||||
PSRAM: 'PSRAM (rozmiar / wolne)',
|
||||
FLASH: 'FLASH (rozmiar / taktowanie)',
|
||||
APPSIZE: 'Aplikacja (wykorzystane / wolne)',
|
||||
APPSIZE: 'Aplikacja (partycja: wykorzystane / wolne)',
|
||||
FILESYSTEM: 'System plików (wykorzystane / wolne)',
|
||||
BUFFER_SIZE: 'Maksymalna pojemność bufora (ilość wpisów)',
|
||||
COMPACT: 'Kompaktowy',
|
||||
ENABLE_OTA: 'Aktywuj aktualizację OTA',
|
||||
DOWNLOAD_CUSTOMIZATION_TEXT: 'Pobierz personalizacje.',
|
||||
DOWNLOAD_SCHEDULE_TEXT: 'Pobierz harmonogram zdarzeń.',
|
||||
DOWNLOAD_SETTINGS_TEXT: 'Pobierz ustawienia aplikacji. Uważaj jeśli udostępniasz plik z ustawieniami, ponieważ zawiera on hasła oraz inne wrażliwe informacje!',
|
||||
UPLOAD_TEXT: 'Wyślij firmware (.bin), ustawienia lub personalizacje (.json). Opcjonalnie, wyślij wcześniej plik walidacji (.md5).',
|
||||
DOWNLOAD_SETTINGS_TEXT: 'Pobierz ustawienia aplikacji. Uwaga! Plik z ustawieniami zawiera hasła oraz inne wrażliwe informacje systemowe! Nie udostepniaj go pochopnie!',
|
||||
UPLOAD_TEXT: 'Wyślij firmware (.bin), ustawienia lub personalizacje (.json). Opcjonalnie, wyślij wcześniej plik walidacji z sumą kontrolną (.md5).',
|
||||
UPLOADING: 'Wysłano',
|
||||
UPLOAD_DROP_TEXT: 'Przeciągnij tutaj plik lub kliknij',
|
||||
ERROR: 'Nieoczekiwany błąd, spróbuj ponownie!',
|
||||
@@ -281,14 +279,15 @@ const pl: BaseTranslation = {
|
||||
SCAN_AGAIN: 'Skanuj ponownie',
|
||||
NETWORK_SCANNER: 'Skaner sieci WiFi',
|
||||
NETWORK_NO_WIFI: 'Brak sieci WiFi w zasięgu',
|
||||
NETWORK_BLANK_SSID: 'pozostaw puste aby wyłączyć WiFi', // and enable ETH // TODO translate
|
||||
NETWORK_BLANK_SSID: 'pozostaw puste aby wyłączyć WiFi i włączyć ETH',
|
||||
NETWORK_BLANK_BSSID: 'pozostaw puste aby używać tylko SSID',
|
||||
TX_POWER: 'Moc nadawania',
|
||||
HOSTNAME: 'Nazwa w sieci',
|
||||
NETWORK_DISABLE_SLEEP: 'Wyłącz tryb uśpienia WiFi',
|
||||
NETWORK_LOW_BAND: 'Używaj mniejszej szerokości pasma WiFi (20MHz)',
|
||||
NETWORK_USE_DNS: 'Włącz wsparcie dla mDNS',
|
||||
NETWORK_ENABLE_CORS: 'Włącz wsparcie dla CORS',
|
||||
NETWORK_CORS_ORIGIN: 'CORS origin',
|
||||
NETWORK_CORS_ORIGIN: 'CORS Origin',
|
||||
NETWORK_ENABLE_IPV6: 'Włącz wsparcie dla IPv6',
|
||||
NETWORK_FIXED_IP: 'Użyj stałego adresu IP',
|
||||
NETWORK_GATEWAY: 'Brama',
|
||||
@@ -317,12 +316,20 @@ const pl: BaseTranslation = {
|
||||
SCHEDULE_TIMER_2: 'co minutę',
|
||||
SCHEDULE_TIMER_3: 'co godzinę',
|
||||
CUSTOM_ENTITIES: '{{N|n|}}iestandardowe{{|j|}} encj{{e|i|}}',
|
||||
ENTITIES_HELP_1: 'Zdefiniuj niestandardowe encje dla magistrali EMS.',
|
||||
ENTITIES_HELP_1: 'Zdefiniuj niestandardowe encje dla magistrali EMS.', // TODO translate
|
||||
ENTITIES_UPDATED: 'Niestandardowe encje zostały uaktualnione.',
|
||||
WRITEABLE: 'zapisywalna',
|
||||
WRITEABLE: 'Zapisywalna',
|
||||
SHOWING: 'Wyświetlane',
|
||||
SEARCH: 'Szukaj',
|
||||
CERT: 'TSL root certificate (leave blank to disable TSL)' // TODO translate
|
||||
CERT: 'Certyfikat główny TLS (pozostaw puste dla TLS-insecure)',
|
||||
ENABLE_TLS: 'Włącz wsparcie dla TLS',
|
||||
ON: 'włączony',
|
||||
OFF: 'wyłączony',
|
||||
POLARITY: 'Typ przekaźnika',
|
||||
ACTIVEHIGH: 'Wyzwalany stanem wysokim',
|
||||
ACTIVELOW: 'Wyzwalany stanem niskim',
|
||||
UNCHANGED: 'Zachowaj stan',
|
||||
ALWAYS: 'Zawsze'
|
||||
};
|
||||
|
||||
export default pl;
|
||||
|
||||
335
interface/src/i18n/sk/index.ts
Normal file
335
interface/src/i18n/sk/index.ts
Normal file
@@ -0,0 +1,335 @@
|
||||
import type { Translation } from '../i18n-types';
|
||||
/* prettier-ignore */
|
||||
/* eslint-disable */
|
||||
|
||||
const sk: Translation = {
|
||||
LANGUAGE: 'Jazyk',
|
||||
RETRY: 'Opakovať',
|
||||
LOADING: 'Načítanie',
|
||||
IS_REQUIRED: '{0} je požadovaných',
|
||||
SIGN_IN: 'Prihlásiť sa',
|
||||
SIGN_OUT: 'Odhlásiť sa',
|
||||
USERNAME: 'Užívateľské meno',
|
||||
PASSWORD: 'Heslo',
|
||||
SU_PASSWORD: 'su heslo',
|
||||
DASHBOARD: 'Panel',
|
||||
SETTINGS_OF: '{0} Nastavenia',
|
||||
HELP_OF: '{0} Pomoc',
|
||||
LOGGED_IN: 'Prihlásený ako {name}',
|
||||
PLEASE_SIGNIN: 'Ak chcete pokračovať, prihláste sa',
|
||||
UPLOAD_SUCCESSFUL: 'Nahratie úspešné',
|
||||
DOWNLOAD_SUCCESSFUL: 'Stiahnutie úspešné',
|
||||
INVALID_LOGIN: 'Nesprávne prihlasovacie údaje',
|
||||
NETWORK: 'Sieť',
|
||||
SECURITY: 'Zabezpečenie',
|
||||
ONOFF_CAP: 'ZAP/VYP',
|
||||
ONOFF: 'zap/vyp',
|
||||
TYPE: 'Typ',
|
||||
DESCRIPTION: 'Popis',
|
||||
ENTITIES: 'Entity',
|
||||
REFRESH: 'Obnoviť',
|
||||
EXPORT: 'Export',
|
||||
DEVICE_DETAILS: 'Detaily zariadenia',
|
||||
ID_OF: '{0} ID',
|
||||
DEVICE: 'Zariadenie',
|
||||
PRODUCT: 'Produkt',
|
||||
VERSION: 'Verzia',
|
||||
BRAND: 'Značka',
|
||||
ENTITY_NAME: 'Názov entity',
|
||||
VALUE: '{{Value|value}}',
|
||||
DEVICE_DATA: 'Dáta zariadenia',
|
||||
SENSOR_DATA: 'Dáta snímača',
|
||||
DEVICES: 'Zariadenia',
|
||||
SENSORS: 'Snímače',
|
||||
RUN_COMMAND: 'Volať príkaz',
|
||||
CHANGE_VALUE: 'Zmena hodnoty',
|
||||
CANCEL: 'Zrušiť',
|
||||
RESET: 'Reset',
|
||||
APPLY_CHANGES: 'Aplikovať zmeny ({0})',
|
||||
UPDATE: 'Aktualizovať',
|
||||
EXECUTE: 'Spustiť',
|
||||
REMOVE: 'Odstrániť',
|
||||
PROBLEM_UPDATING: 'Problém s aktualizáciou',
|
||||
PROBLEM_LOADING: 'Problém s načítaním',
|
||||
ANALOG_SENSOR: 'Analógový snímač',
|
||||
ANALOG_SENSORS: 'Analógové snímače',
|
||||
SETTINGS: 'Nastavenia',
|
||||
UPDATED_OF: '{0} aktualizovaných',
|
||||
UPDATE_OF: '{0} aktualizované',
|
||||
REMOVED_OF: '{0} odstránených',
|
||||
DELETION_OF: '{0} zmazaných',
|
||||
OFFSET: 'Ofset',
|
||||
FACTOR: 'Faktor',
|
||||
FREQ: 'Frekvencia',
|
||||
DUTY_CYCLE: 'Duty Cycle',
|
||||
UNIT: 'UoM',
|
||||
STARTVALUE: 'Počiatočná hodnota',
|
||||
WARN_GPIO: 'Upozornenie: Buďte opatrní pri priraďovaní GPIO!',
|
||||
EDIT: 'Editovať',
|
||||
SENSOR: 'Snímač',
|
||||
TEMP_SENSOR: 'Snímač teploty',
|
||||
TEMP_SENSORS: 'Snímače teploty',
|
||||
WRITE_CMD_SENT: 'Príkaz zápisu bol odoslaný',
|
||||
EMS_BUS_WARNING: 'Zbernica EMS odpojená. Ak toto upozornenie pretrváva aj po niekoľkých sekundách, skontrolujte nastavenia a profil dosky',
|
||||
EMS_BUS_SCANNING: 'Zisťovanie EMS zariadení...',
|
||||
CONNECTED: 'Pripojené',
|
||||
TX_ISSUES: 'Problémy s Tx – skontrolujte Tx režim',
|
||||
DISCONNECTED: 'Odpojené',
|
||||
EMS_SCAN: 'Naozaj chcete spustiť úplnú kontrolu zariadenia zbernice EMS?',
|
||||
EMS_BUS_STATUS: 'Stav zbernice EMS',
|
||||
ACTIVE_DEVICES: 'Aktívne zariadenia a snímače',
|
||||
EMS_DEVICE: 'EMS zariadenie',
|
||||
SUCCESS: 'ÚSPEŠNÉ',
|
||||
FAIL: 'ZLYHANIE',
|
||||
QUALITY: 'KVALITA',
|
||||
SCAN_DEVICES: 'Scan pre nové zariadenia',
|
||||
EMS_BUS_STATUS_TITLE: 'EMS zbernica & stav aktivity',
|
||||
SCAN: 'Scan',
|
||||
STATUS_NAMES: [
|
||||
'EMS Telegramy prijaté (Rx)',
|
||||
'EMS Čítania (Tx)',
|
||||
'EMS Zápisy (Tx)',
|
||||
'Čítanie snímača teploty',
|
||||
'Analógové snímanie',
|
||||
'MQTT Publikovanie',
|
||||
'API volania',
|
||||
'Syslog správy'
|
||||
],
|
||||
NUM_DEVICES: '{num} Zariadenia{{s}}',
|
||||
NUM_TEMP_SENSORS: '{num} Teplotné snímače{{s}}',
|
||||
NUM_ANALOG_SENSORS: '{num} Analógové snímače{{s}}',
|
||||
NUM_DAYS: '{num} dní{{s}}',
|
||||
NUM_SECONDS: '{num} sekúnd{{s}}',
|
||||
NUM_HOURS: '{num} hodín{{s}}',
|
||||
NUM_MINUTES: '{num} minút{{s}}',
|
||||
APPLICATION_SETTINGS: 'Nastavenia aplikácie',
|
||||
CUSTOMIZATIONS: 'Prispôsobenia',
|
||||
APPLICATION_RESTARTING: 'EMS-ESP sa reštartuje',
|
||||
INTERFACE_BOARD_PROFILE: 'Profil boardu rozhrania',
|
||||
BOARD_PROFILE_TEXT: 'Vyberte vopred nakonfigurovaný profil dosky rozhrania zo zoznamu nižšie alebo vyberte možnosť Vlastné a nakonfigurujte svoje vlastné hardvérové nastavenia',
|
||||
BOARD_PROFILE: 'Board profil',
|
||||
CUSTOM: 'Vlastné',
|
||||
GPIO_OF: '{0} GPIO',
|
||||
BUTTON: 'Tlačidlo',
|
||||
TEMPERATURE: 'Teplota',
|
||||
PHY_TYPE: 'Eth PHY Typ',
|
||||
DISABLED: 'zakázané',
|
||||
TX_MODE: 'Tx režim',
|
||||
HARDWARE: 'Hardware',
|
||||
EMS_BUS: '{{BUS|EMS BUS}}',
|
||||
GENERAL_OPTIONS: 'Všeobecné možnosti',
|
||||
LANGUAGE_ENTITIES: 'Jazyk (pre entity zariadenia)',
|
||||
HIDE_LED: 'Skryť LED',
|
||||
ENABLE_TELNET: 'Povoliť Telnet konzolu',
|
||||
ENABLE_ANALOG: 'Povoliť analógové snímače',
|
||||
CONVERT_FAHRENHEIT: 'Previesť hodnoty teploty na fahrenheity',
|
||||
BYPASS_TOKEN: 'Vynechajte autorizáciu prístupového tokenu pri volaniach API',
|
||||
READONLY: 'Povoliť režim len na čítanie (blokuje všetky odchádzajúce príkazy EMS Tx Write)',
|
||||
UNDERCLOCK_CPU: 'Podtaktovanie rýchlosti procesora',
|
||||
HEATINGOFF: 'Spustite kotol s núteným vykurovaním',
|
||||
ENABLE_SHOWER_TIMER: 'Povoliť časovač sprchovania',
|
||||
ENABLE_SHOWER_ALERT: 'Povoliť upozornenie na sprchu',
|
||||
TRIGGER_TIME: 'Čas spustenia',
|
||||
COLD_SHOT_DURATION: 'Trvanie studeného záberu',
|
||||
FORMATTING_OPTIONS: 'Možnosti formátovania',
|
||||
BOOLEAN_FORMAT_DASHBOARD: 'Panel Boolean formát',
|
||||
BOOLEAN_FORMAT_API: 'Boolean formát API/MQTT',
|
||||
ENUM_FORMAT: 'Enum formát API/MQTT',
|
||||
INDEX: 'Index',
|
||||
ENABLE_PARASITE: 'Povolenie parazitného napájania',
|
||||
LOGGING: 'Logovanie',
|
||||
LOG_HEX: 'Záznam telegramov EMS v hexadecimálnej sústave',
|
||||
ENABLE_SYSLOG: 'Povoliť Syslog',
|
||||
LOG_LEVEL: 'Log úroveň',
|
||||
MARK_INTERVAL: 'Označenie intervalu',
|
||||
SECONDS: 'sekundy',
|
||||
MINUTES: 'minúty',
|
||||
HOURS: 'hodiny',
|
||||
RESTART: 'Reštart',
|
||||
RESTART_TEXT: 'EMS-ESP sa musí reštartovať, aby sa použili zmenené systémové nastavenia',
|
||||
RESTART_CONFIRM: 'Ste si istí, že chcete reštartovať EMS-ESP?',
|
||||
COMMAND: 'Príkaz',
|
||||
CUSTOMIZATIONS_RESTART: 'Ste si istí, že chcete reštartovať EMS-ESP?',
|
||||
CUSTOMIZATIONS_FULL: 'Vybrané subjekty prekročili limit. Prosím, ukladajte v dávkach',
|
||||
CUSTOMIZATIONS_SAVED: 'Uložené prispôsobenia',
|
||||
CUSTOMIZATIONS_HELP_1: 'Vyberte zariadenie a prispôsobte možnosti entít alebo kliknutím premenujte',
|
||||
CUSTOMIZATIONS_HELP_2: 'označiť ako obľúbené',
|
||||
CUSTOMIZATIONS_HELP_3: 'zakázať akciu zápisu',
|
||||
CUSTOMIZATIONS_HELP_4: 'vylúčiť z MQTT a API',
|
||||
CUSTOMIZATIONS_HELP_5: 'skryť z panela',
|
||||
CUSTOMIZATIONS_HELP_6: 'odstrániť z pamäte',
|
||||
SELECT_DEVICE: 'Zvoliť zariadenie',
|
||||
SET_ALL: 'nastaviť všetko',
|
||||
OPTIONS: 'Možnosti',
|
||||
NAME: 'Názov',
|
||||
CUSTOMIZATIONS_RESET: 'Naozaj chcete odstrániť všetky prispôsobenia vrátane vlastných nastavení snímačov teploty a analógových snímačov?',
|
||||
DEVICE_ENTITIES: 'Entity zariadenia',
|
||||
SUPPORT_INFORMATION: 'Informácie o podpore',
|
||||
CLICK_HERE: 'Kliknite tu',
|
||||
HELP_INFORMATION_1: 'Navštívte online wiki, kde nájdete pokyny na konfiguráciu EMS-ESP',
|
||||
HELP_INFORMATION_2: 'Pre živý komunitný chat sa pripojte na náš Discord server',
|
||||
HELP_INFORMATION_3: 'Ak chcete požiadať o funkciu alebo nahlásiť chybu',
|
||||
HELP_INFORMATION_4: 'nezabudnite si stiahnuť a pripojiť informácie o vašom systéme, aby ste mohli rýchlejšie reagovať pri nahlasovaní problému',
|
||||
HELP_INFORMATION_5: 'EMS-ESP je bezplatný a open source projekt. Podporte jeho budúci vývoj tým, že mu dáte hviezdičku na Github!',
|
||||
UPLOAD: 'Nahrať',
|
||||
DOWNLOAD: '{{S|s|s}}tiahnuť',
|
||||
ABORTED: 'zrušené',
|
||||
FAILED: 'chybné',
|
||||
SUCCESSFUL: 'úspešné',
|
||||
SYSTEM: 'Systém',
|
||||
LOG_OF: '{0} Log',
|
||||
STATUS_OF: '{0} Stav',
|
||||
UPLOAD_DOWNLOAD: 'Nahrať/Stiahnuť',
|
||||
VERSION_ON: 'Momentálne ste vo verzii',
|
||||
SYSTEM_APPLY_FIRMWARE: 'na použitie nového firmvéru',
|
||||
CLOSE: 'Zatvoriť',
|
||||
USE: 'Použiť',
|
||||
FACTORY_RESET: 'Továrenské nastavenia',
|
||||
SYSTEM_FACTORY_TEXT: 'Zariadenie bolo obnovené z výroby a teraz sa reštartuje',
|
||||
SYSTEM_FACTORY_TEXT_DIALOG: 'Naozaj chcete resetovať EMS-ESP na predvolené výrobné nastavenia?',
|
||||
VERSION_CHECK: 'Kontrola verzie',
|
||||
THE_LATEST: 'Posledná',
|
||||
OFFICIAL: 'officiálna',
|
||||
DEVELOPMENT: 'vývojárska',
|
||||
RELEASE_IS: 'vydanie je',
|
||||
RELEASE_NOTES: 'poznámky k vydaniu',
|
||||
EMS_ESP_VER: 'EMS-ESP verzia',
|
||||
UPTIME: 'Beh systému',
|
||||
HEAP: 'Zásobník (voľné / max pridelenie)',
|
||||
PSRAM: 'PSRAM (Veľkosť / Voľné)',
|
||||
FLASH: 'Flash chip (Veľkosť / Rýchlosť)',
|
||||
APPSIZE: 'Applikácia (Priečka: Použité / Voľné)',
|
||||
FILESYSTEM: 'Súborový systém (Použité / Voľné)',
|
||||
BUFFER_SIZE: 'Maximálna veľkosť vyrovnávacej pamäte',
|
||||
COMPACT: 'Kompaktné',
|
||||
ENABLE_OTA: 'Povoliť OTA aktualizácie',
|
||||
DOWNLOAD_CUSTOMIZATION_TEXT: 'Stiahnutie prispôsobení entity',
|
||||
DOWNLOAD_SCHEDULE_TEXT: 'Stiahnutie plánovača udalostí',
|
||||
DOWNLOAD_SETTINGS_TEXT: 'Stiahnite si nastavenia aplikácie. Pri zdieľaní nastavení buďte opatrní, pretože tento súbor obsahuje heslá a iné citlivé systémové informácie.',
|
||||
UPLOAD_TEXT: 'Najskôr nahrajte nový súbor firmvéru (.bin), nastavenia alebo prispôsobenia (.json), pre voliteľné overenie nahrajte súbor (.md5)',
|
||||
UPLOADING: 'Nahrávanie',
|
||||
UPLOAD_DROP_TEXT: 'Zahodiť súbor alebo kliknúť sem',
|
||||
ERROR: 'Neočakávaná chyba, prosím skúste to znova',
|
||||
TIME_SET: 'Nastavený čas',
|
||||
MANAGE_USERS: 'Správa používateľov',
|
||||
IS_ADMIN: 'je Admin',
|
||||
USER_WARNING: 'Musíte mať nakonfigurovaného aspoň jedného používateľa administrátora',
|
||||
ADD: 'Pridať',
|
||||
ACCESS_TOKEN_FOR: 'Prístupový token pre',
|
||||
ACCESS_TOKEN_TEXT: 'Nižšie uvedený token sa používa pri volaniach REST API, ktoré vyžadujú autorizáciu. Môže byť odovzdaný buď ako token Bearer v hlavičke Authorization (Autorizácia), alebo v parametri dotazu URL access_token.',
|
||||
GENERATING_TOKEN: 'Generovanie tokenu',
|
||||
USER: 'Užívateľ',
|
||||
MODIFY: 'Upraviť',
|
||||
SU_TEXT: 'Heslo su (superužívateľ) sa používa na podpisovanie autentifikačných tokenov a tiež na povolenie oprávnení správcu v rámci konzoly.',
|
||||
NOT_ENABLED: 'Nie je povolené',
|
||||
ERRORS_OF: '{0} errory',
|
||||
DISCONNECT_REASON: 'Dôvod odpojenia',
|
||||
ENABLE_MQTT: 'Povoliť MQTT',
|
||||
BROKER: 'Broker',
|
||||
CLIENT: 'Klient',
|
||||
BASE_TOPIC: 'Base',
|
||||
OPTIONAL: 'voliteľné',
|
||||
FORMATTING: 'Formátovanie',
|
||||
MQTT_FORMAT: 'Formát témy/záťaže',
|
||||
MQTT_NEST_1: 'Vnorené do jednej témy',
|
||||
MQTT_NEST_2: 'Ako jednotlivé témy',
|
||||
MQTT_RESPONSE: 'Publikovanie výstupu príkazu do témy `response`',
|
||||
MQTT_PUBLISH_TEXT_1: 'Zverejňovanie tém jednotlivých hodnôt pri zmene',
|
||||
MQTT_PUBLISH_TEXT_2: 'Publikovanie do tém príkazov (ioBroker)',
|
||||
MQTT_PUBLISH_TEXT_3: 'Povolenie zisťovania MQTT',
|
||||
MQTT_PUBLISH_TEXT_4: 'Predpona tém Discovery',
|
||||
MQTT_PUBLISH_TEXT_5: 'Typ zistenia',
|
||||
MQTT_PUBLISH_INTERVALS: 'Intervaly zverejňovania',
|
||||
MQTT_INT_BOILER: 'Kotly a tepelné čerpadlá',
|
||||
MQTT_INT_THERMOSTATS: 'Termostaty',
|
||||
MQTT_INT_SOLAR: 'Solárne moduly',
|
||||
MQTT_INT_MIXER: 'Zmiešavacie moduley',
|
||||
MQTT_QUEUE: 'Fronta MQTT',
|
||||
DEFAULT: 'Predvolené',
|
||||
MQTT_ENTITY_FORMAT: 'ID formát entity',
|
||||
MQTT_ENTITY_FORMAT_0: 'Jedna inštancia, dlhý názov (v3.4)',
|
||||
MQTT_ENTITY_FORMAT_1: 'Jedna inštancia, krátky názov',
|
||||
MQTT_ENTITY_FORMAT_2: 'Viacero inštancií, krátky názov',
|
||||
MQTT_CLEAN_SESSION: 'Nastavenie čistej relácie',
|
||||
MQTT_RETAIN_FLAG: 'Vždy nastaviť príznak Retain',
|
||||
INACTIVE: 'Neaktívne',
|
||||
ACTIVE: 'Aktívne',
|
||||
UNKNOWN: 'Neznáme',
|
||||
SET_TIME: 'Nastavený čas',
|
||||
SET_TIME_TEXT: 'Na nastavenie času zadajte miestny dátum a čas nižšie',
|
||||
LOCAL_TIME: 'Lokálny čas',
|
||||
UTC_TIME: 'UTC čas',
|
||||
ENABLE_NTP: 'Povoliť NTP',
|
||||
NTP_SERVER: 'NTP Server',
|
||||
TIME_ZONE: 'Časová zóna',
|
||||
ACCESS_POINT: 'Prístupový bod',
|
||||
AP_PROVIDE: 'Povoliť prístupový bod',
|
||||
AP_PROVIDE_TEXT_1: 'vždy',
|
||||
AP_PROVIDE_TEXT_2: 'keď WiFi je odpojená',
|
||||
AP_PROVIDE_TEXT_3: 'nikdy',
|
||||
AP_PREFERRED_CHANNEL: 'Preferovaný kanál',
|
||||
AP_HIDE_SSID: 'Skryť SSID',
|
||||
AP_CLIENTS: 'AP klienti',
|
||||
AP_MAX_CLIENTS: 'Max klientov',
|
||||
AP_LOCAL_IP: 'Lokálna IP',
|
||||
NETWORK_SCAN: 'Scan WiFi siete',
|
||||
IDLE: 'Nečinné',
|
||||
LOST: 'Stratené',
|
||||
SCANNING: 'Scanovanie',
|
||||
SCAN_AGAIN: 'Scanovať znova',
|
||||
NETWORK_SCANNER: 'Sieťový scanner',
|
||||
NETWORK_NO_WIFI: 'WiFi siete nenájdené',
|
||||
NETWORK_BLANK_SSID: 'nechajte prázdne, ak chcete zakázať WiFi a povoliť ETH',
|
||||
NETWORK_BLANK_BSSID: 'ponechajte prázdne, ak chcete používať iba SSID',
|
||||
TX_POWER: 'Tx výkon',
|
||||
HOSTNAME: 'Hostname',
|
||||
NETWORK_DISABLE_SLEEP: 'Zakázanie režimu spánku WiFi',
|
||||
NETWORK_LOW_BAND: 'Používanie menšej šírky pásma WiFi',
|
||||
NETWORK_USE_DNS: 'Povoliť mDNS službu',
|
||||
NETWORK_ENABLE_CORS: 'Povoliť CORS',
|
||||
NETWORK_CORS_ORIGIN: 'CORS origin',
|
||||
NETWORK_ENABLE_IPV6: 'Povoliť podporu IPv6',
|
||||
NETWORK_FIXED_IP: 'Použiť fixnú IP adresu',
|
||||
NETWORK_GATEWAY: 'Brána',
|
||||
NETWORK_SUBNET: 'Maska podsiete',
|
||||
NETWORK_DNS: 'DNS servery',
|
||||
ADDRESS_OF: '{0} adries',
|
||||
ADMIN: 'Admin',
|
||||
GUEST: 'Hosť',
|
||||
NEW: 'Nová',
|
||||
NEW_NAME_OF: 'Nových {0} názvov',
|
||||
ENTITY: 'entita',
|
||||
MIN: 'min',
|
||||
MAX: 'max',
|
||||
BLOCK_NAVIGATE_1: 'Máte neuložené zmeny',
|
||||
BLOCK_NAVIGATE_2: 'Ak prejdete na inú stránku, neuložené zmeny sa stratia. Ste si istí, že chcete opustiť túto stránku?',
|
||||
STAY: 'Zostať',
|
||||
LEAVE: 'Opustiť',
|
||||
SCHEDULER: 'Plánovač',
|
||||
SCHEDULER_HELP_1: 'Automatizujte príkazy pridaním naplánovaných udalostí nižšie. Nastavte jedinečné meno na aktiváciu/deaktiváciu cez API/MQTT.',
|
||||
SCHEDULER_HELP_2: 'Použite 00:00 na jednorazové spustenie pri štarte',
|
||||
SCHEDULE: 'Plánovať',
|
||||
TIME: 'Čas',
|
||||
TIMER: 'Časovač',
|
||||
SCHEDULE_UPDATED: 'Plánovanie aktualizované',
|
||||
SCHEDULE_TIMER_1: 'pri spustení',
|
||||
SCHEDULE_TIMER_2: 'každú minútu',
|
||||
SCHEDULE_TIMER_3: 'každú hodinu',
|
||||
CUSTOM_ENTITIES: 'Vlastné entity',
|
||||
ENTITIES_HELP_1: 'Získavanie vlastných entít zo zbernice EMS', // TODO translate
|
||||
ENTITIES_UPDATED: 'Aktualizované entity',
|
||||
WRITEABLE: 'Zapísateľný',
|
||||
SHOWING: 'Zobrazenie',
|
||||
SEARCH: 'Vyhľadať',
|
||||
CERT: 'Koreňový certifikát TLS (ak chcete vypnúť TLS, nechajte prázdne)',
|
||||
ENABLE_TLS: 'Povoliť TLS',
|
||||
ON: 'Zap',
|
||||
OFF: 'Vyp',
|
||||
POLARITY: 'Polarita',
|
||||
ACTIVEHIGH: 'Aktívny Vysoký',
|
||||
ACTIVELOW: 'Aktívny Nízky',
|
||||
UNCHANGED: 'Nezmenené',
|
||||
ALWAYS: 'Vždy'
|
||||
};
|
||||
|
||||
export default sk;
|
||||
@@ -126,6 +126,7 @@ const sv: Translation = {
|
||||
BYPASS_TOKEN: 'Inaktivera Token-autensiering för API-anrop',
|
||||
READONLY: 'Aktivera read-only (blockerar alla utgående skrivkommandon mot EMS-bussen)',
|
||||
UNDERCLOCK_CPU: 'Nedklocka Processorhastighet',
|
||||
HEATINGOFF: 'Start boiler with forced heating off', // TODO translate
|
||||
ENABLE_SHOWER_TIMER: 'Aktivera Dusch-timer',
|
||||
ENABLE_SHOWER_ALERT: 'Aktivera Dusch-varning',
|
||||
TRIGGER_TIME: 'Aktiveringstid',
|
||||
@@ -170,7 +171,6 @@ const sv: Translation = {
|
||||
HELP_INFORMATION_3: 'Önska en ny funktion eller rapportera en bugg',
|
||||
HELP_INFORMATION_4: 'Bifoga din systeminformation för snabbare hantering när du rapporterar ett problem',
|
||||
HELP_INFORMATION_5: 'EMS-ESP är gratis och är öppen källkod. Bidra till utvecklingen genom att ge oss en stjärna på GitHub!',
|
||||
SUPPORT_INFO: 'Supportinfo',
|
||||
UPLOAD: 'Uppladdning',
|
||||
DOWNLOAD: '{{N|n|n}}edladdning',
|
||||
ABORTED: 'Avbruten',
|
||||
@@ -194,13 +194,11 @@ const sv: Translation = {
|
||||
RELEASE_IS: 'release är', // TODO translate
|
||||
RELEASE_NOTES: 'release-logg',
|
||||
EMS_ESP_VER: 'EMS-ESP Version',
|
||||
PLATFORM: 'Enhet (Plattform / SDK)',
|
||||
UPTIME: 'Systemets Upptid',
|
||||
CPU_FREQ: 'CPU-frekvens',
|
||||
HEAP: 'Heap (Ledigt / Max allokerat)',
|
||||
PSRAM: 'PSRAM (Storlek / Ledigt)',
|
||||
FLASH: 'Flashminne (Storlek / Hastighet)',
|
||||
APPSIZE: 'Applikationer (Använt / Ledigt)',
|
||||
APPSIZE: 'Applikationer (Partition: Använt / Ledigt)',
|
||||
FILESYSTEM: 'Filsystem (Använt / Ledigt)',
|
||||
BUFFER_SIZE: 'Max Bufferstorlek',
|
||||
COMPACT: 'Komprimera',
|
||||
@@ -230,7 +228,7 @@ const sv: Translation = {
|
||||
BROKER: 'Broker',
|
||||
CLIENT: 'Client',
|
||||
BASE_TOPIC: 'Base',
|
||||
OPTIONAL: 'Valfritt',
|
||||
OPTIONAL: 'valfritt',
|
||||
FORMATTING: 'Formatering',
|
||||
MQTT_FORMAT: 'Topic/Payload Format',
|
||||
MQTT_NEST_1: 'Nestlat i en topic.',
|
||||
@@ -282,6 +280,7 @@ const sv: Translation = {
|
||||
NETWORK_SCANNER: 'Hittade nätverk',
|
||||
NETWORK_NO_WIFI: 'Inga WiFi-nätverk hittades',
|
||||
NETWORK_BLANK_SSID: 'lämna blankt för att inaktivera WiFi', // and enable ETH // TODO translate
|
||||
NETWORK_BLANK_BSSID: 'leave blank to use only SSID', // TODO translate
|
||||
TX_POWER: 'Tx Effekt',
|
||||
HOSTNAME: 'Värdnamn',
|
||||
NETWORK_DISABLE_SLEEP: 'Inaktivera sömnläge',
|
||||
@@ -322,7 +321,15 @@ const sv: Translation = {
|
||||
WRITEABLE: 'Writeable', // TODO translate
|
||||
SHOWING: 'Showing', // TODO translate
|
||||
SEARCH: 'Search', // TODO translate
|
||||
CERT: 'TSL root certificate (leave blank to disable TSL)' // TODO translate
|
||||
CERT: 'TLS root certificate (leave blank for insecure)', // TODO translate
|
||||
ENABLE_TLS: 'Aktivera TLS',
|
||||
ON: 'On', // TODO translate
|
||||
OFF: 'Off', // TODO translate
|
||||
POLARITY: 'Polarity', // TODO translate
|
||||
ACTIVEHIGH: 'Active High', // TODO translate
|
||||
ACTIVELOW: 'Active Low', // TODO translate
|
||||
UNCHANGED: 'Unchanged', // TODO translate
|
||||
ALWAYS: 'Always' // TODO translate
|
||||
};
|
||||
|
||||
export default sv;
|
||||
|
||||
@@ -126,6 +126,7 @@ const tr: Translation = {
|
||||
BYPASS_TOKEN: 'API bağlantılarında Erişim Jeton onaylamasını geç',
|
||||
READONLY: 'Salt okunur modu devreye al (bütün giden EMS Tx Yazma komutlarını engeller)',
|
||||
UNDERCLOCK_CPU: 'İşlemci hızını düşür',
|
||||
HEATINGOFF: 'Start boiler with forced heating off', // TODO translate
|
||||
ENABLE_SHOWER_TIMER: 'Duş Sayacını Devreye Al',
|
||||
ENABLE_SHOWER_ALERT: 'Duş Alarmını Devreye Al',
|
||||
TRIGGER_TIME: 'Tetikleme Zamanı',
|
||||
@@ -170,7 +171,6 @@ const tr: Translation = {
|
||||
HELP_INFORMATION_3: 'Yeni bir özellik talep etmek yada hata bildirmek için',
|
||||
HELP_INFORMATION_4: 'Bir sorun bildirirken daha hızlı bir dönüş için sistem bilginizi indirip eklemeyi unutmayın',
|
||||
HELP_INFORMATION_5: 'EMS-ESP ücretsiz ve açık kaynaklı bir projedir. Lütfen geliştirmeyi desteklemek için Githubda projeye yıldız verin!',
|
||||
SUPPORT_INFO: 'Destek Bilgisi',
|
||||
UPLOAD: 'Yükleme',
|
||||
DOWNLOAD: '{{İ|i|i}}İndirme',
|
||||
ABORTED: 'iptal edildi',
|
||||
@@ -194,13 +194,11 @@ const tr: Translation = {
|
||||
RELEASE_IS: 'release is', // TODO translate
|
||||
RELEASE_NOTES: 'yayınlanma notları',
|
||||
EMS_ESP_VER: 'EMS-ESP Sürümü',
|
||||
PLATFORM: 'Cihaz (Platform / SDK)',
|
||||
UPTIME: 'Sistem Çalışma Süresi',
|
||||
CPU_FREQ: 'İşlemci frekansı',
|
||||
HEAP: 'Yığın (Boş / Maksimum Tahsis)',
|
||||
PSRAM: 'PSRAM (Boyut / Boş)',
|
||||
FLASH: 'Flash Çipi (Boyut / Hız)',
|
||||
APPSIZE: 'Uygulama (Kullanılmış / Boş)',
|
||||
APPSIZE: 'Uygulama (Bölme: Kullanılmış / Boş)',
|
||||
FILESYSTEM: 'Dosya Sistemi (Kullanılmış / Boş)',
|
||||
BUFFER_SIZE: 'En fazla bellek boyutu',
|
||||
COMPACT: 'Sıkışık',
|
||||
@@ -230,7 +228,7 @@ const tr: Translation = {
|
||||
BROKER: 'Aracı',
|
||||
CLIENT: 'İstemci',
|
||||
BASE_TOPIC: 'Merkez',
|
||||
OPTIONAL: 'Seçenekli',
|
||||
OPTIONAL: 'seçenekli',
|
||||
FORMATTING: 'Biçimlendiriliyor',
|
||||
MQTT_FORMAT: 'Konu/Mesaj Biçimi',
|
||||
MQTT_NEST_1: 'Tek konu üzerine yerleşmiş',
|
||||
@@ -282,6 +280,7 @@ const tr: Translation = {
|
||||
NETWORK_SCANNER: 'Ağ Tarayıcısı',
|
||||
NETWORK_NO_WIFI: 'Hiçbir Kablosuz Ağ bulunamadı',
|
||||
NETWORK_BLANK_SSID: 'Kablosuz ağı devre dışı bırakmak için boş bırakın', // TODO translate
|
||||
NETWORK_BLANK_BSSID: 'leave blank to use only SSID', // TODO translate
|
||||
TX_POWER: 'Aktarım gücü',
|
||||
HOSTNAME: 'Ana Makine Adı',
|
||||
NETWORK_DISABLE_SLEEP: 'Kablosuz uyku modunu devre dışına al',
|
||||
@@ -322,7 +321,15 @@ const tr: Translation = {
|
||||
WRITEABLE: 'Writeable', // TODO translate
|
||||
SHOWING: 'Showing', // TODO translate
|
||||
SEARCH: 'Search', // TODO translate
|
||||
CERT: 'TSL root certificate (leave blank to disable TSL)' // TODO translate
|
||||
CERT: 'TLS root certificate (leave blank for insecure)',
|
||||
ENABLE_TLS: 'TLS deveye al',
|
||||
ON: 'On', // TODO translate
|
||||
OFF: 'Off', // TODO translate
|
||||
POLARITY: 'Polarity', // TODO translate
|
||||
ACTIVEHIGH: 'Active High', // TODO translate
|
||||
ACTIVELOW: 'Active Low', // TODO translate
|
||||
UNCHANGED: 'Unchanged', // TODO translate
|
||||
ALWAYS: 'Always' // TODO translate
|
||||
};
|
||||
|
||||
export default tr;
|
||||
|
||||
@@ -20,15 +20,15 @@ const Dashboard: FC = () => {
|
||||
return (
|
||||
<>
|
||||
<RouterTabs value={routerTab}>
|
||||
<Tab value="devices" label={LL.DEVICES()} />
|
||||
<Tab value="sensors" label={LL.SENSORS()} />
|
||||
<Tab value="status" label="Status" />
|
||||
<Tab value="/dashboard/devices" label={LL.DEVICES()} />
|
||||
<Tab value="/dashboard/sensors" label={LL.SENSORS()} />
|
||||
<Tab value="/dashboard/status" label="Status" />
|
||||
</RouterTabs>
|
||||
<Routes>
|
||||
<Route path="devices" element={<DashboardDevices />} />
|
||||
<Route path="sensors" element={<DashboardSensors />} />
|
||||
<Route path="status" element={<DashboardStatus />} />
|
||||
<Route path="/*" element={<Navigate replace to="devices" />} />
|
||||
<Route path="*" element={<Navigate replace to="/dashboard/devices" />} />
|
||||
</Routes>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import CommentsDisabledOutlinedIcon from '@mui/icons-material/CommentsDisabledOutlined';
|
||||
import EditIcon from '@mui/icons-material/Edit';
|
||||
import EditOffOutlinedIcon from '@mui/icons-material/EditOffOutlined';
|
||||
import FormatListNumberedIcon from '@mui/icons-material/FormatListNumbered';
|
||||
import DownloadIcon from '@mui/icons-material/GetApp';
|
||||
import HighlightOffIcon from '@mui/icons-material/HighlightOff';
|
||||
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
|
||||
import KeyboardArrowDownOutlinedIcon from '@mui/icons-material/KeyboardArrowDownOutlined';
|
||||
import KeyboardArrowUpOutlinedIcon from '@mui/icons-material/KeyboardArrowUpOutlined';
|
||||
@@ -26,6 +27,7 @@ import {
|
||||
Grid,
|
||||
Typography
|
||||
} from '@mui/material';
|
||||
|
||||
import { useRowSelect } from '@table-library/react-table-library/select';
|
||||
import { useSort, SortToggleType } from '@table-library/react-table-library/sort';
|
||||
import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table';
|
||||
@@ -34,6 +36,7 @@ import { useRequest } from 'alova';
|
||||
import { useState, useContext, useEffect, useCallback, useLayoutEffect } from 'react';
|
||||
|
||||
import { IconContext } from 'react-icons';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { toast } from 'react-toastify';
|
||||
import DashboardDevicesDialog from './DashboardDevicesDialog';
|
||||
import DeviceIcon from './DeviceIcon';
|
||||
@@ -52,15 +55,17 @@ import { AuthenticatedContext } from 'contexts/authentication';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
const DashboardDevices: FC = () => {
|
||||
const [size, setSize] = useState([0, 0]);
|
||||
const { me } = useContext(AuthenticatedContext);
|
||||
const { LL } = useI18nContext();
|
||||
const [size, setSize] = useState([0, 0]);
|
||||
const [selectedDeviceValue, setSelectedDeviceValue] = useState<DeviceValue>();
|
||||
const [onlyFav, setOnlyFav] = useState(false);
|
||||
const [deviceValueDialogOpen, setDeviceValueDialogOpen] = useState(false);
|
||||
const [showDeviceInfo, setShowDeviceInfo] = useState<boolean>(false);
|
||||
const [selectedDevice, setSelectedDevice] = useState<number>();
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { data: coreData, send: readCoreData } = useRequest(() => EMSESP.readCoreData(), {
|
||||
initialData: {
|
||||
connected: true,
|
||||
@@ -89,8 +94,14 @@ const DashboardDevices: FC = () => {
|
||||
}, []);
|
||||
|
||||
const leftOffset = () => {
|
||||
const left = document.getElementById('devices-window')?.getBoundingClientRect().left;
|
||||
const right = document.getElementById('devices-window')?.getBoundingClientRect().right;
|
||||
const devicesWindow = document.getElementById('devices-window');
|
||||
if (!devicesWindow) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const clientRect = devicesWindow.getBoundingClientRect();
|
||||
const left = clientRect.left;
|
||||
const right = clientRect.right;
|
||||
|
||||
if (!left || !right) {
|
||||
return 0;
|
||||
@@ -263,13 +274,16 @@ const DashboardDevices: FC = () => {
|
||||
}, [escFunction]);
|
||||
|
||||
const refreshData = () => {
|
||||
if (deviceValueDialogOpen) {
|
||||
return;
|
||||
if (!deviceValueDialogOpen) {
|
||||
selectedDevice ? void readDeviceData(selectedDevice) : void readCoreData();
|
||||
}
|
||||
if (selectedDevice) {
|
||||
void readDeviceData(selectedDevice);
|
||||
};
|
||||
|
||||
const customize = () => {
|
||||
if (selectedDevice == 99) {
|
||||
navigate('/settings/customentities');
|
||||
} else {
|
||||
void readCoreData();
|
||||
navigate('/settings/customization', { state: selectedDevice });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -287,48 +301,50 @@ const DashboardDevices: FC = () => {
|
||||
return sc;
|
||||
};
|
||||
|
||||
const makeCsvData = (columns: any, data: any) =>
|
||||
data.reduce(
|
||||
(csvString: any, rowItem: any) =>
|
||||
csvString + columns.map(({ accessor }: any) => escapeCsvCell(accessor(rowItem))).join(';') + '\r\n',
|
||||
columns.map(({ name }: any) => escapeCsvCell(name)).join(';') + '\r\n'
|
||||
);
|
||||
|
||||
const downloadAsCsv = (columns: any, data: any, filename: string) => {
|
||||
const csvData = makeCsvData(columns, data);
|
||||
const csvFile = new Blob([csvData], { type: 'text/csv;charset:utf-8' });
|
||||
const downloadLink = document.createElement('a');
|
||||
|
||||
downloadLink.download = filename;
|
||||
downloadLink.href = window.URL.createObjectURL(csvFile);
|
||||
document.body.appendChild(downloadLink);
|
||||
downloadLink.click();
|
||||
document.body.removeChild(downloadLink);
|
||||
};
|
||||
|
||||
const hasMask = (id: string, mask: number) => (parseInt(id.slice(0, 2), 16) & mask) === mask;
|
||||
|
||||
const handleDownloadCsv = () => {
|
||||
const columns = [
|
||||
{ accessor: (dv: any) => dv.id.slice(2), name: LL.ENTITY_NAME(0) },
|
||||
{
|
||||
accessor: (dv: any) => (typeof dv.v === 'number' ? new Intl.NumberFormat().format(dv.v) : dv.v),
|
||||
name: LL.VALUE(0)
|
||||
},
|
||||
{ accessor: (dv: any) => DeviceValueUOM_s[dv.u], name: 'UoM' }
|
||||
];
|
||||
|
||||
const deviceIndex = coreData.devices.findIndex((d) => d.id === device_select.state.id);
|
||||
if (deviceIndex === -1) {
|
||||
return;
|
||||
}
|
||||
const filename = coreData.devices[deviceIndex].tn + '_' + coreData.devices[deviceIndex].n;
|
||||
|
||||
downloadAsCsv(
|
||||
columns,
|
||||
onlyFav ? deviceData.data.filter((dv) => hasMask(dv.id, DeviceEntityMask.DV_FAVORITE)) : deviceData.data,
|
||||
filename
|
||||
const columns = [
|
||||
{ accessor: (dv: DeviceValue) => dv.id.slice(2), name: LL.ENTITY_NAME(0) },
|
||||
{
|
||||
accessor: (dv: DeviceValue) => (typeof dv.v === 'number' ? new Intl.NumberFormat().format(dv.v) : dv.v),
|
||||
name: LL.VALUE(1)
|
||||
},
|
||||
{ accessor: (dv: DeviceValue) => DeviceValueUOM_s[dv.u].replace(/[^a-zA-Z0-9]/g, ''), name: 'UoM' },
|
||||
{
|
||||
accessor: (dv: DeviceValue) => (dv.c && !hasMask(dv.id, DeviceEntityMask.DV_READONLY) ? 'yes' : 'no'),
|
||||
name: LL.WRITEABLE()
|
||||
},
|
||||
{
|
||||
accessor: (dv: DeviceValue) =>
|
||||
dv.h ? dv.h : dv.l ? dv.l.join(' | ') : dv.m !== undefined && dv.x !== undefined ? dv.m + ', ' + dv.x : '',
|
||||
name: 'Range'
|
||||
}
|
||||
];
|
||||
|
||||
const data = onlyFav
|
||||
? deviceData.data.filter((dv) => hasMask(dv.id, DeviceEntityMask.DV_FAVORITE))
|
||||
: deviceData.data;
|
||||
|
||||
const csvData = data.reduce(
|
||||
(csvString: any, rowItem: any) =>
|
||||
csvString + columns.map(({ accessor }: any) => escapeCsvCell(accessor(rowItem))).join(';') + '\r\n',
|
||||
columns.map(({ name }: any) => escapeCsvCell(name)).join(';') + '\r\n'
|
||||
);
|
||||
|
||||
const csvFile = new Blob([csvData], { type: 'text/csv;charset:utf-8' });
|
||||
const downloadLink = document.createElement('a');
|
||||
downloadLink.download = filename;
|
||||
downloadLink.href = window.URL.createObjectURL(csvFile);
|
||||
document.body.appendChild(downloadLink);
|
||||
downloadLink.click();
|
||||
document.body.removeChild(downloadLink);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -340,7 +356,7 @@ const DashboardDevices: FC = () => {
|
||||
|
||||
const deviceValueDialogSave = async (devicevalue: DeviceValue) => {
|
||||
const id = Number(device_select.state.id);
|
||||
await writeDeviceValue({ id, devicevalue })
|
||||
await writeDeviceValue({ id, c: devicevalue.c, v: devicevalue.v })
|
||||
.then(() => {
|
||||
toast.success(LL.WRITE_CMD_SENT());
|
||||
})
|
||||
@@ -406,34 +422,39 @@ const DashboardDevices: FC = () => {
|
||||
const renderCoreData = () => (
|
||||
<IconContext.Provider value={{ color: 'lightblue', size: '24', style: { verticalAlign: 'middle' } }}>
|
||||
{!coreData.connected && <MessageBox my={2} level="error" message={LL.EMS_BUS_WARNING()} />}
|
||||
{coreData.connected && coreData.devices.length === 0 && (
|
||||
{/* {coreData.connected && coreData.devices.length === 0 && (
|
||||
<MessageBox my={2} level="warning" message={LL.EMS_BUS_SCANNING()} />
|
||||
)}
|
||||
)} */}
|
||||
|
||||
<Table data={{ nodes: coreData.devices }} select={device_select} theme={device_theme} layout={{ custom: true }}>
|
||||
{(tableList: any) => (
|
||||
<>
|
||||
<Header>
|
||||
<HeaderRow>
|
||||
<HeaderCell stiff />
|
||||
<HeaderCell resize>{LL.DESCRIPTION()}</HeaderCell>
|
||||
<HeaderCell stiff>{LL.TYPE(0)}</HeaderCell>
|
||||
</HeaderRow>
|
||||
</Header>
|
||||
<Body>
|
||||
{tableList.map((device: Device) => (
|
||||
<Row key={device.id} item={device}>
|
||||
<Cell stiff>
|
||||
<DeviceIcon type_id={device.t} />
|
||||
</Cell>
|
||||
<Cell>{device.n}</Cell>
|
||||
<Cell stiff>{device.tn}</Cell>
|
||||
</Row>
|
||||
))}
|
||||
</Body>
|
||||
</>
|
||||
)}
|
||||
</Table>
|
||||
{coreData.connected && (
|
||||
<Table data={{ nodes: coreData.devices }} select={device_select} theme={device_theme} layout={{ custom: true }}>
|
||||
{(tableList: any) => (
|
||||
<>
|
||||
<Header>
|
||||
<HeaderRow>
|
||||
<HeaderCell stiff />
|
||||
<HeaderCell resize>{LL.DESCRIPTION()}</HeaderCell>
|
||||
<HeaderCell stiff>{LL.TYPE(0)}</HeaderCell>
|
||||
</HeaderRow>
|
||||
</Header>
|
||||
<Body>
|
||||
{tableList.map((device: Device) => (
|
||||
<Row key={device.id} item={device}>
|
||||
<Cell stiff>
|
||||
<DeviceIcon type_id={device.t} />
|
||||
</Cell>
|
||||
<Cell>
|
||||
{device.n}
|
||||
<span style={{ color: 'lightblue' }}> ({device.e})</span>
|
||||
</Cell>
|
||||
<Cell stiff>{device.tn}</Cell>
|
||||
</Row>
|
||||
))}
|
||||
</Body>
|
||||
</>
|
||||
)}
|
||||
</Table>
|
||||
)}
|
||||
</IconContext.Provider>
|
||||
);
|
||||
|
||||
@@ -480,21 +501,31 @@ const DashboardDevices: FC = () => {
|
||||
right: 16,
|
||||
bottom: 0,
|
||||
top: 128,
|
||||
maxHeight: () => size[1] - 210,
|
||||
zIndex: 'modal'
|
||||
zIndex: 'modal',
|
||||
maxHeight: () => size[1] - 189,
|
||||
border: '1px solid #177ac9'
|
||||
}}
|
||||
>
|
||||
<Box sx={{ border: '1px solid #177ac9' }}>
|
||||
<Typography noWrap variant="subtitle1" color="warning.main" sx={{ mx: 1 }}>
|
||||
{coreData.devices[deviceIndex].n}
|
||||
<Typography noWrap variant="subtitle1" color="warning.main" sx={{ ml: 1 }}>
|
||||
{coreData.devices[deviceIndex].tn} | {coreData.devices[deviceIndex].n}
|
||||
</Typography>
|
||||
|
||||
<Grid container justifyContent="space-between">
|
||||
<Typography sx={{ ml: 1 }} variant="subtitle2" color="primary">
|
||||
{shown_data.length + ' ' + LL.ENTITIES(shown_data.length)}
|
||||
{LL.SHOWING() +
|
||||
' ' +
|
||||
shown_data.length +
|
||||
'/' +
|
||||
coreData.devices[deviceIndex].e +
|
||||
' ' +
|
||||
LL.ENTITIES(shown_data.length)}
|
||||
<IconButton onClick={() => setShowDeviceInfo(true)}>
|
||||
<InfoOutlinedIcon color="primary" sx={{ fontSize: 18, verticalAlign: 'middle' }} />
|
||||
</IconButton>
|
||||
<IconButton onClick={customize}>
|
||||
<FormatListNumberedIcon color="primary" sx={{ fontSize: 18, verticalAlign: 'middle' }} />
|
||||
</IconButton>
|
||||
<IconButton onClick={handleDownloadCsv}>
|
||||
<DownloadIcon color="primary" sx={{ fontSize: 18, verticalAlign: 'middle' }} />
|
||||
</IconButton>
|
||||
@@ -511,7 +542,7 @@ const DashboardDevices: FC = () => {
|
||||
</Typography>
|
||||
<Grid item zeroMinWidth justifyContent="flex-end">
|
||||
<IconButton onClick={resetDeviceSelect}>
|
||||
<CancelIcon color="info" sx={{ fontSize: 18, verticalAlign: 'middle' }} />
|
||||
<HighlightOffIcon color="primary" sx={{ fontSize: 18, verticalAlign: 'middle' }} />
|
||||
</IconButton>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
@@ -26,20 +26,10 @@ import type { ValidateFieldsError } from 'async-validator';
|
||||
import { dialogStyle } from 'CustomTheme';
|
||||
import { ValidatedTextField } from 'components';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import { updateValue } from 'utils';
|
||||
import { updateValue, numberValue } from 'utils';
|
||||
|
||||
import { validate } from 'validators';
|
||||
|
||||
// const dialogStyle = {
|
||||
// '& .MuiDialog-paper': {
|
||||
// borderRadius: '8px',
|
||||
// borderColor: '#565656',
|
||||
// borderStyle: 'solid',
|
||||
// borderWidth: '1px'
|
||||
// },
|
||||
// backdropFilter: 'blur(1px)'
|
||||
// };
|
||||
|
||||
type DashboardDevicesDialogProps = {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
@@ -99,32 +89,21 @@ const DashboardDevicesDialog = ({
|
||||
}
|
||||
};
|
||||
|
||||
const showHelperText = (dv: DeviceValue) => {
|
||||
if (dv.h) {
|
||||
return dv.h;
|
||||
}
|
||||
if (dv.l) {
|
||||
return '[ ' + dv.l.join(' | ') + ' ]';
|
||||
}
|
||||
|
||||
let helperText = '<';
|
||||
if (dv.s) {
|
||||
helperText += 'n';
|
||||
if (dv.m !== undefined && dv.x !== undefined) {
|
||||
helperText += ' between ' + dv.m + ' and ' + dv.x;
|
||||
} else {
|
||||
helperText += ' , step ' + dv.s;
|
||||
}
|
||||
} else {
|
||||
helperText += 'text';
|
||||
}
|
||||
return helperText + '>';
|
||||
};
|
||||
const showHelperText = (dv: DeviceValue) =>
|
||||
dv.h ? (
|
||||
dv.h
|
||||
) : dv.l ? (
|
||||
dv.l.join(' | ')
|
||||
) : dv.m !== undefined && dv.x !== undefined ? (
|
||||
<>
|
||||
{dv.m} → {dv.x}
|
||||
</>
|
||||
) : undefined;
|
||||
|
||||
return (
|
||||
<Dialog sx={dialogStyle} open={open} onClose={close}>
|
||||
<DialogTitle>
|
||||
{selectedItem.v === '' && selectedItem.c ? LL.RUN_COMMAND() : writeable ? LL.CHANGE_VALUE() : LL.VALUE(0)}
|
||||
{selectedItem.v === '' && selectedItem.c ? LL.RUN_COMMAND() : writeable ? LL.CHANGE_VALUE() : LL.VALUE(1)}
|
||||
</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
<Box color="warning.main" p={0} pl={0} pr={0} mt={0} mb={2}>
|
||||
@@ -135,7 +114,7 @@ const DashboardDevicesDialog = ({
|
||||
{editItem.l ? (
|
||||
<TextField
|
||||
name="v"
|
||||
label={LL.VALUE(0)}
|
||||
label={LL.VALUE(1)}
|
||||
value={editItem.v}
|
||||
disabled={!writeable}
|
||||
autoFocus
|
||||
@@ -153,8 +132,8 @@ const DashboardDevicesDialog = ({
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="v"
|
||||
label={LL.VALUE(0)}
|
||||
value={Math.round(editItem.v * 10) / 10}
|
||||
label={LL.VALUE(1)}
|
||||
value={numberValue(Math.round(editItem.v * 10) / 10)}
|
||||
autoFocus
|
||||
disabled={!writeable}
|
||||
type="number"
|
||||
@@ -169,7 +148,7 @@ const DashboardDevicesDialog = ({
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="v"
|
||||
label={LL.VALUE(0)}
|
||||
label={LL.VALUE(1)}
|
||||
value={editItem.v}
|
||||
disabled={!writeable}
|
||||
autoFocus
|
||||
@@ -181,7 +160,7 @@ const DashboardDevicesDialog = ({
|
||||
</Grid>
|
||||
{writeable && (
|
||||
<Grid item>
|
||||
<FormHelperText>format: {showHelperText(editItem)}</FormHelperText>
|
||||
<FormHelperText>{showHelperText(editItem)}</FormHelperText>
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
|
||||
@@ -16,7 +16,7 @@ import DashboardSensorsAnalogDialog from './DashboardSensorsAnalogDialog';
|
||||
import DashboardSensorsTemperatureDialog from './DashboardSensorsTemperatureDialog';
|
||||
import * as EMSESP from './api';
|
||||
|
||||
import { DeviceValueUOM, DeviceValueUOM_s, AnalogTypeNames } from './types';
|
||||
import { DeviceValueUOM, DeviceValueUOM_s, AnalogTypeNames, AnalogType } from './types';
|
||||
import { temperatureSensorItemValidation, analogSensorItemValidation } from './validators';
|
||||
import type { TemperatureSensor, AnalogSensor } from './types';
|
||||
import type { FC } from 'react';
|
||||
@@ -38,7 +38,8 @@ const DashboardSensors: FC = () => {
|
||||
initialData: {
|
||||
ts: [],
|
||||
as: [],
|
||||
analog_enabled: false
|
||||
analog_enabled: false,
|
||||
platform: 'ESP32'
|
||||
}
|
||||
});
|
||||
|
||||
@@ -261,7 +262,7 @@ const DashboardSensors: FC = () => {
|
||||
setSelectedAnalogSensor({
|
||||
id: Math.floor(Math.random() * (Math.floor(200) - 100) + 100),
|
||||
n: '',
|
||||
g: 40,
|
||||
g: 21, // default GPIO 21 which is safe for all platforms
|
||||
u: 0,
|
||||
v: 0,
|
||||
o: 0,
|
||||
@@ -391,7 +392,11 @@ const DashboardSensors: FC = () => {
|
||||
<Cell stiff>{a.g}</Cell>
|
||||
<Cell>{a.n}</Cell>
|
||||
<Cell stiff>{AnalogTypeNames[a.t]} </Cell>
|
||||
<Cell stiff>{a.t ? formatValue(a.v, a.u) : ''}</Cell>
|
||||
{a.t === AnalogType.DIGITAL_OUT || a.t === AnalogType.DIGITAL_IN ? (
|
||||
<Cell stiff>{a.v ? LL.ON() : LL.OFF()}</Cell>
|
||||
) : (
|
||||
<Cell stiff>{a.t ? formatValue(a.v, a.u) : ''}</Cell>
|
||||
)}
|
||||
</Row>
|
||||
))}
|
||||
</Body>
|
||||
@@ -402,18 +407,22 @@ const DashboardSensors: FC = () => {
|
||||
|
||||
return (
|
||||
<SectionContent title={LL.SENSOR_DATA()} titleGutter>
|
||||
<Typography sx={{ pt: 2, pb: 1 }} variant="h6" color="secondary">
|
||||
{LL.TEMP_SENSORS()}
|
||||
</Typography>
|
||||
<RenderTemperatureSensors />
|
||||
{selectedTemperatureSensor && (
|
||||
<DashboardSensorsTemperatureDialog
|
||||
open={temperatureDialogOpen}
|
||||
onClose={onTemperatureDialogClose}
|
||||
onSave={onTemperatureDialogSave}
|
||||
selectedItem={selectedTemperatureSensor}
|
||||
validator={temperatureSensorItemValidation()}
|
||||
/>
|
||||
{sensorData.ts.length > 0 && (
|
||||
<>
|
||||
<Typography sx={{ pt: 2, pb: 1 }} variant="h6" color="secondary">
|
||||
{LL.TEMP_SENSORS()}
|
||||
</Typography>
|
||||
<RenderTemperatureSensors />
|
||||
{selectedTemperatureSensor && (
|
||||
<DashboardSensorsTemperatureDialog
|
||||
open={temperatureDialogOpen}
|
||||
onClose={onTemperatureDialogClose}
|
||||
onSave={onTemperatureDialogSave}
|
||||
selectedItem={selectedTemperatureSensor}
|
||||
validator={temperatureSensorItemValidation()}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{sensorData?.analog_enabled === true && (
|
||||
@@ -429,7 +438,7 @@ const DashboardSensors: FC = () => {
|
||||
onSave={onAnalogDialogSave}
|
||||
creating={creating}
|
||||
selectedItem={selectedAnalogSensor}
|
||||
validator={analogSensorItemValidation(sensorData.as, creating)}
|
||||
validator={analogSensorItemValidation(sensorData.as, creating, sensorData.platform)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
@@ -442,14 +451,16 @@ const DashboardSensors: FC = () => {
|
||||
{LL.REFRESH()}
|
||||
</Button>
|
||||
</Box>
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
startIcon={<AddCircleOutlineOutlinedIcon />}
|
||||
onClick={addAnalogSensor}
|
||||
>
|
||||
{LL.ADD(0) + ' ' + LL.ANALOG_SENSOR(1)}
|
||||
</Button>
|
||||
{sensorData?.analog_enabled === true && (
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
startIcon={<AddCircleOutlineOutlinedIcon />}
|
||||
onClick={addAnalogSensor}
|
||||
>
|
||||
{LL.ADD(0) + ' ' + LL.ANALOG_SENSOR(1)}
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
</ButtonRow>
|
||||
</SectionContent>
|
||||
|
||||
@@ -89,7 +89,6 @@ const DashboardSensorsAnalogDialog = ({
|
||||
fieldErrors={fieldErrors}
|
||||
name="g"
|
||||
label="GPIO"
|
||||
disabled={!creating}
|
||||
value={numberValue(editItem.g)}
|
||||
type="number"
|
||||
variant="outlined"
|
||||
@@ -124,66 +123,66 @@ const DashboardSensorsAnalogDialog = ({
|
||||
</TextField>
|
||||
</Grid>
|
||||
{editItem.t >= AnalogType.COUNTER && editItem.t <= AnalogType.RATE && (
|
||||
<>
|
||||
<Grid item xs={4}>
|
||||
<TextField name="u" label={LL.UNIT()} value={editItem.u} fullWidth select onChange={updateFormValue}>
|
||||
{DeviceValueUOM_s.map((val, i) => (
|
||||
<MenuItem key={i} value={i}>
|
||||
{val}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
</Grid>
|
||||
{editItem.t === AnalogType.ADC && (
|
||||
<Grid item xs={4}>
|
||||
<TextField
|
||||
name="o"
|
||||
label={LL.OFFSET()}
|
||||
value={numberValue(editItem.o)}
|
||||
fullWidth
|
||||
type="number"
|
||||
variant="outlined"
|
||||
onChange={updateFormValue}
|
||||
inputProps={{ min: '0', max: '3300', step: '1' }}
|
||||
InputProps={{
|
||||
startAdornment: <InputAdornment position="start">mV</InputAdornment>
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
{editItem.t === AnalogType.COUNTER && (
|
||||
<Grid item xs={4}>
|
||||
<TextField
|
||||
name="o"
|
||||
label={LL.STARTVALUE()}
|
||||
value={numberValue(editItem.o)}
|
||||
fullWidth
|
||||
type="number"
|
||||
variant="outlined"
|
||||
onChange={updateFormValue}
|
||||
inputProps={{ step: '0.001' }}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={4}>
|
||||
<TextField
|
||||
name="f"
|
||||
label={LL.FACTOR()}
|
||||
value={numberValue(editItem.f)}
|
||||
fullWidth
|
||||
type="number"
|
||||
variant="outlined"
|
||||
onChange={updateFormValue}
|
||||
inputProps={{ step: '0.001' }}
|
||||
/>
|
||||
</Grid>
|
||||
</>
|
||||
<Grid item xs={4}>
|
||||
<TextField name="u" label={LL.UNIT()} value={editItem.u} fullWidth select onChange={updateFormValue}>
|
||||
{DeviceValueUOM_s.map((val, i) => (
|
||||
<MenuItem key={i} value={i}>
|
||||
{val}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
</Grid>
|
||||
)}
|
||||
{editItem.t === AnalogType.ADC && (
|
||||
<Grid item xs={4}>
|
||||
<TextField
|
||||
name="o"
|
||||
label={LL.OFFSET()}
|
||||
value={numberValue(editItem.o)}
|
||||
fullWidth
|
||||
type="number"
|
||||
variant="outlined"
|
||||
onChange={updateFormValue}
|
||||
inputProps={{ min: '0', max: '3300', step: '1' }}
|
||||
InputProps={{
|
||||
startAdornment: <InputAdornment position="start">mV</InputAdornment>
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
{editItem.t === AnalogType.COUNTER && (
|
||||
<Grid item xs={4}>
|
||||
<TextField
|
||||
name="o"
|
||||
label={LL.STARTVALUE()}
|
||||
value={numberValue(editItem.o)}
|
||||
fullWidth
|
||||
type="number"
|
||||
variant="outlined"
|
||||
onChange={updateFormValue}
|
||||
inputProps={{ step: '0.001' }}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
{editItem.t >= AnalogType.COUNTER && editItem.t <= AnalogType.RATE && (
|
||||
<Grid item xs={4}>
|
||||
<TextField
|
||||
name="f"
|
||||
label={LL.FACTOR()}
|
||||
value={numberValue(editItem.f)}
|
||||
fullWidth
|
||||
type="number"
|
||||
variant="outlined"
|
||||
onChange={updateFormValue}
|
||||
inputProps={{ step: '0.001' }}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
{editItem.t === AnalogType.DIGITAL_OUT && (editItem.g === 25 || editItem.g === 26) && (
|
||||
<Grid item xs={4}>
|
||||
<TextField
|
||||
name="o"
|
||||
label={LL.VALUE(0)}
|
||||
label={LL.VALUE(1)}
|
||||
value={numberValue(editItem.o)}
|
||||
fullWidth
|
||||
type="number"
|
||||
@@ -194,20 +193,55 @@ const DashboardSensorsAnalogDialog = ({
|
||||
</Grid>
|
||||
)}
|
||||
{editItem.t === AnalogType.DIGITAL_OUT && editItem.g !== 25 && editItem.g !== 26 && (
|
||||
<Grid item xs={4}>
|
||||
<TextField
|
||||
name="o"
|
||||
label={LL.VALUE(0)}
|
||||
value={numberValue(editItem.o)}
|
||||
fullWidth
|
||||
type="number"
|
||||
variant="outlined"
|
||||
onChange={updateFormValue}
|
||||
inputProps={{ min: '0', max: '1', step: '1' }}
|
||||
/>
|
||||
</Grid>
|
||||
<>
|
||||
<Grid item xs={4}>
|
||||
<TextField
|
||||
name="o"
|
||||
label={LL.VALUE(1)}
|
||||
value={numberValue(editItem.o)}
|
||||
fullWidth
|
||||
select
|
||||
variant="outlined"
|
||||
onChange={updateFormValue}
|
||||
>
|
||||
<MenuItem value={0}>{LL.OFF()}</MenuItem>
|
||||
<MenuItem value={1}>{LL.ON()}</MenuItem>
|
||||
</TextField>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<TextField
|
||||
name="f"
|
||||
label={LL.POLARITY()}
|
||||
value={editItem.f}
|
||||
fullWidth
|
||||
select
|
||||
onChange={updateFormValue}
|
||||
>
|
||||
<MenuItem value={1}>{LL.ACTIVEHIGH()}</MenuItem>
|
||||
<MenuItem value={-1}>{LL.ACTIVELOW()}</MenuItem>
|
||||
</TextField>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<TextField
|
||||
name="u"
|
||||
label={LL.STARTVALUE()}
|
||||
value={editItem.u}
|
||||
fullWidth
|
||||
select
|
||||
onChange={updateFormValue}
|
||||
>
|
||||
<MenuItem value={0}>{LL.UNCHANGED()}</MenuItem>
|
||||
<MenuItem value={1}>
|
||||
{LL.ALWAYS()} {LL.OFF()}
|
||||
</MenuItem>
|
||||
<MenuItem value={2}>
|
||||
{LL.ALWAYS()} {LL.ON()}
|
||||
</MenuItem>
|
||||
</TextField>
|
||||
</Grid>
|
||||
</>
|
||||
)}
|
||||
{editItem.t >= AnalogType.PWM_0 && (
|
||||
{(editItem.t === AnalogType.PWM_0 || editItem.t === AnalogType.PWM_1 || editItem.t === AnalogType.PWM_2) && (
|
||||
<>
|
||||
<Grid item xs={4}>
|
||||
<TextField
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { AiOutlineControl, AiOutlineGateway, AiOutlineAlert, AiOutlineChrome } from 'react-icons/ai';
|
||||
import { AiOutlineControl, AiOutlineGateway, AiOutlineAlert } from 'react-icons/ai';
|
||||
import { CgSmartHomeBoiler } from 'react-icons/cg';
|
||||
|
||||
import { FaSolarPanel } from 'react-icons/fa';
|
||||
import { GiHeatHaze } from 'react-icons/gi';
|
||||
import { MdThermostatAuto, MdOutlineSensors, MdOutlineExtension } from 'react-icons/md';
|
||||
import { MdThermostatAuto, MdOutlineSensors, MdOutlineExtension, MdOutlineDevices } from 'react-icons/md';
|
||||
import { TiFlowSwitch } from 'react-icons/ti';
|
||||
import { VscVmConnect } from 'react-icons/vsc';
|
||||
import { DeviceType } from './types';
|
||||
@@ -38,8 +38,8 @@ const DeviceIcon: FC<DeviceIconProps> = ({ type_id }) => {
|
||||
return <VscVmConnect />;
|
||||
case DeviceType.ALERT:
|
||||
return <AiOutlineAlert />;
|
||||
case DeviceType.PUMP:
|
||||
return <AiOutlineChrome />;
|
||||
case DeviceType.EXTENSION:
|
||||
return <MdOutlineDevices />;
|
||||
case DeviceType.CUSTOM:
|
||||
return <MdOutlineExtension />;
|
||||
default:
|
||||
|
||||
@@ -1,28 +1,121 @@
|
||||
import { Tab } from '@mui/material';
|
||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||
import HelpInformation from './HelpInformation';
|
||||
import CommentIcon from '@mui/icons-material/CommentTwoTone';
|
||||
import EastIcon from '@mui/icons-material/East';
|
||||
import DownloadIcon from '@mui/icons-material/GetApp';
|
||||
import GitHubIcon from '@mui/icons-material/GitHub';
|
||||
import MenuBookIcon from '@mui/icons-material/MenuBookTwoTone';
|
||||
import { Box, List, ListItem, ListItemAvatar, ListItemText, Link, Typography, Button } from '@mui/material';
|
||||
import { useRequest } from 'alova';
|
||||
import { toast } from 'react-toastify';
|
||||
import type { FC } from 'react';
|
||||
|
||||
import { RouterTabs, useRouterTab, useLayoutTitle } from 'components';
|
||||
|
||||
import { SectionContent, useLayoutTitle } from 'components';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import * as EMSESP from 'project/api';
|
||||
|
||||
const Help: FC = () => {
|
||||
const { LL } = useI18nContext();
|
||||
const { routerTab } = useRouterTab();
|
||||
|
||||
useLayoutTitle(LL.HELP_OF(''));
|
||||
|
||||
const { send: getAPI, onSuccess: onGetAPI } = useRequest((data) => EMSESP.API(data), {
|
||||
immediate: false
|
||||
});
|
||||
|
||||
onGetAPI((event) => {
|
||||
const anchor = document.createElement('a');
|
||||
anchor.href = URL.createObjectURL(
|
||||
new Blob([JSON.stringify(event.data, null, 2)], {
|
||||
type: 'text/plain'
|
||||
})
|
||||
);
|
||||
anchor.download = 'emsesp_' + event.sendArgs[0].device + '_' + event.sendArgs[0].entity + '.txt';
|
||||
anchor.click();
|
||||
URL.revokeObjectURL(anchor.href);
|
||||
toast.info(LL.DOWNLOAD_SUCCESSFUL());
|
||||
});
|
||||
|
||||
const callAPI = async (device: string, entity: string) => {
|
||||
await getAPI({ device, entity, id: 0 }).catch((error) => {
|
||||
toast.error(error.message);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<RouterTabs value={routerTab}>
|
||||
<Tab value="information" label={LL.HELP_OF('EMS-ESP')} />
|
||||
</RouterTabs>
|
||||
<Routes>
|
||||
<Route path="information" element={<HelpInformation />} />
|
||||
<Route path="/*" element={<Navigate replace to="information" />} />
|
||||
</Routes>
|
||||
</>
|
||||
<SectionContent title={LL.SUPPORT_INFORMATION(0)} titleGutter>
|
||||
<List>
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
<MenuBookIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} />
|
||||
</ListItemAvatar>
|
||||
<ListItemText>
|
||||
{LL.HELP_INFORMATION_1()}
|
||||
<EastIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} />
|
||||
|
||||
<Link target="_blank" href="https://emsesp.github.io/docs" color="primary">
|
||||
{LL.CLICK_HERE()}
|
||||
</Link>
|
||||
</ListItemText>
|
||||
</ListItem>
|
||||
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
<CommentIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} />
|
||||
</ListItemAvatar>
|
||||
<ListItemText>
|
||||
{LL.HELP_INFORMATION_2()}
|
||||
<EastIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} />
|
||||
|
||||
<Link target="_blank" href="https://discord.gg/3J3GgnzpyT" color="primary">
|
||||
{LL.CLICK_HERE()}
|
||||
</Link>
|
||||
</ListItemText>
|
||||
</ListItem>
|
||||
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
<GitHubIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} />
|
||||
</ListItemAvatar>
|
||||
<ListItemText>
|
||||
{LL.HELP_INFORMATION_3()}
|
||||
<EastIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} />
|
||||
<Link target="_blank" href="https://github.com/emsesp/EMS-ESP32/issues/new/choose" color="primary">
|
||||
{LL.CLICK_HERE()}
|
||||
</Link>
|
||||
<br />
|
||||
</ListItemText>
|
||||
</ListItem>
|
||||
</List>
|
||||
|
||||
<Box color="warning.main">
|
||||
<Typography mb={1} variant="body2">
|
||||
{LL.HELP_INFORMATION_4()}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Button startIcon={<DownloadIcon />} variant="outlined" color="primary" onClick={() => callAPI('system', 'info')}>
|
||||
{LL.SUPPORT_INFORMATION(0)}
|
||||
</Button>
|
||||
<Button
|
||||
sx={{ ml: 2 }}
|
||||
startIcon={<DownloadIcon />}
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
onClick={() => callAPI('system', 'allvalues')}
|
||||
>
|
||||
All Values
|
||||
</Button>
|
||||
|
||||
<Box border={1} p={1} mt={4} color="orange">
|
||||
<Typography align="center" variant="subtitle1" color="orange">
|
||||
<b>{LL.HELP_INFORMATION_5()}</b>
|
||||
</Typography>
|
||||
<Typography align="center">
|
||||
<Link target="_blank" href="https://github.com/emsesp/EMS-ESP32" color="primary">
|
||||
{'github.com/emsesp/EMS-ESP32'}
|
||||
</Link>
|
||||
</Typography>
|
||||
<Typography color="white" variant="subtitle2" align="center">
|
||||
@proddy @MichaelDvP
|
||||
</Typography>
|
||||
</Box>
|
||||
</SectionContent>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
import CommentIcon from '@mui/icons-material/CommentTwoTone';
|
||||
import EastIcon from '@mui/icons-material/East';
|
||||
import DownloadIcon from '@mui/icons-material/GetApp';
|
||||
import GitHubIcon from '@mui/icons-material/GitHub';
|
||||
import MenuBookIcon from '@mui/icons-material/MenuBookTwoTone';
|
||||
import { Typography, Button, Box, List, ListItem, ListItemText, Link, ListItemAvatar } from '@mui/material';
|
||||
import { useRequest } from 'alova';
|
||||
import { toast } from 'react-toastify';
|
||||
import * as EMSESP from './api';
|
||||
import type { FC } from 'react';
|
||||
|
||||
import { SectionContent } from 'components';
|
||||
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
const HelpInformation: FC = () => {
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const { send: API, onSuccess: onSuccessAPI } = useRequest((data) => EMSESP.API(data), {
|
||||
immediate: false
|
||||
});
|
||||
|
||||
onSuccessAPI((event) => {
|
||||
const a = document.createElement('a');
|
||||
const filename = 'emsesp_info.txt';
|
||||
a.href = URL.createObjectURL(
|
||||
new Blob([JSON.stringify(event.data, null, 2)], {
|
||||
type: 'text/plain'
|
||||
})
|
||||
);
|
||||
a.setAttribute('download', filename);
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
toast.info(LL.DOWNLOAD_SUCCESSFUL());
|
||||
});
|
||||
|
||||
const callAPI = async () => {
|
||||
await API({ device: 'system', entity: 'info', id: 0 }).catch((error) => {
|
||||
toast.error(error.message);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<SectionContent title={LL.SUPPORT_INFORMATION()} titleGutter>
|
||||
<List>
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
<MenuBookIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} />
|
||||
</ListItemAvatar>
|
||||
<ListItemText>
|
||||
{LL.HELP_INFORMATION_1()}
|
||||
<EastIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} />
|
||||
|
||||
<Link target="_blank" href="https://emsesp.github.io/docs" color="primary">
|
||||
{LL.CLICK_HERE()}
|
||||
</Link>
|
||||
</ListItemText>
|
||||
</ListItem>
|
||||
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
<CommentIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} />
|
||||
</ListItemAvatar>
|
||||
<ListItemText>
|
||||
{LL.HELP_INFORMATION_2()}
|
||||
<EastIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} />
|
||||
|
||||
<Link target="_blank" href="https://discord.gg/3J3GgnzpyT" color="primary">
|
||||
{LL.CLICK_HERE()}
|
||||
</Link>
|
||||
</ListItemText>
|
||||
</ListItem>
|
||||
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
<GitHubIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} />
|
||||
</ListItemAvatar>
|
||||
<ListItemText>
|
||||
{LL.HELP_INFORMATION_3()}
|
||||
<EastIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} />
|
||||
<Link target="_blank" href="https://github.com/emsesp/EMS-ESP32/issues/new/choose" color="primary">
|
||||
{LL.CLICK_HERE()}
|
||||
</Link>
|
||||
<br />
|
||||
<i>({LL.HELP_INFORMATION_4()}</i>
|
||||
<Button
|
||||
startIcon={<DownloadIcon />}
|
||||
size="small"
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
onClick={() => callAPI()}
|
||||
>
|
||||
{LL.SUPPORT_INFO()}
|
||||
</Button>
|
||||
)
|
||||
</ListItemText>
|
||||
</ListItem>
|
||||
</List>
|
||||
|
||||
<Box border={1} p={1} mt={4} color="orange">
|
||||
<Typography align="center" variant="subtitle1" color="orange">
|
||||
<b>{LL.HELP_INFORMATION_5()}</b>
|
||||
</Typography>
|
||||
<Typography align="center">
|
||||
<Link target="_blank" href="https://github.com/emsesp/EMS-ESP32" color="primary">
|
||||
{'github.com/emsesp/EMS-ESP32'}
|
||||
</Link>
|
||||
</Typography>
|
||||
<Typography color="white" align="center">
|
||||
@proddy @MichaelDvP
|
||||
</Typography>
|
||||
</Box>
|
||||
</SectionContent>
|
||||
);
|
||||
};
|
||||
|
||||
export default HelpInformation;
|
||||
@@ -2,8 +2,8 @@ import { Tab } from '@mui/material';
|
||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||
|
||||
import SettingsApplication from './SettingsApplication';
|
||||
import SettingsCustomEntities from './SettingsCustomEntities';
|
||||
import SettingsCustomization from './SettingsCustomization';
|
||||
import SettingsEntities from './SettingsEntities';
|
||||
import SettingsScheduler from './SettingsScheduler';
|
||||
import type { FC } from 'react';
|
||||
import { RouterTabs, useRouterTab, useLayoutTitle } from 'components';
|
||||
@@ -18,17 +18,17 @@ const Settings: FC = () => {
|
||||
return (
|
||||
<>
|
||||
<RouterTabs value={routerTab}>
|
||||
<Tab value="application" label={LL.APPLICATION_SETTINGS()} />
|
||||
<Tab value="customization" label={LL.CUSTOMIZATIONS()} />
|
||||
<Tab value="scheduler" label={LL.SCHEDULER()} />
|
||||
<Tab value="customentities" label={LL.CUSTOM_ENTITIES(0)} />
|
||||
<Tab value="/settings/application" label={LL.APPLICATION_SETTINGS()} />
|
||||
<Tab value="/settings/customization" label={LL.CUSTOMIZATIONS()} />
|
||||
<Tab value="/settings/scheduler" label={LL.SCHEDULER()} />
|
||||
<Tab value="/settings/customentities" label={LL.CUSTOM_ENTITIES(0)} />
|
||||
</RouterTabs>
|
||||
<Routes>
|
||||
<Route path="application" element={<SettingsApplication />} />
|
||||
<Route path="customization" element={<SettingsCustomization />} />
|
||||
<Route path="scheduler" element={<SettingsScheduler />} />
|
||||
<Route path="customentities" element={<SettingsEntities />} />
|
||||
<Route path="/*" element={<Navigate replace to="application" />} />
|
||||
<Route path="customentities" element={<SettingsCustomEntities />} />
|
||||
<Route path="*" element={<Navigate replace to="/settings/application" />} />
|
||||
</Routes>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -384,6 +384,7 @@ const SettingsApplication: FC = () => {
|
||||
<MenuItem value="nl">Nederlands (NL)</MenuItem>
|
||||
<MenuItem value="no">Norsk (NO)</MenuItem>
|
||||
<MenuItem value="pl">Polski (PL)</MenuItem>
|
||||
<MenuItem value="sk">Slovenčina (SK)</MenuItem>
|
||||
<MenuItem value="sv">Svenska (SV)</MenuItem>
|
||||
<MenuItem value="tr">Türk (TR)</MenuItem>
|
||||
</TextField>
|
||||
@@ -425,6 +426,11 @@ const SettingsApplication: FC = () => {
|
||||
label={LL.UNDERCLOCK_CPU()}
|
||||
disabled={saving}
|
||||
/>
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox checked={data.boiler_heatingoff} onChange={updateFormValue} name="boiler_heatingoff" />}
|
||||
label={LL.HEATINGOFF()}
|
||||
disabled={saving}
|
||||
/>
|
||||
<Grid container spacing={0} direction="row" justifyContent="flex-start" alignItems="flex-start">
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox checked={data.shower_timer} onChange={updateFormValue} name="shower_timer" />}
|
||||
@@ -639,7 +645,7 @@ const SettingsApplication: FC = () => {
|
||||
</Grid>
|
||||
)}
|
||||
{restartNeeded && (
|
||||
<MessageBox my={2} level="warning" message={LL.RESTART_TEXT()}>
|
||||
<MessageBox my={2} level="warning" message={LL.RESTART_TEXT(0)}>
|
||||
<Button startIcon={<PowerSettingsNewIcon />} variant="contained" color="error" onClick={restart}>
|
||||
{LL.RESTART()}
|
||||
</Button>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import EditOutlinedIcon from '@mui/icons-material/EditOutlined';
|
||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||
import WarningIcon from '@mui/icons-material/Warning';
|
||||
import { Button, Typography, Box } from '@mui/material';
|
||||
import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table';
|
||||
@@ -7,11 +9,11 @@ import { useTheme } from '@table-library/react-table-library/theme';
|
||||
// eslint-disable-next-line import/named
|
||||
import { updateState, useRequest } from 'alova';
|
||||
import { useState, useCallback } from 'react';
|
||||
import { unstable_useBlocker as useBlocker } from 'react-router-dom';
|
||||
import { useBlocker } from 'react-router-dom';
|
||||
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import SettingsEntitiesDialog from './SettingsEntitiesDialog';
|
||||
import SettingsCustomEntitiesDialog from './SettingsCustomEntitiesDialog';
|
||||
import * as EMSESP from './api';
|
||||
import { DeviceValueTypeNames, DeviceValueUOM_s } from './types';
|
||||
import { entityItemValidation } from './validators';
|
||||
@@ -21,7 +23,7 @@ import { ButtonRow, FormLoader, SectionContent, BlockNavigation } from 'componen
|
||||
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
const SettingsEntities: FC = () => {
|
||||
const SettingsCustomEntities: FC = () => {
|
||||
const { LL } = useI18nContext();
|
||||
const [numChanges, setNumChanges] = useState<number>(0);
|
||||
const blocker = useBlocker(numChanges !== 0);
|
||||
@@ -33,16 +35,17 @@ const SettingsEntities: FC = () => {
|
||||
data: entities,
|
||||
send: fetchEntities,
|
||||
error
|
||||
} = useRequest(EMSESP.readEntities, {
|
||||
} = useRequest(EMSESP.readCustomEntities, {
|
||||
initialData: [],
|
||||
force: true
|
||||
});
|
||||
|
||||
const { send: writeEntities } = useRequest((data) => EMSESP.writeEntities(data), { immediate: false });
|
||||
const { send: writeEntities } = useRequest((data) => EMSESP.writeCustomEntities(data), { immediate: false });
|
||||
|
||||
function hasEntityChanged(ei: EntityItem) {
|
||||
return (
|
||||
ei.id !== ei.o_id ||
|
||||
ei.ram !== ei.o_ram ||
|
||||
(ei?.name || '') !== (ei?.o_name || '') ||
|
||||
ei.device_id !== ei.o_device_id ||
|
||||
ei.type_id !== ei.o_type_id ||
|
||||
@@ -51,7 +54,8 @@ const SettingsEntities: FC = () => {
|
||||
ei.factor !== ei.o_factor ||
|
||||
ei.value_type !== ei.o_value_type ||
|
||||
ei.writeable !== ei.o_writeable ||
|
||||
ei.deleted !== ei.o_deleted
|
||||
ei.deleted !== ei.o_deleted ||
|
||||
(ei.value || '') !== (ei.o_value || '')
|
||||
);
|
||||
}
|
||||
|
||||
@@ -118,6 +122,7 @@ const SettingsEntities: FC = () => {
|
||||
.filter((ei) => !ei.deleted)
|
||||
.map((condensed_ei) => ({
|
||||
id: condensed_ei.id,
|
||||
ram: condensed_ei.ram,
|
||||
name: condensed_ei.name,
|
||||
device_id: condensed_ei.device_id,
|
||||
type_id: condensed_ei.type_id,
|
||||
@@ -125,7 +130,8 @@ const SettingsEntities: FC = () => {
|
||||
factor: condensed_ei.factor,
|
||||
uom: condensed_ei.uom,
|
||||
writeable: condensed_ei.writeable,
|
||||
value_type: condensed_ei.value_type
|
||||
value_type: condensed_ei.value_type,
|
||||
value: condensed_ei.value
|
||||
}))
|
||||
})
|
||||
.then(() => {
|
||||
@@ -173,14 +179,16 @@ const SettingsEntities: FC = () => {
|
||||
setSelectedEntityItem({
|
||||
id: Math.floor(Math.random() * (Math.floor(200) - 100) + 100),
|
||||
name: '',
|
||||
device_id: '',
|
||||
type_id: '',
|
||||
ram: 0,
|
||||
device_id: '0',
|
||||
type_id: '0',
|
||||
offset: 0,
|
||||
factor: 1,
|
||||
uom: 0,
|
||||
value_type: 0,
|
||||
writeable: false,
|
||||
deleted: false
|
||||
deleted: false,
|
||||
value: ''
|
||||
});
|
||||
setDialogOpen(true);
|
||||
};
|
||||
@@ -189,8 +197,8 @@ const SettingsEntities: FC = () => {
|
||||
return value === undefined || uom === undefined
|
||||
? ''
|
||||
: typeof value === 'number'
|
||||
? new Intl.NumberFormat().format(value) + (uom === 0 ? '' : ' ' + DeviceValueUOM_s[uom])
|
||||
: value;
|
||||
? new Intl.NumberFormat().format(value) + (uom === 0 ? '' : ' ' + DeviceValueUOM_s[uom])
|
||||
: value;
|
||||
}
|
||||
|
||||
function showHex(value: number, digit: number) {
|
||||
@@ -212,18 +220,21 @@ const SettingsEntities: FC = () => {
|
||||
<HeaderCell stiff>{LL.ID_OF(LL.DEVICE())}</HeaderCell>
|
||||
<HeaderCell stiff>{LL.ID_OF(LL.TYPE(1))}</HeaderCell>
|
||||
<HeaderCell stiff>{LL.OFFSET()}</HeaderCell>
|
||||
<HeaderCell stiff>{LL.VALUE(1) + ' ' + LL.TYPE(1)}</HeaderCell>
|
||||
<HeaderCell stiff>{LL.TYPE(1)}</HeaderCell>
|
||||
<HeaderCell stiff>{LL.VALUE(1)}</HeaderCell>
|
||||
</HeaderRow>
|
||||
</Header>
|
||||
<Body>
|
||||
{tableList.map((ei: EntityItem) => (
|
||||
<Row key={ei.name} item={ei} onClick={() => editEntityItem(ei)}>
|
||||
<Cell>{ei.name}</Cell>
|
||||
<Cell>{showHex(ei.device_id as number, 2)}</Cell>
|
||||
<Cell>{showHex(ei.type_id as number, 3)}</Cell>
|
||||
<Cell>{ei.offset}</Cell>
|
||||
<Cell>{DeviceValueTypeNames[ei.value_type]}</Cell>
|
||||
<Cell>
|
||||
{ei.name}
|
||||
{ei.writeable && <EditOutlinedIcon color="primary" sx={{ fontSize: 12 }} />}
|
||||
</Cell>
|
||||
<Cell>{ei.ram === 1 ? '' : showHex(ei.device_id as number, 2)}</Cell>
|
||||
<Cell>{ei.ram === 1 ? '' : showHex(ei.type_id as number, 3)}</Cell>
|
||||
<Cell>{ei.ram === 1 ? '' : ei.offset}</Cell>
|
||||
<Cell>{ei.ram === 1 ? 'RAM' : DeviceValueTypeNames[ei.value_type]}</Cell>
|
||||
<Cell>{formatValue(ei.value, ei.uom)}</Cell>
|
||||
</Row>
|
||||
))}
|
||||
@@ -244,7 +255,7 @@ const SettingsEntities: FC = () => {
|
||||
{renderEntity()}
|
||||
|
||||
{selectedEntityItem && (
|
||||
<SettingsEntitiesDialog
|
||||
<SettingsCustomEntitiesDialog
|
||||
open={dialogOpen}
|
||||
creating={creating}
|
||||
onClose={onDialogClose}
|
||||
@@ -274,7 +285,10 @@ const SettingsEntities: FC = () => {
|
||||
</Box>
|
||||
<Box flexWrap="nowrap" whiteSpace="nowrap">
|
||||
<ButtonRow>
|
||||
<Button startIcon={<AddIcon />} variant="outlined" color="secondary" onClick={addEntityItem}>
|
||||
<Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={fetchEntities}>
|
||||
{LL.REFRESH()}
|
||||
</Button>
|
||||
<Button startIcon={<AddIcon />} variant="outlined" color="primary" onClick={addEntityItem}>
|
||||
{LL.ADD(0)}
|
||||
</Button>
|
||||
</ButtonRow>
|
||||
@@ -284,4 +298,4 @@ const SettingsEntities: FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default SettingsEntities;
|
||||
export default SettingsCustomEntities;
|
||||
284
interface/src/project/SettingsCustomEntitiesDialog.tsx
Normal file
284
interface/src/project/SettingsCustomEntitiesDialog.tsx
Normal file
@@ -0,0 +1,284 @@
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import DoneIcon from '@mui/icons-material/Done';
|
||||
import RemoveIcon from '@mui/icons-material/RemoveCircleOutline';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Checkbox,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Grid,
|
||||
InputAdornment,
|
||||
MenuItem,
|
||||
TextField
|
||||
} from '@mui/material';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { DeviceValueUOM_s, DeviceValueType } from './types';
|
||||
import type { EntityItem } from './types';
|
||||
import type Schema from 'async-validator';
|
||||
import type { ValidateFieldsError } from 'async-validator';
|
||||
|
||||
import { dialogStyle } from 'CustomTheme';
|
||||
import { BlockFormControlLabel, ValidatedTextField } from 'components';
|
||||
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
import { numberValue, updateValue } from 'utils';
|
||||
import { validate } from 'validators';
|
||||
|
||||
type SettingsCustomEntitiesDialogProps = {
|
||||
open: boolean;
|
||||
creating: boolean;
|
||||
onClose: () => void;
|
||||
onSave: (ei: EntityItem) => void;
|
||||
selectedItem: EntityItem;
|
||||
validator: Schema;
|
||||
};
|
||||
|
||||
const SettingsCustomEntitiesDialog = ({
|
||||
open,
|
||||
creating,
|
||||
onClose,
|
||||
onSave,
|
||||
selectedItem,
|
||||
validator
|
||||
}: SettingsCustomEntitiesDialogProps) => {
|
||||
const { LL } = useI18nContext();
|
||||
const [editItem, setEditItem] = useState<EntityItem>(selectedItem);
|
||||
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
||||
const updateFormValue = updateValue(setEditItem);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
setFieldErrors(undefined);
|
||||
setEditItem(selectedItem);
|
||||
// convert to hex strings straight away
|
||||
setEditItem({
|
||||
...selectedItem,
|
||||
device_id: selectedItem.device_id.toString(16).toUpperCase(),
|
||||
type_id: selectedItem.type_id.toString(16).toUpperCase()
|
||||
});
|
||||
}
|
||||
}, [open, selectedItem]);
|
||||
|
||||
const close = () => {
|
||||
onClose();
|
||||
};
|
||||
|
||||
const save = async () => {
|
||||
try {
|
||||
setFieldErrors(undefined);
|
||||
await validate(validator, editItem);
|
||||
if (typeof editItem.device_id === 'string') {
|
||||
editItem.device_id = parseInt(editItem.device_id, 16);
|
||||
}
|
||||
if (typeof editItem.type_id === 'string') {
|
||||
editItem.type_id = parseInt(editItem.type_id, 16);
|
||||
}
|
||||
onSave(editItem);
|
||||
} catch (errors: any) {
|
||||
setFieldErrors(errors);
|
||||
}
|
||||
};
|
||||
|
||||
const remove = () => {
|
||||
editItem.deleted = true;
|
||||
onSave(editItem);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog sx={dialogStyle} open={open} onClose={close}>
|
||||
<DialogTitle>
|
||||
{creating ? LL.ADD(1) + ' ' + LL.NEW(1) : LL.EDIT()} {LL.ENTITY()}
|
||||
</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
<Box display="flex" flexWrap="wrap" mb={1}>
|
||||
<Box flexWrap="nowrap" whiteSpace="nowrap" />
|
||||
</Box>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={4}>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="name"
|
||||
label={LL.NAME(0)}
|
||||
value={editItem.name}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
onChange={updateFormValue}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<TextField
|
||||
name="ram"
|
||||
label={LL.VALUE(1) + ' ' + LL.TYPE(1)}
|
||||
value={editItem.ram}
|
||||
variant="outlined"
|
||||
onChange={updateFormValue}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
select
|
||||
>
|
||||
<MenuItem value={0}>EMS-{LL.VALUE(1)}</MenuItem>
|
||||
<MenuItem value={1}>RAM-{LL.VALUE(1)}</MenuItem>
|
||||
</TextField>
|
||||
</Grid>
|
||||
{editItem.ram === 1 && (
|
||||
<Grid item xs={4}>
|
||||
<TextField
|
||||
name="value"
|
||||
label={LL.DEFAULT(0) + ' ' + LL.VALUE(1)}
|
||||
value={editItem.value}
|
||||
variant="outlined"
|
||||
onChange={updateFormValue}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
{editItem.ram === 0 && (
|
||||
<>
|
||||
<Grid item xs={4} mt={3}>
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox checked={editItem.writeable} onChange={updateFormValue} name="writeable" />}
|
||||
label={LL.WRITEABLE()}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="device_id"
|
||||
label={LL.ID_OF(LL.DEVICE())}
|
||||
margin="normal"
|
||||
type="string"
|
||||
fullWidth
|
||||
value={editItem.device_id as string}
|
||||
onChange={updateFormValue}
|
||||
inputProps={{ style: { textTransform: 'uppercase' } }}
|
||||
InputProps={{ startAdornment: <InputAdornment position="start">0x</InputAdornment> }}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="type_id"
|
||||
label={LL.ID_OF(LL.TYPE(1))}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
value={editItem.type_id}
|
||||
onChange={updateFormValue}
|
||||
inputProps={{ style: { textTransform: 'uppercase' } }}
|
||||
InputProps={{ startAdornment: <InputAdornment position="start">0x</InputAdornment> }}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="offset"
|
||||
label={LL.OFFSET()}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
type="number"
|
||||
value={editItem.offset}
|
||||
onChange={updateFormValue}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<TextField
|
||||
name="value_type"
|
||||
label={LL.VALUE(1) + ' ' + LL.TYPE(1)}
|
||||
value={editItem.value_type}
|
||||
variant="outlined"
|
||||
onChange={updateFormValue}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
select
|
||||
>
|
||||
<MenuItem value={DeviceValueType.BOOL}>BOOL</MenuItem>
|
||||
<MenuItem value={DeviceValueType.INT}>INT</MenuItem>
|
||||
<MenuItem value={DeviceValueType.UINT}>UINT</MenuItem>
|
||||
<MenuItem value={DeviceValueType.SHORT}>SHORT</MenuItem>
|
||||
<MenuItem value={DeviceValueType.USHORT}>USHORT</MenuItem>
|
||||
<MenuItem value={DeviceValueType.ULONG}>ULONG</MenuItem>
|
||||
<MenuItem value={DeviceValueType.TIME}>TIME</MenuItem>
|
||||
<MenuItem value={DeviceValueType.STRING}>RAW</MenuItem>
|
||||
</TextField>
|
||||
</Grid>
|
||||
|
||||
{editItem.value_type !== DeviceValueType.BOOL && editItem.value_type !== DeviceValueType.STRING && (
|
||||
<>
|
||||
<Grid item xs={4}>
|
||||
<TextField
|
||||
name="factor"
|
||||
label={LL.FACTOR()}
|
||||
value={numberValue(editItem.factor)}
|
||||
variant="outlined"
|
||||
onChange={updateFormValue}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
type="number"
|
||||
inputProps={{ step: '0.001' }}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<TextField
|
||||
name="uom"
|
||||
label={LL.UNIT()}
|
||||
value={editItem.uom}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
onChange={updateFormValue}
|
||||
select
|
||||
>
|
||||
{DeviceValueUOM_s.map((val, i) => (
|
||||
<MenuItem key={i} value={i}>
|
||||
{val}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
</Grid>
|
||||
</>
|
||||
)}
|
||||
{editItem.value_type === DeviceValueType.STRING && editItem.device_id !== '0' && (
|
||||
<Grid item xs={4}>
|
||||
<TextField
|
||||
name="factor"
|
||||
label="Bytes"
|
||||
value={editItem.factor}
|
||||
variant="outlined"
|
||||
onChange={updateFormValue}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
type="number"
|
||||
inputProps={{ min: '1', max: '27', step: '1' }}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Grid>
|
||||
</DialogContent>
|
||||
|
||||
<DialogActions>
|
||||
{!creating && (
|
||||
<Box flexGrow={1}>
|
||||
<Button startIcon={<RemoveIcon />} variant="outlined" color="warning" onClick={remove}>
|
||||
{LL.REMOVE()}
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
<Button startIcon={<CancelIcon />} variant="outlined" onClick={close} color="secondary">
|
||||
{LL.CANCEL()}
|
||||
</Button>
|
||||
<Button startIcon={creating ? <AddIcon /> : <DoneIcon />} variant="outlined" onClick={save} color="primary">
|
||||
{creating ? LL.ADD(0) : LL.UPDATE()}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default SettingsCustomEntitiesDialog;
|
||||
@@ -23,7 +23,7 @@ import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-li
|
||||
import { useTheme } from '@table-library/react-table-library/theme';
|
||||
import { useRequest } from 'alova';
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { unstable_useBlocker as useBlocker } from 'react-router-dom';
|
||||
import { useBlocker, useLocation } from 'react-router-dom';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import EntityMaskToggle from './EntityMaskToggle';
|
||||
@@ -52,20 +52,26 @@ const SettingsCustomization: FC = () => {
|
||||
const [restarting, setRestarting] = useState<boolean>(false);
|
||||
const [restartNeeded, setRestartNeeded] = useState<boolean>(false);
|
||||
const [deviceEntities, setDeviceEntities] = useState<DeviceEntity[]>([]);
|
||||
const [selectedDevice, setSelectedDevice] = useState<number>(-1);
|
||||
const [confirmReset, setConfirmReset] = useState<boolean>(false);
|
||||
const [selectedFilters, setSelectedFilters] = useState<number>(0);
|
||||
const [search, setSearch] = useState('');
|
||||
const [selectedDeviceEntity, setSelectedDeviceEntity] = useState<DeviceEntity>();
|
||||
const [dialogOpen, setDialogOpen] = useState<boolean>(false);
|
||||
|
||||
// fetch devices first
|
||||
const { data: devices } = useRequest(EMSESP.readDevices);
|
||||
|
||||
// const { state } = useLocation();
|
||||
const [selectedDevice, setSelectedDevice] = useState<number>(useLocation().state || -1);
|
||||
const [selectedDeviceName, setSelectedDeviceName] = useState<string>('');
|
||||
|
||||
const { send: resetCustomizations } = useRequest(EMSESP.resetCustomizations(), {
|
||||
immediate: false
|
||||
});
|
||||
|
||||
const { data: devices } = useRequest(EMSESP.readDevices);
|
||||
|
||||
const { send: writeCustomEntities } = useRequest((data) => EMSESP.writeCustomEntities(data), { immediate: false });
|
||||
const { send: writeCustomizationEntities } = useRequest((data) => EMSESP.writeCustomizationEntities(data), {
|
||||
immediate: false
|
||||
});
|
||||
|
||||
const { send: readDeviceEntities, onSuccess: onSuccess } = useRequest((data) => EMSESP.readDeviceEntities(data), {
|
||||
initialData: [],
|
||||
@@ -174,6 +180,22 @@ const SettingsCustomization: FC = () => {
|
||||
}
|
||||
}, [deviceEntities]);
|
||||
|
||||
useEffect(() => {
|
||||
if (devices && selectedDevice !== -1) {
|
||||
void readDeviceEntities(selectedDevice);
|
||||
const id = devices.devices.findIndex((d) => d.i === selectedDevice);
|
||||
if (id === -1) {
|
||||
setSelectedDevice(-1);
|
||||
setSelectedDeviceName('');
|
||||
} else {
|
||||
setSelectedDeviceName(devices.devices[id].tn || '');
|
||||
setNumChanges(0);
|
||||
setRestartNeeded(false);
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [devices, selectedDevice]);
|
||||
|
||||
const restart = async () => {
|
||||
await restartCommand().catch((error) => {
|
||||
toast.error(error.message);
|
||||
@@ -244,16 +266,6 @@ const SettingsCustomization: FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const changeSelectedDevice = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (devices) {
|
||||
const selected_device = parseInt(event.target.value, 10);
|
||||
setSelectedDevice(selected_device);
|
||||
setNumChanges(0);
|
||||
void readDeviceEntities(devices?.devices[selected_device].i);
|
||||
setRestartNeeded(false);
|
||||
}
|
||||
};
|
||||
|
||||
const resetCustomization = async () => {
|
||||
try {
|
||||
await resetCustomizations();
|
||||
@@ -312,30 +324,21 @@ const SettingsCustomization: FC = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
await writeCustomEntities({ id: devices?.devices[selectedDevice].i, entity_ids: masked_entities }).catch(
|
||||
(error) => {
|
||||
if (error.message === 'Reboot required') {
|
||||
setRestartNeeded(true);
|
||||
} else {
|
||||
toast.error(error.message);
|
||||
}
|
||||
await writeCustomizationEntities({ id: selectedDevice, entity_ids: masked_entities }).catch((error) => {
|
||||
if (error.message === 'Reboot required') {
|
||||
setRestartNeeded(true);
|
||||
} else {
|
||||
toast.error(error.message);
|
||||
}
|
||||
);
|
||||
});
|
||||
setOriginalSettings(deviceEntities);
|
||||
}
|
||||
};
|
||||
|
||||
const renderDeviceList = () => (
|
||||
<>
|
||||
<Box mb={2} color="warning.main">
|
||||
<Box mb={1} color="warning.main">
|
||||
<Typography variant="body2">{LL.CUSTOMIZATIONS_HELP_1()}.</Typography>
|
||||
<Typography variant="body2" mt={1}>
|
||||
<OptionIcon type="favorite" isSet={true} />={LL.CUSTOMIZATIONS_HELP_2()}
|
||||
<OptionIcon type="readonly" isSet={true} />={LL.CUSTOMIZATIONS_HELP_3()}
|
||||
<OptionIcon type="api_mqtt_exclude" isSet={true} />={LL.CUSTOMIZATIONS_HELP_4()}
|
||||
<OptionIcon type="web_exclude" isSet={true} />={LL.CUSTOMIZATIONS_HELP_5()}
|
||||
<OptionIcon type="deleted" isSet={true} />={LL.CUSTOMIZATIONS_HELP_6()}
|
||||
</Typography>
|
||||
</Box>
|
||||
<TextField
|
||||
name="device"
|
||||
@@ -344,15 +347,15 @@ const SettingsCustomization: FC = () => {
|
||||
fullWidth
|
||||
value={selectedDevice}
|
||||
disabled={numChanges !== 0}
|
||||
onChange={changeSelectedDevice}
|
||||
onChange={(e) => setSelectedDevice(parseInt(e.target.value))}
|
||||
margin="normal"
|
||||
select
|
||||
>
|
||||
<MenuItem disabled key={-1} value={-1}>
|
||||
{LL.SELECT_DEVICE()}...
|
||||
</MenuItem>
|
||||
{devices.devices.map((device: DeviceShort, index) => (
|
||||
<MenuItem key={index} value={index}>
|
||||
{devices.devices.map((device: DeviceShort) => (
|
||||
<MenuItem key={device.i} value={device.i}>
|
||||
{device.s}
|
||||
</MenuItem>
|
||||
))}
|
||||
@@ -361,14 +364,19 @@ const SettingsCustomization: FC = () => {
|
||||
);
|
||||
|
||||
const renderDeviceData = () => {
|
||||
if (deviceEntities.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const shown_data = deviceEntities.filter((de) => filter_entity(de));
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box color="warning.main">
|
||||
<Typography variant="body2" mt={1}>
|
||||
<OptionIcon type="favorite" isSet={true} />={LL.CUSTOMIZATIONS_HELP_2()}
|
||||
<OptionIcon type="readonly" isSet={true} />={LL.CUSTOMIZATIONS_HELP_3()}
|
||||
<OptionIcon type="api_mqtt_exclude" isSet={true} />={LL.CUSTOMIZATIONS_HELP_4()}
|
||||
<OptionIcon type="web_exclude" isSet={true} />={LL.CUSTOMIZATIONS_HELP_5()}
|
||||
<OptionIcon type="deleted" isSet={true} />={LL.CUSTOMIZATIONS_HELP_6()}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Grid container mb={1} mt={0} spacing={1} direction="row" justifyContent="flex-start" alignItems="center">
|
||||
<Grid item xs={2}>
|
||||
<TextField
|
||||
@@ -441,7 +449,7 @@ const SettingsCustomization: FC = () => {
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Typography variant="subtitle2" color="primary">
|
||||
{LL.SHOWING()} {shown_data.length}/{deviceEntities.length}
|
||||
{LL.SHOWING()} {shown_data.length}/{deviceEntities.length} {LL.ENTITIES(deviceEntities.length)}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
@@ -465,7 +473,7 @@ const SettingsCustomization: FC = () => {
|
||||
</Cell>
|
||||
<Cell>
|
||||
{formatName(de, false)} (
|
||||
<Link target="_blank" href={APIURL + devices?.devices[selectedDevice].tn + '/' + de.id}>
|
||||
<Link target="_blank" href={APIURL + selectedDeviceName + '/' + de.id}>
|
||||
{de.id}
|
||||
</Link>
|
||||
)
|
||||
@@ -504,9 +512,9 @@ const SettingsCustomization: FC = () => {
|
||||
{LL.DEVICE_ENTITIES()}
|
||||
</Typography>
|
||||
{devices && renderDeviceList()}
|
||||
{renderDeviceData()}
|
||||
{deviceEntities && renderDeviceData()}
|
||||
{restartNeeded && (
|
||||
<MessageBox my={2} level="warning" message={LL.RESTART_TEXT()}>
|
||||
<MessageBox my={2} level="warning" message={LL.RESTART_TEXT(0)}>
|
||||
<Button startIcon={<PowerSettingsNewIcon />} variant="contained" color="error" onClick={restart}>
|
||||
{LL.RESTART()}
|
||||
</Button>
|
||||
@@ -521,7 +529,7 @@ const SettingsCustomization: FC = () => {
|
||||
startIcon={<CancelIcon />}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
onClick={() => devices && readDeviceEntities(devices.devices[selectedDevice].i)}
|
||||
onClick={() => devices && readDeviceEntities(selectedDevice)}
|
||||
>
|
||||
{LL.CANCEL()}
|
||||
</Button>
|
||||
|
||||
@@ -70,7 +70,7 @@ const SettingsCustomizationDialog = ({ open, onClose, onSave, selectedItem }: Se
|
||||
<DialogContent dividers>
|
||||
<Grid container direction="row">
|
||||
<Typography variant="body2" color="warning.main">
|
||||
{LL.ENTITY() + ' ID'}:
|
||||
{LL.ID_OF(LL.ENTITY())}:
|
||||
</Typography>
|
||||
<Typography variant="body2">{editItem.id}</Typography>
|
||||
</Grid>
|
||||
|
||||
@@ -1,252 +0,0 @@
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import DoneIcon from '@mui/icons-material/Done';
|
||||
import RemoveIcon from '@mui/icons-material/RemoveCircleOutline';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Checkbox,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Grid,
|
||||
InputAdornment,
|
||||
MenuItem,
|
||||
TextField
|
||||
} from '@mui/material';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { DeviceValueUOM_s, DeviceValueType } from './types';
|
||||
import type { EntityItem } from './types';
|
||||
import type Schema from 'async-validator';
|
||||
import type { ValidateFieldsError } from 'async-validator';
|
||||
|
||||
import { dialogStyle } from 'CustomTheme';
|
||||
import { BlockFormControlLabel, ValidatedTextField } from 'components';
|
||||
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
import { updateValue } from 'utils';
|
||||
import { validate } from 'validators';
|
||||
|
||||
type SettingsEntitiesDialogProps = {
|
||||
open: boolean;
|
||||
creating: boolean;
|
||||
onClose: () => void;
|
||||
onSave: (ei: EntityItem) => void;
|
||||
selectedItem: EntityItem;
|
||||
validator: Schema;
|
||||
};
|
||||
|
||||
const SettingsEntitiesDialog = ({
|
||||
open,
|
||||
creating,
|
||||
onClose,
|
||||
onSave,
|
||||
selectedItem,
|
||||
validator
|
||||
}: SettingsEntitiesDialogProps) => {
|
||||
const { LL } = useI18nContext();
|
||||
const [editItem, setEditItem] = useState<EntityItem>(selectedItem);
|
||||
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
||||
const updateFormValue = updateValue(setEditItem);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
setFieldErrors(undefined);
|
||||
setEditItem(selectedItem);
|
||||
// convert to hex strings straight away
|
||||
setEditItem({
|
||||
...selectedItem,
|
||||
device_id: selectedItem.device_id.toString(16).toUpperCase(),
|
||||
type_id: selectedItem.type_id.toString(16).toUpperCase()
|
||||
});
|
||||
}
|
||||
}, [open, selectedItem]);
|
||||
|
||||
const close = () => {
|
||||
onClose();
|
||||
};
|
||||
|
||||
const save = async () => {
|
||||
try {
|
||||
setFieldErrors(undefined);
|
||||
await validate(validator, editItem);
|
||||
if (typeof editItem.device_id === 'string') {
|
||||
editItem.device_id = parseInt(editItem.device_id, 16);
|
||||
}
|
||||
if (typeof editItem.type_id === 'string') {
|
||||
editItem.type_id = parseInt(editItem.type_id, 16);
|
||||
}
|
||||
onSave(editItem);
|
||||
} catch (errors: any) {
|
||||
setFieldErrors(errors);
|
||||
}
|
||||
};
|
||||
|
||||
const remove = () => {
|
||||
editItem.deleted = true;
|
||||
onSave(editItem);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog sx={dialogStyle} open={open} onClose={close}>
|
||||
<DialogTitle>
|
||||
{creating ? LL.ADD(1) + ' ' + LL.NEW(1) : LL.EDIT()} {LL.ENTITY()}
|
||||
</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
<Box display="flex" flexWrap="wrap" mb={1}>
|
||||
<Box flexWrap="nowrap" whiteSpace="nowrap" />
|
||||
</Box>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={8}>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="name"
|
||||
label={LL.NAME(0)}
|
||||
value={editItem.name}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
onChange={updateFormValue}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={4} mt={3}>
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox checked={editItem.writeable} onChange={updateFormValue} name="writeable" />}
|
||||
label={LL.WRITEABLE()}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="device_id"
|
||||
label={LL.ID_OF(LL.DEVICE())}
|
||||
margin="normal"
|
||||
type="string"
|
||||
fullWidth
|
||||
value={editItem.device_id as string}
|
||||
onChange={updateFormValue}
|
||||
inputProps={{ style: { textTransform: 'uppercase' } }}
|
||||
InputProps={{ startAdornment: <InputAdornment position="start">0x</InputAdornment> }}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="type_id"
|
||||
label={LL.ID_OF(LL.TYPE(1))}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
value={editItem.type_id}
|
||||
onChange={updateFormValue}
|
||||
inputProps={{ style: { textTransform: 'uppercase' } }}
|
||||
InputProps={{ startAdornment: <InputAdornment position="start">0x</InputAdornment> }}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="offset"
|
||||
label={LL.OFFSET()}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
type="number"
|
||||
value={editItem.offset}
|
||||
onChange={updateFormValue}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<TextField
|
||||
name="value_type"
|
||||
label={LL.VALUE(1) + ' ' + LL.TYPE(1)}
|
||||
value={editItem.value_type}
|
||||
variant="outlined"
|
||||
onChange={updateFormValue}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
select
|
||||
>
|
||||
<MenuItem value={DeviceValueType.BOOL}>BOOL</MenuItem>
|
||||
<MenuItem value={DeviceValueType.INT}>INT</MenuItem>
|
||||
<MenuItem value={DeviceValueType.UINT}>UINT</MenuItem>
|
||||
<MenuItem value={DeviceValueType.SHORT}>SHORT</MenuItem>
|
||||
<MenuItem value={DeviceValueType.USHORT}>USHORT</MenuItem>
|
||||
<MenuItem value={DeviceValueType.ULONG}>ULONG</MenuItem>
|
||||
<MenuItem value={DeviceValueType.TIME}>TIME</MenuItem>
|
||||
<MenuItem value={DeviceValueType.STRING}>RAW</MenuItem>
|
||||
</TextField>
|
||||
</Grid>
|
||||
|
||||
{editItem.value_type !== DeviceValueType.BOOL && editItem.value_type !== DeviceValueType.STRING && (
|
||||
<>
|
||||
<Grid item xs={4}>
|
||||
<TextField
|
||||
name="factor"
|
||||
label={LL.FACTOR()}
|
||||
value={editItem.factor}
|
||||
variant="outlined"
|
||||
onChange={updateFormValue}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
type="number"
|
||||
inputProps={{ step: '0.001' }}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<TextField
|
||||
name="uom"
|
||||
label={LL.UNIT()}
|
||||
value={editItem.uom}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
onChange={updateFormValue}
|
||||
select
|
||||
>
|
||||
{DeviceValueUOM_s.map((val, i) => (
|
||||
<MenuItem key={i} value={i}>
|
||||
{val}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
</Grid>
|
||||
</>
|
||||
)}
|
||||
{editItem.value_type === DeviceValueType.STRING && (
|
||||
<Grid item xs={4}>
|
||||
<TextField
|
||||
name="factor"
|
||||
label="Bytes"
|
||||
value={editItem.factor}
|
||||
variant="outlined"
|
||||
onChange={updateFormValue}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
type="number"
|
||||
inputProps={{ min: '1', max: '27', step: '1' }}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</DialogContent>
|
||||
|
||||
<DialogActions>
|
||||
{!creating && (
|
||||
<Box flexGrow={1}>
|
||||
<Button startIcon={<RemoveIcon />} variant="outlined" color="warning" onClick={remove}>
|
||||
{LL.REMOVE()}
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
<Button startIcon={<CancelIcon />} variant="outlined" onClick={close} color="secondary">
|
||||
{LL.CANCEL()}
|
||||
</Button>
|
||||
<Button startIcon={creating ? <AddIcon /> : <DoneIcon />} variant="outlined" onClick={save} color="primary">
|
||||
{creating ? LL.ADD(0) : LL.UPDATE()}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default SettingsEntitiesDialog;
|
||||
@@ -9,7 +9,7 @@ import { useTheme } from '@table-library/react-table-library/theme';
|
||||
// eslint-disable-next-line import/named
|
||||
import { updateState, useRequest } from 'alova';
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { unstable_useBlocker as useBlocker } from 'react-router-dom';
|
||||
import { useBlocker } from 'react-router-dom';
|
||||
import { toast } from 'react-toastify';
|
||||
import SettingsSchedulerDialog from './SettingsSchedulerDialog';
|
||||
import * as EMSESP from './api';
|
||||
@@ -208,7 +208,7 @@ const SettingsScheduler: FC = () => {
|
||||
<HeaderCell stiff>{LL.SCHEDULE(0)}</HeaderCell>
|
||||
<HeaderCell stiff>{LL.TIME(0)}</HeaderCell>
|
||||
<HeaderCell stiff>{LL.COMMAND(0)}</HeaderCell>
|
||||
<HeaderCell stiff>{LL.VALUE(0)}</HeaderCell>
|
||||
<HeaderCell stiff>{LL.VALUE(1)}</HeaderCell>
|
||||
<HeaderCell stiff>{LL.NAME(0)}</HeaderCell>
|
||||
</HeaderRow>
|
||||
</Header>
|
||||
|
||||
@@ -19,7 +19,8 @@ import { alovaInstance } from 'api/endpoints';
|
||||
export const readCoreData = () => alovaInstance.Get<CoreData>(`/rest/coreData`);
|
||||
export const readDeviceData = (id: number) =>
|
||||
alovaInstance.Get<DeviceData>('/rest/deviceData', {
|
||||
params: { id },
|
||||
// alovaInstance.Get<DeviceData>(`/rest/deviceData/${id}`, {
|
||||
params: { id }, // TODO replace later with id
|
||||
responseType: 'arraybuffer' // uses msgpack
|
||||
});
|
||||
export const writeDeviceValue = (data: any) => alovaInstance.Post('/rest/writeDeviceValue', data);
|
||||
@@ -53,8 +54,9 @@ export const getSchedule = () => alovaInstance.Get('/rest/getSchedule');
|
||||
|
||||
// SettingsCustomization
|
||||
export const readDeviceEntities = (id: number) =>
|
||||
alovaInstance.Get<DeviceEntity[]>('/rest/deviceEntities', {
|
||||
params: { id },
|
||||
// alovaInstance.Get<DeviceEntity[]>(`/rest/deviceEntities/${id}`, {
|
||||
alovaInstance.Get<DeviceEntity[]>(`/rest/deviceEntities`, {
|
||||
params: { id }, // TODO replace later with id
|
||||
responseType: 'arraybuffer',
|
||||
transformData(data: any) {
|
||||
return data.map((de: DeviceEntity) => ({ ...de, o_m: de.m, o_cn: de.cn, o_mi: de.mi, o_ma: de.ma }));
|
||||
@@ -62,7 +64,7 @@ export const readDeviceEntities = (id: number) =>
|
||||
});
|
||||
export const readDevices = () => alovaInstance.Get<Devices>('/rest/devices');
|
||||
export const resetCustomizations = () => alovaInstance.Post('/rest/resetCustomizations');
|
||||
export const writeCustomEntities = (data: any) => alovaInstance.Post('/rest/customEntities', data);
|
||||
export const writeCustomizationEntities = (data: any) => alovaInstance.Post('/rest/customizationEntities', data);
|
||||
|
||||
// SettingsScheduler
|
||||
export const readSchedule = () =>
|
||||
@@ -85,8 +87,8 @@ export const readSchedule = () =>
|
||||
export const writeSchedule = (data: any) => alovaInstance.Post('/rest/schedule', data);
|
||||
|
||||
// SettingsEntities
|
||||
export const readEntities = () =>
|
||||
alovaInstance.Get<EntityItem[]>('/rest/entities', {
|
||||
export const readCustomEntities = () =>
|
||||
alovaInstance.Get<EntityItem[]>('/rest/customEntities', {
|
||||
name: 'entities',
|
||||
transformData(data: any) {
|
||||
return data.entities.map((ei: EntityItem) => ({
|
||||
@@ -104,4 +106,4 @@ export const readEntities = () =>
|
||||
}));
|
||||
}
|
||||
});
|
||||
export const writeEntities = (data: any) => alovaInstance.Post('/rest/entities', data);
|
||||
export const writeCustomEntities = (data: any) => alovaInstance.Post('/rest/customEntities', data);
|
||||
|
||||
@@ -7,6 +7,7 @@ export interface Settings {
|
||||
syslog_mark_interval: number;
|
||||
syslog_host: string;
|
||||
syslog_port: number;
|
||||
boiler_heatingoff: boolean;
|
||||
shower_timer: boolean;
|
||||
shower_alert: boolean;
|
||||
shower_alert_coldshot: number;
|
||||
@@ -68,6 +69,7 @@ export interface Device {
|
||||
d: number; // deviceid
|
||||
p: number; // productid
|
||||
v: string; // version
|
||||
e: number; // entities
|
||||
}
|
||||
|
||||
export interface TemperatureSensor {
|
||||
@@ -100,6 +102,7 @@ export interface SensorData {
|
||||
ts: TemperatureSensor[];
|
||||
as: AnalogSensor[];
|
||||
analog_enabled: boolean;
|
||||
platform: string;
|
||||
}
|
||||
|
||||
export interface CoreData {
|
||||
@@ -127,7 +130,7 @@ export interface DeviceValue {
|
||||
c?: string; // command, optional
|
||||
l?: string[]; // list, optional
|
||||
h?: string; // help text, optional
|
||||
s?: number; // steps for up/down, optional
|
||||
s?: string; // steps for up/down, optional
|
||||
m?: number; // min, optional
|
||||
x?: number; // max, optional
|
||||
}
|
||||
@@ -173,7 +176,9 @@ export enum DeviceValueUOM {
|
||||
M3,
|
||||
L,
|
||||
KMIN,
|
||||
K
|
||||
K,
|
||||
VOLTS,
|
||||
MBAR
|
||||
}
|
||||
|
||||
export const DeviceValueUOM_s = [
|
||||
@@ -199,7 +204,9 @@ export const DeviceValueUOM_s = [
|
||||
'm³',
|
||||
'l',
|
||||
'K*min',
|
||||
'K'
|
||||
'K',
|
||||
'V',
|
||||
'mbar'
|
||||
];
|
||||
|
||||
export enum AnalogType {
|
||||
@@ -218,12 +225,12 @@ export enum AnalogType {
|
||||
|
||||
export const AnalogTypeNames = [
|
||||
'(disabled)',
|
||||
'Digital in',
|
||||
'Digital In',
|
||||
'Counter',
|
||||
'ADC',
|
||||
'Timer',
|
||||
'Rate',
|
||||
'Digital out',
|
||||
'Digital Out',
|
||||
'PWM 0',
|
||||
'PWM 1',
|
||||
'PWM 2'
|
||||
@@ -235,7 +242,9 @@ type BoardProfiles = {
|
||||
|
||||
export const BOARD_PROFILES: BoardProfiles = {
|
||||
S32: 'BBQKees Gateway S32',
|
||||
S32S3: 'BBQKees Gateway S3',
|
||||
E32: 'BBQKees Gateway E32',
|
||||
E32V2: 'BBQKees Gateway E32 V2',
|
||||
NODEMCU: 'NodeMCU 32S',
|
||||
'MH-ET': 'MH-ET Live D1 Mini',
|
||||
LOLIN: 'Lolin D32',
|
||||
@@ -243,8 +252,7 @@ export const BOARD_PROFILES: BoardProfiles = {
|
||||
OLIMEXPOE: 'Olimex ESP32-POE',
|
||||
C3MINI: 'Wemos C3 Mini',
|
||||
S2MINI: 'Wemos S2 Mini',
|
||||
S3MINI: 'Liligo S3',
|
||||
S32S3: 'BBQKees Gateway S3'
|
||||
S3MINI: 'Liligo S3'
|
||||
};
|
||||
|
||||
export interface BoardProfile {
|
||||
@@ -317,6 +325,7 @@ export enum ScheduleFlag {
|
||||
|
||||
export interface EntityItem {
|
||||
id: number; // unique number
|
||||
ram: number;
|
||||
name: string;
|
||||
device_id: number | string;
|
||||
type_id: number | string;
|
||||
@@ -328,6 +337,7 @@ export interface EntityItem {
|
||||
writeable: boolean;
|
||||
deleted?: boolean;
|
||||
o_id?: number;
|
||||
o_ram?: number;
|
||||
o_name?: string;
|
||||
o_device_id?: number | string;
|
||||
o_type_id?: number | string;
|
||||
@@ -337,6 +347,7 @@ export interface EntityItem {
|
||||
o_value_type?: number;
|
||||
o_deleted?: boolean;
|
||||
o_writeable?: boolean;
|
||||
o_value?: any;
|
||||
}
|
||||
|
||||
export interface Entities {
|
||||
@@ -359,7 +370,7 @@ export const enum DeviceType {
|
||||
CONTROLLER,
|
||||
CONNECT,
|
||||
ALERT,
|
||||
PUMP,
|
||||
EXTENSION,
|
||||
GENERIC,
|
||||
HEATSOURCE,
|
||||
CUSTOM,
|
||||
@@ -381,6 +392,7 @@ export const enum DeviceValueType {
|
||||
}
|
||||
|
||||
export const DeviceValueTypeNames = [
|
||||
//
|
||||
'BOOL',
|
||||
'INT',
|
||||
'UINT',
|
||||
@@ -389,6 +401,6 @@ export const DeviceValueTypeNames = [
|
||||
'ULONG',
|
||||
'TIME',
|
||||
'ENUM',
|
||||
'STRING',
|
||||
'RAW',
|
||||
'CMD'
|
||||
];
|
||||
|
||||
@@ -8,8 +8,7 @@ export const GPIO_VALIDATOR = {
|
||||
if (
|
||||
value &&
|
||||
(value === 1 ||
|
||||
(value >= 6 && value <= 12) ||
|
||||
(value >= 14 && value <= 15) ||
|
||||
(value >= 6 && value <= 11) ||
|
||||
value === 20 ||
|
||||
value === 24 ||
|
||||
(value >= 28 && value <= 31) ||
|
||||
@@ -189,12 +188,12 @@ export const isGPIOUniqueValidator = (sensors: AnalogSensor[]) => ({
|
||||
}
|
||||
});
|
||||
|
||||
export const analogSensorItemValidation = (sensors: AnalogSensor[], creating: boolean) =>
|
||||
export const analogSensorItemValidation = (sensors: AnalogSensor[], creating: boolean, platform: string) =>
|
||||
new Schema({
|
||||
n: [{ required: true, message: 'Name is required' }],
|
||||
g: [
|
||||
{ required: true, message: 'GPIO is required' },
|
||||
GPIO_VALIDATOR,
|
||||
platform === 'ESP32-S3' ? GPIO_VALIDATORS3 : platform === 'ESP32-C3' ? GPIO_VALIDATORC3 : GPIO_VALIDATOR,
|
||||
...(creating ? [isGPIOUniqueValidator(sensors)] : [])
|
||||
]
|
||||
});
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export * from './ap';
|
||||
export * from './features';
|
||||
export * from './me';
|
||||
export * from './mqtt';
|
||||
export * from './ntp';
|
||||
|
||||
@@ -25,6 +25,7 @@ export interface MqttSettings {
|
||||
port: number;
|
||||
base: string;
|
||||
rootCA?: string;
|
||||
enableTLS?: boolean;
|
||||
username: string;
|
||||
password: string;
|
||||
client_id: string;
|
||||
|
||||
@@ -33,14 +33,16 @@ export interface NetworkStatus {
|
||||
gateway_ip: string;
|
||||
dns_ip_1: string;
|
||||
dns_ip_2: string;
|
||||
hostname: string;
|
||||
}
|
||||
|
||||
export interface NetworkSettings {
|
||||
ssid: string;
|
||||
bssid: string;
|
||||
password: string;
|
||||
hostname: string;
|
||||
static_ip_config: boolean;
|
||||
enableIPv6: boolean;
|
||||
enableIPv6?: boolean;
|
||||
bandwidth20: boolean;
|
||||
nosleep: boolean;
|
||||
tx_power: number;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user