961 Commits

Author SHA1 Message Date
Proddy
9233f0dfcc v3.5.1 - merge with patch 2023-03-11 16:06:05 +01:00
Proddy
292f743b14 Update bug_report.md 2023-02-19 11:18:08 +01:00
Proddy
dd6dfffd57 Delete questions---troubleshooting.md 2023-02-19 10:49:52 +01:00
Proddy
ec705a5307 Delete feature_request.md 2023-02-19 10:49:45 +01:00
Proddy
f45f071710 Create config.yml 2023-02-19 10:49:32 +01:00
Proddy
f3858546de Merge branch 'origin/dev' 2023-02-06 21:58:27 +01:00
Proddy
de40cb8920 Merge pull request #948 from proddy/dev
prepare 3.5.0
2023-02-06 21:29:00 +01:00
Proddy
8ddf1315eb update packages 2023-02-06 21:28:05 +01:00
Proddy
00d5b16de7 update screenshots 2023-02-06 21:27:57 +01:00
Proddy
0b02bb417a Merge pull request #947 from MichaelDvP/dev
add boiler pump operating mode #944, dev.18
2023-02-06 21:09:13 +01:00
MichaelDvP
d68260411d version to dev.18, changelog 2023-02-06 14:22:41 +01:00
MichaelDvP
7227937660 add boiler pump operating mode #944 2023-02-06 14:19:58 +01:00
Proddy
e66e2c40a6 Merge pull request #941 from proddy/dev
minor cleanup
2023-02-04 14:09:20 +01:00
Proddy
39bc0d5fb2 update packages 2023-02-04 14:08:49 +01:00
Proddy
50e73e2a04 formatting 2023-02-04 14:08:41 +01:00
Proddy
efa9875744 fix bug with custom name 2023-02-04 14:08:25 +01:00
Proddy
8182b0e95a Merge pull request #940 from MichaelDvP/dev
Entitiy blacklist (remove from mem, #891) and RC20/30 modes/seltemps #932
2023-02-04 13:53:21 +01:00
MichaelDvP
6091621858 update pkg 2023-01-31 11:42:17 +01:00
MichaelDvP
ba568581ff Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev 2023-01-31 11:40:04 +01:00
MichaelDvP
c78190c3a0 version b17, changelog 2023-01-31 09:24:22 +01:00
MichaelDvP
84c968e053 RC20/30 temperature for mode off, temp settings inline 2023-01-31 09:24:03 +01:00
Proddy
6b7da4068c Merge pull request #939 from pswid/dev
added hyperlink to the EMS-ESP web interface in Home Assistant (IPv4)
2023-01-30 20:21:32 +01:00
pswid
e3e14e7a66 fixed NTP status in HA (see #931)
I missed this one
2023-01-30 19:50:36 +01:00
pswid
997ced3938 system entities moved to "diagnostic" category in HA 2023-01-30 19:37:50 +01:00
pswid
b18da9064b fixed link to the EMS-ESP in HA
prefix "http:// " is needed though
2023-01-30 18:31:57 +01:00
pswid
b3ee5f4d9a added link to the EMS-ESP web interface in HA (IPv4 only) 2023-01-30 12:05:16 +01:00
MichaelDvP
5398abb074 Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev 2023-01-30 10:41:29 +01:00
MichaelDvP
65397d3e1e RC20/RC30 modes, #932 2023-01-30 10:41:00 +01:00
Proddy
887d53528b Merge pull request #937 from proddy/dev
fix dump_value_info()
2023-01-30 09:29:13 +01:00
Proddy
1603eafbb2 fix dump_value_info() 2023-01-30 09:22:03 +01:00
Proddy
33bcb54aaf Merge pull request #936 from pswid/dev
fixed 1/0 and true/false MQTT/API format options with HA enabled (#931)
2023-01-30 08:26:17 +01:00
pswid
d36f87707a removed "mode"+"step" in HA discovery for BOOL type entities 2023-01-29 19:08:17 +01:00
pswid
226c1fd6c5 fixed 1/0 and true/false MQTT/API format options with HA enabled (#931)
furthermore variable names in the HA discovery payload has been abbreviated according to: https://www.home-assistant.io/integrations/mqtt/#discovery-payload
2023-01-29 18:52:13 +01:00
Proddy
74b961ab29 Merge pull request #935 from proddy/dev
update packages, minor bug in
2023-01-29 12:17:53 +01:00
Proddy
e4358ba489 uptime in HA should be a string 2023-01-29 12:16:34 +01:00
Proddy
26522cb061 package update 2023-01-29 12:16:14 +01:00
MichaelDvP
be128da9e0 Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev 2023-01-28 15:53:31 +01:00
Proddy
381afcbb6d Merge pull request #929 from pswid/dev
PL translation update
2023-01-28 15:48:22 +01:00
MichaelDvP
1dc008855e Moduline 400 manual temp, #932 2023-01-28 15:48:16 +01:00
MichaelDvP
bbf4431b5f allow change customization device without reboot 2023-01-28 15:15:21 +01:00
MichaelDvP
c38ad8e382 allow upload of smaller loader bin 2023-01-28 15:13:18 +01:00
pswid
1416bad9cc removed % from max log buffer size setting 2023-01-24 20:00:00 +01:00
MichaelDvP
0e0e9eccf1 Add blacklist/remove entities #891 2023-01-24 18:37:52 +01:00
pswid
032a631b61 capital first letter in the names of special EMS devices
should eventually be translated
2023-01-23 10:50:32 +01:00
pswid
5d0f6b665b SAVE instead of ADD in user edit box 2023-01-23 10:45:32 +01:00
pswid
4bbf096350 PL translation update (entities) 2023-01-23 10:43:26 +01:00
pswid
e1a950ec21 PL translation update 2023-01-23 10:40:47 +01:00
Proddy
ede8e7dfce Merge pull request #926 from mvjt/dev
Additional SV translations#2
2023-01-22 21:17:53 +01:00
Proddy
9d155d6a0e Merge pull request #927 from proddy/dev
HA use box for numbers now that HA supports this
2023-01-22 19:10:12 +01:00
proddy
1fa92eec57 HA use box for numbers now that HA supports this 2023-01-22 19:09:41 +01:00
Martin Hammarberg
7fc28fd1bb Additional SV translations#2 2023-01-22 17:33:12 +01:00
Proddy
dd338f5f4b Merge pull request #925 from mvjt/dev
Additional SV translations
2023-01-22 17:03:33 +01:00
Martin Hammarberg
486bc5ac28 Additional SV translations 2023-01-22 16:59:08 +01:00
Proddy
68215f37f5 Merge pull request #924 from proddy/dev
preparing for 3.5.0 stable
2023-01-22 13:31:30 +01:00
Proddy
4449d3fce8 formatting 2023-01-22 13:30:19 +01:00
Proddy
cbb20439ed formatting 2023-01-22 13:29:05 +01:00
Proddy
ec357b71f1 formatting 2023-01-22 13:27:37 +01:00
Proddy
5acdb4dc31 default OTA is disabled (to save memory) 2023-01-22 13:27:30 +01:00
Proddy
dd959a7316 update packages 2023-01-22 13:27:20 +01:00
Proddy
0b6dbe8bfa default OTA is disabled (to save memory) 2023-01-22 13:27:03 +01:00
Proddy
78e6263a2e Merge pull request #923 from MichaelDvP/dev
fixes and the tested heatpump/heatsource entities
2023-01-22 13:06:23 +01:00
MichaelDvP
3b2a4d1eb4 Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev_ 2023-01-22 10:48:37 +01:00
Proddy
485195e035 Merge pull request #901 from pswid/dev
added dhw alternating operation
2023-01-22 10:12:47 +01:00
MichaelDvP
d735f397d8 fix crash with non-en language 2023-01-21 14:22:49 +01:00
pswid
5e41481e27 capitalize more special chcaracters 2023-01-21 12:09:17 +01:00
MichaelDvP
d52c54d6fb update changelog, v3.5.0-dev.16 2023-01-21 11:34:17 +01:00
MichaelDvP
36c85eed5e change heatingpump2Mod to absBurnPow, #908 2023-01-21 11:31:02 +01:00
MichaelDvP
b09deb1494 add EMS+ wwCurFlow #918 2023-01-20 09:18:05 +01:00
MichaelDvP
cb4cce119b map device type of integrated devices #917 2023-01-19 09:32:36 +01:00
pswid
7e174a1b7d Fix first letter not capitalized in Polish 2023-01-18 21:43:29 +01:00
MichaelDvP
1f08940e47 add thermostat Rego 3000, TR120RF, heatpump modules, #917 2023-01-18 08:59:58 +01:00
MichaelDvP
8db5724b77 fix rounding of web number-inputs 2023-01-18 08:52:38 +01:00
MichaelDvP
a21352ae4f Check size for sneding customizations 2023-01-18 08:50:56 +01:00
MichaelDvP
a38f4978fa analogsensor publish wth single gpio number fix #915 2023-01-18 07:56:40 +01:00
pswid
4f05ddab93 fix for d72d2b3 (Polish boolean option "wł./wył.")
Because of compiler error, in Polish translation,  we don't use uppercase boolean option "WŁĄCZONO/WYŁĄCZONO" but "wł./wył." instead.
2023-01-15 13:00:22 +01:00
pswid
bbbeb155f0 tags in entity IDs (long format) always in English
now in mqtt discovery topics you can see e.g:
"uniq_id": "thermostat_OG1_mode_type" (for Polish lang.)

but should be:
"uniq_id": "thermostat_hc1_mode_type",
2023-01-13 21:45:29 +01:00
MichaelDvP
f6238cd6ab rename auxmaxtemp->auxmaxlimit, add auxlimitstart 2023-01-12 17:37:00 +01:00
MichaelDvP
d658b67e93 add fluegastemp #906 2023-01-12 07:31:57 +01:00
MichaelDvP
e2c675d9a5 add back wwcircmode off (not writable) #905 2023-01-11 18:40:33 +01:00
MichaelDvP
3e3a600a60 remove/recreate HA config for dallas/analog on change #888 2023-01-11 18:38:07 +01:00
MichaelDvP
1f0d2b147b Add ESP32S3 (Liligo) as S3 Mini 2023-01-11 18:33:44 +01:00
MichaelDvP
285b0dc01d package update 2023-01-10 17:38:51 +01:00
MichaelDvP
172153b840 fix scaling of auxMaxTemp #902 2023-01-10 17:19:04 +01:00
MichaelDvP
ce71b8b5f6 Add back "HA climate config creation", #903 2023-01-10 17:18:15 +01:00
pswid
d72d2b33bd fix Polish boolean option "wł./wył." not working
Because of compiler error, in Polish translation,  we don't use uppercase boolean option "WŁĄCZONO/WYŁĄCZONO" but "wł./wył." instead.
2023-01-10 12:04:27 +01:00
pswid
d15fbe7801 added dhw alternating operation 2023-01-10 11:50:02 +01:00
MichaelDvP
caab1cae39 update packages 2023-01-08 15:20:37 +01:00
MichaelDvP
dced355a29 Add silentmode from/to #896 2023-01-08 14:12:53 +01:00
MichaelDvP
f133fdfb1c Allow reboot to smaller OTA partition or factory partition 2023-01-08 14:12:12 +01:00
MichaelDvP
80ea1e95ee Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev_ 2023-01-07 15:33:18 +01:00
MichaelDvP
4098cef279 add 9 heatpump settings #892 (without translation) 2023-01-07 14:47:45 +01:00
MichaelDvP
d0aac18b88 fix ignore entity, add weblog level OFF 2023-01-06 18:56:58 +01:00
MichaelDvP
7bb35812ff aloow flag entities not to register, test for #891 2023-01-06 13:44:02 +01:00
MichaelDvP
ad49267b29 add one entity #886 2023-01-06 09:29:13 +01:00
Proddy
88404bcb56 Merge pull request #887 from proddy/dev
README update and one change to api_data
2023-01-05 16:04:01 +01:00
Proddy
33af6ec57e update README 2023-01-05 15:56:40 +01:00
Proddy
4b3a0bf4e2 use const char * instead of arduino String 2023-01-05 15:56:26 +01:00
MichaelDvP
508dd399be add entities #886 2023-01-05 15:52:39 +01:00
MichaelDvP
8b2466dcde Merge pull request #870 from MichaelDvP/dev
Changes to heatsource, api, console
2023-01-05 14:48:12 +01:00
MichaelDvP
c754d19015 translate sensor device in web 2023-01-05 11:37:00 +01:00
MichaelDvP
ad680de897 Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev 2023-01-05 11:19:26 +01:00
MichaelDvP
78ae4d7a7d typo, rename from #787, mark missing translations, add #883 2023-01-05 09:56:58 +01:00
Proddy
74acf2fe0f Merge pull request #885 from proddy/dev
#872
2023-01-05 09:45:55 +01:00
proddy
bab9fd8ec4 updates to Translate Device Name #872 2023-01-05 09:45:33 +01:00
Proddy
cfce36c212 small change 2023-01-03 15:00:04 +01:00
Proddy
5c6d6da4f9 update packages 2023-01-03 14:50:56 +01:00
Proddy
5005d06507 Translate Device Name #872 2023-01-03 14:50:50 +01:00
Proddy
9a09062a84 bump version 2023-01-03 14:48:57 +01:00
Proddy
6dc993a276 add note to investigate xTaskSuspend and static buffer size 2023-01-03 14:48:48 +01:00
Proddy
c45ceec33b remove comment 2023-01-03 14:48:20 +01:00
Proddy
c28de99907 remove comments 2023-01-03 14:48:08 +01:00
Proddy
b587c08768 tag missing translations 2023-01-03 14:47:57 +01:00
MichaelDvP
71043963e7 pvCooling, pvEnable, fix #878 2023-01-02 21:40:50 +01:00
MichaelDvP
04b17a15bb Register 0x68/0x69 as cascaded controller for testing! 2023-01-02 16:42:55 +01:00
MichaelDvP
4aa9d11574 calculate command ids from tag names 2023-01-02 15:29:33 +01:00
MichaelDvP
56b5739cfc add EMS1.0 Cascade (MC40?) 2023-01-02 15:07:44 +01:00
MichaelDvP
bb1704ed7a auxMaxTemp, pvRaise/Lower to Kelvin 2023-01-02 15:06:56 +01:00
MichaelDvP
9163fc74d4 fix reading maxHeat values 2023-01-02 12:16:46 +01:00
MichaelDvP
49113edeff add values/settings for #878, #879, #880 2023-01-02 10:45:00 +01:00
MichaelDvP
718ede8439 prevent writing empty settings 2023-01-02 10:08:00 +01:00
MichaelDvP
5ee49d8dad Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev 2023-01-01 21:02:24 +01:00
MichaelDvP
c19885d730 weblog buffer size 2023-01-01 18:38:51 +01:00
Proddy
d2e64468d5 Merge pull request #876 from proddy/dev
Fix Swedish - #874 and #875
2023-01-01 17:46:46 +01:00
proddy
e3b6a3308f remove parse-ms 2023-01-01 16:49:00 +01:00
proddy
c3f88ae0c8 SE to SV - Plural of "days" under EMS Status not working for Swedish #874 2023-01-01 16:48:21 +01:00
proddy
31a5868864 remove parse-ms 2023-01-01 16:46:31 +01:00
proddy
4cb50a1dff fix #875 2023-01-01 16:46:15 +01:00
MichaelDvP
2062703565 fix #874 plural of days 2023-01-01 15:21:18 +01:00
MichaelDvP
5e00f07fd8 fix #875 typo 2023-01-01 15:20:35 +01:00
MichaelDvP
3bf2110df1 command prefix ahs1 2022-12-31 18:48:55 +01:00
MichaelDvP
c3ff60dced move hs1..hs16 to heatsource, AM200 as ahs1 2022-12-31 15:41:43 +01:00
MichaelDvP
7cf3b8f1a9 Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev 2022-12-31 11:35:38 +01:00
MichaelDvP
4536d60e3e v3.5.0-dev.15 2022-12-31 11:35:25 +01:00
MichaelDvP
2f1ea4da67 output api_data to console 2022-12-31 11:25:29 +01:00
MichaelDvP
34f6b412f6 fix #841, call <device> hcx show info 2022-12-31 11:04:24 +01:00
MichaelDvP
b16a16d100 2. fix #865 2022-12-31 11:02:59 +01:00
MichaelDvP
55750844ea fix #865 2022-12-30 19:11:00 +01:00
Proddy
6c09134552 Merge pull request #866 from proddy/dev
add test for #865
2022-12-30 18:45:11 +01:00
proddy
8fa74c63c9 add test for #865 2022-12-30 18:43:51 +01:00
MichaelDvP
275ac351fe Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev 2022-12-30 15:48:40 +01:00
MichaelDvP
b728827324 update packages 2022-12-30 15:46:55 +01:00
Proddy
2db99a3e32 Merge pull request #864 from proddy/dev
fix again for #828
2022-12-30 15:35:01 +01:00
Proddy
263c011a7b fix again for #828 2022-12-30 15:36:25 +01:00
Proddy
8eeebb0cef use espressif32 5.2.0 - #862 2022-12-30 11:50:52 +01:00
Proddy
f055e53987 Merge pull request #858 from proddy/dev
fix for #828 - show thermostat hc1 and mixer wwc
2022-12-30 11:48:01 +01:00
Proddy
6dce5f5931 more fixes for Create table of all EMS-ESP entities, by device, shortname/fullname and characteristics #828 2022-12-29 22:02:36 +01:00
Proddy
14cfbf78bd update package 2022-12-29 22:02:04 +01:00
Proddy
6de577839b update package 2022-12-29 22:01:48 +01:00
MichaelDvP
bfcdf3ef98 fix #860, wwactivated in offset 5 2022-12-29 18:43:35 +01:00
MichaelDvP
ffa7ddebb8 add icon, rename to AM200, show web log buffer filling, #857 2022-12-29 16:18:01 +01:00
MichaelDvP
29838a433a fix #857, add class heatsource 2022-12-29 12:16:47 +01:00
Proddy
1f1422bedd fix for #828 - show thermostat hc1 and mixer wwc 2022-12-28 22:18:43 +01:00
Proddy
d41e634611 Merge pull request #856 from MichaelDvP/dev
HA firstrun only delete config, create HA in next loop (mqtt queue)
2022-12-28 21:05:07 +01:00
MichaelDvP
22cc890cab HA firstrun only delete config, create HA in next loop (mqtt queue) 2022-12-28 18:52:18 +01:00
MichaelDvP
15e940bd2d Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev 2022-12-28 18:46:37 +01:00
Proddy
c788f53bb9 Merge pull request #854 from mvjt/dev
Additional SE translations
2022-12-28 18:07:56 +01:00
mvjt
10f1c5781e Additional SE translation (corrections) 2022-12-28 16:48:58 +01:00
mvjt
75b0869a77 Additional SE translation 2022-12-28 16:37:46 +01:00
Proddy
c4c341922b Merge pull request #851 from proddy/dev
#828 dump all out entities (standalone only)
2022-12-28 15:41:07 +01:00
Proddy
43b4adc618 changes to #828 2022-12-28 15:39:34 +01:00
Proddy
bad982dbdd update ArduinoJson to latest v6.20.0 (saves 1600 bytes in flash. whoot!) 2022-12-28 13:11:57 +01:00
Proddy
5d45064c2d update ArduinoJson to latest v6.20.0 (saves 1600 bytes in flash. whoot!) 2022-12-27 21:48:03 +01:00
Proddy
6390f4aa48 update packages 2022-12-27 21:47:08 +01:00
Proddy
bb94d56bd0 Merge branch 'emsesp:dev' into dev 2022-12-27 21:19:11 +01:00
Proddy
14f3d9ab12 Merge pull request #849 from MichaelDvP/dev
fix #846, #848, remove double values hpsuctionGas/hpHotGas, stick to hpTr5/hpTr6
2022-12-27 21:16:58 +01:00
MichaelDvP
d9ecf0efb8 remove double values hpsuctionGas/hpHotGas, stick to hpTr5/hpTr6 2022-12-27 18:03:10 +01:00
Proddy
9b66b02e46 Merge branch 'emsesp:dev' into dev 2022-12-27 17:26:00 +01:00
proddy
feca878fdd Create table of all EMS-ESP entities, by device, shortname/fullname and characteristics #828 2022-12-27 16:27:57 +01:00
Proddy
f53fd74873 Merge pull request #845 from MichaelDvP/dev
fix copy/paste error
2022-12-27 11:49:23 +01:00
MichaelDvP
bbaf892523 add silentMode #831 2022-12-27 11:34:42 +01:00
MichaelDvP
451b3abddf fix copy/paste error 2022-12-27 10:46:19 +01:00
Proddy
ba03add3d3 Merge pull request #842 from MichaelDvP/dev
fix #841
2022-12-27 10:27:37 +01:00
MichaelDvP
a41de7ed1c Avoid 507, reduce buffer stepwise until it fits 2022-12-27 10:22:28 +01:00
MichaelDvP
28de5bb097 fix #841 2022-12-27 09:02:15 +01:00
Proddy
d08a1224e2 Merge pull request #840 from proddy/dev
added version upgrade/downgrade #832
2022-12-26 15:07:55 +01:00
proddy
327cf7ec75 rollback to c++11 to save on flash 2022-12-25 20:05:46 +01:00
Proddy
83438129a2 fixes #833 2022-12-25 16:50:19 +01:00
Proddy
9b3b7fc8ff Support upgrade/downgrade between versions #832 2022-12-25 16:47:17 +01:00
Proddy
8000497302 check for fresh install 2022-12-25 13:21:21 +01:00
Proddy
84589a4b40 add version to settings file 2022-12-25 13:08:59 +01:00
Proddy
067129f5a9 banner text changes 2022-12-25 13:08:17 +01:00
Proddy
ffcf98b06b move defines for board types 2022-12-25 13:08:01 +01:00
Proddy
b2f001e416 minor text change 2022-12-25 13:06:51 +01:00
Proddy
44c31c9c61 3.5.0-dev.14 2022-12-25 13:05:47 +01:00
Proddy
8aa659eee2 c++17 2022-12-25 13:05:31 +01:00
Proddy
51bf163149 change version name 2022-12-25 13:05:02 +01:00
Proddy
d5c02f6b94 added 2022-12-25 13:04:52 +01:00
Proddy
55fa968afb remove unneeded variables 2022-12-25 13:04:41 +01:00
Proddy
a9aac33a46 rename lib 2022-12-25 13:03:32 +01:00
Proddy
1f45506b37 add semver lib 2022-12-25 13:03:09 +01:00
Proddy
9d9d88b171 compile with limited set of tests (flash mem overload) 2022-12-25 13:02:54 +01:00
Proddy
291da3f7fe Merge pull request #836 from MichaelDvP/dev
fixes and enhancements #826, #835, #820
2022-12-24 17:03:01 +01:00
MichaelDvP
70cfbc3715 cleanup and formatting 2022-12-24 15:46:04 +01:00
MichaelDvP
9d80c2cea7 add generic controller without product id #835 2022-12-24 15:45:11 +01:00
MichaelDvP
a7f7959f91 typo 2022-12-24 12:21:23 +01:00
MichaelDvP
082268d9fc Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev 2022-12-24 12:15:49 +01:00
MichaelDvP
945ef2f1b0 rename setting to "max buffer size", show used buffer in info 2022-12-24 12:06:27 +01:00
MichaelDvP
1f1feed3ae fix limit weblog buffer 2022-12-24 09:54:05 +01:00
Proddy
290890f6e7 Merge pull request #830 from proddy/dev
#822 and #829
2022-12-24 08:09:44 +01:00
proddy
b7bfd803eb kB back to KB, L back to l 2022-12-24 08:09:11 +01:00
Proddy
2151905d46 test upgrade 2022-12-23 18:28:09 +01:00
MichaelDvP
fdde118af1 buffer back to 20k 2022-12-23 15:24:23 +01:00
Proddy
5f9ba2e04b add TODO for missing translations 2022-12-23 14:58:15 +01:00
Proddy
ca871f654b Remove the test.mosquitto.org URL in the MQTT settings #829 2022-12-23 14:40:24 +01:00
Proddy
3c91ac27dc Set device_class to duration in HA sensor definitions #822 2022-12-23 13:42:59 +01:00
MichaelDvP
94b4cb0baf Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev 2022-12-23 12:02:26 +01:00
MichaelDvP
1b956c6ad7 check log_buffer before expanding 2022-12-23 11:11:40 +01:00
MichaelDvP
b9b79bbd9a webbuffer 12k to reduce http:507 messages 2022-12-23 10:59:41 +01:00
Proddy
9802581301 Merge pull request #827 from pswid/dev
Polish translation update
2022-12-23 08:01:13 +01:00
MichaelDvP
c1f39fbf57 Dashboard/Customization Buffer 16k, measure and and log size. 2022-12-22 21:27:59 +01:00
MichaelDvP
f66e7712c3 fix crashes, clean up, update packages 2022-12-22 19:12:39 +01:00
pswid
2bb6d985cc Polish translation update 2022-12-22 14:59:53 +01:00
MichaelDvP
d300ed38ea readonly check with device_id 2022-12-22 14:25:20 +01:00
MichaelDvP
9cbb810fe4 add device_id to commands, fix #826 2022-12-22 14:01:55 +01:00
MichaelDvP
cfa486d8cc Add mixing module MM300, product-id 193 2022-12-22 09:48:52 +01:00
MichaelDvP
ab1924d266 fix #820, 4byte-values 2022-12-22 09:02:05 +01:00
Proddy
d6de0f6fa8 Merge pull request #825 from pswid/dev
added "compressor temp." for more hp types
2022-12-21 09:27:31 +01:00
pswid
d1afea104e added dhw eco+ switch off temp
tested on the Buderus WSW196iT
2022-12-21 09:19:33 +01:00
pswid
fae8cf83cd Merge branch 'emsesp:dev' into dev 2022-12-20 19:11:07 +01:00
pswid
5a69ac074f added "compressor temp." for more hp types
tested with Buderus WSW196iT
2022-12-20 12:01:41 +01:00
Proddy
15f0560005 Merge pull request #819 from MichaelDvP/dev
Some more entities and small fixes
2022-12-20 10:23:09 +01:00
MichaelDvP
6f7fa6abd9 fix #820, NOTSET check for 4 byte values. 2022-12-20 08:26:00 +01:00
MichaelDvP
cbd55b0366 uom "Kelvin" to HA temperature 2022-12-19 13:31:31 +01:00
MichaelDvP
f1dbd3018d dhw comf/eco switch off temps #803 2022-12-19 13:23:32 +01:00
MichaelDvP
502613b433 add uom K, heatpump difftemp #803 2022-12-19 09:14:24 +01:00
Proddy
2bdc0d59cf Merge pull request #818 from MichaelDvP/dev
add step to numeric command-values in HA discovery
2022-12-18 18:16:36 +01:00
MichaelDvP
ce0ee49ebf add step to numeric command-values in HA discovery 2022-12-18 16:22:18 +01:00
Proddy
bc3bdb1221 Merge pull request #817 from MichaelDvP/dev
Next fixes for #803
2022-12-18 15:20:04 +01:00
MichaelDvP
3bea7576b5 FR translations from manual #803 2022-12-18 11:49:55 +01:00
MichaelDvP
d70b5d7dc0 Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev 2022-12-18 11:11:40 +01:00
MichaelDvP
eb30c1e5c7 fixes, nl translation #803 2022-12-18 11:11:21 +01:00
Proddy
17cbd2623f fix another silly mistake 2022-12-17 21:17:57 +01:00
Proddy
dc27a44c0c fix typo 2022-12-17 20:50:29 +01:00
Proddy
f72b02ab0b Merge pull request #815 from MichaelDvP/dev
Additions for #802, #803
2022-12-17 20:42:05 +01:00
Proddy
c0946f1e0c Merge branch 'dev' into dev 2022-12-17 20:41:56 +01:00
Proddy
575fdcb8cd Merge pull request #816 from proddy/dev
support 3 types of MQTT discovery entity id - #804
2022-12-17 19:21:36 +01:00
proddy
783ea7901c support 3 types of MQTT discovery entity id 2022-12-17 19:20:31 +01:00
MichaelDvP
44e6bb79a2 add auxHeaterStatus 2022-12-17 16:32:21 +01:00
MichaelDvP
ab3b9f13b5 Sort languages alphabetical 2022-12-17 13:01:50 +01:00
MichaelDvP
dc6e7f7b1b changelog 2022-12-17 12:05:29 +01:00
MichaelDvP
94b75dda24 Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev 2022-12-17 11:14:53 +01:00
MichaelDvP
a529879889 some aux heater trnlations 2022-12-17 11:10:23 +01:00
Proddy
4b7bbb3d50 minor cleanup and testing 2022-12-17 11:05:20 +01:00
Proddy
88c98efd94 Merge pull request #808 from proddy/dev
add v3.4 entity id flag to MQTT settings
2022-12-17 11:04:42 +01:00
Proddy
147be12583 fixes for #804 2022-12-16 22:57:59 +01:00
MichaelDvP
137e047205 auxheaterdelay and hyst #803 2022-12-16 15:11:56 +01:00
MichaelDvP
4bd6db31c0 Merge branch 'dev' of https://github.com/proddy/EMS-ESP32 into dev 2022-12-16 11:10:58 +01:00
MichaelDvP
1350638fb3 rename auxheater 2022-12-16 11:10:37 +01:00
Proddy
b6de431a56 add French MQTT entity format strings (in EN) 2022-12-15 23:19:53 +01:00
Proddy
1aef27da33 Merge branch 'emsesp:dev' into dev 2022-12-15 23:14:20 +01:00
Proddy
1e78979ed0 default multiple_instances is enabled 2022-12-15 23:14:44 +01:00
Proddy
ba90ebda4c merge multiple_instances with entity_format 2022-12-15 21:51:11 +01:00
MichaelDvP
7c1bade54d add auxilliary heater setting #803 2022-12-15 13:22:54 +01:00
MichaelDvP
a46b394714 Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev 2022-12-15 10:21:22 +01:00
Proddy
6dff1136c5 Merge pull request #807 from PopaSchKing/dev
Add FR language to EMS-ESP
2022-12-15 09:45:25 +01:00
Proddy
ccbb56d403 add v3.4 entity id flag to MQTT settings 2022-12-14 22:07:42 +01:00
PopaSchKing
4f3a7e5451 Fix missing elements from merge with off. dev branch 2022-12-14 19:06:02 +01:00
MichaelDvP
e4cda4087e Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev 2022-12-14 18:52:37 +01:00
MichaelDvP
6eeb8de02c fix #806 2022-12-14 18:46:29 +01:00
PopaSchKing
29c4fec90a Update changelog 2022-12-14 17:48:59 +01:00
MichaelDvP
5c9ba8de43 fetch heatpump telegrams 2022-12-14 17:27:45 +01:00
PopaSchKing
2358f6a9c9 Merge branch 'emsesp:dev' into dev 2022-12-14 17:16:13 +01:00
PopaSchKing
7097279dca Fix wrong language name 2022-12-14 16:02:09 +01:00
MichaelDvP
c3eb553425 UOMs 2022-12-14 15:53:40 +01:00
PopaSchKing
24f64fac6b Update FR 2022-12-14 15:39:23 +01:00
MichaelDvP
4cdd5e9f20 add values/settings #803 2022-12-14 14:20:32 +01:00
MichaelDvP
de9e261807 Test for #802 2022-12-13 11:43:37 +01:00
Proddy
c26793c68d Merge pull request #801 from MichaelDvP/dev
delete empty response
2022-12-12 21:47:40 +01:00
MichaelDvP
11fd833cdb delete empty response 2022-12-11 11:13:56 +01:00
Proddy
2b21a0c31f Merge pull request #800 from MichaelDvP/dev
Avoid blank page (NULL) as response
2022-12-10 20:23:07 +01:00
MichaelDvP
7e888f6408 Avoid blank page (NULL) as response 2022-12-10 18:14:10 +01:00
Proddy
bba70ce852 replace HA ~ with hardcoded base name 2022-12-10 11:58:32 +01:00
Proddy
e96b5af0c8 HA ems_esp status to system_status to be uniform with the other naming conventions 2022-12-10 11:58:00 +01:00
Proddy
5f5e786c0e added comments 2022-12-10 11:57:17 +01:00
Proddy
8d66e43117 Merge pull request #798 from proddy/dev
minor updates and renaming system status entity in HA
2022-12-10 11:57:03 +01:00
Proddy
ccbc809ecf upgrade packages 2022-12-10 11:56:39 +01:00
Proddy
e67fde24f1 formatting 2022-12-10 11:56:28 +01:00
Proddy
923494fdce mention breaking change in HA entity names 2022-12-10 11:55:59 +01:00
MichaelDvP
73bff2cabe Merge pull request #797 from MichaelDvP/dev
Some actual additions/changes, see description
2022-12-09 10:11:40 +01:00
PopaSchKing
c20fc5a345 Update FR translations 2022-12-08 19:28:38 +01:00
MichaelDvP
f71c62f167 restart when changing CORS settings 2022-12-08 16:54:27 +01:00
SchKing
bdaf9e6dc6 Add FR language 2022-12-07 22:26:09 +01:00
MichaelDvP
a2730fb17c extra debug message for payload only ifdef EMSESP_DEBUG
Signed-off-by: MichaelDvP <github@mdvp.de>
2022-12-07 19:13:43 +01:00
MichaelDvP
5061ddf38e add CORS option to network 2022-12-07 13:51:53 +01:00
MichaelDvP
1735c036cc move multiple instances setting to HA section 2022-12-07 13:10:31 +01:00
MichaelDvP
fa703db41e heartbeat time in standalone 2022-12-07 12:53:35 +01:00
MichaelDvP
9665f4136a Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev 2022-12-07 07:41:25 +01:00
Proddy
abeef2be4a Merge pull request #793 from proddy/dev
option to enable multiple instances using a flag
2022-12-06 17:36:37 +01:00
Proddy
995420354e remove breaking change note about MQTT discovery 2022-12-06 11:15:14 +01:00
proddy
1af1a1863a #759 add option to enable multiple instances to keep backward compatibllity with v3.4 2022-12-05 21:33:10 +01:00
proddy
fd04f8be5a add multiple_sessions 2022-12-05 20:37:39 +01:00
Proddy
ccc9e6dcfb Merge pull request #791 from MichaelDvP/dev
small changes for #759
2022-12-05 20:34:20 +01:00
MichaelDvP
9e23710c6d add heartbeat interval to mqtt settings 2022-12-05 19:27:56 +01:00
MichaelDvP
3878a3ee0b analogsensor float->double #789 2022-12-05 18:26:18 +01:00
MichaelDvP
0c9d0a4d15 show mqtt payload in debug log 2022-12-05 14:19:41 +01:00
MichaelDvP
739c007c95 sync sensors and status to same entitiy in path, uniq_id, objdect_id 2022-12-05 14:13:25 +01:00
MichaelDvP
6f27253441 log calling command in debug level 2022-12-05 14:10:05 +01:00
MichaelDvP
b900932574 hardcode maxTopicLength to settingfunction 2022-12-05 14:09:25 +01:00
Proddy
0903b31fcf highlight breaking changes 2022-12-05 10:06:50 +01:00
Proddy
d080f5db0f fix dutch NUM_MINUTES #787 2022-12-04 15:52:31 +01:00
Proddy
5029a1625e remove TODO 2022-12-04 15:51:56 +01:00
Proddy
2950f167aa Merge pull request #780 from MichaelDvP/dev
allow raw telegram sending with other src-id
2022-12-04 13:01:16 +01:00
Proddy
afdf9a3dfb Merge pull request #781 from proddy/dev
#751 and #759
2022-12-04 13:00:49 +01:00
Proddy
132ca9d106 fix mqtt topic length which broke PR #781 2022-12-04 12:45:23 +01:00
Proddy
990d75d42a debug comments 2022-12-04 12:44:57 +01:00
Proddy
1c48aa8444 fixes #787 2022-12-04 12:44:44 +01:00
Proddy
67e07813cb adding missing translations for Value (single/plural) 2022-12-04 10:33:13 +01:00
Proddy
7fbeed88a9 package updates 2022-12-04 10:32:50 +01:00
Proddy
9053e7ac88 minor renaming 2022-12-04 10:32:37 +01:00
Proddy
e3c94cc1f7 updates for #759 2022-11-30 21:21:32 +01:00
Proddy
e8d6c4d451 add new ReturnCmd error called INVALID 2022-11-30 21:21:19 +01:00
Proddy
ba1813c767 removed en_name 2022-11-30 21:21:03 +01:00
Proddy
bcd8992abc added INVALID command return err, so detects when incorrect hc given in a command 2022-11-30 21:20:47 +01:00
Proddy
557b532f74 added comment 2022-11-30 21:20:08 +01:00
Proddy
d450464a1a hardcode "response" topic 2022-11-30 21:19:57 +01:00
Proddy
5d10b28433 rename boot time, free mem, free app, add web buffer to info command output 2022-11-30 21:19:21 +01:00
Proddy
eafe358deb shower uses mqtt base - #759 2022-11-30 21:18:15 +01:00
Proddy
048bd877da fix tests for MQTT 2022-11-30 21:17:55 +01:00
Proddy
7f18dd942f add mqtt base to topic - #759 2022-11-30 21:17:41 +01:00
Proddy
424234ec48 default send_response is false for standalone testing 2022-11-30 21:16:57 +01:00
Proddy
4168a08276 #751 move ntp sending info topic back 2022-11-30 21:16:41 +01:00
MichaelDvP
996063d76d show received (ignored) handlers for devices without registered telegrams. 2022-11-30 16:06:06 +01:00
MichaelDvP
085f5ff22f change GH tagged_release to web language 'en' #754 2022-11-30 14:00:19 +01:00
Proddy
5b5dc6a8cc #759 use b ase in HA unique_id and object_id 2022-11-29 21:15:46 +01:00
Proddy
4103bad8de bump b11 2022-11-29 21:14:40 +01:00
Proddy
d6a8563cc7 fixes for #751 2022-11-29 21:14:31 +01:00
Proddy
c9ef0bcd7b remove max_topic_length, make base mandatory 2022-11-29 21:14:08 +01:00
Proddy
6b978759ca update 2022-11-29 21:13:26 +01:00
Proddy
bc31d54028 package update 2022-11-29 21:13:15 +01:00
MichaelDvP
7bad0e04b1 allow raw telegram sending with other src-id 2022-11-29 17:04:05 +01:00
Proddy
7144c746c6 Merge pull request #779 from proddy/dev
#751 boot time when NTP connected
2022-11-28 21:19:18 +01:00
proddy
d5592e5662 #751 boot time when NTP connected 2022-11-28 21:14:38 +01:00
Proddy
12b027110e Merge pull request #778 from MichaelDvP/dev
Basetranslation, HA-mode fix, analog inputs, etc.
2022-11-28 09:56:34 +01:00
MichaelDvP
644896faff Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev 2022-11-28 09:37:55 +01:00
MichaelDvP
f41f7c0769 analog input fields, package update 2022-11-28 08:21:02 +01:00
Proddy
68f1a891ca Merge pull request #776 from proddy/dev
fix codacy complaining
2022-11-27 20:44:04 +01:00
proddy
b186311968 fix 2022-11-27 20:36:37 +01:00
Proddy
c21b594cf0 Merge pull request #775 from proddy/dev
minor standalone fixes
2022-11-27 20:22:56 +01:00
proddy
21ec46843a add comments 2022-11-27 20:22:25 +01:00
proddy
4da827501a update packages 2022-11-27 20:22:20 +01:00
proddy
5d0c7bfd32 fix standalone crash 2022-11-27 20:22:05 +01:00
MichaelDvP
d583409af4 set mode for ha climate, fix #772 2022-11-27 19:02:54 +01:00
MichaelDvP
559e607601 show eth parameters for eth connection (if eth/wifi connected) 2022-11-27 19:01:44 +01:00
MichaelDvP
276b27d8a6 add Boiler WLW196i, #767 2022-11-24 18:40:17 +01:00
MichaelDvP
2134f42cfd add phy-type to support info 2022-11-24 18:39:35 +01:00
MichaelDvP
88135d2750 workaround for default language en and basetranslation pl 2022-11-23 11:09:46 +01:00
MichaelDvP
1b1a4bcad4 mention boiler GC9800IW in device-library 2022-11-23 11:08:48 +01:00
Proddy
af8e82a860 Merge pull request #764 from MichaelDvP/dev
Command `values` for analog/dallas sensors, writeable for analog-out,…
2022-11-22 22:12:47 +01:00
MichaelDvP
c8d5e37b44 add thermostats UI800/RT800, #765 2022-11-22 18:32:38 +01:00
MichaelDvP
781fe03b5d Command values for analog/dallas sensors, writeable for analog-out, #758 2022-11-21 18:13:52 +01:00
proddy
702103aa66 formatting 2022-11-20 21:11:28 +01:00
proddy
d2503431c6 fix merge chaos 2022-11-20 21:11:24 +01:00
Proddy
dccdf18226 Merge pull request #761 from proddy/dev
use HA connectivity device class #751
2022-11-20 20:55:35 +01:00
Proddy
a92f287256 Merge branch 'dev' into dev 2022-11-20 20:55:24 +01:00
Proddy
a193d67f11 Merge pull request #755 from MichaelDvP/dev
add devices Alert/Pump, UOMs
2022-11-20 20:53:22 +01:00
proddy
6873e113bb Change EMS-ESP device sensor types in Home Assistant #751 2022-11-20 18:48:59 +01:00
proddy
77860d9d05 fix standalone compiling 2022-11-20 18:48:51 +01:00
proddy
de97010cfb package update 2022-11-20 18:48:36 +01:00
MichaelDvP
89245c7af7 translating UOMs hours/minutes/seconds 2022-11-20 10:44:17 +01:00
MichaelDvP
d51745774f add missing DeviceValueUOM_s 2022-11-19 14:55:04 +01:00
MichaelDvP
deda1daa04 add devices alert/Pump #575, #720, UOM:translate times? #752 2022-11-19 12:11:30 +01:00
MichaelDvP
8594c4a7eb restart animation on factory reset 2022-11-19 12:07:38 +01:00
MichaelDvP
d0d73aa5e2 boiler/thermostat alias commands inline 2022-11-19 12:06:38 +01:00
Proddy
1f43bb201c Merge pull request #753 from MichaelDvP/dev
fixes #747, #748
2022-11-18 10:19:57 +01:00
MichaelDvP
6e92d31f5d limit weblog buffer on low heap, use deque 2022-11-17 18:12:40 +01:00
MichaelDvP
ae4070b7f2 package update 2022-11-17 16:17:47 +01:00
MichaelDvP
ee2ac18b69 Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev 2022-11-17 08:38:47 +01:00
MichaelDvP
85ce697dad hp defrost state 6, state 5 unknown 2022-11-17 08:37:55 +01:00
MichaelDvP
82eb79ce40 fix factory partition detection 2022-11-17 08:37:12 +01:00
Proddy
c76b4b9ede Merge pull request #746 from pswid/dev
completed translations of entities into Polish.
2022-11-16 16:47:35 +01:00
MichaelDvP
9c2e814e16 mention Greenstar ErP in database 2022-11-16 07:51:17 +01:00
MichaelDvP
2e9499ea90 typo 2022-11-15 19:25:41 +01:00
pswid
52296bfed1 redundant cwu removed (in Polish translation) 2022-11-15 13:52:34 +01:00
MichaelDvP
a53d54a1d6 don't use hpoperatingmode on boilers, fix #747 2022-11-15 09:32:39 +01:00
MichaelDvP
81cf08b723 add word heatpump mode "defrost" (instead of nofrost) 2022-11-15 09:31:45 +01:00
MichaelDvP
f192d7dffc add nofrost option to hpactivity, #748 2022-11-15 08:25:41 +01:00
pswid
5425896988 Merge branch 'dev' of https://github.com/pswid/EMS-ESP32 into dev 2022-11-14 15:14:51 +01:00
pswid
9e2be00b5c completed translations of entities into Polish.
this also fixes some Norwegian translations that were incorrectly displayed as Polish.
Missing translations (for other languages) have been entered as "", so default English names should be displayed
2022-11-14 15:06:13 +01:00
Proddy
06004ce478 Merge pull request #745 from MichaelDvP/dev
EN-Tags for HA-entities
2022-11-14 12:50:10 +01:00
MichaelDvP
5bda018d25 En-tag for HA-entity 2022-11-14 12:22:12 +01:00
MichaelDvP
b12729a874 HM200 temperatures, fix hybridStrategie #500 2022-11-14 10:03:12 +01:00
Proddy
d3a84e02e4 Merge pull request #743 from MichaelDvP/dev
Typos, selflowtemp check
2022-11-13 19:56:32 +01:00
MichaelDvP
9865c84df5 tag in ha object_id 2022-11-13 19:09:32 +01:00
MichaelDvP
33d2cb9a49 Typos 2022-11-13 18:43:36 +01:00
MichaelDvP
37c121e8de check selflowtemp change before write 2022-11-13 18:43:24 +01:00
Proddy
9dd0bf01e2 formatting 2022-11-13 14:35:04 +01:00
Proddy
92ac601072 Merge pull request #733 from pswid/dev
fix: cosmetic webUI improvements
2022-11-13 14:17:59 +01:00
pswid
dfd7647838 improved webUI display on narrow screens
- unified vertical spacing (settings page)
- auto switching from 3 to 2 boxes displayed in row on narrow screens
- displaying (at least a part off) names of entities names on dashboard
- fixes MQTT discovery field too small
2022-11-13 13:46:52 +01:00
pswid
058246e2ce Merge branch 'emsesp:dev' into dev 2022-11-13 13:00:41 +01:00
Proddy
8ed789892b Merge pull request #741 from proddy/dev
minor updates, also implements #707
2022-11-10 16:12:21 -05:00
Proddy
57775af24b lower case days, hours, minutes, seconds 2022-11-10 22:12:10 +01:00
Proddy
845681b6dc package update 2022-11-10 22:11:54 +01:00
pswid
ea2c9cbc9a Merge branch 'emsesp:dev' into dev 2022-11-10 15:03:57 +01:00
Proddy
96bb3013a3 Merge pull request #740 from MichaelDvP/dev
fix render_value float, leading zeros to decimals
2022-11-10 06:36:37 -05:00
MichaelDvP
aeee37fdae fix render_value float, leading zeros to decimals 2022-11-10 12:17:05 +01:00
Proddy
551497bfeb Merge pull request #739 from MichaelDvP/dev
fix #736
2022-11-09 14:04:21 -05:00
MichaelDvP
57057cac0e fix #736 2022-11-09 15:08:07 +01:00
pswid
dccd9f09e7 Merge branch 'emsesp:dev' into dev 2022-11-08 15:21:39 +01:00
Proddy
b91e474343 Merge pull request #734 from MichaelDvP/dev
fix #727 boiler wwcircmode
2022-11-07 16:47:15 +01:00
pswid
f25262c191 fix: cosmetic webUI improvements
mainly in custom board profile and analog sensors settings. Now it looks more consistent with other settings pages.
2022-11-07 10:41:57 +01:00
MichaelDvP
723662a6bc fix #727 boiler wwcircmode 2022-11-07 10:10:38 +01:00
Proddy
757fcd3ea4 Merge pull request #730 from proddy/dev
fix sensor writing
2022-11-06 18:04:08 +01:00
Proddy
ec3e28436d fix sensor writing 2022-11-06 18:04:30 +01:00
Proddy
6a291ef1e2 Merge pull request #726 from proddy/dev
add missing elint supression to fix build
2022-11-05 18:34:08 +01:00
proddy
1e529a9e19 add missing elint supression to fix build 2022-11-05 18:33:46 +01:00
Proddy
05d393e7fe Merge pull request #725 from proddy/dev
minor fixes for standalone
2022-11-05 18:27:32 +01:00
proddy
c757ace2b4 fix standalone compiling & sonar 2022-11-05 18:26:59 +01:00
proddy
5ab066de5f bump b9 2022-11-05 18:26:48 +01:00
Proddy
b0ae22d493 Merge pull request #724 from proddy/dev
build web i18n on github actions
2022-11-05 18:02:37 +01:00
proddy
03a6abcfc6 build web i18n on github actions 2022-11-05 17:59:31 +01:00
Proddy
e58491ed97 Merge pull request #722 from MichaelDvP/dev
Changes in discussion
2022-11-05 17:52:15 +01:00
MichaelDvP
1e92ae05c0 add GB135 second burner stage 2022-11-05 15:58:55 +01:00
MichaelDvP
513b6181a4 add norwegian entity translations from vikingn 2022-11-05 15:57:57 +01:00
MichaelDvP
05b54bc6f5 "No telegram handler" message only for broadcasted messages 2022-11-05 15:56:29 +01:00
MichaelDvP
1d4634a76c add restart to factory partition (if there is one) 2022-11-05 15:56:08 +01:00
MichaelDvP
342cf12ae7 boardprofile fallback to E32/S32 2022-11-05 15:55:03 +01:00
Proddy
6883dbbce1 Merge pull request #723 from proddy/dev
fix to base translations, auto generate i18n
2022-11-05 14:00:08 +01:00
Proddy
a3d3706b89 generate i18n on build 2022-11-05 12:54:28 +01:00
Proddy
da911e374a auto-formatting 2022-11-05 12:29:28 +01:00
Proddy
26904f4e0e default polish so translations don't break #695 2022-11-05 12:26:07 +01:00
Proddy
ec5601f3ca formatting, added Sensor, removed {{ }} for non Polish 2022-11-05 12:25:48 +01:00
Proddy
a2ee2a5e6b remove custom formatter 2022-11-05 12:25:14 +01:00
Proddy
78d5f8b76d ignore i18n generated files 2022-11-05 12:25:02 +01:00
Proddy
dd0cc00004 updated packages again 2022-11-05 12:24:49 +01:00
Proddy
825836c447 Merge pull request #721 from proddy/dev
formatting, updated packages, fix dialog focus on customization screen
2022-11-03 22:30:43 +01:00
Proddy
4996eb9e3c formatting, updated packages 2022-11-03 22:30:10 +01:00
Proddy
0bc3a3c34e Merge pull request #719 from pswid/dev
fix to the translation of the webUI in Polish.
2022-11-03 20:35:08 +01:00
pswid
ca5bc313ee fixes Polish chars not being displayed properly
The characters "Ę" and "ę" had wrong uni-codes in re.woff2. Now, the problem https://github.com/emsesp/EMS-ESP32/pull/702#issuecomment-1291720910 is finally solved.
2022-11-03 13:04:25 +01:00
pswid
069df92dbf fix to the translation of the webUI in Polish.
Everything visible should already be translated into Polish. However, I was not able to check how the translations of all errors are displayed (because how to display them if there is no error?), so I'm not sure that they are ok.
BTW, there are still many error messages hardcoded (e.g. "Local IP is required"). I think we should translate them as well.
2022-11-03 12:34:32 +01:00
Proddy
bdce4ee9f9 Merge pull request #718 from MichaelDvP/dev
fix #717, rounding error
2022-11-01 18:09:31 +01:00
MichaelDvP
756a136124 fix overflow rendering uint32 2022-11-01 17:38:33 +01:00
MichaelDvP
4809ef3537 fix #717, rounding error 2022-11-01 17:24:10 +01:00
Proddy
2c056d6807 Merge pull request #715 from proddy/dev
minor changes
2022-10-31 20:56:54 +01:00
Proddy
90af466a2f rename Status to Info for info command 2022-10-31 20:56:33 +01:00
Proddy
20cbbcd9f4 fix VALUE() where it needs single and plural translations 2022-10-31 20:26:49 +01:00
Proddy
036953044e auto-formatting 2022-10-31 20:26:30 +01:00
Proddy
499456fa77 add comment 2022-10-31 20:25:03 +01:00
Proddy
22d9705412 package update 2022-10-31 20:24:40 +01:00
Proddy
663c853aff add test for Junkers room sensor issue in HA 2022-10-31 20:24:28 +01:00
Proddy
103ffa4761 fix compiling standalone 2022-10-31 20:24:12 +01:00
Proddy
3baedf01d1 Merge pull request #713 from MichaelDvP/dev
min/max, bus-ids, MD5
2022-10-31 19:28:54 +01:00
MichaelDvP
bfad6d34b5 i18n baseLocale back to en, 18 pl-chars to font. 2022-10-31 14:21:13 +01:00
MichaelDvP
2aa2564078 version 3.5.0b8, Changelog, remove some F() 2022-10-31 11:11:50 +01:00
MichaelDvP
6561bb5a6c add min/max to customization table #686 2022-10-31 11:11:09 +01:00
MichaelDvP
ac75176292 i18n@5.15.0 2022-10-31 11:10:20 +01:00
MichaelDvP
1005079f71 activate restart monitor on button restart 2022-10-31 11:09:12 +01:00
MichaelDvP
e0e07a9deb md5 check for upload bin files #637 2022-10-31 11:08:05 +01:00
MichaelDvP
c65005e5a6 add more bus-ids #673 2022-10-31 11:04:44 +01:00
MichaelDvP
64f2f82e0c low frequency for C3 chip 2022-10-31 11:03:10 +01:00
MichaelDvP
98495c8114 show wifi disconnect reason as text 2022-10-31 11:02:43 +01:00
Proddy
d0ac0b7804 update with version on dev 2022-10-30 16:58:37 +01:00
Proddy
e45c31345e Merge pull request #712 from proddy/dev
auto-formatting web code
2022-10-30 16:56:48 +01:00
Proddy
23cd677133 Merge branch 'emsesp:dev' into dev 2022-10-30 16:56:13 +01:00
Proddy
805c1298fb fix lint formatting so GH action build doesn't break 2022-10-30 16:56:00 +01:00
Proddy
5199edff1e Merge pull request #711 from proddy/dev
minor updates
2022-10-30 16:47:08 +01:00
Proddy
f3adc13c6d auto-formatting 2022-10-30 16:44:31 +01:00
Proddy
40f5f7026d removed TODO 2022-10-30 16:44:23 +01:00
Proddy
b8b96763cf update packages 2022-10-30 16:44:14 +01:00
Proddy
80b94fcd00 update to latest GH actions 2022-10-30 16:44:06 +01:00
Proddy
8d09d3c654 use 2 threads 2022-10-30 16:43:55 +01:00
Proddy
03564b3a82 Merge pull request #708 from mvjt/dev
Translated new index.ts items to Swedish
2022-10-30 16:14:13 +01:00
mvjt
a7569256d0 Corrected error (SE) #2 2022-10-30 15:57:26 +01:00
mvjt
b0d4a094c1 Corrected error (SE) 2022-10-30 15:56:11 +01:00
mvjt
1b232adc72 Translated new items 2022-10-30 15:48:06 +01:00
Proddy
ed81e095ee Merge pull request #702 from pswid/dev
The translation of the web interface into Polish has been corrected and completed (e.g. the word order has been fixed).
2022-10-29 11:03:24 +02:00
Proddy
76734b77f1 Merge pull request #706 from MichaelDvP/dev
fixes #704 and #698
2022-10-29 10:56:13 +02:00
Proddy
d8284ec09f Merge pull request #705 from MichaelDvP/main
v3.4.4 Fix for new installations with filesystem not initializing
2022-10-29 10:46:41 +02:00
MichaelDvP
6e982acde8 v3.4.4 Fix for new installations with filesystem not initializing 2022-10-28 10:50:51 +02:00
MichaelDvP
db6f8eaba1 add boiler heatStarts 2022-10-27 18:50:38 +02:00
MichaelDvP
eb7ad7163f fix #698, add RC30 internal sensors 2022-10-27 18:40:51 +02:00
MichaelDvP
6a478eec5e fix #704 round div100 temperatures to one decimal 2022-10-27 18:37:08 +02:00
pswid
1dc08d6399 The translation of the web interface into Polish has been corrected and completed (e.g. the word order has been fixed).
Newly added words should be translated from English into other languages in index.ts files
2022-10-25 20:57:51 +02:00
Proddy
c3cfed5ac3 Merge pull request #700 from proddy/dev
package update
2022-10-24 13:30:57 -07:00
Proddy
aba1dc93d9 package update 2022-10-24 22:30:54 +02:00
Proddy
d9bbf35afc Merge pull request #693 from pswid/dev
Polish translation improvements
2022-10-24 13:29:11 -07:00
pswid
dec3abfab9 Merge branch 'emsesp:dev' into dev 2022-10-24 11:22:24 +02:00
pswid
bb98f13b19 alternative word translations implementation idea
just for demonstration!
2022-10-18 13:47:10 +02:00
Proddy
8b10970e03 Merge pull request #696 from MichaelDvP/dev 2022-10-18 10:09:48 +02:00
MichaelDvP
b859ab9d78 fix switch commands #692 2022-10-18 09:17:45 +02:00
Proddy
26208103fc Merge pull request #694 from MichaelDvP/dev
DIV10 for solarTemp
2022-10-17 19:07:25 +02:00
MichaelDvP
d150b017e3 DIV10 for solarTemp 2022-10-17 18:56:54 +02:00
pswid
49414adfd2 Polish translation improvements
Strange, but if i use capital letters in Polish translation for "ON' and "OFF" (local_translations.h), I'm getting compile error. So instead of whole words in capital letters I used abbreviations in lower case (more useful, IMHO)
2022-10-17 18:21:57 +02:00
Proddy
5c675c7ce7 Merge pull request #690 from MichaelDvP/dev
solar temperature sensor and some fixes
2022-10-17 15:00:47 +02:00
MichaelDvP
f16aaf7874 ignore mqtt echos if publish_single2command is set. 2022-10-17 10:14:06 +02:00
MichaelDvP
d80831e708 fix possible endless tx repeats on write rejected 2022-10-17 10:12:34 +02:00
MichaelDvP
3e3e7156ec fix setting custom min value 2022-10-17 10:11:18 +02:00
MichaelDvP
dbb2a365cb solar temperature #687 2022-10-17 10:10:42 +02:00
Proddy
52a8c7288d Merge pull request #685 from MichaelDvP/dev
small fixes
2022-10-14 16:52:37 +02:00
MichaelDvP
42f6bf6182 fix rx quality calculation 2022-10-14 14:28:08 +02:00
MichaelDvP
9d4d3738ff fix memory leak 2022-10-14 14:27:50 +02:00
MichaelDvP
1647a6d0a7 Typo 2022-10-14 14:27:26 +02:00
Proddy
e5e058672d Merge pull request #683 from pswid/dev
Added Polish names of the entities
2022-10-14 12:08:13 +02:00
pswid
f5cd8e2523 Merge branch 'dev' into dev 2022-10-14 11:02:31 +02:00
Proddy
fd2cc8aff1 Merge pull request #684 from proddy/dev
update standalone build
2022-10-13 22:07:49 +02:00
proddy
f244fb837b update system test data 2022-10-13 22:07:01 +02:00
proddy
b4848ab7f9 make standalone compile 2022-10-13 21:56:04 +02:00
proddy
0fa60f0502 package updates 2022-10-13 21:55:49 +02:00
Proddy
8d290317d7 Merge pull request #677 from MichaelDvP/dev_
C3/S2 support, tags translated
2022-10-13 21:40:14 +02:00
pswid
cb5c8f3a85 Merge branch 'dev' into dev 2022-10-13 20:02:28 +02:00
pswid
5a0f4c1462 The names of the entities have been translated into Polish
not 100% complete
2022-10-13 19:43:03 +02:00
MichaelDvP
e4445413fd fix console show values #682 2022-10-13 14:08:53 +02:00
MichaelDvP
9be13eb0b9 Lolin C3 mini v1.0 has standard led. 2022-10-13 14:07:52 +02:00
MichaelDvP
2c7eeeca7b C3 led color, red blinking error, green permanent ok 2022-10-11 18:43:32 +02:00
Proddy
b59a552288 Merge pull request #680 from pswid/dev
Polish translation improvements
2022-10-11 18:41:15 +02:00
Proddy
def67899ed Merge pull request #679 from tefracky/tefracky-german-translation
Updated German translation
2022-10-11 18:40:41 +02:00
MichaelDvP
ea0870c180 Check upload bin-header for esp-chip version 2022-10-11 18:32:33 +02:00
pswid
379d57ca8e Merge branch 'dev' into dev 2022-10-11 14:10:30 +02:00
MichaelDvP
761df2b4cc esp32C3 wifi initializaton: set power after starting wifi 2022-10-11 14:05:29 +02:00
tefracky
56e1f02f69 Update locale_translations.h
Reverted to "Solltemperatur Einmalladung"
2022-10-11 13:59:54 +02:00
pswid
f921ef4708 Polish translation improvements 2022-10-11 13:56:19 +02:00
MichaelDvP
f11dc9bc25 links to lolin C3 mini V1, pio-ini 2022-10-11 10:11:21 +02:00
MichaelDvP
0ebfcd7238 api json charset 2022-10-11 09:35:56 +02:00
MichaelDvP
5fb9cd142f thermostat switchtime sday as char * 2022-10-11 09:35:36 +02:00
MichaelDvP
7d5e112efb Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev_ 2022-10-11 09:34:56 +02:00
tefracky
e82dd816de Updated German translation
Fixed some typos and language clearificaiton
2022-10-11 09:19:10 +02:00
Proddy
887a245d82 Merge pull request #676 from proddy/dev
NO and Commands translations
2022-10-11 08:28:12 +02:00
proddy
2f706b33fa add test for modes 2022-10-10 20:12:35 +02:00
proddy
dcbdb04009 typo 2022-10-10 20:12:25 +02:00
proddy
008903cbbd optimize set_mode command 2022-10-10 20:12:16 +02:00
proddy
b67113fc1f use const char * where possible 2022-10-10 20:11:23 +02:00
proddy
3afbe832cc don't use translations for commands in console 2022-10-10 20:10:20 +02:00
proddy
9adfa0ecfc use const char * where possible 2022-10-10 20:10:01 +02:00
proddy
f081d7fd3c remove TODO 2022-10-10 20:09:29 +02:00
MichaelDvP
394a3253aa v3.5.0b7, update changelog 2022-10-10 17:28:17 +02:00
MichaelDvP
13890d2835 translated tags 2022-10-10 16:55:00 +02:00
MichaelDvP
6fd3e567cd ESP32-C3 and ESP32-S2, LED for C3-Mini 2022-10-10 14:13:43 +02:00
MichaelDvP
b6d8e55b00 heatpump values, german translations 2022-10-10 14:12:52 +02:00
MichaelDvP
e6d3d347ab remove unused brackets and inits 2022-10-10 14:10:29 +02:00
proddy
c159ce7eb9 added NO 2022-10-09 22:31:34 +02:00
proddy
65c9bf22dc refactor commands to take dynamic translation descriptions #674 2022-10-09 22:19:29 +02:00
Proddy
87dfffeddb Merge pull request #672 from proddy/dev
fix REST-API - strange characters in description
2022-10-09 19:41:46 +02:00
Proddy
8d6c676fed fix REST-API - strange characters in http://<hostname>/api/<device>/commands output #671 2022-10-09 14:18:29 +02:00
Proddy
324d27896b Merge remote-tracking branch 'origin/flash_optimization' into dev #646 2022-10-08 10:02:48 +02:00
Proddy
8c94ce99b2 quick fix for filesystem initialization 2022-10-08 09:23:00 +02:00
Proddy
fd5fcf356f Merge pull request #667 from ajvdw/dev
Added support for ESP32-S2
2022-10-07 21:11:36 +02:00
Proddy
0dde5a9d2b merge PR #667 and PR #668 2022-10-07 21:11:18 +02:00
Proddy
73f7603c1d Merge pull request #668 from MichaelDvP/dev
Float rendering and Junkers FW500
2022-10-07 21:05:58 +02:00
Proddy
5faffc3886 upgrade axios 2022-10-07 20:26:17 +02:00
MichaelDvP
d09d5a7dbe Junkers settime, dst always zero, no check against NTP 2022-10-07 17:31:21 +02:00
ajvdw
b906fecdff Update platformio.ini 2022-10-07 13:03:55 +02:00
MichaelDvP
a6e4122e44 fix Junkers roomsensor enum 2022-10-07 12:58:10 +02:00
ajvdw
e663ecb458 added esp32-s2 support 2022-10-07 12:38:18 +02:00
MichaelDvP
95876e28bf Junkers Clock not writable 2022-10-07 12:06:46 +02:00
MichaelDvP
348932d929 rollback web output, don't use serialized for webpack 2022-10-07 11:53:18 +02:00
MichaelDvP
bd28516324 render analog value 2022-10-07 11:21:57 +02:00
MichaelDvP
d1fc050bed flags on signin page 2022-10-07 11:14:19 +02:00
MichaelDvP
bace01e4f7 show trailing zero for float values, #663 2022-10-07 10:56:52 +02:00
MichaelDvP
0a56ee7dbb add FW500 thermostat #666 2022-10-07 08:09:07 +02:00
Proddy
c90be99216 fix ha min/max 2022-10-06 17:09:09 +02:00
Proddy
a0a431e0e2 merge https://github.com/emsesp/EMS-ESP32/pull/664 2022-10-06 17:08:57 +02:00
Proddy
4489e7149c Merge pull request #664 from MichaelDvP/dev
add controller BC30, #663
2022-10-05 18:20:21 +02:00
MichaelDvP
ec81420894 add controller BC30, #663 2022-10-05 18:10:50 +02:00
Proddy
619dd0c99d merge https://github.com/emsesp/EMS-ESP32/pull/662 2022-10-05 12:24:31 +02:00
Proddy
9f5a5108fb Merge pull request #662 from MichaelDvP/dev
fix #659, mqtt bool values
2022-10-05 12:09:33 +02:00
MichaelDvP
974510b2c7 fix #659, mqtt bool values 2022-10-05 11:26:15 +02:00
Proddy
f2f10f0c79 Merge pull request #661 from pswid/dev
Polish translation improvements
2022-10-04 21:04:17 +02:00
Proddy
39cbcd4d6f Polish translation improvements #661 2022-10-04 21:04:04 +02:00
Proddy
1accdfcafb Merge pull request #660 from MichaelDvP/dev
Climate min/max #647, RC300 CalIntTemp #657
2022-10-04 21:01:59 +02:00
Proddy
e37bbe420c manual merge of PR 660 - https://github.com/emsesp/EMS-ESP32/pull/660/ 2022-10-04 21:01:23 +02:00
pswid
58c4455076 Update index.ts
Polish translation improvements
2022-10-04 15:01:44 +02:00
MichaelDvP
a413ffb9d2 avoid strange, long decimals in input field 2022-10-04 10:44:19 +02:00
MichaelDvP
1f96622e74 count tx-queue overflows as tx-errors 2022-10-03 15:03:35 +02:00
MichaelDvP
9527cf6abf RC300 calinttemp and fix some other thermostat values 2022-10-03 12:53:37 +02:00
MichaelDvP
13f0bc3296 mqtt status shows queue and reconnects 2022-10-03 08:08:51 +02:00
Proddy
58a0ec9cca move the set_custom_minmax into the class initiator 2022-10-02 15:54:12 +02:00
Proddy
fe385de342 no fullname translation is allowed 2022-10-02 15:53:50 +02:00
Proddy
ae5fb26387 add check fto translated_word for empty arrays, to be crash safe 2022-10-02 15:53:26 +02:00
Proddy
cd67ab03ff fullname array is allowed to be null 2022-10-02 15:52:54 +02:00
Proddy
94f134f3fe formatting to fit on one line 2022-10-02 15:52:34 +02:00
Proddy
ca95e44a81 climate doesn't need a translation 2022-10-02 15:52:17 +02:00
Proddy
ecc045b2a8 add test for min/max 2022-10-02 15:51:54 +02:00
Proddy
9c205a07c5 add test data for min/max 2022-10-02 15:51:43 +02:00
MichaelDvP
c414354708 climate min/max values 2022-10-02 09:52:04 +02:00
Proddy
ddacd2d9d7 min/max example 2022-10-01 16:49:01 +02:00
Proddy
987fcb4a5a Merge remote-tracking branch 'origin/dev' into flash_optimization 2022-10-01 16:48:50 +02:00
Proddy
4c70da28e6 better fix for ha climate Optimize flash with ESP32 #646 2022-10-01 16:03:04 +02:00
Proddy
246684f28c Merge pull request #651 from MichaelDvP/dev
add missing fahrenheits on customization screen
2022-10-01 11:59:48 +02:00
MichaelDvP
e450a0e096 fix bool command for hp input #600 2022-10-01 11:21:15 +02:00
proddy
90d2144588 fix for lists that don't have translations, like climate 2022-09-30 20:48:21 +02:00
proddy
4302d314cf generic test 2022-09-30 20:47:55 +02:00
proddy
22322a55ed update 2022-09-30 20:47:47 +02:00
MichaelDvP
d09c0436e0 missing fahrenheits on customization screen 2022-09-29 15:26:00 +02:00
Proddy
9b619216cb formatting brackets 2022-09-28 21:46:12 +02:00
Proddy
fe0a855618 formatting 2022-09-28 21:41:19 +02:00
Proddy
3bca7c9a13 Merge pull request #650 from MichaelDvP/dev
custom min/max
2022-09-28 21:20:08 +02:00
Proddy
87887494a5 fix endless loop! 2022-09-28 21:19:14 +02:00
Proddy
614a8cb14b add comment 2022-09-28 21:19:03 +02:00
Proddy
da6e64e89f optimize toLower() 2022-09-28 21:18:52 +02:00
Proddy
87d0db0b5c auto formatting 2022-09-28 20:27:48 +02:00
MichaelDvP
c756df90fc update packages 2022-09-28 17:47:27 +02:00
MichaelDvP
d3053d8ce2 check min/max before activating 2022-09-28 17:26:49 +02:00
MichaelDvP
4c728cf777 update changelog, version b4 2022-09-28 16:52:05 +02:00
MichaelDvP
17b90af972 RC35 default min/max values added 2022-09-28 16:16:42 +02:00
MichaelDvP
a0339f5ae2 custom min/max values 2022-09-28 16:08:08 +02:00
MichaelDvP
732aaffbb6 boiler HP-inputConfig, HeaterConfig, thermostat roomsensor 2022-09-28 16:07:38 +02:00
MichaelDvP
d50606ce13 german web translation 2022-09-28 16:05:44 +02:00
proddy
777c9db0f6 remove flash 2022-09-28 15:47:27 +02:00
proddy
815397dba6 move device_library name to const char * 2022-09-25 14:36:45 +02:00
Proddy
01f361e7cd Merge pull request #643 from mvjt/dev
Polishing Swedish Translations
2022-09-25 11:46:26 +02:00
Proddy
1278776297 Merge pull request #642 from proddy/dev
update to commands in web
2022-09-25 11:46:01 +02:00
mvjt
a3a8e515bf Polishing Swedish Translations 2022-09-25 11:14:28 +02:00
Proddy
96af9afc83 commands like reset can't be renamed. use translated name for commands like these. 2022-09-25 10:45:08 +02:00
Proddy
c841c8c284 lighter grey colour 2022-09-25 10:44:23 +02:00
Proddy
d6ee8ccb2d fix reset translation 2022-09-25 10:44:04 +02:00
proddy
a7930d8403 auto formatting using 'npm run format' 2022-09-24 18:52:19 +02:00
proddy
eeeb889ba7 show options when editing Entity 2022-09-24 18:49:35 +02:00
proddy
f1f4147628 bump 2022-09-24 18:49:08 +02:00
proddy
42118e0169 update packages (again!) 2022-09-24 18:49:01 +02:00
proddy
b67d69a5c8 add translation for Entity 2022-09-24 18:48:48 +02:00
Proddy
d8144c901d Merge pull request #640 from MichaelDvP/dev
mqtt ha settings, show memory in KB, etc.
2022-09-24 17:40:07 +02:00
MichaelDvP
e9d9cf7e48 fix console show system 2022-09-24 17:17:16 +02:00
MichaelDvP
0867d7fe0e Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev 2022-09-24 16:31:27 +02:00
MichaelDvP
b646331adc dallas, analog HA object_id also without basename (commented out) 2022-09-24 16:17:33 +02:00
Proddy
30109a54ce Merge pull request #639 from proddy/dev
fix missing NL translations
2022-09-24 16:09:05 +02:00
Proddy
e85ca7dcd3 use translation of Restart 2022-09-24 16:08:40 +02:00
Proddy
faf05ceb72 cleanup text formatting 2022-09-24 16:08:29 +02:00
Proddy
049e37ba82 changes to NL 2022-09-24 16:08:15 +02:00
Proddy
d187ee23ea update packages 2022-09-24 16:07:50 +02:00
MichaelDvP
20b0c9653d kb -> KB 2022-09-24 14:22:49 +02:00
MichaelDvP
022b667858 use old HA object_id, #636, HA (un)subscribes with wildcard 2022-09-24 12:00:41 +02:00
MichaelDvP
1b4af09185 typos application used/free 2022-09-23 22:31:39 +02:00
MichaelDvP
58ca4efd42 Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev_ 2022-09-23 21:58:41 +02:00
MichaelDvP
f1bb183017 ha config to basename, analog/dallas allow single/nested with ha 2022-09-23 21:54:13 +02:00
MichaelDvP
081c11c503 add Greenstar 30Ri boiler #635 2022-09-23 21:36:56 +02:00
MichaelDvP
d54843635a add application info, all memory info in kb 2022-09-23 21:36:01 +02:00
Proddy
12638275fa Merge pull request #633 from tefracky/pull-request-german-translation
Fixed German translation typos
2022-09-22 19:03:36 +05:30
Proddy
25c50435ca Merge pull request #632 from MichaelDvP/dev
fix #631, negative multiplicator
2022-09-22 19:03:04 +05:30
tefracky
e1c61cfadb Fixed German translation typos 2022-09-22 12:44:25 +02:00
tefracky
06fd860964 Fixed German translation typos 2022-09-22 12:13:48 +02:00
MichaelDvP
ab4e1f63c5 fix #631, negative multiplicator 2022-09-22 10:33:38 +02:00
Proddy
fb998a7e6a Merge pull request #630 from MichaelDvP/dev 2022-09-20 22:23:01 +05:30
MichaelDvP
dbbbfc1170 update translations 2022-09-20 17:34:44 +02:00
proddy
eb432d9acb auto formatting 2022-09-20 19:05:11 +05:30
proddy
23f65b9eb2 fixes does Factory Reset from the webUI work? #628 2022-09-20 19:05:02 +05:30
proddy
c11ea4fe0d update 2022-09-20 19:04:49 +05:30
proddy
6b1cfffb6f package updates 2022-09-20 19:04:43 +05:30
Proddy
918c0315eb Merge pull request #627 from MichaelDvP/dev
Fallback to en on empty entity string
2022-09-19 22:41:18 +05:30
MichaelDvP
106517fc8b Fallback to en on empty entity string 2022-09-19 16:28:27 +02:00
Proddy
96070d6b42 Merge pull request #626 from MichaelDvP/dev
Translations, move rf sensor #624
2022-09-19 19:32:40 +05:30
MichaelDvP
b22473d0d0 move rf sensor to thermostat #624 2022-09-19 15:45:00 +02:00
MichaelDvP
ef842b6c52 web polish translations (some with fallback) 2022-09-19 15:38:08 +02:00
MichaelDvP
e9415c1708 add german translations from tp1de 2022-09-19 15:37:24 +02:00
MichaelDvP
0114efc66f add Junkers thermostat wwCharge 2022-09-19 15:34:13 +02:00
Proddy
84e7847c16 Merge pull request #620 from CheeseE/dev
Add support for Lolin C3 mini
2022-09-19 16:18:34 +05:30
CheeseE
3f935942ea update ini file. 2022-09-19 12:32:44 +02:00
CheeseE
8f45322b7b disable board selection. 2022-09-19 12:05:25 +02:00
CheeseE
6e095c1085 fix compile issues. 2022-09-19 12:05:25 +02:00
CheeseE
acaceefc89 Add support for wemos c3 mini 2022-09-19 12:03:14 +02:00
proddy
21c3a4bee8 remove PIOENV for CI GH Actions calling 2022-09-18 17:04:25 +02:00
proddy
a959af80b5 remove duplicate 1/0 boolean code 2022-09-18 17:04:06 +02:00
proddy
e0d7e7698e 3.5.0 2022-09-18 15:43:38 +02:00
proddy
dbf9912c5a Merge remote-tracking branch 'origin/v3.5.0' into dev 2022-09-18 15:43:02 +02:00
proddy
fc057d18c9 Merge remote-tracking branch 'origin/dev' 2022-09-18 14:33:23 +02:00
proddy
326f05d63f updated packages 2022-09-18 12:54:52 +02:00
proddy
eda203e350 formatting 2022-09-18 12:54:45 +02:00
proddy
f9038b9180 package updates 2022-09-17 10:52:11 -07:00
Proddy
506aacbd83 Merge pull request #623 from MichaelDvP/v3.5.0
disable uncomplete translations
2022-09-17 10:50:12 -07:00
MichaelDvP
63e31d672b add no-translation setting, disable uncomplete translations 2022-09-17 19:10:58 +02:00
MichaelDvP
0d5d353e99 read LittleFS.totalBytes only once on start 2022-09-17 17:10:23 +02:00
Proddy
766ec6a6e4 Merge pull request #622 from MichaelDvP/v3.5.0
add operation sign to log and some other small changes
2022-09-17 08:09:54 -07:00
MichaelDvP
872effb297 update packages 2022-09-17 16:59:45 +02:00
MichaelDvP
fb13b79a76 console always allow watch on/off in all languages 2022-09-17 16:42:16 +02:00
MichaelDvP
706daeb678 remove unused uart setting 2022-09-17 16:40:26 +02:00
MichaelDvP
9d6af82f9c remove double comment 2022-09-17 09:09:10 +02:00
MichaelDvP
2bc37027dd remove unused/obsolet command_pin 2022-09-13 07:56:07 +02:00
MichaelDvP
e70b6b210e check command send for valid data 2022-09-13 07:54:36 +02:00
MichaelDvP
df8a36c695 calculate length of telegram next part 2022-09-13 07:27:41 +02:00
MichaelDvP
702af4b1c8 add operation to log messages #594 2022-09-13 07:17:42 +02:00
MichaelDvP
cd15e11ce3 prepare for polish translation 2022-09-12 19:22:05 +02:00
MichaelDvP
ca8b236ccb add se, fix typos, formatting 2022-09-12 08:08:39 +02:00
MichaelDvP
06b057f4a2 Merge pull request #618 from mvjt/v3.5.0
Add Swedish language
2022-09-12 07:57:10 +02:00
mvjt
0158c9b434 Henriks changes again 2022-09-11 20:16:46 +02:00
mvjt
368728d12b Henriks changes 2022-09-11 20:10:22 +02:00
mvjt
c1cab103aa Removed WS 2022-09-11 14:02:33 +02:00
mvjt
d9da85638c Both files translated 2022-09-11 13:01:55 +02:00
mvjt
a25989d213 Both files translated 2022-09-11 13:00:37 +02:00
mvjt
6bfe4fe258 Both files translated 2022-09-11 12:52:40 +02:00
Proddy
e7cbb8cc77 Merge pull request #617 from MichaelDvP/v3.5.0
V3.5.0 small updates
2022-09-11 11:10:19 +01:00
MichaelDvP
06354efa76 update packages 2022-09-11 11:20:44 +02:00
MichaelDvP
345f05b23f don't render custom devices while editing 2022-09-11 11:20:29 +02:00
MichaelDvP
f0489cb918 fix custom name saving 2022-09-11 11:19:51 +02:00
MichaelDvP
d0b265985b update de/index 2022-09-11 11:18:03 +02:00
MichaelDvP
82f0902fa4 network/mqtt init fix #42 2022-09-11 11:17:34 +02:00
mvjt
1dcb44f81a Continue with translation 2022-09-10 22:14:58 +02:00
Proddy
9bbc421407 fix bug in masks 2022-09-10 21:39:29 +02:00
mvjt
54f335946e Started with second translation file 2022-09-10 21:08:10 +02:00
mvjt
d95d8278de Changed retry 2022-09-10 19:51:00 +02:00
mvjt
c813339945 Translated first file to SE 2022-09-10 19:43:03 +02:00
Proddy
cc44bc9d7f Command uses flash fullname instead of custom, to save on heap 2022-09-10 17:18:43 +02:00
Proddy
4cd655fb36 added Swedish/SE - Multi-language/I18n support #22 2022-09-09 21:09:51 +02:00
Proddy
70efa4f4e6 Merge pull request #613 from MichaelDvP/dev
fix #42
2022-09-09 16:06:04 +01:00
Proddy
5845c37672 Change name of entity within WebUI #612 2022-09-09 17:04:31 +02:00
MichaelDvP
a020a48e63 network/mqtt init fix #42 2022-09-09 15:28:57 +02:00
MichaelDvP
a56c847790 update packages 2022-09-09 15:28:13 +02:00
proddy
9ae81779ff Change name of entity within WebUI #612 2022-09-09 12:35:21 +02:00
proddy
df4aa64883 Change name of entity within WebUI #612 2022-09-09 12:33:15 +02:00
MichaelDvP
70f94322ee csv-header and online time translation 2022-09-06 19:43:42 +02:00
MichaelDvP
c2ccb4edda Help page DE 2022-09-06 19:42:41 +02:00
MichaelDvP
f42fafb25f unused solar values 2022-09-06 19:42:08 +02:00
proddy
0bccf9d358 text changes again 2022-09-06 18:10:53 +02:00
proddy
d3f105d9b9 minor updates to text 2022-09-06 18:08:14 +02:00
proddy
5c240316ee use GB flag instead of US 2022-09-06 17:45:12 +02:00
Proddy
2fde0c6d8c add flag to menubar - #22 2022-09-06 16:43:32 +02:00
proddy
76d78f34ee updates #22 2022-09-06 15:04:25 +02:00
MichaelDvP
3efb3a99e9 Merge pull request #611 from bbqkees/bbqkees-dutch-translations
Bbqkees dutch translations web and entities
2022-09-05 18:28:39 +02:00
Kees
65dfc3b878 Update locale_translations.h 2022-09-05 15:30:05 +02:00
Kees
b820aad3fc Update index.ts 2022-09-05 15:28:39 +02:00
MichaelDvP
09d4cae6f4 allow enum commands to use en or translation 2022-09-02 16:00:15 +02:00
MichaelDvP
85308e52a7 formatting, add missing text 2022-09-02 15:33:42 +02:00
MichaelDvP
4f42ae7efc console show handle enum/bool like web 2022-09-02 14:16:24 +02:00
MichaelDvP
7cc2eac79d add nl to entity translation 2022-09-02 14:15:49 +02:00
MichaelDvP
e3487b4845 add nl language to web, add more translations 2022-09-02 14:15:13 +02:00
MichaelDvP
354b2facf7 update packages 2022-09-02 14:04:39 +02:00
MichaelDvP
0d381fd5e9 more german translations 2022-08-30 18:23:19 +02:00
MichaelDvP
65b7d64e92 Thermostat: mode, RC30/35 timers, telegram 13, 2022-08-30 17:38:40 +02:00
MichaelDvP
e79115d719 offset to new dallas, remove sensors if disabled 2022-08-30 17:36:03 +02:00
MichaelDvP
be4f49e96d name known device_ids 2022-08-30 17:34:50 +02:00
MichaelDvP
6b24a71ad7 set NTP sync interval 1 hour 2022-08-30 17:29:41 +02:00
Proddy
1ccacdc600 translate network 2022-08-28 16:36:04 +02:00
Proddy
3ea71e1dfb translate NTP, AP, MQTT and User 2022-08-28 15:06:20 +02:00
proddy
d8e324a005 translate system menu 2022-08-28 10:44:22 +02:00
proddy
7122e878a5 upload translations 2022-08-27 13:02:04 +02:00
proddy
c8848a9e76 #596 2022-08-27 13:01:52 +02:00
Proddy
84499dab35 DE translations for Settings, Customizations and Help 2022-08-26 16:14:28 +02:00
proddy
e9c695f76a add back HA mqtt subscribe 2022-08-26 09:21:24 +02:00
proddy
029ade8dd6 update packages latest typescript 2022-08-26 08:25:21 +02:00
proddy
0ebb509205 restart after rx/tx gpio change 2022-08-26 08:25:07 +02:00
proddy
9fa59310d2 fix translating times 2022-08-25 23:04:41 +02:00
Proddy
1f8e6cc82b translate dashboard status 2022-08-25 15:50:23 +02:00
Proddy
1a4ce643fc Multi-language/I18n support #22 2022-08-24 21:50:19 +02:00
Proddy
763337db3f Merge pull request #603 from MichaelDvP/dev
AM200, RC100H, etc.
2022-08-20 09:31:44 +02:00
MichaelDvP
f6c5a4d064 fix #601 dhw modes for RC300 2022-08-18 12:40:29 +02:00
MichaelDvP
a20b6b59c9 Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev 2022-08-18 12:37:13 +02:00
MichaelDvP
9a708a263c ignore tx-read errors for higher offsets 2022-08-11 12:47:07 +02:00
MichaelDvP
c78a42e42f fix offset of dummy reply 2022-08-11 11:22:49 +02:00
MichaelDvP
df555acc0e set wwFlowTempOffset to 100 2022-08-11 09:20:15 +02:00
MichaelDvP
bf1ce5150a map RC100H values to device and tag hc 2022-08-11 09:19:53 +02:00
MichaelDvP
b499d908ab handle telegrams from src only 2022-08-10 15:24:08 +02:00
MichaelDvP
029ef56edd hc checks for RC100H 2022-08-10 10:48:55 +02:00
MichaelDvP
70f52ce484 humidity, dewtemp as uint 2022-08-09 14:57:18 +02:00
MichaelDvP
a087974acb add RC100H remoteseltemp and temp correction 2022-08-09 08:36:46 +02:00
MichaelDvP
f96b13c3cd fix tag of wwStarts2 2022-08-09 08:11:59 +02:00
MichaelDvP
9cf89ee6f6 update comment, fix hp humidity scale 2022-08-08 17:36:14 +02:00
MichaelDvP
a5be7f4e38 reboot required when changing uart pins 2022-08-08 17:35:17 +02:00
MichaelDvP
d367c20a13 add disconnected banner to dashboard, #591 2022-08-08 09:58:13 +02:00
MichaelDvP
97132a4758 min/max settings for AM200 2022-08-08 09:57:28 +02:00
MichaelDvP
308b8fb779 fix position of am200 mixingvalves 2022-08-05 06:49:04 +02:00
MichaelDvP
79e0b80e51 no fetch for broadcasted remote telegrams 2022-08-05 06:48:09 +02:00
MichaelDvP
f79258f645 product_id 200 as thermostat RC100H, #590 2022-08-02 19:28:54 +02:00
Proddy
8d3bcde8d6 Merge pull request #589 from ichsteffen/dev
Update MqttSettingsForm.tsx
2022-07-30 16:55:36 +02:00
MichaelDvP
8ee70a1263 version/changelog 2022-07-30 14:50:40 +02:00
MichaelDvP
fb940269de AM200 settings #573 2022-07-30 14:50:23 +02:00
MichaelDvP
ec02e55635 Fix typo mqtt-base 2022-07-30 14:30:42 +02:00
ichsteffen
f87f18ca8e Update MqttSettingsForm.tsx 2022-07-30 14:14:52 +02:00
MichaelDvP
94afd8a3a6 rename telegrams am200 2022-07-27 07:34:16 +02:00
MichaelDvP
0d69a0a3db add ahs valve positions 0-100% 2022-07-26 07:06:25 +02:00
Proddy
beae2a2587 Merge pull request #586 from proddy/dev
link to device entity details
2022-07-25 19:40:16 +02:00
Proddy
a3a29132ab no need to add uique id to end of device type since it's unique anyway 2022-07-25 17:17:10 +02:00
Proddy
cdc04f987c update packages 2022-07-25 17:16:49 +02:00
Proddy
ba2ded1a5a wifi disconnect if Warning log 2022-07-24 15:20:23 +02:00
Proddy
d0a779b185 minor formatting 2022-07-24 13:40:08 +02:00
Proddy
d12879b07b link to device entity details from customization page 2022-07-24 13:39:52 +02:00
Proddy
d0bcbb8d1b Merge pull request #585 from MichaelDvP/dev
AM200, EM10 changes
2022-07-23 14:55:29 +02:00
Proddy
97257ac821 Merge pull request #583 from proddy/dev
minor changes when testing coldshot
2022-07-23 13:04:46 +02:00
Proddy
d5e19fdf5b updated mock doc 2022-07-23 13:03:42 +02:00
MichaelDvP
9c1d08c057 prevent reinitialization of all on first setting change 2022-07-23 10:40:44 +02:00
MichaelDvP
c4f4a440ac EM10 as gateway 2022-07-22 16:16:55 +02:00
MichaelDvP
8ebefebb0a don't show AM200 valves with unknown offset 2022-07-22 16:16:33 +02:00
Proddy
3b86d1b5aa minor text change 2022-07-22 15:56:54 +02:00
Proddy
505e339406 testing coldshot 2022-07-22 15:56:45 +02:00
Proddy
297134dd81 remove serial output if no serial selected during upload 2022-07-22 15:56:27 +02:00
Proddy
02989ec4b3 cleanup duplicate text 2022-07-22 15:56:11 +02:00
Proddy
9573869c7c fix compile errors with debug 2022-07-22 15:55:58 +02:00
MichaelDvP
295bbed4ae fix #581 2022-07-21 17:49:38 +02:00
MichaelDvP
c81e3e832a handle EM10 as switch 2022-07-21 17:48:28 +02:00
MichaelDvP
a018432b81 Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev 2022-07-21 17:48:04 +02:00
MichaelDvP
6bc8a4b6c5 EM10 as switch 2022-07-21 08:57:48 +02:00
Proddy
32dcd7313b Merge pull request #579 from proddy/dev 2022-07-21 08:16:48 +02:00
Proddy
387c9c63f1 typo 2022-07-20 20:34:55 +02:00
Proddy
ca30b8233b fixes 2022-07-20 20:33:43 +02:00
Proddy
c3757f95e5 only send MQTT after shower finished 2022-07-20 12:04:01 +02:00
Proddy
66fbd2b359 show MQTT disconnects as warnings #543 2022-07-20 12:03:47 +02:00
Proddy
4420ae33b8 merge upload and download in webUI #577 2022-07-20 12:03:21 +02:00
Proddy
d707e92d59 formatting 2022-07-20 12:02:32 +02:00
MichaelDvP
273526dc16 check tags 2022-07-19 12:59:54 +02:00
MichaelDvP
ebc5cfa2d8 add tag "ahs" for alternative heating source 2022-07-19 07:50:37 +02:00
MichaelDvP
13430de3ba detect boiler with id 0x60 2022-07-18 21:04:53 +02:00
MichaelDvP
78dee6c7fe AM200 temperatures 2022-07-18 20:04:56 +02:00
MichaelDvP
43db536878 add EM10 error detection module 2022-07-18 19:43:59 +02:00
MichaelDvP
c765a274b2 AM200, first test 2022-07-18 17:20:05 +02:00
MichaelDvP
8101fcf95d button for help->info, file: .json.txt 2022-07-18 13:48:49 +02:00
Proddy
69568abb8a Merge pull request #572 from MichaelDvP/dev
fix automatic build
2022-07-13 00:26:22 +05:30
MichaelDvP
a14b43dbe2 update packages 2022-07-12 13:51:46 +02:00
Proddy
2fedd6da15 Merge pull request #571 from MichaelDvP/dev
FS_Buffer to 8k for customizations, fix#570
2022-07-12 14:36:20 +05:30
MichaelDvP
fc3224c9ea FS_Buffer to 8k for customizations 2022-07-12 10:27:35 +02:00
Proddy
39feb25600 Merge pull request #568 from proddy/dev
fix column size for temperature
2022-07-05 13:12:01 +02:00
proddy
eddbacf5a7 fix column size for temperature 2022-07-05 13:00:58 +02:00
Proddy
4e5b460447 Merge pull request #567 from proddy/dev
update table packages
2022-07-04 21:43:03 +02:00
Proddy
0e172ff06d update table packages 2022-07-04 21:42:35 +02:00
Proddy
40fa1d47bc Merge pull request #566 from MichaelDvP/dev 2022-06-30 11:56:58 +02:00
MichaelDvP
70fe08a3c8 fix #550 set_summermode 2022-06-27 16:45:32 +02:00
MichaelDvP
b840e2a6b5 Merge branch 'emsesp:dev' into dev 2022-06-27 14:38:58 +02:00
MichaelDvP
422657fbf7 fix #550
typo summersetmode/summermode
2022-06-27 14:26:56 +02:00
Proddy
da596a01b9 Merge pull request #563 from proddy/dev
optimize for mobile phone layout
2022-06-26 21:13:16 +02:00
Proddy
96d2fd2393 optimize for mobile phone layout 2022-06-26 21:12:21 +02:00
Proddy
42c9aeeed6 Merge pull request #562 from proddy/dev
fix multiline
2022-06-25 16:35:05 +02:00
Proddy
a9b32d8ba7 Merge branch 'dev' of https://github.com/proddy/EMS-ESP32 into dev 2022-06-25 16:34:19 +02:00
Proddy
30da267db0 fix #547 2022-06-25 16:34:18 +02:00
Proddy
84f643e461 Merge pull request #561 from proddy/dev
table resizing fix
2022-06-25 14:28:07 +02:00
Proddy
6298fab94f Merge branch 'dev' of https://github.com/proddy/EMS-ESP32 into dev 2022-06-25 14:25:53 +02:00
Proddy
9639f9cee6 fix table column resizing #519 2022-06-25 14:25:51 +02:00
Proddy
4af1de4093 auto-formatting 2022-06-25 14:25:31 +02:00
Proddy
2c90a2c3aa 3.4.2b3 2022-06-25 14:25:11 +02:00
Proddy
254d4dd02d updated packages 2022-06-25 14:25:05 +02:00
Proddy
4eceeea4d1 3.4.2b3 2022-06-25 14:24:52 +02:00
MichaelDvP
efa5eb273c show summermode or operatingmode, fix #550 2022-06-25 09:51:15 +02:00
Proddy
8a05e11297 Merge pull request #560 from pswid/dev
Shorten "friendly names" in Home Assistant
2022-06-24 13:59:58 +02:00
pswid
1238e89084 add unit of measurement in HA for analog sensors 2022-06-21 20:39:52 +02:00
pswid
1c48d12167 Shorten "friendly names" in Home Assistant
see #555
2022-06-21 20:28:22 +02:00
Proddy
1fab47acd5 Merge pull request #558 from MichaelDvP/dev
burner max power to 254%, fix #553
2022-06-21 10:06:17 +02:00
MichaelDvP
0e563e91b1 burner max power to 254%, fix #553 2022-06-21 06:37:47 +02:00
Proddy
43b1a4572a Merge pull request #557 from MichaelDvP/dev 2022-06-20 22:21:54 +02:00
MichaelDvP
bf18718b9a shortname to wwVacations/wwHoliday to avoid conflict 2022-06-20 21:38:12 +02:00
Proddy
8790d81e03 Merge pull request #556 from MichaelDvP/dev
fix #554, add dhw vacation/holiday
2022-06-20 20:16:56 +02:00
MichaelDvP
0e5cb2df8e fix #554, add dhw vacation/holiday 2022-06-20 17:42:38 +02:00
Proddy
46c5913000 Merge pull request #552 from proddy/dev
update packages
2022-06-17 11:29:50 +02:00
Proddy
c35433856f update packages 2022-06-17 11:28:55 +02:00
Proddy
67f898bbec force removal of directories 2022-06-17 11:28:50 +02:00
Proddy
449c01d345 Merge pull request #551 from MichaelDvP/dev
fix #549, set minimum emergencyTemp to 15°C
2022-06-17 10:59:38 +02:00
MichaelDvP
cec1fce745 fix #549, set minimum emergencyTemp to 15°C 2022-06-15 09:56:25 +02:00
Proddy
a02d4ab259 Merge pull request #545 from MichaelDvP/dev
update OneWire lib #542
2022-06-07 21:19:25 +02:00
MichaelDvP
a22783a3e4 update OneWire lib 2022-06-07 08:18:56 +02:00
Proddy
9524ec1317 Merge pull request #544 from proddy/dev
minor fixes
2022-06-06 11:23:23 +02:00
proddy
64a7108e6d make it compile standalone 2022-06-06 11:20:16 +02:00
proddy
cf4818f09c update packages 2022-06-06 11:20:09 +02:00
proddy
98fb970dac add test for #541 2022-06-06 11:19:56 +02:00
Proddy
d162206f75 Merge pull request #540 from MichaelDvP/dev
WebStatus: add syslog count/fail, show only enabled services
2022-06-03 16:08:22 +02:00
MichaelDvP
1609b76f0a WebStatus:add syslog count/fail, show only enabled services 2022-06-03 13:39:26 +02:00
Proddy
794b3c0471 Merge pull request #538 from MichaelDvP/dev
fix #536 and uart task priority
2022-06-03 07:36:49 +02:00
MichaelDvP
e7bcc380e3 fix #536, FR100 datetime not writable 2022-06-02 18:34:02 +02:00
MichaelDvP
fe12f1903d uart higher prriority 2022-06-02 18:33:23 +02:00
Proddy
41ec4b9bcf Merge pull request #537 from proddy/dev
update packages
2022-06-02 14:54:01 +02:00
Proddy
f64dd00cce update packages 2022-06-02 14:53:38 +02:00
Proddy
5ef2f8292c Merge pull request #534 from MichaelDvP/dev
some small corrections
2022-05-30 12:11:14 +02:00
MichaelDvP
ae3ead6b10 some small corrections 2022-05-30 10:00:45 +02:00
Proddy
874f686882 Merge pull request #533 from proddy/dev
chamges for standalone compiling
2022-05-29 21:16:29 +02:00
Proddy
383c30f649 chamges for standalone compiling 2022-05-29 21:16:12 +02:00
Proddy
0b2c95fc4e Merge pull request #532 from MichaelDvP/idf4_no_master
Update to platformio2.3.0/IDF4/Arduino2.0, remove master themostat
2022-05-29 20:42:16 +02:00
MichaelDvP
eacdd62cc8 update changelog 2022-05-29 19:15:45 +02:00
MichaelDvP
a692eb557d Merge branch 'idf4' into idf4_no_master 2022-05-29 19:12:58 +02:00
MichaelDvP
99b6acfc07 Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into idf4 2022-05-29 19:07:32 +02:00
Proddy
fa486c8b60 Merge pull request #520 from tp1de/dev
Additions for RC310
2022-05-29 16:28:31 +02:00
Proddy
b19b3e5869 Merge branch 'dev' into dev 2022-05-29 16:28:15 +02:00
Proddy
e6bfd90235 Merge pull request #531 from proddy/dev
3.4.2b2
2022-05-29 16:20:12 +02:00
Proddy
072fe526ea 3.4.2b2 2022-05-29 16:19:55 +02:00
Proddy
18e9b99413 Merge remote-tracking branch 'origin/dev' into main 2022-05-29 16:16:38 +02:00
Proddy
e65f5072fe Merge pull request #527 from proddy/dev
various minor changes, including managing hidden entities in customization screen
2022-05-29 16:11:53 +02:00
Proddy
435218888a #513 2022-05-29 14:36:13 +02:00
Proddy
3e7b743dfa updated changelog for 3.4.1 2022-05-29 13:58:01 +02:00
Proddy
c693ef6307 update packages 2022-05-29 13:57:54 +02:00
Proddy
e4447ee1b9 improved icon toggling 2022-05-28 20:23:16 +02:00
Proddy
356390c92b fix standalone server for handling customizations 2022-05-28 15:27:08 +02:00
tp1de
ab6893adeb corrections nofrostmode 2022-05-28 13:16:40 +02:00
Proddy
55133b028a typo 2022-05-27 17:18:50 +02:00
Proddy
1fbd69b718 fixes to customizations 2022-05-27 17:16:23 +02:00
tp1de
97239268ac damping 2022-05-27 16:07:34 +02:00
tp1de
10bf065a2a Add damping to RC310 2022-05-27 16:00:18 +02:00
tp1de
107106d759 requested changes on PR from @MichaelDvP 2022-05-27 14:05:56 +02:00
tp1de
622a5db8d1 wwprio for RC310 thermostat 2022-05-27 11:07:21 +02:00
Proddy
1e082f941a customizations, swap shortname and fullname, using id as unique shortname 2022-05-27 09:49:32 +02:00
Proddy
8824f4f3da added formatName so hidden entities render nicely 2022-05-26 17:49:43 +02:00
Proddy
f8bf6b5cc8 fix isCmdOnly() function for commands like reset 2022-05-26 17:49:25 +02:00
Proddy
be844a5184 if no fullname, use shortname in customizations json 2022-05-26 17:49:04 +02:00
Proddy
6ec67f7417 rename ha_climate_config_creation 2022-05-26 17:48:34 +02:00
Proddy
2c468c7225 formatting 2022-05-26 17:48:18 +02:00
Proddy
af7cd7b009 rename generate_values_web_customization 2022-05-26 17:48:10 +02:00
Proddy
cba081379e rename generate_values_web_customization 2022-05-26 17:48:01 +02:00
Proddy
a9064baefc 3.4.1b1 2022-05-26 17:47:15 +02:00
Proddy
31627bb704 remove debug_flags 2022-05-26 17:47:04 +02:00
Proddy
c4cfabfbaf update test data for customizations 2022-05-26 17:46:51 +02:00
Proddy
9d25f8049c Merge pull request #525 from MichaelDvP/dev
fixes #522, #523, #524
2022-05-26 15:17:51 +02:00
MichaelDvP
2d50f18dcf changelog from 3.4.0 2022-05-26 13:34:51 +02:00
MichaelDvP
89b0711464 changelog 2022-05-26 13:05:37 +02:00
MichaelDvP
34cb3ad375 fix #524, free memory of json response 2022-05-26 12:43:10 +02:00
MichaelDvP
d2609e4291 fix #523, rename 'climate' to more explaning name 2022-05-26 12:42:55 +02:00
MichaelDvP
77a8857e2f fix #522, device-flag for IVT controller 2022-05-26 12:42:38 +02:00
tp1de
53d3bda326 Added switchonoptimization for RC310 2022-05-25 21:20:32 +02:00
tp1de
570588f498 delete obsolete reducetemp1 2022-05-25 20:01:56 +02:00
tp1de
60e1a93966 correct nofrostmodet1 & reducemode1 - just change enums ! 2022-05-25 14:22:12 +02:00
tp1de
3115fae807 Added enum_controlmode1 - Adjusted enum list for RC310 2022-05-25 13:38:50 +02:00
Thomas
c2767a866f Merge branch 'emsesp:dev' into dev 2022-05-25 09:54:53 +02:00
Proddy
60714b0b0b Merge pull request #521 from proddy/dev
React18 modifications
2022-05-24 22:50:52 +02:00
Proddy
c32cdf5d98 React18 modifications 2022-05-24 22:50:18 +02:00
tp1de
c1598f3d4e changelog 2022-05-24 20:08:10 +02:00
tp1de
94c45891b4 add nofrostmode1 for RC310 2022-05-24 19:51:37 +02:00
tp1de
9d3426877d reducemode1, reducetemp and noreducetemp for RC310 2022-05-24 16:53:01 +02:00
MichaelDvP
d3d9a9300b Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into idf4_no_master 2022-05-24 07:15:23 +02:00
Proddy
472b97e89e update to 3.4.1b0 2022-05-23 21:34:00 +02:00
MichaelDvP
c502aa7d1c Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into idf4_no_master 2022-05-23 19:32:08 +02:00
MichaelDvP
6670dfdf07 formatting 2022-05-21 11:36:38 +02:00
MichaelDvP
b426f4f904 Merge remote-tracking branch 'origin/dev' into idf4_no_master 2022-05-21 11:36:08 +02:00
MichaelDvP
a6e0280ac1 NPM update 2022-05-21 08:37:00 +02:00
MichaelDvP
9a3668c16b Merge remote-tracking branch 'origin/dev' into idf4 2022-05-20 14:39:36 +02:00
MichaelDvP
3a538a97d6 Merge remote-tracking branch 'proddy/dev' into idf4_no_master 2022-05-20 13:24:05 +02:00
MichaelDvP
95927fc5d8 Merge branch 'idf4' into idf4_no_master 2022-05-18 18:03:31 +02:00
MichaelDvP
4023d7856b NPM update 2022-05-18 17:45:04 +02:00
MichaelDvP
8307753d53 fix uart crash with dev platform 2022-05-18 17:18:01 +02:00
MichaelDvP
c7fb339ee9 Merge branch 'dev' into idf4 2022-05-18 17:09:38 +02:00
MichaelDvP
ca50c5178a fix typos 2022-05-18 17:04:54 +02:00
MichaelDvP
a0454943a7 Merge branch 'dev' of https://github.com/proddy/EMS-ESP32 into idf4_no_master 2022-05-10 14:01:31 +02:00
MichaelDvP
a3130d498e Merge branch 'dev' of https://github.com/proddy/EMS-ESP32 into idf4 2022-05-10 13:44:22 +02:00
MichaelDvP
3168438d6f LITTLEFS->LittleFS 2022-05-10 13:26:50 +02:00
MichaelDvP
9202d799c6 npm update 2022-05-10 13:26:35 +02:00
MichaelDvP
c9f10fb5cc uart isr to IRAM 2022-05-10 12:42:46 +02:00
MichaelDvP
b3fde5a2da Merge branch 'dev' of https://github.com/proddy/EMS-ESP32 into idf4 2022-05-10 12:40:45 +02:00
MichaelDvP
8339fc735c uart in IRAM 2022-05-10 12:36:42 +02:00
MichaelDvP
3e2f7a5976 Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into idf4_no_master 2022-05-10 09:59:15 +02:00
MichaelDvP
9c732ae19f uart back 2022-05-10 09:59:04 +02:00
MichaelDvP
b16fa6d5c0 Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into idf4_no_master 2022-05-08 18:21:48 +02:00
MichaelDvP
6203eedbe5 uart 2022-05-08 17:27:56 +02:00
MichaelDvP
fdab56936f Merge branch 'dev' into idf4_no_master 2022-05-08 11:35:04 +02:00
MichaelDvP
6c151654cc parse_command 2022-05-08 10:33:17 +02:00
MichaelDvP
9e14b34e1f uart, tx-mode 1 check buffersize, change event-task 2022-05-07 15:34:58 +02:00
MichaelDvP
bf54ee2e9d Merge branch 'dev' into idf4 2022-05-07 15:31:54 +02:00
MichaelDvP
9d2ed1cdbd uart, tx-mode 1 check buffersize 2022-05-07 15:29:11 +02:00
MichaelDvP
db9237451f Merge branch 'dev' of https://github.com/MichaelDvP/EMS-ESP32 into idf4_no_master 2022-05-06 17:54:34 +02:00
MichaelDvP
f4aa4ed912 allow id for attribute 2022-05-06 12:02:56 +02:00
MichaelDvP
d075ee3111 Merge branch 'dev_no_master_thermostat' into idf4_no_master 2022-05-06 12:01:38 +02:00
MichaelDvP
5917238a62 Merge branch 'dev_' into idf4 2022-05-06 08:12:02 +02:00
MichaelDvP
cfff3a4b4a enableMDNS to lib_standalone 2022-05-05 16:56:33 +02:00
MichaelDvP
5abbec9dea uart: larger queue, check buffer and telegram 2022-05-05 06:59:34 +02:00
MichaelDvP
7b6ca7cd73 Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into idf4 2022-05-04 16:04:46 +02:00
MichaelDvP
ca7bea3f24 Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into idf4 2022-05-04 07:10:13 +02:00
MichaelDvP
221e28c996 uart checks 2022-05-03 07:32:51 +02:00
MichaelDvP
579c869688 Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into idf4 2022-05-02 15:42:02 +02:00
MichaelDvP
8dc5f0a5ac use development platform 2022-05-02 13:01:10 +02:00
MichaelDvP
9c38987dd4 Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into idf4 2022-05-02 11:21:35 +02:00
MichaelDvP
0c6c7f999f replace OneWire 2022-05-02 11:19:48 +02:00
MichaelDvP
dc9c21fc65 packages, platform, typo 2022-05-01 19:10:25 +02:00
MichaelDvP
1afa21c199 Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into idf4 2022-05-01 18:15:34 +02:00
MichaelDvP
7dc65a8c98 tasmota forc of if2.0.3 2022-05-01 18:14:09 +02:00
MichaelDvP
53e3600af7 uart driver installed, events 2022-04-29 11:52:39 +02:00
MichaelDvP
43d838548b faster network reconnect 2022-04-29 11:51:55 +02:00
MichaelDvP
5b6c0317f6 set platform 2022-04-25 18:21:01 +02:00
MichaelDvP
9fe0ee119b 1. try to fix uart, irq called not only on brk-irq? 2022-04-25 17:21:27 +02:00
MichaelDvP
c70d0352ea DHCP (works with framewaork 3.5.0) 2022-04-25 17:09:40 +02:00
MichaelDvP
9a8449e4fe NTP Settings 2022-04-25 17:08:44 +02:00
MichaelDvP
fa166483bb SYSTEM_EVENT.. to ARDUINO_EVENT_... 2022-04-25 17:07:52 +02:00
MichaelDvP
986b4df997 use buildin LittleFS 2022-04-25 17:02:50 +02:00
MichaelDvP
fbec88f6c8 OneWire check arduino version 2022-04-25 16:56:35 +02:00
MichaelDvP
adc88d53d3 update mbedtsl (works also for framework 3.5.0) 2022-04-25 16:55:51 +02:00
MichaelDvP
ab17dd5812 Cell height for multiline values 2022-04-25 16:23:48 +02:00
MichaelDvP
f9207ecd0f Merge branch 'dev_' into dev_no_master_thermostat 2022-04-14 07:27:07 +02:00
MichaelDvP
0495aecf4c Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev_no_master_thermostat 2022-03-11 11:51:12 +01:00
MichaelDvP
5634f46bd5 Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev_no_master_thermostat 2022-03-08 10:53:13 +01:00
MichaelDvP
f076829b9b fix mDNS 2022-03-07 18:53:25 +01:00
MichaelDvP
6bbe2687ef autoformatting, check HS circuits 2022-03-06 10:41:04 +01:00
MichaelDvP
7168f6c75e version b8, package update 2022-03-01 17:31:30 +01:00
MichaelDvP
57582aa90d add boiler_data_ww 2022-03-01 14:47:57 +01:00
MichaelDvP
732dced999 resort tags, boiler ww extra 2022-02-28 16:13:32 +01:00
MichaelDvP
eb9df59f15 remove master_thermostat 2022-02-28 12:44:29 +01:00
382 changed files with 29525 additions and 40397 deletions

View File

@@ -1,35 +1,50 @@
--- ---
name: Bug report name: Problem Report
about: Create a report to help us improve about: Create a Report to help us improve
title: ''
labels: bug
assignees: ''
--- ---
*Before creating a new issue please check that you have:* <!-- Thanks for reporting a problem for this project. READ THIS FIRST:
* *searched the existing [issues](https://github.com/emsesp/EMS-ESP32/issues) (both open and closed)* Please DO NOT OPEN AN ISSUE if your EMS-ESP version is not the latest from the dev branch, please update your device before submitting your issue. Your problem might already be solved. The latest precompiled binaries of EMS-ESP can be downloaded from https://github.com/emsesp/EMS-ESP32/releases/tag/latest
* *searched the [documentation help section](https://emsesp.github.io/docs)*
*Completing this template will help developers and contributors to address the issue. Try to be as specific and extensive as possible. If the information provided is not enough the issue will likely be closed.* Please take a few minutes to complete the requested information below.
*You can now remove this line and the above ones. Text in italic is meant to be replaced by your own words. If any of the sections below are not relevant to the issue (for instance, the screenshots) then you can delete them.* -->
**Bug description** ### PROBLEM DESCRIPTION
*A clear and concise description of what the bug is. Mention which EMS-ESP version you're using.*
**Steps to reproduce** _A clear and concise description of what the problem is._
*Steps to reproduce the behavior.*
**Expected behavior** ### REQUESTED INFORMATION
*A clear and concise description of what you expected to happen.*
**Screenshots** _Make sure your have performed every step and checked the applicable boxes before submitting your issue. Thank you!_
*If applicable, add screenshots to help explain your problem.*
**Device information** - [ ] Searched the problem in [issues](https://github.com/emsesp/EMS-ESP32/issues)
*Copy-paste here the information as it is outputted by the device. You can get this information by from http://ems-esp.local/api/system* - [ ] Searched the problem in [discussions](https://github.com/emsesp/EMS-ESP32/discussions)
- [ ] Searched the problem in the [docs](https://emsesp.github.io/docs/Troubleshooting/)
- [ ] Searched the problem in the [chat](https://discord.gg/3J3GgnzpyT)
- [ ] Provide the output of http://ems-esp.local/api/system :
**Additional context** ```lua
*Add any other context about the problem here.* System information output here:
```
### TO REPRODUCE
_Steps to reproduce the behavior:_
### EXPECTED BEHAVIOUR
_A clear and concise description of what you expected to happen._
### SCREENSHOTS
_If applicable, add screenshots to help explain your problem._
### ADDITIONAL CONTEXT
_Add any other context about the problem here._
**(Please, remember to close the issue when the problem has been addressed)**

11
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,11 @@
blank_issues_enabled: false
contact_links:
- name: EMS-ESP Docs
url: https://emsesp.github.io/docs/
about: All the information related to EMS-ESP.
- name: EMS-ESP Discussions and Support
url: https://github.com/emsesp/EMS-ESP32/discussions
about: EMS-ESP usage Questions, Feature Requests and Projects.
- name: EMS-ESP Users Chat
url: https://discord.gg/3J3GgnzpyT
about: Chat for feedback, questions and troubleshooting.

View File

@@ -1,26 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
*Before creating a new feature request please check that you have searched the existing [issues](https://github.com/emsesp/EMS-ESP32/issues) (both open and closed)*
*Completing this template will help developers and contributors evaluating the feature. If the information provided is not enough the issue will likely be closed.*
*You can now remove this line and the above ones. Text in italic is meant to be replaced by your own words. If any of the sections below are not relevant to the request then you can delete them.*
**Is your feature request related to a problem? Please describe.**
*A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]*
**Describe the solution you'd like**
*A clear and concise description of what you want to happen.*
**Describe alternatives you've considered**
*A clear and concise description of any alternative solutions or features you've considered.*
**Additional context**
*Add any other context or screenshots about the feature request here.*

View File

@@ -1,29 +0,0 @@
---
name: Questions & Troubleshooting
about: Anything not a bug or feature request
title: ''
labels: question
assignees: ''
---
*Before creating a new issue please check that you have:*
* *searched the existing [issues](https://github.com/emsesp/EMS-ESP32/issues) (both open and closed)*
* *searched the [documentation help section](https://emsesp.github.io/docs)*
*Completing this template will help developers and contributors help you. Try to be as specific and extensive as possible. If the information provided is not enough the issue will likely be closed.*
*You can now remove this line and the above ones. Text in italic is meant to be replaced by your own words. If any of the sections below are not relevant to the issue (for instance, the screenshots) then you can delete them.*
**Question**
*A clear and concise description of what the problem/doubt is.*
**Screenshots**
*If applicable, add screenshots to help explain your problem.*
**Device information**
*Copy-paste here the information as it is outputted by the device. You can get this information from http://ems-esp.local/api/system*
**Additional context**
*Add any other context about the problem here.*

View File

@@ -14,9 +14,9 @@ jobs:
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: actions/setup-python@v2 - uses: actions/setup-python@v4
- uses: actions/setup-node@v2 - uses: actions/setup-node@v3
with: with:
node-version: '16' node-version: '16'
@@ -24,19 +24,19 @@ jobs:
id: build_info id: build_info
run: | run: |
version=`grep -E '^#define EMSESP_APP_VERSION' ./src/version.h | awk -F'"' '{print $2}'` version=`grep -E '^#define EMSESP_APP_VERSION' ./src/version.h | awk -F'"' '{print $2}'`
echo "::set-output name=version::$version" echo "VERSION=$version" >> $GITHUB_OUTPUT
- name: Install PlatformIO - name: Install PlatformIO
run: | run: |
python -m pip install --upgrade pip python -m pip install --upgrade pip
pip install -U platformio pip install -U platformio
platformio upgrade
platformio update
- name: Build WebUI - name: Build WebUI
run: | run: |
cd interface cd interface
npm ci npm ci
npx typesafe-i18n --no-watch
sed -i "s/= 'pl'/= 'en'/" ./src/i18n/i18n-util.ts
npm run build npm run build
- name: Build firmware - name: Build firmware
@@ -48,7 +48,7 @@ jobs:
uses: "marvinpinto/action-automatic-releases@latest" uses: "marvinpinto/action-automatic-releases@latest"
with: with:
repo_token: "${{ secrets.GITHUB_TOKEN }}" repo_token: "${{ secrets.GITHUB_TOKEN }}"
title: Development Build v${{steps.build_info.outputs.version}} title: Development Build v${{steps.build_info.outputs.VERSION}}
automatic_release_tag: "latest" automatic_release_tag: "latest"
prerelease: true prerelease: true
files: | files: |

View File

@@ -9,9 +9,10 @@ jobs:
build: build:
name: Build name: Build
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.repository_owner == 'emsesp'
# if: github.repository == 'emsesp/EMS-ESP32'
env: env:
# https://binaries.sonarsource.com/?prefix=Distribution/sonar-scanner-cli/ # https://binaries.sonarsource.com/?prefix=Distribution/sonar-scanner-cli/
# SONAR_SCANNER_VERSION: 4.6.1.2450
SONAR_SCANNER_VERSION: 4.7.0.2747 SONAR_SCANNER_VERSION: 4.7.0.2747
SONAR_SERVER_URL: "https://sonarcloud.io" SONAR_SERVER_URL: "https://sonarcloud.io"
BUILD_WRAPPER_OUT_DIR: build_wrapper_output_directory BUILD_WRAPPER_OUT_DIR: build_wrapper_output_directory

View File

@@ -24,12 +24,14 @@ jobs:
python -m pip install --upgrade pip python -m pip install --upgrade pip
pip install -U platformio pip install -U platformio
platformio upgrade platformio upgrade
platformio update pio pkg update
- name: Build WebUI - name: Build WebUI
run: | run: |
cd interface cd interface
npm ci npm ci
npx typesafe-i18n --no-watch
sed -i "s/= 'pl'/= 'en'/" ./src/i18n/i18n-util.ts
npm run build npm run build
- name: Build firmware - name: Build firmware

12
.gitignore vendored
View File

@@ -13,7 +13,6 @@ debug.log
# platformio # platformio
.pio .pio
pio_local.ini pio_local.ini
/.VSCodeCounter
# OS specific # OS specific
.DS_Store .DS_Store
@@ -31,7 +30,18 @@ test.sh
scripts/__pycache__ scripts/__pycache__
.temp .temp
# i18n generated files
interface/src/i18n/i18n-react.tsx
interface/src/i18n/i18n-types.ts
interface/src/i18n/i18n-util.ts
interface/src/i18n/i18n-util.sync.ts
interface/src/i18n/i18n-util.async.ts
# sonar # sonar
.scannerwork/ .scannerwork/
sonar/ sonar/
build_wrapper_output_directory/ build_wrapper_output_directory/
# other build files
dump_entities.csv
dump_entities.xls*

View File

@@ -5,6 +5,124 @@ 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/), 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). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
# [3.5.1] March 11 2023
## Added
- Detect old Tado thermostat, device-id 0x19, no entities
- Some more HM200 entities [#500](https://github.com/emsesp/EMS-ESP32/issues/500)
- Add entity to force heating off (for systems without thermostat) [#951](https://github.com/emsesp/EMS-ESP32/issues/951)
## Fixed
- HA-discovery for analog sensor commands [#1035](https://github.com/emsesp/EMS-ESP32/issues/1035)
## Changed
- Use byte 0 for detection RC30 active heatingcircuit [#786](https://github.com/emsesp/EMS-ESP32/issues/786)
- Write repeated selflowtemp if tx-queue is empty without verify [#954](https://github.com/emsesp/EMS-ESP32/issues/954)
- HA discovery recreate after disconnect by device [#1067](https://github.com/emsesp/EMS-ESP32/issues/1067)
- File upload: check flash size (overflow) instead of filesize
# [3.5.0] February 6 2023
## **IMPORTANT! BREAKING CHANGES**
- When upgrading to v3.5 for the first time from v3.4 on a BBQKees Gateway board you will need to use the [EMS-EPS Flasher](https://github.com/emsesp/EMS-ESP-Flasher/releases) to correctly re-partition the flash. Make sure you backup the settings and customizations from the WebUI (System->Upload/Download) and restore after the upgrade.
- Support for multiple EMS-ESPs [#759] has been added as an optional setting for MQTT. When enabled, which is now the default, all MQTT Discovery Entity IDs will include the MQTT base name and the shortname of the EMS-ESP device entity. For example what was previously `sensor.boiler_actual_boiler_temperature` will now become `sensor.ems_esp_boiler_boiltemp`. If you still want to use the old format and retain the history and script compatibility in Home Assistant then set this back to the old format.
## Added
- Translations in Web UI and all device entity names (DE, NL, SV, PL, NO, FR) [#22](https://github.com/emsesp/EMS-ESP32/issues/22)
- Add support for Lolin C3 mini [#620](https://github.com/emsesp/EMS-ESP32/pull/620)
- Add support for ESP32-S2 [#667](https://github.com/emsesp/EMS-ESP32/pull/667)
- Add devices: Greenstar 30Ri boiler, Junkers FW500 thermostat, Buderus BC30 controller
- Add program memory info
- Add mqtt queue and connection infos
- Adapt min/max if ems-value is not in this range
- Add heat pump settings for inputs and limits [#600](https://github.com/emsesp/EMS-ESP32/issues/600)
- Add hybrid heatpump [#500](https://github.com/emsesp/EMS-ESP32/issues/500)
- Add translated tags
- Add min/max to customization table [#686](https://github.com/emsesp/EMS-ESP32/issues/686)
- Add MD5 check [#637](https://github.com/emsesp/EMS-ESP32/issues/637)
- Add more bus-ids [#673](https://github.com/emsesp/EMS-ESP32/issues/673)
- Use HA connectivity device class for Status, added boot time [#751](https://github.com/emsesp/EMS-ESP32/issues/751)
- Add commands for analog sensors outputs
- Support for multiple EMS-ESPs with MQTT and HA [[#759](https://github.com/emsesp/EMS-ESP32/issues/759)]
- Settings for heatpump silent mode and additional heater [[#802](https://github.com/emsesp/EMS-ESP32/issues/802)] [[#803](https://github.com/emsesp/EMS-ESP32/issues/803)]
- Zone module MZ100 [#826](https://github.com/emsesp/EMS-ESP32/issues/826)
- Default MQTT hostname is blank [#829](https://github.com/emsesp/EMS-ESP32/issues/829)
- wwCurFlow for ems+ devices [#829](https://github.com/emsesp/EMS-ESP32/issues/829)
- Add Rego 3000, TR120RF thermostats [#917](https://github.com/emsesp/EMS-ESP32/issues/917)
- Add config for ESP32-S3
- Add heatpump silent mode and other entities [#896](https://github.com/emsesp/EMS-ESP32/issues/896)
- Allow reboot to other partition (factory or asymetric OTA)
- Blacklist entities to remove from memory [#891](https://github.com/emsesp/EMS-ESP32/issues/891)
- Add boiler pump operating mode [#944](https://github.com/emsesp/EMS-ESP32/issues/944)
## Fixed
- Factory Reset not working [#628](https://github.com/emsesp/EMS-ESP32/issues/628)
- Valid 4 byte values [#820](https://github.com/emsesp/EMS-ESP32/issues/820)
- Commands for multiple thermostats [#826](https://github.com/emsesp/EMS-ESP32/issues/826)
- API queries for multiple devices [#865](https://github.com/emsesp/EMS-ESP32/issues/865)
- Console crash when using call with command `hcx` only. [#841](https://github.com/emsesp/EMS-ESP32/issues/841)
- `heatingPump2Mod` was wrong, changed to absBurnPow [[#908](https://github.com/emsesp/EMS-ESP32/issues/908)
- Rounding of web input values
- Analog sensor with single gpio number [#915](https://github.com/emsesp/EMS-ESP32/issues/915)
- HA dallas and analog configs: remove/rebuild on change [#888](https://github.com/emsesp/EMS-ESP32/issues/888)
- Modes and set seltemp for RC30 and RC20 [#932](https://github.com/emsesp/EMS-ESP32/issues/932)
## Changed
- Discovery in HomeAssistant don't work with custom base topic. [#596](https://github.com/emsesp/EMS-ESP32/issues/596) Base topic containing `/` are changed to `_`
- RF room temperature sensor are shown as thermostat
- Render mqtt float json values with trailing zero
- Removed flash strings, to increase available heap memory
- Reload page after restart button is pressed
- Analog/dallas values command as list like ems-devices
- Analog/dallas HA-entities based on id
- MQTT Base is a mandatory field. Removed MQTT topic length from settings
- HA duration class for time entities [[#822](https://github.com/emsesp/EMS-ESP32/issues/822)
- AM200 alternative heatsource as class heatsource [[#857](https://github.com/emsesp/EMS-ESP32/issues/857)
# [3.4.2] September 18 2022
## Added
- RC310 additions [#520](https://github.com/emsesp/EMS-ESP32/pull/520)
- damping
- wwprio for RC310 heating circuits
- switchonoptimization for RC310 heating circuits
- enum_controlmode for RC310 (new enum list)
- nofrostmode, reducemode, reducetemp & noreducetemp for RC310
- emergencyops and emergencytemp, wwmaxtemp, wwflowtempoffset and wwcomfort1 for RC310
- HM200 hybrid module [#500](https://github.com/emsesp/EMS-ESP32/issues/500)
- AM200 alternative heatsource module [#573](https://github.com/emsesp/EMS-ESP32/issues/573)
- EM10 error module as gateway [#575](https://github.com/emsesp/EMS-ESP32/issues/575)
## Fixed
- 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)
## Changed
- Shorten "friendly names" in Home Assistant [#555](https://github.com/emsesp/EMS-ESP32/issues/555)
- platformio 2.3.0 (IDF 4, Arduino 2)
- remove master-thermostat, support multiple thermostats
- merge up- and download in webui [#577](https://github.com/emsesp/EMS-ESP32/issues/577)
# [3.4.1] May 29 2022
## Fixed
- Fix memory leak in api [#524](https://github.com/emsesp/EMS-ESP32/issues/524)
## Changed
# [3.4.0] May 23 2022 # [3.4.0] May 23 2022
## Added ## Added
@@ -270,51 +388,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Added ## Added
- power settings, disabling BLE and turning off Wifi sleep
- Rx and Tx counts to Heartbeat MQTT payload
- ethernet support
- id to info command to show only a heatingcircuit
- add sending devices that are not listed to 0x07
- extra MQTT boolean option for "ON" and "OFF"
- support for chunked MQTT payloads to allow large data sets > 2kb
- external Button support (#708) for resetting to factory defaults and other actions
- new console set command in `system`, `set board_profile <profile>` for quickly enabling cabled ethernet connections without using the captive wifi portal
- added in MQTT nested mode, for thermostat and mixer, like we had back in v2
- cascade MC400 (product-id 210) (3.0.0b6), power values for heating sources (3.0.1b1)
- values for wwMaxPower, wwFlowtempOffset
- RC300 `thermostat temp -1` to clear temporary setpoint in auto mode
- syslog port selectable (#744)
- individual mqtt commands (#31) - individual mqtt commands (#31)
- board Profiles (#11) - board Profiles (#11)
## Fixed ## Fixed
- telegrams matched to masterthermostat 0x18
- multiple roomcontrollers
- readback after write with delay (give ems-devices time to set the value)
- thermostat ES72/RC20 device 66 to command-set RC20_2
- MQTT payloads not adding to queue when MQTT is re-connecting (fixes #369)
- fix for HA topics with invalid command formats (#728)
- wrong position of values #723, #732
- OTA Upload via Web on OSX
- Rx and Tx quality % would sometimes show > 100 - Rx and Tx quality % would sometimes show > 100
## Changed ## Changed
- changed how telegram parameters are rendered for mqtt, console and web (#632)
- split `show values` in smaller packages (edited)
- extended length of IP/hostname from 32 to 48 chars (#676)
- check flowsensor for `tap_water_active`
- mqtt prefixed with `Base`
- count Dallas sensor fails
- switch from SPIFFS to LITTLEFS
- added ID to MQTT payloads which is the Device's product ID and used in HA to identify a unique HA device
- increased MQTT buffer and reduced wait time between publishes
- updated to the latest ArduinoJson library
- some names of mqtt-tags like in v2.2.1
- new ESP32 partition side to allow for smoother OTA and fallback
- network Gateway IP is optional (#682)emsesp/EMS-ESP
- moved to a new GitHub repo https://github.com/emsesp/EMS-ESP32
- invert LED changed to Hide LED. Default is off. - invert LED changed to Hide LED. Default is off.
- renamed Scan Network to Scan WiFi Network - renamed Scan Network to Scan WiFi Network
- added version to cmd=settings - added version to cmd=settings

View File

@@ -0,0 +1,2 @@
# Changelog

View File

@@ -1,7 +1,7 @@
# #
# GNUMakefile for EMS-ESP # GNUMakefile for EMS-ESP
# (c) 2020 Paul Derbyshire
# #
NUMJOBS=${NUMJOBS:-" -j4 "} NUMJOBS=${NUMJOBS:-" -j4 "}
MAKEFLAGS+="j " MAKEFLAGS+="j "
#---------------------------------------------------------------------- #----------------------------------------------------------------------
@@ -17,23 +17,30 @@ MAKEFLAGS+="j "
#TARGET := $(notdir $(CURDIR)) #TARGET := $(notdir $(CURDIR))
TARGET := emsesp TARGET := emsesp
BUILD := build BUILD := build
SOURCES := src src/* lib_standalone lib/uuid-common/src lib/uuid-console/src lib/uuid-log/src src/devices lib/ArduinoJson/src lib/PButton SOURCES := src src/* lib_standalone lib/uuid-common/src lib/uuid-console/src lib/uuid-log/src src/devices lib/ArduinoJson/src lib/PButton lib/semver
INCLUDES := src lib_standalone lib/ArduinoJson/src lib/uuid-common/src lib/uuid-console/src lib/uuid-log/src lib/uuid-telnet/src lib/uuid-syslog/src lib/* src/devices INCLUDES := src lib_standalone lib/ArduinoJson/src lib/uuid-common/src lib/uuid-console/src lib/uuid-log/src lib/uuid-telnet/src lib/uuid-syslog/src lib/semver lib/* src/devices
LIBRARIES := LIBRARIES :=
CPPCHECK = cppcheck CPPCHECK = cppcheck
# CHECKFLAGS = -q --force --std=c++17
CHECKFLAGS = -q --force --std=c++11 CHECKFLAGS = -q --force --std=c++11
#---------------------------------------------------------------------- #----------------------------------------------------------------------
# Languages Standard # Languages Standard
#---------------------------------------------------------------------- #----------------------------------------------------------------------
# C_STANDARD := -std=c17
# CXX_STANDARD := -std=c++17
C_STANDARD := -std=c11 C_STANDARD := -std=c11
CXX_STANDARD := -std=c++11 CXX_STANDARD := -std=c++11
#---------------------------------------------------------------------- #----------------------------------------------------------------------
# Defined Symbols # Defined Symbols
#---------------------------------------------------------------------- #----------------------------------------------------------------------
DEFINES += -DFACTORY_WIFI_HOSTNAME=\"ems-esp\" -DARDUINOJSON_ENABLE_STD_STRING=1 -DARDUINOJSON_ENABLE_PROGMEM=1 -DARDUINOJSON_ENABLE_ARDUINO_STRING -DARDUINOJSON_USE_DOUBLE=0 -DEMSESP_DEBUG -DEMSESP_STANDALONE -DEMSESP_USE_SERIAL -DEMSESP_DEFAULT_BOARD_PROFILE=\"LOLIN\" DEFINES += -DARDUINOJSON_ENABLE_STD_STRING=1 -DARDUINOJSON_ENABLE_PROGMEM=1 -DARDUINOJSON_ENABLE_ARDUINO_STRING -DARDUINOJSON_USE_DOUBLE=0
DEFINES += -DEMSESP_DEBUG -DEMSESP_STANDALONE -DEMSESP_USE_SERIAL
DEFINES += $(ARGS)
DEFAULTS = -DEMSESP_DEFAULT_LOCALE=\"en\" -DEMSESP_DEFAULT_TX_MODE=8 -DEMSESP_DEFAULT_VERSION=\"3.5.0b11\" -DEMSESP_DEFAULT_BOARD_PROFILE=\"S32\"
#---------------------------------------------------------------------- #----------------------------------------------------------------------
# Sources & Files # Sources & Files
@@ -66,7 +73,7 @@ CXX := /usr/bin/g++
# CXXFLAGS C++ Compiler Flags # CXXFLAGS C++ Compiler Flags
# LDFLAGS Linker Flags # LDFLAGS Linker Flags
#---------------------------------------------------------------------- #----------------------------------------------------------------------
CPPFLAGS += $(DEFINES) $(INCLUDE) CPPFLAGS += $(DEFINES) $(DEFAULTS) $(INCLUDE)
CPPFLAGS += -ggdb CPPFLAGS += -ggdb
CPPFLAGS += -g3 CPPFLAGS += -g3
CPPFLAGS += -Os CPPFLAGS += -Os
@@ -114,6 +121,8 @@ COMPILE.cpp = $(CXX) $(CXX_STANDARD) $(CXXFLAGS) $(DEPFLAGS) -c $< -o $@
# Targets # Targets
#---------------------------------------------------------------------- #----------------------------------------------------------------------
.PHONY: all .PHONY: all
.SILENT: $(OUTPUT)
all: $(OUTPUT) all: $(OUTPUT)
$(OUTPUT): $(OBJS) $(OUTPUT): $(OBJS)
@@ -141,7 +150,7 @@ run: $(OUTPUT)
.PHONY: clean .PHONY: clean
clean: clean:
@$(RM) -r $(BUILD) $(OUTPUT) @$(RM) -rf $(BUILD) $(OUTPUT)
help: help:
@echo available targets: all run clean @echo available targets: all run clean

131
README.md
View File

@@ -1,17 +1,5 @@
# ![logo](media/EMS-ESP_logo_dark.png) # ![logo](media/EMS-ESP_logo_dark.png)
**EMS-ESP** is an open-source firmware for the Espressif ESP8266 and ESP32 microcontroller that communicates with **EMS** (Energy Management System) based equipment from manufacturers like Bosch, Buderus, Nefit, Junkers, Worcester and Sieger.
This project is the specifically for the ESP32. Compared with the previous ESP8266 (version 2) release it has the following enhancements:
- Ethernet Support
- Pre-configured circuit board layouts
- Supports writing EMS values directly from within Web UI
- Mock API server for faster offline development and testing
- Improved API and MQTT commands
- Improvements to Dallas temperature sensors
- Embedded log tracing in the Web UI
[![version](https://img.shields.io/github/release/emsesp/EMS-ESP32.svg?label=Latest%20Release)](https://github.com/emsesp/EMS-ESP32/blob/main/CHANGELOG.md) [![version](https://img.shields.io/github/release/emsesp/EMS-ESP32.svg?label=Latest%20Release)](https://github.com/emsesp/EMS-ESP32/blob/main/CHANGELOG.md)
[![release-date](https://img.shields.io/github/release-date/emsesp/EMS-ESP32.svg?label=Released)](https://github.com/emsesp/EMS-ESP32/commits/main) [![release-date](https://img.shields.io/github/release-date/emsesp/EMS-ESP32.svg?label=Released)](https://github.com/emsesp/EMS-ESP32/commits/main)
[![license](https://img.shields.io/github/license/emsesp/EMS-ESP32.svg)](LICENSE) [![license](https://img.shields.io/github/license/emsesp/EMS-ESP32.svg)](LICENSE)
@@ -20,34 +8,54 @@ This project is the specifically for the ESP32. Compared with the previous ESP82
[![downloads](https://img.shields.io/github/downloads/emsesp/EMS-ESP32/total.svg)](https://github.com/emsesp/EMS-ESP32/releases) [![downloads](https://img.shields.io/github/downloads/emsesp/EMS-ESP32/total.svg)](https://github.com/emsesp/EMS-ESP32/releases)
[![chat](https://img.shields.io/discord/816637840644505620.svg?style=flat-square&color=blueviolet)](https://discord.gg/3J3GgnzpyT) [![chat](https://img.shields.io/discord/816637840644505620.svg?style=flat-square&color=blueviolet)](https://discord.gg/3J3GgnzpyT)
If you like **EMS-ESP**, please give it a star, or fork it and contribute!
[![GitHub stars](https://img.shields.io/github/stars/emsesp/EMS-ESP32.svg?style=social&label=Star)](https://github.com/emsesp/EMS-ESP32/stargazers) [![GitHub stars](https://img.shields.io/github/stars/emsesp/EMS-ESP32.svg?style=social&label=Star)](https://github.com/emsesp/EMS-ESP32/stargazers)
[![GitHub forks](https://img.shields.io/github/forks/emsesp/EMS-ESP32.svg?style=social&label=Fork)](https://github.com/emsesp/EMS-ES32P/network) [![GitHub forks](https://img.shields.io/github/forks/emsesp/EMS-ESP32.svg?style=social&label=Fork)](https://github.com/emsesp/EMS-ES32P/network)
[![donate](https://img.shields.io/badge/donate-PayPal-blue.svg)](https://www.paypal.com/paypalme/prderbyshire/2) [![donate](https://img.shields.io/badge/donate-PayPal-blue.svg)](https://www.paypal.com/paypalme/prderbyshire/2)
Note, EMS-ESP requires a small hardware circuit that can convert the EMS bus data to be read by the microcontroller. These can be ordered at <https://bbqkees-electronics.nl> or contact the contributors that can provide the schematic and designs. **EMS-ESP** is an open-source firmware for the Espressif ESP32 microcontroller that communicates with **EMS** (Energy Management System) based equipment from manufacturers like Bosch, Buderus, Nefit, Junkers, Worcester and Sieger. It requires a small gateway circuit to interface with the EMS bus which can be purchased from <https://bbqkees-electronics.nl> or custom built.
<img src="media/gateway-integration.jpg" width=40%> ## **Features**
--- - A multi-user, multi-language secure web interface to change settings and monitor incoming data
# **Features**
- A multi-user secure web interface to change settings and monitor incoming data
- A console, accessible via Serial and Telnet for more advanced monitoring - A console, accessible via Serial and Telnet for more advanced monitoring
- Native support for Home Assistant and Domoticz via [MQTT Discovery](https://www.home-assistant.io/docs/mqtt/discovery/) - Native support for Home Assistant, Domoticz and openHAB via [MQTT Discovery](https://www.home-assistant.io/docs/mqtt/discovery/)
- Can run standalone as an independent WiFi Access Point or join an existing WiFi network - Can run standalone as an independent WiFi Access Point or join an existing WiFi network
- Easy first-time configuration via a web Captive Portal - Easy first-time configuration via a web Captive Portal
- Support for more than [100 EMS devices](https://emsesp.github.io/docs/#/Supported-EMS-Devices) (boilers, thermostats, solar modules, mixer modules, heat pumps, gateways) - Support for more than [110 EMS devices](https://emsesp.github.io/docs/#/Supported-EMS-Devices) (boilers, thermostats, solar modules, mixer modules, heat pumps, gateways, switches, heat sources)
## **Documentation**
For the complete documentation on how to install, configure and get support visit the [EMS-ESP Wiki](https://emsesp.github.io/docs).
## **Support**
To chat with the community reach out on our [Discord Server](https://discord.gg/3J3GgnzpyT).
If you like **EMS-ESP**, please give it a star, or fork it and contribute or offer a small donation!
## **Demo** ## **Demo**
See a demo [here](https://ems-esp.derbyshire.nl). Log in with any username/password. For a live demo of the Web UI click [here](https://ems-esp.derbyshire.nl) and log in with any username/password.
# **Screenshots** ## **Contributors ✨**
## Web Interface EMS-ESP is a project owned and maintained by [proddy](https://github.com/proddy) and [MichaelDvP](https://github.com/MichaelDvP).
## **Libraries used**
- [esp8266-react](https://github.com/rjwats/esp8266-react) by @rjwats for the framework that provides the core of the Web UI
- [uuid-\*](https://github.com/nomis/mcu-uuid-console) from @nomis. The console, syslog, telnet and logging are based off these open source libraries
- [ArduinoJson](https://github.com/bblanchon/ArduinoJson) for all the JSON
- [AsyncMqttClient](https://github.com/marvinroger/async-mqtt-client) for the MQTT client, with custom modifications from @bertmelis and @proddy
- ESPAsyncWebServer and AsyncTCP for the Web server and TCP backends, with custom modifications for performance
## **License**
This program is licensed under GPL-3.0
## **Screenshots**
### Web Interface
| | | | | |
| ---------------------------------- | -------------------------------- | | ---------------------------------- | -------------------------------- |
@@ -55,75 +63,10 @@ See a demo [here](https://ems-esp.derbyshire.nl). Log in with any username/passw
| <img src="media/web_devices.png"> | <img src="media/web_mqtt.png"> | | <img src="media/web_devices.png"> | <img src="media/web_mqtt.png"> |
| <img src="media/web_edit.png"> | <img src="media/web_log.png"> | | <img src="media/web_edit.png"> | <img src="media/web_log.png"> |
## Telnet Console ### Telnet Console
<img src="media/console.png" width=80% height=80%> <img src="media/console0.png" width=80% height=80%>
## In Home Assistant ### In Home Assistant
<img src="media/ha_lovelace.png" width=80% height=80%> <img src="media/ha_lovelace.png" width=80% height=80%>
# **Installing**
Refer to the [official documentation](https://emsesp.github.io/docs) to how to install the firmware and configure it. The documentation is being constantly updated as new features and settings are added.
You can choose to use an pre-built firmware image or compile the code yourself:
- [Uploading a pre-built firmware build](https://emsesp.github.io/docs/#/Uploading-firmware)
- [Building the firmware from source code and flashing manually](https://emsesp.github.io/docs/#/Building-firmware)
# **Support Information**
If you're looking for support on **EMS-ESP** there are some options available:
## Documentation
- [Official EMS-ESP Documentation](https://emsesp.github.io/docs): For information on how to build and upload the firmware
- [FAQ and Troubleshooting](https://emsesp.github.io/docs/#/Troubleshooting): For information on common problems and solutions. See also [BBQKees's wiki](https://bbqkees-electronics.nl/wiki/gateway/troubleshooting.html)
## Support Community
- [Discord Server](https://discord.gg/3J3GgnzpyT): For support, troubleshooting and general questions. You have better chances to get fast answers from members of the community
- [Search in Issues](https://github.com/emsesp/EMS-ESP32/issues): You might find an answer to your question by searching current or closed issues
## Developer's Community
- [Bug Report](https://github.com/emsesp/EMS-ESP32/issues/new?template=bug_report.md): For reporting Bugs
- [Feature Request](https://github.com/emsesp/EMS-ESP32/issues/new?template=feature_request.md): For requesting features/functions
- [Troubleshooting](https://github.com/emsesp/EMS-ESP32/issues/new?template=questions---troubleshooting.md): As a last resort, you can open new _Troubleshooting & Question_ issue on GitHub if the solution could not be found using the other channels. Just remember: the more info you provide the more chances you'll have to get an accurate answer
# **Contributors ✨**
EMS-ESP is a project originally created and owned by [proddy](https://github.com/proddy). Key contributors are:
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<table>
<tr>
<td align="center">
<a href="https://github.com/MichaelDvP"><img src="https://avatars.githubusercontent.com/u/59284019?v=3?s=100" width="100px;" alt=""/><br /><sub><b>MichaelDvP</b></sub></a><br /></a> <a href="https://github.com/emsesp/EMS-ESP/commits?author=MichaelDvP" title="v2 Commits">v2</a>
<a href="https://github.com/emsesp/EMS-ESP32/commits?author=MichaelDvP" title="v3 Commits">v3</a>
</td>
</tr>
</table>
<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
You can also contribute to EMS-ESP by
- providing Pull Requests (Features, Fixes, suggestions)
- testing new released features and report issues on your EMS equipment
- contributing to missing [Documentation](https://emsesp.github.io/docs)
# **Libraries used**
- [esp8266-react](https://github.com/rjwats/esp8266-react) by @rjwats for the framework that provides the core of the Web UI
- [uuid-\*](https://github.com/nomis/mcu-uuid-console) from @nomis. The console, syslog, telnet and logging are based off these open source libraries
- [ArduinoJson](https://github.com/bblanchon/ArduinoJson) for JSON
- [AsyncMqttClient](https://github.com/marvinroger/async-mqtt-client) for the MQTT client, with custom modifications from @bertmelis and @proddy
- ESPAsyncWebServer and AsyncTCP for the Web server and TCP backends, with custom modifications for performance
# **License**
This program is licensed under GPL-3.0

View File

@@ -3,4 +3,3 @@
# Firmware Installation # Firmware Installation
Follow the instructions in the [documentation](https://emsesp.github.io/docs) on how to install the firmware binaries in the Assets below. Follow the instructions in the [documentation](https://emsesp.github.io/docs) on how to install the firmware binaries in the Assets below.

View File

@@ -5,4 +5,3 @@ This is a snapshot of the current "beta" development code and firmware binaries
# Firmware Installation # Firmware Installation
Follow the instructions in the [documentation](https://emsesp.github.io/docs) on how to install the firmware binaries in the Assets below. Follow the instructions in the [documentation](https://emsesp.github.io/docs) on how to install the firmware binaries in the Assets below.

6
esp32_partition_16M.csv Normal file
View File

@@ -0,0 +1,6 @@
# 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,
1 # Name Type SubType Offset Size Flags
2 nvs data nvs 0x9000 0x5000
3 otadata data ota 0x2000
4 app0 app ota_0 0x7F0000
5 app1 app ota_1 0x7F0000
6 spiffs data spiffs 64K

6
esp32_partition_4M.csv Normal file
View File

@@ -0,0 +1,6 @@
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, , 0x2000,
app0, app, ota_0, , 0x1F0000,
app1, app, ota_1, , 0x1F0000,
spiffs, data, spiffs, , 64K,
1 # Name Type SubType Offset Size Flags
2 nvs data nvs 0x9000 0x5000
3 otadata data ota 0x2000
4 app0 app ota_0 0x1F0000
5 app1 app ota_1 0x1F0000
6 spiffs data spiffs 64K

View File

@@ -1,6 +0,0 @@
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xE000, 0x2000,
app0, app, ota_0, 0x10000, 0x1F0000,
app1, app, ota_1, 0x200000, 0x1F0000,
spiffs, data, spiffs, 0x3F0000, 0x10000,
1 # Name Type SubType Offset Size Flags
2 nvs data nvs 0x9000 0x5000
3 otadata data ota 0xE000 0x2000
4 app0 app ota_0 0x10000 0x1F0000
5 app1 app ota_1 0x200000 0x1F0000
6 spiffs data spiffs 0x3F0000 0x10000

View File

@@ -7,8 +7,8 @@ build_flags =
; Access point settings ; Access point settings
-D FACTORY_AP_PROVISION_MODE=AP_MODE_DISCONNECTED -D FACTORY_AP_PROVISION_MODE=AP_MODE_DISCONNECTED
-D FACTORY_AP_SSID=\"ems-esp\" ; 1-64 characters -D FACTORY_AP_SSID=\"ems-esp\"
-D FACTORY_AP_PASSWORD=\"ems-esp-neo\" ; 8-64 characters -D FACTORY_AP_PASSWORD=\"ems-esp-neo\"
-D FACTORY_AP_LOCAL_IP=\"192.168.4.1\" -D FACTORY_AP_LOCAL_IP=\"192.168.4.1\"
-D FACTORY_AP_GATEWAY_IP=\"192.168.4.1\" -D FACTORY_AP_GATEWAY_IP=\"192.168.4.1\"
-D FACTORY_AP_SUBNET_MASK=\"255.255.255.0\" -D FACTORY_AP_SUBNET_MASK=\"255.255.255.0\"
@@ -28,11 +28,11 @@ build_flags =
; OTA settings ; OTA settings
-D FACTORY_OTA_PORT=8266 -D FACTORY_OTA_PORT=8266
-D FACTORY_OTA_PASSWORD=\"ems-esp-neo\" -D FACTORY_OTA_PASSWORD=\"ems-esp-neo\"
-D FACTORY_OTA_ENABLED=true -D FACTORY_OTA_ENABLED=false
; MQTT settings ; MQTT settings
-D FACTORY_MQTT_ENABLED=false -D FACTORY_MQTT_ENABLED=false
-D FACTORY_MQTT_HOST=\"test.mosquitto.org\" -D FACTORY_MQTT_HOST=\"\"
-D FACTORY_MQTT_PORT=1883 -D FACTORY_MQTT_PORT=1883
-D FACTORY_MQTT_USERNAME=\"\" -D FACTORY_MQTT_USERNAME=\"\"
-D FACTORY_MQTT_PASSWORD=\"\" -D FACTORY_MQTT_PASSWORD=\"\"

View File

@@ -0,0 +1,5 @@
{
"adapter": "react",
"baseLocale": "pl",
"$schema": "https://unpkg.com/typesafe-i18n@5.24.2/schema/typesafe-i18n.json"
}

19824
interface/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,36 +1,35 @@
{ {
"name": "EMS-ESP", "name": "EMS-ESP",
"version": "3.4.0", "version": "3.5.0",
"private": true, "private": true,
"proxy": "http://localhost:3080", "proxy": "http://localhost:3080",
"dependencies": { "dependencies": {
"@emotion/react": "^11.9.0", "@emotion/react": "^11.10.5",
"@emotion/styled": "^11.8.1", "@emotion/styled": "^11.10.5",
"@msgpack/msgpack": "^2.7.2", "@msgpack/msgpack": "^2.8.0",
"@mui/icons-material": "^5.8.0", "@mui/icons-material": "^5.11.0",
"@mui/material": "^5.8.1", "@mui/material": "^5.11.7",
"@table-library/react-table-library": "^3.1.2", "@table-library/react-table-library": "4.0.24",
"@types/lodash": "^4.14.182", "@types/lodash": "^4.14.191",
"@types/node": "^17.0.35", "@types/node": "^18.11.19",
"@types/react": "^18.0.9", "@types/react": "^18.0.27",
"@types/react-dom": "^18.0.5", "@types/react-dom": "^18.0.10",
"@types/react-router-dom": "^5.3.3", "@types/react-router-dom": "^5.3.3",
"async-validator": "^4.1.1", "async-validator": "^4.2.5",
"axios": "^0.27.2", "axios": "^1.3.2",
"http-proxy-middleware": "^2.0.6",
"jwt-decode": "^3.1.2", "jwt-decode": "^3.1.2",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"notistack": "^2.0.5", "notistack": "^2.0.8",
"parse-ms": "^3.0.0", "react": "^18.2.0",
"react": "^18.1.0",
"react-app-rewired": "^2.2.1", "react-app-rewired": "^2.2.1",
"react-dom": "^18.1.0", "react-dom": "^18.2.0",
"react-dropzone": "^14.2.1", "react-dropzone": "^14.2.3",
"react-icons": "^4.3.1", "react-icons": "^4.7.1",
"react-router-dom": "^6.3.0", "react-router-dom": "^6.8.1",
"react-scripts": "5.0.1", "react-scripts": "5.0.1",
"sockette": "^2.0.6", "sockette": "^2.0.6",
"typescript": "^4.6.4" "typesafe-i18n": "^5.24.0",
"typescript": "^4.9.5"
}, },
"scripts": { "scripts": {
"start": "react-app-rewired start", "start": "react-app-rewired start",
@@ -41,8 +40,9 @@
"build-hosted": "env-cmd -f .env.hosted npm run build", "build-hosted": "env-cmd -f .env.hosted npm run build",
"build-localhost": "PUBLIC_URL=/ react-app-rewired build", "build-localhost": "PUBLIC_URL=/ react-app-rewired build",
"mock-api": "nodemon --watch ../mock-api ../mock-api/server.js", "mock-api": "nodemon --watch ../mock-api ../mock-api/server.js",
"standalone": "npm-run-all -p start mock-api", "standalone": "npm-run-all -p start typesafe-i18n mock-api",
"lint": "eslint . --ext .ts,.tsx" "lint": "eslint . --ext .ts,.tsx",
"typesafe-i18n": "typesafe-i18n"
}, },
"eslintConfig": { "eslintConfig": {
"extends": [ "extends": [
@@ -78,7 +78,7 @@
"max-len": [ "max-len": [
1, 1,
{ {
"code": 200 "code": 220
} }
], ],
"arrow-parens": 1 "arrow-parens": 1
@@ -97,7 +97,8 @@
] ]
}, },
"devDependencies": { "devDependencies": {
"nodemon": "^2.0.16", "nodemon": "^2.0.20",
"npm-run-all": "^4.1.5" "npm-run-all": "^4.1.5",
"http-proxy-middleware": "^2.0.6"
} }
} }

View File

@@ -10,8 +10,9 @@
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
src: local('Roboto'), local('Roboto-Regular'), url(../fonts/re.woff2) format('woff2'); src: local('Roboto'), local('Roboto-Regular'), url(../fonts/re.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, unicode-range: U+0000-00FF, U+0104-0107, U+0118-0119, U+0131, U+0141-0144, U+0152-0153, U+015A-015B, U+0179-017C,
U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF,
U+FFFD;
} }
@font-face { @font-face {
@@ -19,6 +20,7 @@
font-style: normal; font-style: normal;
font-weight: 500; font-weight: 500;
src: local('Roboto Medium'), local('Roboto-Medium'), url(../fonts/md.woff2) format('woff2'); src: local('Roboto Medium'), local('Roboto-Medium'), url(../fonts/md.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, unicode-range: U+0000-00FF, U+0104-0107, U+0118-0119, U+0131, U+0141-0144, U+0152-0153, U+015A-015B, U+0179-017C,
U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF,
U+FFFD;
} }

Binary file not shown.

Binary file not shown.

View File

@@ -1,4 +1,4 @@
import { FC, createRef, createContext, useContext, RefObject } from 'react'; import { FC, createRef, createContext, useContext, useEffect, useState, RefObject } from 'react';
import { SnackbarProvider } from 'notistack'; import { SnackbarProvider } from 'notistack';
import { IconButton } from '@mui/material'; import { IconButton } from '@mui/material';
@@ -9,6 +9,13 @@ import { FeaturesLoader } from './contexts/features';
import CustomTheme from './CustomTheme'; import CustomTheme from './CustomTheme';
import AppRouting from './AppRouting'; import AppRouting from './AppRouting';
import { localStorageDetector } from 'typesafe-i18n/detectors';
import TypesafeI18n from './i18n/i18n-react';
import { detectLocale } from './i18n/i18n-util';
import { loadLocaleAsync } from './i18n/i18n-util.async';
const detectedLocale = detectLocale(localStorageDetector);
const App: FC = () => { const App: FC = () => {
const notistackRef: RefObject<any> = createRef(); const notistackRef: RefObject<any> = createRef();
@@ -20,24 +27,34 @@ const App: FC = () => {
const colorMode = useContext(ColorModeContext); const colorMode = useContext(ColorModeContext);
const [wasLoaded, setWasLoaded] = useState(false);
useEffect(() => {
loadLocaleAsync(detectedLocale).then(() => setWasLoaded(true));
}, []);
if (!wasLoaded) return null;
return ( return (
<ColorModeContext.Provider value={colorMode}> <ColorModeContext.Provider value={colorMode}>
<CustomTheme> <TypesafeI18n locale={detectedLocale}>
<SnackbarProvider <CustomTheme>
maxSnack={3} <SnackbarProvider
anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }} maxSnack={3}
ref={notistackRef} anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
action={(key) => ( ref={notistackRef}
<IconButton onClick={onClickDismiss(key)} size="small"> action={(key) => (
<CloseIcon /> <IconButton onClick={onClickDismiss(key)} size="small">
</IconButton> <CloseIcon />
)} </IconButton>
> )}
<FeaturesLoader> >
<AppRouting /> <FeaturesLoader>
</FeaturesLoader> <AppRouting />
</SnackbarProvider> </FeaturesLoader>
</CustomTheme> </SnackbarProvider>
</CustomTheme>
</TypesafeI18n>
</ColorModeContext.Provider> </ColorModeContext.Provider>
); );
}; };

View File

@@ -2,6 +2,8 @@ import { FC, useContext, useEffect } from 'react';
import { Navigate, Routes, Route, useLocation } from 'react-router-dom'; import { Navigate, Routes, Route, useLocation } from 'react-router-dom';
import { useSnackbar, VariantType } from 'notistack'; import { useSnackbar, VariantType } from 'notistack';
import { useI18nContext } from './i18n/i18n-react';
import { Authentication, AuthenticationContext } from './contexts/authentication'; import { Authentication, AuthenticationContext } from './contexts/authentication';
import { FeaturesContext } from './contexts/features'; import { FeaturesContext } from './contexts/features';
import { RequireAuthenticated, RequireUnauthenticated } from './components'; import { RequireAuthenticated, RequireUnauthenticated } from './components';
@@ -41,13 +43,14 @@ export const RemoveTrailingSlashes = () => {
const AppRouting: FC = () => { const AppRouting: FC = () => {
const { features } = useContext(FeaturesContext); const { features } = useContext(FeaturesContext);
const { LL } = useI18nContext();
return ( return (
<Authentication> <Authentication>
<RemoveTrailingSlashes /> <RemoveTrailingSlashes />
<Routes> <Routes>
<Route path="/unauthorized" element={<RootRedirect message="Please sign in to continue" signOut />} /> <Route path="/unauthorized" element={<RootRedirect message={LL.PLEASE_SIGNIN()} signOut />} />
<Route path="/fileUpdated" element={<RootRedirect message="Upload successful" variant="success" />} /> <Route path="/fileUpdated" element={<RootRedirect message={LL.UPLOAD_SUCCESSFUL()} variant="success" />} />
{features.security && ( {features.security && (
<Route <Route
path="/" path="/"

View File

@@ -2,20 +2,30 @@ import { FC, useContext, useState } from 'react';
import { ValidateFieldsError } from 'async-validator'; import { ValidateFieldsError } from 'async-validator';
import { useSnackbar } from 'notistack'; import { useSnackbar } from 'notistack';
import { Box, Fab, Paper, Typography } from '@mui/material'; import { Box, Fab, Paper, Typography, Button } from '@mui/material';
import ForwardIcon from '@mui/icons-material/Forward'; import ForwardIcon from '@mui/icons-material/Forward';
import * as AuthenticationApi from './api/authentication'; import * as AuthenticationApi from './api/authentication';
import { PROJECT_NAME } from './api/env'; import { PROJECT_NAME } from './api/env';
import { AuthenticationContext } from './contexts/authentication'; import { AuthenticationContext } from './contexts/authentication';
import { AxiosError } from 'axios';
import { extractErrorMessage, onEnterCallback, updateValue } from './utils'; import { extractErrorMessage, onEnterCallback, updateValue } from './utils';
import { SignInRequest } from './types'; import { SignInRequest } from './types';
import { ValidatedTextField } from './components'; import { ValidatedTextField } from './components';
import { SIGN_IN_REQUEST_VALIDATOR, validate } from './validators'; import { SIGN_IN_REQUEST_VALIDATOR, validate } from './validators';
import { I18nContext } from './i18n/i18n-react';
import type { Locales } from './i18n/i18n-types';
import { loadLocaleAsync } from './i18n/i18n-util.async';
import { ReactComponent as NLflag } from './i18n/NL.svg';
import { ReactComponent as DEflag } from './i18n/DE.svg';
import { ReactComponent as GBflag } from './i18n/GB.svg';
import { ReactComponent as SVflag } from './i18n/SV.svg';
import { ReactComponent as PLflag } from './i18n/PL.svg';
import { ReactComponent as NOflag } from './i18n/NO.svg';
import { ReactComponent as FRflag } from './i18n/FR.svg';
const SignIn: FC = () => { const SignIn: FC = () => {
const authenticationContext = useContext(AuthenticationContext); const authenticationContext = useContext(AuthenticationContext);
const { enqueueSnackbar } = useSnackbar(); const { enqueueSnackbar } = useSnackbar();
@@ -31,6 +41,9 @@ const SignIn: FC = () => {
const validateAndSignIn = async () => { const validateAndSignIn = async () => {
setProcessing(true); setProcessing(true);
SIGN_IN_REQUEST_VALIDATOR.messages({
required: LL.IS_REQUIRED('%s')
});
try { try {
await validate(SIGN_IN_REQUEST_VALIDATOR, signInRequest); await validate(SIGN_IN_REQUEST_VALIDATOR, signInRequest);
signIn(); signIn();
@@ -44,13 +57,13 @@ const SignIn: FC = () => {
try { try {
const { data: loginResponse } = await AuthenticationApi.signIn(signInRequest); const { data: loginResponse } = await AuthenticationApi.signIn(signInRequest);
authenticationContext.signIn(loginResponse.access_token); authenticationContext.signIn(loginResponse.access_token);
} catch (error: unknown) { } catch (error) {
if (error instanceof AxiosError) { if (error.response) {
if (error.response?.status === 401) { if (error.response?.status === 401) {
enqueueSnackbar('Invalid login details', { variant: 'warning' }); enqueueSnackbar(LL.INVALID_LOGIN(), { variant: 'warning' });
} }
} else { } else {
enqueueSnackbar(extractErrorMessage(error, 'Unexpected error, please try again'), { variant: 'error' }); enqueueSnackbar(extractErrorMessage(error, LL.ERROR()), { variant: 'error' });
} }
setProcessing(false); setProcessing(false);
} }
@@ -58,6 +71,14 @@ const SignIn: FC = () => {
const submitOnEnter = onEnterCallback(signIn); const submitOnEnter = onEnterCallback(signIn);
const { LL, setLocale, locale } = useContext(I18nContext);
const selectLocale = async (loc: Locales) => {
localStorage.setItem('lang', loc);
await loadLocaleAsync(loc);
setLocale(loc);
};
return ( return (
<Box <Box
display="flex" display="flex"
@@ -81,11 +102,49 @@ const SignIn: FC = () => {
})} })}
> >
<Typography variant="h4">{PROJECT_NAME}</Typography> <Typography variant="h4">{PROJECT_NAME}</Typography>
<Box
sx={{
'& button, & a, & .MuiCard-root': {
mt: 0.5,
mx: 0.5
}
}}
>
<Button size="small" variant={locale === 'en' ? 'contained' : 'outlined'} onClick={() => selectLocale('en')}>
<GBflag style={{ width: 24 }} />
&nbsp;EN
</Button>
<Button size="small" variant={locale === 'de' ? 'contained' : 'outlined'} onClick={() => selectLocale('de')}>
<DEflag style={{ width: 24 }} />
&nbsp;DE
</Button>
<Button size="small" variant={locale === 'fr' ? 'contained' : 'outlined'} onClick={() => selectLocale('fr')}>
<FRflag style={{ width: 24 }} />
&nbsp;FR
</Button>
<Button size="small" variant={locale === 'nl' ? 'contained' : 'outlined'} onClick={() => selectLocale('nl')}>
<NLflag style={{ width: 24 }} />
&nbsp;NL
</Button>
<Button size="small" variant={locale === 'no' ? 'contained' : 'outlined'} onClick={() => selectLocale('no')}>
<NOflag style={{ width: 24 }} />
&nbsp;NO
</Button>
<Button size="small" variant={locale === 'pl' ? 'contained' : 'outlined'} onClick={() => selectLocale('pl')}>
<PLflag style={{ width: 24 }} />
&nbsp;PL
</Button>
<Button size="small" variant={locale === 'sv' ? 'contained' : 'outlined'} onClick={() => selectLocale('sv')}>
<SVflag style={{ width: 24 }} />
&nbsp;SV
</Button>
</Box>
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
disabled={processing} disabled={processing}
name="username" name="username"
label="Username" label={LL.USERNAME(0)}
value={signInRequest.username} value={signInRequest.username}
onChange={updateLoginRequestValue} onChange={updateLoginRequestValue}
margin="normal" margin="normal"
@@ -97,7 +156,7 @@ const SignIn: FC = () => {
disabled={processing} disabled={processing}
type="password" type="password"
name="password" name="password"
label="Password" label={LL.PASSWORD()}
value={signInRequest.password} value={signInRequest.password}
onChange={updateLoginRequestValue} onChange={updateLoginRequestValue}
onKeyDown={submitOnEnter} onKeyDown={submitOnEnter}
@@ -107,7 +166,7 @@ const SignIn: FC = () => {
/> />
<Fab variant="extended" color="primary" sx={{ mt: 2 }} onClick={validateAndSignIn} disabled={processing}> <Fab variant="extended" color="primary" sx={{ mt: 2 }} onClick={validateAndSignIn} disabled={processing}>
<ForwardIcon sx={{ mr: 1 }} /> <ForwardIcon sx={{ mr: 1 }} />
Sign In {LL.SIGN_IN()}
</Fab> </Fab>
</Paper> </Paper>
</Box> </Box>

View File

@@ -1,4 +1,4 @@
import axios, { AxiosPromise, CancelToken } from 'axios'; import axios, { AxiosPromise, CancelToken, AxiosProgressEvent } from 'axios';
import { decode } from '@msgpack/msgpack'; import { decode } from '@msgpack/msgpack';
@@ -89,7 +89,7 @@ function calculateEventSourceRoot(endpointPath: string) {
export interface FileUploadConfig { export interface FileUploadConfig {
cancelToken?: CancelToken; cancelToken?: CancelToken;
onUploadProgress?: (progressEvent: ProgressEvent) => void; onUploadProgress?: (progressEvent: AxiosProgressEvent) => void;
} }
export const startUploadFile = (url: string, file: File, config?: FileUploadConfig): AxiosPromise<void> => { export const startUploadFile = (url: string, file: File, config?: FileUploadConfig): AxiosPromise<void> => {

View File

@@ -11,6 +11,6 @@ export function readMqttSettings(): AxiosPromise<MqttSettings> {
return AXIOS.get('/mqttSettings'); return AXIOS.get('/mqttSettings');
} }
export function updateMqttSettings(ntpSettings: MqttSettings): AxiosPromise<MqttSettings> { export function updateMqttSettings(mqttSettings: MqttSettings): AxiosPromise<MqttSettings> {
return AXIOS.post('/mqttSettings', ntpSettings); return AXIOS.post('/mqttSettings', mqttSettings);
} }

View File

@@ -12,6 +12,10 @@ export function restart(): AxiosPromise<void> {
return AXIOS.post('/restart'); return AXIOS.post('/restart');
} }
export function partition(): AxiosPromise<void> {
return AXIOS.post('/partition');
}
export function factoryReset(): AxiosPromise<void> { export function factoryReset(): AxiosPromise<void> {
return AXIOS.post('/factoryReset'); return AXIOS.post('/factoryReset');
} }
@@ -38,4 +42,3 @@ export function updateLogSettings(logSettings: LogSettings): AxiosPromise<LogSet
export function readLogEntries(): AxiosPromise<LogEntries> { export function readLogEntries(): AxiosPromise<LogEntries> {
return AXIOS_BIN.get('/fetchLog'); return AXIOS_BIN.get('/fetchLog');
} }

View File

@@ -1,12 +1,36 @@
import { FC, useState, useContext } from 'react'; import { FC, useState, useContext, ChangeEventHandler } from 'react';
import { Box, Button, Divider, IconButton, Popover, Typography, Avatar, styled, TypographyProps } from '@mui/material'; import {
Box,
Button,
Divider,
IconButton,
Popover,
Typography,
Avatar,
styled,
TypographyProps,
MenuItem,
TextField
} from '@mui/material';
import PersonIcon from '@mui/icons-material/Person'; import PersonIcon from '@mui/icons-material/Person';
import AccountCircleIcon from '@mui/icons-material/AccountCircle'; import AccountCircleIcon from '@mui/icons-material/AccountCircle';
import { AuthenticatedContext } from '../../contexts/authentication'; import { AuthenticatedContext } from '../../contexts/authentication';
import { I18nContext } from '../../i18n/i18n-react';
import type { Locales } from '../../i18n/i18n-types';
import { loadLocaleAsync } from '../../i18n/i18n-util.async';
import { ReactComponent as NLflag } from '../../i18n/NL.svg';
import { ReactComponent as DEflag } from '../../i18n/DE.svg';
import { ReactComponent as GBflag } from '../../i18n/GB.svg';
import { ReactComponent as SVflag } from '../../i18n/SV.svg';
import { ReactComponent as PLflag } from '../../i18n/PL.svg';
import { ReactComponent as NOflag } from '../../i18n/NO.svg';
import { ReactComponent as FRflag } from '../../i18n/FR.svg';
const ItemTypography = styled(Typography)<TypographyProps>({ const ItemTypography = styled(Typography)<TypographyProps>({
maxWidth: '250px', maxWidth: '250px',
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
@@ -23,6 +47,15 @@ const LayoutAuthMenu: FC = () => {
setAnchorEl(event.currentTarget); setAnchorEl(event.currentTarget);
}; };
const { locale, LL, setLocale } = useContext(I18nContext);
const onLocaleSelected: ChangeEventHandler<HTMLInputElement> = async ({ target }) => {
const loc = target.value as Locales;
localStorage.setItem('lang', loc);
await loadLocaleAsync(loc);
setLocale(loc);
};
const handleClose = () => { const handleClose = () => {
setAnchorEl(null); setAnchorEl(null);
}; };
@@ -32,7 +65,53 @@ const LayoutAuthMenu: FC = () => {
return ( return (
<> <>
<IconButton id="open-auth-menu" sx={{ padding: 0 }} aria-describedby={id} color="inherit" onClick={handleClick}> <TextField
name="locale"
InputProps={{ style: { fontSize: 10 } }}
variant="outlined"
value={locale}
onChange={onLocaleSelected}
size="small"
select
>
<MenuItem key="en" value="en">
<GBflag style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;EN
</MenuItem>
<Divider />
<MenuItem key="de" value="de">
<DEflag style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;DE
</MenuItem>
<MenuItem key="fr" value="fr">
<FRflag style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;FR
</MenuItem>
<MenuItem key="nl" value="nl">
<NLflag style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;NL
</MenuItem>
<MenuItem key="no" value="no">
<NOflag style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;NO
</MenuItem>
<MenuItem key="pl" value="pl">
<PLflag style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;PL
</MenuItem>
<MenuItem key="sv" value="sv">
<SVflag style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;SV
</MenuItem>
</TextField>
<IconButton
id="open-auth-menu"
sx={{ ml: 1, padding: 0 }}
aria-describedby={id}
color="inherit"
onClick={handleClick}
>
<AccountCircleIcon /> <AccountCircleIcon />
</IconButton> </IconButton>
<Popover <Popover
@@ -56,13 +135,15 @@ const LayoutAuthMenu: FC = () => {
</Avatar> </Avatar>
<Box pl={2}> <Box pl={2}>
<ItemTypography variant="h6">{me.username}</ItemTypography> <ItemTypography variant="h6">{me.username}</ItemTypography>
<ItemTypography variant="body1">{me.admin ? 'Admin User' : 'Guest User'}</ItemTypography> <ItemTypography variant="body1">
{me.admin ? LL.ADMIN() : LL.GUEST()}&nbsp;{LL.USER(2)}
</ItemTypography>
</Box> </Box>
</Box> </Box>
<Divider /> <Divider />
<Box p={1.5}> <Box p={1.5}>
<Button variant="outlined" fullWidth color="primary" onClick={() => signOut(true)}> <Button variant="outlined" fullWidth color="primary" onClick={() => signOut(true)}>
Sign Out {LL.SIGN_OUT()}
</Button> </Button>
</Box> </Box>
</Popover> </Popover>

View File

@@ -15,9 +15,12 @@ import ProjectMenu from '../../project/ProjectMenu';
import LayoutMenuItem from './LayoutMenuItem'; import LayoutMenuItem from './LayoutMenuItem';
import { AuthenticatedContext } from '../../contexts/authentication'; import { AuthenticatedContext } from '../../contexts/authentication';
import { useI18nContext } from '../../i18n/i18n-react';
const LayoutMenu: FC = () => { const LayoutMenu: FC = () => {
const { features } = useContext(FeaturesContext); const { features } = useContext(FeaturesContext);
const authenticatedContext = useContext(AuthenticatedContext); const authenticatedContext = useContext(AuthenticatedContext);
const { LL } = useI18nContext();
return ( return (
<> <>
@@ -28,12 +31,17 @@ const LayoutMenu: FC = () => {
</List> </List>
)} )}
<List disablePadding component="nav"> <List disablePadding component="nav">
<LayoutMenuItem icon={SettingsEthernetIcon} label="Network Connection" to="/network" /> <LayoutMenuItem icon={SettingsEthernetIcon} label={LL.NETWORK(0)} to="/network" />
<LayoutMenuItem icon={SettingsInputAntennaIcon} label="Access Point" to="/ap" /> <LayoutMenuItem icon={SettingsInputAntennaIcon} label={LL.ACCESS_POINT(0)} to="/ap" />
{features.ntp && <LayoutMenuItem icon={AccessTimeIcon} label="Network Time" to="/ntp" />} {features.ntp && <LayoutMenuItem icon={AccessTimeIcon} label="NTP" to="/ntp" />}
{features.mqtt && <LayoutMenuItem icon={DeviceHubIcon} label="MQTT" to="/mqtt" />} {features.mqtt && <LayoutMenuItem icon={DeviceHubIcon} label="MQTT" to="/mqtt" />}
<LayoutMenuItem icon={LockIcon} label="Security" to="/security" disabled={!authenticatedContext.me.admin} /> <LayoutMenuItem
<LayoutMenuItem icon={SettingsIcon} label="System" to="/system" /> icon={LockIcon}
label={LL.SECURITY(0)}
to="/security"
disabled={!authenticatedContext.me.admin}
/>
<LayoutMenuItem icon={SettingsIcon} label={LL.SYSTEM(0)} to="/system" />
</List> </List>
</> </>
); );

View File

@@ -5,6 +5,8 @@ import RefreshIcon from '@mui/icons-material/Refresh';
import { MessageBox } from '..'; import { MessageBox } from '..';
import { useI18nContext } from '../../i18n/i18n-react';
interface FormLoaderProps { interface FormLoaderProps {
message?: string; message?: string;
errorMessage?: string; errorMessage?: string;
@@ -12,12 +14,14 @@ interface FormLoaderProps {
} }
const FormLoader: FC<FormLoaderProps> = ({ errorMessage, onRetry, message = 'Loading…' }) => { const FormLoader: FC<FormLoaderProps> = ({ errorMessage, onRetry, message = 'Loading…' }) => {
const { LL } = useI18nContext();
if (errorMessage) { if (errorMessage) {
return ( return (
<MessageBox my={2} level="error" message={errorMessage}> <MessageBox my={2} level="error" message={errorMessage}>
{onRetry && ( {onRetry && (
<Button startIcon={<RefreshIcon />} variant="contained" color="error" onClick={onRetry}> <Button startIcon={<RefreshIcon />} variant="contained" color="error" onClick={onRetry}>
Retry {LL.RETRY()}
</Button> </Button>
)} )}
</MessageBox> </MessageBox>

View File

@@ -2,23 +2,29 @@ import { FC } from 'react';
import { CircularProgress, Box, Typography, Theme } from '@mui/material'; import { CircularProgress, Box, Typography, Theme } from '@mui/material';
import { useI18nContext } from '../../i18n/i18n-react';
interface LoadingSpinnerProps { interface LoadingSpinnerProps {
height?: number | string; height?: number | string;
} }
const LoadingSpinner: FC<LoadingSpinnerProps> = ({ height = '100%' }) => ( const LoadingSpinner: FC<LoadingSpinnerProps> = ({ height = '100%' }) => {
<Box display="flex" alignItems="center" justifyContent="center" flexDirection="column" padding={2} height={height}> const { LL } = useI18nContext();
<CircularProgress
sx={(theme: Theme) => ({ return (
margin: theme.spacing(4), <Box display="flex" alignItems="center" justifyContent="center" flexDirection="column" padding={2} height={height}>
color: theme.palette.text.secondary <CircularProgress
})} sx={(theme: Theme) => ({
size={100} margin: theme.spacing(4),
/> color: theme.palette.text.secondary
<Typography variant="h4" color="textSecondary"> })}
Loading&hellip; size={100}
</Typography> />
</Box> <Typography variant="h4" color="textSecondary">
); {LL.LOADING()}&hellip;
</Typography>
</Box>
);
};
export default LoadingSpinner; export default LoadingSpinner;

View File

@@ -1,12 +1,14 @@
import { FC, Fragment } from 'react'; import { FC, Fragment } from 'react';
import { useDropzone, DropzoneState } from 'react-dropzone'; import { useDropzone, DropzoneState } from 'react-dropzone';
import { AxiosProgressEvent } from 'axios';
import { Box, Button, LinearProgress, Theme, Typography, useTheme } from '@mui/material'; import { Box, Button, LinearProgress, Theme, Typography, useTheme } from '@mui/material';
import CloudUploadIcon from '@mui/icons-material/CloudUpload'; import CloudUploadIcon from '@mui/icons-material/CloudUpload';
import CancelIcon from '@mui/icons-material/Cancel'; import CancelIcon from '@mui/icons-material/Cancel';
const progressPercentage = (progress: ProgressEvent) => Math.round((progress.loaded * 100) / progress.total); import { useI18nContext } from '../../i18n/i18n-react';
const getBorderColor = (theme: Theme, props: DropzoneState) => { const getBorderColor = (theme: Theme, props: DropzoneState) => {
if (props.isDragAccept) { if (props.isDragAccept) {
@@ -25,7 +27,7 @@ export interface SingleUploadProps {
onDrop: (acceptedFiles: File[]) => void; onDrop: (acceptedFiles: File[]) => void;
onCancel: () => void; onCancel: () => void;
uploading: boolean; uploading: boolean;
progress?: ProgressEvent; progress?: AxiosProgressEvent;
} }
const SingleUpload: FC<SingleUploadProps> = ({ onDrop, onCancel, uploading, progress }) => { const SingleUpload: FC<SingleUploadProps> = ({ onDrop, onCancel, uploading, progress }) => {
@@ -33,7 +35,8 @@ const SingleUpload: FC<SingleUploadProps> = ({ onDrop, onCancel, uploading, prog
onDrop, onDrop,
accept: { accept: {
'application/octet-stream': ['.bin'], 'application/octet-stream': ['.bin'],
'application/json': ['.json'] 'application/json': ['.json'],
'text/plain': ['.md5']
}, },
disabled: uploading, disabled: uploading,
multiple: false multiple: false
@@ -41,14 +44,16 @@ const SingleUpload: FC<SingleUploadProps> = ({ onDrop, onCancel, uploading, prog
const { getRootProps, getInputProps } = dropzoneState; const { getRootProps, getInputProps } = dropzoneState;
const theme = useTheme(); const theme = useTheme();
const { LL } = useI18nContext();
const progressText = () => { const progressText = () => {
if (uploading) { if (uploading) {
if (progress?.lengthComputable) { if (progress?.total) {
return `Uploading: ${progressPercentage(progress)}%`; return LL.UPLOADING() + ': ' + Math.round((progress.loaded * 100) / progress.total) + '%';
} }
return 'Uploading\u2026'; return LL.UPLOADING() + `\u2026`;
} }
return 'Drop file or click here'; return LL.UPLOAD_DROP_TEXT();
}; };
return ( return (
@@ -60,7 +65,7 @@ const SingleUpload: FC<SingleUploadProps> = ({ onDrop, onCancel, uploading, prog
borderWidth: 2, borderWidth: 2,
borderRadius: 2, borderRadius: 2,
borderStyle: 'dashed', borderStyle: 'dashed',
color: theme.palette.grey[700], color: theme.palette.grey[400],
transition: 'border .24s ease-in-out', transition: 'border .24s ease-in-out',
width: '100%', width: '100%',
cursor: uploading ? 'default' : 'pointer', cursor: uploading ? 'default' : 'pointer',
@@ -76,12 +81,12 @@ const SingleUpload: FC<SingleUploadProps> = ({ onDrop, onCancel, uploading, prog
<Fragment> <Fragment>
<Box width="100%" p={2}> <Box width="100%" p={2}>
<LinearProgress <LinearProgress
variant={!progress || progress.lengthComputable ? 'determinate' : 'indeterminate'} variant={!progress || progress.total ? 'determinate' : 'indeterminate'}
value={!progress ? 0 : progress.lengthComputable ? progressPercentage(progress) : 0} value={!progress ? 0 : progress.total ? Math.round((progress.loaded * 100) / progress.total) : 0}
/> />
</Box> </Box>
<Button startIcon={<CancelIcon />} variant="outlined" color="secondary" onClick={onCancel}> <Button startIcon={<CancelIcon />} variant="outlined" color="secondary" onClick={onCancel}>
Cancel {LL.CANCEL()}
</Button> </Button>
</Fragment> </Fragment>
)} )}

View File

@@ -1,24 +1,30 @@
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import axios, { AxiosPromise, CancelTokenSource } from 'axios'; import axios, { AxiosPromise, CancelTokenSource, AxiosProgressEvent } from 'axios';
import { useSnackbar } from 'notistack'; import { useSnackbar } from 'notistack';
import { extractErrorMessage } from '../../utils'; import { extractErrorMessage } from '../../utils';
import { FileUploadConfig } from '../../api/endpoints'; import { FileUploadConfig } from '../../api/endpoints';
import { useI18nContext } from '../../i18n/i18n-react';
interface MediaUploadOptions { interface MediaUploadOptions {
upload: (file: File, config?: FileUploadConfig) => AxiosPromise<void>; upload: (file: File, config?: FileUploadConfig) => AxiosPromise<void>;
} }
const useFileUpload = ({ upload }: MediaUploadOptions) => { const useFileUpload = ({ upload }: MediaUploadOptions) => {
const { LL } = useI18nContext();
const { enqueueSnackbar } = useSnackbar(); const { enqueueSnackbar } = useSnackbar();
const [uploading, setUploading] = useState<boolean>(false); const [uploading, setUploading] = useState<boolean>(false);
const [uploadProgress, setUploadProgress] = useState<ProgressEvent>(); const [md5, setMd5] = useState<string>('');
const [uploadProgress, setUploadProgress] = useState<AxiosProgressEvent>();
const [uploadCancelToken, setUploadCancelToken] = useState<CancelTokenSource>(); const [uploadCancelToken, setUploadCancelToken] = useState<CancelTokenSource>();
const resetUploadingStates = () => { const resetUploadingStates = () => {
setUploading(false); setUploading(false);
setUploadProgress(undefined); setUploadProgress(undefined);
setUploadCancelToken(undefined); setUploadCancelToken(undefined);
setMd5('');
}; };
const cancelUpload = useCallback(() => { const cancelUpload = useCallback(() => {
@@ -37,23 +43,28 @@ const useFileUpload = ({ upload }: MediaUploadOptions) => {
const cancelToken = axios.CancelToken.source(); const cancelToken = axios.CancelToken.source();
setUploadCancelToken(cancelToken); setUploadCancelToken(cancelToken);
setUploading(true); setUploading(true);
await upload(images[0], { const response = await upload(images[0], {
onUploadProgress: setUploadProgress, onUploadProgress: setUploadProgress,
cancelToken: cancelToken.token cancelToken: cancelToken.token
}); });
resetUploadingStates(); resetUploadingStates();
enqueueSnackbar('File uploaded', { variant: 'success' }); if (response.status === 200) {
} catch (error: unknown) { enqueueSnackbar(LL.UPLOAD() + ' ' + LL.SUCCESSFUL(), { variant: 'success' });
} else if (response.status === 201) {
setMd5(String(response.data));
enqueueSnackbar(LL.UPLOAD() + ' MD5 ' + LL.SUCCESSFUL(), { variant: 'success' });
}
} catch (error) {
if (axios.isCancel(error)) { if (axios.isCancel(error)) {
enqueueSnackbar('Upload aborted', { variant: 'warning' }); enqueueSnackbar(LL.UPLOAD() + ' ' + LL.ABORTED(), { variant: 'warning' });
} else { } else {
resetUploadingStates(); resetUploadingStates();
enqueueSnackbar(extractErrorMessage(error, 'Upload failed'), { variant: 'error' }); enqueueSnackbar(extractErrorMessage(error, LL.UPLOAD() + ' ' + LL.FAILED()), { variant: 'error' });
} }
} }
}; };
return [uploadFile, cancelUpload, uploading, uploadProgress] as const; return [uploadFile, cancelUpload, uploading, uploadProgress, md5] as const;
}; };
export default useFileUpload; export default useFileUpload;

View File

@@ -2,6 +2,8 @@ import { FC, useCallback, useContext, useEffect, useState } from 'react';
import { useSnackbar } from 'notistack'; import { useSnackbar } from 'notistack';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { useI18nContext } from '../../i18n/i18n-react';
import * as AuthenticationApi from '../../api/authentication'; import * as AuthenticationApi from '../../api/authentication';
import { ACCESS_TOKEN } from '../../api/endpoints'; import { ACCESS_TOKEN } from '../../api/endpoints';
import { RequiredChildrenProps } from '../../utils'; import { RequiredChildrenProps } from '../../utils';
@@ -12,6 +14,8 @@ import { AuthenticationContext } from './context';
const Authentication: FC<RequiredChildrenProps> = ({ children }) => { const Authentication: FC<RequiredChildrenProps> = ({ children }) => {
const { features } = useContext(FeaturesContext); const { features } = useContext(FeaturesContext);
const { LL } = useI18nContext();
const navigate = useNavigate(); const navigate = useNavigate();
const { enqueueSnackbar } = useSnackbar(); const { enqueueSnackbar } = useSnackbar();
@@ -23,8 +27,8 @@ const Authentication: FC<RequiredChildrenProps> = ({ children }) => {
AuthenticationApi.getStorage().setItem(ACCESS_TOKEN, accessToken); AuthenticationApi.getStorage().setItem(ACCESS_TOKEN, accessToken);
const decodedMe = AuthenticationApi.decodeMeJWT(accessToken); const decodedMe = AuthenticationApi.decodeMeJWT(accessToken);
setMe(decodedMe); setMe(decodedMe);
enqueueSnackbar(`Logged in as ${decodedMe.username}`, { variant: 'success' }); enqueueSnackbar(LL.LOGGED_IN({ name: decodedMe.username }), { variant: 'success' });
} catch (error: unknown) { } catch (error) {
setMe(undefined); setMe(undefined);
throw new Error('Failed to parse JWT'); throw new Error('Failed to parse JWT');
} }
@@ -50,7 +54,7 @@ const Authentication: FC<RequiredChildrenProps> = ({ children }) => {
await AuthenticationApi.verifyAuthorization(); await AuthenticationApi.verifyAuthorization();
setMe(AuthenticationApi.decodeMeJWT(accessToken)); setMe(AuthenticationApi.decodeMeJWT(accessToken));
setInitialized(true); setInitialized(true);
} catch (error: unknown) { } catch (error) {
setMe(undefined); setMe(undefined);
setInitialized(true); setInitialized(true);
} }

View File

@@ -16,7 +16,7 @@ const FeaturesLoader: FC<RequiredChildrenProps> = (props) => {
try { try {
const response = await FeaturesApi.readFeatures(); const response = await FeaturesApi.readFeatures();
setFeatures(response.data); setFeatures(response.data);
} catch (error: unknown) { } catch (error) {
setErrorMessage(extractErrorMessage(error, 'Failed to fetch application details.')); setErrorMessage(extractErrorMessage(error, 'Failed to fetch application details.'));
} }
}, []); }, []);

View File

@@ -19,6 +19,8 @@ import { APProvisionMode, APSettings } from '../../types';
import { numberValue, updateValue, useRest } from '../../utils'; import { numberValue, updateValue, useRest } from '../../utils';
import * as APApi from '../../api/ap'; import * as APApi from '../../api/ap';
import { useI18nContext } from '../../i18n/i18n-react';
export const isAPEnabled = ({ provision_mode }: APSettings) => { export const isAPEnabled = ({ provision_mode }: APSettings) => {
return provision_mode === APProvisionMode.AP_MODE_ALWAYS || provision_mode === APProvisionMode.AP_MODE_DISCONNECTED; return provision_mode === APProvisionMode.AP_MODE_ALWAYS || provision_mode === APProvisionMode.AP_MODE_DISCONNECTED;
}; };
@@ -29,6 +31,8 @@ const APSettingsForm: FC = () => {
update: APApi.updateAPSettings update: APApi.updateAPSettings
}); });
const { LL } = useI18nContext();
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>(); const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
const updateFormValue = updateValue(setData); const updateFormValue = updateValue(setData);
@@ -53,7 +57,7 @@ const APSettingsForm: FC = () => {
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="provision_mode" name="provision_mode"
label="Provide Access Point&hellip;" label={LL.AP_PROVIDE() + '...'}
value={data.provision_mode} value={data.provision_mode}
fullWidth fullWidth
select select
@@ -61,16 +65,16 @@ const APSettingsForm: FC = () => {
onChange={updateFormValue} onChange={updateFormValue}
margin="normal" margin="normal"
> >
<MenuItem value={APProvisionMode.AP_MODE_ALWAYS}>Always</MenuItem> <MenuItem value={APProvisionMode.AP_MODE_ALWAYS}>{LL.AP_PROVIDE_TEXT_1()}</MenuItem>
<MenuItem value={APProvisionMode.AP_MODE_DISCONNECTED}>When WiFi Disconnected</MenuItem> <MenuItem value={APProvisionMode.AP_MODE_DISCONNECTED}>{LL.AP_PROVIDE_TEXT_2()}</MenuItem>
<MenuItem value={APProvisionMode.AP_NEVER}>Never</MenuItem> <MenuItem value={APProvisionMode.AP_NEVER}>{LL.AP_PROVIDE_TEXT_3()}</MenuItem>
</ValidatedTextField> </ValidatedTextField>
{isAPEnabled(data) && ( {isAPEnabled(data) && (
<> <>
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="ssid" name="ssid"
label="Access Point SSID" label={LL.ACCESS_POINT(2) + ' SSID'}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.ssid} value={data.ssid}
@@ -80,7 +84,7 @@ const APSettingsForm: FC = () => {
<ValidatedPasswordField <ValidatedPasswordField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="password" name="password"
label="Access Point Password" label={LL.ACCESS_POINT(2) + ' ' + LL.PASSWORD()}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.password} value={data.password}
@@ -90,7 +94,7 @@ const APSettingsForm: FC = () => {
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="channel" name="channel"
label="Preferred Channel" label={LL.AP_PREFERRED_CHANNEL()}
value={numberValue(data.channel)} value={numberValue(data.channel)}
fullWidth fullWidth
select select
@@ -107,12 +111,12 @@ const APSettingsForm: FC = () => {
</ValidatedTextField> </ValidatedTextField>
<BlockFormControlLabel <BlockFormControlLabel
control={<Checkbox name="ssid_hidden" checked={data.ssid_hidden} onChange={updateFormValue} />} control={<Checkbox name="ssid_hidden" checked={data.ssid_hidden} onChange={updateFormValue} />}
label="Hide SSID" label={LL.AP_HIDE_SSID()}
/> />
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="max_clients" name="max_clients"
label="Max Clients" label={LL.AP_MAX_CLIENTS()}
value={numberValue(data.max_clients)} value={numberValue(data.max_clients)}
fullWidth fullWidth
select select
@@ -130,7 +134,7 @@ const APSettingsForm: FC = () => {
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="local_ip" name="local_ip"
label="Local IP" label={LL.AP_LOCAL_IP()}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.local_ip} value={data.local_ip}
@@ -140,7 +144,7 @@ const APSettingsForm: FC = () => {
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="gateway_ip" name="gateway_ip"
label="Gateway" label={LL.NETWORK_GATEWAY()}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.gateway_ip} value={data.gateway_ip}
@@ -150,7 +154,7 @@ const APSettingsForm: FC = () => {
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="subnet_mask" name="subnet_mask"
label="Subnet" label={LL.NETWORK_SUBNET()}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.subnet_mask} value={data.subnet_mask}
@@ -168,7 +172,7 @@ const APSettingsForm: FC = () => {
type="submit" type="submit"
onClick={validateAndSubmit} onClick={validateAndSubmit}
> >
Save {LL.SAVE()}
</Button> </Button>
</ButtonRow> </ButtonRow>
</> </>
@@ -176,7 +180,7 @@ const APSettingsForm: FC = () => {
}; };
return ( return (
<SectionContent title="Access Point Settings" titleGutter> <SectionContent title={LL.SETTINGS_OF(LL.ACCESS_POINT(1))} titleGutter>
{content()} {content()}
</SectionContent> </SectionContent>
); );

View File

@@ -11,6 +11,8 @@ import { APNetworkStatus, APStatus } from '../../types';
import { ButtonRow, FormLoader, SectionContent } from '../../components'; import { ButtonRow, FormLoader, SectionContent } from '../../components';
import { useRest } from '../../utils'; import { useRest } from '../../utils';
import { useI18nContext } from '../../i18n/i18n-react';
export const apStatusHighlight = ({ status }: APStatus, theme: Theme) => { export const apStatusHighlight = ({ status }: APStatus, theme: Theme) => {
switch (status) { switch (status) {
case APNetworkStatus.ACTIVE: case APNetworkStatus.ACTIVE:
@@ -24,24 +26,26 @@ export const apStatusHighlight = ({ status }: APStatus, theme: Theme) => {
} }
}; };
export const apStatus = ({ status }: APStatus) => {
switch (status) {
case APNetworkStatus.ACTIVE:
return 'Active';
case APNetworkStatus.INACTIVE:
return 'Inactive';
case APNetworkStatus.LINGERING:
return 'Lingering until idle';
default:
return 'Unknown';
}
};
const APStatusForm: FC = () => { const APStatusForm: FC = () => {
const { loadData, data, errorMessage } = useRest<APStatus>({ read: APApi.readAPStatus }); const { loadData, data, errorMessage } = useRest<APStatus>({ read: APApi.readAPStatus });
const { LL } = useI18nContext();
const theme = useTheme(); const theme = useTheme();
const apStatus = ({ status }: APStatus) => {
switch (status) {
case APNetworkStatus.ACTIVE:
return LL.ACTIVE();
case APNetworkStatus.INACTIVE:
return LL.INACTIVE(0);
case APNetworkStatus.LINGERING:
return 'Lingering until idle';
default:
return LL.UNKNOWN();
}
};
const content = () => { const content = () => {
if (!data) { if (!data) {
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />; return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
@@ -56,14 +60,14 @@ const APStatusForm: FC = () => {
<SettingsInputAntennaIcon /> <SettingsInputAntennaIcon />
</Avatar> </Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText primary="Status" secondary={apStatus(data)} /> <ListItemText primary={LL.STATUS_OF('')} secondary={apStatus(data)} />
</ListItem> </ListItem>
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />
<ListItem> <ListItem>
<ListItemAvatar> <ListItemAvatar>
<Avatar>IP</Avatar> <Avatar>IP</Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText primary="IP Address" secondary={data.ip_address} /> <ListItemText primary={LL.ADDRESS_OF('IP')} secondary={data.ip_address} />
</ListItem> </ListItem>
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />
<ListItem> <ListItem>
@@ -72,7 +76,7 @@ const APStatusForm: FC = () => {
<DeviceHubIcon /> <DeviceHubIcon />
</Avatar> </Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText primary="MAC Address" secondary={data.mac_address} /> <ListItemText primary={LL.ADDRESS_OF('MAC')} secondary={data.mac_address} />
</ListItem> </ListItem>
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />
<ListItem> <ListItem>
@@ -81,13 +85,13 @@ const APStatusForm: FC = () => {
<ComputerIcon /> <ComputerIcon />
</Avatar> </Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText primary="AP Clients" secondary={data.station_num} /> <ListItemText primary={LL.AP_CLIENTS()} secondary={data.station_num} />
</ListItem> </ListItem>
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />
</List> </List>
<ButtonRow> <ButtonRow>
<Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={loadData}> <Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={loadData}>
Refresh {LL.REFRESH()}
</Button> </Button>
</ButtonRow> </ButtonRow>
</> </>
@@ -95,7 +99,7 @@ const APStatusForm: FC = () => {
}; };
return ( return (
<SectionContent title="Access Point Status" titleGutter> <SectionContent title={LL.STATUS_OF(LL.ACCESS_POINT(1))} titleGutter>
{content()} {content()}
</SectionContent> </SectionContent>
); );

View File

@@ -8,8 +8,12 @@ import APStatusForm from './APStatusForm';
import APSettingsForm from './APSettingsForm'; import APSettingsForm from './APSettingsForm';
import { RequireAdmin, RouterTabs, useLayoutTitle, useRouterTab } from '../../components'; import { RequireAdmin, RouterTabs, useLayoutTitle, useRouterTab } from '../../components';
import { useI18nContext } from '../../i18n/i18n-react';
const AccessPoint: FC = () => { const AccessPoint: FC = () => {
useLayoutTitle('Access Point'); const { LL } = useI18nContext();
useLayoutTitle(LL.ACCESS_POINT(0));
const authenticatedContext = useContext(AuthenticatedContext); const authenticatedContext = useContext(AuthenticatedContext);
@@ -18,8 +22,8 @@ const AccessPoint: FC = () => {
return ( return (
<> <>
<RouterTabs value={routerTab}> <RouterTabs value={routerTab}>
<Tab value="status" label="Access Point Status" /> <Tab value="status" label={LL.STATUS_OF(LL.ACCESS_POINT(1))} />
<Tab value="settings" label="Access Point Settings" disabled={!authenticatedContext.me.admin} /> <Tab value="settings" label={LL.SETTINGS_OF(LL.ACCESS_POINT(1))} disabled={!authenticatedContext.me.admin} />
</RouterTabs> </RouterTabs>
<Routes> <Routes>
<Route path="status" element={<APStatusForm />} /> <Route path="status" element={<APStatusForm />} />

View File

@@ -9,7 +9,11 @@ import { AuthenticatedContext } from '../../contexts/authentication';
import MqttStatusForm from './MqttStatusForm'; import MqttStatusForm from './MqttStatusForm';
import MqttSettingsForm from './MqttSettingsForm'; import MqttSettingsForm from './MqttSettingsForm';
import { useI18nContext } from '../../i18n/i18n-react';
const Mqtt: FC = () => { const Mqtt: FC = () => {
const { LL } = useI18nContext();
useLayoutTitle('MQTT'); useLayoutTitle('MQTT');
const authenticatedContext = useContext(AuthenticatedContext); const authenticatedContext = useContext(AuthenticatedContext);
@@ -18,8 +22,8 @@ const Mqtt: FC = () => {
return ( return (
<> <>
<RouterTabs value={routerTab}> <RouterTabs value={routerTab}>
<Tab value="status" label="MQTT Status" /> <Tab value="status" label={LL.STATUS_OF('MQTT')} />
<Tab value="settings" label="MQTT Settings" disabled={!authenticatedContext.me.admin} /> <Tab value="settings" label={LL.SETTINGS_OF('MQTT')} disabled={!authenticatedContext.me.admin} />
</RouterTabs> </RouterTabs>
<Routes> <Routes>
<Route path="status" element={<MqttStatusForm />} /> <Route path="status" element={<MqttStatusForm />} />

View File

@@ -1,10 +1,10 @@
import { FC, useState } from 'react'; import { FC, useState } from 'react';
import { ValidateFieldsError } from 'async-validator'; import { ValidateFieldsError } from 'async-validator';
import { Button, Checkbox, MenuItem, Grid, Typography } from '@mui/material'; import { Button, Checkbox, MenuItem, Grid, Typography, InputAdornment } from '@mui/material';
import SaveIcon from '@mui/icons-material/Save'; import SaveIcon from '@mui/icons-material/Save';
import { MQTT_SETTINGS_VALIDATOR, validate } from '../../validators'; import { createMqttSettingsValidator, validate } from '../../validators';
import { import {
BlockFormControlLabel, BlockFormControlLabel,
ButtonRow, ButtonRow,
@@ -17,12 +17,16 @@ import { MqttSettings } from '../../types';
import { numberValue, updateValue, useRest } from '../../utils'; import { numberValue, updateValue, useRest } from '../../utils';
import * as MqttApi from '../../api/mqtt'; import * as MqttApi from '../../api/mqtt';
import { useI18nContext } from '../../i18n/i18n-react';
const MqttSettingsForm: FC = () => { const MqttSettingsForm: FC = () => {
const { loadData, saving, data, setData, saveData, errorMessage } = useRest<MqttSettings>({ const { loadData, saving, data, setData, saveData, errorMessage } = useRest<MqttSettings>({
read: MqttApi.readMqttSettings, read: MqttApi.readMqttSettings,
update: MqttApi.updateMqttSettings update: MqttApi.updateMqttSettings
}); });
const { LL } = useI18nContext();
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>(); const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
const updateFormValue = updateValue(setData); const updateFormValue = updateValue(setData);
@@ -35,7 +39,7 @@ const MqttSettingsForm: FC = () => {
const validateAndSubmit = async () => { const validateAndSubmit = async () => {
try { try {
setFieldErrors(undefined); setFieldErrors(undefined);
await validate(MQTT_SETTINGS_VALIDATOR, data); await validate(createMqttSettingsValidator(data), data);
saveData(); saveData();
} catch (errors: any) { } catch (errors: any) {
setFieldErrors(errors); setFieldErrors(errors);
@@ -46,14 +50,14 @@ const MqttSettingsForm: FC = () => {
<> <>
<BlockFormControlLabel <BlockFormControlLabel
control={<Checkbox name="enabled" checked={data.enabled} onChange={updateFormValue} />} control={<Checkbox name="enabled" checked={data.enabled} onChange={updateFormValue} />}
label="Enable MQTT" label={LL.ENABLE_MQTT()}
/> />
<Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start"> <Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start">
<Grid item xs={6}> <Grid item xs={6}>
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="host" name="host"
label="Host" label={LL.ADDRESS_OF(LL.BROKER())}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.host} value={data.host}
@@ -80,7 +84,7 @@ const MqttSettingsForm: FC = () => {
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="base" name="base"
label="Bsse" label={LL.BASE_TOPIC()}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.base} value={data.base}
@@ -91,7 +95,7 @@ const MqttSettingsForm: FC = () => {
<Grid item xs={6}> <Grid item xs={6}>
<ValidatedTextField <ValidatedTextField
name="client_id" name="client_id"
label="Client ID (optional)" label={LL.ID_OF(LL.CLIENT()) + ' (' + LL.OPTIONAL() + ')'}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.client_id} value={data.client_id}
@@ -104,7 +108,7 @@ const MqttSettingsForm: FC = () => {
<Grid item xs={6}> <Grid item xs={6}>
<ValidatedTextField <ValidatedTextField
name="username" name="username"
label="Username" label={LL.USERNAME(0)}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.username} value={data.username}
@@ -115,7 +119,7 @@ const MqttSettingsForm: FC = () => {
<Grid item xs={6}> <Grid item xs={6}>
<ValidatedPasswordField <ValidatedPasswordField
name="password" name="password"
label="Password" label={LL.PASSWORD()}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.password} value={data.password}
@@ -129,7 +133,10 @@ const MqttSettingsForm: FC = () => {
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="keep_alive" name="keep_alive"
label="Keep Alive (seconds)" label="Keep Alive"
InputProps={{
endAdornment: <InputAdornment position="end">{LL.SECONDS()}</InputAdornment>
}}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={numberValue(data.keep_alive)} value={numberValue(data.keep_alive)}
@@ -149,7 +156,7 @@ const MqttSettingsForm: FC = () => {
margin="normal" margin="normal"
select select
> >
<MenuItem value={0}>0 (default)</MenuItem> <MenuItem value={0}>0</MenuItem>
<MenuItem value={1}>1</MenuItem> <MenuItem value={1}>1</MenuItem>
<MenuItem value={2}>2</MenuItem> <MenuItem value={2}>2</MenuItem>
</ValidatedTextField> </ValidatedTextField>
@@ -157,18 +164,19 @@ const MqttSettingsForm: FC = () => {
</Grid> </Grid>
<BlockFormControlLabel <BlockFormControlLabel
control={<Checkbox name="clean_session" checked={data.clean_session} onChange={updateFormValue} />} control={<Checkbox name="clean_session" checked={data.clean_session} onChange={updateFormValue} />}
label="Set Clean Session" label={LL.MQTT_CLEAN_SESSION()}
/> />
<BlockFormControlLabel <BlockFormControlLabel
control={<Checkbox name="mqtt_retain" checked={data.mqtt_retain} onChange={updateFormValue} />} control={<Checkbox name="mqtt_retain" checked={data.mqtt_retain} onChange={updateFormValue} />}
label="Always use Retain Flag" label={LL.MQTT_RETAIN_FLAG()}
/> />
<Typography sx={{ pt: 2 }} variant="h6" color="primary"> <Typography sx={{ pt: 2 }} variant="h6" color="primary">
Formatting {LL.FORMATTING()}
</Typography> </Typography>
<ValidatedTextField <ValidatedTextField
name="nested_format" name="nested_format"
label="Topic/Payload Format" label={LL.MQTT_FORMAT()}
value={data.nested_format} value={data.nested_format}
fullWidth fullWidth
variant="outlined" variant="outlined"
@@ -176,19 +184,19 @@ const MqttSettingsForm: FC = () => {
margin="normal" margin="normal"
select select
> >
<MenuItem value={1}>Nested in a single topic</MenuItem> <MenuItem value={1}>{LL.MQTT_NEST_1()}</MenuItem>
<MenuItem value={2}>As individual topics</MenuItem> <MenuItem value={2}>{LL.MQTT_NEST_2()}</MenuItem>
</ValidatedTextField> </ValidatedTextField>
<BlockFormControlLabel <BlockFormControlLabel
control={<Checkbox name="send_response" checked={data.send_response} onChange={updateFormValue} />} control={<Checkbox name="send_response" checked={data.send_response} onChange={updateFormValue} />}
label="Publish command output to a 'response' topic" label={LL.MQTT_RESPONSE()}
/> />
{!data.ha_enabled && ( {!data.ha_enabled && (
<Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start"> <Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start">
<Grid item> <Grid item>
<BlockFormControlLabel <BlockFormControlLabel
control={<Checkbox name="publish_single" checked={data.publish_single} onChange={updateFormValue} />} control={<Checkbox name="publish_single" checked={data.publish_single} onChange={updateFormValue} />}
label="Publish single value topics on change" label={LL.MQTT_PUBLISH_TEXT_1()}
/> />
</Grid> </Grid>
{data.publish_single && ( {data.publish_single && (
@@ -197,7 +205,7 @@ const MqttSettingsForm: FC = () => {
control={ control={
<Checkbox name="publish_single2cmd" checked={data.publish_single2cmd} onChange={updateFormValue} /> <Checkbox name="publish_single2cmd" checked={data.publish_single2cmd} onChange={updateFormValue} />
} }
label="Publish to command topics (ioBroker)" label={LL.MQTT_PUBLISH_TEXT_2()}
/> />
</Grid> </Grid>
)} )}
@@ -207,34 +215,74 @@ const MqttSettingsForm: FC = () => {
<Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start"> <Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start">
<Grid item> <Grid item>
<BlockFormControlLabel <BlockFormControlLabel
sx={{ pb: 1 }}
control={<Checkbox name="ha_enabled" checked={data.ha_enabled} onChange={updateFormValue} />} control={<Checkbox name="ha_enabled" checked={data.ha_enabled} onChange={updateFormValue} />}
label="Enable MQTT Discovery (Home Assistant, Domoticz)" label={LL.MQTT_PUBLISH_TEXT_3()}
/> />
</Grid> </Grid>
{data.ha_enabled && ( {data.ha_enabled && (
<Grid item xs={6}> <>
<ValidatedTextField <Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start">
name="discovery_prefix" <Grid item>
label="Prefix for the Discovery topics" <ValidatedTextField
fullWidth name="discovery_prefix"
variant="outlined" label={LL.MQTT_PUBLISH_TEXT_4()}
value={data.discovery_prefix} fullWidth
onChange={updateFormValue} variant="outlined"
margin="normal" value={data.discovery_prefix}
/> onChange={updateFormValue}
</Grid> margin="normal"
/>
</Grid>
<Grid item>
<ValidatedTextField
name="entity_format"
label={LL.MQTT_ENTITY_FORMAT()}
value={data.entity_format}
fullWidth
variant="outlined"
onChange={updateFormValue}
margin="normal"
select
>
<MenuItem value={0}>{LL.MQTT_ENTITY_FORMAT_0()}</MenuItem>
<MenuItem value={1}>{LL.MQTT_ENTITY_FORMAT_1()}</MenuItem>
<MenuItem value={2}>{LL.MQTT_ENTITY_FORMAT_2()}</MenuItem>
</ValidatedTextField>
</Grid>
</Grid>
</>
)} )}
</Grid> </Grid>
)} )}
<Typography sx={{ pt: 2 }} variant="h6" color="primary"> <Typography sx={{ pt: 2 }} variant="h6" color="primary">
Publish Intervals (in seconds, 0=automatic) {LL.MQTT_PUBLISH_INTERVALS()}&nbsp;(0=auto)
</Typography> </Typography>
<Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start"> <Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start">
<Grid item xs={4}> <Grid item xs={6} sm={4}>
<ValidatedTextField
fieldErrors={fieldErrors}
name="publish_time_heartbeat"
label={LL.MQTT_INT_HEARTBEAT()}
InputProps={{
endAdornment: <InputAdornment position="end">{LL.SECONDS()}</InputAdornment>
}}
fullWidth
variant="outlined"
value={numberValue(data.publish_time_heartbeat)}
type="number"
onChange={updateFormValue}
margin="normal"
/>
</Grid>
<Grid item xs={6} sm={4}>
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="publish_time_boiler" name="publish_time_boiler"
label="Boilers and Heat Pumps" label={LL.MQTT_INT_BOILER()}
InputProps={{
endAdornment: <InputAdornment position="end">{LL.SECONDS()}</InputAdornment>
}}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={numberValue(data.publish_time_boiler)} value={numberValue(data.publish_time_boiler)}
@@ -243,11 +291,14 @@ const MqttSettingsForm: FC = () => {
margin="normal" margin="normal"
/> />
</Grid> </Grid>
<Grid item xs={4}> <Grid item xs={6} sm={4}>
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="publish_time_thermostat" name="publish_time_thermostat"
label="Thermostats" label={LL.MQTT_INT_THERMOSTATS()}
InputProps={{
endAdornment: <InputAdornment position="end">{LL.SECONDS()}</InputAdornment>
}}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={numberValue(data.publish_time_thermostat)} value={numberValue(data.publish_time_thermostat)}
@@ -256,11 +307,14 @@ const MqttSettingsForm: FC = () => {
margin="normal" margin="normal"
/> />
</Grid> </Grid>
<Grid item xs={4}> <Grid item xs={6} sm={4}>
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="publish_time_solar" name="publish_time_solar"
label="Solar Modules" label={LL.MQTT_INT_SOLAR()}
InputProps={{
endAdornment: <InputAdornment position="end">{LL.SECONDS()}</InputAdornment>
}}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={numberValue(data.publish_time_solar)} value={numberValue(data.publish_time_solar)}
@@ -269,11 +323,14 @@ const MqttSettingsForm: FC = () => {
margin="normal" margin="normal"
/> />
</Grid> </Grid>
<Grid item xs={4}> <Grid item xs={6} sm={4}>
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="publish_time_mixer" name="publish_time_mixer"
label="Mixer Modules" label={LL.MQTT_INT_MIXER()}
InputProps={{
endAdornment: <InputAdornment position="end">{LL.SECONDS()}</InputAdornment>
}}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={numberValue(data.publish_time_mixer)} value={numberValue(data.publish_time_mixer)}
@@ -282,11 +339,14 @@ const MqttSettingsForm: FC = () => {
margin="normal" margin="normal"
/> />
</Grid> </Grid>
<Grid item xs={4}> <Grid item xs={6} sm={4}>
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="publish_time_sensor" name="publish_time_sensor"
label="Temperature Sensors" label={LL.TEMP_SENSORS()}
InputProps={{
endAdornment: <InputAdornment position="end">{LL.SECONDS()}</InputAdornment>
}}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={numberValue(data.publish_time_sensor)} value={numberValue(data.publish_time_sensor)}
@@ -295,11 +355,14 @@ const MqttSettingsForm: FC = () => {
margin="normal" margin="normal"
/> />
</Grid> </Grid>
<Grid item xs={4}> <Grid item xs={6} sm={4}>
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="publish_time_other" name="publish_time_other"
label="Default" InputProps={{
endAdornment: <InputAdornment position="end">{LL.SECONDS()}</InputAdornment>
}}
label={LL.DEFAULT(0)}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={numberValue(data.publish_time_other)} value={numberValue(data.publish_time_other)}
@@ -318,7 +381,7 @@ const MqttSettingsForm: FC = () => {
type="submit" type="submit"
onClick={validateAndSubmit} onClick={validateAndSubmit}
> >
Save {LL.SAVE()}
</Button> </Button>
</ButtonRow> </ButtonRow>
</> </>
@@ -326,7 +389,7 @@ const MqttSettingsForm: FC = () => {
}; };
return ( return (
<SectionContent title="MQTT Settings" titleGutter> <SectionContent title={LL.SETTINGS_OF('MQTT')} titleGutter>
{content()} {content()}
</SectionContent> </SectionContent>
); );

View File

@@ -5,12 +5,15 @@ import DeviceHubIcon from '@mui/icons-material/DeviceHub';
import RefreshIcon from '@mui/icons-material/Refresh'; import RefreshIcon from '@mui/icons-material/Refresh';
import ReportIcon from '@mui/icons-material/Report'; import ReportIcon from '@mui/icons-material/Report';
import SpeakerNotesOffIcon from '@mui/icons-material/SpeakerNotesOff'; import SpeakerNotesOffIcon from '@mui/icons-material/SpeakerNotesOff';
import AutoAwesomeMotionIcon from '@mui/icons-material/AutoAwesomeMotion';
import { ButtonRow, FormLoader, SectionContent } from '../../components'; import { ButtonRow, FormLoader, SectionContent } from '../../components';
import { MqttStatus, MqttDisconnectReason } from '../../types'; import { MqttStatus, MqttDisconnectReason } from '../../types';
import * as MqttApi from '../../api/mqtt'; import * as MqttApi from '../../api/mqtt';
import { useRest } from '../../utils'; import { useRest } from '../../utils';
import { useI18nContext } from '../../i18n/i18n-react';
export const mqttStatusHighlight = ({ enabled, connected }: MqttStatus, theme: Theme) => { export const mqttStatusHighlight = ({ enabled, connected }: MqttStatus, theme: Theme) => {
if (!enabled) { if (!enabled) {
return theme.palette.info.main; return theme.palette.info.main;
@@ -29,80 +32,96 @@ export const mqttPublishHighlight = ({ mqtt_fails }: MqttStatus, theme: Theme) =
return theme.palette.error.main; return theme.palette.error.main;
}; };
export const mqttStatus = ({ enabled, connected }: MqttStatus) => { export const mqttQueueHighlight = ({ mqtt_queued }: MqttStatus, theme: Theme) => {
if (!enabled) { if (mqtt_queued <= 1) return theme.palette.success.main;
return 'Not enabled';
}
if (connected) {
return 'Connected';
}
return 'Disconnected';
};
export const disconnectReason = ({ disconnect_reason }: MqttStatus) => { return theme.palette.warning.main;
switch (disconnect_reason) {
case MqttDisconnectReason.TCP_DISCONNECTED:
return 'TCP disconnected';
case MqttDisconnectReason.MQTT_UNACCEPTABLE_PROTOCOL_VERSION:
return 'Unacceptable protocol version';
case MqttDisconnectReason.MQTT_IDENTIFIER_REJECTED:
return 'Client ID rejected';
case MqttDisconnectReason.MQTT_SERVER_UNAVAILABLE:
return 'Server unavailable';
case MqttDisconnectReason.MQTT_MALFORMED_CREDENTIALS:
return 'Malformed credentials';
case MqttDisconnectReason.MQTT_NOT_AUTHORIZED:
return 'Not authorized';
case MqttDisconnectReason.ESP8266_NOT_ENOUGH_SPACE:
return 'Device out of memory';
case MqttDisconnectReason.TLS_BAD_FINGERPRINT:
return 'Server fingerprint invalid';
default:
return 'Unknown';
}
}; };
const MqttStatusForm: FC = () => { const MqttStatusForm: FC = () => {
const { loadData, data, errorMessage } = useRest<MqttStatus>({ read: MqttApi.readMqttStatus }); const { loadData, data, errorMessage } = useRest<MqttStatus>({ read: MqttApi.readMqttStatus });
const { LL } = useI18nContext();
const theme = useTheme(); const theme = useTheme();
const mqttStatus = ({ enabled, connected, connect_count }: MqttStatus) => {
if (!enabled) {
return LL.NOT_ENABLED();
}
if (connected) {
return LL.CONNECTED(0) + (connect_count > 1 ? ' (' + connect_count + ')' : '');
}
return LL.DISCONNECTED() + (connect_count > 1 ? ' (' + connect_count + ')' : '');
};
const disconnectReason = ({ disconnect_reason }: MqttStatus) => {
switch (disconnect_reason) {
case MqttDisconnectReason.TCP_DISCONNECTED:
return 'TCP disconnected';
case MqttDisconnectReason.MQTT_UNACCEPTABLE_PROTOCOL_VERSION:
return 'Unacceptable protocol version';
case MqttDisconnectReason.MQTT_IDENTIFIER_REJECTED:
return 'Client ID rejected';
case MqttDisconnectReason.MQTT_SERVER_UNAVAILABLE:
return 'Server unavailable';
case MqttDisconnectReason.MQTT_MALFORMED_CREDENTIALS:
return 'Malformed credentials';
case MqttDisconnectReason.MQTT_NOT_AUTHORIZED:
return 'Not authorized';
case MqttDisconnectReason.ESP8266_NOT_ENOUGH_SPACE:
return 'Device out of memory';
case MqttDisconnectReason.TLS_BAD_FINGERPRINT:
return 'Server fingerprint invalid';
default:
return 'Unknown';
}
};
const content = () => { const content = () => {
if (!data) { if (!data) {
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />; return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
} }
const renderConnectionStatus = () => { const renderConnectionStatus = () => {
if (data.connected) {
return (
<>
<ListItem>
<ListItemAvatar>
<Avatar>#</Avatar>
</ListItemAvatar>
<ListItemText primary="Client ID" secondary={data.client_id} />
</ListItem>
<Divider variant="inset" component="li" />
<ListItem>
<ListItemAvatar>
<Avatar sx={{ bgcolor: mqttPublishHighlight(data, theme) }}>
<SpeakerNotesOffIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary="MQTT Publish Errors" secondary={data.mqtt_fails} />
</ListItem>
</>
);
}
return ( return (
<> <>
{!data.connected && (
<>
<ListItem>
<ListItemAvatar>
<Avatar>
<ReportIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary={LL.DISCONNECT_REASON()} secondary={disconnectReason(data)} />
</ListItem>
<Divider variant="inset" component="li" />
</>
)}
<ListItem> <ListItem>
<ListItemAvatar> <ListItemAvatar>
<Avatar> <Avatar>#</Avatar>
<ReportIcon /> </ListItemAvatar>
<ListItemText primary={LL.ID_OF(LL.CLIENT())} secondary={data.client_id} />
</ListItem>
<Divider variant="inset" component="li" />
<ListItem>
<ListItemAvatar>
<Avatar sx={{ bgcolor: mqttQueueHighlight(data, theme) }}>
<AutoAwesomeMotionIcon />
</Avatar> </Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText primary="Disconnect Reason" secondary={disconnectReason(data)} /> <ListItemText primary={LL.MQTT_QUEUE()} secondary={data.mqtt_queued} />
</ListItem>
<Divider variant="inset" component="li" />
<ListItem>
<ListItemAvatar>
<Avatar sx={{ bgcolor: mqttPublishHighlight(data, theme) }}>
<SpeakerNotesOffIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary={LL.ERRORS_OF('MQTT')} secondary={data.mqtt_fails} />
</ListItem> </ListItem>
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />
</> </>
@@ -118,14 +137,14 @@ const MqttStatusForm: FC = () => {
<DeviceHubIcon /> <DeviceHubIcon />
</Avatar> </Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText primary="Status" secondary={mqttStatus(data)} /> <ListItemText primary={LL.STATUS_OF('')} secondary={mqttStatus(data)} />
</ListItem> </ListItem>
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />
{data.enabled && renderConnectionStatus()} {data.enabled && renderConnectionStatus()}
</List> </List>
<ButtonRow> <ButtonRow>
<Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={loadData}> <Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={loadData}>
Refresh {LL.REFRESH()}
</Button> </Button>
</ButtonRow> </ButtonRow>
</> </>
@@ -133,7 +152,7 @@ const MqttStatusForm: FC = () => {
}; };
return ( return (
<SectionContent title="MQTT Status" titleGutter> <SectionContent title={LL.STATUS_OF('MQTT')} titleGutter>
{content()} {content()}
</SectionContent> </SectionContent>
); );

View File

@@ -11,12 +11,16 @@ import NetworkStatusForm from './NetworkStatusForm';
import WiFiNetworkScanner from './WiFiNetworkScanner'; import WiFiNetworkScanner from './WiFiNetworkScanner';
import NetworkSettingsForm from './NetworkSettingsForm'; import NetworkSettingsForm from './NetworkSettingsForm';
import { useI18nContext } from '../../i18n/i18n-react';
const NetworkConnection: FC = () => { const NetworkConnection: FC = () => {
useLayoutTitle('Network Connection'); const { LL } = useI18nContext();
useLayoutTitle(LL.NETWORK(0));
const { routerTab } = useRouterTab();
const authenticatedContext = useContext(AuthenticatedContext); const authenticatedContext = useContext(AuthenticatedContext);
const navigate = useNavigate(); const navigate = useNavigate();
const { routerTab } = useRouterTab();
const [selectedNetwork, setSelectedNetwork] = useState<WiFiNetwork>(); const [selectedNetwork, setSelectedNetwork] = useState<WiFiNetwork>();
@@ -41,9 +45,9 @@ const NetworkConnection: FC = () => {
}} }}
> >
<RouterTabs value={routerTab}> <RouterTabs value={routerTab}>
<Tab value="status" label="Network Status" /> <Tab value="status" label={LL.STATUS_OF(LL.NETWORK(1))} />
<Tab value="scan" label="Scan WiFi Networks" disabled={!authenticatedContext.me.admin} /> <Tab value="scan" label={LL.NETWORK_SCAN()} disabled={!authenticatedContext.me.admin} />
<Tab value="settings" label="Network Settings" disabled={!authenticatedContext.me.admin} /> <Tab value="settings" label={LL.SETTINGS_OF(LL.NETWORK(1))} disabled={!authenticatedContext.me.admin} />
</RouterTabs> </RouterTabs>
<Routes> <Routes>
<Route path="status" element={<NetworkStatusForm />} /> <Route path="status" element={<NetworkStatusForm />} />

View File

@@ -1,4 +1,5 @@
import { FC, useContext, useEffect, useState } from 'react'; import { FC, useContext, useEffect, useState } from 'react';
import { useSnackbar } from 'notistack';
import { import {
Avatar, Avatar,
@@ -10,13 +11,15 @@ import {
ListItemAvatar, ListItemAvatar,
ListItemSecondaryAction, ListItemSecondaryAction,
ListItemText, ListItemText,
Typography Typography,
InputAdornment
} from '@mui/material'; } from '@mui/material';
import LockOpenIcon from '@mui/icons-material/LockOpen'; import LockOpenIcon from '@mui/icons-material/LockOpen';
import DeleteIcon from '@mui/icons-material/Delete'; import DeleteIcon from '@mui/icons-material/Delete';
import SaveIcon from '@mui/icons-material/Save'; import SaveIcon from '@mui/icons-material/Save';
import LockIcon from '@mui/icons-material/Lock'; import LockIcon from '@mui/icons-material/Lock';
import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
import { import {
BlockFormControlLabel, BlockFormControlLabel,
@@ -24,11 +27,13 @@ import {
FormLoader, FormLoader,
SectionContent, SectionContent,
ValidatedPasswordField, ValidatedPasswordField,
ValidatedTextField ValidatedTextField,
MessageBox
} from '../../components'; } from '../../components';
import { NetworkSettings } from '../../types'; import { NetworkSettings } from '../../types';
import * as NetworkApi from '../../api/network'; import * as NetworkApi from '../../api/network';
import { numberValue, updateValue, useRest } from '../../utils'; import { numberValue, updateValue, useRest } from '../../utils';
import * as EMSESP from '../../project/api';
import { WiFiConnectionContext } from './WiFiConnectionContext'; import { WiFiConnectionContext } from './WiFiConnectionContext';
import { isNetworkOpen, networkSecurityMode } from './WiFiNetworkSelector'; import { isNetworkOpen, networkSecurityMode } from './WiFiNetworkSelector';
@@ -36,11 +41,18 @@ import { ValidateFieldsError } from 'async-validator';
import { validate } from '../../validators'; import { validate } from '../../validators';
import { createNetworkSettingsValidator } from '../../validators/network'; import { createNetworkSettingsValidator } from '../../validators/network';
import { useI18nContext } from '../../i18n/i18n-react';
import RestartMonitor from '../system/RestartMonitor';
const WiFiSettingsForm: FC = () => { const WiFiSettingsForm: FC = () => {
const { LL } = useI18nContext();
const { enqueueSnackbar } = useSnackbar();
const { selectedNetwork, deselectNetwork } = useContext(WiFiConnectionContext); const { selectedNetwork, deselectNetwork } = useContext(WiFiConnectionContext);
const [initialized, setInitialized] = useState(false); const [initialized, setInitialized] = useState(false);
const { loadData, saving, data, setData, saveData, errorMessage } = useRest<NetworkSettings>({ const [restarting, setRestarting] = useState(false);
const { loadData, saving, data, setData, saveData, errorMessage, restartNeeded } = useRest<NetworkSettings>({
read: NetworkApi.readNetworkSettings, read: NetworkApi.readNetworkSettings,
update: NetworkApi.updateNetworkSettings update: NetworkApi.updateNetworkSettings
}); });
@@ -57,7 +69,9 @@ const WiFiSettingsForm: FC = () => {
bandwidth20: false, bandwidth20: false,
tx_power: 20, tx_power: 20,
nosleep: false, nosleep: false,
enableMDNS: true enableMDNS: true,
enableCORS: false,
CORSOrigin: '*'
}); });
} }
setInitialized(true); setInitialized(true);
@@ -85,6 +99,15 @@ const WiFiSettingsForm: FC = () => {
} }
}; };
const restart = async () => {
try {
await EMSESP.restart();
setRestarting(true);
} catch (error) {
enqueueSnackbar(LL.PROBLEM_UPDATING(), { variant: 'error' });
}
};
return ( return (
<> <>
<Typography sx={{ pt: 2 }} variant="h6" color="primary"> <Typography sx={{ pt: 2 }} variant="h6" color="primary">
@@ -111,7 +134,7 @@ const WiFiSettingsForm: FC = () => {
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="ssid" name="ssid"
label="SSID (leave blank to disable WiFi)" label={'SSID (' + LL.NETWORK_BLANK_SSID() + ')'}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.ssid} value={data.ssid}
@@ -123,7 +146,7 @@ const WiFiSettingsForm: FC = () => {
<ValidatedPasswordField <ValidatedPasswordField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="password" name="password"
label="Password" label={LL.PASSWORD()}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.password} value={data.password}
@@ -135,7 +158,10 @@ const WiFiSettingsForm: FC = () => {
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="tx_power" name="tx_power"
label="WiFi Tx Power (dBm)" label={LL.TX_POWER()}
InputProps={{
endAdornment: <InputAdornment position="end">dBm</InputAdornment>
}}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={numberValue(data.tx_power)} value={numberValue(data.tx_power)}
@@ -146,27 +172,22 @@ const WiFiSettingsForm: FC = () => {
<BlockFormControlLabel <BlockFormControlLabel
control={<Checkbox name="nosleep" checked={data.nosleep} onChange={updateFormValue} />} control={<Checkbox name="nosleep" checked={data.nosleep} onChange={updateFormValue} />}
label="Disable WiFi Sleep Mode" label={LL.NETWORK_DISABLE_SLEEP()}
/> />
<BlockFormControlLabel <BlockFormControlLabel
control={<Checkbox name="bandwidth20" checked={data.bandwidth20} onChange={updateFormValue} />} control={<Checkbox name="bandwidth20" checked={data.bandwidth20} onChange={updateFormValue} />}
label="Use Lower WiFi Bandwidth" label={LL.NETWORK_LOW_BAND()}
/>
<BlockFormControlLabel
control={<Checkbox name="enableMDNS" checked={data.enableMDNS} onChange={updateFormValue} />}
label="Enable mDNS Service"
/> />
<Typography sx={{ pt: 2 }} variant="h6" color="primary"> <Typography sx={{ pt: 2 }} variant="h6" color="primary">
General {LL.GENERAL_OPTIONS()}
</Typography> </Typography>
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="hostname" name="hostname"
label="Hostname" label={LL.HOSTNAME()}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.hostname} value={data.hostname}
@@ -174,21 +195,43 @@ const WiFiSettingsForm: FC = () => {
margin="normal" margin="normal"
/> />
<BlockFormControlLabel
control={<Checkbox name="enableMDNS" checked={data.enableMDNS} onChange={updateFormValue} />}
label={LL.NETWORK_USE_DNS()}
/>
<BlockFormControlLabel
control={<Checkbox name="enableCORS" checked={data.enableCORS} onChange={updateFormValue} />}
label={LL.NETWORK_ENABLE_CORS()}
/>
{data.enableCORS && (
<ValidatedTextField
fieldErrors={fieldErrors}
name="CORSOrigin"
label={LL.NETWORK_CORS_ORIGIN()}
fullWidth
variant="outlined"
value={data.CORSOrigin}
onChange={updateFormValue}
margin="normal"
/>
)}
<BlockFormControlLabel <BlockFormControlLabel
control={<Checkbox name="enableIPv6" checked={data.enableIPv6} onChange={updateFormValue} />} control={<Checkbox name="enableIPv6" checked={data.enableIPv6} onChange={updateFormValue} />}
label="Enable IPv6 support" label={LL.NETWORK_ENABLE_IPV6()}
/> />
<BlockFormControlLabel <BlockFormControlLabel
control={<Checkbox name="static_ip_config" checked={data.static_ip_config} onChange={updateFormValue} />} control={<Checkbox name="static_ip_config" checked={data.static_ip_config} onChange={updateFormValue} />}
label="Use Fixed IP address" label={LL.NETWORK_FIXED_IP()}
/> />
{data.static_ip_config && ( {data.static_ip_config && (
<> <>
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="local_ip" name="local_ip"
label="Local IP" label={LL.AP_LOCAL_IP()}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.local_ip} value={data.local_ip}
@@ -198,7 +241,7 @@ const WiFiSettingsForm: FC = () => {
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="gateway_ip" name="gateway_ip"
label="Gateway" label={LL.NETWORK_GATEWAY()}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.gateway_ip} value={data.gateway_ip}
@@ -208,7 +251,7 @@ const WiFiSettingsForm: FC = () => {
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="subnet_mask" name="subnet_mask"
label="Subnet" label={LL.NETWORK_SUBNET()}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.subnet_mask} value={data.subnet_mask}
@@ -218,7 +261,7 @@ const WiFiSettingsForm: FC = () => {
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="dns_ip_1" name="dns_ip_1"
label="DNS IP #1" label="DNS #1"
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.dns_ip_1} value={data.dns_ip_1}
@@ -228,7 +271,7 @@ const WiFiSettingsForm: FC = () => {
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="dns_ip_2" name="dns_ip_2"
label="DNS IP #2" label="DNS #2"
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.dns_ip_2} value={data.dns_ip_2}
@@ -237,25 +280,34 @@ const WiFiSettingsForm: FC = () => {
/> />
</> </>
)} )}
<ButtonRow> {restartNeeded && (
<Button <MessageBox my={2} level="warning" message={LL.RESTART_TEXT()}>
startIcon={<SaveIcon />} <Button startIcon={<PowerSettingsNewIcon />} variant="contained" color="error" onClick={restart}>
disabled={saving} {LL.RESTART()}
variant="outlined" </Button>
color="primary" </MessageBox>
type="submit" )}
onClick={validateAndSubmit} {!restartNeeded && (
> <ButtonRow>
Save <Button
</Button> startIcon={<SaveIcon />}
</ButtonRow> disabled={saving}
variant="outlined"
color="primary"
type="submit"
onClick={validateAndSubmit}
>
{LL.SAVE()}
</Button>
</ButtonRow>
)}
</> </>
); );
}; };
return ( return (
<SectionContent title="Network Settings" titleGutter> <SectionContent title={LL.SETTINGS_OF(LL.NETWORK(1))} titleGutter>
{content()} {restarting ? <RestartMonitor /> : content()}
</SectionContent> </SectionContent>
); );
}; };

View File

@@ -14,6 +14,8 @@ import { NetworkConnectionStatus, NetworkStatus } from '../../types';
import * as NetworkApi from '../../api/network'; import * as NetworkApi from '../../api/network';
import { useRest } from '../../utils'; import { useRest } from '../../utils';
import { useI18nContext } from '../../i18n/i18n-react';
const isConnected = ({ status }: NetworkStatus) => const isConnected = ({ status }: NetworkStatus) =>
status === NetworkConnectionStatus.WIFI_STATUS_CONNECTED || status === NetworkConnectionStatus.WIFI_STATUS_CONNECTED ||
status === NetworkConnectionStatus.ETHERNET_STATUS_CONNECTED; status === NetworkConnectionStatus.ETHERNET_STATUS_CONNECTED;
@@ -35,29 +37,6 @@ const networkStatusHighlight = ({ status }: NetworkStatus, theme: Theme) => {
} }
}; };
const networkStatus = ({ status }: NetworkStatus) => {
switch (status) {
case NetworkConnectionStatus.WIFI_STATUS_NO_SHIELD:
return 'Inactive';
case NetworkConnectionStatus.WIFI_STATUS_IDLE:
return 'Idle';
case NetworkConnectionStatus.WIFI_STATUS_NO_SSID_AVAIL:
return 'No SSID Available';
case NetworkConnectionStatus.WIFI_STATUS_CONNECTED:
return 'Connected (WiFi)';
case NetworkConnectionStatus.ETHERNET_STATUS_CONNECTED:
return 'Connected (Ethernet)';
case NetworkConnectionStatus.WIFI_STATUS_CONNECT_FAILED:
return 'Connection Failed';
case NetworkConnectionStatus.WIFI_STATUS_CONNECTION_LOST:
return 'Connection Lost';
case NetworkConnectionStatus.WIFI_STATUS_DISCONNECTED:
return 'Disconnected';
default:
return 'Unknown';
}
};
export const isWiFi = ({ status }: NetworkStatus) => status === NetworkConnectionStatus.WIFI_STATUS_CONNECTED; export const isWiFi = ({ status }: NetworkStatus) => status === NetworkConnectionStatus.WIFI_STATUS_CONNECTED;
export const isEthernet = ({ status }: NetworkStatus) => status === NetworkConnectionStatus.ETHERNET_STATUS_CONNECTED; export const isEthernet = ({ status }: NetworkStatus) => status === NetworkConnectionStatus.ETHERNET_STATUS_CONNECTED;
@@ -65,7 +44,7 @@ const dnsServers = ({ dns_ip_1, dns_ip_2 }: NetworkStatus) => {
if (!dns_ip_1) { if (!dns_ip_1) {
return 'none'; return 'none';
} }
return dns_ip_1 + (dns_ip_2 === '0.0.0.0' ? '' : ',' + dns_ip_2); return dns_ip_1 + (!dns_ip_2 || dns_ip_2 === '0.0.0.0' ? '' : ',' + dns_ip_2);
}; };
const IPs = (status: NetworkStatus) => { const IPs = (status: NetworkStatus) => {
@@ -81,8 +60,33 @@ const IPs = (status: NetworkStatus) => {
const NetworkStatusForm: FC = () => { const NetworkStatusForm: FC = () => {
const { loadData, data, errorMessage } = useRest<NetworkStatus>({ read: NetworkApi.readNetworkStatus }); const { loadData, data, errorMessage } = useRest<NetworkStatus>({ read: NetworkApi.readNetworkStatus });
const { LL } = useI18nContext();
const theme = useTheme(); const theme = useTheme();
const networkStatus = ({ status }: NetworkStatus) => {
switch (status) {
case NetworkConnectionStatus.WIFI_STATUS_NO_SHIELD:
return LL.INACTIVE(1);
case NetworkConnectionStatus.WIFI_STATUS_IDLE:
return LL.IDLE();
case NetworkConnectionStatus.WIFI_STATUS_NO_SSID_AVAIL:
return 'No SSID Available';
case NetworkConnectionStatus.WIFI_STATUS_CONNECTED:
return LL.CONNECTED(0) + ' (WiFi)';
case NetworkConnectionStatus.ETHERNET_STATUS_CONNECTED:
return LL.CONNECTED(0) + ' (Ethernet)';
case NetworkConnectionStatus.WIFI_STATUS_CONNECT_FAILED:
return LL.CONNECTED(1) + ' ' + LL.FAILED();
case NetworkConnectionStatus.WIFI_STATUS_CONNECTION_LOST:
return LL.CONNECTED(1) + ' ' + LL.LOST();
case NetworkConnectionStatus.WIFI_STATUS_DISCONNECTED:
return LL.DISCONNECTED();
default:
return LL.UNKNOWN();
}
};
const content = () => { const content = () => {
if (!data) { if (!data) {
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />; return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
@@ -120,7 +124,7 @@ const NetworkStatusForm: FC = () => {
<ListItemAvatar> <ListItemAvatar>
<Avatar>IP</Avatar> <Avatar>IP</Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText primary="IP Address" secondary={IPs(data)} /> <ListItemText primary={LL.ADDRESS_OF('IP')} secondary={IPs(data)} />
</ListItem> </ListItem>
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />
<ListItem> <ListItem>
@@ -129,14 +133,14 @@ const NetworkStatusForm: FC = () => {
<DeviceHubIcon /> <DeviceHubIcon />
</Avatar> </Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText primary="MAC Address" secondary={data.mac_address} /> <ListItemText primary={LL.ADDRESS_OF('MAC')} secondary={data.mac_address} />
</ListItem> </ListItem>
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />
<ListItem> <ListItem>
<ListItemAvatar> <ListItemAvatar>
<Avatar>#</Avatar> <Avatar>#</Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText primary="Subnet Mask" secondary={data.subnet_mask} /> <ListItemText primary={LL.NETWORK_SUBNET()} secondary={data.subnet_mask} />
</ListItem> </ListItem>
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />
<ListItem> <ListItem>
@@ -145,7 +149,7 @@ const NetworkStatusForm: FC = () => {
<SettingsInputComponentIcon /> <SettingsInputComponentIcon />
</Avatar> </Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText primary="Gateway IP" secondary={data.gateway_ip || 'none'} /> <ListItemText primary={LL.NETWORK_GATEWAY()} secondary={data.gateway_ip || 'none'} />
</ListItem> </ListItem>
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />
<ListItem> <ListItem>
@@ -154,7 +158,7 @@ const NetworkStatusForm: FC = () => {
<DnsIcon /> <DnsIcon />
</Avatar> </Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText primary="DNS Server IP" secondary={dnsServers(data)} /> <ListItemText primary={LL.NETWORK_DNS()} secondary={dnsServers(data)} />
</ListItem> </ListItem>
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />
</> </>
@@ -162,7 +166,7 @@ const NetworkStatusForm: FC = () => {
</List> </List>
<ButtonRow> <ButtonRow>
<Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={loadData}> <Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={loadData}>
Refresh {LL.REFRESH()}
</Button> </Button>
</ButtonRow> </ButtonRow>
</> </>
@@ -170,7 +174,7 @@ const NetworkStatusForm: FC = () => {
}; };
return ( return (
<SectionContent title="Network Status" titleGutter> <SectionContent title={LL.STATUS_OF(LL.NETWORK(1))} titleGutter>
{content()} {content()}
</SectionContent> </SectionContent>
); );

View File

@@ -1,8 +1,6 @@
import { useEffect, FC, useState, useCallback, useRef } from 'react'; import { useEffect, FC, useState, useCallback, useRef } from 'react';
import { useSnackbar } from 'notistack'; import { useSnackbar } from 'notistack';
import { AxiosError } from 'axios';
import { Button } from '@mui/material'; import { Button } from '@mui/material';
import PermScanWifiIcon from '@mui/icons-material/PermScanWifi'; import PermScanWifiIcon from '@mui/icons-material/PermScanWifi';
@@ -12,6 +10,8 @@ import { ButtonRow, FormLoader, SectionContent } from '../../components';
import WiFiNetworkSelector from './WiFiNetworkSelector'; import WiFiNetworkSelector from './WiFiNetworkSelector';
import { useI18nContext } from '../../i18n/i18n-react';
const NUM_POLLS = 10; const NUM_POLLS = 10;
const POLLING_FREQUENCY = 500; const POLLING_FREQUENCY = 500;
@@ -22,6 +22,8 @@ const compareNetworks = (network1: WiFiNetwork, network2: WiFiNetwork) => {
}; };
const WiFiNetworkScanner: FC = () => { const WiFiNetworkScanner: FC = () => {
const { LL } = useI18nContext();
const { enqueueSnackbar } = useSnackbar(); const { enqueueSnackbar } = useSnackbar();
const pollCount = useRef(0); const pollCount = useRef(0);
@@ -46,21 +48,21 @@ const WiFiNetworkScanner: FC = () => {
pollCount.current = completedPollCount; pollCount.current = completedPollCount;
setTimeout(pollNetworkList, POLLING_FREQUENCY); setTimeout(pollNetworkList, POLLING_FREQUENCY);
} else { } else {
finishedWithError('Device did not return network list in timely manner'); finishedWithError(LL.PROBLEM_LOADING());
} }
} else { } else {
const newNetworkList = response.data; const newNetworkList = response.data;
newNetworkList.networks.sort(compareNetworks); newNetworkList.networks.sort(compareNetworks);
setNetworkList(newNetworkList); setNetworkList(newNetworkList);
} }
} catch (error: unknown) { } catch (error) {
if (error instanceof AxiosError) { if (error.response) {
finishedWithError('Problem listing WiFi networks ' + error.response?.data.message); finishedWithError(LL.PROBLEM_LOADING() + ' ' + error.response?.data.message);
} else { } else {
finishedWithError('Problem listing WiFi networks'); finishedWithError(LL.PROBLEM_LOADING());
} }
} }
}, [finishedWithError]); }, [finishedWithError, LL]);
const startNetworkScan = useCallback(async () => { const startNetworkScan = useCallback(async () => {
pollCount.current = 0; pollCount.current = 0;
@@ -69,14 +71,14 @@ const WiFiNetworkScanner: FC = () => {
try { try {
await NetworkApi.scanNetworks(); await NetworkApi.scanNetworks();
setTimeout(pollNetworkList, POLLING_FREQUENCY); setTimeout(pollNetworkList, POLLING_FREQUENCY);
} catch (error: unknown) { } catch (error) {
if (error instanceof AxiosError) { if (error.response) {
finishedWithError('Problem scanning for WiFi networks ' + error.response?.data.message); finishedWithError(LL.PROBLEM_LOADING() + ' ' + error.response?.data.message);
} else { } else {
finishedWithError('Problem scanning for WiFi networks'); finishedWithError(LL.PROBLEM_LOADING());
} }
} }
}, [finishedWithError, pollNetworkList]); }, [finishedWithError, pollNetworkList, LL]);
useEffect(() => { useEffect(() => {
startNetworkScan(); startNetworkScan();
@@ -84,13 +86,13 @@ const WiFiNetworkScanner: FC = () => {
const renderNetworkScanner = () => { const renderNetworkScanner = () => {
if (!networkList) { if (!networkList) {
return <FormLoader message="Scanning&hellip;" errorMessage={errorMessage} />; return <FormLoader message={LL.SCANNING() + '...'} errorMessage={errorMessage} />;
} }
return <WiFiNetworkSelector networkList={networkList} />; return <WiFiNetworkSelector networkList={networkList} />;
}; };
return ( return (
<SectionContent title="Network Scanner"> <SectionContent title={LL.NETWORK_SCANNER()}>
{renderNetworkScanner()} {renderNetworkScanner()}
<ButtonRow> <ButtonRow>
<Button <Button
@@ -100,7 +102,7 @@ const WiFiNetworkScanner: FC = () => {
onClick={startNetworkScan} onClick={startNetworkScan}
disabled={!errorMessage && !networkList} disabled={!errorMessage && !networkList}
> >
Scan again&hellip; {LL.SCAN_AGAIN()}&hellip;
</Button> </Button>
</ButtonRow> </ButtonRow>
</SectionContent> </SectionContent>

View File

@@ -12,6 +12,8 @@ import { WiFiEncryptionType, WiFiNetwork, WiFiNetworkList } from '../../types';
import { WiFiConnectionContext } from './WiFiConnectionContext'; import { WiFiConnectionContext } from './WiFiConnectionContext';
import { useI18nContext } from '../../i18n/i18n-react';
interface WiFiNetworkSelectorProps { interface WiFiNetworkSelectorProps {
networkList: WiFiNetworkList; networkList: WiFiNetworkList;
} }
@@ -39,6 +41,8 @@ export const networkSecurityMode = ({ encryption_type }: WiFiNetwork) => {
}; };
const WiFiNetworkSelector: FC<WiFiNetworkSelectorProps> = ({ networkList }) => { const WiFiNetworkSelector: FC<WiFiNetworkSelectorProps> = ({ networkList }) => {
const { LL } = useI18nContext();
const wifiConnectionContext = useContext(WiFiConnectionContext); const wifiConnectionContext = useContext(WiFiConnectionContext);
const renderNetwork = (network: WiFiNetwork) => { const renderNetwork = (network: WiFiNetwork) => {
@@ -61,7 +65,7 @@ const WiFiNetworkSelector: FC<WiFiNetworkSelectorProps> = ({ networkList }) => {
}; };
if (networkList.networks.length === 0) { if (networkList.networks.length === 0) {
return <MessageBox mt={2} mb={1} message="No WiFi networks found" level="info" />; return <MessageBox mt={2} mb={1} message={LL.NETWORK_NO_WIFI()} level="info" />;
} }
return <List>{networkList.networks.map(renderNetwork)}</List>; return <List>{networkList.networks.map(renderNetwork)}</List>;

View File

@@ -12,12 +12,16 @@ import * as NTPApi from '../../api/ntp';
import { selectedTimeZone, timeZoneSelectItems, TIME_ZONES } from './TZ'; import { selectedTimeZone, timeZoneSelectItems, TIME_ZONES } from './TZ';
import { NTP_SETTINGS_VALIDATOR } from '../../validators/ntp'; import { NTP_SETTINGS_VALIDATOR } from '../../validators/ntp';
import { useI18nContext } from '../../i18n/i18n-react';
const NTPSettingsForm: FC = () => { const NTPSettingsForm: FC = () => {
const { loadData, saving, data, setData, saveData, errorMessage } = useRest<NTPSettings>({ const { loadData, saving, data, setData, saveData, errorMessage } = useRest<NTPSettings>({
read: NTPApi.readNTPSettings, read: NTPApi.readNTPSettings,
update: NTPApi.updateNTPSettings update: NTPApi.updateNTPSettings
}); });
const { LL } = useI18nContext();
const updateFormValue = updateValue(setData); const updateFormValue = updateValue(setData);
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>(); const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
@@ -49,12 +53,12 @@ const NTPSettingsForm: FC = () => {
<> <>
<BlockFormControlLabel <BlockFormControlLabel
control={<Checkbox name="enabled" checked={data.enabled} onChange={updateFormValue} />} control={<Checkbox name="enabled" checked={data.enabled} onChange={updateFormValue} />}
label="Enable NTP" label={LL.ENABLE_NTP()}
/> />
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="server" name="server"
label="Server" label={LL.NTP_SERVER()}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.server} value={data.server}
@@ -64,7 +68,7 @@ const NTPSettingsForm: FC = () => {
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="tz_label" name="tz_label"
label="Time zone" label={LL.TIME_ZONE()}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={selectedTimeZone(data.tz_label, data.tz_format)} value={selectedTimeZone(data.tz_label, data.tz_format)}
@@ -72,7 +76,7 @@ const NTPSettingsForm: FC = () => {
margin="normal" margin="normal"
select select
> >
<MenuItem disabled>Time zone...</MenuItem> <MenuItem disabled>{LL.TIME_ZONE()}...</MenuItem>
{timeZoneSelectItems()} {timeZoneSelectItems()}
</ValidatedTextField> </ValidatedTextField>
<ButtonRow> <ButtonRow>
@@ -84,7 +88,7 @@ const NTPSettingsForm: FC = () => {
type="submit" type="submit"
onClick={validateAndSubmit} onClick={validateAndSubmit}
> >
Save {LL.SAVE()}
</Button> </Button>
</ButtonRow> </ButtonRow>
</> </>
@@ -92,7 +96,7 @@ const NTPSettingsForm: FC = () => {
}; };
return ( return (
<SectionContent title="NTP Settings" titleGutter> <SectionContent title={LL.SETTINGS_OF('NTP')} titleGutter>
{content()} {content()}
</SectionContent> </SectionContent>
); );

View File

@@ -16,7 +16,8 @@ import {
ListItemText, ListItemText,
TextField, TextField,
Theme, Theme,
useTheme useTheme,
Typography
} from '@mui/material'; } from '@mui/material';
import RefreshIcon from '@mui/icons-material/Refresh'; import RefreshIcon from '@mui/icons-material/Refresh';
import AccessTimeIcon from '@mui/icons-material/AccessTime'; import AccessTimeIcon from '@mui/icons-material/AccessTime';
@@ -31,6 +32,8 @@ import { ButtonRow, FormLoader, SectionContent } from '../../components';
import { extractErrorMessage, formatDateTime, formatLocalDateTime, useRest } from '../../utils'; import { extractErrorMessage, formatDateTime, formatLocalDateTime, useRest } from '../../utils';
import { AuthenticatedContext } from '../../contexts/authentication'; import { AuthenticatedContext } from '../../contexts/authentication';
import { useI18nContext } from '../../i18n/i18n-react';
export const isNtpActive = ({ status }: NTPStatus) => status === NTPSyncStatus.NTP_ACTIVE; export const isNtpActive = ({ status }: NTPStatus) => status === NTPSyncStatus.NTP_ACTIVE;
export const isNtpEnabled = ({ status }: NTPStatus) => status !== NTPSyncStatus.NTP_DISABLED; export const isNtpEnabled = ({ status }: NTPStatus) => status !== NTPSyncStatus.NTP_DISABLED;
@@ -47,19 +50,6 @@ export const ntpStatusHighlight = ({ status }: NTPStatus, theme: Theme) => {
} }
}; };
export const ntpStatus = ({ status }: NTPStatus) => {
switch (status) {
case NTPSyncStatus.NTP_DISABLED:
return 'Disabled';
case NTPSyncStatus.NTP_INACTIVE:
return 'Inactive';
case NTPSyncStatus.NTP_ACTIVE:
return 'Active';
default:
return 'Unknown';
}
};
const NTPStatusForm: FC = () => { const NTPStatusForm: FC = () => {
const { loadData, data, errorMessage } = useRest<NTPStatus>({ read: NTPApi.readNTPStatus }); const { loadData, data, errorMessage } = useRest<NTPStatus>({ read: NTPApi.readNTPStatus });
const [localTime, setLocalTime] = useState<string>(''); const [localTime, setLocalTime] = useState<string>('');
@@ -68,6 +58,8 @@ const NTPStatusForm: FC = () => {
const { enqueueSnackbar } = useSnackbar(); const { enqueueSnackbar } = useSnackbar();
const { me } = useContext(AuthenticatedContext); const { me } = useContext(AuthenticatedContext);
const { LL } = useI18nContext();
const updateLocalTime = (event: React.ChangeEvent<HTMLInputElement>) => setLocalTime(event.target.value); const updateLocalTime = (event: React.ChangeEvent<HTMLInputElement>) => setLocalTime(event.target.value);
const openSetTime = () => { const openSetTime = () => {
@@ -77,59 +69,71 @@ const NTPStatusForm: FC = () => {
const theme = useTheme(); const theme = useTheme();
const ntpStatus = ({ status }: NTPStatus) => {
switch (status) {
case NTPSyncStatus.NTP_DISABLED:
return LL.DISABLED(0);
case NTPSyncStatus.NTP_INACTIVE:
return LL.INACTIVE(0);
case NTPSyncStatus.NTP_ACTIVE:
return LL.ACTIVE();
default:
return LL.UNKNOWN();
}
};
const configureTime = async () => { const configureTime = async () => {
setProcessing(true); setProcessing(true);
try { try {
await NTPApi.updateTime({ await NTPApi.updateTime({
local_time: formatLocalDateTime(new Date(localTime)) local_time: formatLocalDateTime(new Date(localTime))
}); });
enqueueSnackbar('Time set', { variant: 'success' }); enqueueSnackbar(LL.TIME_SET(), { variant: 'success' });
setSettingTime(false); setSettingTime(false);
loadData(); loadData();
} catch (error: unknown) { } catch (error) {
enqueueSnackbar(extractErrorMessage(error, 'Problem updating time'), { variant: 'error' }); enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
} finally { } finally {
setProcessing(false); setProcessing(false);
} }
}; };
const renderSetTimeDialog = () => { const renderSetTimeDialog = () => (
return ( <Dialog open={settingTime} onClose={() => setSettingTime(false)}>
<Dialog open={settingTime} onClose={() => setSettingTime(false)}> <DialogTitle>{LL.SET_TIME(1)}</DialogTitle>
<DialogTitle>Set Time</DialogTitle> <DialogContent dividers>
<DialogContent dividers> <Box color="warning.main" p={0} pl={0} pr={0} mt={0} mb={2}>
<Box mb={2}>Enter local date and time below to set the device's time.</Box> <Typography variant="body2">{LL.SET_TIME_TEXT()}</Typography>
<TextField </Box>
label="Local Time" <TextField
type="datetime-local" label={LL.LOCAL_TIME()}
value={localTime} type="datetime-local"
onChange={updateLocalTime} value={localTime}
disabled={processing} onChange={updateLocalTime}
variant="outlined" disabled={processing}
fullWidth fullWidth
InputLabelProps={{ InputLabelProps={{
shrink: true shrink: true
}} }}
/> />
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button startIcon={<CancelIcon />} variant="outlined" onClick={() => setSettingTime(false)} color="secondary"> <Button startIcon={<CancelIcon />} variant="outlined" onClick={() => setSettingTime(false)} color="secondary">
Cancel {LL.CANCEL()}
</Button> </Button>
<Button <Button
startIcon={<AccessTimeIcon />} startIcon={<AccessTimeIcon />}
variant="outlined" variant="outlined"
onClick={configureTime} onClick={configureTime}
disabled={processing} disabled={processing}
color="primary" color="primary"
autoFocus autoFocus
> >
Set Time {LL.SAVE()}
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
); );
};
const content = () => { const content = () => {
if (!data) { if (!data) {
@@ -145,7 +149,7 @@ const NTPStatusForm: FC = () => {
<UpdateIcon /> <UpdateIcon />
</Avatar> </Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText primary="Status" secondary={ntpStatus(data)} /> <ListItemText primary={LL.STATUS_OF('')} secondary={ntpStatus(data)} />
</ListItem> </ListItem>
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />
{isNtpEnabled(data) && ( {isNtpEnabled(data) && (
@@ -156,7 +160,7 @@ const NTPStatusForm: FC = () => {
<DnsIcon /> <DnsIcon />
</Avatar> </Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText primary="NTP Server" secondary={data.server} /> <ListItemText primary={LL.NTP_SERVER()} secondary={data.server} />
</ListItem> </ListItem>
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />
</> </>
@@ -167,7 +171,7 @@ const NTPStatusForm: FC = () => {
<AccessTimeIcon /> <AccessTimeIcon />
</Avatar> </Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText primary="Local Time" secondary={formatDateTime(data.local_time)} /> <ListItemText primary={LL.LOCAL_TIME()} secondary={formatDateTime(data.local_time)} />
</ListItem> </ListItem>
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />
<ListItem> <ListItem>
@@ -176,7 +180,7 @@ const NTPStatusForm: FC = () => {
<SwapVerticalCircleIcon /> <SwapVerticalCircleIcon />
</Avatar> </Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText primary="UTC Time" secondary={formatDateTime(data.utc_time)} /> <ListItemText primary={LL.UTC_TIME()} secondary={formatDateTime(data.utc_time)} />
</ListItem> </ListItem>
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />
</List> </List>
@@ -184,7 +188,7 @@ const NTPStatusForm: FC = () => {
<Box flexGrow={1}> <Box flexGrow={1}>
<ButtonRow> <ButtonRow>
<Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={loadData}> <Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={loadData}>
Refresh {LL.REFRESH()}
</Button> </Button>
</ButtonRow> </ButtonRow>
</Box> </Box>
@@ -192,7 +196,7 @@ const NTPStatusForm: FC = () => {
<Box flexWrap="nowrap" whiteSpace="nowrap"> <Box flexWrap="nowrap" whiteSpace="nowrap">
<ButtonRow> <ButtonRow>
<Button onClick={openSetTime} variant="outlined" color="primary" startIcon={<AccessTimeIcon />}> <Button onClick={openSetTime} variant="outlined" color="primary" startIcon={<AccessTimeIcon />}>
Set Time {LL.SET_TIME(0)}
</Button> </Button>
</ButtonRow> </ButtonRow>
</Box> </Box>
@@ -204,7 +208,7 @@ const NTPStatusForm: FC = () => {
}; };
return ( return (
<SectionContent title="NTP Status" titleGutter> <SectionContent title={LL.STATUS_OF('NTP')} titleGutter>
{content()} {content()}
</SectionContent> </SectionContent>
); );

View File

@@ -9,8 +9,11 @@ import { AuthenticatedContext } from '../../contexts/authentication';
import NTPStatusForm from './NTPStatusForm'; import NTPStatusForm from './NTPStatusForm';
import NTPSettingsForm from './NTPSettingsForm'; import NTPSettingsForm from './NTPSettingsForm';
import { useI18nContext } from '../../i18n/i18n-react';
const NetworkTime: FC = () => { const NetworkTime: FC = () => {
useLayoutTitle('Network Time'); const { LL } = useI18nContext();
useLayoutTitle('NTP');
const authenticatedContext = useContext(AuthenticatedContext); const authenticatedContext = useContext(AuthenticatedContext);
const { routerTab } = useRouterTab(); const { routerTab } = useRouterTab();
@@ -18,8 +21,8 @@ const NetworkTime: FC = () => {
return ( return (
<> <>
<RouterTabs value={routerTab}> <RouterTabs value={routerTab}>
<Tab value="status" label="NTP Status" /> <Tab value="status" label={LL.STATUS_OF('NTP')} />
<Tab value="settings" label="NTP Settings" disabled={!authenticatedContext.me.admin} /> <Tab value="settings" label={LL.SETTINGS_OF('NTP')} disabled={!authenticatedContext.me.admin} />
</RouterTabs> </RouterTabs>
<Routes> <Routes>
<Route path="status" element={<NTPStatusForm />} /> <Route path="status" element={<NTPStatusForm />} />

View File

@@ -19,6 +19,8 @@ import { MessageBox } from '../../components';
import * as SecurityApi from '../../api/security'; import * as SecurityApi from '../../api/security';
import { Token } from '../../types'; import { Token } from '../../types';
import { useI18nContext } from '../../i18n/i18n-react';
interface GenerateTokenProps { interface GenerateTokenProps {
username?: string; username?: string;
onClose: () => void; onClose: () => void;
@@ -28,15 +30,17 @@ const GenerateToken: FC<GenerateTokenProps> = ({ username, onClose }) => {
const [token, setToken] = useState<Token>(); const [token, setToken] = useState<Token>();
const open = !!username; const open = !!username;
const { LL } = useI18nContext();
const { enqueueSnackbar } = useSnackbar(); const { enqueueSnackbar } = useSnackbar();
const getToken = useCallback(async () => { const getToken = useCallback(async () => {
try { try {
setToken((await SecurityApi.generateToken(username)).data); setToken((await SecurityApi.generateToken(username)).data);
} catch (error: unknown) { } catch (error) {
enqueueSnackbar(extractErrorMessage(error, 'Problem generating token'), { variant: 'error' }); enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
} }
}, [username, enqueueSnackbar]); }, [username, enqueueSnackbar, LL]);
useEffect(() => { useEffect(() => {
if (open) { if (open) {
@@ -46,16 +50,11 @@ const GenerateToken: FC<GenerateTokenProps> = ({ username, onClose }) => {
return ( return (
<Dialog onClose={onClose} aria-labelledby="generate-token-dialog-title" open={!!username} fullWidth maxWidth="sm"> <Dialog onClose={onClose} aria-labelledby="generate-token-dialog-title" open={!!username} fullWidth maxWidth="sm">
<DialogTitle id="generate-token-dialog-title">Access Token for {username}</DialogTitle> <DialogTitle id="generate-token-dialog-title">{LL.ACCESS_TOKEN_FOR() + ' ' + username}</DialogTitle>
<DialogContent dividers> <DialogContent dividers>
{token ? ( {token ? (
<> <>
<MessageBox <MessageBox message={LL.ACCESS_TOKEN_TEXT()} level="info" my={2} />
message="The token below is used with REST API calls that require authorization. It can be passed either as a Bearer token in the
'Authorization' header or in the 'access_token' URL query parameter."
level="info"
my={2}
/>
<Box mt={2} mb={2}> <Box mt={2} mb={2}>
<TextField label="Token" multiline value={token.token} fullWidth contentEditable={false} /> <TextField label="Token" multiline value={token.token} fullWidth contentEditable={false} />
</Box> </Box>
@@ -63,13 +62,13 @@ const GenerateToken: FC<GenerateTokenProps> = ({ username, onClose }) => {
) : ( ) : (
<Box m={4} textAlign="center"> <Box m={4} textAlign="center">
<LinearProgress /> <LinearProgress />
<Typography variant="h6">Generating token&hellip;</Typography> <Typography variant="h6">{LL.GENERATING_TOKEN()}&hellip;</Typography>
</Box> </Box>
)} )}
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button startIcon={<CloseIcon />} variant="outlined" onClick={onClose} color="secondary"> <Button startIcon={<CloseIcon />} variant="outlined" onClick={onClose} color="secondary">
Close {LL.CLOSE()}
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>

View File

@@ -20,6 +20,8 @@ import { createUserValidator } from '../../validators';
import { useRest } from '../../utils'; import { useRest } from '../../utils';
import { AuthenticatedContext } from '../../contexts/authentication'; import { AuthenticatedContext } from '../../contexts/authentication';
import { useI18nContext } from '../../i18n/i18n-react';
import GenerateToken from './GenerateToken'; import GenerateToken from './GenerateToken';
import UserForm from './UserForm'; import UserForm from './UserForm';
@@ -34,44 +36,47 @@ const ManageUsersForm: FC = () => {
const [generatingToken, setGeneratingToken] = useState<string>(); const [generatingToken, setGeneratingToken] = useState<string>();
const authenticatedContext = useContext(AuthenticatedContext); const authenticatedContext = useContext(AuthenticatedContext);
const { LL } = useI18nContext();
const table_theme = useTheme({ const table_theme = useTheme({
Table: `
--data-table-library_grid-template-columns: repeat(1, minmax(0, 1fr)) minmax(120px, max-content) 120px;
`,
BaseRow: ` BaseRow: `
font-size: 14px; font-size: 14px;
color: white;
padding-left: 8px;
`, `,
HeaderRow: ` HeaderRow: `
text-transform: uppercase; text-transform: uppercase;
background-color: black; background-color: black;
color: #90CAF9; color: #90CAF9;
font-weight: 500; .th {
border-bottom: 1px solid #e0e0e0; padding: 8px;
height: 42px;
font-weight: 500;
border-bottom: 1px solid #565656;
}
`, `,
Row: ` Row: `
&:nth-of-type(odd) { .td {
padding: 8px;
border-top: 1px solid #565656;
border-bottom: 1px solid #565656;
}
&:nth-of-type(odd) .td {
background-color: #303030; background-color: #303030;
} }
&:nth-of-type(even) { &:nth-of-type(even) .td {
background-color: #1e1e1e; background-color: #1e1e1e;
} }
border-top: 1px solid #565656;
border-bottom: 1px solid #565656;
position: relative;
z-index: 1;
&:not(:last-of-type) {
margin-bottom: -1px;
}
&:not(:first-of-type) {
margin-top: -1px;
}
&:hover {
color: white;
}
`, `,
BaseCell: ` BaseCell: `
border-top: 1px solid transparent; &:nth-of-type(2) {
border-right: 1px solid transparent; text-align: center;
border-bottom: 1px solid transparent; }
&:last-of-type {
text-align: right;
}
` `
}); });
@@ -130,22 +135,22 @@ const ManageUsersForm: FC = () => {
return ( return (
<> <>
<Table data={{ nodes: user_table }} theme={table_theme}> <Table data={{ nodes: user_table }} theme={table_theme} layout={{ custom: true }}>
{(tableList: any) => ( {(tableList: any) => (
<> <>
<Header> <Header>
<HeaderRow> <HeaderRow>
<HeaderCell>USERNAME</HeaderCell> <HeaderCell resize>{LL.USERNAME(1)}</HeaderCell>
<HeaderCell>IS ADMIN</HeaderCell> <HeaderCell stiff>{LL.IS_ADMIN(0)}</HeaderCell>
<HeaderCell /> <HeaderCell stiff />
</HeaderRow> </HeaderRow>
</Header> </Header>
<Body> <Body>
{tableList.map((u: any) => ( {tableList.map((u: any) => (
<Row key={u.id} item={u}> <Row key={u.id} item={u}>
<Cell>{u.username}</Cell> <Cell>{u.username}</Cell>
<Cell>{u.admin ? <CheckIcon /> : <CloseIcon />}</Cell> <Cell stiff>{u.admin ? <CheckIcon /> : <CloseIcon />}</Cell>
<Cell> <Cell stiff>
<IconButton <IconButton
size="small" size="small"
disabled={!authenticatedContext.me.admin} disabled={!authenticatedContext.me.admin}
@@ -168,9 +173,7 @@ const ManageUsersForm: FC = () => {
)} )}
</Table> </Table>
{noAdminConfigured() && ( {noAdminConfigured() && <MessageBox level="warning" message={LL.USER_WARNING()} my={2} />}
<MessageBox level="warning" message="You must have at least one admin user configured" my={2} />
)}
<Box display="flex" flexWrap="wrap"> <Box display="flex" flexWrap="wrap">
<Box flexGrow={1} sx={{ '& button': { mt: 2 } }}> <Box flexGrow={1} sx={{ '& button': { mt: 2 } }}>
@@ -182,14 +185,14 @@ const ManageUsersForm: FC = () => {
type="submit" type="submit"
onClick={onSubmit} onClick={onSubmit}
> >
Save {LL.SAVE()}
</Button> </Button>
</Box> </Box>
<Box flexWrap="nowrap" whiteSpace="nowrap"> <Box flexWrap="nowrap" whiteSpace="nowrap">
<ButtonRow> <ButtonRow>
<Button startIcon={<PersonAddIcon />} variant="outlined" color="secondary" onClick={createUser}> <Button startIcon={<PersonAddIcon />} variant="outlined" color="secondary" onClick={createUser}>
Add {LL.ADD(0)}
</Button> </Button>
</ButtonRow> </ButtonRow>
</Box> </Box>
@@ -209,7 +212,7 @@ const ManageUsersForm: FC = () => {
}; };
return ( return (
<SectionContent title="Manage Users" titleGutter> <SectionContent title={LL.MANAGE_USERS()} titleGutter>
{content()} {content()}
</SectionContent> </SectionContent>
); );

View File

@@ -8,16 +8,19 @@ import { RouterTabs, useRouterTab, useLayoutTitle } from '../../components';
import SecuritySettingsForm from './SecuritySettingsForm'; import SecuritySettingsForm from './SecuritySettingsForm';
import ManageUsersForm from './ManageUsersForm'; import ManageUsersForm from './ManageUsersForm';
import { useI18nContext } from '../../i18n/i18n-react';
const Security: FC = () => { const Security: FC = () => {
useLayoutTitle('Security'); const { LL } = useI18nContext();
useLayoutTitle(LL.SECURITY(0));
const { routerTab } = useRouterTab(); const { routerTab } = useRouterTab();
return ( return (
<> <>
<RouterTabs value={routerTab}> <RouterTabs value={routerTab}>
<Tab value="users" label="Manage Users" /> <Tab value="users" label={LL.MANAGE_USERS()} />
<Tab value="settings" label="Security Settings" /> <Tab value="settings" label={LL.SETTINGS_OF(LL.SECURITY(1))} />
</RouterTabs> </RouterTabs>
<Routes> <Routes>
<Route path="users" element={<ManageUsersForm />} /> <Route path="users" element={<ManageUsersForm />} />

View File

@@ -11,7 +11,11 @@ import { SECURITY_SETTINGS_VALIDATOR, validate } from '../../validators';
import { updateValue, useRest } from '../../utils'; import { updateValue, useRest } from '../../utils';
import { AuthenticatedContext } from '../../contexts/authentication'; import { AuthenticatedContext } from '../../contexts/authentication';
import { useI18nContext } from '../../i18n/i18n-react';
const SecuritySettingsForm: FC = () => { const SecuritySettingsForm: FC = () => {
const { LL } = useI18nContext();
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>(); const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
const { loadData, saving, data, setData, saveData, errorMessage } = useRest<SecuritySettings>({ const { loadData, saving, data, setData, saveData, errorMessage } = useRest<SecuritySettings>({
read: SecurityApi.readSecuritySettings, read: SecurityApi.readSecuritySettings,
@@ -42,18 +46,14 @@ const SecuritySettingsForm: FC = () => {
<ValidatedPasswordField <ValidatedPasswordField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="jwt_secret" name="jwt_secret"
label="su Password" label={LL.SU_PASSWORD()}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.jwt_secret} value={data.jwt_secret}
onChange={updateFormValue} onChange={updateFormValue}
margin="normal" margin="normal"
/> />
<MessageBox <MessageBox level="info" message={LL.SU_TEXT()} mt={1} />
level="info"
message="The su (super user) password is used to sign authentication tokens and also enable admin privileges within the Console."
mt={1}
/>
<ButtonRow> <ButtonRow>
<Button <Button
startIcon={<SaveIcon />} startIcon={<SaveIcon />}
@@ -63,7 +63,7 @@ const SecuritySettingsForm: FC = () => {
type="submit" type="submit"
onClick={validateAndSubmit} onClick={validateAndSubmit}
> >
Save {LL.SAVE()}
</Button> </Button>
</ButtonRow> </ButtonRow>
</> </>
@@ -71,7 +71,7 @@ const SecuritySettingsForm: FC = () => {
}; };
return ( return (
<SectionContent title="Security Settings" titleGutter> <SectionContent title={LL.SETTINGS_OF(LL.SECURITY(1))} titleGutter>
{content()} {content()}
</SectionContent> </SectionContent>
); );

View File

@@ -3,6 +3,7 @@ import Schema, { ValidateFieldsError } from 'async-validator';
import CancelIcon from '@mui/icons-material/Cancel'; import CancelIcon from '@mui/icons-material/Cancel';
import PersonAddIcon from '@mui/icons-material/PersonAdd'; import PersonAddIcon from '@mui/icons-material/PersonAdd';
import SaveIcon from '@mui/icons-material/Save';
import { Button, Checkbox, Dialog, DialogActions, DialogContent, DialogTitle } from '@mui/material'; import { Button, Checkbox, Dialog, DialogActions, DialogContent, DialogTitle } from '@mui/material';
@@ -11,6 +12,8 @@ import { User } from '../../types';
import { updateValue } from '../../utils'; import { updateValue } from '../../utils';
import { validate } from '../../validators'; import { validate } from '../../validators';
import { useI18nContext } from '../../i18n/i18n-react';
interface UserFormProps { interface UserFormProps {
creating: boolean; creating: boolean;
validator: Schema; validator: Schema;
@@ -23,6 +26,8 @@ interface UserFormProps {
} }
const UserForm: FC<UserFormProps> = ({ creating, validator, user, setUser, onDoneEditing, onCancelEditing }) => { const UserForm: FC<UserFormProps> = ({ creating, validator, user, setUser, onDoneEditing, onCancelEditing }) => {
const { LL } = useI18nContext();
const updateFormValue = updateValue(setUser); const updateFormValue = updateValue(setUser);
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>(); const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
const open = !!user; const open = !!user;
@@ -49,12 +54,14 @@ const UserForm: FC<UserFormProps> = ({ creating, validator, user, setUser, onDon
<Dialog onClose={onCancelEditing} open={!!user} fullWidth maxWidth="sm"> <Dialog onClose={onCancelEditing} open={!!user} fullWidth maxWidth="sm">
{user && ( {user && (
<> <>
<DialogTitle id="user-form-dialog-title">{creating ? 'Add' : 'Modify'} User</DialogTitle> <DialogTitle id="user-form-dialog-title">
{creating ? LL.ADD(1) : LL.MODIFY()}&nbsp;{LL.USER(1)}
</DialogTitle>
<DialogContent dividers> <DialogContent dividers>
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="username" name="username"
label="Username" label={LL.USERNAME(1)}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={user.username} value={user.username}
@@ -65,7 +72,7 @@ const UserForm: FC<UserFormProps> = ({ creating, validator, user, setUser, onDon
<ValidatedPasswordField <ValidatedPasswordField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="password" name="password"
label="Password" label={LL.PASSWORD()}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={user.password} value={user.password}
@@ -74,21 +81,21 @@ const UserForm: FC<UserFormProps> = ({ creating, validator, user, setUser, onDon
/> />
<BlockFormControlLabel <BlockFormControlLabel
control={<Checkbox name="admin" checked={user.admin} onChange={updateFormValue} />} control={<Checkbox name="admin" checked={user.admin} onChange={updateFormValue} />}
label="is Admin?" label={LL.IS_ADMIN(1)}
/> />
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button startIcon={<CancelIcon />} variant="outlined" onClick={onCancelEditing} color="secondary"> <Button startIcon={<CancelIcon />} variant="outlined" onClick={onCancelEditing} color="secondary">
Cancel {LL.CANCEL()}
</Button> </Button>
<Button <Button
startIcon={<PersonAddIcon />} startIcon={creating ? <PersonAddIcon /> : <SaveIcon />}
variant="outlined" variant="outlined"
onClick={validateAndDone} onClick={validateAndDone}
color="primary" color="primary"
autoFocus autoFocus
> >
Add {creating ? LL.ADD(0) : LL.SAVE()}
</Button> </Button>
</DialogActions> </DialogActions>
</> </>

View File

@@ -1,26 +1,121 @@
import { AxiosPromise } from 'axios';
import { FC } from 'react'; import { FC } from 'react';
import { AxiosPromise } from 'axios';
import { Typography, Button, Box } from '@mui/material';
import { FileUploadConfig } from '../../api/endpoints'; import { FileUploadConfig } from '../../api/endpoints';
import { MessageBox, SingleUpload, useFileUpload } from '../../components';
import { SingleUpload, useFileUpload } from '../../components';
import DownloadIcon from '@mui/icons-material/GetApp';
import { useSnackbar } from 'notistack';
import { extractErrorMessage } from '../../utils';
import * as EMSESP from '../../project/api';
import { useI18nContext } from '../../i18n/i18n-react';
interface UploadFileProps { interface UploadFileProps {
uploadGeneralFile: (file: File, config?: FileUploadConfig) => AxiosPromise<void>; uploadGeneralFile: (file: File, config?: FileUploadConfig) => AxiosPromise<void>;
} }
const GeneralFileUpload: FC<UploadFileProps> = ({ uploadGeneralFile }) => { const GeneralFileUpload: FC<UploadFileProps> = ({ uploadGeneralFile }) => {
const [uploadFile, cancelUpload, uploading, uploadProgress] = useFileUpload({ upload: uploadGeneralFile }); const [uploadFile, cancelUpload, uploading, uploadProgress, md5] = useFileUpload({ upload: uploadGeneralFile });
const { enqueueSnackbar } = useSnackbar();
const { LL } = useI18nContext();
const saveFile = (json: any, endpoint: string) => {
const a = document.createElement('a');
const filename = 'emsesp_' + endpoint + '.json';
a.href = URL.createObjectURL(
new Blob([JSON.stringify(json, null, 2)], {
type: 'text/plain'
})
);
a.setAttribute('download', filename);
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
enqueueSnackbar(LL.DOWNLOAD_SUCCESSFUL(), { variant: 'info' });
};
const downloadSettings = async () => {
try {
const response = await EMSESP.getSettings();
if (response.status !== 200) {
enqueueSnackbar(LL.PROBLEM_LOADING(), { variant: 'error' });
} else {
saveFile(response.data, 'settings');
}
} catch (error) {
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' });
}
};
const downloadCustomizations = async () => {
try {
const response = await EMSESP.getCustomizations();
if (response.status !== 200) {
enqueueSnackbar(LL.PROBLEM_LOADING(), { variant: 'error' });
} else {
saveFile(response.data, 'customizations');
}
} catch (error) {
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' });
}
};
return ( return (
<> <>
{!uploading && ( {!uploading && (
<MessageBox <>
message="Upload a new firmware (.bin) file or an exported settings or customizations (.json) file below. EMS-ESP will restart afterwards to apply the new changes." <Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
level="warning" {LL.UPLOAD()}
my={2} </Typography>
/> <Box mb={2} color="warning.main">
<Typography variant="body2">{LL.UPLOAD_TEXT()} </Typography>
</Box>
</>
)}
{md5 !== '' && (
<Box mb={2}>
<Typography variant="body2">{'MD5: ' + md5}</Typography>
</Box>
)} )}
<SingleUpload onDrop={uploadFile} onCancel={cancelUpload} uploading={uploading} progress={uploadProgress} /> <SingleUpload onDrop={uploadFile} onCancel={cancelUpload} uploading={uploading} progress={uploadProgress} />
{!uploading && (
<>
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
{LL.DOWNLOAD(0)}
</Typography>
<Box color="warning.main">
<Typography mb={1} variant="body2">
{LL.DOWNLOAD_SETTINGS_TEXT()}
</Typography>
</Box>
<Button startIcon={<DownloadIcon />} variant="outlined" color="primary" onClick={() => downloadSettings()}>
{LL.SETTINGS_OF('')}
</Button>
<Box color="warning.main">
<Typography mt={2} mb={1} variant="body2">
{LL.DOWNLOAD_CUSTOMIZATION_TEXT()}{' '}
</Typography>
</Box>
<Button
startIcon={<DownloadIcon />}
variant="outlined"
color="primary"
onClick={() => downloadCustomizations()}
>
{LL.CUSTOMIZATION()}
</Button>
</>
)}
</> </>
); );
}; };

View File

@@ -12,6 +12,7 @@ import {
ValidatedPasswordField, ValidatedPasswordField,
ValidatedTextField ValidatedTextField
} from '../../components'; } from '../../components';
import { OTASettings } from '../../types'; import { OTASettings } from '../../types';
import { numberValue, updateValue, useRest } from '../../utils'; import { numberValue, updateValue, useRest } from '../../utils';
@@ -19,12 +20,16 @@ import { ValidateFieldsError } from 'async-validator';
import { validate } from '../../validators'; import { validate } from '../../validators';
import { OTA_SETTINGS_VALIDATOR } from '../../validators/system'; import { OTA_SETTINGS_VALIDATOR } from '../../validators/system';
import { useI18nContext } from '../../i18n/i18n-react';
const OTASettingsForm: FC = () => { const OTASettingsForm: FC = () => {
const { loadData, saving, data, setData, saveData, errorMessage } = useRest<OTASettings>({ const { loadData, saving, data, setData, saveData, errorMessage } = useRest<OTASettings>({
read: SystemApi.readOTASettings, read: SystemApi.readOTASettings,
update: SystemApi.updateOTASettings update: SystemApi.updateOTASettings
}); });
const { LL } = useI18nContext();
const updateFormValue = updateValue(setData); const updateFormValue = updateValue(setData);
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>(); const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
@@ -48,7 +53,7 @@ const OTASettingsForm: FC = () => {
<> <>
<BlockFormControlLabel <BlockFormControlLabel
control={<Checkbox name="enabled" checked={data.enabled} onChange={updateFormValue} />} control={<Checkbox name="enabled" checked={data.enabled} onChange={updateFormValue} />}
label="Enable OTA Updates" label={LL.ENABLE_OTA()}
/> />
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
@@ -64,7 +69,7 @@ const OTASettingsForm: FC = () => {
<ValidatedPasswordField <ValidatedPasswordField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="password" name="password"
label="Password" label={LL.PASSWORD()}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.password} value={data.password}
@@ -80,7 +85,7 @@ const OTASettingsForm: FC = () => {
type="submit" type="submit"
onClick={validateAndSubmit} onClick={validateAndSubmit}
> >
Save {LL.SAVE()}
</Button> </Button>
</ButtonRow> </ButtonRow>
</> </>
@@ -88,7 +93,7 @@ const OTASettingsForm: FC = () => {
}; };
return ( return (
<SectionContent title="OTA Settings" titleGutter> <SectionContent title={LL.SETTINGS_OF('OTA')} titleGutter>
{content()} {content()}
</SectionContent> </SectionContent>
); );

View File

@@ -4,6 +4,8 @@ import { FC, useRef, useState } from 'react';
import * as SystemApi from '../../api/system'; import * as SystemApi from '../../api/system';
import { FormLoader } from '../../components'; import { FormLoader } from '../../components';
import { useI18nContext } from '../../i18n/i18n-react';
const RESTART_TIMEOUT = 2 * 60 * 1000; const RESTART_TIMEOUT = 2 * 60 * 1000;
const POLL_TIMEOUT = 2000; const POLL_TIMEOUT = 2000;
const POLL_INTERVAL = 5000; const POLL_INTERVAL = 5000;
@@ -12,12 +14,14 @@ const RestartMonitor: FC = () => {
const [failed, setFailed] = useState<boolean>(false); const [failed, setFailed] = useState<boolean>(false);
const [timeoutId, setTimeoutId] = useState<NodeJS.Timeout>(); const [timeoutId, setTimeoutId] = useState<NodeJS.Timeout>();
const { LL } = useI18nContext();
const timeoutAt = useRef(new Date().getTime() + RESTART_TIMEOUT); const timeoutAt = useRef(new Date().getTime() + RESTART_TIMEOUT);
const poll = useRef(async () => { const poll = useRef(async () => {
try { try {
await SystemApi.readSystemStatus(POLL_TIMEOUT); await SystemApi.readSystemStatus(POLL_TIMEOUT);
document.location.href = '/fileUpdated'; document.location.href = '/fileUpdated';
} catch (error: unknown) { } catch (error) {
if (new Date().getTime() < timeoutAt.current) { if (new Date().getTime() < timeoutAt.current) {
setTimeoutId(setTimeout(poll.current, POLL_INTERVAL)); setTimeoutId(setTimeout(poll.current, POLL_INTERVAL));
} else { } else {
@@ -32,12 +36,7 @@ const RestartMonitor: FC = () => {
useEffect(() => () => timeoutId && clearTimeout(timeoutId), [timeoutId]); useEffect(() => () => timeoutId && clearTimeout(timeoutId), [timeoutId]);
return ( return <FormLoader message={LL.APPLICATION_RESTARTING() + '...'} errorMessage={failed ? 'Timed out' : undefined} />;
<FormLoader
message="EMS-ESP is restarting, please wait&hellip;"
errorMessage={failed ? 'Timed out waiting for device to restart.' : undefined}
/>
);
}; };
export default RestartMonitor; export default RestartMonitor;

View File

@@ -12,8 +12,12 @@ import OTASettingsForm from './OTASettingsForm';
import SystemLog from './SystemLog'; import SystemLog from './SystemLog';
import { useI18nContext } from '../../i18n/i18n-react';
const System: FC = () => { const System: FC = () => {
useLayoutTitle('System'); const { LL } = useI18nContext();
useLayoutTitle(LL.SYSTEM(0));
const { me } = useContext(AuthenticatedContext); const { me } = useContext(AuthenticatedContext);
const { features } = useContext(FeaturesContext); const { features } = useContext(FeaturesContext);
@@ -22,11 +26,11 @@ const System: FC = () => {
return ( return (
<> <>
<RouterTabs value={routerTab}> <RouterTabs value={routerTab}>
<Tab value="status" label="System Status" /> <Tab value="status" label={LL.STATUS_OF(LL.SYSTEM(1))} />
<Tab value="log" label="System Log" /> <Tab value="log" label={LL.LOG_OF(LL.SYSTEM(2))} />
{features.ota && <Tab value="ota" label="OTA Settings" disabled={!me.admin} />} {features.ota && <Tab value="ota" label={LL.SETTINGS_OF('OTA')} disabled={!me.admin} />}
{features.upload_firmware && <Tab value="upload" label="Upload" disabled={!me.admin} />} {features.upload_firmware && <Tab value="upload" label={LL.UPLOAD_DOWNLOAD()} disabled={!me.admin} />}
</RouterTabs> </RouterTabs>
<Routes> <Routes>
<Route path="status" element={<SystemStatusForm />} /> <Route path="status" element={<SystemStatusForm />} />

View File

@@ -15,6 +15,9 @@ import DownloadIcon from '@mui/icons-material/GetApp';
import { useSnackbar } from 'notistack'; import { useSnackbar } from 'notistack';
import { EVENT_SOURCE_ROOT } from '../../api/endpoints'; import { EVENT_SOURCE_ROOT } from '../../api/endpoints';
import { useI18nContext } from '../../i18n/i18n-react';
export const LOG_EVENTSOURCE_URL = EVENT_SOURCE_ROOT + 'log'; export const LOG_EVENTSOURCE_URL = EVENT_SOURCE_ROOT + 'log';
const useWindowSize = () => { const useWindowSize = () => {
@@ -63,12 +66,13 @@ const levelLabel = (level: LogLevel) => {
const SystemLog: FC = () => { const SystemLog: FC = () => {
useWindowSize(); useWindowSize();
const { LL } = useI18nContext();
const { loadData, data, setData } = useRest<LogSettings>({ const { loadData, data, setData } = useRest<LogSettings>({
read: SystemApi.readLogSettings read: SystemApi.readLogSettings
}); });
const [errorMessage, setErrorMessage] = useState<string>(); const [errorMessage, setErrorMessage] = useState<string>();
const [reconnectTimeout, setReconnectTimeout] = useState<NodeJS.Timeout>();
const [logEntries, setLogEntries] = useState<LogEntries>({ events: [] }); const [logEntries, setLogEntries] = useState<LogEntries>({ events: [] });
const [lastIndex, setLastIndex] = useState<number>(0); const [lastIndex, setLastIndex] = useState<number>(0);
@@ -104,10 +108,10 @@ const SystemLog: FC = () => {
compact: data.compact compact: data.compact
}); });
if (response.status !== 200) { if (response.status !== 200) {
enqueueSnackbar('Problem applying log settings', { variant: 'error' }); enqueueSnackbar(LL.PROBLEM_UPDATING(), { variant: 'error' });
} }
} catch (error: unknown) { } catch (error) {
enqueueSnackbar(extractErrorMessage(error, 'Problem applying log settings'), { variant: 'error' }); enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
} }
} }
}; };
@@ -157,11 +161,11 @@ const SystemLog: FC = () => {
const fetchLog = useCallback(async () => { const fetchLog = useCallback(async () => {
try { try {
setLogEntries((await SystemApi.readLogEntries()).data); await SystemApi.readLogEntries();
} catch (error: unknown) { } catch (error) {
setErrorMessage(extractErrorMessage(error, 'Failed to fetch log')); setErrorMessage(extractErrorMessage(error, LL.PROBLEM_LOADING()));
} }
}, []); }, [LL]);
useEffect(() => { useEffect(() => {
fetchLog(); fetchLog();
@@ -171,20 +175,14 @@ const SystemLog: FC = () => {
const es = new EventSource(addAccessTokenParameter(LOG_EVENTSOURCE_URL)); const es = new EventSource(addAccessTokenParameter(LOG_EVENTSOURCE_URL));
es.onmessage = onMessage; es.onmessage = onMessage;
es.onerror = () => { es.onerror = () => {
if (reconnectTimeout) { es.close();
es.close(); reloadPage();
setReconnectTimeout(setTimeout(reloadPage, 1000));
}
}; };
return () => { return () => {
es.close(); es.close();
if (reconnectTimeout) {
clearTimeout(reconnectTimeout);
}
}; };
// eslint-disable-next-line // eslint-disable-next-line
}, [reconnectTimeout]); }, []);
const content = () => { const content = () => {
if (!data) { if (!data) {
@@ -197,7 +195,7 @@ const SystemLog: FC = () => {
<Grid item xs={4}> <Grid item xs={4}>
<ValidatedTextField <ValidatedTextField
name="level" name="level"
label="Log Level" label={LL.LOG_LEVEL()}
value={data.level} value={data.level}
fullWidth fullWidth
variant="outlined" variant="outlined"
@@ -205,6 +203,7 @@ const SystemLog: FC = () => {
margin="normal" margin="normal"
select select
> >
<MenuItem value={-1}>OFF</MenuItem>
<MenuItem value={3}>ERROR</MenuItem> <MenuItem value={3}>ERROR</MenuItem>
<MenuItem value={4}>WARNING</MenuItem> <MenuItem value={4}>WARNING</MenuItem>
<MenuItem value={5}>NOTICE</MenuItem> <MenuItem value={5}>NOTICE</MenuItem>
@@ -214,7 +213,7 @@ const SystemLog: FC = () => {
</ValidatedTextField> </ValidatedTextField>
</Grid> </Grid>
<Grid item xs={3}> <Grid item xs={3}>
<FormLabel>Buffer size</FormLabel> <FormLabel>{LL.BUFFER_SIZE()}</FormLabel>
<Slider <Slider
value={data.max_messages} value={data.max_messages}
valueLabelDisplay="auto" valueLabelDisplay="auto"
@@ -235,12 +234,12 @@ const SystemLog: FC = () => {
<Grid item> <Grid item>
<BlockFormControlLabel <BlockFormControlLabel
control={<Checkbox checked={data.compact} onChange={updateFormValue} name="compact" />} control={<Checkbox checked={data.compact} onChange={updateFormValue} name="compact" />}
label="Compact" label={LL.COMPACT()}
/> />
</Grid> </Grid>
<Grid item> <Grid item>
<Button startIcon={<DownloadIcon />} variant="outlined" color="secondary" onClick={onDownload}> <Button startIcon={<DownloadIcon />} variant="outlined" color="secondary" onClick={onDownload}>
Export {LL.EXPORT()}
</Button> </Button>
</Grid> </Grid>
</Grid> </Grid>
@@ -273,7 +272,7 @@ const SystemLog: FC = () => {
}; };
return ( return (
<SectionContent title="System Log" titleGutter id="log-window"> <SectionContent title={LL.LOG_OF(LL.SYSTEM(2))} titleGutter id="log-window">
{content()} {content()}
</SectionContent> </SectionContent>
); );

View File

@@ -22,6 +22,7 @@ import ShowChartIcon from '@mui/icons-material/ShowChart';
import MemoryIcon from '@mui/icons-material/Memory'; import MemoryIcon from '@mui/icons-material/Memory';
import AppsIcon from '@mui/icons-material/Apps'; import AppsIcon from '@mui/icons-material/Apps';
import SdStorageIcon from '@mui/icons-material/SdStorage'; import SdStorageIcon from '@mui/icons-material/SdStorage';
import SdCardAlertIcon from '@mui/icons-material/SdCardAlert';
import FolderIcon from '@mui/icons-material/Folder'; import FolderIcon from '@mui/icons-material/Folder';
import RefreshIcon from '@mui/icons-material/Refresh'; import RefreshIcon from '@mui/icons-material/Refresh';
import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew'; import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
@@ -31,13 +32,16 @@ import TimerIcon from '@mui/icons-material/Timer';
import CancelIcon from '@mui/icons-material/Cancel'; import CancelIcon from '@mui/icons-material/Cancel';
import { ButtonRow, FormLoader, SectionContent, MessageBox } from '../../components'; import { ButtonRow, FormLoader, SectionContent, MessageBox } from '../../components';
import { EspPlatform, SystemStatus, Version } from '../../types'; import { SystemStatus, Version } from '../../types';
import * as SystemApi from '../../api/system'; import * as SystemApi from '../../api/system';
import { extractErrorMessage, useRest } from '../../utils'; import { extractErrorMessage, useRest } from '../../utils';
import { AuthenticatedContext } from '../../contexts/authentication'; import { AuthenticatedContext } from '../../contexts/authentication';
import axios from 'axios'; import axios from 'axios';
import RestartMonitor from './RestartMonitor';
import { useI18nContext } from '../../i18n/i18n-react';
export const VERSIONCHECK_ENDPOINT = 'https://api.github.com/repos/emsesp/EMS-ESP32/releases/latest'; export const VERSIONCHECK_ENDPOINT = 'https://api.github.com/repos/emsesp/EMS-ESP32/releases/latest';
export const VERSIONCHECK_DEV_ENDPOINT = 'https://api.github.com/repos/emsesp/EMS-ESP32/releases/tags/latest'; export const VERSIONCHECK_DEV_ENDPOINT = 'https://api.github.com/repos/emsesp/EMS-ESP32/releases/tags/latest';
@@ -48,6 +52,9 @@ function formatNumber(num: number) {
} }
const SystemStatusForm: FC = () => { const SystemStatusForm: FC = () => {
const { LL } = useI18nContext();
const [restarting, setRestarting] = useState<boolean>();
const { loadData, data, errorMessage } = useRest<SystemStatus>({ read: SystemApi.readSystemStatus }); const { loadData, data, errorMessage } = useRest<SystemStatus>({ read: SystemApi.readSystemStatus });
const { me } = useContext(AuthenticatedContext); const { me } = useContext(AuthenticatedContext);
@@ -64,7 +71,7 @@ const SystemStatusForm: FC = () => {
setLatestVersion({ setLatestVersion({
version: response.data.name, version: response.data.name,
url: response.data.assets[1].browser_download_url, url: response.data.assets[1].browser_download_url,
changelog: response.data.html_url changelog: response.data.assets[0].browser_download_url
}); });
}); });
axios.get(VERSIONCHECK_DEV_ENDPOINT).then((response) => { axios.get(VERSIONCHECK_DEV_ENDPOINT).then((response) => {
@@ -79,10 +86,25 @@ const SystemStatusForm: FC = () => {
const restart = async () => { const restart = async () => {
setProcessing(true); setProcessing(true);
try { try {
await SystemApi.restart(); const response = await SystemApi.restart();
enqueueSnackbar('EMS-ESP is restarting...', { variant: 'info' }); if (response.status === 200) {
} catch (error: unknown) { setRestarting(true);
enqueueSnackbar(extractErrorMessage(error, 'Problem restarting device'), { variant: 'error' }); }
} catch (error) {
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' });
} finally {
setConfirmRestart(false);
setProcessing(false);
}
};
const partition = async () => {
setProcessing(true);
try {
await SystemApi.partition();
setRestarting(true);
} catch (error) {
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' });
} finally { } finally {
setConfirmRestart(false); setConfirmRestart(false);
setProcessing(false); setProcessing(false);
@@ -91,16 +113,17 @@ const SystemStatusForm: FC = () => {
const renderRestartDialog = () => ( const renderRestartDialog = () => (
<Dialog open={confirmRestart} onClose={() => setConfirmRestart(false)}> <Dialog open={confirmRestart} onClose={() => setConfirmRestart(false)}>
<DialogTitle>Restart</DialogTitle> <DialogTitle>{LL.RESTART()}</DialogTitle>
<DialogContent dividers>Are you sure you want to restart EMS-ESP?</DialogContent> <DialogContent dividers>{LL.RESTART_CONFIRM()}</DialogContent>
<DialogActions> <DialogActions>
<Button <Button
startIcon={<CancelIcon />} startIcon={<CancelIcon />}
variant="outlined" variant="outlined"
onClick={() => setConfirmRestart(false)} onClick={() => setConfirmRestart(false)}
disabled={processing}
color="secondary" color="secondary"
> >
Cancel {LL.CANCEL()}
</Button> </Button>
<Button <Button
startIcon={<PowerSettingsNewIcon />} startIcon={<PowerSettingsNewIcon />}
@@ -110,8 +133,19 @@ const SystemStatusForm: FC = () => {
color="primary" color="primary"
autoFocus autoFocus
> >
Restart {LL.RESTART()}
</Button> </Button>
{data?.has_loader && (
<Button
startIcon={<PowerSettingsNewIcon />}
variant="outlined"
onClick={partition}
disabled={processing}
color="primary"
>
EMS-ESP-Loader
</Button>
)}
</DialogActions> </DialogActions>
</Dialog> </Dialog>
); );
@@ -119,22 +153,19 @@ const SystemStatusForm: FC = () => {
const renderVersionDialog = () => { const renderVersionDialog = () => {
return ( return (
<Dialog open={showingVersion} onClose={() => setShowingVersion(false)}> <Dialog open={showingVersion} onClose={() => setShowingVersion(false)}>
<DialogTitle>Version Check</DialogTitle> <DialogTitle>{LL.VERSION_CHECK(1)}</DialogTitle>
<DialogContent dividers> <DialogContent dividers>
<MessageBox <MessageBox my={0} level="info" message={LL.SYSTEM_VERSION_RUNNING() + ' ' + data?.emsesp_version} />
my={0}
level="info"
message={'You are currently running EMS-ESP version ' + data?.emsesp_version}
/>
{latestVersion && ( {latestVersion && (
<Box mt={2} mb={2}> <Box mt={2} mb={2}>
The latest <u>official</u> version is <b>{latestVersion.version}</b>&nbsp;( {LL.THE_LATEST()}&nbsp;<u>{LL.OFFICIAL()}</u>&nbsp;{LL.VERSION_IS()}&nbsp;<b>{latestVersion.version}</b>
&nbsp;(
<Link target="_blank" href={latestVersion.changelog} color="primary"> <Link target="_blank" href={latestVersion.changelog} color="primary">
{'release notes'} {LL.RELEASE_NOTES()}
</Link> </Link>
)&nbsp;( )&nbsp;(
<Link target="_blank" href={latestVersion.url} color="primary"> <Link target="_blank" href={latestVersion.url} color="primary">
{'download'} {LL.DOWNLOAD(1)}
</Link> </Link>
) )
</Box> </Box>
@@ -142,14 +173,15 @@ const SystemStatusForm: FC = () => {
{latestDevVersion && ( {latestDevVersion && (
<Box mt={2} mb={2}> <Box mt={2} mb={2}>
The latest <u>development</u> version is&nbsp;<b>{latestDevVersion.version}</b> {LL.THE_LATEST()}&nbsp;<u>{LL.DEVELOPMENT()}</u>&nbsp;{LL.VERSION_IS()}&nbsp;
<b>{latestDevVersion.version}</b>
&nbsp;( &nbsp;(
<Link target="_blank" href={latestDevVersion.changelog} color="primary"> <Link target="_blank" href={latestDevVersion.changelog} color="primary">
{'release notes'} {LL.RELEASE_NOTES()}
</Link> </Link>
)&nbsp;( )&nbsp;(
<Link target="_blank" href={latestDevVersion.url} color="primary"> <Link target="_blank" href={latestDevVersion.url} color="primary">
{'download'} {LL.DOWNLOAD(1)}
</Link> </Link>
) )
</Box> </Box>
@@ -157,17 +189,17 @@ const SystemStatusForm: FC = () => {
<Box color="warning.main" p={0} pl={0} pr={0} mt={4} mb={0}> <Box color="warning.main" p={0} pl={0} pr={0} mt={4} mb={0}>
<Typography variant="body2"> <Typography variant="body2">
Use&nbsp; {LL.USE()}&nbsp;
<Link target="_blank" href={uploadURL} color="primary"> <Link href={uploadURL} color="primary">
{'UPLOAD'} {LL.UPLOAD()}
</Link> </Link>
&nbsp;to apply the new firmware &nbsp;{LL.SYSTEM_APPLY_FIRMWARE()}
</Typography> </Typography>
</Box> </Box>
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button variant="outlined" onClick={() => setShowingVersion(false)} color="secondary"> <Button variant="outlined" onClick={() => setShowingVersion(false)} color="secondary">
Close {LL.CLOSE()}
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
@@ -178,9 +210,9 @@ const SystemStatusForm: FC = () => {
setProcessing(true); setProcessing(true);
try { try {
await SystemApi.factoryReset(); await SystemApi.factoryReset();
enqueueSnackbar('Device has been factory reset and will now restart', { variant: 'info' }); setRestarting(true);
} catch (error: unknown) { } catch (error) {
enqueueSnackbar(extractErrorMessage(error, 'Problem factory resetting the device'), { variant: 'error' }); enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
} finally { } finally {
setConfirmFactoryReset(false); setConfirmFactoryReset(false);
setProcessing(false); setProcessing(false);
@@ -189,16 +221,17 @@ const SystemStatusForm: FC = () => {
const renderFactoryResetDialog = () => ( const renderFactoryResetDialog = () => (
<Dialog open={confirmFactoryReset} onClose={() => setConfirmFactoryReset(false)}> <Dialog open={confirmFactoryReset} onClose={() => setConfirmFactoryReset(false)}>
<DialogTitle>Factory Reset</DialogTitle> <DialogTitle>{LL.FACTORY_RESET()}</DialogTitle>
<DialogContent dividers>Are you sure you want to reset the device to its factory defaults?</DialogContent> <DialogContent dividers>{LL.SYSTEM_FACTORY_TEXT_DIALOG()}</DialogContent>
<DialogActions> <DialogActions>
<Button <Button
startIcon={<CancelIcon />} startIcon={<CancelIcon />}
variant="outlined" variant="outlined"
onClick={() => setConfirmFactoryReset(false)} onClick={() => setConfirmFactoryReset(false)}
disabled={processing}
color="secondary" color="secondary"
> >
Cancel {LL.CANCEL()}
</Button> </Button>
<Button <Button
startIcon={<SettingsBackupRestoreIcon />} startIcon={<SettingsBackupRestoreIcon />}
@@ -208,7 +241,7 @@ const SystemStatusForm: FC = () => {
autoFocus autoFocus
color="error" color="error"
> >
Factory Reset {LL.FACTORY_RESET()}
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
@@ -228,10 +261,10 @@ const SystemStatusForm: FC = () => {
<BuildIcon /> <BuildIcon />
</Avatar> </Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText primary="EMS-ESP Version" secondary={'v' + data.emsesp_version} /> <ListItemText primary={LL.EMS_ESP_VER()} secondary={'v' + data.emsesp_version} />
{latestVersion && ( {latestVersion && (
<Button color="primary" onClick={() => setShowingVersion(true)}> <Button color="primary" onClick={() => setShowingVersion(true)}>
Version Check {LL.VERSION_CHECK(0)}
</Button> </Button>
)} )}
</ListItem> </ListItem>
@@ -242,7 +275,7 @@ const SystemStatusForm: FC = () => {
<DevicesIcon /> <DevicesIcon />
</Avatar> </Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText primary="Device (Platform / SDK)" secondary={data.esp_platform + ' / ' + data.sdk_version} /> <ListItemText primary={LL.PLATFORM()} secondary={data.esp_platform + ' / ' + data.sdk_version} />
</ListItem> </ListItem>
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />
<ListItem> <ListItem>
@@ -251,7 +284,7 @@ const SystemStatusForm: FC = () => {
<TimerIcon /> <TimerIcon />
</Avatar> </Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText primary="System Uptime" secondary={data.uptime} /> <ListItemText primary={LL.UPTIME()} secondary={data.uptime} />
</ListItem> </ListItem>
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />
<ListItem> <ListItem>
@@ -260,7 +293,7 @@ const SystemStatusForm: FC = () => {
<ShowChartIcon /> <ShowChartIcon />
</Avatar> </Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText primary="CPU Frequency" secondary={data.cpu_freq_mhz + ' MHz'} /> <ListItemText primary={LL.CPU_FREQ()} secondary={data.cpu_freq_mhz + ' MHz'} />
</ListItem> </ListItem>
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />
<ListItem> <ListItem>
@@ -270,17 +303,11 @@ const SystemStatusForm: FC = () => {
</Avatar> </Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText <ListItemText
primary="Heap (Free / Max Alloc)" primary={LL.HEAP()}
secondary={ secondary={formatNumber(data.free_heap) + ' KB / ' + formatNumber(data.max_alloc_heap) + ' KB '}
formatNumber(data.free_heap) +
' / ' +
formatNumber(data.max_alloc_heap) +
' bytes ' +
(data.esp_platform === EspPlatform.ESP8266 ? '(' + data.heap_fragmentation + '% fragmentation)' : '')
}
/> />
</ListItem> </ListItem>
{data.esp_platform === EspPlatform.ESP32 && data.psram_size > 0 && ( {data.psram_size !== undefined && data.free_psram !== undefined && (
<> <>
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />
<ListItem> <ListItem>
@@ -290,8 +317,8 @@ const SystemStatusForm: FC = () => {
</Avatar> </Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText <ListItemText
primary="PSRAM (Size / Free)" primary={LL.PSRAM()}
secondary={formatNumber(data.psram_size) + ' / ' + formatNumber(data.free_psram) + ' bytes'} secondary={formatNumber(data.psram_size) + ' KB / ' + formatNumber(data.free_psram) + ' KB'}
/> />
</ListItem> </ListItem>
</> </>
@@ -304,13 +331,25 @@ const SystemStatusForm: FC = () => {
</Avatar> </Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText <ListItemText
primary="Flash Chip (Size / Speed)" primary={LL.FLASH()}
secondary={ secondary={
formatNumber(data.flash_chip_size) + ' bytes / ' + (data.flash_chip_speed / 1000000).toFixed(0) + ' MHz' formatNumber(data.flash_chip_size) + ' KB / ' + (data.flash_chip_speed / 1000000).toFixed(0) + ' MHz'
} }
/> />
</ListItem> </ListItem>
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />
<ListItem>
<ListItemAvatar>
<Avatar>
<SdCardAlertIcon />
</Avatar>
</ListItemAvatar>
<ListItemText
primary={LL.APPSIZE()}
secondary={formatNumber(data.app_used) + ' KB / ' + formatNumber(data.app_free) + ' KB'}
/>
</ListItem>
<Divider variant="inset" component="li" />
<ListItem> <ListItem>
<ListItemAvatar> <ListItemAvatar>
<Avatar> <Avatar>
@@ -318,15 +357,8 @@ const SystemStatusForm: FC = () => {
</Avatar> </Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText <ListItemText
primary="File System (Used / Total)" primary={LL.FILESYSTEM()}
secondary={ secondary={formatNumber(data.fs_used) + ' KB / ' + formatNumber(data.fs_free) + ' KB'}
formatNumber(data.fs_used) +
' / ' +
formatNumber(data.fs_total) +
' bytes (' +
formatNumber(data.fs_total - data.fs_used) +
'\xa0bytes free)'
}
/> />
</ListItem> </ListItem>
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />
@@ -335,7 +367,7 @@ const SystemStatusForm: FC = () => {
<Box flexGrow={1} sx={{ '& button': { mt: 2 } }}> <Box flexGrow={1} sx={{ '& button': { mt: 2 } }}>
<ButtonRow> <ButtonRow>
<Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={loadData}> <Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={loadData}>
Refresh {LL.REFRESH()}
</Button> </Button>
</ButtonRow> </ButtonRow>
</Box> </Box>
@@ -348,7 +380,7 @@ const SystemStatusForm: FC = () => {
color="primary" color="primary"
onClick={() => setConfirmRestart(true)} onClick={() => setConfirmRestart(true)}
> >
Restart {LL.RESTART()}
</Button> </Button>
<Button <Button
startIcon={<SettingsBackupRestoreIcon />} startIcon={<SettingsBackupRestoreIcon />}
@@ -356,7 +388,7 @@ const SystemStatusForm: FC = () => {
onClick={() => setConfirmFactoryReset(true)} onClick={() => setConfirmFactoryReset(true)}
color="error" color="error"
> >
Factory reset {LL.FACTORY_RESET()}
</Button> </Button>
</ButtonRow> </ButtonRow>
</Box> </Box>
@@ -370,8 +402,8 @@ const SystemStatusForm: FC = () => {
}; };
return ( return (
<SectionContent title="System Status" titleGutter> <SectionContent title={LL.STATUS_OF(LL.SYSTEM(1))} titleGutter>
{content()} {restarting ? <RestartMonitor /> : content()}
</SectionContent> </SectionContent>
); );
}; };

View File

@@ -7,17 +7,23 @@ import { FileUploadConfig } from '../../api/endpoints';
import GeneralFileUpload from './GeneralFileUpload'; import GeneralFileUpload from './GeneralFileUpload';
import RestartMonitor from './RestartMonitor'; import RestartMonitor from './RestartMonitor';
import { useI18nContext } from '../../i18n/i18n-react';
const UploadFileForm: FC = () => { const UploadFileForm: FC = () => {
const [restarting, setRestarting] = useState<boolean>(); const [restarting, setRestarting] = useState<boolean>();
const { LL } = useI18nContext();
const uploadFile = useRef(async (file: File, config?: FileUploadConfig) => { const uploadFile = useRef(async (file: File, config?: FileUploadConfig) => {
const response = await SystemApi.uploadFile(file, config); const response = await SystemApi.uploadFile(file, config);
setRestarting(true); if (response.status === 200) {
setRestarting(true);
}
return response; return response;
}); });
return ( return (
<SectionContent title="Upload File" titleGutter> <SectionContent title={LL.UPLOAD_DOWNLOAD()} titleGutter>
{restarting ? <RestartMonitor /> : <GeneralFileUpload uploadGeneralFile={uploadFile.current} />} {restarting ? <RestartMonitor /> : <GeneralFileUpload uploadGeneralFile={uploadFile.current} />}
</SectionContent> </SectionContent>
); );

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 85.333 512 341.333"><path fill="#D80027" d="M0 85.331h512v341.337H0z"/><path d="M0 85.331h512v113.775H0z"/><path fill="#FFDA44" d="M0 312.882h512v113.775H0z"/></svg>

After

Width:  |  Height:  |  Size: 216 B

1
interface/src/i18n/FR.svg Executable file
View 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.331h512v341.337H0z"/><path fill="#0052B4" d="M0 85.331h170.663v341.337H0z"/><path fill="#D80027" d="M341.337 85.331H512v341.337H341.337z"/></svg>

After

Width:  |  Height:  |  Size: 243 B

View 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.333h512V426.67H0z"/><path fill="#D80027" d="M288 85.33h-64v138.666H0v64h224v138.666h64V287.996h224v-64H288z"/><g fill="#0052B4"><path d="M393.785 315.358 512 381.034v-65.676zM311.652 315.358 512 426.662v-31.474l-143.693-79.83zM458.634 426.662l-146.982-81.664v81.664z"/></g><path fill="#FFF" d="M311.652 315.358 512 426.662v-31.474l-143.693-79.83z"/><path fill="#D80027" d="M311.652 315.358 512 426.662v-31.474l-143.693-79.83z"/><g fill="#0052B4"><path d="M90.341 315.356 0 365.546v-50.19zM200.348 329.51v97.151H25.491z"/></g><path fill="#D80027" d="M143.693 315.358 0 395.188v31.474l200.348-111.304z"/><g fill="#0052B4"><path d="M118.215 196.634 0 130.958v65.676zM200.348 196.634 0 85.33v31.474l143.693 79.83zM53.366 85.33l146.982 81.664V85.33z"/></g><path fill="#FFF" d="M200.348 196.634 0 85.33v31.474l143.693 79.83z"/><path fill="#D80027" d="M200.348 196.634 0 85.33v31.474l143.693 79.83z"/><g fill="#0052B4"><path d="M421.659 196.636 512 146.446v50.19zM311.652 182.482V85.331h174.857z"/></g><path fill="#D80027" d="M368.307 196.634 512 116.804V85.33L311.652 196.634z"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 85.5 513 342"><path fill="#FFF" d="M0 85.5h513v342H0z"/><path fill="#cd1f2a" d="M0 85.5h513v114H0z"/><path fill="#1d4185" d="M0 312h513v114H0z"/></svg>

After

Width:  |  Height:  |  Size: 202 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 85.333 512 341.333"><path fill="#D80027" d="M0 85.334h512v341.337H0z"/><path fill="#FFF" d="M512 295.883H202.195v130.783H122.435V295.883H0V216.111h122.435V85.329H202.195v130.782H512V277.329z"/><path fill="#2E52B2" d="M512 234.666v42.663H183.652v149.337h-42.674V277.329H0v-42.663h140.978V85.329h42.674v149.337z"/></svg>

After

Width:  |  Height:  |  Size: 369 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 85.333 512 341.333"><g fill="#FFF"><path d="M0 85.337h512v341.326H0z"/><path d="M0 85.337h512V256H0z"/></g><path fill="#D80027" d="M0 256h512v170.663H0z"/></svg>

After

Width:  |  Height:  |  Size: 212 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 85.333 512 341.333"><path fill="#0052B4" d="M0 85.333h512V426.67H0z"/><path fill="#FFDA44" d="M192 85.33h-64v138.666H0v64h128v138.666h64V287.996h320v-64H192z"/></svg>

After

Width:  |  Height:  |  Size: 217 B

View File

@@ -0,0 +1,310 @@
import type { Translation } from '../i18n-types';
/* prettier-ignore */
/* eslint-disable */
const de: Translation = {
LANGUAGE: 'Sprache',
RETRY: 'Neuer Versuch',
LOADING: 'Laden',
IS_REQUIRED: '{0} ist erforderlich',
SIGN_IN: 'Einloggen',
SIGN_OUT: 'Ausloggen',
USERNAME: 'Nutzername',
PASSWORD: 'Passwort',
SU_PASSWORD: 'su Passwort',
DASHBOARD: 'Kontrollzentrum',
SETTINGS_OF: '{0} Einstellungen',
SAVED: 'gespeichert',
HELP_OF: '{0} Hilfe',
LOGGED_IN: 'Eingeloggt als {name}',
PLEASE_SIGNIN: 'Bitte einloggen, um fortzufahren',
UPLOAD_SUCCESSFUL: 'Hochladen erfolgreich',
DOWNLOAD_SUCCESSFUL: 'Herunterladen erfolgreich',
INVALID_LOGIN: 'Ungültige Login Daten',
NETWORK: 'Netzwerk',
SECURITY: 'Sicherheit',
ONOFF_CAP: 'AN/AUS',
ONOFF: 'an/aus',
TYPE: 'Typ',
DESCRIPTION: 'Bezeichnung',
ENTITIES: 'Entitäten',
REFRESH: 'Aktualisieren',
EXPORT: 'Exportieren',
DEVICE_DETAILS: 'Geräte Details',
BRAND: 'Marke',
ID_OF: '{0} ID',
DEVICE: 'Geräte',
PRODUCT: 'Produkt',
VERSION: 'Version',
ENTITY_NAME: 'Entitätsname',
VALUE: '{{Wert|wert}}',
SHOW_FAV: 'nur Favoriten anzeigen',
DEVICE_SENSOR_DATA: 'Geräte- und Sensordaten',
DEVICES_SENSORS: 'Geräte & Sensoren',
ATTACHED_SENSORS: 'Angeschlossene EMS-ESP Sensoren',
RUN_COMMAND: 'Befehl ausführen',
CHANGE_VALUE: 'Wert ändern',
CANCEL: 'Abbrechen',
RESET: 'Zurücksetzen',
SEND: 'Senden',
SAVE: 'Speichern',
REMOVE: 'Entfernen',
PROBLEM_UPDATING: 'Problem beim Aktualisieren',
PROBLEM_LOADING: 'Problem beim Laden',
ACCESS_DENIED: 'Zugriff abgelehnt',
ANALOG_SENSOR: 'Analogsensor',
ANALOG_SENSORS: 'Analogsensoren',
UPDATED_OF: '{0} Aktualisiert',
UPDATE_OF: '{0} Aktualisieren',
REMOVED_OF: '{0} Entfernt',
DELETION_OF: '{0} Löschung',
OFFSET: 'Addition',
FACTOR: 'Faktor',
FREQ: 'Frequenz',
DUTY_CYCLE: 'Duty Cycle',
UNIT: 'UoM',
STARTVALUE: 'Startwert',
WARN_GPIO: 'Warnung: Vorsicht bei der korrekten Wahl des GPIO!',
EDIT: 'Editiere',
SENSOR: 'Sensor',
TEMP_SENSOR: 'Temperatursensor',
TEMP_SENSORS: 'Temperatursensoren',
WRITE_CMD_SENT: 'Befehl schreiben wurde gesendet',
WRITE_CMD_FAILED: 'Befehl schreiben failed', // TODO translate
EMS_BUS_WARNING: 'EMS-Bus getrennt. Wenn diese Warnung nach einigen Sekunden immer noch besteht, überprüfen Sie bitte die Einstellungen und das Board-Profil',
EMS_BUS_SCANNING: 'Suche nach EMS Geräten...',
CONNECTED: 'Verbunden',
TX_ISSUES: 'Tx-Probleme - versuchen Sie einen anderen Tx-Modus',
DISCONNECTED: 'Getrennt',
EMS_SCAN: 'Möchten Sie wirklich eine vollständige Gerätesuche des EMS-Busses starten?',
EMS_BUS_STATUS: 'EMS-Busstatus',
ACTIVE_DEVICES: 'Aktive Geräte und Sensoren',
EMS_DEVICE: 'EMS Gerät',
SUCCESS: 'ERFOLG',
FAIL: 'FEHLER',
QUALITY: 'QUALITÄT',
SCAN_DEVICES: 'Nach neuen Geräten suchen',
EMS_BUS_STATUS_TITLE: 'EMS-Bus- und Aktivitätsstatus',
SCAN: 'Suche',
STATUS_NAMES: [
'EMS-Telegramme empfangen (Rx)',
'EMS-Telegramme gelesen (Tx)',
'EMS-Telegramme geschrieben (Tx)',
'Temperatursensoren gelesen',
'Analogsensoren gelesen',
'MQTT-Nachrichten gesendet',
'API-Aufrufe',
'Syslog-Mitteilungen'
],
NUM_DEVICES: '{num} Gerät{{e}}',
NUM_TEMP_SENSORS: '{num} Temperatursensor{{en}}',
NUM_ANALOG_SENSORS: '{num} Analogsensor{{en}}',
NUM_DAYS: '{num} Tag{{e}}',
NUM_SECONDS: '{num} Sekunde{{n}}',
NUM_HOURS: '{num} Stunde{{n}}',
NUM_MINUTES: '{num} Minute{{n}}',
APPLICATION_SETTINGS: 'Anwendungseinstellungen',
CUSTOMIZATION: 'Anpassungen',
APPLICATION_RESTARTING: 'EMS-ESP startet neu',
INTERFACE_BOARD_PROFILE: 'Interface Platinenprofil',
BOARD_PROFILE_TEXT: 'Wählen Sie ein vorkonfiguriertes Platinenprofil aus der Liste unten aus oder wählen Sie "Custom", um Ihre eigenen Hardwareeinstellungen zu konfigurieren',
BOARD_PROFILE: 'Platinenprofil',
CUSTOM: 'Custom',
GPIO_OF: '{0} GPIO',
BUTTON: 'Taste',
TEMPERATURE: 'Temperatur',
PHY_TYPE: 'Eth PHY Typ',
DISABLED: 'deaktiviert',
TX_MODE: 'Tx Modus',
HARDWARE: 'Hardware',
EMS_BUS: '{{BUS|EMS BUS}}',
GENERAL_OPTIONS: 'Allgemeine Optionen',
LANGUAGE_ENTITIES: 'Sprache (für Geräteentitäten)',
HIDE_LED: 'LED ausblenden',
ENABLE_TELNET: 'Aktiviere Telnet Konsole',
ENABLE_ANALOG: 'Aktiviere Analogsensoren',
CONVERT_FAHRENHEIT: 'Konvertiere Temperaturwerte in Fahrenheit',
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: 'Boiler Start mit Heizung ausgeschaltet',
ENABLE_SHOWER_TIMER: 'Duschtimer aktivieren',
ENABLE_SHOWER_ALERT: 'Duschalarm aktivieren',
TRIGGER_TIME: 'Auslösezeit',
COLD_SHOT_DURATION: 'Kaltschussdauer',
FORMATTING_OPTIONS: 'Formatierungsoptionen',
BOOLEAN_FORMAT_DASHBOARD: 'Boolsches Format für Web',
BOOLEAN_FORMAT_API: 'Boolesches Format API/MQTT',
ENUM_FORMAT: 'Enum Format API/MQTT',
INDEX: 'Index',
ENABLE_PARASITE: 'Parasitäre Stomversorgung',
LOGGING: 'Protokollierung',
LOG_HEX: 'EMS-Telegramme hexadezimal protokollieren',
ENABLE_SYSLOG: 'Syslog aktivieren',
LOG_LEVEL: 'Log Level',
MARK_INTERVAL: 'Intervallmarke',
SECONDS: 'Sekunden',
MINUTES: 'Minuten',
HOURS: 'Stunden',
RESTART: 'Neu starten',
RESTART_TEXT: 'EMS-ESP muss neu gestartet werden, um geänderte Systemeinstellungen zu übernehmen',
RESTART_CONFIRM: 'EMS-ESP wirklich neu starten?',
COMMAND: 'Befehl',
CUSTOMIZATIONS_RESTART: 'Alle Anpassungen wurden entfernt. Neustart...',
CUSTOMIZATIONS_FULL: 'Ausgewählte Entitäten haben das Limit überschritten. Bitte stapelweise speichern',
CUSTOMIZATIONS_SAVED: 'Anpassungen gespeichert',
CUSTOMIZATIONS_HELP_1: 'Wählen Sie ein Gerät aus und passen Sie die Entitäten mithilfe der Optionen an',
CUSTOMIZATIONS_HELP_2: 'als Favorit markieren',
CUSTOMIZATIONS_HELP_3: 'Schreibaktion deaktivieren',
CUSTOMIZATIONS_HELP_4: 'von MQTT und API ausschließen',
CUSTOMIZATIONS_HELP_5: 'Aus dem Kontrollzentrum ausblenden',
CUSTOMIZATIONS_HELP_6: 'Aus dem Speicher löschen',
SELECT_DEVICE: 'Wählen Sie ein Gerät aus',
SET_ALL: 'setzen Sie alle',
OPTIONS: 'Optionen',
NAME: 'Name',
CUSTOMIZATIONS_RESET: 'Möchten Sie wirklich alle Anpassungen entfernen, einschließlich der benutzerdefinierten Einstellungen der Temperatur- und Analogsensoren?',
DEVICE_ENTITIES: 'Geräteentitäten',
USER_CUSTOMIZATION: 'Benutzeranpassung',
SUPPORT_INFORMATION: 'Unterstützende Informationen',
CLICK_HERE: 'Hier klicken',
HELP_INFORMATION_1: 'EMS-ESP Konfigurationsanweisungen und mehr finden Sie im Online-Wiki',
HELP_INFORMATION_2: 'Für einen Live-Community-Chat besuchen Sie unseren Discord-Server',
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_OF: '{0} Hochladen',
UPLOAD: 'Hochladen',
DOWNLOAD: 'Herunterladen',
ABORTED: 'abgebrochen',
FAILED: 'gescheitert',
SUCCESSFUL: 'erfolgreich',
SYSTEM: 'System',
LOG_OF: '{0} Log',
STATUS_OF: '{0} Status',
UPLOAD_DOWNLOAD: 'Hoch-/Herunterladen',
SYSTEM_VERSION_RUNNING: 'Sie verwenden die Version',
SYSTEM_APPLY_FIRMWARE: 'um die neue Firmware anzuwenden',
CLOSE: 'Schließen',
USE: 'Verwenden Sie',
FACTORY_RESET: 'Werkseinstellung',
SYSTEM_FACTORY_TEXT: 'EMS-ESP wurde auf Werkseinstellung gesetzt und startet als Zugangspunkt neu',
SYSTEM_FACTORY_TEXT_DIALOG: 'Sind Sie sicher alle Einstellungen auf Werkseinstellung zu setzen?',
VERSION_CHECK: 'Versionsprüfung',
THE_LATEST: 'Die neueste',
OFFICIAL: 'offizielle',
DEVELOPMENT: 'Entwicklungs',
VERSION_IS: 'Version 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)',
FILESYSTEM: 'Dateisystem (Genutzt / Frei)',
BUFFER_SIZE: 'max. Puffergröße',
COMPACT: 'Kompakte Darstellung',
ENABLE_OTA: 'OTA Updates verwenden',
DOWNLOAD_CUSTOMIZATION_TEXT: 'Herunterladen der individuellen Entitätsanpassungen',
DOWNLOAD_SETTINGS_TEXT: 'Herunterladen der Anwendungseinstellungen. Vorsicht beim Teilen der Einstellungen, da sie Passwörter und andere sensitive Einstellungen enthalten',
UPLOAD_TEXT: 'Hochladen von neuer Firmware (.bin), Geräte- oder Entitätseinstellungen (.json), zur optionalen Validitätsprüfung zuerst die (.md5) Datei hochladen',
UPLOADING: 'Hochladen',
UPLOAD_DROP_TEXT: 'Klicken Sie hier, oder ziehen eine Datei hierher',
ERROR: 'Unerwarteter Fehler, bitter versuchen Sie es erneut',
TIME_SET: 'Zeit gesetzt',
MANAGE_USERS: 'Nutzerverwaltung',
IS_ADMIN: 'ist Admin',
USER_WARNING: 'Sie müssen mindestens einen Admin-Nutzer konfigurieren',
ADD: 'Hinzufügen',
ACCESS_TOKEN_FOR: 'Zugangs-Token für',
ACCESS_TOKEN_TEXT: 'Dieses Token ist für REST API Aufrufe bestimmt, die eine Authentifizierung benötigen. Es kann entweder als Bearer Token im `Authorization-Header` oder in der Access_Token URL verwendet werden.',
GENERATING_TOKEN: 'Erzeuge Token',
USER: 'Nutzer',
MODIFY: 'Ändern',
SU_TEXT: 'Das su (super user) Passwort wird zum Signieren der Authentifikations-Tokens verwendet und ermöglicht Admin-Berechtigung in der Konsole.',
NOT_ENABLED: 'Nicht aktiviert',
ERRORS_OF: '{0} Fehler',
DISCONNECT_REASON: 'Grund der Verbindungsunterbrechung',
ENABLE_MQTT: 'MQTT aktivieren',
BROKER: 'Broker',
CLIENT: 'Client',
BASE_TOPIC: 'Base',
OPTIONAL: 'Optional',
FORMATTING: 'Formattierung',
MQTT_FORMAT: 'Topic/Payload Format',
MQTT_NEST_1: 'Eingebettet in einem Gesamttopic',
MQTT_NEST_2: 'Als einzelne Topics',
MQTT_RESPONSE: 'Veröffentliche die Kommandoantwort als `response` Topic',
MQTT_PUBLISH_TEXT_1: 'Veröffentliche einzelne Werte bei Veränderung als eigene Topics',
MQTT_PUBLISH_TEXT_2: 'Veröffentliche als Kommando-Topic (ioBroker)',
MQTT_PUBLISH_TEXT_3: 'Aktiviere `MQTT Discovery` (Home Assistant, Domoticz)',
MQTT_PUBLISH_TEXT_4: 'Prefix für die `Discovery`-Topics',
MQTT_PUBLISH_INTERVALS: 'Veröffentlichungs-Intervalle',
MQTT_INT_BOILER: 'Boiler und Wärmepumpen',
MQTT_INT_THERMOSTATS: 'Thermostate',
MQTT_INT_SOLAR: 'Solarmodule',
MQTT_INT_MIXER: 'Mischermodule',
MQTT_INT_HEARTBEAT: 'Heartbeat',
MQTT_QUEUE: 'MQTT Queue',
DEFAULT: 'Standard',
MQTT_ENTITY_FORMAT: 'Entitäts-ID Format',
MQTT_ENTITY_FORMAT_0: 'Einzelinstanz, Langname (v3.4)',
MQTT_ENTITY_FORMAT_1: 'Einzelinstanz, MQTT-Namen',
MQTT_ENTITY_FORMAT_2: 'Mehrfachinstanzen, MQTT-Namen',
MQTT_CLEAN_SESSION: 'Setze `Clean Session`',
MQTT_RETAIN_FLAG: 'Setze `Retain flag` immer',
INACTIVE: 'Inaktiv',
ACTIVE: 'Aktiv',
UNKNOWN: 'Unbekannt',
SET_TIME: 'Zeiteinstellung',
SET_TIME_TEXT: 'Geben Sie das lokale Datum und die Zeit ein',
LOCAL_TIME: 'Lokalzeit',
UTC_TIME: 'UTC Zeit',
ENABLE_NTP: 'Aktiviere NTP',
NTP_SERVER: 'NTP Server',
TIME_ZONE: 'Zeitzone',
ACCESS_POINT: 'Zugangspunkt',
AP_PROVIDE: 'Aktiviere Zugangspunkt',
AP_PROVIDE_TEXT_1: 'Immer',
AP_PROVIDE_TEXT_2: 'Wenn WiFi nicht verbunden',
AP_PROVIDE_TEXT_3: 'Niemals',
AP_PREFERRED_CHANNEL: 'Bevorzugter Kanal',
AP_HIDE_SSID: 'Verstecke SSID',
AP_CLIENTS: 'AP-Klienten',
AP_MAX_CLIENTS: 'Max Anzahl AP-Klienten',
AP_LOCAL_IP: 'Lokale IP',
NETWORK_SCAN: 'Suche nach WiFi Netzwerken',
IDLE: 'Leerlauf',
LOST: 'Verloren',
SCANNING: 'Suche',
SCAN_AGAIN: 'Erneute Suche',
NETWORK_SCANNER: 'Netzwerk Suche',
NETWORK_NO_WIFI: 'Keine WiFi Netzwerke gefunden',
NETWORK_BLANK_SSID: 'Freilassen um WiFi zu deaktivieren',
TX_POWER: 'Tx Leistung',
HOSTNAME: 'Hostname',
NETWORK_DISABLE_SLEEP: 'Deaktiviere WiFi Schlafmodus',
NETWORK_LOW_BAND: 'Verwende niedrige WiFi Bandbreite',
NETWORK_USE_DNS: 'Aktiviere mDNS Service',
NETWORK_ENABLE_CORS: 'Aktiviere CORS',
NETWORK_CORS_ORIGIN: 'CORS origin',
NETWORK_ENABLE_IPV6: 'Aktiviere IPv6 Unterstützung',
NETWORK_FIXED_IP: 'Feste IP Adresse',
NETWORK_GATEWAY: 'Gateway',
NETWORK_SUBNET: 'Subnetz Maske',
NETWORK_DNS: 'DNS Server',
ADDRESS_OF: '{0} Adresse',
ADMIN: 'Administrator',
GUEST: 'Gast',
NEW: 'Neuer',
NEW_NAME_OF: 'Ändere {0}',
ENTITY: 'Entität',
MIN: 'min',
MAX: 'max'
};
export default de;

View File

@@ -0,0 +1,310 @@
import type { Translation } from '../i18n-types';
/* prettier-ignore */
/* eslint-disable */
const en: Translation = {
LANGUAGE: 'Language',
RETRY: 'Retry',
LOADING: 'Loading',
IS_REQUIRED: '{0} is required',
SIGN_IN: 'Sign In',
SIGN_OUT: 'Sign Out',
USERNAME: 'Username',
PASSWORD: 'Password',
SU_PASSWORD: 'su Password',
DASHBOARD: 'Dashboard',
SETTINGS_OF: '{0} Settings',
SAVED: 'saved',
HELP_OF: '{0} Help',
LOGGED_IN: 'Logged in as {name}',
PLEASE_SIGNIN: 'Please sign in to continue',
UPLOAD_SUCCESSFUL: 'Upload finished',
DOWNLOAD_SUCCESSFUL: 'Download finished',
INVALID_LOGIN: 'Invalid login details',
NETWORK: 'Network',
SECURITY: 'Security',
ONOFF_CAP: 'ON/OFF',
ONOFF: 'on/off',
TYPE: 'Type',
DESCRIPTION: 'Description',
ENTITIES: 'Entities',
REFRESH: 'Refresh',
EXPORT: 'Export',
DEVICE_DETAILS: 'Device Details',
ID_OF: '{0} ID',
DEVICE: 'Device',
PRODUCT: 'Product',
VERSION: 'Version',
BRAND: 'Brand',
ENTITY_NAME: 'Entity Name',
VALUE: '{{Value|value}}',
SHOW_FAV: 'only show favorites',
DEVICE_SENSOR_DATA: 'Device and Sensor Data',
DEVICES_SENSORS: 'Devices & Sensors',
ATTACHED_SENSORS: 'Attached EMS-ESP Sensors',
RUN_COMMAND: 'Call Command',
CHANGE_VALUE: 'Change Value',
CANCEL: 'Cancel',
RESET: 'Reset',
SEND: 'Send',
SAVE: 'Save',
REMOVE: 'Remove',
PROBLEM_UPDATING: 'Problem updating',
PROBLEM_LOADING: 'Problem loading',
ACCESS_DENIED: 'Access Denied',
ANALOG_SENSOR: 'Analog Sensor',
ANALOG_SENSORS: 'Analog Sensors',
UPDATED_OF: '{0} Updated',
UPDATE_OF: '{0} Update',
REMOVED_OF: '{0} Removed',
DELETION_OF: '{0} Deletion',
OFFSET: 'Offset',
FACTOR: 'Factor',
FREQ: 'Frequency',
DUTY_CYCLE: 'Duty Cycle',
UNIT: 'UoM',
STARTVALUE: 'Start value',
WARN_GPIO: 'Warning: be careful when assigning a GPIO!',
EDIT: 'Edit',
SENSOR: 'Sensor',
TEMP_SENSOR: 'Temperature Sensor',
TEMP_SENSORS: 'Temperature Sensors',
WRITE_CMD_SENT: 'Write command has been sent',
WRITE_CMD_FAILED: 'Write command failed',
EMS_BUS_WARNING: 'EMS bus disconnected. If this warning still persists after a few seconds please check settings and board profile',
EMS_BUS_SCANNING: 'Scanning for EMS devices...',
CONNECTED: 'Connected',
TX_ISSUES: 'Tx issues - try a different Tx Mode',
DISCONNECTED: 'Disconnected',
EMS_SCAN: 'Are you sure you want to initiate a full device scan of the EMS bus?',
EMS_BUS_STATUS: 'EMS Bus Status',
ACTIVE_DEVICES: 'Active Devices & Sensors',
EMS_DEVICE: 'EMS Device',
SUCCESS: 'SUCCESS',
FAIL: 'FAIL',
QUALITY: 'QUALITY',
SCAN_DEVICES: 'Scan for new devices',
EMS_BUS_STATUS_TITLE: 'EMS Bus & Activity Status',
SCAN: 'Scan',
STATUS_NAMES: [
'EMS Telegrams Received (Rx)',
'EMS Reads (Tx)',
'EMS Writes (Tx)',
'Temperature Sensor Reads',
'Analog Sensor Reads',
'MQTT Publishes',
'API Calls',
'Syslog Messages'
],
NUM_DEVICES: '{num} Device{{s}}',
NUM_TEMP_SENSORS: '{num} Temperature Sensor{{s}}',
NUM_ANALOG_SENSORS: '{num} Analog Sensor{{s}}',
NUM_DAYS: '{num} day{{s}}',
NUM_SECONDS: '{num} second{{s}}',
NUM_HOURS: '{num} hour{{s}}',
NUM_MINUTES: '{num} minute{{s}}',
APPLICATION_SETTINGS: 'Application Settings',
CUSTOMIZATION: 'Customization',
APPLICATION_RESTARTING: 'EMS-ESP is restarting',
INTERFACE_BOARD_PROFILE: 'Interface Board Profile',
BOARD_PROFILE_TEXT: 'Select a pre-configured interface board profile from the list below or choose Custom to configure your own hardware settings',
BOARD_PROFILE: 'Board Profile',
CUSTOM: 'Custom',
GPIO_OF: '{0} GPIO',
BUTTON: 'Button',
TEMPERATURE: 'Temperature',
PHY_TYPE: 'Eth PHY Type',
DISABLED: 'disabled',
TX_MODE: 'Tx Mode',
HARDWARE: 'Hardware',
EMS_BUS: '{{BUS|EMS BUS}}',
GENERAL_OPTIONS: 'General Options',
LANGUAGE_ENTITIES: 'Language (for device entities)',
HIDE_LED: 'Hide LED',
ENABLE_TELNET: 'Enable Telnet Console',
ENABLE_ANALOG: 'Enable Analog Sensors',
CONVERT_FAHRENHEIT: 'Convert temperature values to Fahrenheit',
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',
COLD_SHOT_DURATION: 'Cold Shot Duration',
FORMATTING_OPTIONS: 'Formatting Options',
BOOLEAN_FORMAT_DASHBOARD: 'Boolean Format Dashboard',
BOOLEAN_FORMAT_API: 'Boolean Format API/MQTT',
ENUM_FORMAT: 'Enum Format API/MQTT',
INDEX: 'Index',
ENABLE_PARASITE: 'Enable parasite power',
LOGGING: 'Logging',
LOG_HEX: 'Log EMS telegrams in hexadecimal',
ENABLE_SYSLOG: 'Enable Syslog',
LOG_LEVEL: 'Log Level',
MARK_INTERVAL: 'Mark Interval',
SECONDS: 'seconds',
MINUTES: 'minutes',
HOURS: 'hours',
RESTART: 'Restart',
RESTART_TEXT: 'EMS-ESP needs to be restarted to apply changed system settings',
RESTART_CONFIRM: 'Are you sure you want to restart EMS-ESP?',
COMMAND: 'Command',
CUSTOMIZATIONS_RESTART: 'All customizations have been removed. Restarting...',
CUSTOMIZATIONS_FULL: 'Selected entities exceeded limit. Please save in batches',
CUSTOMIZATIONS_SAVED: 'Customizations saved',
CUSTOMIZATIONS_HELP_1: 'Select a device and customize the entities options or click to rename',
CUSTOMIZATIONS_HELP_2: 'mark as favorite',
CUSTOMIZATIONS_HELP_3: 'disable write action',
CUSTOMIZATIONS_HELP_4: 'exclude from MQTT and API',
CUSTOMIZATIONS_HELP_5: 'hide from Dashboard',
CUSTOMIZATIONS_HELP_6: 'remove from memory',
SELECT_DEVICE: 'Select a device',
SET_ALL: 'set all',
OPTIONS: 'Options',
NAME: 'Name',
CUSTOMIZATIONS_RESET: 'Are you sure you want remove all customizations including the custom settings of the Temperature and Analog sensors?',
DEVICE_ENTITIES: 'Device Entities',
USER_CUSTOMIZATION: 'User Customization',
SUPPORT_INFORMATION: 'Support Information',
CLICK_HERE: 'Click Here',
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_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_OF: '{0} Upload',
UPLOAD: 'Upload',
DOWNLOAD: 'Download',
ABORTED: 'aborted',
FAILED: 'failed',
SUCCESSFUL: 'successful',
SYSTEM: 'System',
LOG_OF: '{0} Log',
STATUS_OF: '{0} Status',
UPLOAD_DOWNLOAD: 'Upload/Download',
SYSTEM_VERSION_RUNNING: 'You are currently running version',
SYSTEM_APPLY_FIRMWARE: 'to apply the new firmware',
CLOSE: 'Close',
USE: 'Use',
FACTORY_RESET: 'Factory Reset',
SYSTEM_FACTORY_TEXT: 'Device has been factory reset and will now restart',
SYSTEM_FACTORY_TEXT_DIALOG: 'Are you sure you want to reset the device to its factory defaults?',
VERSION_CHECK: 'Version Check',
THE_LATEST: 'The latest',
OFFICIAL: 'official',
DEVELOPMENT: 'development',
VERSION_IS: 'version is',
RELEASE_NOTES: 'release notes',
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)',
FILESYSTEM: 'File System (Used / Free)',
BUFFER_SIZE: 'Max Buffer Size',
COMPACT: 'Compact',
ENABLE_OTA: 'Enable OTA Updates',
DOWNLOAD_CUSTOMIZATION_TEXT: 'Download the entity customizations',
DOWNLOAD_SETTINGS_TEXT: 'Download the application settings. Be careful when sharing your settings as this file contains passwords and other sensitive system information',
UPLOAD_TEXT: 'Upload a new firmware (.bin) file, settings or customizations (.json) file below, for optional validation upload (.md5) first',
UPLOADING: 'Uploading',
UPLOAD_DROP_TEXT: 'Drop file or click here',
ERROR: 'Unexpected Error, please try again',
TIME_SET: 'Time set',
MANAGE_USERS: 'Manage Users',
IS_ADMIN: 'is Admin',
USER_WARNING: 'You must have at least one admin user configured',
ADD: 'Add',
ACCESS_TOKEN_FOR: 'Access Token for',
ACCESS_TOKEN_TEXT: 'The token below is used with REST API calls that require authorization. It can be passed either as a Bearer token in the Authorization header or in the access_token URL query parameter.',
GENERATING_TOKEN: 'Generating token',
USER: 'User',
MODIFY: 'Modify',
SU_TEXT: 'The su (super user) password is used to sign authentication tokens and also enable admin privileges within the Console.',
NOT_ENABLED: 'Not enabled',
ERRORS_OF: '{0} Errors',
DISCONNECT_REASON: 'Disconnect Reason',
ENABLE_MQTT: 'Enable MQTT',
BROKER: 'Broker',
CLIENT: 'Client',
BASE_TOPIC: 'Base',
OPTIONAL: 'Optional',
FORMATTING: 'Formatting',
MQTT_FORMAT: 'Topic/Payload Format',
MQTT_NEST_1: 'Nested in a single topic',
MQTT_NEST_2: 'As individual topics',
MQTT_RESPONSE: 'Publish command output to a `response` topic',
MQTT_PUBLISH_TEXT_1: 'Publish single value topics on change',
MQTT_PUBLISH_TEXT_2: 'Publish to command topics (ioBroker)',
MQTT_PUBLISH_TEXT_3: 'Enable MQTT Discovery (Home Assistant, Domoticz)',
MQTT_PUBLISH_TEXT_4: 'Prefix for the Discovery topics',
MQTT_PUBLISH_INTERVALS: 'Publish Intervals',
MQTT_INT_BOILER: 'Boilers and Heat Pumps',
MQTT_INT_THERMOSTATS: 'Thermostats',
MQTT_INT_SOLAR: 'Solar Modules',
MQTT_INT_MIXER: 'Mixer Modules',
MQTT_INT_HEARTBEAT: 'Heartbeat',
MQTT_QUEUE: 'MQTT Queue',
DEFAULT: 'Default',
MQTT_ENTITY_FORMAT: 'Entity ID format',
MQTT_ENTITY_FORMAT_0: 'Single instance, long name (v3.4)',
MQTT_ENTITY_FORMAT_1: 'Single instance, short name',
MQTT_ENTITY_FORMAT_2: 'Multiple instances, short name',
MQTT_CLEAN_SESSION: 'Set Clean Session',
MQTT_RETAIN_FLAG: 'Always set Retain flag',
INACTIVE: 'Inactive',
ACTIVE: 'Active',
UNKNOWN: 'Unknown',
SET_TIME: 'Set Time',
SET_TIME_TEXT: 'Enter local date and time below to set the time',
LOCAL_TIME: 'Local Time',
UTC_TIME: 'UTC Time',
ENABLE_NTP: 'Enable NTP',
NTP_SERVER: 'NTP Server',
TIME_ZONE: 'Time Zone',
ACCESS_POINT: 'Access Point',
AP_PROVIDE: 'Enable Access Point',
AP_PROVIDE_TEXT_1: 'always',
AP_PROVIDE_TEXT_2: 'when WiFi is disconnected',
AP_PROVIDE_TEXT_3: 'never',
AP_PREFERRED_CHANNEL: 'Preferred Channel',
AP_HIDE_SSID: 'Hide SSID',
AP_CLIENTS: 'AP Clients',
AP_MAX_CLIENTS: 'Max Clients',
AP_LOCAL_IP: 'Local IP',
NETWORK_SCAN: 'Scan WiFi Networks',
IDLE: 'Idle',
LOST: 'Lost',
SCANNING: 'Scanning',
SCAN_AGAIN: 'Scan again',
NETWORK_SCANNER: 'Network Scanner',
NETWORK_NO_WIFI: 'No WiFi networks found',
NETWORK_BLANK_SSID: 'leave blank to disable WiFi',
TX_POWER: 'Tx Power',
HOSTNAME: 'Hostname',
NETWORK_DISABLE_SLEEP: 'Disable WiFi Sleep Mode',
NETWORK_LOW_BAND: 'Use Lower WiFi Bandwidth',
NETWORK_USE_DNS: 'Enable mDNS Service',
NETWORK_ENABLE_CORS: 'Enable CORS',
NETWORK_CORS_ORIGIN: 'CORS origin',
NETWORK_ENABLE_IPV6: 'Enable IPv6 support',
NETWORK_FIXED_IP: 'Use Fixed IP address',
NETWORK_GATEWAY: 'Gateway',
NETWORK_SUBNET: 'Subnet Mask',
NETWORK_DNS: 'DNS Servers',
ADDRESS_OF: '{0} Address',
ADMIN: 'Admin',
GUEST: 'Guest',
NEW: 'New',
NEW_NAME_OF: 'New {0} name',
ENTITY: 'entity',
MIN: 'min',
MAX: 'max'
};
export default en;

View File

@@ -0,0 +1,10 @@
import type { FormattersInitializer } from 'typesafe-i18n';
import type { Locales, Formatters } from './i18n-types';
export const initFormatters: FormattersInitializer<Locales, Formatters> = (locale: Locales) => {
const formatters: Formatters = {
// add your formatter functions here
};
return formatters;
};

View File

@@ -0,0 +1,310 @@
import type { Translation } from '../i18n-types';
/* prettier-ignore */
/* eslint-disable */
const fr: Translation = {
LANGUAGE: 'Langue',
RETRY: 'Réessayer',
LOADING: 'Chargement',
IS_REQUIRED: '{0} est requis',
SIGN_IN: 'Se connecter',
SIGN_OUT: 'Se déconnecter',
USERNAME: 'Nom d\'utilisateur',
PASSWORD: 'Mot de passe',
SU_PASSWORD: 'Mot de passe su',
DASHBOARD: 'Tableau de bord',
SETTINGS_OF: 'Paramètres {0}',
SAVED: 'sauvegardé',
HELP_OF: 'Aide {0}',
LOGGED_IN: 'Connecté en tant que {name}',
PLEASE_SIGNIN: 'Veuillez vous connecter pour continuer',
UPLOAD_SUCCESSFUL: 'Upload terminée',
DOWNLOAD_SUCCESSFUL: 'Téléchargement terminé',
INVALID_LOGIN: 'Informations de connexion invalides',
NETWORK: 'Réseau',
SECURITY: 'Sécurité',
ONOFF_CAP: 'ON/OFF',
ONOFF: 'on/off',
TYPE: 'Type',
DESCRIPTION: 'Description',
ENTITIES: 'Entités',
REFRESH: 'Rafraîchir',
EXPORT: 'Exporter',
DEVICE_DETAILS: 'Détails de l\'appareil',
ID_OF: 'ID {0}',
DEVICE: 'Appareil',
PRODUCT: 'Produit',
VERSION: 'Version',
BRAND: 'Marque',
ENTITY_NAME: 'Nom de l\'entité',
VALUE: 'Valeur',
SHOW_FAV: 'ne montrer que les favoris',
DEVICE_SENSOR_DATA: 'Données des appareils et capteurs',
DEVICES_SENSORS: 'Appareils et capteurs',
ATTACHED_SENSORS: 'Capteurs EMS-ESP connectés',
RUN_COMMAND: 'Lancer une commande',
CHANGE_VALUE: 'Changer la valeur',
CANCEL: 'Annuler',
RESET: 'Réinitialiser',
SEND: 'Envoyer',
SAVE: 'Sauvegarder',
REMOVE: 'Enlever',
PROBLEM_UPDATING: 'Problème lors de la mise à jour',
PROBLEM_LOADING: 'Problème lors du chargement',
ACCESS_DENIED: 'Accès refusé',
ANALOG_SENSOR: 'Capteur analogique',
ANALOG_SENSORS: 'Capteurs analogiques',
UPDATED_OF: '{0} mis à jour',
UPDATE_OF: 'Mise à jour de {0}',
REMOVED_OF: '{0} enlevé',
DELETION_OF: '{0} supprimé',
OFFSET: 'Offset',
FACTOR: 'Facteur',
FREQ: 'Fréquence',
DUTY_CYCLE: 'Cycle de fonctionnement',
UNIT: 'Unité',
STARTVALUE: 'Valeur de départ',
WARN_GPIO: 'Attention: soyez vigilant en choisissant un GPIO!',
EDIT: 'Éditer',
SENSOR: 'Capteur',
TEMP_SENSOR: 'Capteur de température',
TEMP_SENSORS: 'Capteurs de température',
WRITE_CMD_SENT: 'Envoyer la commande sent', // TODO translate
WRITE_CMD_FAILED: 'Envoyer la commande failed', // TODO translate
EMS_BUS_WARNING: 'Bus EMS déconnecté. Si ce message persiste après quelques secondes, vérifiez les paramètres et la configuration de la carte.',
EMS_BUS_SCANNING: 'Scan des appareils EMS...',
CONNECTED: 'Connecté',
TX_ISSUES: 'Problèmes de transmission (Tx) - Essayez un autre mode Tx',
DISCONNECTED: 'Déconnecté',
EMS_SCAN: 'Etes-vous sûr de vouloir lancer un scan complet du bus EMS ?',
EMS_BUS_STATUS: 'Statut du bus EMS',
ACTIVE_DEVICES: 'Appareils et capteurs actifs',
EMS_DEVICE: 'Appareils EMS',
SUCCESS: 'SUCCÈS',
FAIL: 'ÉCHEC',
QUALITY: 'QUALITÉ',
SCAN_DEVICES: 'Rechercher de nouveaux appareils',
EMS_BUS_STATUS_TITLE: 'Statut du bus et de l\'activité EMS',
SCAN: 'Scan',
STATUS_NAMES: [
'Télégrammes EMS reçus (Rx)',
'Lectures EMS (Tx)',
'Écritures EMS (Tx)',
'Lectures capteurs de température',
'Lectures capteurs analogiques',
'Publications MQTT',
'Appels à l\'API',
'Messages Syslog'
],
NUM_DEVICES: '{num} Appareil{{s}}',
NUM_TEMP_SENSORS: '{num} Capteur{{s}} de température',
NUM_ANALOG_SENSORS: '{num} Capteur{{s}} analogique{{s}}',
NUM_DAYS: '{num} jour{{s}}',
NUM_SECONDS: '{num} seconde{{s}}',
NUM_HOURS: '{num} heure{{s}}',
NUM_MINUTES: '{num} minute{{s}}',
APPLICATION_SETTINGS: 'Paramètres de l\'application',
CUSTOMIZATION: 'Personnalisation',
APPLICATION_RESTARTING: 'EMS-ESP redémarre',
INTERFACE_BOARD_PROFILE: 'Profile de carte d\'interface',
BOARD_PROFILE_TEXT: 'Sélectionnez un profil de carte d\'interface préconfiguré dans la liste ci-dessous ou choisissez Personnalisé pour configurer vos propres paramètres matériels',
BOARD_PROFILE: 'Profil de carte',
CUSTOM: 'Personnalisé',
GPIO_OF: 'GPIO {0}',
BUTTON: 'Bouton',
TEMPERATURE: 'Température',
PHY_TYPE: 'Eth PHY Type',
DISABLED: 'désactivé',
TX_MODE: 'Tx Mode',
HARDWARE: 'Hardware',
EMS_BUS: '{{BUS|EMS BUS}}',
GENERAL_OPTIONS: 'Options générales',
LANGUAGE_ENTITIES: 'Langue (pour les entités du matériel)',
HIDE_LED: 'Cacher la LED',
ENABLE_TELNET: 'Activer la console Telnet',
ENABLE_ANALOG: 'Activer les capteurs analogiques',
CONVERT_FAHRENHEIT: 'Convertir les températures en Fahrenheit',
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',
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',
COLD_SHOT_DURATION: 'Durée du coup d\'eau froide',
FORMATTING_OPTIONS: 'Options de mise en forme',
BOOLEAN_FORMAT_DASHBOARD: 'Tableau de bord du format booléen',
BOOLEAN_FORMAT_API: 'Format booléen API/MQTT',
ENUM_FORMAT: 'Format enum API/MQTT',
INDEX: 'Index',
ENABLE_PARASITE: 'Activer la puissance parasite',
LOGGING: 'Journal',
LOG_HEX: 'Enregistrer les télégrammes EMS en hexadécimal',
ENABLE_SYSLOG: 'Activer les logs système',
LOG_LEVEL: 'Niveau de log',
MARK_INTERVAL: 'Intervalle de marquage',
SECONDS: 'secondes',
MINUTES: 'minutes',
HOURS: 'heures',
RESTART: 'Redémarrer',
RESTART_TEXT: 'EMS-ESP a besoin de redémarrer pour appliquer les changements de paramètres du système',
RESTART_CONFIRM: 'Etes-vous sûr de vouloir redémarrer EMS-ESP ?',
COMMAND: 'Commande',
CUSTOMIZATIONS_RESTART: 'Toutes les personnalisations ont été supprimées. Redémarrage...',
CUSTOMIZATIONS_FULL: 'Les entités sélectionnées ont dépassé la limite. Veuillez sauvegarder par lots',
CUSTOMIZATIONS_SAVED: 'Personnalisations enregistrées',
CUSTOMIZATIONS_HELP_1: 'Sélectionnez un appareil et personnalisez les options des entités ou cliquez pour renommer',
CUSTOMIZATIONS_HELP_2: 'marquer comme favori',
CUSTOMIZATIONS_HELP_3: 'désactiver l\'action d\'écriture',
CUSTOMIZATIONS_HELP_4: 'exclure de MQTT et de l\'API',
CUSTOMIZATIONS_HELP_5: 'cacher du Tableau de bord',
CUSTOMIZATIONS_HELP_6: 'remove from memory',
SELECT_DEVICE: 'Sélectionnez un appareil',
SET_ALL: 'tout régler',
OPTIONS: 'Options',
NAME: 'Nom',
CUSTOMIZATIONS_RESET: 'Êtes-vous sûr de vouloir supprimer toutes les personnalisations, y compris les paramètres personnalisés des capteurs de température et analogiques ?',
DEVICE_ENTITIES: 'Entités de l\'appareil',
USER_CUSTOMIZATION: 'Personnalisation de l\'utilisateur',
SUPPORT_INFORMATION: 'Information de support',
CLICK_HERE: 'Cliquez ici',
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_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_OF: 'Upload de {0}',
UPLOAD: 'Upload',
DOWNLOAD: 'Download',
ABORTED: 'annulé',
FAILED: 'échoué',
SUCCESSFUL: 'réussi',
SYSTEM: 'Système',
LOG_OF: '{0} Log',
STATUS_OF: 'Statut {0}',
UPLOAD_DOWNLOAD: 'Upload/Download',
SYSTEM_VERSION_RUNNING: 'Vous utilisez actuellement la version',
SYSTEM_APPLY_FIRMWARE: 'pour appliquer le nouveau firmware',
CLOSE: 'Fermer',
USE: 'Utiliser',
FACTORY_RESET: 'Réinitialisation',
SYSTEM_FACTORY_TEXT: 'L\'appareil a été réinitialisé et va maintenant redémarrer',
SYSTEM_FACTORY_TEXT_DIALOG: 'Êtes-vous sûr de vouloir réinitialiser l\'appareil à ses paramètres d\'usine ?',
VERSION_CHECK: 'Vérification de la version',
THE_LATEST: 'La dernière',
OFFICIAL: 'officielle',
DEVELOPMENT: 'développement',
VERSION_IS: 'version est',
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)',
FILESYSTEM: 'File System (Utilisée / Libre)',
BUFFER_SIZE: 'Max taille du buffer',
COMPACT: 'Compact',
ENABLE_OTA: 'Activer les updates OTA',
DOWNLOAD_CUSTOMIZATION_TEXT: 'Télécharger les personnalisations d\'entités',
DOWNLOAD_SETTINGS_TEXT: 'Téléchargez les paramètres de l\'application. Soyez prudent lorsque vous partagez vos paramètres car ce fichier contient des mots de passe et d\'autres informations système sensibles.',
UPLOAD_TEXT: 'Téléchargez un nouveau fichier de firmware (.bin), un fichier de paramètres ou de personnalisations (.json) ci-dessous, pour une validation optionnelle téléchargez d\'abord un fichier (.md5)',
UPLOADING: 'Téléchargement',
UPLOAD_DROP_TEXT: 'Déposer le fichier ou cliquer ici',
ERROR: 'Erreur inattendue, veuillez réessayer',
TIME_SET: 'Time set',
MANAGE_USERS: 'Gérer les utilisateurs',
IS_ADMIN: 'admin',
USER_WARNING: 'Vous devez avoir au moins un utilisateur admin configuré',
ADD: 'Ajouter',
ACCESS_TOKEN_FOR: 'Jeton d\'accès pour',
ACCESS_TOKEN_TEXT: 'Le jeton ci-dessous est utilisé avec les appels d\'API REST qui nécessitent une autorisation. Il peut être passé soit en tant que jeton Bearer dans l\'en-tête Authorization, soit dans le paramètre de requête URL access_token.',
GENERATING_TOKEN: 'Génération de jeton',
USER: 'Utilisateur',
MODIFY: 'Modifier',
SU_TEXT: 'Le mot de passe su (super utilisateur) est utilisé pour signer les jetons d\'authentification et activer les privilèges d\'administrateur dans la console.',
NOT_ENABLED: 'Non activé',
ERRORS_OF: 'Erreurs {0}',
DISCONNECT_REASON: 'Raison de la déconnexion',
ENABLE_MQTT: 'Activer le MQTT',
BROKER: 'Broker',
CLIENT: 'Client',
BASE_TOPIC: 'Base',
OPTIONAL: 'Optionnel',
FORMATTING: 'Mise en forme',
MQTT_FORMAT: 'Format du Topic/Payload',
MQTT_NEST_1: 'Englobé dans un topic unique',
MQTT_NEST_2: 'En tant que topics individuels',
MQTT_RESPONSE: 'Publier le résultat des commandes dans un topic `response`',
MQTT_PUBLISH_TEXT_1: 'Publier des topics à valeur unique sur changement',
MQTT_PUBLISH_TEXT_2: 'Publier vers des topics de commande (ioBroker)',
MQTT_PUBLISH_TEXT_3: 'Activer la découverte MQTT (Home Assistant, Domoticz)',
MQTT_PUBLISH_TEXT_4: 'Préfixe pour les topics découverte',
MQTT_PUBLISH_INTERVALS: 'Intervalles de publication',
MQTT_INT_BOILER: 'Chaudières et pompes à chaleur',
MQTT_INT_THERMOSTATS: 'Thermostats',
MQTT_INT_SOLAR: 'Modules solaires',
MQTT_INT_MIXER: 'Modules mélangeurs',
MQTT_INT_HEARTBEAT: 'Battements',
MQTT_QUEUE: 'Queue MQTT',
DEFAULT: 'Défaut',
MQTT_ENTITY_FORMAT: 'Entity ID format', // TODO translate
MQTT_ENTITY_FORMAT_0: 'Single instance, long name (v3.4)',// TODO translate
MQTT_ENTITY_FORMAT_1: 'Single instance, short name', // TODO translate
MQTT_ENTITY_FORMAT_2: 'Multiple instances, short name', // TODO translate
MQTT_CLEAN_SESSION: 'Flag Clean Session',
MQTT_RETAIN_FLAG: 'Toujours activer le Retain Flag',
INACTIVE: 'Inactif',
ACTIVE: 'Actif',
UNKNOWN: 'Inconnu',
SET_TIME: 'Définir l\'heure',
SET_TIME_TEXT: 'Entrer la date et l\'heure locale ci-dessous pour régler l\'heure',
LOCAL_TIME: 'Heure locale',
UTC_TIME: 'Heure UTC',
ENABLE_NTP: 'Activer le NTP',
NTP_SERVER: 'Serveur NTP',
TIME_ZONE: 'Fuseau horaire',
ACCESS_POINT: 'Point d\'accès',
AP_PROVIDE: 'Activer le Point d\'Accès',
AP_PROVIDE_TEXT_1: 'toujours',
AP_PROVIDE_TEXT_2: 'quand le WiFi est déconnecté',
AP_PROVIDE_TEXT_3: 'jamais',
AP_PREFERRED_CHANNEL: 'Canal préféré',
AP_HIDE_SSID: 'Cacher le SSID',
AP_CLIENTS: 'AP Clients',
AP_MAX_CLIENTS: 'Max Clients',
AP_LOCAL_IP: 'IP locale',
NETWORK_SCAN: 'Scanner les réseaux WiFi',
IDLE: 'Inactif',
LOST: 'Perdu',
SCANNING: 'Scan en cours',
SCAN_AGAIN: 'Rescanner',
NETWORK_SCANNER: 'Scan réseau',
NETWORK_NO_WIFI: 'Pas de réseau WiFi trouvé',
NETWORK_BLANK_SSID: 'laisser vide pour désactiver le WiFi',
TX_POWER: 'Puissance Tx',
HOSTNAME: 'Nom d\'hôte',
NETWORK_DISABLE_SLEEP: 'Désactiver le mode veille du WiFi',
NETWORK_LOW_BAND: 'Utiliser une bande passante WiFi plus faible',
NETWORK_USE_DNS: 'Activer le service mDNS',
NETWORK_ENABLE_CORS: 'Activer CORS',
NETWORK_CORS_ORIGIN: 'Origine CORS',
NETWORK_ENABLE_IPV6: 'Activer le support de l\'IPv6',
NETWORK_FIXED_IP: 'Utiliser une adresse IP fixe',
NETWORK_GATEWAY: 'Passerelle',
NETWORK_SUBNET: 'Masque de sous-réseau',
NETWORK_DNS: 'Serveurs DNS',
ADDRESS_OF: 'Adresse de {0}',
ADMIN: 'Admin',
GUEST: 'Invité',
NEW: 'Nouveau',
NEW_NAME_OF: 'Nouveau nom de {0}',
ENTITY: 'entité',
MIN: 'min',
MAX: 'max'
};
export default fr;

View File

@@ -0,0 +1,310 @@
import type { Translation } from '../i18n-types';
/* prettier-ignore */
/* eslint-disable */
const nl: Translation = {
LANGUAGE: 'Taal',
RETRY: 'Opnieuw proberen',
LOADING: 'Laden',
IS_REQUIRED: '{0} is verplicht',
SIGN_IN: 'Inloggen',
SIGN_OUT: 'Uitloggen',
USERNAME: 'Gebruikersnaam',
PASSWORD: 'Wachtwoord',
SU_PASSWORD: 'su Wachtwoord',
DASHBOARD: 'Dashboard',
SETTINGS_OF: '{0} Instellingen',
SAVED: 'opgeslagen',
HELP_OF: '{0} Help',
LOGGED_IN: 'Ingelogd als {name}',
PLEASE_SIGNIN: 'Log in om verder te gaan',
UPLOAD_SUCCESSFUL: 'Upload successvol',
DOWNLOAD_SUCCESSFUL: 'Download successvol',
INVALID_LOGIN: 'Logingegevens fout',
NETWORK: 'Netwerk',
SECURITY: 'Beveiliging',
ONOFF_CAP: 'AAN/UIT',
ONOFF: 'aan/uit',
TYPE: 'Type',
DESCRIPTION: 'Beschrijving',
ENTITIES: 'Entiteiten',
REFRESH: 'Ververs',
EXPORT: 'Export',
DEVICE_DETAILS: 'Device Gegevens',
ID_OF: '{0} ID',
DEVICE: 'Apparaat',
PRODUCT: 'Product',
VERSION: 'Versie',
BRAND: 'Merk',
ENTITY_NAME: 'Entiteit',
VALUE: '{{Waarde|waarde}}',
SHOW_FAV: 'alleen favorieten weergeven',
DEVICE_SENSOR_DATA: 'Apparaat en Sensor data',
DEVICES_SENSORS: 'Apparaten & Sensoren',
ATTACHED_SENSORS: 'Aangesloten EMS-ESP sensoren',
RUN_COMMAND: 'Call commando',
CHANGE_VALUE: 'Wijzig waarde',
CANCEL: 'Annuleren',
RESET: 'Reset',
SEND: 'Verzenden',
SAVE: 'Opslaan',
REMOVE: 'Verwijderen',
PROBLEM_UPDATING: 'Probleem met updaten',
PROBLEM_LOADING: 'Probleem met laden',
ACCESS_DENIED: 'Toegang geweigerd',
ANALOG_SENSOR: 'Analoge sensor',
ANALOG_SENSORS: 'Analoge Sensoren',
UPDATED_OF: '{0} Bijgewerkt',
UPDATE_OF: '{0} Bijwerken',
REMOVED_OF: '{0} Verwijderd',
DELETION_OF: '{0} Verwijder',
OFFSET: 'Offset',
FACTOR: 'Factor',
FREQ: 'Frequentie',
DUTY_CYCLE: 'Duty Cycle',
UNIT: 'UoM',
STARTVALUE: 'Startwaarde',
WARN_GPIO: 'Waarschuwing: let op met het koppelen van de juiste GPIO pin!',
EDIT: 'Wijzigen',
SENSOR: 'Sensor',
TEMP_SENSOR: 'Temperatuur sensor',
TEMP_SENSORS: 'Temperatuur Sensoren',
WRITE_CMD_SENT: 'Schrijf commando sent', // TODO translate
WRITE_CMD_FAILED: 'Schrijf commando failed', // TODO translate
EMS_BUS_WARNING: 'EMS bus niet gevonden. Als deze waarschuwing blijft staan na een paar seconden dan loop de instellingen na en in het bijzonder het apparaat type profiel na.',
EMS_BUS_SCANNING: 'Scannen naar EMS apparaten...',
CONNECTED: 'Verbonden',
TX_ISSUES: 'Tx bus probleem. Probeer een andere Tx verzendmodus',
DISCONNECTED: 'Niet verbonden',
EMS_SCAN: 'Weet je zeker dat je een volledige EMS bus scan uit wilt voeren?',
EMS_BUS_STATUS: 'EMS busstatus',
ACTIVE_DEVICES: 'Actieve Apparaten & Sensoren',
EMS_DEVICE: 'EMS Apparaat',
SUCCESS: 'SUCCESS',
FAIL: 'MISLUKT',
QUALITY: 'QUALITEIT',
SCAN_DEVICES: 'Scannen naar nieuwe apparaten',
EMS_BUS_STATUS_TITLE: 'EMS Bus & Activiteitenstatus',
SCAN: 'Scan',
STATUS_NAMES: [
'EMS Telegrammen ontvangen (Rx)',
'EMS Leesopdrachten (Tx)',
'EMS Schrijfopdrachten (Tx)',
'Temperatuursensoren uitgelezen',
'Analoge sensoren uitgelezen',
'MQTT publicaties',
'API calls',
'Syslog berichten'
],
NUM_DEVICES: '{num} Apparaat{{en}}',
NUM_TEMP_SENSORS: '{num} Temperatuursensor{{en}}',
NUM_ANALOG_SENSORS: '{num} Analoge sensor{{en}}',
NUM_DAYS: '{num} dag{{en}}',
NUM_SECONDS: '{num} second{{en}}',
NUM_HOURS: '{num} {{uur|uren}}',
NUM_MINUTES: '{num} {{minuut|minuten}}',
APPLICATION_SETTINGS: 'Applicatieinstellingen',
CUSTOMIZATION: 'Custom aanpassingen',
APPLICATION_RESTARTING: 'EMS-ESP herstarten',
INTERFACE_BOARD_PROFILE: 'Interface Apparaatprofiel',
BOARD_PROFILE_TEXT: 'Selecteer een vooraf ingesteld apparaat profiel uit de lijst of kies Eigen om zelf uw hardware te configureren',
BOARD_PROFILE: 'Apparaatprofiel',
CUSTOM: 'Custom',
GPIO_OF: '{0} GPIO',
BUTTON: 'Toets',
TEMPERATURE: 'Temperatuur',
PHY_TYPE: 'Eth PHY Type',
TX_MODE: 'Tx Mode',
HARDWARE: 'Hardware',
EMS_BUS: '{{BUS|EMS BUS}}',
DISABLED: 'Uitgeschakeld',
GENERAL_OPTIONS: 'Algemene Opties',
LANGUAGE_ENTITIES: 'Taal (voor apparaat entiteiten)',
HIDE_LED: 'Verberg LED',
ENABLE_TELNET: 'Activeer Telnet console',
ENABLE_ANALOG: 'Activeer analoge sensoren',
CONVERT_FAHRENHEIT: 'Converteer temperatuurwaarden naar Fahrenheit',
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',
ENABLE_SHOWER_TIMER: 'Activeer Douche Timer (tijdmeting)',
ENABLE_SHOWER_ALERT: 'Activeer Douchemelding',
TRIGGER_TIME: 'Trigger tijd',
COLD_SHOT_DURATION: 'Tijd Shot koud water',
FORMATTING_OPTIONS: 'Formatteringsopties',
BOOLEAN_FORMAT_DASHBOARD: 'Boolean formaat dashboard',
BOOLEAN_FORMAT_API: 'Boolean formaat API/MQTT',
ENUM_FORMAT: 'Enum formaat API/MQTT',
INDEX: 'Index',
ENABLE_PARASITE: 'Activeer Dallas parasitaire modus',
LOGGING: 'Logging',
LOG_HEX: 'Log EMS telegrammen in hexadecimaal',
ENABLE_SYSLOG: 'Activeer Syslog',
LOG_LEVEL: 'Log Level',
MARK_INTERVAL: 'Markeringsinterval',
SECONDS: 'seconden',
MINUTES: 'minuten',
HOURS: 'uren',
RESTART: 'Herstarten',
RESTART_TEXT: 'EMS-ESP dient opnieuw gestart te worden om de wijzingen toe te passen',
RESTART_CONFIRM: 'Weet je zeker dat je EMS-ESP wilt herstarten?',
COMMAND: 'Commando',
CUSTOMIZATIONS_RESTART: 'Alle custom profielen worden verwijderd. Herstarten...',
CUSTOMIZATIONS_FULL: 'Te veel entiteiten geselecteerd. Sla op in delen aub',
CUSTOMIZATIONS_SAVED: 'Custom aanpassingen opgeslagen',
CUSTOMIZATIONS_HELP_1: 'Selecteer een apparaat en pas de entiteiten aan door middel van de opties',
CUSTOMIZATIONS_HELP_2: 'Markeer as favoriet',
CUSTOMIZATIONS_HELP_3: 'Zet schrijfacties uit',
CUSTOMIZATIONS_HELP_4: 'Uitsluiten van MQTT en API',
CUSTOMIZATIONS_HELP_5: 'verberg van het Dashboard',
CUSTOMIZATIONS_HELP_6: 'remove from memory',
SELECT_DEVICE: 'Selecteer een apparaat',
SET_ALL: 'Alles aanzetten',
OPTIONS: 'Opties',
NAME: 'Naam',
CUSTOMIZATIONS_RESET: 'Weet je zeker dat je alle custom aanpassingen wilt verwijderen inclusief de custom instellingen voor analoge temperatuursensoren?',
DEVICE_ENTITIES: 'Apparaat Entiteiten',
USER_CUSTOMIZATION: 'Custom Instellingen',
SUPPORT_INFORMATION: 'Support Informatie',
CLICK_HERE: 'Klik Hier',
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_5: 'EMS-ESP is een gratis en open source project. Steun ons met een Star op Github!',
SUPPORT_INFO: 'Support Info',
UPLOAD_OF: '{0} Upload',
UPLOAD: 'Upload',
DOWNLOAD: 'Download',
ABORTED: 'afgebroken',
FAILED: 'mislukt',
SUCCESSFUL: 'successvol',
SYSTEM: 'Systeem',
LOG_OF: '{0} Log',
STATUS_OF: '{0} Status',
UPLOAD_DOWNLOAD: 'Upload/Download',
SYSTEM_VERSION_RUNNING: 'op dit moment draai je versie',
SYSTEM_APPLY_FIRMWARE: 'om de nieuwe firmware te activeren',
CLOSE: 'Sluiten',
USE: 'Gebruik',
FACTORY_RESET: 'Fabrieksinstellingen',
SYSTEM_FACTORY_TEXT: 'Gateway is gereset en start nu weer op met fabrieksinstellingen',
SYSTEM_FACTORY_TEXT_DIALOG: 'Weet je zeker dat je een reset naar fabrieksinstellingen uit wilt voeren?',
VERSION_CHECK: 'Versie Check',
THE_LATEST: 'De laatste',
OFFICIAL: 'official',
DEVELOPMENT: 'development',
VERSION_IS: 'versie is',
RELEASE_NOTES: 'release notes',
EMS_ESP_VER: 'EMS-ESP Version',
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)',
FILESYSTEM: 'File System (Used / Free)',
BUFFER_SIZE: 'Max Buffer Size',
COMPACT: 'Compact',
ENABLE_OTA: 'Acitveer OTA Updates',
DOWNLOAD_CUSTOMIZATION_TEXT: 'Download alle custom instellingen',
DOWNLOAD_SETTINGS_TEXT: 'Download de applicatie settings. Wees voorzichting met het delen van dit bestand want het bevat o.a. de wachtwoorden in plain text',
UPLOAD_TEXT: 'Upload een nieuwe firmware (.bin) file, instellingen of custom instellingen (.json) bestand hieronder',
UPLOADING: 'Uploading',
UPLOAD_DROP_TEXT: 'Sleep bestand hierheen of klik hier',
ERROR: 'Onverwachte fout, probeer opnieuw',
TIME_SET: 'Tijd ingesteld',
MANAGE_USERS: 'Beheer Gebruikers',
IS_ADMIN: 'is Admin',
USER_WARNING: 'U dient tenminste 1 admin gebruiker te configureren',
ADD: 'Toevoegen',
ACCESS_TOKEN_FOR: 'Access Token voor',
ACCESS_TOKEN_TEXT: 'Het token hieronder wordt gebruikt voor de REST API calls die authorisatie nodig hebben. Het kan zowel als Bearer token in de Authorization header of in acccess_token URL query parameter gebruikt worden',
GENERATING_TOKEN: 'Token aan het genereren',
USER: 'Gebruiker',
MODIFY: 'Aanpassen',
SU_TEXT: 'Het su (super user) wachtwoord wordt gebruikt om authorisatie tokens te signeren en ook om admin privileges te activeren in de console.',
NOT_ENABLED: 'Niet geactiveerd',
ERRORS_OF: '{0} Foutmeldingen',
DISCONNECT_REASON: 'Verbinding verbroken vanwege',
ENABLE_MQTT: 'Activeer MQTT',
BROKER: 'Broker',
CLIENT: 'Client',
BASE_TOPIC: 'Base',
OPTIONAL: 'Optioneel',
FORMATTING: 'Formatteren',
MQTT_FORMAT: 'Topic/Payload Formattering',
MQTT_NEST_1: 'Genest in 1 topic',
MQTT_NEST_2: 'Als individuele topics',
MQTT_RESPONSE: 'Publiceer commando output naar een `response` topic',
MQTT_PUBLISH_TEXT_1: 'Publiceer enkele waarde topics on change',
MQTT_PUBLISH_TEXT_2: 'Publiceer naar commando topics (ioBroker)',
MQTT_PUBLISH_TEXT_3: 'Activeer MQTT Discovery (Home Assistant, Domoticz)',
MQTT_PUBLISH_TEXT_4: 'Prefix voor de Discovery topics',
MQTT_PUBLISH_INTERVALS: 'Publicatie intervallen',
MQTT_INT_BOILER: 'CV ketels en warmtepompen',
MQTT_INT_THERMOSTATS: 'Thermostaten',
MQTT_INT_SOLAR: 'Solar Modules',
MQTT_INT_MIXER: 'Mixer Modules',
MQTT_INT_HEARTBEAT: 'Heartbeat',
MQTT_QUEUE: 'MQTT Queue',
DEFAULT: 'Default',
MQTT_ENTITY_FORMAT: 'Entity ID format', // TODO translate
MQTT_ENTITY_FORMAT_0: 'Single instance, long name (v3.4)', // TODO translate
MQTT_ENTITY_FORMAT_1: 'Single instance, short name', // TODO translate
MQTT_ENTITY_FORMAT_2: 'Multiple instances, short name', // TODO translate
MQTT_CLEAN_SESSION: 'Clean Session aan',
MQTT_RETAIN_FLAG: 'Retain flag aan',
INACTIVE: 'Inactief',
ACTIVE: 'Actief',
UNKNOWN: 'Onbekend',
SET_TIME: 'Tijd instellen',
SET_TIME_TEXT: 'Geef de locale datum en tijd in',
LOCAL_TIME: 'Locale Tijd',
UTC_TIME: 'UTC Tijd',
ENABLE_NTP: 'Activeer NTP',
NTP_SERVER: 'NTP Server',
TIME_ZONE: 'Tijdzone',
ACCESS_POINT: 'Access Point',
AP_PROVIDE: 'Activeer Access Point',
AP_PROVIDE_TEXT_1: 'altijd',
AP_PROVIDE_TEXT_2: 'als WiFi niet is verbonden',
AP_PROVIDE_TEXT_3: 'nooit',
AP_PREFERRED_CHANNEL: 'Voorkeurskanaal',
AP_HIDE_SSID: 'SSID verbergen',
AP_CLIENTS: 'AP Clients',
AP_MAX_CLIENTS: 'Max Clients',
AP_LOCAL_IP: 'Local IP',
NETWORK_SCAN: 'Scan WiFi Networken',
IDLE: 'Idle',
LOST: 'Verloren',
SCANNING: 'Scannen',
SCAN_AGAIN: 'Opnieuw scannen',
NETWORK_SCANNER: 'Netwerk Scanner',
NETWORK_NO_WIFI: 'Geen WiFi networken gevonden',
NETWORK_BLANK_SSID: 'laat leeg om WiFi uit te schakelen',
TX_POWER: 'Tx Vermogen',
HOSTNAME: 'Hostname',
NETWORK_DISABLE_SLEEP: 'WiFi Sleep Mode uitzetten',
NETWORK_LOW_BAND: 'Lagere WiFi bandbreedte gebruiken',
NETWORK_USE_DNS: 'Activeer mDNS Service',
NETWORK_ENABLE_CORS: 'Activeer CORS',
NETWORK_CORS_ORIGIN: 'CORS origin',
NETWORK_ENABLE_IPV6: 'Activeer IPv6 support',
NETWORK_FIXED_IP: 'Gebruik vast IP addres',
NETWORK_GATEWAY: 'Gateway',
NETWORK_SUBNET: 'Subnetmasker',
NETWORK_DNS: 'DNS Servers',
ADDRESS_OF: '{0} Address',
ADMIN: 'Admin',
GUEST: 'Gast',
NEW: 'Nieuwe',
NEW_NAME_OF: 'Hernoem {0}',
ENTITY: 'Entiteit',
MIN: 'min',
MAX: 'max'
};
export default nl;

View File

@@ -0,0 +1,310 @@
import type { Translation } from '../i18n-types';
/* prettier-ignore */
/* eslint-disable */
const no: Translation = {
LANGUAGE: 'Språk',
RETRY: 'Forsøk igjen',
LOADING: 'Laster',
IS_REQUIRED: '{0} er nødvendig',
SIGN_IN: 'Logg inn',
SIGN_OUT: 'Logg ut',
USERNAME: 'Brukernavn',
PASSWORD: 'Passord',
SU_PASSWORD: 'su Passord',
DASHBOARD: 'Dashboard',
SETTINGS_OF: '{0} Innstillinger',
SAVED: 'lagret',
HELP_OF: '{0} Hjelp',
LOGGED_IN: 'Logget in som {name}',
PLEASE_SIGNIN: 'Venligst logge inn for å fortsetta',
UPLOAD_SUCCESSFUL: 'Opplasting lykkes',
DOWNLOAD_SUCCESSFUL: 'Nedlasting lykkes',
INVALID_LOGIN: 'Ugyldig innlogging',
NETWORK: 'Nettverk',
SECURITY: 'Sikkerhet',
ONOFF_CAP: 'PÅ/AV',
ONOFF: 'på/av',
TYPE: 'Type',
DESCRIPTION: 'Beskrivelse',
ENTITIES: 'Ojekter',
REFRESH: 'Oppdater',
EXPORT: 'Eksport',
DEVICE_DETAILS: 'Enhetsdetaljer',
ID_OF: '{0}-ID',
DEVICE: 'Enhets',
PRODUCT: 'Produkt',
VERSION: 'Versjon',
BRAND: 'Fabrikat',
ENTITY_NAME: 'Objektsnavn',
VALUE: '{{Verdi|verdi}}',
SHOW_FAV: ' Vis kun favoritter',
DEVICE_SENSOR_DATA: 'Enheter og Sensordata',
DEVICES_SENSORS: 'Enheter og Sensorer',
ATTACHED_SENSORS: 'Tilkoblede EMS-ESP Sensorer',
RUN_COMMAND: 'Kjør kommando',
CHANGE_VALUE: 'Endre Verdi',
CANCEL: 'Avbryt',
RESET: 'Nullstill',
SEND: 'Send',
SAVE: 'Lagre',
REMOVE: 'Fjern',
PROBLEM_UPDATING: 'Problem med oppdatering',
PROBLEM_LOADING: 'Problem med opplasting',
ACCESS_DENIED: 'Tilgang nektet',
ANALOG_SENSOR: 'Analog Sensor',
ANALOG_SENSORS: 'Analoge Sensorer',
UPDATED_OF: '{0} Oppdatert',
UPDATE_OF: '{0} Oppdater',
REMOVED_OF: '{0} Slettet',
DELETION_OF: '{0} Sletting',
OFFSET: 'Kompensering',
FACTOR: 'Faktor',
FREQ: 'Frekvens',
DUTY_CYCLE: 'Duty Cycle',
UNIT: 'UoM',
STARTVALUE: 'Startverdi',
WARN_GPIO: 'Advarsel: vær forsiktig ved aktivering av GPIO!',
EDIT: 'Endre',
SENSOR: 'Sensor',
TEMP_SENSOR: 'Temperatursensor',
TEMP_SENSORS: 'Temperaturesensorer',
WRITE_CMD_SENT: 'Skriv kommando sent', // TODO translate
WRITE_CMD_FAILED: 'Skriv kommando failed', // TODO translate
EMS_BUS_WARNING: 'EMS bussen koblet ned. Hvis denne advarselen fortsetter etter noen f¨sekunder sjekk instillinger og prosessorkort',
EMS_BUS_SCANNING: 'Søker etter EMS enheter...',
CONNECTED: 'Tilkoblet',
TX_ISSUES: 'Tx problemer - prøv en annen Tx Modus',
DISCONNECTED: 'Frakoblet',
EMS_SCAN: 'Er du sikker på du vil starte full søking av EMS bussen?',
EMS_BUS_STATUS: 'EMS Buss Status',
ACTIVE_DEVICES: 'Aktive Enheter og Sensorer',
EMS_DEVICE: 'EMS Enhet',
SUCCESS: 'VELLYKKET',
FAIL: 'MISLYKKET',
QUALITY: 'KVALITET',
SCAN_DEVICES: 'Søk etter nye enheter',
EMS_BUS_STATUS_TITLE: 'EMS Buss & Aktivitet Status',
SCAN: 'Søk',
STATUS_NAMES: [
'EMS Telegrammer Mottatt (Rx)',
'EMS Lest (Tx)',
'EMS Skrevet (Tx)',
'Temperatur Sensor Lest',
'Analog Sensor Lest',
'MQTT Publiseringer',
'API Anrop',
'Syslog Meldinger'
],
NUM_DEVICES: '{num} Enhet{{er}}',
NUM_TEMP_SENSORS: '{num} Temperatursensor{{er}}',
NUM_ANALOG_SENSORS: '{num} Analogsensor{{er}}',
NUM_DAYS: '{num} sag{{er}}',
NUM_SECONDS: '{num} sekund{{er}}',
NUM_HOURS: '{num} time{{r}}',
NUM_MINUTES: '{num} minutt{{er}}',
APPLICATION_SETTINGS: 'Innstillinger',
CUSTOMIZATION: 'Tilpasninger',
APPLICATION_RESTARTING: 'EMS-ESP restarter',
INTERFACE_BOARD_PROFILE: 'Interface Prosessor Profil',
BOARD_PROFILE_TEXT: 'Velg en pre-konfigurert prosessor profil fra listen under eller velg Tilpasset for å konfigurere dine egne innstillinger',
BOARD_PROFILE: 'Prosessor Profil',
CUSTOM: 'Custom',
GPIO_OF: '{0} GPIO',
BUTTON: 'Knapp',
TEMPERATURE: 'Temperatur',
PHY_TYPE: 'Eth PHY Type',
DISABLED: 'avslått',
TX_MODE: 'Tx Mode',
HARDWARE: 'Hardware',
EMS_BUS: '{{BUS|EMS BUS}}',
GENERAL_OPTIONS: 'Generelle Innstillinger',
LANGUAGE_ENTITIES: 'Språk (for objekter)',
HIDE_LED: 'Skjul LED',
ENABLE_TELNET: 'Aktiver Telnet',
ENABLE_ANALOG: 'Aktiver Analoge Sensorer',
CONVERT_FAHRENHEIT: 'Konverter temperatur til Fahrenheit',
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',
ENABLE_SHOWER_TIMER: 'Aktiver Dusjtimer',
ENABLE_SHOWER_ALERT: 'Aktiver Dusj-varsling',
TRIGGER_TIME: 'Aktiveringstid',
COLD_SHOT_DURATION: 'Tid på kaldt vann',
FORMATTING_OPTIONS: 'Formatteringsalternativs',
BOOLEAN_FORMAT_DASHBOARD: 'Bool Format Dashboard',
BOOLEAN_FORMAT_API: 'Bool Format API/MQTT',
ENUM_FORMAT: 'Enum Format API/MQTT',
INDEX: 'Indeks',
ENABLE_PARASITE: 'Aktiver parasitt strømforsyning',
LOGGING: 'Logging',
LOG_HEX: 'Logg EMS telegrammer i hexadesimal',
ENABLE_SYSLOG: 'Aktiver Syslog',
LOG_LEVEL: 'Log Level',
MARK_INTERVAL: 'Oppdateringsintervall',
SECONDS: 'sekunder',
MINUTES: 'minutter',
HOURS: 'timer',
RESTART: 'Omstart',
RESTART_TEXT: 'EMS-ESP må omstartes for å iverksette endrede systeminstillinger',
RESTART_CONFIRM: 'Er du sikker på at du vil omstarte EMS-ESP?',
COMMAND: 'Kommando',
CUSTOMIZATIONS_RESTART: 'Alle tilpasninger har blitt slettet. Restarter...',
CUSTOMIZATIONS_FULL: 'Antall valgte objekter for høyt. Largre i mindre antall om gangen',
CUSTOMIZATIONS_SAVED: 'Tilpasninger lagret',
CUSTOMIZATIONS_HELP_1: 'Velg en enhet og tilpass underenheter med hjelp av alternativer eller velg å gi nytt navn',
CUSTOMIZATIONS_HELP_2: 'merk som favoritt',
CUSTOMIZATIONS_HELP_3: 'inaktiviser skriving',
CUSTOMIZATIONS_HELP_4: 'ekskludere fra MQTT og API',
CUSTOMIZATIONS_HELP_5: 'gjemme fra Dashboard',
CUSTOMIZATIONS_HELP_6: 'remove from memory',
SELECT_DEVICE: 'Velg en enhet',
SET_ALL: 'sett alle',
OPTIONS: 'Alternativ',
NAME: 'Navn',
CUSTOMIZATIONS_RESET: 'Er du sikker på att du vil fjerne tilpassninger inkludert innstillinger for Temperatur og Analoge sensorer?',
DEVICE_ENTITIES: 'Enhets objekter',
USER_CUSTOMIZATION: 'Brukertilpasninger',
SUPPORT_INFORMATION: 'Supportinformasjon',
CLICK_HERE: 'Klikk her',
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_5: 'EMS-ESP er gratis og åpen kildekode. Bidra til utviklingen ved å gi oss en stjerne på GitHub!',
SUPPORT_INFO: 'Supportinfo',
UPLOAD_OF: '{0} Opplasning',
UPLOAD: 'Opplasning',
DOWNLOAD: 'Nedlasting',
ABORTED: 'avbrutt',
FAILED: 'feilet',
SUCCESSFUL: 'vellykket',
SYSTEM: 'System',
LOG_OF: '{0} Logg',
STATUS_OF: '{0} Status',
UPLOAD_DOWNLOAD: 'Opp/Nedlasting',
SYSTEM_VERSION_RUNNING: 'Du benytter versjon',
SYSTEM_APPLY_FIRMWARE: 'for å aktivere ny firmware',
CLOSE: 'Steng',
USE: 'Bruk',
FACTORY_RESET: 'Sett tilbake til fabrikkinstilling',
SYSTEM_FACTORY_TEXT: 'Enhet har blitt satt tilbake til fabrikkinstilling og vil restarte',
SYSTEM_FACTORY_TEXT_DIALOG: 'Er du sikker på at du vil resette enheten til fabrikkinstillinger?',
VERSION_CHECK: 'Versjonsjekk',
THE_LATEST: 'Den nyeste',
OFFICIAL: 'official',
DEVELOPMENT: 'development',
VERSION_IS: 'versjonen 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)',
FILESYSTEM: 'File System (Brukt / Ledig)',
BUFFER_SIZE: 'Max Buffer Størrelse',
COMPACT: 'Komprimere',
ENABLE_OTA: 'Aktiviser OTA oppdateringer',
DOWNLOAD_CUSTOMIZATION_TEXT: 'Last ned objektstilpasninger',
DOWNLOAD_SETTINGS_TEXT: 'Last ned applikasjonskonfigurasjon. Vær varsom med å dele fila da den inneholder passord og annen sensitiv system informasjon',
UPLOAD_TEXT: 'Last opp en ny firmware (.bin) fil, innstillinger eller tilpassninger (.json) fil nedenfor',
UPLOADING: 'Opplasting',
UPLOAD_DROP_TEXT: 'Slipp fil eller klikk her',
ERROR: 'Ukjent feil, prøv igjen',
TIME_SET: 'Still in tid',
MANAGE_USERS: 'Administrer Brukere',
IS_ADMIN: 'er Admin',
USER_WARNING: 'Du må ha minst en admin bruker konfigurert',
ADD: 'Legg til',
ACCESS_TOKEN_FOR: 'Aksess Token for',
ACCESS_TOKEN_TEXT: 'Token nedenfor benyttes med REST API-kall som krever autorisering. Den kan sendes med enten som en Bearer token i Authorization-headern eller i access_token URL query-parameter.',
GENERATING_TOKEN: 'Generer token',
USER: 'Bruker',
MODIFY: 'Endre',
SU_TEXT: 'su brukeren (super user) passord benyttes for å signere autentiserings token samt å tillate admin privileger i konsoll modus.',
NOT_ENABLED: 'Ikke aktiv',
ERRORS_OF: '{0} Feil',
DISCONNECT_REASON: 'Årsak til nedkobling',
ENABLE_MQTT: 'Aktiver MQTT',
BROKER: 'Broker',
CLIENT: 'Client',
BASE_TOPIC: 'Base',
OPTIONAL: 'Valgfritt',
FORMATTING: 'Formatering',
MQTT_FORMAT: 'Topic/Payload Format',
MQTT_NEST_1: 'Nestet i en topic',
MQTT_NEST_2: 'Som individuelle topics',
MQTT_RESPONSE: 'Publiser kommandoer til en `response` topic',
MQTT_PUBLISH_TEXT_1: 'Publiser singel verdi topics ved endringer',
MQTT_PUBLISH_TEXT_2: 'Publiser til kommando topics (ioBroker)',
MQTT_PUBLISH_TEXT_3: 'Aktiver MQTT Discovery (Home Assistant, Domoticz)',
MQTT_PUBLISH_TEXT_4: 'Prefiks for Discovery topics',
MQTT_PUBLISH_INTERVALS: 'Publiseringsintervall',
MQTT_INT_BOILER: 'Fyr/Varmepumpe',
MQTT_INT_THERMOSTATS: 'Termostat',
MQTT_INT_SOLAR: 'Solpaneler',
MQTT_INT_MIXER: 'Blandeventil',
MQTT_INT_HEARTBEAT: 'Heartbeat',
MQTT_QUEUE: 'MQTT Queue',
DEFAULT: 'Standard',
MQTT_ENTITY_FORMAT: 'Entity ID format', // TODO translate
MQTT_ENTITY_FORMAT_0: 'Single instance, long name (v3.4)', // TODO translate
MQTT_ENTITY_FORMAT_1: 'Single instance, short name', // TODO translate
MQTT_ENTITY_FORMAT_2: 'Multiple instances, short name', // TODO translate
MQTT_CLEAN_SESSION: 'Benytt Clean Session',
MQTT_RETAIN_FLAG: 'Alltid sett Retain flag',
INACTIVE: 'Innaktiv',
ACTIVE: 'Aktiv',
UNKNOWN: 'Ukjent',
SET_TIME: 'Sett Tid',
SET_TIME_TEXT: 'Skriv inn dato og klokke nedenfor',
LOCAL_TIME: 'Lokaltid',
UTC_TIME: 'UTC Tid',
ENABLE_NTP: 'Aktiver NTP',
NTP_SERVER: 'NTP Server',
TIME_ZONE: 'Tidssone',
ACCESS_POINT: 'Aksesspunkt',
AP_PROVIDE: 'Aktiver Aksesspunkt',
AP_PROVIDE_TEXT_1: 'alltid',
AP_PROVIDE_TEXT_2: 'når WiFi er utilgjengelig',
AP_PROVIDE_TEXT_3: 'aldri',
AP_PREFERRED_CHANNEL: 'Foretrukket kanal',
AP_HIDE_SSID: 'Skjul SSID',
AP_CLIENTS: 'AP Clients',
AP_MAX_CLIENTS: 'Max Clients',
AP_LOCAL_IP: 'Local IP',
NETWORK_SCAN: 'Søk etter trådløst nettverk',
IDLE: 'Klar',
LOST: 'Mistet',
SCANNING: 'Søker',
SCAN_AGAIN: 'Søk igjen',
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',
TX_POWER: 'Tx Effekt',
HOSTNAME: 'Hostname',
NETWORK_DISABLE_SLEEP: 'Hindre at trådløst nettverk går i Sleep Mode',
NETWORK_LOW_BAND: 'Benytt smalere båndbredde på trådløst nettverk',
NETWORK_USE_DNS: 'Aktiviser mDNS Service',
NETWORK_ENABLE_CORS: 'Aktiviser CORS',
NETWORK_CORS_ORIGIN: 'CORS origin',
NETWORK_ENABLE_IPV6: 'Aktiviser IPv6 støtte',
NETWORK_FIXED_IP: 'Benytt statisk IP adresse',
NETWORK_GATEWAY: 'Gateway',
NETWORK_SUBNET: 'Nettverksmaske',
NETWORK_DNS: 'DNS Servers',
ADDRESS_OF: '{0} Address',
ADMIN: 'Admin',
GUEST: 'Gjest',
NEW: 'Ny',
NEW_NAME_OF: 'Bytt navn {0}',
ENTITY: 'Entitet',
MIN: 'min',
MAX: 'max'
};
export default no;

View File

@@ -0,0 +1,310 @@
import type { BaseTranslation } from '../i18n-types';
/* prettier-ignore */
/* eslint-disable */
const pl: BaseTranslation = {
LANGUAGE: 'Język',
RETRY: 'Ponów',
LOADING: 'Ładowanie',
IS_REQUIRED: 'Pole {0} nie może być puste!',
SIGN_IN: 'Zaloguj się',
SIGN_OUT: 'Wyloguj się',
USERNAME: '{{Użytkownik|Nazwa użytkownika|}}',
PASSWORD: 'Hasło',
SU_PASSWORD: 'Hasło "su"',
DASHBOARD: 'Pulpit',
SETTINGS_OF: 'Ustawienia {0}',
SAVED: 'zostały zapisane.',
HELP_OF: 'Pomoc {0}',
LOGGED_IN: 'Zalogowano użytkownika {name}.',
PLEASE_SIGNIN: 'Zaloguj się aby kontynuować.',
UPLOAD_SUCCESSFUL: 'Wysyłanie zakończone.',
DOWNLOAD_SUCCESSFUL: 'Pobieranie zakończone.',
INVALID_LOGIN: 'Nieprawidłowy użytkownik lub hasło!',
NETWORK: '{{Sieć|sieci|}}',
SECURITY: '{{B|b|}}ezpieczeństw{{o|a|}}',
ONOFF_CAP: 'wł./wył.',
ONOFF: 'włączono/wyłączono',
TYPE: 'Typ',
DESCRIPTION: 'Opis',
ENTITIES: 'Encje',
REFRESH: 'Odśwież',
EXPORT: 'Eksportuj',
DEVICE_DETAILS: 'Szczegóły urządzenia',
ID_OF: 'ID {0}',
DEVICE: 'urządzenia',
PRODUCT: 'produktu',
BRAND: 'Marka',
VERSION: 'Wersja',
ENTITY_NAME: 'Nazwa encji',
VALUE: '{{W|w|}}artość',
SHOW_FAV: 'Pokaż tylko "ulubione"',
DEVICE_SENSOR_DATA: 'Dane z urządzeń i czujników',
DEVICES_SENSORS: 'Urządzenia i czujniki',
ATTACHED_SENSORS: 'Urządzenia podłączone do EMS-ESP (czujniki temperatury/analogowe/cyfrowe, wyjścia cyfrowe)',
RUN_COMMAND: 'Wykonaj komendę',
CHANGE_VALUE: 'Zmień wartość',
CANCEL: 'Anuluj',
RESET: 'Reset{{uj|owanie|}}',
SEND: 'Wyślij',
SAVE: 'Zapisz',
REMOVE: 'Usuń',
PROBLEM_UPDATING: 'Problem z uaktualnieniem!',
PROBLEM_LOADING: 'Problem z załadowaniem!',
ACCESS_DENIED: 'Brak dostępu!',
ANALOG_SENSOR: 'urządzenia podłączonego do EMS-ESP',
ANALOG_SENSORS: 'Urządzenia podłączone do EMS-ESP',
UPDATED_OF: 'Zaktualizowano ustawienia {0}.',
UPDATE_OF: 'Aktualizacja {0}',
REMOVED_OF: 'Usunięto ustawienia {0}.',
DELETION_OF: 'Kasowanie {0}',
OFFSET: 'Korekta ±',
FACTOR: 'Mnożnik',
FREQ: 'Częstotliwość',
DUTY_CYCLE: 'Wypełnienie',
UNIT: 'J.m.',
STARTVALUE: 'Wartość początkowa',
WARN_GPIO: 'Uwaga! Zachowaj ostrożność przypisując GPIO do urządzenia!',
EDIT: 'Edycja',
SENSOR: 'czujnika',
TEMP_SENSOR: 'czujnika temperatury',
TEMP_SENSORS: 'Czujniki temperatury 1-Wire®',
WRITE_CMD_SENT: 'Komenda zapisu została wysłana.',
WRITE_CMD_FAILED: 'Komenda zapisu nie powiodła się!',
EMS_BUS_WARNING: 'Brak połączenia z magistralą EMS. Jeśli ten błąd występuje dłużej niż kilka sekund, sprawdź ustawienia oraz profil płytki interfejsu.',
EMS_BUS_SCANNING: 'Trwa skanowanie urządzeń na magistrali EMS...',
CONNECTED: '{{połączono|połączenie|}}',
TX_ISSUES: 'problem z zapisem na magistralę EMS, spróbuj wybrać inny "Tryb transmisji (Tx)"',
DISCONNECTED: 'brak połączenia',
EMS_SCAN: 'Czy na pewno wykonać pełne skanowanie magistrali EMS?',
EMS_BUS_STATUS: 'Status magistrali EMS',
ACTIVE_DEVICES: 'Aktywne urządzenia i czujniki',
EMS_DEVICE: 'Urządzenie EMS',
SUCCESS: 'Udane',
FAIL: 'Nieudane',
QUALITY: 'Jakość',
SCAN_DEVICES: 'Wyszukiwanie nowych urządzeń',
EMS_BUS_STATUS_TITLE: 'Aktywność',
SCAN: 'Skanuj',
STATUS_NAMES: [
'EMS, telegramy odebrane (Rx)',
'EMS, wysłane telegramy "odczyt" (Tx)',
'EMS, wysłane telegramy "zapis" (Tx)',
'Odczyty czujników temperatury 1-Wire®',
'Odczyty czujników analogowych i cyfrowych',
'Publikacje MQTT',
'Wywołania API',
'Wpisy w SysLog'
],
NUM_DEVICES: '{num} urządze{{ń|nie|nia|nia|ń}} EMS',
NUM_TEMP_SENSORS: '{num} czujni{{ków|k|ki|ki|ków}} temperatury',
NUM_ANALOG_SENSORS: '{num} inn{{ych|e|e|e|ych}} urządze{{ń|nie|nia(two)|nia|ń}} podłączon{{ych|e|e|e|ych}} do EMS-ESP',
NUM_DAYS: '{num} d{{ni|zień|ni|ni|ni}}',
NUM_SECONDS: '{num} sekun{{d|da|dy|dy|d}}',
NUM_HOURS: '{num} godzi{{n|na|ny|ny|n}}',
NUM_MINUTES: '{num} minu{{t|ta|ty|ty|t}}',
APPLICATION_SETTINGS: 'Ustawienia aplikacji',
CUSTOMIZATION: 'Personalizacja',
APPLICATION_RESTARTING: 'Trwa ponowne uruchamianie',
INTERFACE_BOARD_PROFILE: 'Profil płytki interfejsu',
BOARD_PROFILE_TEXT: 'Wybierz z listy gotowy profil płytki interfejsu lub "własny..." i samodzielnie skonfiguruj posiadany sprzęt.',
BOARD_PROFILE: 'Profil płytki',
CUSTOM: 'własny',
GPIO_OF: 'GPIO {0}',
BUTTON: 'przycisku',
TEMPERATURE: '1-Wire®',
PHY_TYPE: 'Typ układu ethernetowego (PHY)',
DISABLED: '{{wyłączono|brak|}}',
TX_MODE: 'Tryb transmisji (Tx)',
EMS_BUS: '{{magistrali EMS|na magistrali|}}',
HARDWARE: 'sprzętowy',
GENERAL_OPTIONS: 'Opcje podstawowe',
LANGUAGE_ENTITIES: 'Język encji',
HIDE_LED: 'Wyłącz LED',
ENABLE_TELNET: 'Aktywuj dostęp dla konsoli Telnet',
ENABLE_ANALOG: 'Aktywuj urządzenia GPIO (czujniki analogowe i cyfrowe oraz wyjścia cyfrowe)',
CONVERT_FAHRENHEIT: 'Konwertuj temperatury do skali Fahrenheita',
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: 'Start boiler with forced heating off',
ENABLE_SHOWER_TIMER: 'Aktywuj minutnik prysznica',
ENABLE_SHOWER_ALERT: 'Aktywuj alarm prysznica',
TRIGGER_TIME: 'Wyzwalaj po czasie',
COLD_SHOT_DURATION: 'Czas trwania tryśnięcia zimnej wody',
FORMATTING_OPTIONS: 'Opcje formatowania',
BOOLEAN_FORMAT_DASHBOARD: 'Wartości dwustanowe na pulpicie',
BOOLEAN_FORMAT_API: 'Wartości dwustanowe w API/MQTT',
ENUM_FORMAT: 'Wartości z listy w API/MQTT',
INDEX: 'indeks',
ENABLE_PARASITE: 'Aktywuj zasilanie pasożytnicze',
LOGGING: 'Logowanie',
LOG_HEX: 'Loguj telegramy EMS w systemie szesnastkowym (hex)',
ENABLE_SYSLOG: 'Aktywuj SysLog',
LOG_LEVEL: 'Poziom logowania',
MARK_INTERVAL: 'Znaczniki interwałów (0=brak)',
SECONDS: 'sekund',
MINUTES: 'minut',
HOURS: 'godzin',
RESTART: 'Restart',
RESTART_TEXT: 'Aby zastosować wprowadzone zmiany interfejs EMS-ESP musi zostać zrestartowany.',
RESTART_CONFIRM: 'Jesteś pewien, że chcesz zrestartować interfejs EMS-ESP?',
COMMAND: '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_2: 'oznacz jako ulubioną',
CUSTOMIZATIONS_HELP_3: 'zablokuj akcje zapisu',
CUSTOMIZATIONS_HELP_4: 'wyklucz z MQTT i API',
CUSTOMIZATIONS_HELP_5: 'ukryj na pulpicie',
CUSTOMIZATIONS_HELP_6: 'usuń z pamięci',
SELECT_DEVICE: 'wybierz urządzenie',
SET_ALL: 'Ustaw wszystko jako',
OPTIONS: 'Opcje',
NAME: '{{Nazwa|nazwa|}}',
CUSTOMIZATIONS_RESET: 'Czy jesteś pewien, że 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',
USER_CUSTOMIZATION: 'Personalizacje użytkownika',
SUPPORT_INFORMATION: 'Informacje dotyczące wsparcia',
CLICK_HERE: 'Kliknij tu',
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_5: 'EMS-ESP jest darmowym projektem typu open-source. Aby go wesprzeć, rozważ przyznanie nam gwiazdki na Github!',
SUPPORT_INFO: 'Pobierz informacje',
UPLOAD_OF: 'Wysyłanie {0}',
UPLOAD: 'Wysyłanie',
DOWNLOAD: '{{P|p||P}}obier{{anie|z||z}}',
ABORTED: 'zostało przerwane!',
FAILED: 'nie powiodło się!',
SUCCESSFUL: 'powiodło się.',
SYSTEM: '{{S|s||s}}yste{{m|mu||mowy}}',
LOG_OF: 'Log {0}',
STATUS_OF: 'Status {0}',
UPLOAD_DOWNLOAD: 'Przesyłanie plików',
SYSTEM_VERSION_RUNNING: 'Obecnie zainstalowana wersja to:',
SYSTEM_APPLY_FIRMWARE: '',
CLOSE: 'Zamknij',
USE: 'Aby zaktualizować firmware skorzystaj z funkcji',
FACTORY_RESET: 'Ustawienia fabryczne',
SYSTEM_FACTORY_TEXT: 'Interfejs EMS-ESP został przywrócony do ustawień fabrycznych i zostanie teraz ponownie uruchomiony.',
SYSTEM_FACTORY_TEXT_DIALOG: 'Czy jesteś pewien, że chcesz przywrócić ustawienia fabryczne interfejsu EMS-ESP? ',
VERSION_CHECK: 'Sprawd{{ź|zanie|}} wersj{{ę|i|}}',
THE_LATEST: 'Najnowsza',
OFFICIAL: 'oficjalna',
DEVELOPMENT: 'testowa',
VERSION_IS: 'wersja 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)',
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_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).',
UPLOADING: 'Wysłano',
UPLOAD_DROP_TEXT: 'Przeciągnij tutaj plik lub kliknij',
ERROR: 'Nieoczekiwany błąd, spróbuj ponownie!',
TIME_SET: 'Zegar został ustawiony.',
MANAGE_USERS: 'Zarządzanie użytkownikami',
IS_ADMIN: '{{Administrator|Uprawnienia administratora|}}',
USER_WARNING: 'Przynajmniej jeden użytkownik musi mieć uprawnienia administratora!',
ADD: 'Doda{{j|wanie|}}',
ACCESS_TOKEN_FOR: 'Token dostępu dla użytkownika',
ACCESS_TOKEN_TEXT: 'Token jest używany w wywołaniach REST API wymagających autoryzacji. Można go przekazywać bezpośrednio lub przez URL.',
GENERATING_TOKEN: 'Generowanie tokenu',
USER: '{{Użytkownik|użytkownika|}}',
MODIFY: 'Edycja',
SU_TEXT: 'Hasło "su" (super-użytkownika) służy do podpisywania tokenów autoryzujących oraz włączania uprawnień administratora w konsoli.',
NOT_ENABLED: 'nie aktywowano',
ERRORS_OF: 'Błędy {0}',
DISCONNECT_REASON: 'Przyczyna braku połączenia',
ENABLE_MQTT: 'Aktywuj MQTT',
BROKER: 'brokera',
CLIENT: 'klienta',
BASE_TOPIC: 'Prefiks bazowy (unikalny!)',
OPTIONAL: 'opcjonalny',
FORMATTING: 'Formatowanie',
MQTT_FORMAT: 'Sposób publikowania danych',
MQTT_NEST_1: 'zagnieżdżone w jednym temacie',
MQTT_NEST_2: 'jako oddzielne tematy',
MQTT_RESPONSE: 'Rezultat wykonania komendy publikuj w temacie "response"',
MQTT_PUBLISH_TEXT_1: 'Tematy z pojedynczą wartością publikuj po jej zmianie',
MQTT_PUBLISH_TEXT_2: 'Publikuj w tematach "command" (ioBroker)',
MQTT_PUBLISH_TEXT_3: 'Włącz opcję "MQTT discovery" (Home Assistant, Domoticz)',
MQTT_PUBLISH_TEXT_4: 'Prefiks dla "MQTT discovery"',
MQTT_PUBLISH_INTERVALS: 'Interwały publikowania',
MQTT_INT_BOILER: 'Kotły i pompy ciepła',
MQTT_INT_THERMOSTATS: 'Termostaty',
MQTT_INT_SOLAR: 'Panele solarne',
MQTT_INT_MIXER: 'Mieszacze',
MQTT_INT_HEARTBEAT: '"Heartbeat" (aktywność)',
MQTT_QUEUE: 'Kolejka MQTT',
DEFAULT: '{{Pozostałe|Domyślna|}}',
MQTT_ENTITY_FORMAT: 'Format "Entity ID"',
MQTT_ENTITY_FORMAT_0: 'długa nazwa (jak w v3.4)',
MQTT_ENTITY_FORMAT_1: 'krótka nazwa',
MQTT_ENTITY_FORMAT_2: 'prefiks bazowy + krótka nazwa',
MQTT_CLEAN_SESSION: 'Ustawiaj flagę "Clean session"',
MQTT_RETAIN_FLAG: 'Ustawiaj flagę "Retain"',
INACTIVE: 'nieaktywn{{y|a|}}',
ACTIVE: 'aktywny',
UNKNOWN: 'nieznany',
SET_TIME: '{{Ustaw zegar|Ustawianie zegara|}}',
SET_TIME_TEXT: 'Wprowadź aktualną datę i godzinę',
LOCAL_TIME: 'Czas lokalny',
UTC_TIME: 'Czas UTC',
ENABLE_NTP: 'Aktywuj NTP (data i godzina będą automatycznie synchronizowane z poniższym serwerem czasu)',
NTP_SERVER: 'Serwer NTP',
TIME_ZONE: 'Strefa czasowa',
ACCESS_POINT: '{{Punkt|punktu|}} {{dostępowy|dostępowego|}}',
AP_PROVIDE: 'Punkt dostępowy',
AP_PROVIDE_TEXT_1: 'zawsze aktywny',
AP_PROVIDE_TEXT_2: 'aktywny jeśli brak połączenia z siecią',
AP_PROVIDE_TEXT_3: 'nieaktywny',
AP_PREFERRED_CHANNEL: 'Preferowany kanał',
AP_HIDE_SSID: 'Ukryj SSID',
AP_CLIENTS: 'Liczba klientów',
AP_MAX_CLIENTS: 'Maksymalna liczba klientów',
AP_LOCAL_IP: 'Lokalny adres IP',
NETWORK_SCAN: 'Skanowanie sieci WiFi',
IDLE: 'bezczynna',
LOST: 'zostało utracone.',
SCANNING: 'Skanuję',
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',
TX_POWER: 'Moc nadawania',
HOSTNAME: 'Nazwa w sieci',
NETWORK_DISABLE_SLEEP: 'Wyłącz tryb usypiania 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_ENABLE_IPV6: 'Włącz wsparcie dla IPv6',
NETWORK_FIXED_IP: 'Użyj stałego adresu IP',
NETWORK_GATEWAY: 'Brama',
NETWORK_SUBNET: 'Maska podsieci',
NETWORK_DNS: 'Serwery DNS',
ADDRESS_OF: 'Adres {0}',
ADMIN: 'Użytkownik "administrator".',
GUEST: 'Użytkownik "gość".',
NEW: 'Nowy',
NEW_NAME_OF: 'Nowa nazwa {0}',
ENTITY: 'encji',
MIN: 'Min.',
MAX: 'Maks.'
};
export default pl;

View File

@@ -0,0 +1,310 @@
import type { Translation } from '../i18n-types';
/* prettier-ignore */
/* eslint-disable */
const sv: Translation = {
LANGUAGE: 'Språk',
RETRY: 'Försök igen',
LOADING: 'Laddar',
IS_REQUIRED: '{0} Krävs',
SIGN_IN: 'Logga In',
SIGN_OUT: 'Logga Ut',
USERNAME: 'Användarnamn',
PASSWORD: 'Lösenord',
SU_PASSWORD: 'su Lösenord',
DASHBOARD: 'Kontrollpanel',
SETTINGS_OF: '{0} Inställningar',
SAVED: 'Sparat',
HELP_OF: '{0} Hjälp',
LOGGED_IN: 'Inloggad som {name}',
PLEASE_SIGNIN: 'Vänligen logga in för att fortsätta',
UPLOAD_SUCCESSFUL: 'Uppladdning lyckades',
DOWNLOAD_SUCCESSFUL: 'Nedladdning lyckades',
INVALID_LOGIN: 'Ogiltig login',
NETWORK: 'Nätverk',
SECURITY: 'Säkerhet',
ONOFF_CAP: 'PÅ/AV',
ONOFF: 'på/av',
TYPE: 'Typ',
DESCRIPTION: 'Beskrivning',
ENTITIES: 'Entiteter',
REFRESH: 'Uppdatera',
EXPORT: 'Exportera',
DEVICE_DETAILS: 'Enhetsdetaljer',
ID_OF: '{0}-ID',
DEVICE: 'Enhets',
PRODUCT: 'Produkt',
VERSION: 'Version',
BRAND: 'Fabrikat',
ENTITY_NAME: 'Entitetsnamn',
VALUE: '{{Värde|värde}}',
SHOW_FAV: 'Visa enbart favoriter',
DEVICE_SENSOR_DATA: 'Enhets och Sensor-data',
DEVICES_SENSORS: 'Enheter & Sensorer',
ATTACHED_SENSORS: 'Anslutna EMS-ESP Sensorer',
RUN_COMMAND: 'Kör Kommando',
CHANGE_VALUE: 'Ändra Värde',
CANCEL: 'Avbryt',
RESET: 'Nollställ',
SEND: 'Skicka',
SAVE: 'Spara',
REMOVE: 'Ta bort',
PROBLEM_UPDATING: 'Problem vid uppdatering',
PROBLEM_LOADING: 'Problem vid hämtning',
ACCESS_DENIED: 'Åtkomst Nekad',
ANALOG_SENSOR: 'Analog Sensor',
ANALOG_SENSORS: 'Analoga Sensorer',
UPDATED_OF: '{0} Uppdaterad',
UPDATE_OF: '{0} Uppdatera',
REMOVED_OF: '{0} Raderad',
DELETION_OF: '{0} Radering',
OFFSET: 'Kompensering',
FACTOR: 'Faktor',
FREQ: 'Frekvens',
DUTY_CYCLE: 'Duty Cycle',
UNIT: 'UoM',
STARTVALUE: 'Startvärde',
WARN_GPIO: 'Varning: Var försiktig vid aktivering av GPIO!',
EDIT: 'Ändra',
SENSOR: 'Sensor',
TEMP_SENSOR: 'Temperatursensor',
TEMP_SENSORS: 'Temperatursensorer',
WRITE_CMD_SENT: 'Skrivkommandon skickade',
WRITE_CMD_FAILED: 'Skrivkommandon misslyckade',
EMS_BUS_WARNING: 'EMS-buss nedkopplad. Om denna varning kvarstår efter några sekunder, kontrollera inställningar och enhets-profil.',
EMS_BUS_SCANNING: 'Söker efter EMS-enheter...',
CONNECTED: 'Ansluten',
TX_ISSUES: 'Sändfel - Prova ett annat TX-läge',
DISCONNECTED: 'Nedkopplad',
EMS_SCAN: 'Är du säker att du vill initiera en full genomsökning av EMS-bussen?',
EMS_BUS_STATUS: 'Status',
ACTIVE_DEVICES: 'Aktiva Enheter',
EMS_DEVICE: 'EMS Enhet',
SUCCESS: 'Lyckades',
FAIL: 'Misslyckades',
QUALITY: 'Kvalitet',
SCAN_DEVICES: 'Sök efter nya enheter',
EMS_BUS_STATUS_TITLE: 'EMS-buss & aktivitetsstatus',
SCAN: 'Sök',
STATUS_NAMES: [
'EMS-telegram (Rx)',
'EMS-läsningar (Tx)',
'EMS-skrivningar (Tx)',
'Temperatursensor-läsningar',
'Analog Sensor-läsningar',
'MQTT-publiceringar',
'API-anrop',
'Syslog-meddelanden'
],
NUM_DEVICES: '{num} Enhet{{er}}',
NUM_TEMP_SENSORS: '{num} Temperatur-sensor{{er}}',
NUM_ANALOG_SENSORS: '{num} Analoga Sensor{{er}}',
NUM_DAYS: '{num} dag{{ar}}',
NUM_SECONDS: '{num} sekund{{er}}',
NUM_HOURS: '{num} timmar',
NUM_MINUTES: '{num} minut{{er}}',
APPLICATION_SETTINGS: 'Inställningar',
CUSTOMIZATION: 'Anpassa',
APPLICATION_RESTARTING: 'EMS-ESP startar om',
INTERFACE_BOARD_PROFILE: 'Interface Hårdvaruprofil',
BOARD_PROFILE_TEXT: 'Välj en förkonfigurerad hårdvaruprofil från listan nedan eller välj Anpassad för att konfigurera dina egna hårdvaruinställningar',
BOARD_PROFILE: 'Hårdvarutyp',
CUSTOM: 'Anpassa',
GPIO_OF: '{0} GPIO',
BUTTON: 'Knapp',
TEMPERATURE: 'Temperatur',
PHY_TYPE: 'Eth PHY-typ',
DISABLED: 'inaktiverad',
TX_MODE: 'Tx-läge',
HARDWARE: 'Hårdvara',
EMS_BUS: '{{BUSS|EMS-BUSS}}',
GENERAL_OPTIONS: 'Allmänna Inställningar',
LANGUAGE_ENTITIES: 'Språk (för entiteter)',
HIDE_LED: 'Inaktivera LED',
ENABLE_TELNET: 'Aktivera Telnet',
ENABLE_ANALOG: 'Aktivera Analoga Sensorer',
CONVERT_FAHRENHEIT: 'Konvertera temperaturer till Fahrenheit',
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',
ENABLE_SHOWER_TIMER: 'Aktivera Dusch-timer',
ENABLE_SHOWER_ALERT: 'Aktivera Dusch-varning',
TRIGGER_TIME: 'Aktiveringstid',
COLD_SHOT_DURATION: 'Längd på kalldusch',
FORMATTING_OPTIONS: 'Formatteringsalternativ',
BOOLEAN_FORMAT_DASHBOARD: 'Bool-format Kontrollpanel',
BOOLEAN_FORMAT_API: 'Bool-format API/MQTT',
ENUM_FORMAT: 'Enum-format API/MQTT',
INDEX: 'Index',
ENABLE_PARASITE: 'Aktivera parasitström',
LOGGING: 'Loggning',
LOG_HEX: 'Logga EMS-telegram i hexadecimal',
ENABLE_SYSLOG: 'Aktivera Syslog',
LOG_LEVEL: 'Loggnivå',
MARK_INTERVAL: 'Markerings-interval',
SECONDS: 'sekunder',
MINUTES: 'minuter',
HOURS: 'timmar',
RESTART: 'Starta om',
RESTART_TEXT: 'EMS-ESP kräver en omstart för att applicera förändrade systeminställningar',
RESTART_CONFIRM: 'Är du säker på att du vill starta om EMS-ESP?',
COMMAND: 'Kommando',
CUSTOMIZATIONS_RESTART: 'Alla anpassningr har raderats. Startar om...',
CUSTOMIZATIONS_FULL: 'Antal valda enheter för högt. Vänligen spara i mindre antal åt gången.',
CUSTOMIZATIONS_SAVED: 'Anpassningar sparade',
CUSTOMIZATIONS_HELP_1: 'Välj en enhet och anpassa underenheter med hjälp av alternativen',
CUSTOMIZATIONS_HELP_2: 'Favorit',
CUSTOMIZATIONS_HELP_3: 'Inaktivera skrivningar',
CUSTOMIZATIONS_HELP_4: 'Exkludera från MQTT & API',
CUSTOMIZATIONS_HELP_5: 'Göm från Kontrollpanel',
CUSTOMIZATIONS_HELP_6: 'remove from memory',
SELECT_DEVICE: 'Välj en enhet',
SET_ALL: 'ställ in alla',
OPTIONS: 'Alternativ',
NAME: 'Namn',
CUSTOMIZATIONS_RESET: 'Är du säker på att du vill ta bort alla anpassningar inklusive inställningar för Temperatur och Analoga sensorer?',
DEVICE_ENTITIES: 'Enhets-entiteter',
USER_CUSTOMIZATION: 'Användaranpassningar',
SUPPORT_INFORMATION: 'Supportinformation',
CLICK_HERE: 'Klicka Här',
HELP_INFORMATION_1: 'Besök Wikin för instruktioner för hur du kan konfigurera EMS-ESP',
HELP_INFORMATION_2: 'För community-support besök vår Discord-server',
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_OF: '{0} Uppladdning',
UPLOAD: 'Uppladdning',
DOWNLOAD: 'Nedladdning',
ABORTED: 'Avbruten',
FAILED: 'Misslyckades',
SUCCESSFUL: 'Lyckades',
SYSTEM: 'System',
LOG_OF: '{0} Logg',
STATUS_OF: '{0} Status',
UPLOAD_DOWNLOAD: 'Upp/Nedladdning',
SYSTEM_VERSION_RUNNING: 'Du använder version',
SYSTEM_APPLY_FIRMWARE: 'för att aktivera ny firmware',
CLOSE: 'Stäng',
USE: 'Använd',
FACTORY_RESET: 'Fabriksåterställning',
SYSTEM_FACTORY_TEXT: 'Enheten har blivit fabriksåterställd och startar nu om',
SYSTEM_FACTORY_TEXT_DIALOG: 'Är du säker att du vill fabriksåterställa enheten?',
VERSION_CHECK: 'Senaste versioner',
THE_LATEST: 'Den senaste',
OFFICIAL: 'officiell',
DEVELOPMENT: 'utveckling',
VERSION_IS: 'version är',
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)',
FILESYSTEM: 'Filsystem (Använt / Ledigt)',
BUFFER_SIZE: 'Max Bufferstorlek',
COMPACT: 'Komprimera',
ENABLE_OTA: 'Aktivera OTA-uppdateringar',
DOWNLOAD_CUSTOMIZATION_TEXT: 'Ladda ner entitetsanpassningar',
DOWNLOAD_SETTINGS_TEXT: 'Ladda ner applikationsinställningar. Var försiktig om du delar dina iställlningar då de innehåller lösenord och annan känslig systeminformation',
UPLOAD_TEXT: 'Ladda upp ett nytt firmware (.bin), inställningar eller anpassningar (.json) nedan',
UPLOADING: 'Laddar upp',
UPLOAD_DROP_TEXT: 'Släpp fil eller klicka här',
ERROR: 'Okänt Fel, var god försök igen',
TIME_SET: 'Ställ in tid',
MANAGE_USERS: 'Användare',
IS_ADMIN: 'Admin',
USER_WARNING: 'Du måste ha minst en admin konfigurerad',
ADD: 'Lägg till',
ACCESS_TOKEN_FOR: 'Access Token för',
ACCESS_TOKEN_TEXT: 'Nedan Token används med REST API-anrop som kräver auktorisering. Den kan skickas med antingen som en Bearer token i Authorization-headern eller i access_token URL query-parametern.',
GENERATING_TOKEN: 'Genererar token',
USER: 'Användare',
MODIFY: 'Ändra',
SU_TEXT: 'SU-användarens (super user) lösenord används för att signera autensierings-tokens samt för att aktivera administratörsprivilegier i Console-läge',
NOT_ENABLED: 'Ej aktiv',
ERRORS_OF: '{0} fel',
DISCONNECT_REASON: 'Anledning till nedkoppling',
ENABLE_MQTT: 'Aktivera MQTT',
BROKER: 'Broker',
CLIENT: 'Client',
BASE_TOPIC: 'Base',
OPTIONAL: 'Valfritt',
FORMATTING: 'Formatering',
MQTT_FORMAT: 'Topic/Payload Format',
MQTT_NEST_1: 'Nestlat i en topic.',
MQTT_NEST_2: 'Som individuella topics',
MQTT_RESPONSE: 'Publish-kommando som ett `response` topic',
MQTT_PUBLISH_TEXT_1: 'Publicera single value topics vid värdeförändring',
MQTT_PUBLISH_TEXT_2: 'Publicera till kommando-topics (ioBroker)',
MQTT_PUBLISH_TEXT_3: 'Aktivera MQTT Discovery (Home Assistant, Domoticz)',
MQTT_PUBLISH_TEXT_4: 'Prefix för Discovery topics',
MQTT_PUBLISH_INTERVALS: 'Publiceringsintervall',
MQTT_INT_BOILER: 'Värmepump/panna',
MQTT_INT_THERMOSTATS: 'Termostater',
MQTT_INT_SOLAR: 'Solpaneler',
MQTT_INT_MIXER: 'Blandningsventiler',
MQTT_INT_HEARTBEAT: 'Heartbeat',
MQTT_QUEUE: 'MQTT-kö',
DEFAULT: 'Standard',
MQTT_ENTITY_FORMAT: 'Entitets-ID format',
MQTT_ENTITY_FORMAT_0: 'Singel-instans, långt namn(v3.4)',
MQTT_ENTITY_FORMAT_1: 'Singel-instans, kort name',
MQTT_ENTITY_FORMAT_2: 'Multi-instans, kort name',
MQTT_CLEAN_SESSION: 'Använd "Clean Session"-flaggan',
MQTT_RETAIN_FLAG: 'Använd "Always Retain"-flaggan',
INACTIVE: 'Inaktiv',
ACTIVE: 'Aktiv',
UNKNOWN: 'Okänt',
SET_TIME: 'Ställ in klockan',
SET_TIME_TEXT: 'Ange lokal datum och tid nedan för att ställa in klockan',
LOCAL_TIME: 'Tid (lokal)',
UTC_TIME: 'Tid (UTC)',
ENABLE_NTP: 'Aktivera NTP',
NTP_SERVER: 'NTP-server',
TIME_ZONE: 'Tidszon',
ACCESS_POINT: 'Accesspunkt',
AP_PROVIDE: 'Aktivera Accesspunkt',
AP_PROVIDE_TEXT_1: 'alltid',
AP_PROVIDE_TEXT_2: 'när WiFi är nedkopplat',
AP_PROVIDE_TEXT_3: 'aldrig',
AP_PREFERRED_CHANNEL: 'Kanal',
AP_HIDE_SSID: 'Göm SSID',
AP_CLIENTS: 'AP-klienter',
AP_MAX_CLIENTS: 'Max Klienter',
AP_LOCAL_IP: 'Lokalt IP',
NETWORK_SCAN: 'Sök efter WiFi-nätverk',
IDLE: 'Vilande',
LOST: 'Förlorad',
SCANNING: 'Söker',
SCAN_AGAIN: 'Sök igen',
NETWORK_SCANNER: 'Hittade nätverk',
NETWORK_NO_WIFI: 'Inga WiFi-nätverk hittades',
NETWORK_BLANK_SSID: 'lämna blankt för att inaktivera WiFi',
TX_POWER: 'Tx Effekt',
HOSTNAME: 'Värdnamn',
NETWORK_DISABLE_SLEEP: 'Inaktivera sömnläge',
NETWORK_LOW_BAND: 'Använd lägre bandbredd',
NETWORK_USE_DNS: 'Aktivera mDNS-tjänsten',
NETWORK_ENABLE_CORS: 'Aktivera CORS',
NETWORK_CORS_ORIGIN: 'CORS origin',
NETWORK_ENABLE_IPV6: 'Aktivera IPv6-support',
NETWORK_FIXED_IP: 'Använd statiskt IP',
NETWORK_GATEWAY: 'Gateway',
NETWORK_SUBNET: 'Subnätmask',
NETWORK_DNS: 'DNS-Server',
ADDRESS_OF: '{0} Adress',
ADMIN: 'Admin',
GUEST: 'Gäst',
NEW: 'Ny',
NEW_NAME_OF: 'Byt namn {0}',
ENTITY: 'Entitet',
MIN: 'min',
MAX: 'max'
};
export default sv;

View File

@@ -1,15 +1,15 @@
import React from 'react'; import { StrictMode } from 'react';
import ReactDOM from 'react-dom'; import { createRoot } from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom'; import { BrowserRouter } from 'react-router-dom';
import App from './App'; import App from './App';
ReactDOM.render( const root = createRoot(document.getElementById('root') as HTMLElement);
<React.StrictMode>
root.render(
<StrictMode>
<BrowserRouter> <BrowserRouter>
<App /> <App />
</BrowserRouter> </BrowserRouter>
</React.StrictMode>, </StrictMode>
document.getElementById('root')
); );

View File

@@ -5,17 +5,21 @@ import { Tab } from '@mui/material';
import { RouterTabs, useRouterTab, useLayoutTitle } from '../components'; import { RouterTabs, useRouterTab, useLayoutTitle } from '../components';
import { useI18nContext } from '../i18n/i18n-react';
import DashboardStatus from './DashboardStatus'; import DashboardStatus from './DashboardStatus';
import DashboardData from './DashboardData'; import DashboardData from './DashboardData';
const Dashboard: FC = () => { const Dashboard: FC = () => {
useLayoutTitle('Dashboard');
const { routerTab } = useRouterTab(); const { routerTab } = useRouterTab();
const { LL } = useI18nContext();
useLayoutTitle(LL.DASHBOARD());
return ( return (
<> <>
<RouterTabs value={routerTab}> <RouterTabs value={routerTab}>
<Tab value="data" label="Devices &amp; Sensors" /> <Tab value="data" label={LL.DEVICES_SENSORS()} />
<Tab value="status" label="Status" /> <Tab value="status" label="Status" />
</RouterTabs> </RouterTabs>
<Routes> <Routes>

File diff suppressed because it is too large Load Diff

View File

@@ -32,10 +32,13 @@ import { ButtonRow, FormLoader, SectionContent } from '../components';
import { Status, busConnectionStatus, Stat } from './types'; import { Status, busConnectionStatus, Stat } from './types';
import { formatDurationSec, pluralize, extractErrorMessage, useRest } from '../utils'; import { extractErrorMessage, useRest } from '../utils';
import * as EMSESP from './api'; import * as EMSESP from './api';
import type { Translation } from '../i18n/i18n-types';
import { useI18nContext } from '../i18n/i18n-react';
export const isConnected = ({ status }: Status) => status !== busConnectionStatus.BUS_STATUS_OFFLINE; export const isConnected = ({ status }: Status) => status !== busConnectionStatus.BUS_STATUS_OFFLINE;
const busStatusHighlight = ({ status }: Status, theme: Theme) => { const busStatusHighlight = ({ status }: Status, theme: Theme) => {
@@ -51,19 +54,6 @@ const busStatusHighlight = ({ status }: Status, theme: Theme) => {
} }
}; };
const busStatus = ({ status }: Status) => {
switch (status) {
case busConnectionStatus.BUS_STATUS_CONNECTED:
return 'Connected';
case busConnectionStatus.BUS_STATUS_TX_ERRORS:
return 'Tx issues - try a different Tx Mode';
case busConnectionStatus.BUS_STATUS_OFFLINE:
return 'Disconnected';
default:
return 'Unknown';
}
};
const showQuality = (stat: Stat) => { const showQuality = (stat: Stat) => {
if (stat.q === 0 || stat.s + stat.f === 0) { if (stat.q === 0 || stat.s + stat.f === 0) {
return; return;
@@ -81,72 +71,67 @@ const showQuality = (stat: Stat) => {
const DashboardStatus: FC = () => { const DashboardStatus: FC = () => {
const { loadData, data, errorMessage } = useRest<Status>({ read: EMSESP.readStatus }); const { loadData, data, errorMessage } = useRest<Status>({ read: EMSESP.readStatus });
const { LL } = useI18nContext();
const theme = useTheme(); const theme = useTheme();
const [confirmScan, setConfirmScan] = useState<boolean>(false); const [confirmScan, setConfirmScan] = useState<boolean>(false);
const { enqueueSnackbar } = useSnackbar(); const { enqueueSnackbar } = useSnackbar();
const { me } = useContext(AuthenticatedContext); const { me } = useContext(AuthenticatedContext);
const showName = (id: any) => {
let name: keyof Translation['STATUS_NAMES'] = id;
return LL.STATUS_NAMES[name]();
};
const busStatus = ({ status }: Status) => {
switch (status) {
case busConnectionStatus.BUS_STATUS_CONNECTED:
return LL.CONNECTED(0);
case busConnectionStatus.BUS_STATUS_TX_ERRORS:
return LL.TX_ISSUES();
case busConnectionStatus.BUS_STATUS_OFFLINE:
return LL.DISCONNECTED();
default:
return 'Unknown';
}
};
const stats_theme = tableTheme({ const stats_theme = tableTheme({
Table: `
--data-table-library_grid-template-columns: repeat(1, minmax(0, 1fr)) 90px 90px 80px;
`,
BaseRow: ` BaseRow: `
font-size: 14px; font-size: 14px;
color: white;
height: 32px;
`, `,
HeaderRow: ` HeaderRow: `
text-transform: uppercase; text-transform: uppercase;
background-color: black; background-color: black;
color: #90CAF9; color: #90CAF9;
font-weight: 500;
border-bottom: 1px solid #e0e0e0; .th {
padding-left: 8px; height: 42px;
font-weight: 500;
border-bottom: 1px solid #565656;
}
`, `,
Row: ` Row: `
&:nth-of-type(odd) { .td {
padding: 8px;
border-top: 1px solid #565656;
border-bottom: 1px solid #565656;
}
&:nth-of-type(odd) .td {
background-color: #303030; background-color: #303030;
} }
&:nth-of-type(even) { &:nth-of-type(even) .td {
background-color: #1e1e1e; background-color: #1e1e1e;
} }
border-top: 1px solid #565656;
border-bottom: 1px solid #565656;
position: relative;
z-index: 1;
&:not(:last-of-type) {
margin-bottom: -1px;
}
&:not(:first-of-type) {
margin-top: -1px;
}
&:hover {
color: white;
}
`, `,
BaseCell: ` BaseCell: `
border-top: 1px solid transparent; &:not(:first-of-type) {
border-right: 1px solid transparent; text-align: center;
border-bottom: 1px solid transparent;
&:not(.stiff) > div {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
&:nth-of-type(1) {
padding-left: 8px;
flex: 1;
}
&:nth-of-type(2) {
width: 70px;
text-align: right;
}
&:nth-of-type(3) {
width: 40px;
text-align: right;
}
&:last-of-type {
width: 75px;
text-align: right;
padding-right: 8px;
} }
` `
}); });
@@ -162,24 +147,44 @@ const DashboardStatus: FC = () => {
const scan = async () => { const scan = async () => {
try { try {
await EMSESP.scanDevices(); await EMSESP.scanDevices();
enqueueSnackbar('Scanning for devices...', { variant: 'info' }); enqueueSnackbar(LL.SCANNING() + '...', { variant: 'info' });
} catch (error: unknown) { } catch (error) {
enqueueSnackbar(extractErrorMessage(error, 'Problem initiating scan'), { variant: 'error' }); enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
} finally { } finally {
setConfirmScan(false); setConfirmScan(false);
} }
}; };
const formatDurationSec = (duration_sec: number) => {
const days = Math.trunc((duration_sec * 1000) / 86400000);
const hours = Math.trunc((duration_sec * 1000) / 3600000) % 24;
const minutes = Math.trunc((duration_sec * 1000) / 60000) % 60;
const seconds = Math.trunc((duration_sec * 1000) / 1000) % 60;
let formatted = '';
if (days) {
formatted += LL.NUM_DAYS({ num: days }) + ' ';
}
if (hours) {
formatted += LL.NUM_HOURS({ num: hours }) + ' ';
}
if (minutes) {
formatted += LL.NUM_MINUTES({ num: minutes }) + ' ';
}
formatted += LL.NUM_SECONDS({ num: seconds });
return formatted;
};
const renderScanDialog = () => ( const renderScanDialog = () => (
<Dialog open={confirmScan} onClose={() => setConfirmScan(false)}> <Dialog open={confirmScan} onClose={() => setConfirmScan(false)}>
<DialogTitle>EMS Device Scan</DialogTitle> <DialogTitle>{LL.SCAN_DEVICES()}</DialogTitle>
<DialogContent dividers>Are you sure you want to initiate a full device scan of the EMS bus?</DialogContent> <DialogContent dividers>{LL.EMS_SCAN()}</DialogContent>
<DialogActions> <DialogActions>
<Button startIcon={<CancelIcon />} variant="outlined" onClick={() => setConfirmScan(false)} color="secondary"> <Button startIcon={<CancelIcon />} variant="outlined" onClick={() => setConfirmScan(false)} color="secondary">
Cancel {LL.CANCEL()}
</Button> </Button>
<Button startIcon={<PermScanWifiIcon />} variant="outlined" onClick={scan} color="primary" autoFocus> <Button startIcon={<PermScanWifiIcon />} variant="outlined" onClick={scan} color="primary" autoFocus>
Scan {LL.SCAN()}
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
@@ -199,7 +204,10 @@ const DashboardStatus: FC = () => {
<DirectionsBusIcon /> <DirectionsBusIcon />
</Avatar> </Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText primary="EMS Bus Status" secondary={busStatus(data) + formatDurationSec(data.uptime)} /> <ListItemText
primary={LL.EMS_BUS_STATUS()}
secondary={busStatus(data) + ' (' + formatDurationSec(data.uptime) + ')'}
/>
</ListItem> </ListItem>
<ListItem> <ListItem>
<ListItemAvatar> <ListItemAvatar>
@@ -208,13 +216,13 @@ const DashboardStatus: FC = () => {
</Avatar> </Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText <ListItemText
primary="Active Devices &amp; Sensors" primary={LL.ACTIVE_DEVICES()}
secondary={ secondary={
pluralize(data.num_devices, 'EMS Device') + LL.NUM_DEVICES({ num: data.num_devices }) +
', ' + ', ' +
pluralize(data.num_sensors, 'Temperature Sensor') + LL.NUM_TEMP_SENSORS({ num: data.num_sensors }) +
', ' + ', ' +
pluralize(data.num_analogs, 'Analog Sensor') LL.NUM_ANALOG_SENSORS({ num: data.num_analogs })
} }
/> />
</ListItem> </ListItem>
@@ -224,19 +232,19 @@ const DashboardStatus: FC = () => {
<> <>
<Header> <Header>
<HeaderRow> <HeaderRow>
<HeaderCell></HeaderCell> <HeaderCell resize></HeaderCell>
<HeaderCell>SUCCESS</HeaderCell> <HeaderCell stiff>{LL.SUCCESS()}</HeaderCell>
<HeaderCell>FAIL</HeaderCell> <HeaderCell stiff>{LL.FAIL()}</HeaderCell>
<HeaderCell>QUALITY</HeaderCell> <HeaderCell stiff>{LL.QUALITY()}</HeaderCell>
</HeaderRow> </HeaderRow>
</Header> </Header>
<Body> <Body>
{tableList.map((stat: Stat) => ( {tableList.map((stat: Stat) => (
<Row key={stat.id} item={stat}> <Row key={stat.id} item={stat}>
<Cell>{stat.id}</Cell> <Cell>{showName(stat.id)}</Cell>
<Cell>{Intl.NumberFormat().format(stat.s)}</Cell> <Cell stiff>{Intl.NumberFormat().format(stat.s)}</Cell>
<Cell>{Intl.NumberFormat().format(stat.f)}</Cell> <Cell stiff>{Intl.NumberFormat().format(stat.f)}</Cell>
<Cell>{showQuality(stat)}</Cell> <Cell stiff>{showQuality(stat)}</Cell>
</Row> </Row>
))} ))}
</Body> </Body>
@@ -248,7 +256,7 @@ const DashboardStatus: FC = () => {
<Box display="flex" flexWrap="wrap"> <Box display="flex" flexWrap="wrap">
<Box flexGrow={1} sx={{ '& button': { mt: 2 } }}> <Box flexGrow={1} sx={{ '& button': { mt: 2 } }}>
<Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={loadData}> <Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={loadData}>
Refresh {LL.REFRESH()}
</Button> </Button>
</Box> </Box>
<Box flexWrap="nowrap" whiteSpace="nowrap"> <Box flexWrap="nowrap" whiteSpace="nowrap">
@@ -260,7 +268,7 @@ const DashboardStatus: FC = () => {
disabled={!me.admin} disabled={!me.admin}
onClick={() => setConfirmScan(true)} onClick={() => setConfirmScan(true)}
> >
Scan for new devices {LL.SCAN_DEVICES()}
</Button> </Button>
</ButtonRow> </ButtonRow>
</Box> </Box>
@@ -270,7 +278,7 @@ const DashboardStatus: FC = () => {
}; };
return ( return (
<SectionContent title="EMS Bus &amp; Activity Status" titleGutter> <SectionContent title={LL.EMS_BUS_STATUS_TITLE()} titleGutter>
{content()} {content()}
</SectionContent> </SectionContent>
); );

View File

@@ -9,31 +9,61 @@ import { GiHeatHaze } from 'react-icons/gi';
import { TiFlowSwitch } from 'react-icons/ti'; import { TiFlowSwitch } from 'react-icons/ti';
import { VscVmConnect } from 'react-icons/vsc'; import { VscVmConnect } from 'react-icons/vsc';
import { AiOutlineGateway } from 'react-icons/ai'; import { AiOutlineGateway } from 'react-icons/ai';
import { AiOutlineAlert } from 'react-icons/ai';
import { AiOutlineChrome } from 'react-icons/ai';
interface DeviceIconProps { interface DeviceIconProps {
type: string; type_id: number;
} }
const DeviceIcon: FC<DeviceIconProps> = ({ type }) => { // matches emsdevice.h DeviceType
switch (type) { const enum DeviceType {
case 'Boiler': SYSTEM = 0,
return <CgSmartHomeBoiler />; DALLASSENSOR,
case 'Sensor': ANALOGSENSOR,
BOILER,
THERMOSTAT,
MIXER,
SOLAR,
HEATPUMP,
GATEWAY,
SWITCH,
CONTROLLER,
CONNECT,
ALERT,
PUMP,
GENERIC,
HEATSOURCE,
UNKNOWN
}
const DeviceIcon: FC<DeviceIconProps> = ({ type_id }) => {
switch (type_id) {
case DeviceType.DALLASSENSOR:
case DeviceType.ANALOGSENSOR:
return <MdOutlineSensors />; return <MdOutlineSensors />;
case 'Solar': case DeviceType.BOILER:
return <FaSolarPanel />; case DeviceType.HEATSOURCE:
case 'Thermostat': return <CgSmartHomeBoiler />;
case DeviceType.THERMOSTAT:
return <MdThermostatAuto />; return <MdThermostatAuto />;
case 'Mixer': case DeviceType.MIXER:
return <AiOutlineControl />; return <AiOutlineControl />;
case 'Heatpump': case DeviceType.SOLAR:
return <FaSolarPanel />;
case DeviceType.HEATPUMP:
return <GiHeatHaze />; return <GiHeatHaze />;
case 'Switch': case DeviceType.GATEWAY:
return <TiFlowSwitch />;
case 'Connect':
return <VscVmConnect />;
case 'Gateway':
return <AiOutlineGateway />; return <AiOutlineGateway />;
case DeviceType.SWITCH:
return <TiFlowSwitch />;
case DeviceType.CONTROLLER:
case DeviceType.CONNECT:
return <VscVmConnect />;
case DeviceType.ALERT:
return <AiOutlineAlert />;
case DeviceType.PUMP:
return <AiOutlineChrome />;
default: default:
return null; return null;
} }

View File

@@ -5,16 +5,20 @@ import { Tab } from '@mui/material';
import { RouterTabs, useRouterTab, useLayoutTitle } from '../components'; import { RouterTabs, useRouterTab, useLayoutTitle } from '../components';
import { useI18nContext } from '../i18n/i18n-react';
import HelpInformation from './HelpInformation'; import HelpInformation from './HelpInformation';
const Help: FC = () => { const Help: FC = () => {
useLayoutTitle('Help'); const { LL } = useI18nContext();
const { routerTab } = useRouterTab(); const { routerTab } = useRouterTab();
useLayoutTitle(LL.HELP_OF(''));
return ( return (
<> <>
<RouterTabs value={routerTab}> <RouterTabs value={routerTab}>
<Tab value="information" label="EMS-ESP Help" /> <Tab value="information" label={LL.HELP_OF('EMS-ESP')} />
</RouterTabs> </RouterTabs>
<Routes> <Routes>
<Route path="information" element={<HelpInformation />} /> <Route path="information" element={<HelpInformation />} />

View File

@@ -1,32 +1,31 @@
import { FC, useContext } from 'react'; import { FC } from 'react';
import { Typography, Button, Box, List, ListItem, ListItemText, Link, ListItemAvatar } from '@mui/material'; import { Typography, Button, Box, List, ListItem, ListItemText, Link, ListItemAvatar } from '@mui/material';
import { SectionContent, ButtonRow, MessageBox } from '../components'; import { SectionContent } from '../components';
import { AuthenticatedContext } from '../contexts/authentication';
import { useSnackbar } from 'notistack'; import { useSnackbar } from 'notistack';
import CommentIcon from '@mui/icons-material/CommentTwoTone'; import CommentIcon from '@mui/icons-material/CommentTwoTone';
import MenuBookIcon from '@mui/icons-material/MenuBookTwoTone'; import MenuBookIcon from '@mui/icons-material/MenuBookTwoTone';
import GitHubIcon from '@mui/icons-material/GitHub'; import GitHubIcon from '@mui/icons-material/GitHub';
import StarIcon from '@mui/icons-material/Star';
import DownloadIcon from '@mui/icons-material/GetApp'; import DownloadIcon from '@mui/icons-material/GetApp';
import TuneIcon from '@mui/icons-material/Tune'; import EastIcon from '@mui/icons-material/East';
import { extractErrorMessage } from '../utils'; import { extractErrorMessage } from '../utils';
import { useI18nContext } from '../i18n/i18n-react';
import * as EMSESP from './api'; import * as EMSESP from './api';
const HelpInformation: FC = () => { const HelpInformation: FC = () => {
const { enqueueSnackbar } = useSnackbar(); const { LL } = useI18nContext();
const { me } = useContext(AuthenticatedContext); const { enqueueSnackbar } = useSnackbar();
const saveFile = (json: any, endpoint: string) => { const saveFile = (json: any, endpoint: string) => {
const a = document.createElement('a'); const a = document.createElement('a');
const filename = 'emsesp_' + endpoint + '.json'; const filename = 'emsesp_' + endpoint + '.txt';
a.href = URL.createObjectURL( a.href = URL.createObjectURL(
new Blob([JSON.stringify(json, null, 2)], { new Blob([JSON.stringify(json, null, 2)], {
type: 'text/plain' type: 'text/plain'
@@ -36,7 +35,7 @@ const HelpInformation: FC = () => {
document.body.appendChild(a); document.body.appendChild(a);
a.click(); a.click();
document.body.removeChild(a); document.body.removeChild(a);
enqueueSnackbar('File downloaded', { variant: 'info' }); enqueueSnackbar(LL.DOWNLOAD_SUCCESSFUL(), { variant: 'info' });
}; };
const callAPI = async (endpoint: string) => { const callAPI = async (endpoint: string) => {
@@ -47,151 +46,83 @@ const HelpInformation: FC = () => {
id: 0 id: 0
}); });
if (response.status !== 200) { if (response.status !== 200) {
enqueueSnackbar('API call failed', { variant: 'error' }); enqueueSnackbar(LL.PROBLEM_LOADING(), { variant: 'error' });
} else { } else {
saveFile(response.data, endpoint); saveFile(response.data, endpoint);
} }
} catch (error: unknown) { } catch (error) {
enqueueSnackbar(extractErrorMessage(error, 'Problem with downloading'), { variant: 'error' }); enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' });
}
};
const downloadSettings = async () => {
try {
const response = await EMSESP.getSettings();
if (response.status !== 200) {
enqueueSnackbar('Unable to get settings', { variant: 'error' });
} else {
saveFile(response.data, 'settings');
}
} catch (error: unknown) {
enqueueSnackbar(extractErrorMessage(error, 'Problem with downloading'), { variant: 'error' });
}
};
const downloadCustomizations = async () => {
try {
const response = await EMSESP.getCustomizations();
if (response.status !== 200) {
enqueueSnackbar('Unable to get customizations', { variant: 'error' });
} else {
saveFile(response.data, 'customizations');
}
} catch (error: unknown) {
enqueueSnackbar(extractErrorMessage(error, 'Problem with downloading'), { variant: 'error' });
} }
}; };
return ( return (
<SectionContent title="Application Information &amp; Support" titleGutter> <SectionContent title={LL.SUPPORT_INFORMATION()} titleGutter>
<List> <List>
<ListItem> <ListItem>
<ListItemAvatar> <ListItemAvatar>
<TuneIcon /> <MenuBookIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} />
</ListItemAvatar> </ListItemAvatar>
<ListItemText> <ListItemText>
For a help on each of the Application Settings see&nbsp; {LL.HELP_INFORMATION_1()}&nbsp;
<Link <EastIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} />
target="_blank" &nbsp;
href="https://emsesp.github.io/docs/#/Configure-firmware?id=ems-esp-settings"
color="primary"
>
{'Configuring EMS-ESP'}
</Link>
</ListItemText>
</ListItem>
<ListItem>
<ListItemAvatar>
<MenuBookIcon />
</ListItemAvatar>
<ListItemText>
For general information about EMS-ESP visit the online&nbsp;
<Link target="_blank" href="https://emsesp.github.io/docs" color="primary"> <Link target="_blank" href="https://emsesp.github.io/docs" color="primary">
{'Documentation'} {LL.CLICK_HERE()}
</Link> </Link>
</ListItemText> </ListItemText>
</ListItem> </ListItem>
<ListItem> <ListItem>
<ListItemAvatar> <ListItemAvatar>
<CommentIcon /> <CommentIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} />
</ListItemAvatar> </ListItemAvatar>
<ListItemText> <ListItemText>
For live community chat join our&nbsp; {LL.HELP_INFORMATION_2()}&nbsp;
<EastIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} />
&nbsp;
<Link target="_blank" href="https://discord.gg/3J3GgnzpyT" color="primary"> <Link target="_blank" href="https://discord.gg/3J3GgnzpyT" color="primary">
{'Discord'} {LL.CLICK_HERE()}
</Link> </Link>
&nbsp;server
</ListItemText> </ListItemText>
</ListItem> </ListItem>
<ListItem> <ListItem>
<ListItemAvatar> <ListItemAvatar>
<GitHubIcon /> <GitHubIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} />
</ListItemAvatar> </ListItemAvatar>
<ListItemText> <ListItemText>
To report an issue or request a feature, please&nbsp; {LL.HELP_INFORMATION_3()}&nbsp;
<Link component="button" variant="body1" onClick={() => callAPI('info')}> <EastIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} />
download
</Link>
&nbsp;the debug information and include in a new&nbsp;
<Link target="_blank" href="https://github.com/emsesp/EMS-ESP32/issues/new/choose" color="primary"> <Link target="_blank" href="https://github.com/emsesp/EMS-ESP32/issues/new/choose" color="primary">
GitHub issue {LL.CLICK_HERE()}
</Link> </Link>
<br />
<i>({LL.HELP_INFORMATION_4()}</i>&nbsp;&nbsp;
<Button
startIcon={<DownloadIcon />}
size="small"
variant="outlined"
color="primary"
onClick={() => callAPI('info')}
>
{LL.SUPPORT_INFO()}
</Button>
&nbsp;)
</ListItemText> </ListItemText>
</ListItem> </ListItem>
</List> </List>
{me.admin && ( <Box border={1} p={1} mt={4} color="orange">
<> <Typography align="center" variant="subtitle1" color="orange">
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary"> <b>{LL.HELP_INFORMATION_5()}</b>
Download Settings </Typography>
</Typography> <Typography align="center">
<Box color="warning.main"> <Link target="_blank" href="https://github.com/emsesp/EMS-ESP32" color="primary">
<Typography variant="body2"> {'github.com/emsesp/EMS-ESP32'}
Export the application settings and any customizations to a JSON file. These files can later be uploaded
via System&rarr;Upload.
</Typography>
</Box>
<Box sx={{ display: 'flex' }}>
<ButtonRow>
<Button
startIcon={<DownloadIcon />}
variant="outlined"
color="primary"
onClick={() => downloadSettings()}
>
settings
</Button>
<Button
startIcon={<DownloadIcon />}
variant="outlined"
color="primary"
onClick={() => downloadCustomizations()}
>
customizations
</Button>
</ButtonRow>
</Box>
<MessageBox
my={2}
level="warning"
message="Be careful when sharing your Settings as the file contains passwords and other sensitive system
information!"
/>
</>
)}
<Box bgcolor="secondary.info" border={1} p={1} mt={4}>
<Typography align="center" variant="h6">
EMS-ESP is a free and open-source project.
<br></br>Please consider supporting us by giving it a&nbsp;
<StarIcon style={{ fontSize: 16, color: '#fdff3a', verticalAlign: 'middle' }} /> on&nbsp;
<Link href="https://github.com/emsesp/EMS-ESP32" color="primary">
{'GitHub'}
</Link> </Link>
&nbsp;! </Typography>
<Typography color="white" align="center">
@proddy @MichaelDvP
</Typography> </Typography>
</Box> </Box>
</SectionContent> </SectionContent>

View File

@@ -0,0 +1,43 @@
import { FC } from 'react';
import { SvgIconProps } from '@mui/material';
import EditOutlinedIcon from '@mui/icons-material/EditOutlined';
import EditOffOutlinedIcon from '@mui/icons-material/EditOffOutlined';
import StarIcon from '@mui/icons-material/Star';
import StarOutlineIcon from '@mui/icons-material/StarOutline';
import VisibilityOffOutlinedIcon from '@mui/icons-material/VisibilityOffOutlined';
import VisibilityOutlinedIcon from '@mui/icons-material/VisibilityOutlined';
import CommentsDisabledOutlinedIcon from '@mui/icons-material/CommentsDisabledOutlined';
import InsertCommentOutlinedIcon from '@mui/icons-material/InsertCommentOutlined';
import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
import DeleteForeverIcon from '@mui/icons-material/DeleteForever';
type OptionType = 'deleted' | 'readonly' | 'web_exclude' | 'api_mqtt_exclude' | 'favorite';
const OPTION_ICONS: { [type in OptionType]: [React.ComponentType<SvgIconProps>, React.ComponentType<SvgIconProps>] } = {
deleted: [DeleteForeverIcon, DeleteOutlineIcon],
readonly: [EditOffOutlinedIcon, EditOutlinedIcon],
web_exclude: [VisibilityOffOutlinedIcon, VisibilityOutlinedIcon],
api_mqtt_exclude: [CommentsDisabledOutlinedIcon, InsertCommentOutlinedIcon],
favorite: [StarIcon, StarOutlineIcon]
};
interface OptionIconProps {
type: OptionType;
isSet: boolean;
}
const OptionIcon: FC<OptionIconProps> = ({ type, isSet }) => {
const Icon = OPTION_ICONS[type][isSet ? 0 : 1];
return isSet ? (
<Icon color="primary" sx={{ fontSize: 14, verticalAlign: 'middle' }} />
) : (
<Icon sx={{ fontSize: 14, verticalAlign: 'middle' }} />
);
};
export default OptionIcon;

View File

@@ -6,6 +6,8 @@ import { AuthenticatedContext } from '../contexts/authentication';
import { PROJECT_PATH } from '../api/env'; import { PROJECT_PATH } from '../api/env';
import { useI18nContext } from '../i18n/i18n-react';
import TuneIcon from '@mui/icons-material/Tune'; import TuneIcon from '@mui/icons-material/Tune';
import DashboardIcon from '@mui/icons-material/Dashboard'; import DashboardIcon from '@mui/icons-material/Dashboard';
import LayoutMenuItem from '../components/layout/LayoutMenuItem'; import LayoutMenuItem from '../components/layout/LayoutMenuItem';
@@ -13,17 +15,18 @@ import InfoIcon from '@mui/icons-material/Info';
const ProjectMenu: FC = () => { const ProjectMenu: FC = () => {
const authenticatedContext = useContext(AuthenticatedContext); const authenticatedContext = useContext(AuthenticatedContext);
const { LL } = useI18nContext();
return ( return (
<List> <List>
<LayoutMenuItem icon={DashboardIcon} label="Dashboard" to={`/${PROJECT_PATH}/dashboard`} /> <LayoutMenuItem icon={DashboardIcon} label={LL.DASHBOARD()} to={`/${PROJECT_PATH}/dashboard`} />
<LayoutMenuItem <LayoutMenuItem
icon={TuneIcon} icon={TuneIcon}
label="Settings" label={LL.SETTINGS_OF('')}
to={`/${PROJECT_PATH}/settings`} to={`/${PROJECT_PATH}/settings`}
disabled={!authenticatedContext.me.admin} disabled={!authenticatedContext.me.admin}
/> />
<LayoutMenuItem icon={InfoIcon} label="Help" to={`/${PROJECT_PATH}/help`} /> <LayoutMenuItem icon={InfoIcon} label={LL.HELP_OF('')} to={`/${PROJECT_PATH}/help`} />
</List> </List>
); );
}; };

View File

@@ -5,18 +5,22 @@ import { Tab } from '@mui/material';
import { RouterTabs, useRouterTab, useLayoutTitle } from '../components'; import { RouterTabs, useRouterTab, useLayoutTitle } from '../components';
import { useI18nContext } from '../i18n/i18n-react';
import SettingsApplication from './SettingsApplication'; import SettingsApplication from './SettingsApplication';
import SettingsCustomization from './SettingsCustomization'; import SettingsCustomization from './SettingsCustomization';
const Settings: FC = () => { const Settings: FC = () => {
useLayoutTitle('Settings'); const { LL } = useI18nContext();
const { routerTab } = useRouterTab(); const { routerTab } = useRouterTab();
useLayoutTitle(LL.SETTINGS_OF(''));
return ( return (
<> <>
<RouterTabs value={routerTab}> <RouterTabs value={routerTab}>
<Tab value="application" label="Application Settings" /> <Tab value="application" label={LL.APPLICATION_SETTINGS()} />
<Tab value="customization" label="Customization" /> <Tab value="customization" label={LL.CUSTOMIZATION()} />
</RouterTabs> </RouterTabs>
<Routes> <Routes>
<Route path="application" element={<SettingsApplication />} /> <Route path="application" element={<SettingsApplication />} />

View File

@@ -3,7 +3,7 @@ import { ValidateFieldsError } from 'async-validator';
import { useSnackbar } from 'notistack'; import { useSnackbar } from 'notistack';
import { Box, Button, Checkbox, MenuItem, Grid, Typography, Divider } from '@mui/material'; import { Box, Button, Checkbox, MenuItem, Grid, Typography, Divider, InputAdornment } from '@mui/material';
import SaveIcon from '@mui/icons-material/Save'; import SaveIcon from '@mui/icons-material/Save';
import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew'; import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
@@ -24,6 +24,9 @@ import { numberValue, extractErrorMessage, updateValue, useRest } from '../utils
import * as EMSESP from './api'; import * as EMSESP from './api';
import { Settings, BOARD_PROFILES } from './types'; import { Settings, BOARD_PROFILES } from './types';
import { useI18nContext } from '../i18n/i18n-react';
import RestartMonitor from '../framework/system/RestartMonitor';
export function boardProfileSelectItems() { export function boardProfileSelectItems() {
return Object.keys(BOARD_PROFILES).map((code) => ( return Object.keys(BOARD_PROFILES).map((code) => (
<MenuItem key={code} value={code}> <MenuItem key={code} value={code}>
@@ -37,6 +40,9 @@ const SettingsApplication: FC = () => {
read: EMSESP.readSettings, read: EMSESP.readSettings,
update: EMSESP.writeSettings update: EMSESP.writeSettings
}); });
const [restarting, setRestarting] = useState<boolean>();
const { LL } = useI18nContext();
const { enqueueSnackbar } = useSnackbar(); const { enqueueSnackbar } = useSnackbar();
@@ -64,8 +70,8 @@ const SettingsApplication: FC = () => {
eth_clock_mode: response.data.eth_clock_mode eth_clock_mode: response.data.eth_clock_mode
}); });
} }
} catch (error: unknown) { } catch (error) {
enqueueSnackbar(extractErrorMessage(error, 'Problem fetching board profile'), { variant: 'error' }); enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
} finally { } finally {
setProcessingBoard(false); setProcessingBoard(false);
} }
@@ -102,28 +108,26 @@ const SettingsApplication: FC = () => {
validateAndSubmit(); validateAndSubmit();
try { try {
await EMSESP.restart(); await EMSESP.restart();
enqueueSnackbar('EMS-ESP is restarting...', { variant: 'info' }); setRestarting(true);
} catch (error: unknown) { } catch (error) {
enqueueSnackbar(extractErrorMessage(error, 'Problem restarting device'), { variant: 'error' }); enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
} }
}; };
return ( return (
<> <>
<Typography sx={{ pt: 2 }} variant="h6" color="primary"> <Typography sx={{ pt: 2 }} variant="h6" color="primary">
Interface Board Profile {LL.INTERFACE_BOARD_PROFILE()}
</Typography> </Typography>
<Box color="warning.main"> <Box color="warning.main">
<Typography variant="body2"> <Typography variant="body2">{LL.BOARD_PROFILE_TEXT()}</Typography>
Select a pre-configured interface board profile from the list below or choose "Custom" to configure your own
hardware settings.
</Typography>
</Box> </Box>
<ValidatedTextField <ValidatedTextField
name="board_profile" name="board_profile"
label="Board Profile" label={LL.BOARD_PROFILE()}
value={data.board_profile} value={data.board_profile}
disabled={processingBoard} disabled={processingBoard}
fullWidth
variant="outlined" variant="outlined"
onChange={changeBoardProfile} onChange={changeBoardProfile}
margin="normal" margin="normal"
@@ -132,17 +136,24 @@ const SettingsApplication: FC = () => {
{boardProfileSelectItems()} {boardProfileSelectItems()}
<Divider /> <Divider />
<MenuItem key={'CUSTOM'} value={'CUSTOM'}> <MenuItem key={'CUSTOM'} value={'CUSTOM'}>
Custom&hellip; {LL.CUSTOM()}&hellip;
</MenuItem> </MenuItem>
</ValidatedTextField> </ValidatedTextField>
{data.board_profile === 'CUSTOM' && ( {data.board_profile === 'CUSTOM' && (
<> <>
<Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start"> <Grid
<Grid item xs={4}> container
spacing={1}
sx={{ pt: 1 }}
direction="row"
justifyContent="flex-start"
alignItems="flex-start"
>
<Grid item xs={6} sm={4}>
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="rx_gpio" name="rx_gpio"
label="Rx GPIO" label={LL.GPIO_OF('Rx')}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={numberValue(data.rx_gpio)} value={numberValue(data.rx_gpio)}
@@ -152,11 +163,11 @@ const SettingsApplication: FC = () => {
disabled={saving} disabled={saving}
/> />
</Grid> </Grid>
<Grid item xs={4}> <Grid item xs={6} sm={4}>
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="tx_gpio" name="tx_gpio"
label="Tx GPIO" label={LL.GPIO_OF('Tx')}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={numberValue(data.tx_gpio)} value={numberValue(data.tx_gpio)}
@@ -166,11 +177,11 @@ const SettingsApplication: FC = () => {
disabled={saving} disabled={saving}
/> />
</Grid> </Grid>
<Grid item xs={4}> <Grid item xs={6} sm={4}>
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="pbutton_gpio" name="pbutton_gpio"
label="Button GPIO" label={LL.GPIO_OF(LL.BUTTON())}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={numberValue(data.pbutton_gpio)} value={numberValue(data.pbutton_gpio)}
@@ -180,11 +191,11 @@ const SettingsApplication: FC = () => {
disabled={saving} disabled={saving}
/> />
</Grid> </Grid>
<Grid item> <Grid item xs={6} sm={4}>
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="dallas_gpio" name="dallas_gpio"
label="Temperature GPIO (0=disabled)" label={LL.GPIO_OF(LL.TEMPERATURE()) + ' (0=' + LL.DISABLED(1) + ')'}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={numberValue(data.dallas_gpio)} value={numberValue(data.dallas_gpio)}
@@ -194,11 +205,11 @@ const SettingsApplication: FC = () => {
disabled={saving} disabled={saving}
/> />
</Grid> </Grid>
<Grid item> <Grid item xs={6} sm={4}>
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="led_gpio" name="led_gpio"
label="LED GPIO (0=disabled)" label={LL.GPIO_OF('LED') + ' (0=' + LL.DISABLED(1) + ')'}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={numberValue(data.led_gpio)} value={numberValue(data.led_gpio)}
@@ -208,30 +219,37 @@ const SettingsApplication: FC = () => {
disabled={saving} disabled={saving}
/> />
</Grid> </Grid>
</Grid> <Grid item xs={6} sm={4}>
<Grid item xs={4}> <ValidatedTextField
<ValidatedTextField name="phy_type"
name="phy_type" label={LL.PHY_TYPE()}
label="Eth PHY Type" disabled={saving}
disabled={saving} value={data.phy_type}
value={data.phy_type} fullWidth
fullWidth variant="outlined"
variant="outlined" onChange={updateFormValue}
onChange={updateFormValue} margin="normal"
margin="normal" select
select >
> <MenuItem value={0}>{LL.DISABLED(1)}</MenuItem>
<MenuItem value={0}>No Ethernet Module</MenuItem> <MenuItem value={1}>LAN8720</MenuItem>
<MenuItem value={1}>LAN8720</MenuItem> <MenuItem value={2}>TLK110</MenuItem>
<MenuItem value={2}>TLK110</MenuItem> </ValidatedTextField>
</ValidatedTextField> </Grid>
</Grid> </Grid>
{data.phy_type !== 0 && ( {data.phy_type !== 0 && (
<Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start"> <Grid
<Grid item> container
spacing={1}
sx={{ pt: 1 }}
direction="row"
justifyContent="flex-start"
alignItems="flex-start"
>
<Grid item xs={6} sm={4}>
<ValidatedTextField <ValidatedTextField
name="eth_power" name="eth_power"
label="Eth Power GPIO (-1=disabled)" label={LL.GPIO_OF('PHY Power') + ' (-1=' + LL.DISABLED(1) + ')'}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={numberValue(data.eth_power)} value={numberValue(data.eth_power)}
@@ -241,10 +259,10 @@ const SettingsApplication: FC = () => {
disabled={saving} disabled={saving}
/> />
</Grid> </Grid>
<Grid item> <Grid item xs={6} sm={4}>
<ValidatedTextField <ValidatedTextField
name="eth_phy_addr" name="eth_phy_addr"
label="Eth I²C-address" label={LL.ADDRESS_OF('PHY I²C')}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={numberValue(data.eth_phy_addr)} value={numberValue(data.eth_phy_addr)}
@@ -254,10 +272,10 @@ const SettingsApplication: FC = () => {
disabled={saving} disabled={saving}
/> />
</Grid> </Grid>
<Grid item> <Grid item xs={6} sm={4}>
<ValidatedTextField <ValidatedTextField
name="eth_clock_mode" name="eth_clock_mode"
label="Eth Clock Mode" label="PHY Clk"
disabled={saving} disabled={saving}
value={data.eth_clock_mode} value={data.eth_clock_mode}
fullWidth fullWidth
@@ -276,14 +294,14 @@ const SettingsApplication: FC = () => {
)} )}
</> </>
)} )}
<Typography variant="h6" color="primary"> <Typography sx={{ pt: 2 }} variant="h6" color="primary">
EMS Bus Settings {LL.SETTINGS_OF(LL.EMS_BUS(0))}
</Typography> </Typography>
<Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start"> <Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start">
<Grid item xs={6}> <Grid item xs={6}>
<ValidatedTextField <ValidatedTextField
name="tx_mode" name="tx_mode"
label="Tx Mode" label={LL.TX_MODE()}
disabled={saving} disabled={saving}
value={data.tx_mode} value={data.tx_mode}
fullWidth fullWidth
@@ -295,13 +313,13 @@ const SettingsApplication: FC = () => {
<MenuItem value={1}>EMS</MenuItem> <MenuItem value={1}>EMS</MenuItem>
<MenuItem value={2}>EMS+</MenuItem> <MenuItem value={2}>EMS+</MenuItem>
<MenuItem value={3}>HT3</MenuItem> <MenuItem value={3}>HT3</MenuItem>
<MenuItem value={4}>Hardware</MenuItem> <MenuItem value={4}>{LL.HARDWARE()}</MenuItem>
</ValidatedTextField> </ValidatedTextField>
</Grid> </Grid>
<Grid item xs={6}> <Grid item xs={6}>
<ValidatedTextField <ValidatedTextField
name="ems_bus_id" name="ems_bus_id"
label="Bus ID" label={LL.ID_OF(LL.EMS_BUS(1))}
disabled={saving} disabled={saving}
value={data.ems_bus_id} value={data.ems_bus_id}
fullWidth fullWidth
@@ -310,102 +328,148 @@ const SettingsApplication: FC = () => {
margin="normal" margin="normal"
select select
> >
<MenuItem value={0x0a}>Terminal (0x0A)</MenuItem>
<MenuItem value={0x0b}>Service Key (0x0B)</MenuItem> <MenuItem value={0x0b}>Service Key (0x0B)</MenuItem>
<MenuItem value={0x0d}>Modem (0x0D)</MenuItem> <MenuItem value={0x0d}>Modem (0x0D)</MenuItem>
<MenuItem value={0x0a}>Terminal (0x0A)</MenuItem> <MenuItem value={0x0e}>Converter (0x0E)</MenuItem>
<MenuItem value={0x0f}>Time Module (0x0F)</MenuItem> <MenuItem value={0x0f}>Time Module (0x0F)</MenuItem>
<MenuItem value={0x12}>Alarm Module (0x12)</MenuItem> <MenuItem value={0x48}>Gateway 1 (0x48)</MenuItem>
<MenuItem value={0x49}>Gateway 2 (0x49)</MenuItem>
<MenuItem value={0x4a}>Gateway 3 (0x4A)</MenuItem>
<MenuItem value={0x4b}>Gateway 4 (0x4B)</MenuItem>
<MenuItem value={0x4c}>Gateway 5 (0x4C)</MenuItem>
<MenuItem value={0x4d}>Gateway 7 (0x4D)</MenuItem>
</ValidatedTextField> </ValidatedTextField>
</Grid> </Grid>
</Grid> </Grid>
<Typography sx={{ pt: 2 }} variant="h6" color="primary"> <Typography sx={{ pt: 2 }} variant="h6" color="primary">
General Options {LL.GENERAL_OPTIONS()}
</Typography> </Typography>
<Box
sx={{
'& .MuiTextField-root': { width: '25ch' }
}}
>
<ValidatedTextField
name="locale"
label={LL.LANGUAGE_ENTITIES()}
disabled={saving}
value={data.locale}
variant="outlined"
onChange={updateFormValue}
margin="normal"
size="small"
select
>
<MenuItem value="en">English (EN)</MenuItem>
<Divider />
<MenuItem value="de">Deutsch (DE)</MenuItem>
<MenuItem value="fr">Français (FR)</MenuItem>
<MenuItem value="nl">Nederlands (NL)</MenuItem>
<MenuItem value="no">Norsk (NO)</MenuItem>
<MenuItem value="pl">Polski (PL)</MenuItem>
<MenuItem value="sv">Svenska (SV)</MenuItem>
</ValidatedTextField>
</Box>
{data.led_gpio !== 0 && ( {data.led_gpio !== 0 && (
<BlockFormControlLabel <BlockFormControlLabel
control={<Checkbox checked={data.hide_led} onChange={updateFormValue} name="hide_led" />} control={<Checkbox checked={data.hide_led} onChange={updateFormValue} name="hide_led" />}
label="Hide LED" label={LL.HIDE_LED()}
disabled={saving} disabled={saving}
/> />
)} )}
<BlockFormControlLabel <BlockFormControlLabel
control={<Checkbox checked={data.telnet_enabled} onChange={updateFormValue} name="telnet_enabled" />} control={<Checkbox checked={data.telnet_enabled} onChange={updateFormValue} name="telnet_enabled" />}
label="Enable Telnet Console" label={LL.ENABLE_TELNET()}
disabled={saving} disabled={saving}
/> />
<BlockFormControlLabel <BlockFormControlLabel
control={<Checkbox checked={data.analog_enabled} onChange={updateFormValue} name="analog_enabled" />} control={<Checkbox checked={data.analog_enabled} onChange={updateFormValue} name="analog_enabled" />}
label="Enable Analog Sensors" label={LL.ENABLE_ANALOG()}
disabled={saving} disabled={saving}
/> />
<BlockFormControlLabel <BlockFormControlLabel
control={<Checkbox checked={data.fahrenheit} onChange={updateFormValue} name="fahrenheit" />} control={<Checkbox checked={data.fahrenheit} onChange={updateFormValue} name="fahrenheit" />}
label="Convert temperature values to Fahrenheit" label={LL.CONVERT_FAHRENHEIT()}
disabled={saving} disabled={saving}
/> />
<BlockFormControlLabel <BlockFormControlLabel
control={<Checkbox checked={data.notoken_api} onChange={updateFormValue} name="notoken_api" />} control={<Checkbox checked={data.notoken_api} onChange={updateFormValue} name="notoken_api" />}
label="Bypass Access Token authorization on API calls" label={LL.BYPASS_TOKEN()}
disabled={saving} disabled={saving}
/> />
<BlockFormControlLabel <BlockFormControlLabel
control={<Checkbox checked={data.readonly_mode} onChange={updateFormValue} name="readonly_mode" />} control={<Checkbox checked={data.readonly_mode} onChange={updateFormValue} name="readonly_mode" />}
label="Enable Read only mode (blocks all outgoing EMS Tx write commands)" label={LL.READONLY()}
disabled={saving} disabled={saving}
/> />
<BlockFormControlLabel <BlockFormControlLabel
control={<Checkbox checked={data.low_clock} onChange={updateFormValue} name="low_clock" />} control={<Checkbox checked={data.low_clock} onChange={updateFormValue} name="low_clock" />}
label="Underclock CPU speed" label={LL.UNDERCLOCK_CPU()}
disabled={saving}
/>
<BlockFormControlLabel
control={<Checkbox checked={data.boiler_heatingoff} onChange={updateFormValue} name="boiler_heatingoff" />}
label={LL.HEATINGOFF()}
disabled={saving} disabled={saving}
/> />
<Grid container spacing={0} direction="row" justifyContent="flex-start" alignItems="flex-start"> <Grid container spacing={0} direction="row" justifyContent="flex-start" alignItems="flex-start">
<BlockFormControlLabel <BlockFormControlLabel
control={<Checkbox checked={data.shower_timer} onChange={updateFormValue} name="shower_timer" />} control={<Checkbox checked={data.shower_timer} onChange={updateFormValue} name="shower_timer" />}
label="Enable Shower Timer" label={LL.ENABLE_SHOWER_TIMER()}
disabled={saving} disabled={saving}
/> />
<BlockFormControlLabel <BlockFormControlLabel
sx={{ pb: 2 }}
control={<Checkbox checked={data.shower_alert} onChange={updateFormValue} name="shower_alert" />} control={<Checkbox checked={data.shower_alert} onChange={updateFormValue} name="shower_alert" />}
label="Enable Shower Alert" label={LL.ENABLE_SHOWER_ALERT()}
disabled={!data.shower_timer} disabled={!data.shower_timer}
/> />
{data.shower_alert && ( {data.shower_alert && (
<> <>
<Grid item xs={2}> <Grid item sx={{ pr: 1, pb: 2 }}>
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="shower_alert_trigger" name="shower_alert_trigger"
label="Trigger Time (minutes)" label={LL.TRIGGER_TIME()}
InputProps={{
endAdornment: <InputAdornment position="end">{LL.MINUTES()}</InputAdornment>
}}
variant="outlined" variant="outlined"
value={data.shower_alert_trigger} value={data.shower_alert_trigger}
type="number" type="number"
onChange={updateFormValue} onChange={updateFormValue}
size="small"
disabled={!data.shower_timer} disabled={!data.shower_timer}
/> />
</Grid> </Grid>
<Grid item xs={2}> <Grid item sx={{ pb: 3 }}>
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="shower_alert_coldshot" name="shower_alert_coldshot"
label="Cold Shot Time (seconds)" label={LL.COLD_SHOT_DURATION()}
InputProps={{
endAdornment: <InputAdornment position="end">{LL.SECONDS()}</InputAdornment>
}}
variant="outlined" variant="outlined"
value={data.shower_alert_coldshot} value={data.shower_alert_coldshot}
type="number" type="number"
onChange={updateFormValue} onChange={updateFormValue}
size="small"
disabled={!data.shower_timer} disabled={!data.shower_timer}
/> />
</Grid> </Grid>
</> </>
)} )}
</Grid> </Grid>
<Typography sx={{ pt: 2 }} variant="h6" color="primary"> <Typography variant="h6" color="primary">
Formatting Options {LL.FORMATTING_OPTIONS()}
</Typography> </Typography>
<Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start"> <Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start">
<Grid item xs={4}> <Grid item xs={6} sm={4}>
<ValidatedTextField <ValidatedTextField
name="bool_dashboard" name="bool_dashboard"
label="Boolean Format Dashboard" label={LL.BOOLEAN_FORMAT_DASHBOARD()}
value={data.bool_dashboard} value={data.bool_dashboard}
fullWidth fullWidth
variant="outlined" variant="outlined"
@@ -413,16 +477,16 @@ const SettingsApplication: FC = () => {
margin="normal" margin="normal"
select select
> >
<MenuItem value={1}>on/off</MenuItem> <MenuItem value={1}>{LL.ONOFF()}</MenuItem>
<MenuItem value={2}>ON/OFF</MenuItem> <MenuItem value={2}>{LL.ONOFF_CAP()}</MenuItem>
<MenuItem value={3}>true/false</MenuItem> <MenuItem value={3}>true/false</MenuItem>
<MenuItem value={5}>1/0</MenuItem> <MenuItem value={5}>1/0</MenuItem>
</ValidatedTextField> </ValidatedTextField>
</Grid> </Grid>
<Grid item xs={4}> <Grid item xs={6} sm={4}>
<ValidatedTextField <ValidatedTextField
name="bool_format" name="bool_format"
label="Boolean Format API/MQTT" label={LL.BOOLEAN_FORMAT_API()}
value={data.bool_format} value={data.bool_format}
fullWidth fullWidth
variant="outlined" variant="outlined"
@@ -430,18 +494,18 @@ const SettingsApplication: FC = () => {
margin="normal" margin="normal"
select select
> >
<MenuItem value={1}>"on"/"off"</MenuItem> <MenuItem value={1}>{LL.ONOFF()}</MenuItem>
<MenuItem value={2}>"ON"/"OFF"</MenuItem> <MenuItem value={2}>{LL.ONOFF_CAP()}</MenuItem>
<MenuItem value={3}>"true"/"false"</MenuItem> <MenuItem value={3}>"true"/"false"</MenuItem>
<MenuItem value={4}>true/false</MenuItem> <MenuItem value={4}>true/false</MenuItem>
<MenuItem value={5}>"1"/"0"</MenuItem> <MenuItem value={5}>"1"/"0"</MenuItem>
<MenuItem value={6}>1/0</MenuItem> <MenuItem value={6}>1/0</MenuItem>
</ValidatedTextField> </ValidatedTextField>
</Grid> </Grid>
<Grid item xs={4}> <Grid item xs={6} sm={4}>
<ValidatedTextField <ValidatedTextField
name="enum_format" name="enum_format"
label="Enum Format API/MQTT" label={LL.ENUM_FORMAT()}
value={data.enum_format} value={data.enum_format}
fullWidth fullWidth
variant="outlined" variant="outlined"
@@ -449,29 +513,29 @@ const SettingsApplication: FC = () => {
margin="normal" margin="normal"
select select
> >
<MenuItem value={1}>Value</MenuItem> <MenuItem value={1}>{LL.VALUE(1)}</MenuItem>
<MenuItem value={2}>Index</MenuItem> <MenuItem value={2}>{LL.INDEX()}</MenuItem>
</ValidatedTextField> </ValidatedTextField>
</Grid> </Grid>
</Grid> </Grid>
{data.dallas_gpio !== 0 && ( {data.dallas_gpio !== 0 && (
<> <>
<Typography sx={{ pt: 2 }} variant="h6" color="primary"> <Typography sx={{ pt: 2 }} variant="h6" color="primary">
Temperature Sensors {LL.TEMP_SENSORS()}
</Typography> </Typography>
<BlockFormControlLabel <BlockFormControlLabel
control={<Checkbox checked={data.dallas_parasite} onChange={updateFormValue} name="dallas_parasite" />} control={<Checkbox checked={data.dallas_parasite} onChange={updateFormValue} name="dallas_parasite" />}
label="Enable parasite power" label={LL.ENABLE_PARASITE()}
disabled={saving} disabled={saving}
/> />
</> </>
)} )}
<Typography sx={{ pt: 2 }} variant="h6" color="primary"> <Typography sx={{ pt: 2 }} variant="h6" color="primary">
Logging {LL.LOGGING()}
</Typography> </Typography>
<BlockFormControlLabel <BlockFormControlLabel
control={<Checkbox checked={data.trace_raw} onChange={updateFormValue} name="trace_raw" />} control={<Checkbox checked={data.trace_raw} onChange={updateFormValue} name="trace_raw" />}
label="Log EMS telegrams in hexadecimal" label={LL.LOG_HEX()}
disabled={saving} disabled={saving}
/> />
<BlockFormControlLabel <BlockFormControlLabel
@@ -483,11 +547,11 @@ const SettingsApplication: FC = () => {
disabled={saving} disabled={saving}
/> />
} }
label="Enable Syslog" label={LL.ENABLE_SYSLOG()}
/> />
{data.syslog_enabled && ( {data.syslog_enabled && (
<Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start"> <Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start">
<Grid item xs={5}> <Grid item xs={4}>
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="syslog_host" name="syslog_host"
@@ -500,7 +564,7 @@ const SettingsApplication: FC = () => {
disabled={saving} disabled={saving}
/> />
</Grid> </Grid>
<Grid item xs={6}> <Grid item xs={4}>
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="syslog_port" name="syslog_port"
@@ -514,10 +578,10 @@ const SettingsApplication: FC = () => {
disabled={saving} disabled={saving}
/> />
</Grid> </Grid>
<Grid item xs={5}> <Grid item xs={4}>
<ValidatedTextField <ValidatedTextField
name="syslog_level" name="syslog_level"
label="Log Level" label={LL.LOG_LEVEL()}
value={data.syslog_level} value={data.syslog_level}
fullWidth fullWidth
variant="outlined" variant="outlined"
@@ -534,11 +598,14 @@ const SettingsApplication: FC = () => {
<MenuItem value={9}>ALL</MenuItem> <MenuItem value={9}>ALL</MenuItem>
</ValidatedTextField> </ValidatedTextField>
</Grid> </Grid>
<Grid item xs={6}> <Grid item xs={4}>
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="syslog_mark_interval" name="syslog_mark_interval"
label="Mark Interval (seconds, 0=off)" label={LL.MARK_INTERVAL()}
InputProps={{
endAdornment: <InputAdornment position="end">{LL.SECONDS()}</InputAdornment>
}}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.syslog_mark_interval} value={data.syslog_mark_interval}
@@ -551,9 +618,9 @@ const SettingsApplication: FC = () => {
</Grid> </Grid>
)} )}
{restartNeeded && ( {restartNeeded && (
<MessageBox my={2} level="warning" message="EMS-ESP needs to be restarted to apply changed system settings"> <MessageBox my={2} level="warning" message={LL.RESTART_TEXT()}>
<Button startIcon={<PowerSettingsNewIcon />} variant="contained" color="error" onClick={restart}> <Button startIcon={<PowerSettingsNewIcon />} variant="contained" color="error" onClick={restart}>
Restart {LL.RESTART()}
</Button> </Button>
</MessageBox> </MessageBox>
)} )}
@@ -567,7 +634,7 @@ const SettingsApplication: FC = () => {
type="submit" type="submit"
onClick={validateAndSubmit} onClick={validateAndSubmit}
> >
Save {LL.SAVE()}
</Button> </Button>
</ButtonRow> </ButtonRow>
)} )}
@@ -576,8 +643,8 @@ const SettingsApplication: FC = () => {
}; };
return ( return (
<SectionContent title="Application Settings" titleGutter> <SectionContent title={LL.APPLICATION_SETTINGS()} titleGutter>
{content()} {restarting ? <RestartMonitor /> : content()}
</SectionContent> </SectionContent>
); );
}; };

View File

@@ -13,174 +13,157 @@ import {
ToggleButtonGroup, ToggleButtonGroup,
Tooltip, Tooltip,
Grid, Grid,
TextField TextField,
Link
} from '@mui/material'; } from '@mui/material';
import { MessageBox } from '../components';
import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
import { Table } from '@table-library/react-table-library/table'; import { Table } from '@table-library/react-table-library/table';
import { useTheme } from '@table-library/react-table-library/theme'; import { useTheme } from '@table-library/react-table-library/theme';
import { useSort, SortToggleType } from '@table-library/react-table-library/sort';
import { Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table'; import { Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table';
import { useSnackbar } from 'notistack'; import { useSnackbar } from 'notistack';
import SaveIcon from '@mui/icons-material/Save'; import SaveIcon from '@mui/icons-material/Save';
import CancelIcon from '@mui/icons-material/Cancel'; import CancelIcon from '@mui/icons-material/Cancel';
import EditOffOutlinedIcon from '@mui/icons-material/EditOffOutlined';
import StarIcon from '@mui/icons-material/Star';
import VisibilityOffOutlinedIcon from '@mui/icons-material/VisibilityOffOutlined';
import CommentsDisabledOutlinedIcon from '@mui/icons-material/CommentsDisabledOutlined';
import SettingsBackupRestoreIcon from '@mui/icons-material/SettingsBackupRestore'; import SettingsBackupRestoreIcon from '@mui/icons-material/SettingsBackupRestore';
import KeyboardArrowUpOutlinedIcon from '@mui/icons-material/KeyboardArrowUpOutlined';
import KeyboardArrowDownOutlinedIcon from '@mui/icons-material/KeyboardArrowDownOutlined';
import UnfoldMoreOutlinedIcon from '@mui/icons-material/UnfoldMoreOutlined';
import SearchIcon from '@mui/icons-material/Search'; import SearchIcon from '@mui/icons-material/Search';
import FilterListIcon from '@mui/icons-material/FilterList'; import FilterListIcon from '@mui/icons-material/FilterList';
import OptionIcon from './OptionIcon';
import { ButtonRow, FormLoader, ValidatedTextField, SectionContent } from '../components'; import { ButtonRow, FormLoader, ValidatedTextField, SectionContent } from '../components';
import * as EMSESP from './api'; import * as EMSESP from './api';
import { extractErrorMessage } from '../utils'; import { extractErrorMessage, updateValue } from '../utils';
import { DeviceShort, Devices, DeviceEntity, DeviceEntityMask } from './types'; import { DeviceShort, Devices, DeviceEntity, DeviceEntityMask } from './types';
import { useI18nContext } from '../i18n/i18n-react';
import RestartMonitor from '../framework/system/RestartMonitor';
export const APIURL = window.location.origin + '/api/';
const SettingsCustomization: FC = () => { const SettingsCustomization: FC = () => {
const { LL } = useI18nContext();
const [restarting, setRestarting] = useState<boolean>(false);
const [restartNeeded, setRestartNeeded] = useState<boolean>(false);
const { enqueueSnackbar } = useSnackbar(); const { enqueueSnackbar } = useSnackbar();
const [deviceEntities, setDeviceEntities] = useState<DeviceEntity[]>([{ id: '', v: 0, s: '', m: 0, w: false }]); const emptyDeviceEntity = { id: '', v: 0, n: '', cn: '', m: 0, w: false };
const [deviceEntities, setDeviceEntities] = useState<DeviceEntity[]>([emptyDeviceEntity]);
const [devices, setDevices] = useState<Devices>(); const [devices, setDevices] = useState<Devices>();
const [errorMessage, setErrorMessage] = useState<string>(); const [errorMessage, setErrorMessage] = useState<string>();
const [selectedDevice, setSelectedDevice] = useState<number>(0); const [selectedDevice, setSelectedDevice] = useState<number>(-1);
const [confirmReset, setConfirmReset] = useState<boolean>(false); const [confirmReset, setConfirmReset] = useState<boolean>(false);
const [selectedFilters, setSelectedFilters] = useState<number>(0); const [selectedFilters, setSelectedFilters] = useState<number>(0);
const [search, setSearch] = useState(''); const [search, setSearch] = useState('');
const [deviceEntity, setDeviceEntity] = useState<DeviceEntity>();
// eslint-disable-next-line // eslint-disable-next-line
const [masks, setMasks] = useState(() => ['']); const [masks, setMasks] = useState(() => ['']);
const entities_theme = useTheme({ const entities_theme = useTheme({
Table: `
--data-table-library_grid-template-columns: 150px repeat(1, minmax(80px, 1fr)) 45px 45px 120px;
`,
BaseRow: ` BaseRow: `
font-size: 14px; font-size: 14px;
color: white; .td {
height: 32px; height: 32px;
min-height: 32px; }
`,
BaseCell: `
&:nth-of-type(3) {
text-align: right;
}
&:nth-of-type(4) {
text-align: right;
}
&:last-of-type {
text-align: right;
}
`, `,
HeaderRow: ` HeaderRow: `
text-transform: uppercase; text-transform: uppercase;
background-color: black; background-color: black;
border-bottom: 1px solid #e0e0e0;
color: #90CAF9; color: #90CAF9;
font-weight: 500;
.th {
border-bottom: 1px solid #565656;
font-weight: 500;
}
&:nth-of-type(1) .th {
text-align: center;
}
`, `,
Row: ` Row: `
background-color: #1e1e1e; background-color: #1e1e1e;
border-top: 1px solid #565656;
border-bottom: 1px solid #565656;
position: relative; position: relative;
z-index: 1; cursor: pointer;
&:not(:last-of-type) {
margin-bottom: -1px; .td {
border-top: 1px solid #565656;
border-bottom: 1px solid #565656;
} }
&:not(:first-of-type) {
margin-top: -1px; &.tr.tr-body.row-select.row-select-single-selected {
}
&:hover {
z-index: 2;
color: white;
border-top: 1px solid #177ac9;
border-bottom: 1px solid #177ac9;
},
&.tr.tr-body.row-select.row-select-single-selected, &.tr.tr-body.row-select.row-select-selected {
background-color: #3d4752; background-color: #3d4752;
color: white; color: white;
font-weight: normal; font-weight: normal;
z-index: 2; }
&:hover .td {
border-top: 1px solid #177ac9; border-top: 1px solid #177ac9;
border-bottom: 1px solid #177ac9; border-bottom: 1px solid #177ac9;
} }
&:nth-of-type(odd) .td {
background-color: #303030;
}
`, `,
BaseCell: ` Cell: `
border-top: 1px solid transparent;
border-right: 1px solid transparent;
border-bottom: 1px solid transparent;
&:not(.stiff) > div {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
&:nth-of-type(1) {
width: 120px;
min-width: 120px;
max-width: 120px;
}
&:nth-of-type(2) { &:nth-of-type(2) {
padding-left: 8px; padding: 8px;
flex: 1;
} }
&:nth-of-type(3) { &:nth-of-type(3) {
padding-right: 4px;
}
&:nth-of-type(4) {
padding-right: 4px;
}
&:last-of-type {
padding-right: 8px; padding-right: 8px;
text-align: right;
width: 120px;
min-width: 120px;
}
`,
HeaderCell: `
&:nth-of-type(1) {
padding-left: 24px;
}
&:nth-of-type(2) {
padding-left: 0px;
}
&:not(:last-of-type) {
border-right: 1px solid #565656;
} }
` `
}); });
const getSortIcon = (state: any, sortKey: any) => {
if (state.sortKey === sortKey && state.reverse) {
return <KeyboardArrowDownOutlinedIcon />;
}
if (state.sortKey === sortKey && !state.reverse) {
return <KeyboardArrowUpOutlinedIcon />;
}
return <UnfoldMoreOutlinedIcon />;
};
const entity_sort = useSort(
{ nodes: deviceEntities },
{},
{
sortIcon: {
iconDefault: <UnfoldMoreOutlinedIcon />,
iconUp: <KeyboardArrowUpOutlinedIcon />,
iconDown: <KeyboardArrowDownOutlinedIcon />
},
sortToggleType: SortToggleType.AlternateWithReset,
sortFns: {
NAME: (array) => array.sort((a, b) => a.id.localeCompare(b.id))
}
}
);
const fetchDevices = useCallback(async () => { const fetchDevices = useCallback(async () => {
try { try {
setDevices((await EMSESP.readDevices()).data); setDevices((await EMSESP.readDevices()).data);
} catch (error: unknown) { } catch (error) {
setErrorMessage(extractErrorMessage(error, 'Failed to fetch device list')); setErrorMessage(extractErrorMessage(error, LL.PROBLEM_LOADING()));
} }
}, []); }, [LL]);
const setInitialMask = (data: DeviceEntity[]) => { const setInitialMask = (data: DeviceEntity[]) => {
setDeviceEntities(data.map((de) => ({ ...de, om: de.m }))); setDeviceEntities(data.map((de) => ({ ...de, o_m: de.m, o_cn: de.cn, o_mi: de.mi, o_ma: de.ma })));
}; };
const fetchDeviceEntities = async (unique_id: number) => { const fetchDeviceEntities = async (unique_id: number) => {
try { try {
const data = (await EMSESP.readDeviceEntities({ id: unique_id })).data; const new_deviceEntities = (await EMSESP.readDeviceEntities({ id: unique_id })).data;
setInitialMask(data); setInitialMask(new_deviceEntities);
} catch (error: unknown) { } catch (error) {
setErrorMessage(extractErrorMessage(error, 'Problem fetching device entities')); setErrorMessage(extractErrorMessage(error, LL.PROBLEM_LOADING()));
} }
}; };
@@ -199,6 +182,18 @@ const SettingsCustomization: FC = () => {
return value; return value;
} }
function formatName(de: DeviceEntity) {
return (
<>
{de.n && (de.n[0] === '!' ? LL.COMMAND() + ': ' + de.n.slice(1) : de.cn && de.cn !== '' ? de.cn : de.n) + ' '}(
<Link target="_blank" href={APIURL + devices?.devices[selectedDevice].tn + '/' + de.id}>
{de.id}
</Link>
)
</>
);
}
const getMaskNumber = (newMask: string[]) => { const getMaskNumber = (newMask: string[]) => {
var new_mask = 0; var new_mask = 0;
for (let entry of newMask) { for (let entry of newMask) {
@@ -221,6 +216,9 @@ const SettingsCustomization: FC = () => {
if ((m & 8) === 8) { if ((m & 8) === 8) {
new_masks.push('8'); new_masks.push('8');
} }
if ((m & 128) === 128) {
new_masks.push('128');
}
return new_masks; return new_masks;
}; };
@@ -241,56 +239,70 @@ const SettingsCustomization: FC = () => {
); );
}; };
function compareDevices(a: DeviceShort, b: DeviceShort) {
if (a.s < b.s) {
return -1;
}
if (a.s > b.s) {
return 1;
}
return 0;
}
const changeSelectedDevice = (event: React.ChangeEvent<HTMLInputElement>) => { const changeSelectedDevice = (event: React.ChangeEvent<HTMLInputElement>) => {
const selected_device = parseInt(event.target.value, 10); if (devices) {
setSelectedDevice(selected_device); const selected_device = parseInt(event.target.value, 10);
fetchDeviceEntities(selected_device); setSelectedDevice(selected_device);
fetchDeviceEntities(devices?.devices[selected_device].i);
setRestartNeeded(false);
}
}; };
const resetCustomization = async () => { const resetCustomization = async () => {
try { try {
await EMSESP.resetCustomizations(); await EMSESP.resetCustomizations();
enqueueSnackbar('All customizations have been removed. Restarting...', { variant: 'info' }); enqueueSnackbar(LL.CUSTOMIZATIONS_RESTART(), { variant: 'info' });
} catch (error: unknown) { } catch (error) {
enqueueSnackbar(extractErrorMessage(error, 'Problem resetting customizations'), { variant: 'error' }); enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
} finally { } finally {
setConfirmReset(false); setConfirmReset(false);
} }
}; };
const saveCustomization = async () => { const restart = async () => {
if (deviceEntities && selectedDevice) { try {
const masked_entities = deviceEntities await EMSESP.restart();
.filter((de) => de.m !== de.om) setRestarting(true);
.map((new_de) => new_de.m.toString(16).padStart(2, '0') + new_de.s); } catch (error) {
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
}
};
if (masked_entities.length > 60) { const saveCustomization = async () => {
enqueueSnackbar('Selected entities exceeded limit of 60. Please Save in batches', { variant: 'warning' }); if (devices && deviceEntities && selectedDevice !== -1) {
const masked_entities = deviceEntities
.filter((de) => de.m !== de.o_m || de.cn !== de.o_cn || de.ma !== de.o_ma || de.mi !== de.o_mi)
.map(
(new_de) =>
new_de.m.toString(16).padStart(2, '0') +
new_de.id +
(new_de.cn || new_de.mi || new_de.ma ? '|' : '') +
(new_de.cn ? new_de.cn : '') +
(new_de.mi ? '>' + new_de.mi : '') +
(new_de.ma ? '<' + new_de.ma : '')
);
// check size in bytes to match buffer in CPP, which is 2048
const bytes = new TextEncoder().encode(JSON.stringify(masked_entities)).length;
if (bytes > 2000) {
enqueueSnackbar(LL.CUSTOMIZATIONS_FULL(), { variant: 'warning' });
return; return;
} }
try { try {
const response = await EMSESP.writeMaskedEntities({ const response = await EMSESP.writeCustomEntities({
id: selectedDevice, id: devices?.devices[selectedDevice].i,
entity_ids: masked_entities entity_ids: masked_entities
}); });
if (response.status === 200) { if (response.status === 200) {
enqueueSnackbar('Customization saved', { variant: 'success' }); enqueueSnackbar(LL.CUSTOMIZATIONS_SAVED(), { variant: 'success' });
} else if (response.status === 201) {
setRestartNeeded(true);
} else { } else {
enqueueSnackbar('Customization save failed', { variant: 'error' }); enqueueSnackbar(LL.PROBLEM_UPDATING(), { variant: 'error' });
} }
} catch (error: unknown) { } catch (error) {
enqueueSnackbar(extractErrorMessage(error, 'Problem sending entity list'), { variant: 'error' }); enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
} }
setInitialMask(deviceEntities); setInitialMask(deviceEntities);
} }
@@ -303,12 +315,19 @@ const SettingsCustomization: FC = () => {
return ( return (
<> <>
<Box color="warning.main"> <Box mb={2} color="warning.main">
<Typography variant="body2">Select a device and customize each of its entities using the options.</Typography> <Typography variant="body2">{LL.CUSTOMIZATIONS_HELP_1()}</Typography>
<Typography variant="body2">
<OptionIcon type="favorite" isSet={true} />={LL.CUSTOMIZATIONS_HELP_2()}&nbsp;&nbsp;
<OptionIcon type="readonly" isSet={true} />={LL.CUSTOMIZATIONS_HELP_3()}&nbsp;&nbsp;
<OptionIcon type="api_mqtt_exclude" isSet={true} />={LL.CUSTOMIZATIONS_HELP_4()}&nbsp;&nbsp;
<OptionIcon type="web_exclude" isSet={true} />={LL.CUSTOMIZATIONS_HELP_5()}&nbsp;&nbsp;
<OptionIcon type="deleted" isSet={true} />={LL.CUSTOMIZATIONS_HELP_6()}
</Typography>
</Box> </Box>
<ValidatedTextField <ValidatedTextField
name="device" name="device"
label="EMS Device" label={LL.EMS_DEVICE()}
variant="outlined" variant="outlined"
fullWidth fullWidth
value={selectedDevice} value={selectedDevice}
@@ -316,11 +335,11 @@ const SettingsCustomization: FC = () => {
margin="normal" margin="normal"
select select
> >
<MenuItem disabled key={0} value={0}> <MenuItem disabled key={0} value={-1}>
Select a device... {LL.SELECT_DEVICE()}...
</MenuItem> </MenuItem>
{devices.devices.sort(compareDevices).map((device: DeviceShort, index) => ( {devices.devices.map((device: DeviceShort, index) => (
<MenuItem key={index} value={device.i}> <MenuItem key={index} value={index}>
{device.s} {device.s}
</MenuItem> </MenuItem>
))} ))}
@@ -329,6 +348,33 @@ const SettingsCustomization: FC = () => {
); );
}; };
const editEntity = (de: DeviceEntity) => {
if (de.n === undefined || (de.n && de.n[0] === '!')) {
return;
}
if (de.cn === undefined) {
de.cn = '';
}
setDeviceEntity(de);
};
const updateEntity = () => {
if (deviceEntity) {
setDeviceEntities((prevState) => {
const newState = prevState.map((obj) => {
if (obj.id === deviceEntity.id) {
return { ...obj, cn: deviceEntity.cn, mi: deviceEntity.mi, ma: deviceEntity.ma };
}
return obj;
});
return newState;
});
}
setDeviceEntity(undefined);
};
const renderDeviceData = () => { const renderDeviceData = () => {
if (devices?.devices.length === 0 || deviceEntities[0].id === '') { if (devices?.devices.length === 0 || deviceEntities[0].id === '') {
return; return;
@@ -363,9 +409,11 @@ const SettingsCustomization: FC = () => {
}} }}
/> />
</Grid> </Grid>
<Grid item> <Tooltip arrow placement="top" title="apply filter">
<FilterListIcon color="primary" sx={{ fontSize: 14, verticalAlign: 'middle' }} />: <Grid item>
</Grid> <FilterListIcon color="primary" sx={{ fontSize: 14, verticalAlign: 'middle' }} />:
</Grid>
</Tooltip>
<Grid item> <Grid item>
<ToggleButtonGroup <ToggleButtonGroup
size="small" size="small"
@@ -376,34 +424,25 @@ const SettingsCustomization: FC = () => {
}} }}
> >
<ToggleButton value="8"> <ToggleButton value="8">
<Tooltip arrow placement="top" title="filter favorites"> <OptionIcon type="favorite" isSet={true} />
<StarIcon sx={{ fontSize: 14 }} />
</Tooltip>
</ToggleButton> </ToggleButton>
<ToggleButton value="4"> <ToggleButton value="4">
<Tooltip arrow placement="top" title="filter entities with write action disabled"> <OptionIcon type="readonly" isSet={true} />
<EditOffOutlinedIcon sx={{ fontSize: 14 }} />
</Tooltip>
</ToggleButton> </ToggleButton>
<ToggleButton value="2"> <ToggleButton value="2">
<Tooltip arrow placement="top" title="filter entities excluded from MQTT and API outputs"> <OptionIcon type="api_mqtt_exclude" isSet={true} />
<CommentsDisabledOutlinedIcon sx={{ fontSize: 14 }} />
</Tooltip>
</ToggleButton> </ToggleButton>
<ToggleButton value="1"> <ToggleButton value="1">
<Tooltip arrow placement="top" title="filter entities hidden from Web Dashboard"> <OptionIcon type="web_exclude" isSet={true} />
<VisibilityOffOutlinedIcon sx={{ fontSize: 14 }} /> </ToggleButton>
</Tooltip> <ToggleButton value="128">
<OptionIcon type="deleted" isSet={true} />
</ToggleButton> </ToggleButton>
</ToggleButtonGroup> </ToggleButtonGroup>
</Grid> </Grid>
<Grid item> <Grid item>
<CommentsDisabledOutlinedIcon color="primary" sx={{ fontSize: 14, verticalAlign: 'middle' }} /> <Tooltip arrow placement="top" title="set selected entities to be both visible and output">
<VisibilityOffOutlinedIcon color="primary" sx={{ fontSize: 14, verticalAlign: 'middle' }} />:
</Grid>
<Grid item>
<Tooltip arrow placement="top" title="mark shown entities to be all visible and output ">
<Button <Button
size="small" size="small"
sx={{ fontSize: 10 }} sx={{ fontSize: 10 }}
@@ -411,12 +450,14 @@ const SettingsCustomization: FC = () => {
color="inherit" color="inherit"
onClick={() => maskDisabled(false)} onClick={() => maskDisabled(false)}
> >
enable {LL.SET_ALL()}&nbsp;
<OptionIcon type="api_mqtt_exclude" isSet={false} />
<OptionIcon type="web_exclude" isSet={false} />
</Button> </Button>
</Tooltip> </Tooltip>
</Grid> </Grid>
<Grid item> <Grid item>
<Tooltip arrow placement="top" title="mark shown entities to be not visible or output "> <Tooltip arrow placement="top" title="set selected entities to be not visible and not output">
<Button <Button
size="small" size="small"
sx={{ fontSize: 10 }} sx={{ fontSize: 10 }}
@@ -424,64 +465,88 @@ const SettingsCustomization: FC = () => {
color="inherit" color="inherit"
onClick={() => maskDisabled(true)} onClick={() => maskDisabled(true)}
> >
disable {LL.SET_ALL()}&nbsp;
<OptionIcon type="api_mqtt_exclude" isSet={true} />
<OptionIcon type="web_exclude" isSet={true} />
</Button> </Button>
</Tooltip> </Tooltip>
</Grid> </Grid>
</Grid> </Grid>
<Table data={{ nodes: shown_data }} theme={entities_theme} sort={entity_sort} layout={{ custom: true }}> <Table data={{ nodes: shown_data }} theme={entities_theme} layout={{ custom: true }}>
{(tableList: any) => ( {(tableList: any) => (
<> <>
<Header> <Header>
<HeaderRow> <HeaderRow>
<HeaderCell>OPTIONS</HeaderCell> <HeaderCell stiff>{LL.OPTIONS()}</HeaderCell>
<HeaderCell resize> <HeaderCell resize>
<Button <Button fullWidth style={{ fontSize: '14px', justifyContent: 'flex-start' }}>
fullWidth {LL.NAME(1)}
style={{ fontSize: '14px', justifyContent: 'flex-start' }}
endIcon={getSortIcon(entity_sort.state, 'NAME')}
onClick={() => entity_sort.fns.onToggleSort({ sortKey: 'NAME' })}
>
NAME
</Button> </Button>
</HeaderCell> </HeaderCell>
<HeaderCell>VALUE</HeaderCell> <HeaderCell stiff>{LL.MIN()}</HeaderCell>
<HeaderCell stiff>{LL.MAX()}</HeaderCell>
<HeaderCell resize>{LL.VALUE(0)}</HeaderCell>
</HeaderRow> </HeaderRow>
</Header> </Header>
<Body> <Body>
{tableList.map((de: DeviceEntity) => ( {tableList.map((de: DeviceEntity) => (
<Row key={de.id} item={de}> <Row key={de.id} item={de} onClick={() => editEntity(de)}>
<Cell> <Cell stiff>
<ToggleButtonGroup {!deviceEntity && (
size="small" <ToggleButtonGroup
color="secondary" size="small"
value={getMaskString(de.m)} color="secondary"
onChange={(event, mask) => { value={getMaskString(de.m)}
de.m = getMaskNumber(mask); onChange={(event, mask) => {
if (de.m & DeviceEntityMask.DV_WEB_EXCLUDE) { de.m = getMaskNumber(mask);
de.m = de.m & ~DeviceEntityMask.DV_FAVORITE; if (de.n === '' && de.m & DeviceEntityMask.DV_READONLY) {
} de.m = de.m | DeviceEntityMask.DV_WEB_EXCLUDE;
setMasks(['']); }
}} if (de.m & DeviceEntityMask.DV_WEB_EXCLUDE) {
> de.m = de.m & ~DeviceEntityMask.DV_FAVORITE;
<ToggleButton value="8" disabled={(de.m & 1) !== 0 || de.id === ''}> }
<StarIcon sx={{ fontSize: 14 }} /> setMasks(['']);
</ToggleButton> }}
<ToggleButton value="4" disabled={!de.w || (de.m & 3) === 3}> >
<EditOffOutlinedIcon sx={{ fontSize: 14 }} /> <ToggleButton value="8" disabled={(de.m & 0x81) !== 0 || de.n === undefined}>
</ToggleButton> <OptionIcon
<ToggleButton value="2"> type="favorite"
<CommentsDisabledOutlinedIcon sx={{ fontSize: 14 }} /> isSet={(de.m & DeviceEntityMask.DV_FAVORITE) === DeviceEntityMask.DV_FAVORITE}
</ToggleButton> />
<ToggleButton value="1"> </ToggleButton>
<VisibilityOffOutlinedIcon sx={{ fontSize: 14 }} /> <ToggleButton value="4" disabled={!de.w || (de.m & 0x83) >= 3}>
</ToggleButton> <OptionIcon
</ToggleButtonGroup> type="readonly"
isSet={(de.m & DeviceEntityMask.DV_READONLY) === DeviceEntityMask.DV_READONLY}
/>
</ToggleButton>
<ToggleButton value="2" disabled={de.n === '' || (de.m & 0x80) !== 0}>
<OptionIcon
type="api_mqtt_exclude"
isSet={
(de.m & DeviceEntityMask.DV_API_MQTT_EXCLUDE) === DeviceEntityMask.DV_API_MQTT_EXCLUDE
}
/>
</ToggleButton>
<ToggleButton value="1" disabled={de.n === undefined || (de.m & 0x80) !== 0}>
<OptionIcon
type="web_exclude"
isSet={(de.m & DeviceEntityMask.DV_WEB_EXCLUDE) === DeviceEntityMask.DV_WEB_EXCLUDE}
/>
</ToggleButton>
<ToggleButton value="128">
<OptionIcon
type="deleted"
isSet={(de.m & DeviceEntityMask.DV_DELETED) === DeviceEntityMask.DV_DELETED}
/>
</ToggleButton>
</ToggleButtonGroup>
)}
</Cell> </Cell>
<Cell> <Cell>{!deviceEntity && formatName(de)}</Cell>
{de.id}&nbsp;({de.s}) <Cell>{!deviceEntity && !(de.m & DeviceEntityMask.DV_READONLY) && formatValue(de.mi)}</Cell>
</Cell> <Cell>{!deviceEntity && !(de.m & DeviceEntityMask.DV_READONLY) && formatValue(de.ma)}</Cell>
<Cell>{formatValue(de.v)}</Cell> <Cell>{!deviceEntity && formatValue(de.v)}</Cell>
</Row> </Row>
))} ))}
</Body> </Body>
@@ -494,14 +559,11 @@ const SettingsCustomization: FC = () => {
const renderResetDialog = () => ( const renderResetDialog = () => (
<Dialog open={confirmReset} onClose={() => setConfirmReset(false)}> <Dialog open={confirmReset} onClose={() => setConfirmReset(false)}>
<DialogTitle>Reset</DialogTitle> <DialogTitle>{LL.RESET(1)}</DialogTitle>
<DialogContent dividers> <DialogContent dividers>{LL.CUSTOMIZATIONS_RESET()}</DialogContent>
Are you sure you want remove all customizations including the custom settings of the Temperature and Analog
sensors?
</DialogContent>
<DialogActions> <DialogActions>
<Button startIcon={<CancelIcon />} variant="outlined" onClick={() => setConfirmReset(false)} color="secondary"> <Button startIcon={<CancelIcon />} variant="outlined" onClick={() => setConfirmReset(false)} color="secondary">
Cancel {LL.CANCEL()}
</Button> </Button>
<Button <Button
startIcon={<SettingsBackupRestoreIcon />} startIcon={<SettingsBackupRestoreIcon />}
@@ -510,25 +572,32 @@ const SettingsCustomization: FC = () => {
autoFocus autoFocus
color="error" color="error"
> >
Reset {LL.RESET(0)}
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
); );
const content = () => { const renderContent = () => (
return ( <>
<> <Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary"> {LL.DEVICE_ENTITIES()}
Device Entities </Typography>
</Typography> {renderDeviceList()}
{renderDeviceList()} {renderDeviceData()}
{renderDeviceData()} {restartNeeded && (
<MessageBox my={2} level="warning" message={LL.RESTART_TEXT()}>
<Button startIcon={<PowerSettingsNewIcon />} variant="contained" color="error" onClick={restart}>
{LL.RESTART()}
</Button>
</MessageBox>
)}
{!restartNeeded && (
<Box display="flex" flexWrap="wrap"> <Box display="flex" flexWrap="wrap">
<Box flexGrow={1}> <Box flexGrow={1}>
<ButtonRow> <ButtonRow>
<Button startIcon={<SaveIcon />} variant="outlined" color="primary" onClick={() => saveCustomization()}> <Button startIcon={<SaveIcon />} variant="outlined" color="primary" onClick={() => saveCustomization()}>
Save {LL.SAVE()}
</Button> </Button>
</ButtonRow> </ButtonRow>
</Box> </Box>
@@ -539,18 +608,90 @@ const SettingsCustomization: FC = () => {
color="error" color="error"
onClick={() => setConfirmReset(true)} onClick={() => setConfirmReset(true)}
> >
Reset {LL.RESET(0)}
</Button> </Button>
</ButtonRow> </ButtonRow>
</Box> </Box>
{renderResetDialog()} )}
</> {renderResetDialog()}
); </>
);
const renderEditDialog = () => {
if (deviceEntity) {
const de = deviceEntity;
return (
<Dialog open={!!deviceEntity} onClose={() => setDeviceEntity(undefined)}>
<DialogTitle>{LL.EDIT() + ' ' + LL.ENTITY() + ' "' + de.id + '"'}</DialogTitle>
<DialogContent dividers>
<Box color="warning.main" mb={2}>
<Typography variant="body2">
{LL.DEFAULT(1) + ' ' + LL.NAME(1)}:&nbsp;{deviceEntity.n}
</Typography>
</Box>
<Grid container spacing={1}>
<Grid item>
<TextField
name="cn"
label={LL.NEW_NAME_OF(LL.ENTITY())}
value={deviceEntity.cn}
autoFocus
sx={{ width: '30ch' }}
onChange={updateValue(setDeviceEntity)}
/>
</Grid>
{typeof de.v === 'number' && de.w && !(de.m & DeviceEntityMask.DV_READONLY) && (
<>
<Grid item>
<TextField
name="mi"
label={LL.MIN()}
value={deviceEntity.mi}
sx={{ width: '8ch' }}
onChange={updateValue(setDeviceEntity)}
/>
</Grid>
<Grid item>
<TextField
name="ma"
label={LL.MAX()}
value={deviceEntity.ma}
sx={{ width: '8ch' }}
onChange={updateValue(setDeviceEntity)}
/>
</Grid>
</>
)}
</Grid>
</DialogContent>
<DialogActions>
<Button
startIcon={<CancelIcon />}
variant="outlined"
onClick={() => setDeviceEntity(undefined)}
color="secondary"
>
{LL.CANCEL()}
</Button>
<Button
startIcon={<SaveIcon />}
variant="outlined"
type="submit"
onClick={() => updateEntity()}
color="warning"
>
{LL.SAVE()}
</Button>
</DialogActions>
</Dialog>
);
}
}; };
return ( return (
<SectionContent title="User Customization" titleGutter> <SectionContent title={LL.USER_CUSTOMIZATION()} titleGutter>
{content()} {restarting ? <RestartMonitor /> : renderContent()}
{renderEditDialog()}
</SectionContent> </SectionContent>
); );
}; };

View File

@@ -12,7 +12,7 @@ import {
DeviceData, DeviceData,
DeviceEntity, DeviceEntity,
UniqueID, UniqueID,
MaskedEntities, CustomEntities,
WriteValue, WriteValue,
WriteSensor, WriteSensor,
WriteAnalog, WriteAnalog,
@@ -63,8 +63,8 @@ export function readDeviceEntities(unique_id: UniqueID): AxiosPromise<DeviceEnti
return AXIOS_BIN.post('/deviceEntities', unique_id); return AXIOS_BIN.post('/deviceEntities', unique_id);
} }
export function writeMaskedEntities(maskedEntities: MaskedEntities): AxiosPromise<void> { export function writeCustomEntities(customEntities: CustomEntities): AxiosPromise<void> {
return AXIOS.post('/maskedEntities', maskedEntities); return AXIOS.post('/customEntities', customEntities);
} }
export function writeValue(writevalue: WriteValue): AxiosPromise<void> { export function writeValue(writevalue: WriteValue): AxiosPromise<void> {

View File

@@ -1,4 +1,5 @@
export interface Settings { export interface Settings {
locale: string;
tx_mode: number; tx_mode: number;
ems_bus_id: number; ems_bus_id: number;
syslog_enabled: boolean; syslog_enabled: boolean;
@@ -6,7 +7,7 @@ export interface Settings {
syslog_mark_interval: number; syslog_mark_interval: number;
syslog_host: string; syslog_host: string;
syslog_port: number; syslog_port: number;
master_thermostat: number; boiler_heatingoff: boolean;
shower_timer: boolean; shower_timer: boolean;
shower_alert: boolean; shower_alert: boolean;
shower_alert_coldshot: number; shower_alert_coldshot: number;
@@ -33,6 +34,7 @@ export interface Settings {
eth_power: number; eth_power: number;
eth_phy_addr: number; eth_phy_addr: number;
eth_clock_mode: number; eth_clock_mode: number;
platform: string;
} }
export enum busConnectionStatus { export enum busConnectionStatus {
@@ -42,7 +44,7 @@ export enum busConnectionStatus {
} }
export interface Stat { export interface Stat {
id: string; // name id: string; // id - needs to be a string
s: number; // success s: number; // success
f: number; // fail f: number; // fail
q: number; // quality q: number; // quality
@@ -58,7 +60,8 @@ export interface Status {
} }
export interface Device { export interface Device {
id: string; // id index id: string; // id index
t: string; // type tn: string; // device type translated name
t: number; // device type id
b: string; // brand b: string; // brand
n: string; // name n: string; // name
d: number; // deviceid d: number; // deviceid
@@ -98,16 +101,20 @@ export interface SensorData {
} }
export interface CoreData { export interface CoreData {
connected: boolean;
devices: Device[]; devices: Device[];
s_n: string;
active_sensors: number; active_sensors: number;
analog_enabled: boolean; analog_enabled: boolean;
} }
export interface DeviceShort { export interface DeviceShort {
i: number; // id i: number; // id
d: number; // deviceid d?: number; // deviceid
p: number; // productid p?: number; // productid
s: string; // shortname s: string; // shortname
t?: number; // device type id
tn?: string; // device type internal name
} }
export interface Devices { export interface Devices {
@@ -132,15 +139,21 @@ export interface DeviceData {
} }
export interface DeviceEntity { export interface DeviceEntity {
id: string; // name id: string; // shortname
v: any; // value, in any format v?: any; // value, in any format, optional
s: string; // shortname n?: string; // fullname, optional
cn?: string; // custom fullname, optional
m: number; // mask m: number; // mask
om?: number; // original mask before edits o_m?: number; // original mask before edits
o_cn?: string; // original cn before edits
w: boolean; // writeable w: boolean; // writeable
mi?: string; // min value
ma?: string; // max value
o_mi?: string;
o_ma?: string;
} }
export interface MaskedEntities { export interface CustomEntities {
id: number; id: number;
entity_ids: string[]; entity_ids: string[];
} }
@@ -170,7 +183,9 @@ export enum DeviceValueUOM {
MV, MV,
SQM, SQM,
M3, M3,
L L,
KMIN,
K
} }
export const DeviceValueUOM_s = [ export const DeviceValueUOM_s = [
@@ -183,18 +198,20 @@ export const DeviceValueUOM_s = [
'Wh', 'Wh',
'hours', 'hours',
'minutes', 'minutes',
'uA', 'µA',
'bar', 'bar',
'kW', 'kW',
'W', 'W',
'KB', 'KB',
'second', 'seconds',
'dBm', 'dBm',
'°F', '°F',
'mV', 'mV',
'sqm', 'm²',
'm3', 'm³',
'l' 'l',
'K*min',
'K'
]; ];
export enum AnalogType { export enum AnalogType {
@@ -234,7 +251,10 @@ export const BOARD_PROFILES: BoardProfiles = {
'MH-ET': 'MH-ET Live D1 Mini', 'MH-ET': 'MH-ET Live D1 Mini',
LOLIN: 'Lolin D32', LOLIN: 'Lolin D32',
OLIMEX: 'Olimex ESP32-EVB', OLIMEX: 'Olimex ESP32-EVB',
OLIMEXPOE: 'Olimex ESP32-POE' OLIMEXPOE: 'Olimex ESP32-POE',
C3MINI: 'Wemos C3 Mini',
S2MINI: 'Wemos S2 Mini',
S3MINI: 'Liligo S3'
}; };
export interface BoardProfileName { export interface BoardProfileName {
@@ -279,5 +299,6 @@ export enum DeviceEntityMask {
DV_WEB_EXCLUDE = 1, DV_WEB_EXCLUDE = 1,
DV_API_MQTT_EXCLUDE = 2, DV_API_MQTT_EXCLUDE = 2,
DV_READONLY = 4, DV_READONLY = 4,
DV_FAVORITE = 8 DV_FAVORITE = 8,
DV_DELETED = 128
} }

View File

@@ -7,12 +7,13 @@ export const GPIO_VALIDATOR = {
if ( if (
value && value &&
(value === 1 || (value === 1 ||
(value >= 6 && value <= 12) || (value >= 10 && value <= 12) ||
(value >= 14 && value <= 15) || (value >= 14 && value <= 15) ||
value === 20 || value === 20 ||
value === 24 || value === 24 ||
(value >= 28 && value <= 31) || (value >= 28 && value <= 31) ||
value > 40) value > 40 ||
value < 0)
) { ) {
callback('Must be an valid GPIO port'); callback('Must be an valid GPIO port');
} else { } else {
@@ -21,24 +22,61 @@ export const GPIO_VALIDATOR = {
} }
}; };
export const GPIO_VALIDATORC3 = {
validator(rule: InternalRuleItem, value: number, callback: (error?: string) => void) {
if (value && ((value >= 11 && value <= 19) || value > 21 || value < 0)) {
callback('Must be an valid GPIO port');
} else {
callback();
}
}
};
export const GPIO_VALIDATORS2 = {
validator(rule: InternalRuleItem, value: number, callback: (error?: string) => void) {
if (value && ((value >= 19 && value <= 20) || (value >= 22 && value <= 32) || value > 40 || value < 0)) {
callback('Must be an valid GPIO port');
} else {
callback();
}
}
};
export const createSettingsValidator = (settings: Settings) => export const createSettingsValidator = (settings: Settings) =>
new Schema({ new Schema({
...(settings.board_profile === 'CUSTOM' && { ...(settings.board_profile === 'CUSTOM' &&
led_gpio: [{ required: true, message: 'LED GPIO is required' }, GPIO_VALIDATOR], settings.platform === 'ESP32' && {
dallas_gpio: [{ required: true, message: 'GPIO is required' }, GPIO_VALIDATOR], led_gpio: [{ required: true, message: 'LED GPIO is required' }, GPIO_VALIDATOR],
pbutton_gpio: [{ required: true, message: 'Button GPIO is required' }, GPIO_VALIDATOR], dallas_gpio: [{ required: true, message: 'GPIO is required' }, GPIO_VALIDATOR],
tx_gpio: [{ required: true, message: 'Tx GPIO is required' }, GPIO_VALIDATOR], pbutton_gpio: [{ required: true, message: 'Button GPIO is required' }, GPIO_VALIDATOR],
rx_gpio: [{ required: true, message: 'Rx GPIO is required' }, GPIO_VALIDATOR] tx_gpio: [{ required: true, message: 'Tx GPIO is required' }, GPIO_VALIDATOR],
}), rx_gpio: [{ required: true, message: 'Rx GPIO is required' }, GPIO_VALIDATOR]
}),
...(settings.board_profile === 'CUSTOM' &&
settings.platform === 'ESP32-C3' && {
led_gpio: [{ required: true, message: 'LED GPIO is required' }, GPIO_VALIDATORC3],
dallas_gpio: [{ required: true, message: 'GPIO is required' }, GPIO_VALIDATORC3],
pbutton_gpio: [{ required: true, message: 'Button GPIO is required' }, GPIO_VALIDATORC3],
tx_gpio: [{ required: true, message: 'Tx GPIO is required' }, GPIO_VALIDATORC3],
rx_gpio: [{ required: true, message: 'Rx GPIO is required' }, GPIO_VALIDATORC3]
}),
...(settings.board_profile === 'CUSTOM' &&
settings.platform === 'ESP32-S2' && {
led_gpio: [{ required: true, message: 'LED GPIO is required' }, GPIO_VALIDATORS2],
dallas_gpio: [{ required: true, message: 'GPIO is required' }, GPIO_VALIDATORS2],
pbutton_gpio: [{ required: true, message: 'Button GPIO is required' }, GPIO_VALIDATORS2],
tx_gpio: [{ required: true, message: 'Tx GPIO is required' }, GPIO_VALIDATORS2],
rx_gpio: [{ required: true, message: 'Rx GPIO is required' }, GPIO_VALIDATORS2]
}),
...(settings.syslog_enabled && { ...(settings.syslog_enabled && {
syslog_host: [{ required: true, message: 'Host is required' }, IP_OR_HOSTNAME_VALIDATOR], syslog_host: [{ required: true, message: 'Host is required' }, IP_OR_HOSTNAME_VALIDATOR],
syslog_port: [ syslog_port: [
{ required: true, message: 'Port is required' }, { required: true, message: 'Port is required' },
{ type: 'number', min: 0, max: 65535, message: 'Port must be between 0 and 65535' } { type: 'number', min: 0, max: 65535, message: 'Invalid Port' }
], ],
syslog_mark_interval: [ syslog_mark_interval: [
{ required: true, message: 'Mark interval is required' }, { required: true, message: 'Mark interval is required' },
{ type: 'number', min: 0, max: 10, message: 'Port must be between 0 and 10' } { type: 'number', min: 0, max: 10, message: ' must be between 0 and 10' }
] ]
}), }),
...(settings.shower_alert && { ...(settings.shower_alert && {

View File

@@ -15,6 +15,8 @@ export interface MqttStatus {
client_id: string; client_id: string;
disconnect_reason: MqttDisconnectReason; disconnect_reason: MqttDisconnectReason;
mqtt_fails: number; mqtt_fails: number;
mqtt_queued: number;
connect_count: number;
} }
export interface MqttSettings { export interface MqttSettings {
@@ -27,13 +29,14 @@ export interface MqttSettings {
client_id: string; client_id: string;
keep_alive: number; keep_alive: number;
clean_session: boolean; clean_session: boolean;
max_topic_length: number; entity_format: number;
publish_time_boiler: number; publish_time_boiler: number;
publish_time_thermostat: number; publish_time_thermostat: number;
publish_time_solar: number; publish_time_solar: number;
publish_time_mixer: number; publish_time_mixer: number;
publish_time_other: number; publish_time_other: number;
publish_time_sensor: number; publish_time_sensor: number;
publish_time_heartbeat: number;
mqtt_qos: number; mqtt_qos: number;
mqtt_retain: boolean; mqtt_retain: boolean;
ha_enabled: boolean; ha_enabled: boolean;

View File

@@ -48,6 +48,8 @@ export interface NetworkSettings {
dns_ip_1?: string; dns_ip_1?: string;
dns_ip_2?: string; dns_ip_2?: string;
enableMDNS: boolean; enableMDNS: boolean;
enableCORS: boolean;
CORSOrigin: string;
} }
export interface WiFiNetworkList { export interface WiFiNetworkList {

View File

@@ -1,36 +1,23 @@
export enum EspPlatform { export interface SystemStatus {
ESP8266 = 'esp8266',
ESP32 = 'esp32'
}
interface ESPSystemStatus {
emsesp_version: string; emsesp_version: string;
esp_platform: EspPlatform; esp_platform: string;
max_alloc_heap: number; max_alloc_heap: number;
cpu_freq_mhz: number; cpu_freq_mhz: number;
free_heap: number; free_heap: number;
sdk_version: string; sdk_version: string;
flash_chip_size: number; flash_chip_size: number;
flash_chip_speed: number; flash_chip_speed: number;
app_used: number;
app_free: number;
fs_used: number; fs_used: number;
fs_total: number; fs_free: number;
uptime: string; uptime: string;
free_mem: number; free_mem: number;
psram_size?: number;
free_psram?: number;
has_loader: boolean;
} }
export interface ESP32SystemStatus extends ESPSystemStatus {
esp_platform: EspPlatform.ESP32;
psram_size: number;
free_psram: number;
}
export interface ESP8266SystemStatus extends ESPSystemStatus {
esp_platform: EspPlatform.ESP8266;
heap_fragmentation: number;
}
export type SystemStatus = ESP8266SystemStatus | ESP32SystemStatus;
export interface OTASettings { export interface OTASettings {
enabled: boolean; enabled: boolean;
port: number; port: number;

View File

@@ -1,8 +1,6 @@
import { AxiosError } from 'axios'; export const extractErrorMessage = (error: any, defaultMessage: string) => {
if (error.request) {
export const extractErrorMessage = (error: unknown, defaultMessage: string) => { return defaultMessage + ' (' + error.request.status + ': ' + error.request.statusText + ')';
if (error instanceof AxiosError) {
return defaultMessage + ' (' + error.request.statusText + ')';
} else if (error instanceof Error) { } else if (error instanceof Error) {
return defaultMessage + ' (' + error.message + ')'; return defaultMessage + ' (' + error.message + ')';
} }

View File

@@ -5,4 +5,3 @@ export * from './submit';
export * from './time'; export * from './time';
export * from './useRest'; export * from './useRest';
export * from './props'; export * from './props';

View File

@@ -1,5 +1,3 @@
import parseMilliseconds from 'parse-ms';
const LOCALE_FORMAT = new Intl.DateTimeFormat([...window.navigator.languages], { const LOCALE_FORMAT = new Intl.DateTimeFormat([...window.navigator.languages], {
day: 'numeric', day: 'numeric',
month: 'short', month: 'short',
@@ -21,21 +19,6 @@ export const formatLocalDateTime = (date: Date) => {
export const pluralize = (count: number, noun: string) => export const pluralize = (count: number, noun: string) =>
`${Intl.NumberFormat().format(count)} ${noun}${count !== 1 ? 's' : ''}`; `${Intl.NumberFormat().format(count)} ${noun}${count !== 1 ? 's' : ''}`;
export const formatDurationMin = (duration_min: number) => {
const { days, hours, minutes } = parseMilliseconds(duration_min * 60000);
let formatted = '';
if (days) {
formatted += pluralize(days, 'day') + ' ';
}
if (hours) {
formatted += pluralize(hours, 'hour') + ' ';
}
if (minutes) {
formatted += pluralize(minutes, 'minute') + ' ';
}
return formatted;
};
export const formatDurationSec = (duration_sec: number) => { export const formatDurationSec = (duration_sec: number) => {
if (duration_sec === 0) { if (duration_sec === 0) {
return ' '; return ' ';

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