718 Commits

Author SHA1 Message Date
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
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
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
5845c37672 Change name of entity within WebUI #612 2022-09-09 17:04:31 +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
348 changed files with 24729 additions and 25425 deletions

View File

@@ -14,9 +14,9 @@ jobs:
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- uses: actions/setup-node@v2
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
- uses: actions/setup-node@v3
with:
node-version: '16'
@@ -24,19 +24,19 @@ jobs:
id: build_info
run: |
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
run: |
python -m pip install --upgrade pip
pip install -U platformio
platformio upgrade
platformio update
- name: Build WebUI
run: |
cd interface
npm ci
npx typesafe-i18n --no-watch
sed -i "s/= 'pl'/= 'en'/" ./src/i18n/i18n-util.ts
npm run build
- name: Build firmware
@@ -48,7 +48,7 @@ jobs:
uses: "marvinpinto/action-automatic-releases@latest"
with:
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"
prerelease: true
files: |

View File

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

12
.gitignore vendored
View File

@@ -13,7 +13,6 @@ debug.log
# platformio
.pio
pio_local.ini
/.VSCodeCounter
# OS specific
.DS_Store
@@ -31,7 +30,18 @@ test.sh
scripts/__pycache__
.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
.scannerwork/
sonar/
build_wrapper_output_directory/
# other build files
dump_entities.csv
dump_entities.xls*

View File

@@ -5,6 +5,74 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
# [3.5.0]
## **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.4]
## Fixed
- Fix for new installations with filesystem not initializing
# [3.4.3]
## Fixed
@@ -35,7 +103,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## 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)

View File

@@ -1,7 +1,7 @@
#
# GNUMakefile for EMS-ESP
# (c) 2020 Paul Derbyshire
#
NUMJOBS=${NUMJOBS:-" -j4 "}
MAKEFLAGS+="j "
#----------------------------------------------------------------------
@@ -17,23 +17,30 @@ MAKEFLAGS+="j "
#TARGET := $(notdir $(CURDIR))
TARGET := emsesp
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
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
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/semver lib/* src/devices
LIBRARIES :=
CPPCHECK = cppcheck
# CHECKFLAGS = -q --force --std=c++17
CHECKFLAGS = -q --force --std=c++11
#----------------------------------------------------------------------
# Languages Standard
#----------------------------------------------------------------------
# C_STANDARD := -std=c17
# CXX_STANDARD := -std=c++17
C_STANDARD := -std=c11
CXX_STANDARD := -std=c++11
#----------------------------------------------------------------------
# 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
@@ -66,7 +73,7 @@ CXX := /usr/bin/g++
# CXXFLAGS C++ Compiler Flags
# LDFLAGS Linker Flags
#----------------------------------------------------------------------
CPPFLAGS += $(DEFINES) $(INCLUDE)
CPPFLAGS += $(DEFINES) $(DEFAULTS) $(INCLUDE)
CPPFLAGS += -ggdb
CPPFLAGS += -g3
CPPFLAGS += -Os
@@ -114,6 +121,8 @@ COMPILE.cpp = $(CXX) $(CXX_STANDARD) $(CXXFLAGS) $(DEPFLAGS) -c $< -o $@
# Targets
#----------------------------------------------------------------------
.PHONY: all
.SILENT: $(OUTPUT)
all: $(OUTPUT)
$(OUTPUT): $(OBJS)

131
README.md
View File

@@ -1,17 +1,5 @@
# ![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)
[![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)
@@ -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)
[![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 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)
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**
---
# **Features**
- A multi-user secure web interface to change settings and monitor incoming data
- A multi-user, multi-language secure web interface to change settings and monitor incoming data
- 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
- 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**
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_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%>
# **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

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

View File

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

15257
interface/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,36 +1,35 @@
{
"name": "EMS-ESP",
"version": "3.4.0",
"version": "3.5.0",
"private": true,
"proxy": "http://localhost:3080",
"dependencies": {
"@emotion/react": "^11.10.4",
"@emotion/styled": "^11.10.4",
"@emotion/react": "^11.10.5",
"@emotion/styled": "^11.10.5",
"@msgpack/msgpack": "^2.8.0",
"@mui/icons-material": "^5.10.3",
"@mui/material": "^5.10.5",
"@table-library/react-table-library": "4.0.18",
"@types/lodash": "^4.14.185",
"@types/node": "^18.7.18",
"@types/react": "^18.0.20",
"@types/react-dom": "^18.0.6",
"@mui/icons-material": "^5.11.0",
"@mui/material": "^5.11.7",
"@table-library/react-table-library": "4.0.24",
"@types/lodash": "^4.14.191",
"@types/node": "^18.11.19",
"@types/react": "^18.0.27",
"@types/react-dom": "^18.0.10",
"@types/react-router-dom": "^5.3.3",
"async-validator": "^4.2.5",
"axios": "^0.27.2",
"http-proxy-middleware": "^2.0.6",
"axios": "^1.3.2",
"jwt-decode": "^3.1.2",
"lodash": "^4.17.21",
"notistack": "^2.0.5",
"parse-ms": "^3.0.0",
"notistack": "^2.0.8",
"react": "^18.2.0",
"react-app-rewired": "^2.2.1",
"react-dom": "^18.2.0",
"react-dropzone": "^14.2.2",
"react-icons": "^4.4.0",
"react-router-dom": "^6.4.0",
"react-dropzone": "^14.2.3",
"react-icons": "^4.7.1",
"react-router-dom": "^6.8.1",
"react-scripts": "5.0.1",
"sockette": "^2.0.6",
"typescript": "^4.8.3"
"typesafe-i18n": "^5.24.0",
"typescript": "^4.9.5"
},
"scripts": {
"start": "react-app-rewired start",
@@ -41,8 +40,9 @@
"build-hosted": "env-cmd -f .env.hosted npm run build",
"build-localhost": "PUBLIC_URL=/ react-app-rewired build",
"mock-api": "nodemon --watch ../mock-api ../mock-api/server.js",
"standalone": "npm-run-all -p start mock-api",
"lint": "eslint . --ext .ts,.tsx"
"standalone": "npm-run-all -p start typesafe-i18n mock-api",
"lint": "eslint . --ext .ts,.tsx",
"typesafe-i18n": "typesafe-i18n"
},
"eslintConfig": {
"extends": [
@@ -78,7 +78,7 @@
"max-len": [
1,
{
"code": 200
"code": 220
}
],
"arrow-parens": 1
@@ -98,6 +98,7 @@
},
"devDependencies": {
"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-weight: 400;
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,
U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
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+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 {
@@ -19,6 +20,7 @@
font-style: normal;
font-weight: 500;
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,
U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
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+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 { IconButton } from '@mui/material';
@@ -9,6 +9,13 @@ import { FeaturesLoader } from './contexts/features';
import CustomTheme from './CustomTheme';
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 notistackRef: RefObject<any> = createRef();
@@ -20,8 +27,17 @@ const App: FC = () => {
const colorMode = useContext(ColorModeContext);
const [wasLoaded, setWasLoaded] = useState(false);
useEffect(() => {
loadLocaleAsync(detectedLocale).then(() => setWasLoaded(true));
}, []);
if (!wasLoaded) return null;
return (
<ColorModeContext.Provider value={colorMode}>
<TypesafeI18n locale={detectedLocale}>
<CustomTheme>
<SnackbarProvider
maxSnack={3}
@@ -38,6 +54,7 @@ const App: FC = () => {
</FeaturesLoader>
</SnackbarProvider>
</CustomTheme>
</TypesafeI18n>
</ColorModeContext.Provider>
);
};

View File

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

View File

@@ -2,20 +2,30 @@ import { FC, useContext, useState } from 'react';
import { ValidateFieldsError } from 'async-validator';
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 * as AuthenticationApi from './api/authentication';
import { PROJECT_NAME } from './api/env';
import { AuthenticationContext } from './contexts/authentication';
import { AxiosError } from 'axios';
import { extractErrorMessage, onEnterCallback, updateValue } from './utils';
import { SignInRequest } from './types';
import { ValidatedTextField } from './components';
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 authenticationContext = useContext(AuthenticationContext);
const { enqueueSnackbar } = useSnackbar();
@@ -31,6 +41,9 @@ const SignIn: FC = () => {
const validateAndSignIn = async () => {
setProcessing(true);
SIGN_IN_REQUEST_VALIDATOR.messages({
required: LL.IS_REQUIRED('%s')
});
try {
await validate(SIGN_IN_REQUEST_VALIDATOR, signInRequest);
signIn();
@@ -44,13 +57,13 @@ const SignIn: FC = () => {
try {
const { data: loginResponse } = await AuthenticationApi.signIn(signInRequest);
authenticationContext.signIn(loginResponse.access_token);
} catch (error: unknown) {
if (error instanceof AxiosError) {
} catch (error) {
if (error.response) {
if (error.response?.status === 401) {
enqueueSnackbar('Invalid login details', { variant: 'warning' });
enqueueSnackbar(LL.INVALID_LOGIN(), { variant: 'warning' });
}
} else {
enqueueSnackbar(extractErrorMessage(error, 'Unexpected error, please try again'), { variant: 'error' });
enqueueSnackbar(extractErrorMessage(error, LL.ERROR()), { variant: 'error' });
}
setProcessing(false);
}
@@ -58,6 +71,14 @@ const SignIn: FC = () => {
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 (
<Box
display="flex"
@@ -81,11 +102,49 @@ const SignIn: FC = () => {
})}
>
<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
fieldErrors={fieldErrors}
disabled={processing}
name="username"
label="Username"
label={LL.USERNAME(0)}
value={signInRequest.username}
onChange={updateLoginRequestValue}
margin="normal"
@@ -97,7 +156,7 @@ const SignIn: FC = () => {
disabled={processing}
type="password"
name="password"
label="Password"
label={LL.PASSWORD()}
value={signInRequest.password}
onChange={updateLoginRequestValue}
onKeyDown={submitOnEnter}
@@ -107,7 +166,7 @@ const SignIn: FC = () => {
/>
<Fab variant="extended" color="primary" sx={{ mt: 2 }} onClick={validateAndSignIn} disabled={processing}>
<ForwardIcon sx={{ mr: 1 }} />
Sign In
{LL.SIGN_IN()}
</Fab>
</Paper>
</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';
@@ -89,7 +89,7 @@ function calculateEventSourceRoot(endpointPath: string) {
export interface FileUploadConfig {
cancelToken?: CancelToken;
onUploadProgress?: (progressEvent: ProgressEvent) => void;
onUploadProgress?: (progressEvent: AxiosProgressEvent) => 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');
}
export function updateMqttSettings(ntpSettings: MqttSettings): AxiosPromise<MqttSettings> {
return AXIOS.post('/mqttSettings', ntpSettings);
export function updateMqttSettings(mqttSettings: MqttSettings): AxiosPromise<MqttSettings> {
return AXIOS.post('/mqttSettings', mqttSettings);
}

View File

@@ -12,6 +12,10 @@ export function restart(): AxiosPromise<void> {
return AXIOS.post('/restart');
}
export function partition(): AxiosPromise<void> {
return AXIOS.post('/partition');
}
export function factoryReset(): AxiosPromise<void> {
return AXIOS.post('/factoryReset');
}
@@ -38,4 +42,3 @@ export function updateLogSettings(logSettings: LogSettings): AxiosPromise<LogSet
export function readLogEntries(): AxiosPromise<LogEntries> {
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 AccountCircleIcon from '@mui/icons-material/AccountCircle';
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>({
maxWidth: '250px',
whiteSpace: 'nowrap',
@@ -23,6 +47,15 @@ const LayoutAuthMenu: FC = () => {
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 = () => {
setAnchorEl(null);
};
@@ -32,7 +65,53 @@ const LayoutAuthMenu: FC = () => {
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 />
</IconButton>
<Popover
@@ -56,13 +135,15 @@ const LayoutAuthMenu: FC = () => {
</Avatar>
<Box pl={2}>
<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>
<Divider />
<Box p={1.5}>
<Button variant="outlined" fullWidth color="primary" onClick={() => signOut(true)}>
Sign Out
{LL.SIGN_OUT()}
</Button>
</Box>
</Popover>

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,24 +1,30 @@
import { useCallback, useEffect, useState } from 'react';
import axios, { AxiosPromise, CancelTokenSource } from 'axios';
import axios, { AxiosPromise, CancelTokenSource, AxiosProgressEvent } from 'axios';
import { useSnackbar } from 'notistack';
import { extractErrorMessage } from '../../utils';
import { FileUploadConfig } from '../../api/endpoints';
import { useI18nContext } from '../../i18n/i18n-react';
interface MediaUploadOptions {
upload: (file: File, config?: FileUploadConfig) => AxiosPromise<void>;
}
const useFileUpload = ({ upload }: MediaUploadOptions) => {
const { LL } = useI18nContext();
const { enqueueSnackbar } = useSnackbar();
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 resetUploadingStates = () => {
setUploading(false);
setUploadProgress(undefined);
setUploadCancelToken(undefined);
setMd5('');
};
const cancelUpload = useCallback(() => {
@@ -37,23 +43,28 @@ const useFileUpload = ({ upload }: MediaUploadOptions) => {
const cancelToken = axios.CancelToken.source();
setUploadCancelToken(cancelToken);
setUploading(true);
await upload(images[0], {
const response = await upload(images[0], {
onUploadProgress: setUploadProgress,
cancelToken: cancelToken.token
});
resetUploadingStates();
enqueueSnackbar('File uploaded', { variant: 'success' });
} catch (error: unknown) {
if (response.status === 200) {
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)) {
enqueueSnackbar('Upload aborted', { variant: 'warning' });
enqueueSnackbar(LL.UPLOAD() + ' ' + LL.ABORTED(), { variant: 'warning' });
} else {
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;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,12 +5,15 @@ import DeviceHubIcon from '@mui/icons-material/DeviceHub';
import RefreshIcon from '@mui/icons-material/Refresh';
import ReportIcon from '@mui/icons-material/Report';
import SpeakerNotesOffIcon from '@mui/icons-material/SpeakerNotesOff';
import AutoAwesomeMotionIcon from '@mui/icons-material/AutoAwesomeMotion';
import { ButtonRow, FormLoader, SectionContent } from '../../components';
import { MqttStatus, MqttDisconnectReason } from '../../types';
import * as MqttApi from '../../api/mqtt';
import { useRest } from '../../utils';
import { useI18nContext } from '../../i18n/i18n-react';
export const mqttStatusHighlight = ({ enabled, connected }: MqttStatus, theme: Theme) => {
if (!enabled) {
return theme.palette.info.main;
@@ -29,17 +32,30 @@ export const mqttPublishHighlight = ({ mqtt_fails }: MqttStatus, theme: Theme) =
return theme.palette.error.main;
};
export const mqttStatus = ({ enabled, connected }: MqttStatus) => {
if (!enabled) {
return 'Not enabled';
}
if (connected) {
return 'Connected';
}
return 'Disconnected';
export const mqttQueueHighlight = ({ mqtt_queued }: MqttStatus, theme: Theme) => {
if (mqtt_queued <= 1) return theme.palette.success.main;
return theme.palette.warning.main;
};
export const disconnectReason = ({ disconnect_reason }: MqttStatus) => {
const MqttStatusForm: FC = () => {
const { loadData, data, errorMessage } = useRest<MqttStatus>({ read: MqttApi.readMqttStatus });
const { LL } = useI18nContext();
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';
@@ -60,12 +76,7 @@ export const disconnectReason = ({ disconnect_reason }: MqttStatus) => {
default:
return 'Unknown';
}
};
const MqttStatusForm: FC = () => {
const { loadData, data, errorMessage } = useRest<MqttStatus>({ read: MqttApi.readMqttStatus });
const theme = useTheme();
};
const content = () => {
if (!data) {
@@ -73,14 +84,35 @@ const MqttStatusForm: FC = () => {
}
const renderConnectionStatus = () => {
if (data.connected) {
return (
<>
{!data.connected && (
<>
<ListItem>
<ListItemAvatar>
<Avatar>
<ReportIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary={LL.DISCONNECT_REASON()} secondary={disconnectReason(data)} />
</ListItem>
<Divider variant="inset" component="li" />
</>
)}
<ListItem>
<ListItemAvatar>
<Avatar>#</Avatar>
</ListItemAvatar>
<ListItemText primary="Client ID" secondary={data.client_id} />
<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>
</ListItemAvatar>
<ListItemText primary={LL.MQTT_QUEUE()} secondary={data.mqtt_queued} />
</ListItem>
<Divider variant="inset" component="li" />
<ListItem>
@@ -89,20 +121,7 @@ const MqttStatusForm: FC = () => {
<SpeakerNotesOffIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary="MQTT Publish Errors" secondary={data.mqtt_fails} />
</ListItem>
</>
);
}
return (
<>
<ListItem>
<ListItemAvatar>
<Avatar>
<ReportIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary="Disconnect Reason" secondary={disconnectReason(data)} />
<ListItemText primary={LL.ERRORS_OF('MQTT')} secondary={data.mqtt_fails} />
</ListItem>
<Divider variant="inset" component="li" />
</>
@@ -118,14 +137,14 @@ const MqttStatusForm: FC = () => {
<DeviceHubIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary="Status" secondary={mqttStatus(data)} />
<ListItemText primary={LL.STATUS_OF('')} secondary={mqttStatus(data)} />
</ListItem>
<Divider variant="inset" component="li" />
{data.enabled && renderConnectionStatus()}
</List>
<ButtonRow>
<Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={loadData}>
Refresh
{LL.REFRESH()}
</Button>
</ButtonRow>
</>
@@ -133,7 +152,7 @@ const MqttStatusForm: FC = () => {
};
return (
<SectionContent title="MQTT Status" titleGutter>
<SectionContent title={LL.STATUS_OF('MQTT')} titleGutter>
{content()}
</SectionContent>
);

View File

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

View File

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

View File

@@ -14,6 +14,8 @@ import { NetworkConnectionStatus, NetworkStatus } from '../../types';
import * as NetworkApi from '../../api/network';
import { useRest } from '../../utils';
import { useI18nContext } from '../../i18n/i18n-react';
const isConnected = ({ status }: NetworkStatus) =>
status === NetworkConnectionStatus.WIFI_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 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) {
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) => {
@@ -81,8 +60,33 @@ const IPs = (status: NetworkStatus) => {
const NetworkStatusForm: FC = () => {
const { loadData, data, errorMessage } = useRest<NetworkStatus>({ read: NetworkApi.readNetworkStatus });
const { LL } = useI18nContext();
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 = () => {
if (!data) {
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
@@ -120,7 +124,7 @@ const NetworkStatusForm: FC = () => {
<ListItemAvatar>
<Avatar>IP</Avatar>
</ListItemAvatar>
<ListItemText primary="IP Address" secondary={IPs(data)} />
<ListItemText primary={LL.ADDRESS_OF('IP')} secondary={IPs(data)} />
</ListItem>
<Divider variant="inset" component="li" />
<ListItem>
@@ -129,14 +133,14 @@ const NetworkStatusForm: FC = () => {
<DeviceHubIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary="MAC Address" secondary={data.mac_address} />
<ListItemText primary={LL.ADDRESS_OF('MAC')} secondary={data.mac_address} />
</ListItem>
<Divider variant="inset" component="li" />
<ListItem>
<ListItemAvatar>
<Avatar>#</Avatar>
</ListItemAvatar>
<ListItemText primary="Subnet Mask" secondary={data.subnet_mask} />
<ListItemText primary={LL.NETWORK_SUBNET()} secondary={data.subnet_mask} />
</ListItem>
<Divider variant="inset" component="li" />
<ListItem>
@@ -145,7 +149,7 @@ const NetworkStatusForm: FC = () => {
<SettingsInputComponentIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary="Gateway IP" secondary={data.gateway_ip || 'none'} />
<ListItemText primary={LL.NETWORK_GATEWAY()} secondary={data.gateway_ip || 'none'} />
</ListItem>
<Divider variant="inset" component="li" />
<ListItem>
@@ -154,7 +158,7 @@ const NetworkStatusForm: FC = () => {
<DnsIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary="DNS Server IP" secondary={dnsServers(data)} />
<ListItemText primary={LL.NETWORK_DNS()} secondary={dnsServers(data)} />
</ListItem>
<Divider variant="inset" component="li" />
</>
@@ -162,7 +166,7 @@ const NetworkStatusForm: FC = () => {
</List>
<ButtonRow>
<Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={loadData}>
Refresh
{LL.REFRESH()}
</Button>
</ButtonRow>
</>
@@ -170,7 +174,7 @@ const NetworkStatusForm: FC = () => {
};
return (
<SectionContent title="Network Status" titleGutter>
<SectionContent title={LL.STATUS_OF(LL.NETWORK(1))} titleGutter>
{content()}
</SectionContent>
);

View File

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

View File

@@ -12,6 +12,8 @@ import { WiFiEncryptionType, WiFiNetwork, WiFiNetworkList } from '../../types';
import { WiFiConnectionContext } from './WiFiConnectionContext';
import { useI18nContext } from '../../i18n/i18n-react';
interface WiFiNetworkSelectorProps {
networkList: WiFiNetworkList;
}
@@ -39,6 +41,8 @@ export const networkSecurityMode = ({ encryption_type }: WiFiNetwork) => {
};
const WiFiNetworkSelector: FC<WiFiNetworkSelectorProps> = ({ networkList }) => {
const { LL } = useI18nContext();
const wifiConnectionContext = useContext(WiFiConnectionContext);
const renderNetwork = (network: WiFiNetwork) => {
@@ -61,7 +65,7 @@ const WiFiNetworkSelector: FC<WiFiNetworkSelectorProps> = ({ networkList }) => {
};
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>;

View File

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

View File

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

View File

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

View File

@@ -19,6 +19,8 @@ import { MessageBox } from '../../components';
import * as SecurityApi from '../../api/security';
import { Token } from '../../types';
import { useI18nContext } from '../../i18n/i18n-react';
interface GenerateTokenProps {
username?: string;
onClose: () => void;
@@ -28,15 +30,17 @@ const GenerateToken: FC<GenerateTokenProps> = ({ username, onClose }) => {
const [token, setToken] = useState<Token>();
const open = !!username;
const { LL } = useI18nContext();
const { enqueueSnackbar } = useSnackbar();
const getToken = useCallback(async () => {
try {
setToken((await SecurityApi.generateToken(username)).data);
} catch (error: unknown) {
enqueueSnackbar(extractErrorMessage(error, 'Problem generating token'), { variant: 'error' });
} catch (error) {
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
}
}, [username, enqueueSnackbar]);
}, [username, enqueueSnackbar, LL]);
useEffect(() => {
if (open) {
@@ -46,16 +50,11 @@ const GenerateToken: FC<GenerateTokenProps> = ({ username, onClose }) => {
return (
<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>
{token ? (
<>
<MessageBox
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}
/>
<MessageBox message={LL.ACCESS_TOKEN_TEXT()} level="info" my={2} />
<Box mt={2} mb={2}>
<TextField label="Token" multiline value={token.token} fullWidth contentEditable={false} />
</Box>
@@ -63,13 +62,13 @@ const GenerateToken: FC<GenerateTokenProps> = ({ username, onClose }) => {
) : (
<Box m={4} textAlign="center">
<LinearProgress />
<Typography variant="h6">Generating token&hellip;</Typography>
<Typography variant="h6">{LL.GENERATING_TOKEN()}&hellip;</Typography>
</Box>
)}
</DialogContent>
<DialogActions>
<Button startIcon={<CloseIcon />} variant="outlined" onClick={onClose} color="secondary">
Close
{LL.CLOSE()}
</Button>
</DialogActions>
</Dialog>

View File

@@ -20,6 +20,8 @@ import { createUserValidator } from '../../validators';
import { useRest } from '../../utils';
import { AuthenticatedContext } from '../../contexts/authentication';
import { useI18nContext } from '../../i18n/i18n-react';
import GenerateToken from './GenerateToken';
import UserForm from './UserForm';
@@ -34,9 +36,11 @@ const ManageUsersForm: FC = () => {
const [generatingToken, setGeneratingToken] = useState<string>();
const authenticatedContext = useContext(AuthenticatedContext);
const { LL } = useI18nContext();
const table_theme = useTheme({
Table: `
--data-table-library_grid-template-columns: repeat(1, minmax(0, 1fr)) 90px 120px;
--data-table-library_grid-template-columns: repeat(1, minmax(0, 1fr)) minmax(120px, max-content) 120px;
`,
BaseRow: `
font-size: 14px;
@@ -136,8 +140,8 @@ const ManageUsersForm: FC = () => {
<>
<Header>
<HeaderRow>
<HeaderCell resize>USERNAME</HeaderCell>
<HeaderCell stiff>IS ADMIN</HeaderCell>
<HeaderCell resize>{LL.USERNAME(1)}</HeaderCell>
<HeaderCell stiff>{LL.IS_ADMIN(0)}</HeaderCell>
<HeaderCell stiff />
</HeaderRow>
</Header>
@@ -169,9 +173,7 @@ const ManageUsersForm: FC = () => {
)}
</Table>
{noAdminConfigured() && (
<MessageBox level="warning" message="You must have at least one admin user configured" my={2} />
)}
{noAdminConfigured() && <MessageBox level="warning" message={LL.USER_WARNING()} my={2} />}
<Box display="flex" flexWrap="wrap">
<Box flexGrow={1} sx={{ '& button': { mt: 2 } }}>
@@ -183,14 +185,14 @@ const ManageUsersForm: FC = () => {
type="submit"
onClick={onSubmit}
>
Save
{LL.SAVE()}
</Button>
</Box>
<Box flexWrap="nowrap" whiteSpace="nowrap">
<ButtonRow>
<Button startIcon={<PersonAddIcon />} variant="outlined" color="secondary" onClick={createUser}>
Add
{LL.ADD(0)}
</Button>
</ButtonRow>
</Box>
@@ -210,7 +212,7 @@ const ManageUsersForm: FC = () => {
};
return (
<SectionContent title="Manage Users" titleGutter>
<SectionContent title={LL.MANAGE_USERS()} titleGutter>
{content()}
</SectionContent>
);

View File

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

View File

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

View File

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

View File

@@ -4,6 +4,7 @@ import { AxiosPromise } from 'axios';
import { Typography, Button, Box } from '@mui/material';
import { FileUploadConfig } from '../../api/endpoints';
import { SingleUpload, useFileUpload } from '../../components';
import DownloadIcon from '@mui/icons-material/GetApp';
@@ -14,15 +15,19 @@ import { extractErrorMessage } from '../../utils';
import * as EMSESP from '../../project/api';
import { useI18nContext } from '../../i18n/i18n-react';
interface UploadFileProps {
uploadGeneralFile: (file: File, config?: FileUploadConfig) => AxiosPromise<void>;
}
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';
@@ -35,19 +40,19 @@ const GeneralFileUpload: FC<UploadFileProps> = ({ uploadGeneralFile }) => {
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
enqueueSnackbar('File downloaded', { variant: 'info' });
enqueueSnackbar(LL.DOWNLOAD_SUCCESSFUL(), { variant: 'info' });
};
const downloadSettings = async () => {
try {
const response = await EMSESP.getSettings();
if (response.status !== 200) {
enqueueSnackbar('Unable to get settings', { variant: 'error' });
enqueueSnackbar(LL.PROBLEM_LOADING(), { variant: 'error' });
} else {
saveFile(response.data, 'settings');
}
} catch (error: unknown) {
enqueueSnackbar(extractErrorMessage(error, 'Problem with downloading'), { variant: 'error' });
} catch (error) {
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' });
}
};
@@ -55,47 +60,50 @@ const GeneralFileUpload: FC<UploadFileProps> = ({ uploadGeneralFile }) => {
try {
const response = await EMSESP.getCustomizations();
if (response.status !== 200) {
enqueueSnackbar('Unable to get customizations', { variant: 'error' });
enqueueSnackbar(LL.PROBLEM_LOADING(), { variant: 'error' });
} else {
saveFile(response.data, 'customizations');
}
} catch (error: unknown) {
enqueueSnackbar(extractErrorMessage(error, 'Problem with downloading'), { variant: 'error' });
} catch (error) {
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' });
}
};
return (
<>
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
Upload
</Typography>
{!uploading && (
<Box mb={2} color="warning.main">
<Typography variant="body2">
Upload a new firmware (.bin) file, settings or customizations (.json) file below.
<>
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
{LL.UPLOAD()}
</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} />
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
Download
</Typography>
{!uploading && (
<>
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
{LL.DOWNLOAD(0)}
</Typography>
<Box color="warning.main">
<Typography mb={1} variant="body2">
Download the application settings. Be careful when sharing your settings as this file contains passwords
and other sensitive system information.
{LL.DOWNLOAD_SETTINGS_TEXT()}
</Typography>
</Box>
<Button startIcon={<DownloadIcon />} variant="outlined" color="primary" onClick={() => downloadSettings()}>
settings
{LL.SETTINGS_OF('')}
</Button>
<Box color="warning.main">
<Typography mt={2} mb={1} variant="body2">
Download the entity customizations.
{LL.DOWNLOAD_CUSTOMIZATION_TEXT()}{' '}
</Typography>
</Box>
<Button
@@ -104,7 +112,7 @@ const GeneralFileUpload: FC<UploadFileProps> = ({ uploadGeneralFile }) => {
color="primary"
onClick={() => downloadCustomizations()}
>
customizations
{LL.CUSTOMIZATION()}
</Button>
</>
)}

View File

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

View File

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

View File

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

View File

@@ -15,6 +15,9 @@ import DownloadIcon from '@mui/icons-material/GetApp';
import { useSnackbar } from 'notistack';
import { EVENT_SOURCE_ROOT } from '../../api/endpoints';
import { useI18nContext } from '../../i18n/i18n-react';
export const LOG_EVENTSOURCE_URL = EVENT_SOURCE_ROOT + 'log';
const useWindowSize = () => {
@@ -63,6 +66,8 @@ const levelLabel = (level: LogLevel) => {
const SystemLog: FC = () => {
useWindowSize();
const { LL } = useI18nContext();
const { loadData, data, setData } = useRest<LogSettings>({
read: SystemApi.readLogSettings
});
@@ -104,10 +109,10 @@ const SystemLog: FC = () => {
compact: data.compact
});
if (response.status !== 200) {
enqueueSnackbar('Problem applying log settings', { variant: 'error' });
enqueueSnackbar(LL.PROBLEM_UPDATING(), { variant: 'error' });
}
} catch (error: unknown) {
enqueueSnackbar(extractErrorMessage(error, 'Problem applying log settings'), { variant: 'error' });
} catch (error) {
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
}
}
};
@@ -158,10 +163,10 @@ const SystemLog: FC = () => {
const fetchLog = useCallback(async () => {
try {
setLogEntries((await SystemApi.readLogEntries()).data);
} catch (error: unknown) {
setErrorMessage(extractErrorMessage(error, 'Failed to fetch log'));
} catch (error) {
setErrorMessage(extractErrorMessage(error, LL.PROBLEM_LOADING()));
}
}, []);
}, [LL]);
useEffect(() => {
fetchLog();
@@ -197,7 +202,7 @@ const SystemLog: FC = () => {
<Grid item xs={4}>
<ValidatedTextField
name="level"
label="Log Level"
label={LL.LOG_LEVEL()}
value={data.level}
fullWidth
variant="outlined"
@@ -205,6 +210,7 @@ const SystemLog: FC = () => {
margin="normal"
select
>
<MenuItem value={-1}>OFF</MenuItem>
<MenuItem value={3}>ERROR</MenuItem>
<MenuItem value={4}>WARNING</MenuItem>
<MenuItem value={5}>NOTICE</MenuItem>
@@ -214,7 +220,7 @@ const SystemLog: FC = () => {
</ValidatedTextField>
</Grid>
<Grid item xs={3}>
<FormLabel>Buffer size</FormLabel>
<FormLabel>{LL.BUFFER_SIZE()}</FormLabel>
<Slider
value={data.max_messages}
valueLabelDisplay="auto"
@@ -235,12 +241,12 @@ const SystemLog: FC = () => {
<Grid item>
<BlockFormControlLabel
control={<Checkbox checked={data.compact} onChange={updateFormValue} name="compact" />}
label="Compact"
label={LL.COMPACT()}
/>
</Grid>
<Grid item>
<Button startIcon={<DownloadIcon />} variant="outlined" color="secondary" onClick={onDownload}>
Export
{LL.EXPORT()}
</Button>
</Grid>
</Grid>
@@ -273,7 +279,7 @@ const SystemLog: FC = () => {
};
return (
<SectionContent title="System Log" titleGutter id="log-window">
<SectionContent title={LL.LOG_OF(LL.SYSTEM(2))} titleGutter id="log-window">
{content()}
</SectionContent>
);

View File

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

View File

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

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

View File

@@ -49,8 +49,6 @@ import DeviceIcon from './DeviceIcon';
import { IconContext } from 'react-icons';
import { formatDurationMin, pluralize } from '../utils';
import { AuthenticatedContext } from '../contexts/authentication';
import { ButtonRow, ValidatedTextField, SectionContent, MessageBox } from '../components';
@@ -74,12 +72,23 @@ import {
DeviceEntityMask
} from './types';
import { useI18nContext } from '../i18n/i18n-react';
const DashboardData: FC = () => {
const { me } = useContext(AuthenticatedContext);
const { LL } = useI18nContext();
const { enqueueSnackbar } = useSnackbar();
const [coreData, setCoreData] = useState<CoreData>({ connected: true, devices: [], active_sensors: 0, analog_enabled: false });
const [coreData, setCoreData] = useState<CoreData>({
connected: true,
devices: [],
s_n: '',
active_sensors: 0,
analog_enabled: false
});
const [deviceData, setDeviceData] = useState<DeviceData>({ label: '', data: [] });
const [sensorData, setSensorData] = useState<SensorData>({ sensors: [], analogs: [] });
const [deviceValue, setDeviceValue] = useState<DeviceValue>();
@@ -134,7 +143,7 @@ const DashboardData: FC = () => {
common_theme,
{
Table: `
--data-table-library_grid-template-columns: 40px 100px repeat(1, minmax(0, 1fr)) 80px 40px;
--data-table-library_grid-template-columns: 40px 160px repeat(1, minmax(0, 1fr)) 100px 40px;
`,
BaseRow: `
.td {
@@ -162,7 +171,7 @@ const DashboardData: FC = () => {
common_theme,
{
Table: `
--data-table-library_grid-template-columns: repeat(1, minmax(0, 1fr)) 140px 40px;
--data-table-library_grid-template-columns: minmax(0, 1fr) 35% 40px;
`,
BaseRow: `
.td {
@@ -319,10 +328,10 @@ const DashboardData: FC = () => {
const handleDownloadCsv = () => {
const columns = [
{ accessor: (dv: any) => dv.id.slice(2), name: 'Entity' },
{ accessor: (dv: any) => dv.id.slice(2), name: LL.ENTITY_NAME() },
{
accessor: (dv: any) => (typeof dv.v === 'number' ? new Intl.NumberFormat().format(dv.v) : dv.v),
name: 'Value'
name: LL.VALUE(0)
},
{ accessor: (dv: any) => DeviceValueUOM_s[dv.u], name: 'UoM' }
];
@@ -354,10 +363,10 @@ const DashboardData: FC = () => {
const fetchCoreData = useCallback(async () => {
try {
setCoreData((await EMSESP.readCoreData()).data);
} catch (error: unknown) {
enqueueSnackbar(extractErrorMessage(error, 'Failed to fetch core data'), { variant: 'error' });
} catch (error) {
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' });
}
}, [enqueueSnackbar]);
}, [enqueueSnackbar, LL]);
useEffect(() => {
fetchCoreData();
@@ -375,30 +384,50 @@ const DashboardData: FC = () => {
const unique_id = parseInt(id);
try {
setDeviceData((await EMSESP.readDeviceData({ id: unique_id })).data);
} catch (error: unknown) {
enqueueSnackbar(extractErrorMessage(error, 'Problem fetching device data'), { variant: 'error' });
} catch (error) {
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' });
}
};
const fetchSensorData = async () => {
try {
setSensorData((await EMSESP.readSensorData()).data);
} catch (error: unknown) {
enqueueSnackbar(extractErrorMessage(error, 'Problem fetching sensor data'), { variant: 'error' });
} catch (error) {
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' });
}
};
const isCmdOnly = (dv: DeviceValue) => dv.v === '' && dv.c;
const formatDurationMin = (duration_min: number) => {
const days = Math.trunc((duration_min * 60000) / 86400000);
const hours = Math.trunc((duration_min * 60000) / 3600000) % 24;
const minutes = Math.trunc((duration_min * 60000) / 60000) % 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 });
}
return formatted;
};
function formatValue(value: any, uom: number) {
if (value === undefined) {
return '';
}
switch (uom) {
case DeviceValueUOM.HOURS:
return value ? formatDurationMin(value * 60) : '0 hours';
return value ? formatDurationMin(value * 60) : LL.NUM_HOURS({ num: 0 });
case DeviceValueUOM.MINUTES:
return value ? formatDurationMin(value) : '0 minutes';
return value ? formatDurationMin(value) : LL.NUM_MINUTES({ num: 0 });
case DeviceValueUOM.SECONDS:
return LL.NUM_SECONDS({ num: value });
case DeviceValueUOM.NONE:
if (typeof value === 'number') {
return new Intl.NumberFormat().format(value);
@@ -414,13 +443,24 @@ const DashboardData: FC = () => {
' ' +
DeviceValueUOM_s[uom]
);
case DeviceValueUOM.SECONDS:
return pluralize(value, DeviceValueUOM_s[uom]);
default:
return new Intl.NumberFormat().format(value) + ' ' + DeviceValueUOM_s[uom];
}
}
const setUom = (uom: number) => {
switch (uom) {
case DeviceValueUOM.HOURS:
return LL.HOURS();
case DeviceValueUOM.MINUTES:
return LL.MINUTES();
case DeviceValueUOM.SECONDS:
return LL.SECONDS();
default:
return DeviceValueUOM_s[uom];
}
};
const sendDeviceValue = async () => {
if (deviceValue) {
try {
@@ -429,15 +469,15 @@ const DashboardData: FC = () => {
devicevalue: deviceValue
});
if (response.status === 204) {
enqueueSnackbar('Write command failed', { variant: 'error' });
enqueueSnackbar(LL.WRITE_CMD_FAILED(), { variant: 'error' });
} else if (response.status === 403) {
enqueueSnackbar('Write access denied', { variant: 'error' });
enqueueSnackbar(LL.ACCESS_DENIED(), { variant: 'error' });
} else {
enqueueSnackbar('Write command sent', { variant: 'success' });
enqueueSnackbar(LL.WRITE_CMD_SENT(), { variant: 'success' });
}
setDeviceValue(undefined);
} catch (error: unknown) {
enqueueSnackbar(extractErrorMessage(error, 'Problem writing value'), { variant: 'error' });
} catch (error) {
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
} finally {
refreshData();
setDeviceValue(undefined);
@@ -449,7 +489,7 @@ const DashboardData: FC = () => {
if (deviceValue) {
return (
<Dialog open={deviceValue !== undefined} onClose={() => setDeviceValue(undefined)}>
<DialogTitle>{isCmdOnly(deviceValue) ? 'Run Command' : 'Change Value'}</DialogTitle>
<DialogTitle>{isCmdOnly(deviceValue) ? LL.RUN_COMMAND() : LL.CHANGE_VALUE()}</DialogTitle>
<DialogContent dividers>
{deviceValue.l && (
<ValidatedTextField
@@ -470,7 +510,7 @@ const DashboardData: FC = () => {
<ValidatedTextField
name="v"
label={deviceValue.id.slice(2)}
value={deviceValue.u ? numberValue(deviceValue.v) : deviceValue.v}
value={typeof deviceValue.v === 'number' ? Math.round(deviceValue.v * 10) / 10 : deviceValue.v}
autoFocus
multiline={deviceValue.u ? false : true}
sx={{ width: '30ch' }}
@@ -478,7 +518,7 @@ const DashboardData: FC = () => {
onChange={updateValue(setDeviceValue)}
inputProps={deviceValue.u ? { min: deviceValue.m, max: deviceValue.x, step: deviceValue.s } : {}}
InputProps={{
startAdornment: <InputAdornment position="start">{DeviceValueUOM_s[deviceValue.u]}</InputAdornment>
startAdornment: <InputAdornment position="start">{setUom(deviceValue.u)}</InputAdornment>
}}
/>
)}
@@ -491,7 +531,7 @@ const DashboardData: FC = () => {
onClick={() => setDeviceValue(undefined)}
color="secondary"
>
Cancel
{LL.CANCEL()}
</Button>
<Button
startIcon={<SendIcon />}
@@ -500,7 +540,7 @@ const DashboardData: FC = () => {
onClick={() => sendDeviceValue()}
color="warning"
>
Send
{LL.SEND()}
</Button>
</DialogActions>
</Dialog>
@@ -521,15 +561,15 @@ const DashboardData: FC = () => {
offset: sensor.o
});
if (response.status === 204) {
enqueueSnackbar('Sensor change failed', { variant: 'error' });
enqueueSnackbar(LL.UPLOAD_OF(LL.SENSOR()) + ' ' + LL.FAILED(), { variant: 'error' });
} else if (response.status === 403) {
enqueueSnackbar('Access denied', { variant: 'error' });
enqueueSnackbar(LL.ACCESS_DENIED(), { variant: 'error' });
} else {
enqueueSnackbar('Sensor updated', { variant: 'success' });
enqueueSnackbar(LL.UPDATED_OF(LL.SENSOR()), { variant: 'success' });
}
setSensor(undefined);
} catch (error: unknown) {
enqueueSnackbar(extractErrorMessage(error, 'Problem updating sensor'), { variant: 'error' });
} catch (error) {
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
} finally {
setSensor(undefined);
fetchSensorData();
@@ -541,16 +581,20 @@ const DashboardData: FC = () => {
if (sensor) {
return (
<Dialog open={sensor !== undefined} onClose={() => setSensor(undefined)}>
<DialogTitle>Edit Temperature Sensor</DialogTitle>
<DialogTitle>
{LL.EDIT()} {LL.TEMP_SENSOR()}
</DialogTitle>
<DialogContent dividers>
<Box color="warning.main" p={0} pl={0} pr={0} mt={0} mb={2}>
<Typography variant="body2">Sensor ID {sensor.id}</Typography>
<Typography variant="body2">
{LL.ID_OF(LL.SENSOR())}: {sensor.id}
</Typography>
</Box>
<Grid container spacing={1}>
<Grid item>
<ValidatedTextField
name="n"
label="Name"
label={LL.ENTITY_NAME()}
value={sensor.n}
autoFocus
sx={{ width: '30ch' }}
@@ -560,7 +604,7 @@ const DashboardData: FC = () => {
<Grid item>
<ValidatedTextField
name="o"
label="Offset"
label={LL.OFFSET()}
value={numberValue(sensor.o)}
sx={{ width: '12ch' }}
type="number"
@@ -581,7 +625,7 @@ const DashboardData: FC = () => {
onClick={() => setSensor(undefined)}
color="secondary"
>
Cancel
{LL.CANCEL()}
</Button>
<Button
startIcon={<SaveIcon />}
@@ -590,7 +634,7 @@ const DashboardData: FC = () => {
onClick={() => sendSensor()}
color="warning"
>
Save
{LL.SAVE()}
</Button>
</DialogActions>
</Dialog>
@@ -602,35 +646,35 @@ const DashboardData: FC = () => {
if (coreData && coreData.devices.length > 0 && deviceDialog !== -1) {
return (
<Dialog open={deviceDialog !== -1} onClose={() => setDeviceDialog(-1)}>
<DialogTitle>Device Details</DialogTitle>
<DialogTitle>{LL.DEVICE_DETAILS()}</DialogTitle>
<DialogContent dividers>
<List dense={true}>
<ListItem>
<ListItemText primary="Type" secondary={coreData.devices[deviceDialog].t} />
<ListItemText primary={LL.TYPE()} secondary={coreData.devices[deviceDialog].t} />
</ListItem>
<ListItem>
<ListItemText primary="Name" secondary={coreData.devices[deviceDialog].n} />
<ListItemText primary={LL.NAME(0)} secondary={coreData.devices[deviceDialog].n} />
</ListItem>
<ListItem>
<ListItemText primary="Brand" secondary={coreData.devices[deviceDialog].b} />
<ListItemText primary={LL.BRAND()} secondary={coreData.devices[deviceDialog].b} />
</ListItem>
<ListItem>
<ListItemText
primary="Device ID"
primary={LL.ID_OF(LL.DEVICE())}
secondary={'0x' + ('00' + coreData.devices[deviceDialog].d.toString(16).toUpperCase()).slice(-2)}
/>
</ListItem>
<ListItem>
<ListItemText primary="Product ID" secondary={coreData.devices[deviceDialog].p} />
<ListItemText primary={LL.ID_OF(LL.PRODUCT())} secondary={coreData.devices[deviceDialog].p} />
</ListItem>
<ListItem>
<ListItemText primary="Version" secondary={coreData.devices[deviceDialog].v} />
<ListItemText primary={LL.VERSION()} secondary={coreData.devices[deviceDialog].v} />
</ListItem>
</List>
</DialogContent>
<DialogActions>
<Button variant="outlined" onClick={() => setDeviceDialog(-1)} color="secondary">
Close
{LL.CLOSE()}
</Button>
</DialogActions>
</Dialog>
@@ -640,17 +684,20 @@ const DashboardData: FC = () => {
const renderCoreData = () => (
<IconContext.Provider value={{ color: 'lightblue', size: '24', style: { verticalAlign: 'middle' } }}>
{!coreData.connected && <MessageBox my={2} level="error" message="EMSbus disconnected, check settings and board profile" />}
{coreData.connected && coreData.devices.length === 0 && <MessageBox my={2} level="warning" message="Scanning for EMS devices..." />}
{!coreData.connected && <MessageBox my={2} level="error" message={LL.EMS_BUS_WARNING()} />}
{coreData.connected && coreData.devices.length === 0 && (
<MessageBox my={2} level="warning" message={LL.EMS_BUS_SCANNING()} />
)}
<Table data={{ nodes: coreData.devices }} select={device_select} theme={device_theme} layout={{ custom: true }}>
{(tableList: any) => (
<>
<Header>
<HeaderRow>
<HeaderCell stiff />
<HeaderCell stiff>TYPE</HeaderCell>
<HeaderCell resize>DESCRIPTION</HeaderCell>
<HeaderCell stiff>ENTITIES</HeaderCell>
<HeaderCell stiff>{LL.TYPE()}</HeaderCell>
<HeaderCell resize>{LL.DESCRIPTION()}</HeaderCell>
<HeaderCell stiff>{LL.ENTITIES()}</HeaderCell>
<HeaderCell stiff />
</HeaderRow>
</Header>
@@ -658,9 +705,9 @@ const DashboardData: FC = () => {
{tableList.map((device: Device, index: number) => (
<Row key={device.id} item={device}>
<Cell stiff>
<DeviceIcon type={device.t} />
<DeviceIcon type_id={device.t} />
</Cell>
<Cell stiff>{device.t}</Cell>
<Cell stiff>{device.tn}</Cell>
<Cell>{device.n}</Cell>
<Cell stiff>{device.e}</Cell>
<Cell stiff>
@@ -673,10 +720,10 @@ const DashboardData: FC = () => {
{(coreData.active_sensors > 0 || coreData.analog_enabled) && (
<Row key="sensor" item={{ id: 'sensor' }}>
<Cell>
<DeviceIcon type="Sensor" />
<DeviceIcon type_id={1} />
</Cell>
<Cell>Sensors</Cell>
<Cell>Attached EMS-ESP Sensors</Cell>
<Cell>{coreData.s_n}</Cell>
<Cell>{LL.ATTACHED_SENSORS()}</Cell>
<Cell>{coreData.active_sensors}</Cell>
<Cell>
<IconButton size="small" onClick={() => addAnalogSensor()}>
@@ -723,7 +770,7 @@ const DashboardData: FC = () => {
control={<Checkbox size="small" name="onlyFav" checked={onlyFav} onChange={() => setOnlyFav(!onlyFav)} />}
label={
<span style={{ fontSize: '12px' }}>
only show favorites&nbsp;
{LL.SHOW_FAV()}&nbsp;
<StarIcon color="primary" sx={{ fontSize: 12 }} />
</span>
}
@@ -749,7 +796,7 @@ const DashboardData: FC = () => {
endIcon={getSortIcon(dv_sort.state, 'NAME')}
onClick={() => dv_sort.fns.onToggleSort({ sortKey: 'NAME' })}
>
ENTITY NAME
{LL.ENTITY_NAME()}
</Button>
</HeaderCell>
<HeaderCell resize>
@@ -759,7 +806,7 @@ const DashboardData: FC = () => {
endIcon={getSortIcon(dv_sort.state, 'VALUE')}
onClick={() => dv_sort.fns.onToggleSort({ sortKey: 'VALUE' })}
>
VALUE
{LL.VALUE(0)}
</Button>
</HeaderCell>
<HeaderCell stiff />
@@ -806,7 +853,7 @@ const DashboardData: FC = () => {
const renderDallasData = () => (
<>
<Typography sx={{ pt: 2, pb: 1 }} variant="h6" color="secondary">
Temperature Sensors
{LL.TEMP_SENSORS()}
</Typography>
<Table
data={{ nodes: sensorData.sensors }}
@@ -825,7 +872,7 @@ const DashboardData: FC = () => {
endIcon={getSortIcon(sensor_sort.state, 'NAME')}
onClick={() => sensor_sort.fns.onToggleSort({ sortKey: 'NAME' })}
>
NAME
{LL.ENTITY_NAME()}
</Button>
</HeaderCell>
<HeaderCell stiff>
@@ -835,7 +882,7 @@ const DashboardData: FC = () => {
endIcon={getSortIcon(sensor_sort.state, 'TEMPERATURE')}
onClick={() => sensor_sort.fns.onToggleSort({ sortKey: 'TEMPERATURE' })}
>
TEMPERATURE
{LL.VALUE(0)}
</Button>
</HeaderCell>
<HeaderCell stiff />
@@ -865,7 +912,7 @@ const DashboardData: FC = () => {
const renderAnalogData = () => (
<>
<Typography sx={{ pt: 2, pb: 1 }} variant="h6" color="secondary">
Analog Sensors
{LL.ANALOG_SENSORS()}
</Typography>
<Table data={{ nodes: sensorData.analogs }} theme={analog_theme} sort={analog_sort} layout={{ custom: true }}>
@@ -890,7 +937,7 @@ const DashboardData: FC = () => {
endIcon={getSortIcon(analog_sort.state, 'NAME')}
onClick={() => analog_sort.fns.onToggleSort({ sortKey: 'NAME' })}
>
NAME
{LL.ENTITY_NAME()}
</Button>
</HeaderCell>
<HeaderCell stiff>
@@ -900,10 +947,10 @@ const DashboardData: FC = () => {
endIcon={getSortIcon(analog_sort.state, 'TYPE')}
onClick={() => analog_sort.fns.onToggleSort({ sortKey: 'TYPE' })}
>
TYPE
{LL.TYPE()}
</Button>
</HeaderCell>
<HeaderCell stiff>VALUE</HeaderCell>
<HeaderCell stiff>{LL.VALUE(0)}</HeaderCell>
<HeaderCell stiff />
</HeaderRow>
</Header>
@@ -943,14 +990,14 @@ const DashboardData: FC = () => {
});
if (response.status === 204) {
enqueueSnackbar('Analog deletion failed', { variant: 'error' });
enqueueSnackbar(LL.DELETION_OF(LL.ANALOG_SENSOR()) + ' ' + LL.FAILED(), { variant: 'error' });
} else if (response.status === 403) {
enqueueSnackbar('Access denied', { variant: 'error' });
enqueueSnackbar(LL.ACCESS_DENIED(), { variant: 'error' });
} else {
enqueueSnackbar('Analog sensor removed', { variant: 'success' });
enqueueSnackbar(LL.REMOVED_OF(LL.ANALOG_SENSOR()), { variant: 'success' });
}
} catch (error: unknown) {
enqueueSnackbar(extractErrorMessage(error, 'Problem updating analog sensor'), { variant: 'error' });
} catch (error) {
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
} finally {
setAnalog(undefined);
fetchSensorData();
@@ -971,14 +1018,14 @@ const DashboardData: FC = () => {
});
if (response.status === 204) {
enqueueSnackbar('Analog sensor update failed', { variant: 'error' });
enqueueSnackbar(LL.UPDATE_OF(LL.ANALOG_SENSOR()) + ' ' + LL.FAILED(), { variant: 'error' });
} else if (response.status === 403) {
enqueueSnackbar('Access denied', { variant: 'error' });
enqueueSnackbar(LL.ACCESS_DENIED(), { variant: 'error' });
} else {
enqueueSnackbar('Analog sensor updated', { variant: 'success' });
enqueueSnackbar(LL.UPDATED_OF(LL.ANALOG_SENSOR()), { variant: 'success' });
}
} catch (error: unknown) {
enqueueSnackbar(extractErrorMessage(error, 'Problem updating analog'), { variant: 'error' });
} catch (error) {
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
} finally {
setAnalog(undefined);
fetchSensorData();
@@ -990,32 +1037,42 @@ const DashboardData: FC = () => {
if (analog) {
return (
<Dialog open={analog !== undefined} onClose={() => setAnalog(undefined)}>
<DialogTitle>Edit Analog Sensor</DialogTitle>
<DialogTitle>
{LL.EDIT()} {LL.ANALOG_SENSOR()}
</DialogTitle>
<DialogContent dividers>
<Grid container spacing={2}>
<Grid item>
<Grid item xs={12}>
<ValidatedTextField
name="n"
label={LL.ENTITY_NAME()}
value={analog.n}
fullWidth
variant="outlined"
onChange={updateValue(setAnalog)}
/>
</Grid>
<Grid item xs={4}>
<ValidatedTextField
name="g"
label="GPIO"
value={analog.g}
fullWidth
type="number"
variant="outlined"
autoFocus
onChange={updateValue(setAnalog)}
/>
</Grid>
<Grid item>
<Grid item xs={8}>
<ValidatedTextField
name="n"
label="Name"
value={analog.n}
sx={{ width: '20ch' }}
variant="outlined"
name="t"
label={LL.TYPE()}
value={analog.t}
fullWidth
select
onChange={updateValue(setAnalog)}
/>
</Grid>
<Grid item>
<ValidatedTextField name="t" label="Type" value={analog.t} select onChange={updateValue(setAnalog)}>
>
{AnalogTypeNames.map((val, i) => (
<MenuItem key={i} value={i}>
{val}
@@ -1025,8 +1082,15 @@ const DashboardData: FC = () => {
</Grid>
{analog.t >= AnalogType.COUNTER && analog.t <= AnalogType.RATE && (
<>
<Grid item>
<ValidatedTextField name="u" label="UoM" value={analog.u} select onChange={updateValue(setAnalog)}>
<Grid item xs={4}>
<ValidatedTextField
name="u"
label={LL.UNIT()}
value={analog.u}
fullWidth
select
onChange={updateValue(setAnalog)}
>
{DeviceValueUOM_s.map((val, i) => (
<MenuItem key={i} value={i}>
{val}
@@ -1035,12 +1099,12 @@ const DashboardData: FC = () => {
</ValidatedTextField>
</Grid>
{analog.t === AnalogType.ADC && (
<Grid item>
<Grid item xs={4}>
<ValidatedTextField
name="o"
label="Offset"
label={LL.OFFSET()}
value={numberValue(analog.o)}
sx={{ width: '20ch' }}
fullWidth
type="number"
variant="outlined"
onChange={updateValue(setAnalog)}
@@ -1052,41 +1116,41 @@ const DashboardData: FC = () => {
</Grid>
)}
{analog.t === AnalogType.COUNTER && (
<Grid item>
<Grid item xs={4}>
<ValidatedTextField
name="o"
label="Start Value"
label={LL.STARTVALUE()}
value={numberValue(analog.o)}
sx={{ width: '20ch' }}
fullWidth
type="number"
variant="outlined"
onChange={updateValue(setAnalog)}
inputProps={{ min: '0', step: '1' }}
inputProps={{ step: '0.001' }}
/>
</Grid>
)}
<Grid item>
<Grid item xs={4}>
<ValidatedTextField
name="f"
label="Factor"
label={LL.FACTOR()}
value={numberValue(analog.f)}
sx={{ width: '20ch' }}
fullWidth
type="number"
variant="outlined"
onChange={updateValue(setAnalog)}
inputProps={{ min: '-100', max: '100', step: '0.1' }}
inputProps={{ step: '0.001' }}
/>
</Grid>
</>
)}
{analog.t === AnalogType.DIGITAL_OUT && (analog.id === '25' || analog.id === '26') && (
{analog.t === AnalogType.DIGITAL_OUT && (analog.g === 25 || analog.g === 26) && (
<>
<Grid item>
<Grid item xs={4}>
<ValidatedTextField
name="o"
label="DAC Value"
label={LL.VALUE(0)}
value={numberValue(analog.o)}
sx={{ width: '20ch' }}
fullWidth
type="number"
variant="outlined"
onChange={updateValue(setAnalog)}
@@ -1095,14 +1159,14 @@ const DashboardData: FC = () => {
</Grid>
</>
)}
{analog.t === AnalogType.DIGITAL_OUT && analog.id !== '25' && analog.id !== '26' && (
{analog.t === AnalogType.DIGITAL_OUT && analog.g !== 25 && analog.g !== 26 && (
<>
<Grid item>
<Grid item xs={4}>
<ValidatedTextField
name="o"
label="Value"
label={LL.VALUE(0)}
value={numberValue(analog.o)}
sx={{ width: '20ch' }}
fullWidth
type="number"
variant="outlined"
onChange={updateValue(setAnalog)}
@@ -1113,12 +1177,12 @@ const DashboardData: FC = () => {
)}
{analog.t >= AnalogType.PWM_0 && (
<>
<Grid item>
<Grid item xs={4}>
<ValidatedTextField
name="f"
label="Frequency"
label={LL.FREQ()}
value={numberValue(analog.f)}
sx={{ width: '20ch' }}
fullWidth
type="number"
variant="outlined"
onChange={updateValue(setAnalog)}
@@ -1128,12 +1192,12 @@ const DashboardData: FC = () => {
}}
/>
</Grid>
<Grid item>
<Grid item xs={4}>
<ValidatedTextField
name="o"
label="Dutycycle"
label={LL.DUTY_CYCLE()}
value={numberValue(analog.o)}
sx={{ width: '20ch' }}
fullWidth
type="number"
variant="outlined"
onChange={updateValue(setAnalog)}
@@ -1147,13 +1211,13 @@ const DashboardData: FC = () => {
)}
</Grid>
<Box color="warning.main" mt={2}>
<Typography variant="body2">Warning: be careful when assigning a GPIO!</Typography>
<Typography variant="body2">{LL.WARN_GPIO()}</Typography>
</Box>
</DialogContent>
<DialogActions>
<Box flexGrow={1} sx={{ '& button': { mt: 0 } }}>
<Button startIcon={<RemoveIcon />} variant="outlined" color="error" onClick={() => sendRemoveAnalog()}>
Remove
{LL.REMOVE()}
</Button>
</Box>
<Button
@@ -1162,7 +1226,7 @@ const DashboardData: FC = () => {
onClick={() => setAnalog(undefined)}
color="secondary"
>
Cancel
{LL.CANCEL()}
</Button>
<Button
startIcon={<SaveIcon />}
@@ -1171,7 +1235,7 @@ const DashboardData: FC = () => {
onClick={() => sendAnalog()}
color="warning"
>
Save
{LL.SAVE()}
</Button>
</DialogActions>
</Dialog>
@@ -1180,7 +1244,7 @@ const DashboardData: FC = () => {
};
return (
<SectionContent title="Device and Sensor Data" titleGutter>
<SectionContent title={LL.DEVICE_SENSOR_DATA()} titleGutter>
{renderCoreData()}
{renderDeviceData()}
{renderDeviceDialog()}
@@ -1191,11 +1255,11 @@ const DashboardData: FC = () => {
{renderAnalogDialog()}
<ButtonRow>
<Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={refreshData}>
Refresh
{LL.REFRESH()}
</Button>
{device_select.state.id && device_select.state.id !== 'sensor' && (
<Button startIcon={<DownloadIcon />} variant="outlined" onClick={handleDownloadCsv}>
Export
{LL.EXPORT()}
</Button>
)}
</ButtonRow>

View File

@@ -32,10 +32,13 @@ import { ButtonRow, FormLoader, SectionContent } from '../components';
import { Status, busConnectionStatus, Stat } from './types';
import { formatDurationSec, pluralize, extractErrorMessage, useRest } from '../utils';
import { extractErrorMessage, useRest } from '../utils';
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;
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) => {
if (stat.q === 0 || stat.s + stat.f === 0) {
return;
@@ -81,12 +71,32 @@ const showQuality = (stat: Stat) => {
const DashboardStatus: FC = () => {
const { loadData, data, errorMessage } = useRest<Status>({ read: EMSESP.readStatus });
const { LL } = useI18nContext();
const theme = useTheme();
const [confirmScan, setConfirmScan] = useState<boolean>(false);
const { enqueueSnackbar } = useSnackbar();
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({
Table: `
--data-table-library_grid-template-columns: repeat(1, minmax(0, 1fr)) 90px 90px 80px;
@@ -137,24 +147,44 @@ const DashboardStatus: FC = () => {
const scan = async () => {
try {
await EMSESP.scanDevices();
enqueueSnackbar('Scanning for devices...', { variant: 'info' });
} catch (error: unknown) {
enqueueSnackbar(extractErrorMessage(error, 'Problem initiating scan'), { variant: 'error' });
enqueueSnackbar(LL.SCANNING() + '...', { variant: 'info' });
} catch (error) {
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
} finally {
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 = () => (
<Dialog open={confirmScan} onClose={() => setConfirmScan(false)}>
<DialogTitle>EMS Device Scan</DialogTitle>
<DialogContent dividers>Are you sure you want to initiate a full device scan of the EMS bus?</DialogContent>
<DialogTitle>{LL.SCAN_DEVICES()}</DialogTitle>
<DialogContent dividers>{LL.EMS_SCAN()}</DialogContent>
<DialogActions>
<Button startIcon={<CancelIcon />} variant="outlined" onClick={() => setConfirmScan(false)} color="secondary">
Cancel
{LL.CANCEL()}
</Button>
<Button startIcon={<PermScanWifiIcon />} variant="outlined" onClick={scan} color="primary" autoFocus>
Scan
{LL.SCAN()}
</Button>
</DialogActions>
</Dialog>
@@ -174,7 +204,10 @@ const DashboardStatus: FC = () => {
<DirectionsBusIcon />
</Avatar>
</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>
<ListItemAvatar>
@@ -183,13 +216,13 @@ const DashboardStatus: FC = () => {
</Avatar>
</ListItemAvatar>
<ListItemText
primary="Active Devices &amp; Sensors"
primary={LL.ACTIVE_DEVICES()}
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>
@@ -200,15 +233,15 @@ const DashboardStatus: FC = () => {
<Header>
<HeaderRow>
<HeaderCell resize></HeaderCell>
<HeaderCell stiff>SUCCESS</HeaderCell>
<HeaderCell stiff>FAIL</HeaderCell>
<HeaderCell stiff>QUALITY</HeaderCell>
<HeaderCell stiff>{LL.SUCCESS()}</HeaderCell>
<HeaderCell stiff>{LL.FAIL()}</HeaderCell>
<HeaderCell stiff>{LL.QUALITY()}</HeaderCell>
</HeaderRow>
</Header>
<Body>
{tableList.map((stat: Stat) => (
<Row key={stat.id} item={stat}>
<Cell>{stat.id}</Cell>
<Cell>{showName(stat.id)}</Cell>
<Cell stiff>{Intl.NumberFormat().format(stat.s)}</Cell>
<Cell stiff>{Intl.NumberFormat().format(stat.f)}</Cell>
<Cell stiff>{showQuality(stat)}</Cell>
@@ -223,7 +256,7 @@ const DashboardStatus: FC = () => {
<Box display="flex" flexWrap="wrap">
<Box flexGrow={1} sx={{ '& button': { mt: 2 } }}>
<Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={loadData}>
Refresh
{LL.REFRESH()}
</Button>
</Box>
<Box flexWrap="nowrap" whiteSpace="nowrap">
@@ -235,7 +268,7 @@ const DashboardStatus: FC = () => {
disabled={!me.admin}
onClick={() => setConfirmScan(true)}
>
Scan for new devices
{LL.SCAN_DEVICES()}
</Button>
</ButtonRow>
</Box>
@@ -245,7 +278,7 @@ const DashboardStatus: FC = () => {
};
return (
<SectionContent title="EMS Bus &amp; Activity Status" titleGutter>
<SectionContent title={LL.EMS_BUS_STATUS_TITLE()} titleGutter>
{content()}
</SectionContent>
);

View File

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

View File

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

View File

@@ -9,14 +9,18 @@ import { useSnackbar } from 'notistack';
import CommentIcon from '@mui/icons-material/CommentTwoTone';
import MenuBookIcon from '@mui/icons-material/MenuBookTwoTone';
import GitHubIcon from '@mui/icons-material/GitHub';
import StarIcon from '@mui/icons-material/Star';
import DownloadIcon from '@mui/icons-material/GetApp';
import EastIcon from '@mui/icons-material/East';
import { extractErrorMessage } from '../utils';
import { useI18nContext } from '../i18n/i18n-react';
import * as EMSESP from './api';
const HelpInformation: FC = () => {
const { LL } = useI18nContext();
const { enqueueSnackbar } = useSnackbar();
const saveFile = (json: any, endpoint: string) => {
@@ -31,7 +35,7 @@ const HelpInformation: FC = () => {
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
enqueueSnackbar('System information downloaded', { variant: 'info' });
enqueueSnackbar(LL.DOWNLOAD_SUCCESSFUL(), { variant: 'info' });
};
const callAPI = async (endpoint: string) => {
@@ -42,83 +46,84 @@ const HelpInformation: FC = () => {
id: 0
});
if (response.status !== 200) {
enqueueSnackbar('API call failed', { variant: 'error' });
enqueueSnackbar(LL.PROBLEM_LOADING(), { variant: 'error' });
} else {
saveFile(response.data, endpoint);
}
} catch (error: unknown) {
enqueueSnackbar(extractErrorMessage(error, 'Problem with downloading'), { variant: 'error' });
} catch (error) {
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' });
}
};
return (
<SectionContent title="Support Information" titleGutter>
<SectionContent title={LL.SUPPORT_INFORMATION()} titleGutter>
<List>
<ListItem>
<ListItemAvatar>
<MenuBookIcon />
<MenuBookIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} />
</ListItemAvatar>
<ListItemText>
Visit the online&nbsp;
{LL.HELP_INFORMATION_1()}&nbsp;
<EastIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} />
&nbsp;
<Link target="_blank" href="https://emsesp.github.io/docs" color="primary">
{'Wiki'}
{LL.CLICK_HERE()}
</Link>
&nbsp;to get instructions on how to&nbsp;
<Link
target="_blank"
href="https://emsesp.github.io/docs/#/Configure-firmware?id=ems-esp-settings"
color="primary"
>
{'configure'}
</Link>
&nbsp;EMS-ESP and access other information.
</ListItemText>
</ListItem>
<ListItem>
<ListItemAvatar>
<CommentIcon />
<CommentIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} />
</ListItemAvatar>
<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">
{'Discord'}
{LL.CLICK_HERE()}
</Link>
&nbsp;server.
</ListItemText>
</ListItem>
<ListItem>
<ListItemAvatar>
<GitHubIcon />
<GitHubIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} />
</ListItemAvatar>
<ListItemText>
Submit a&nbsp;
{LL.HELP_INFORMATION_3()}&nbsp;
<EastIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} />
<Link target="_blank" href="https://github.com/emsesp/EMS-ESP32/issues/new/choose" color="primary">
support issue
{LL.CLICK_HERE()}
</Link>
&nbsp;for requesting a new feature or reporting a bug.
<br />
Make sure you also&nbsp;
<Button startIcon={<DownloadIcon />} variant="outlined" color="primary" onClick={() => callAPI('info')}>
download
<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; and attach your system details for a faster response.
&nbsp;)
</ListItemText>
</ListItem>
</List>
<Box border={1} p={1} mt={4}>
<Typography align="center" variant="h6" color="orange">
EMS-ESP will always be a free and open-source project
<br></br>Please consider supporting it with a&nbsp;
<StarIcon style={{ fontSize: 16, color: 'yellow', verticalAlign: 'middle' }} /> on&nbsp;
<Link href="https://github.com/emsesp/EMS-ESP32" color="primary">
{'GitHub'}
<Box border={1} p={1} mt={4} color="orange">
<Typography align="center" variant="subtitle1" color="orange">
<b>{LL.HELP_INFORMATION_5()}</b>
</Typography>
<Typography align="center">
<Link target="_blank" href="https://github.com/emsesp/EMS-ESP32" color="primary">
{'github.com/emsesp/EMS-ESP32'}
</Link>
</Typography>
<Typography align="center">@proddy @MichaelDvP</Typography>
<Typography color="white" align="center">
@proddy @MichaelDvP
</Typography>
</Box>
</SectionContent>
);

View File

@@ -13,9 +13,13 @@ import VisibilityOutlinedIcon from '@mui/icons-material/VisibilityOutlined';
import CommentsDisabledOutlinedIcon from '@mui/icons-material/CommentsDisabledOutlined';
import InsertCommentOutlinedIcon from '@mui/icons-material/InsertCommentOutlined';
type OptionType = 'readonly' | 'web_exclude' | 'api_mqtt_exclude' | 'favorite';
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],

View File

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

View File

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

View File

@@ -3,7 +3,7 @@ import { ValidateFieldsError } from 'async-validator';
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 PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
@@ -24,6 +24,9 @@ import { numberValue, extractErrorMessage, updateValue, useRest } from '../utils
import * as EMSESP from './api';
import { Settings, BOARD_PROFILES } from './types';
import { useI18nContext } from '../i18n/i18n-react';
import RestartMonitor from '../framework/system/RestartMonitor';
export function boardProfileSelectItems() {
return Object.keys(BOARD_PROFILES).map((code) => (
<MenuItem key={code} value={code}>
@@ -37,6 +40,9 @@ const SettingsApplication: FC = () => {
read: EMSESP.readSettings,
update: EMSESP.writeSettings
});
const [restarting, setRestarting] = useState<boolean>();
const { LL } = useI18nContext();
const { enqueueSnackbar } = useSnackbar();
@@ -64,8 +70,8 @@ const SettingsApplication: FC = () => {
eth_clock_mode: response.data.eth_clock_mode
});
}
} catch (error: unknown) {
enqueueSnackbar(extractErrorMessage(error, 'Problem fetching board profile'), { variant: 'error' });
} catch (error) {
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
} finally {
setProcessingBoard(false);
}
@@ -102,28 +108,26 @@ const SettingsApplication: FC = () => {
validateAndSubmit();
try {
await EMSESP.restart();
enqueueSnackbar('EMS-ESP is restarting...', { variant: 'info' });
} catch (error: unknown) {
enqueueSnackbar(extractErrorMessage(error, 'Problem restarting device'), { variant: 'error' });
setRestarting(true);
} catch (error) {
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
}
};
return (
<>
<Typography sx={{ pt: 2 }} variant="h6" color="primary">
Interface Board Profile
{LL.INTERFACE_BOARD_PROFILE()}
</Typography>
<Box color="warning.main">
<Typography variant="body2">
Select a pre-configured interface board profile from the list below or choose "Custom" to configure your own
hardware settings.
</Typography>
<Typography variant="body2">{LL.BOARD_PROFILE_TEXT()}</Typography>
</Box>
<ValidatedTextField
name="board_profile"
label="Board Profile"
label={LL.BOARD_PROFILE()}
value={data.board_profile}
disabled={processingBoard}
fullWidth
variant="outlined"
onChange={changeBoardProfile}
margin="normal"
@@ -132,17 +136,24 @@ const SettingsApplication: FC = () => {
{boardProfileSelectItems()}
<Divider />
<MenuItem key={'CUSTOM'} value={'CUSTOM'}>
Custom&hellip;
{LL.CUSTOM()}&hellip;
</MenuItem>
</ValidatedTextField>
{data.board_profile === 'CUSTOM' && (
<>
<Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start">
<Grid item xs={4}>
<Grid
container
spacing={1}
sx={{ pt: 1 }}
direction="row"
justifyContent="flex-start"
alignItems="flex-start"
>
<Grid item xs={6} sm={4}>
<ValidatedTextField
fieldErrors={fieldErrors}
name="rx_gpio"
label="Rx GPIO"
label={LL.GPIO_OF('Rx')}
fullWidth
variant="outlined"
value={numberValue(data.rx_gpio)}
@@ -152,11 +163,11 @@ const SettingsApplication: FC = () => {
disabled={saving}
/>
</Grid>
<Grid item xs={4}>
<Grid item xs={6} sm={4}>
<ValidatedTextField
fieldErrors={fieldErrors}
name="tx_gpio"
label="Tx GPIO"
label={LL.GPIO_OF('Tx')}
fullWidth
variant="outlined"
value={numberValue(data.tx_gpio)}
@@ -166,11 +177,11 @@ const SettingsApplication: FC = () => {
disabled={saving}
/>
</Grid>
<Grid item xs={4}>
<Grid item xs={6} sm={4}>
<ValidatedTextField
fieldErrors={fieldErrors}
name="pbutton_gpio"
label="Button GPIO"
label={LL.GPIO_OF(LL.BUTTON())}
fullWidth
variant="outlined"
value={numberValue(data.pbutton_gpio)}
@@ -180,11 +191,11 @@ const SettingsApplication: FC = () => {
disabled={saving}
/>
</Grid>
<Grid item>
<Grid item xs={6} sm={4}>
<ValidatedTextField
fieldErrors={fieldErrors}
name="dallas_gpio"
label="Temperature GPIO (0=disabled)"
label={LL.GPIO_OF(LL.TEMPERATURE()) + ' (0=' + LL.DISABLED(1) + ')'}
fullWidth
variant="outlined"
value={numberValue(data.dallas_gpio)}
@@ -194,11 +205,11 @@ const SettingsApplication: FC = () => {
disabled={saving}
/>
</Grid>
<Grid item>
<Grid item xs={6} sm={4}>
<ValidatedTextField
fieldErrors={fieldErrors}
name="led_gpio"
label="LED GPIO (0=disabled)"
label={LL.GPIO_OF('LED') + ' (0=' + LL.DISABLED(1) + ')'}
fullWidth
variant="outlined"
value={numberValue(data.led_gpio)}
@@ -208,11 +219,10 @@ const SettingsApplication: FC = () => {
disabled={saving}
/>
</Grid>
</Grid>
<Grid item xs={4}>
<Grid item xs={6} sm={4}>
<ValidatedTextField
name="phy_type"
label="Eth PHY Type"
label={LL.PHY_TYPE()}
disabled={saving}
value={data.phy_type}
fullWidth
@@ -221,17 +231,25 @@ const SettingsApplication: FC = () => {
margin="normal"
select
>
<MenuItem value={0}>No Ethernet Module</MenuItem>
<MenuItem value={0}>{LL.DISABLED(1)}</MenuItem>
<MenuItem value={1}>LAN8720</MenuItem>
<MenuItem value={2}>TLK110</MenuItem>
</ValidatedTextField>
</Grid>
</Grid>
{data.phy_type !== 0 && (
<Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start">
<Grid item>
<Grid
container
spacing={1}
sx={{ pt: 1 }}
direction="row"
justifyContent="flex-start"
alignItems="flex-start"
>
<Grid item xs={6} sm={4}>
<ValidatedTextField
name="eth_power"
label="Eth Power GPIO (-1=disabled)"
label={LL.GPIO_OF('PHY Power') + ' (-1=' + LL.DISABLED(1) + ')'}
fullWidth
variant="outlined"
value={numberValue(data.eth_power)}
@@ -241,10 +259,10 @@ const SettingsApplication: FC = () => {
disabled={saving}
/>
</Grid>
<Grid item>
<Grid item xs={6} sm={4}>
<ValidatedTextField
name="eth_phy_addr"
label="Eth I²C-address"
label={LL.ADDRESS_OF('PHY I²C')}
fullWidth
variant="outlined"
value={numberValue(data.eth_phy_addr)}
@@ -254,10 +272,10 @@ const SettingsApplication: FC = () => {
disabled={saving}
/>
</Grid>
<Grid item>
<Grid item xs={6} sm={4}>
<ValidatedTextField
name="eth_clock_mode"
label="Eth Clock Mode"
label="PHY Clk"
disabled={saving}
value={data.eth_clock_mode}
fullWidth
@@ -276,14 +294,14 @@ const SettingsApplication: FC = () => {
)}
</>
)}
<Typography variant="h6" color="primary">
EMS Bus Settings
<Typography sx={{ pt: 2 }} variant="h6" color="primary">
{LL.SETTINGS_OF(LL.EMS_BUS(0))}
</Typography>
<Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start">
<Grid item xs={6}>
<ValidatedTextField
name="tx_mode"
label="Tx Mode"
label={LL.TX_MODE()}
disabled={saving}
value={data.tx_mode}
fullWidth
@@ -295,13 +313,13 @@ const SettingsApplication: FC = () => {
<MenuItem value={1}>EMS</MenuItem>
<MenuItem value={2}>EMS+</MenuItem>
<MenuItem value={3}>HT3</MenuItem>
<MenuItem value={4}>Hardware</MenuItem>
<MenuItem value={4}>{LL.HARDWARE()}</MenuItem>
</ValidatedTextField>
</Grid>
<Grid item xs={6}>
<ValidatedTextField
name="ems_bus_id"
label="Bus ID"
label={LL.ID_OF(LL.EMS_BUS(1))}
disabled={saving}
value={data.ems_bus_id}
fullWidth
@@ -310,102 +328,143 @@ const SettingsApplication: FC = () => {
margin="normal"
select
>
<MenuItem value={0x0a}>Terminal (0x0A)</MenuItem>
<MenuItem value={0x0b}>Service Key (0x0B)</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={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>
</Grid>
</Grid>
<Typography sx={{ pt: 2 }} variant="h6" color="primary">
General Options
{LL.GENERAL_OPTIONS()}
</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 && (
<BlockFormControlLabel
control={<Checkbox checked={data.hide_led} onChange={updateFormValue} name="hide_led" />}
label="Hide LED"
label={LL.HIDE_LED()}
disabled={saving}
/>
)}
<BlockFormControlLabel
control={<Checkbox checked={data.telnet_enabled} onChange={updateFormValue} name="telnet_enabled" />}
label="Enable Telnet Console"
label={LL.ENABLE_TELNET()}
disabled={saving}
/>
<BlockFormControlLabel
control={<Checkbox checked={data.analog_enabled} onChange={updateFormValue} name="analog_enabled" />}
label="Enable Analog Sensors"
label={LL.ENABLE_ANALOG()}
disabled={saving}
/>
<BlockFormControlLabel
control={<Checkbox checked={data.fahrenheit} onChange={updateFormValue} name="fahrenheit" />}
label="Convert temperature values to Fahrenheit"
label={LL.CONVERT_FAHRENHEIT()}
disabled={saving}
/>
<BlockFormControlLabel
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}
/>
<BlockFormControlLabel
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}
/>
<BlockFormControlLabel
control={<Checkbox checked={data.low_clock} onChange={updateFormValue} name="low_clock" />}
label="Underclock CPU speed"
label={LL.UNDERCLOCK_CPU()}
disabled={saving}
/>
<Grid container spacing={0} direction="row" justifyContent="flex-start" alignItems="flex-start">
<BlockFormControlLabel
control={<Checkbox checked={data.shower_timer} onChange={updateFormValue} name="shower_timer" />}
label="Enable Shower Timer"
label={LL.ENABLE_SHOWER_TIMER()}
disabled={saving}
/>
<BlockFormControlLabel
sx={{ pb: 2 }}
control={<Checkbox checked={data.shower_alert} onChange={updateFormValue} name="shower_alert" />}
label="Enable Shower Alert"
label={LL.ENABLE_SHOWER_ALERT()}
disabled={!data.shower_timer}
/>
{data.shower_alert && (
<>
<Grid item xs={2}>
<Grid item sx={{ pr: 1, pb: 2 }}>
<ValidatedTextField
fieldErrors={fieldErrors}
name="shower_alert_trigger"
label="Trigger Time (minutes)"
label={LL.TRIGGER_TIME()}
InputProps={{
endAdornment: <InputAdornment position="end">{LL.MINUTES()}</InputAdornment>
}}
variant="outlined"
value={data.shower_alert_trigger}
type="number"
onChange={updateFormValue}
size="small"
disabled={!data.shower_timer}
/>
</Grid>
<Grid item xs={2}>
<Grid item sx={{ pb: 3 }}>
<ValidatedTextField
fieldErrors={fieldErrors}
name="shower_alert_coldshot"
label="Cold Shot Time (seconds)"
label={LL.COLD_SHOT_DURATION()}
InputProps={{
endAdornment: <InputAdornment position="end">{LL.SECONDS()}</InputAdornment>
}}
variant="outlined"
value={data.shower_alert_coldshot}
type="number"
onChange={updateFormValue}
size="small"
disabled={!data.shower_timer}
/>
</Grid>
</>
)}
</Grid>
<Typography sx={{ pt: 2 }} variant="h6" color="primary">
Formatting Options
<Typography variant="h6" color="primary">
{LL.FORMATTING_OPTIONS()}
</Typography>
<Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start">
<Grid item xs={4}>
<Grid item xs={6} sm={4}>
<ValidatedTextField
name="bool_dashboard"
label="Boolean Format Dashboard"
label={LL.BOOLEAN_FORMAT_DASHBOARD()}
value={data.bool_dashboard}
fullWidth
variant="outlined"
@@ -413,16 +472,16 @@ const SettingsApplication: FC = () => {
margin="normal"
select
>
<MenuItem value={1}>on/off</MenuItem>
<MenuItem value={2}>ON/OFF</MenuItem>
<MenuItem value={1}>{LL.ONOFF()}</MenuItem>
<MenuItem value={2}>{LL.ONOFF_CAP()}</MenuItem>
<MenuItem value={3}>true/false</MenuItem>
<MenuItem value={5}>1/0</MenuItem>
</ValidatedTextField>
</Grid>
<Grid item xs={4}>
<Grid item xs={6} sm={4}>
<ValidatedTextField
name="bool_format"
label="Boolean Format API/MQTT"
label={LL.BOOLEAN_FORMAT_API()}
value={data.bool_format}
fullWidth
variant="outlined"
@@ -430,18 +489,18 @@ const SettingsApplication: FC = () => {
margin="normal"
select
>
<MenuItem value={1}>"on"/"off"</MenuItem>
<MenuItem value={2}>"ON"/"OFF"</MenuItem>
<MenuItem value={1}>{LL.ONOFF()}</MenuItem>
<MenuItem value={2}>{LL.ONOFF_CAP()}</MenuItem>
<MenuItem value={3}>"true"/"false"</MenuItem>
<MenuItem value={4}>true/false</MenuItem>
<MenuItem value={5}>"1"/"0"</MenuItem>
<MenuItem value={6}>1/0</MenuItem>
</ValidatedTextField>
</Grid>
<Grid item xs={4}>
<Grid item xs={6} sm={4}>
<ValidatedTextField
name="enum_format"
label="Enum Format API/MQTT"
label={LL.ENUM_FORMAT()}
value={data.enum_format}
fullWidth
variant="outlined"
@@ -449,29 +508,29 @@ const SettingsApplication: FC = () => {
margin="normal"
select
>
<MenuItem value={1}>Value</MenuItem>
<MenuItem value={2}>Index</MenuItem>
<MenuItem value={1}>{LL.VALUE(1)}</MenuItem>
<MenuItem value={2}>{LL.INDEX()}</MenuItem>
</ValidatedTextField>
</Grid>
</Grid>
{data.dallas_gpio !== 0 && (
<>
<Typography sx={{ pt: 2 }} variant="h6" color="primary">
Temperature Sensors
{LL.TEMP_SENSORS()}
</Typography>
<BlockFormControlLabel
control={<Checkbox checked={data.dallas_parasite} onChange={updateFormValue} name="dallas_parasite" />}
label="Enable parasite power"
label={LL.ENABLE_PARASITE()}
disabled={saving}
/>
</>
)}
<Typography sx={{ pt: 2 }} variant="h6" color="primary">
Logging
{LL.LOGGING()}
</Typography>
<BlockFormControlLabel
control={<Checkbox checked={data.trace_raw} onChange={updateFormValue} name="trace_raw" />}
label="Log EMS telegrams in hexadecimal"
label={LL.LOG_HEX()}
disabled={saving}
/>
<BlockFormControlLabel
@@ -483,11 +542,11 @@ const SettingsApplication: FC = () => {
disabled={saving}
/>
}
label="Enable Syslog"
label={LL.ENABLE_SYSLOG()}
/>
{data.syslog_enabled && (
<Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start">
<Grid item xs={5}>
<Grid item xs={4}>
<ValidatedTextField
fieldErrors={fieldErrors}
name="syslog_host"
@@ -500,7 +559,7 @@ const SettingsApplication: FC = () => {
disabled={saving}
/>
</Grid>
<Grid item xs={6}>
<Grid item xs={4}>
<ValidatedTextField
fieldErrors={fieldErrors}
name="syslog_port"
@@ -514,10 +573,10 @@ const SettingsApplication: FC = () => {
disabled={saving}
/>
</Grid>
<Grid item xs={5}>
<Grid item xs={4}>
<ValidatedTextField
name="syslog_level"
label="Log Level"
label={LL.LOG_LEVEL()}
value={data.syslog_level}
fullWidth
variant="outlined"
@@ -534,11 +593,14 @@ const SettingsApplication: FC = () => {
<MenuItem value={9}>ALL</MenuItem>
</ValidatedTextField>
</Grid>
<Grid item xs={6}>
<Grid item xs={4}>
<ValidatedTextField
fieldErrors={fieldErrors}
name="syslog_mark_interval"
label="Mark Interval (seconds, 0=off)"
label={LL.MARK_INTERVAL()}
InputProps={{
endAdornment: <InputAdornment position="end">{LL.SECONDS()}</InputAdornment>
}}
fullWidth
variant="outlined"
value={data.syslog_mark_interval}
@@ -551,9 +613,9 @@ const SettingsApplication: FC = () => {
</Grid>
)}
{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}>
Restart
{LL.RESTART()}
</Button>
</MessageBox>
)}
@@ -567,7 +629,7 @@ const SettingsApplication: FC = () => {
type="submit"
onClick={validateAndSubmit}
>
Save
{LL.SAVE()}
</Button>
</ButtonRow>
)}
@@ -576,8 +638,8 @@ const SettingsApplication: FC = () => {
};
return (
<SectionContent title="Application Settings" titleGutter>
{content()}
<SectionContent title={LL.APPLICATION_SETTINGS()} titleGutter>
{restarting ? <RestartMonitor /> : content()}
</SectionContent>
);
};

View File

@@ -17,9 +17,11 @@ import {
Link
} from '@mui/material';
import { MessageBox } from '../components';
import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
import { Table } from '@table-library/react-table-library/table';
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 { useSnackbar } from 'notistack';
@@ -28,9 +30,6 @@ import SaveIcon from '@mui/icons-material/Save';
import CancelIcon from '@mui/icons-material/Cancel';
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 FilterListIcon from '@mui/icons-material/FilterList';
@@ -40,16 +39,25 @@ import { ButtonRow, FormLoader, ValidatedTextField, SectionContent } from '../co
import * as EMSESP from './api';
import { extractErrorMessage } from '../utils';
import { extractErrorMessage, updateValue } from '../utils';
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 { LL } = useI18nContext();
const [restarting, setRestarting] = useState<boolean>(false);
const [restartNeeded, setRestartNeeded] = useState<boolean>(false);
const { enqueueSnackbar } = useSnackbar();
const [deviceEntities, setDeviceEntities] = useState<DeviceEntity[]>([{ id: '', v: 0, n: '', 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 [errorMessage, setErrorMessage] = useState<string>();
const [selectedDevice, setSelectedDevice] = useState<number>(-1);
@@ -57,12 +65,14 @@ const SettingsCustomization: FC = () => {
const [selectedFilters, setSelectedFilters] = useState<number>(0);
const [search, setSearch] = useState('');
const [deviceEntity, setDeviceEntity] = useState<DeviceEntity>();
// eslint-disable-next-line
const [masks, setMasks] = useState(() => ['']);
const entities_theme = useTheme({
Table: `
--data-table-library_grid-template-columns: 120px repeat(1, minmax(0, 1fr)) 120px;
--data-table-library_grid-template-columns: 150px repeat(1, minmax(80px, 1fr)) 45px 45px 120px;
`,
BaseRow: `
font-size: 14px;
@@ -71,6 +81,12 @@ const SettingsCustomization: FC = () => {
}
`,
BaseCell: `
&:nth-of-type(3) {
text-align: right;
}
&:nth-of-type(4) {
text-align: right;
}
&:last-of-type {
text-align: right;
}
@@ -92,6 +108,7 @@ const SettingsCustomization: FC = () => {
Row: `
background-color: #1e1e1e;
position: relative;
cursor: pointer;
.td {
border-top: 1px solid #565656;
@@ -104,6 +121,11 @@ const SettingsCustomization: FC = () => {
font-weight: normal;
}
&:hover .td {
border-top: 1px solid #177ac9;
border-bottom: 1px solid #177ac9;
}
&:nth-of-type(odd) .td {
background-color: #303030;
}
@@ -112,56 +134,36 @@ const SettingsCustomization: FC = () => {
&:nth-of-type(2) {
padding: 8px;
}
&:nth-of-type(3) {
padding-right: 4px;
}
&:nth-of-type(4) {
padding-right: 4px;
}
&:last-of-type {
padding-right: 8px;
}
`
});
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 () => {
try {
setDevices((await EMSESP.readDevices()).data);
} catch (error: unknown) {
setErrorMessage(extractErrorMessage(error, 'Failed to fetch device list'));
} catch (error) {
setErrorMessage(extractErrorMessage(error, LL.PROBLEM_LOADING()));
}
}, []);
}, [LL]);
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) => {
try {
const data = (await EMSESP.readDeviceEntities({ id: unique_id })).data;
setInitialMask(data);
} catch (error: unknown) {
setErrorMessage(extractErrorMessage(error, 'Problem fetching device entities'));
const new_deviceEntities = (await EMSESP.readDeviceEntities({ id: unique_id })).data;
setInitialMask(new_deviceEntities);
} catch (error) {
setErrorMessage(extractErrorMessage(error, LL.PROBLEM_LOADING()));
}
};
@@ -181,15 +183,10 @@ const SettingsCustomization: FC = () => {
}
function formatName(de: DeviceEntity) {
if (de.n === undefined || de.n === de.id) {
return de.id;
} else if (de.n === '') {
return 'Command: ' + de.id;
}
return (
<>
{de.n}&nbsp;(
<Link target="_blank" href={APIURL + devices?.devices[selectedDevice].t + '/' + de.id}>
{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>
)
@@ -219,6 +216,9 @@ const SettingsCustomization: FC = () => {
if ((m & 8) === 8) {
new_masks.push('8');
}
if ((m & 128) === 128) {
new_masks.push('128');
}
return new_masks;
};
@@ -244,43 +244,65 @@ const SettingsCustomization: FC = () => {
const selected_device = parseInt(event.target.value, 10);
setSelectedDevice(selected_device);
fetchDeviceEntities(devices?.devices[selected_device].i);
setRestartNeeded(false);
}
};
const resetCustomization = async () => {
try {
await EMSESP.resetCustomizations();
enqueueSnackbar('All customizations have been removed. Restarting...', { variant: 'info' });
} catch (error: unknown) {
enqueueSnackbar(extractErrorMessage(error, 'Problem resetting customizations'), { variant: 'error' });
enqueueSnackbar(LL.CUSTOMIZATIONS_RESTART(), { variant: 'info' });
} catch (error) {
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
} finally {
setConfirmReset(false);
}
};
const restart = async () => {
try {
await EMSESP.restart();
setRestarting(true);
} catch (error) {
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
}
};
const saveCustomization = async () => {
if (devices && deviceEntities && selectedDevice !== -1) {
const masked_entities = deviceEntities
.filter((de) => de.m !== de.om)
.map((new_de) => new_de.m.toString(16).padStart(2, '0') + new_de.id);
.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 : '')
);
if (masked_entities.length > 60) {
enqueueSnackbar('Selected entities exceeded limit of 60. Please Save in batches', { variant: 'warning' });
// 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;
}
try {
const response = await EMSESP.writeMaskedEntities({
const response = await EMSESP.writeCustomEntities({
id: devices?.devices[selectedDevice].i,
entity_ids: masked_entities
});
if (response.status === 200) {
enqueueSnackbar('Customization saved', { variant: 'success' });
enqueueSnackbar(LL.CUSTOMIZATIONS_SAVED(), { variant: 'success' });
} else if (response.status === 201) {
setRestartNeeded(true);
} else {
enqueueSnackbar('Customization save failed', { variant: 'error' });
enqueueSnackbar(LL.PROBLEM_UPDATING(), { variant: 'error' });
}
} catch (error: unknown) {
enqueueSnackbar(extractErrorMessage(error, 'Problem sending entity list'), { variant: 'error' });
} catch (error) {
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
}
setInitialMask(deviceEntities);
}
@@ -294,21 +316,18 @@ const SettingsCustomization: FC = () => {
return (
<>
<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} />
=mark as favorite&nbsp;&nbsp;
<OptionIcon type="readonly" isSet={true} />
=disable write action&nbsp;&nbsp;
<OptionIcon type="api_mqtt_exclude" isSet={true} />
=exclude from MQTT and API&nbsp;&nbsp;
<OptionIcon type="web_exclude" isSet={true} />
=hide from Dashboard
<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>
<ValidatedTextField
name="device"
label="EMS Device"
label={LL.EMS_DEVICE()}
variant="outlined"
fullWidth
value={selectedDevice}
@@ -317,7 +336,7 @@ const SettingsCustomization: FC = () => {
select
>
<MenuItem disabled key={0} value={-1}>
Select a device...
{LL.SELECT_DEVICE()}...
</MenuItem>
{devices.devices.map((device: DeviceShort, index) => (
<MenuItem key={index} value={index}>
@@ -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 = () => {
if (devices?.devices.length === 0 || deviceEntities[0].id === '') {
return;
@@ -389,6 +435,9 @@ const SettingsCustomization: FC = () => {
<ToggleButton value="1">
<OptionIcon type="web_exclude" isSet={true} />
</ToggleButton>
<ToggleButton value="128">
<OptionIcon type="deleted" isSet={true} />
</ToggleButton>
</ToggleButtonGroup>
</Grid>
@@ -401,7 +450,7 @@ const SettingsCustomization: FC = () => {
color="inherit"
onClick={() => maskDisabled(false)}
>
set all&nbsp;
{LL.SET_ALL()}&nbsp;
<OptionIcon type="api_mqtt_exclude" isSet={false} />
<OptionIcon type="web_exclude" isSet={false} />
</Button>
@@ -416,36 +465,34 @@ const SettingsCustomization: FC = () => {
color="inherit"
onClick={() => maskDisabled(true)}
>
set all&nbsp;
{LL.SET_ALL()}&nbsp;
<OptionIcon type="api_mqtt_exclude" isSet={true} />
<OptionIcon type="web_exclude" isSet={true} />
</Button>
</Tooltip>
</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) => (
<>
<Header>
<HeaderRow>
<HeaderCell stiff>OPTIONS</HeaderCell>
<HeaderCell stiff>{LL.OPTIONS()}</HeaderCell>
<HeaderCell resize>
<Button
fullWidth
style={{ fontSize: '14px', justifyContent: 'flex-start' }}
endIcon={getSortIcon(entity_sort.state, 'NAME')}
onClick={() => entity_sort.fns.onToggleSort({ sortKey: 'NAME' })}
>
NAME
<Button fullWidth style={{ fontSize: '14px', justifyContent: 'flex-start' }}>
{LL.NAME(1)}
</Button>
</HeaderCell>
<HeaderCell resize>VALUE</HeaderCell>
<HeaderCell stiff>{LL.MIN()}</HeaderCell>
<HeaderCell stiff>{LL.MAX()}</HeaderCell>
<HeaderCell resize>{LL.VALUE(0)}</HeaderCell>
</HeaderRow>
</Header>
<Body>
{tableList.map((de: DeviceEntity) => (
<Row key={de.id} item={de}>
<Row key={de.id} item={de} onClick={() => editEntity(de)}>
<Cell stiff>
{!deviceEntity && (
<ToggleButtonGroup
size="small"
color="secondary"
@@ -461,19 +508,19 @@ const SettingsCustomization: FC = () => {
setMasks(['']);
}}
>
<ToggleButton value="8" disabled={(de.m & 1) !== 0 || de.n === undefined}>
<ToggleButton value="8" disabled={(de.m & 0x81) !== 0 || de.n === undefined}>
<OptionIcon
type="favorite"
isSet={(de.m & DeviceEntityMask.DV_FAVORITE) === DeviceEntityMask.DV_FAVORITE}
/>
</ToggleButton>
<ToggleButton value="4" disabled={!de.w || (de.m & 3) === 3}>
<ToggleButton value="4" disabled={!de.w || (de.m & 0x83) >= 3}>
<OptionIcon
type="readonly"
isSet={(de.m & DeviceEntityMask.DV_READONLY) === DeviceEntityMask.DV_READONLY}
/>
</ToggleButton>
<ToggleButton value="2" disabled={de.n === ''}>
<ToggleButton value="2" disabled={de.n === '' || (de.m & 0x80) !== 0}>
<OptionIcon
type="api_mqtt_exclude"
isSet={
@@ -481,16 +528,25 @@ const SettingsCustomization: FC = () => {
}
/>
</ToggleButton>
<ToggleButton value="1" disabled={de.n === undefined}>
<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>{formatName(de)}</Cell>
<Cell>{formatValue(de.v)}</Cell>
<Cell>{!deviceEntity && formatName(de)}</Cell>
<Cell>{!deviceEntity && !(de.m & DeviceEntityMask.DV_READONLY) && formatValue(de.mi)}</Cell>
<Cell>{!deviceEntity && !(de.m & DeviceEntityMask.DV_READONLY) && formatValue(de.ma)}</Cell>
<Cell>{!deviceEntity && formatValue(de.v)}</Cell>
</Row>
))}
</Body>
@@ -503,14 +559,11 @@ const SettingsCustomization: FC = () => {
const renderResetDialog = () => (
<Dialog open={confirmReset} onClose={() => setConfirmReset(false)}>
<DialogTitle>Reset</DialogTitle>
<DialogContent dividers>
Are you sure you want remove all customizations including the custom settings of the Temperature and Analog
sensors?
</DialogContent>
<DialogTitle>{LL.RESET(1)}</DialogTitle>
<DialogContent dividers>{LL.CUSTOMIZATIONS_RESET()}</DialogContent>
<DialogActions>
<Button startIcon={<CancelIcon />} variant="outlined" onClick={() => setConfirmReset(false)} color="secondary">
Cancel
{LL.CANCEL()}
</Button>
<Button
startIcon={<SettingsBackupRestoreIcon />}
@@ -519,25 +572,32 @@ const SettingsCustomization: FC = () => {
autoFocus
color="error"
>
Reset
{LL.RESET(0)}
</Button>
</DialogActions>
</Dialog>
);
const content = () => {
return (
const renderContent = () => (
<>
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
Device Entities
{LL.DEVICE_ENTITIES()}
</Typography>
{renderDeviceList()}
{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 flexGrow={1}>
<ButtonRow>
<Button startIcon={<SaveIcon />} variant="outlined" color="primary" onClick={() => saveCustomization()}>
Save
{LL.SAVE()}
</Button>
</ButtonRow>
</Box>
@@ -548,18 +608,90 @@ const SettingsCustomization: FC = () => {
color="error"
onClick={() => setConfirmReset(true)}
>
Reset
{LL.RESET(0)}
</Button>
</ButtonRow>
</Box>
)}
{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 (
<SectionContent title="User Customization" titleGutter>
{content()}
<SectionContent title={LL.USER_CUSTOMIZATION()} titleGutter>
{restarting ? <RestartMonitor /> : renderContent()}
{renderEditDialog()}
</SectionContent>
);
};

View File

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

View File

@@ -1,4 +1,5 @@
export interface Settings {
locale: string;
tx_mode: number;
ems_bus_id: number;
syslog_enabled: boolean;
@@ -32,6 +33,7 @@ export interface Settings {
eth_power: number;
eth_phy_addr: number;
eth_clock_mode: number;
platform: string;
}
export enum busConnectionStatus {
@@ -41,7 +43,7 @@ export enum busConnectionStatus {
}
export interface Stat {
id: string; // name
id: string; // id - needs to be a string
s: number; // success
f: number; // fail
q: number; // quality
@@ -57,7 +59,8 @@ export interface Status {
}
export interface Device {
id: string; // id index
t: string; // type
tn: string; // device type translated name
t: number; // device type id
b: string; // brand
n: string; // name
d: number; // deviceid
@@ -99,6 +102,7 @@ export interface SensorData {
export interface CoreData {
connected: boolean;
devices: Device[];
s_n: string;
active_sensors: number;
analog_enabled: boolean;
}
@@ -108,7 +112,8 @@ export interface DeviceShort {
d?: number; // deviceid
p?: number; // productid
s: string; // shortname
t?: string; // device type name
t?: number; // device type id
tn?: string; // device type internal name
}
export interface Devices {
@@ -136,12 +141,18 @@ export interface DeviceEntity {
id: string; // shortname
v?: any; // value, in any format, optional
n?: string; // fullname, optional
cn?: string; // custom fullname, optional
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
mi?: string; // min value
ma?: string; // max value
o_mi?: string;
o_ma?: string;
}
export interface MaskedEntities {
export interface CustomEntities {
id: number;
entity_ids: string[];
}
@@ -171,7 +182,9 @@ export enum DeviceValueUOM {
MV,
SQM,
M3,
L
L,
KMIN,
K
}
export const DeviceValueUOM_s = [
@@ -184,18 +197,20 @@ export const DeviceValueUOM_s = [
'Wh',
'hours',
'minutes',
'uA',
'µA',
'bar',
'kW',
'W',
'KB',
'second',
'seconds',
'dBm',
'°F',
'mV',
'sqm',
'm3',
'l'
'm²',
'm³',
'l',
'K*min',
'K'
];
export enum AnalogType {
@@ -235,7 +250,10 @@ export const BOARD_PROFILES: BoardProfiles = {
'MH-ET': 'MH-ET Live D1 Mini',
LOLIN: 'Lolin D32',
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 {
@@ -280,5 +298,6 @@ export enum DeviceEntityMask {
DV_WEB_EXCLUDE = 1,
DV_API_MQTT_EXCLUDE = 2,
DV_READONLY = 4,
DV_FAVORITE = 8
DV_FAVORITE = 8,
DV_DELETED = 128
}

View File

@@ -7,12 +7,13 @@ export const GPIO_VALIDATOR = {
if (
value &&
(value === 1 ||
(value >= 6 && value <= 12) ||
(value >= 10 && value <= 12) ||
(value >= 14 && value <= 15) ||
value === 20 ||
value === 24 ||
(value >= 28 && value <= 31) ||
value > 40)
value > 40 ||
value < 0)
) {
callback('Must be an valid GPIO port');
} 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) =>
new Schema({
...(settings.board_profile === 'CUSTOM' && {
...(settings.board_profile === 'CUSTOM' &&
settings.platform === 'ESP32' && {
led_gpio: [{ required: true, message: 'LED GPIO is required' }, GPIO_VALIDATOR],
dallas_gpio: [{ required: true, message: 'GPIO is required' }, GPIO_VALIDATOR],
pbutton_gpio: [{ required: true, message: 'Button 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 && {
syslog_host: [{ required: true, message: 'Host is required' }, IP_OR_HOSTNAME_VALIDATOR],
syslog_port: [
{ 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: [
{ 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 && {

View File

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

View File

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

View File

@@ -1,36 +1,23 @@
export enum EspPlatform {
ESP8266 = 'esp8266',
ESP32 = 'esp32'
}
interface ESPSystemStatus {
export interface SystemStatus {
emsesp_version: string;
esp_platform: EspPlatform;
esp_platform: string;
max_alloc_heap: number;
cpu_freq_mhz: number;
free_heap: number;
sdk_version: string;
flash_chip_size: number;
flash_chip_speed: number;
app_used: number;
app_free: number;
fs_used: number;
fs_total: number;
fs_free: number;
uptime: string;
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 {
enabled: boolean;
port: number;

View File

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

View File

@@ -1,5 +1,3 @@
import parseMilliseconds from 'parse-ms';
const LOCALE_FORMAT = new Intl.DateTimeFormat([...window.navigator.languages], {
day: 'numeric',
month: 'short',
@@ -21,21 +19,6 @@ export const formatLocalDateTime = (date: Date) => {
export const pluralize = (count: number, noun: string) =>
`${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) => {
if (duration_sec === 0) {
return ' ';

View File

@@ -4,12 +4,16 @@ import { AxiosPromise } from 'axios';
import { extractErrorMessage } from '.';
import { useI18nContext } from '../i18n/i18n-react';
export interface RestRequestOptions<D> {
read: () => AxiosPromise<D>;
update?: (value: D) => AxiosPromise<D>;
}
export const useRest = <D>({ read, update }: RestRequestOptions<D>) => {
const { LL } = useI18nContext();
const { enqueueSnackbar } = useSnackbar();
const [saving, setSaving] = useState<boolean>(false);
@@ -22,12 +26,12 @@ export const useRest = <D>({ read, update }: RestRequestOptions<D>) => {
setErrorMessage(undefined);
try {
setData((await read()).data);
} catch (error: unknown) {
const message = extractErrorMessage(error, 'Problem loading data');
} catch (error) {
const message = extractErrorMessage(error, LL.PROBLEM_LOADING());
enqueueSnackbar(message, { variant: 'error' });
setErrorMessage(message);
}
}, [read, enqueueSnackbar]);
}, [read, enqueueSnackbar, LL]);
const save = useCallback(
async (toSave: D) => {
@@ -43,17 +47,17 @@ export const useRest = <D>({ read, update }: RestRequestOptions<D>) => {
if (response.status === 202) {
setRestartNeeded(true);
} else {
enqueueSnackbar('Settings saved', { variant: 'success' });
enqueueSnackbar(LL.SETTINGS_OF('') + ' ' + LL.SAVED(), { variant: 'success' });
}
} catch (error: unknown) {
const message = extractErrorMessage(error, 'Problem saving data');
} catch (error) {
const message = extractErrorMessage(error, LL.PROBLEM_UPDATING());
enqueueSnackbar(message, { variant: 'error' });
setErrorMessage(message);
} finally {
setSaving(false);
}
},
[update, enqueueSnackbar]
[update, enqueueSnackbar, LL]
);
const saveData = () => data && save(data);

View File

@@ -2,11 +2,9 @@ import Schema from 'async-validator';
export const SIGN_IN_REQUEST_VALIDATOR = new Schema({
username: {
required: true,
message: 'Please provide a username'
required: true
},
password: {
required: true,
message: 'Please provide a password'
required: true
}
});

View File

@@ -1,18 +1,23 @@
import Schema from 'async-validator';
import { MqttSettings } from '../types';
import { IP_OR_HOSTNAME_VALIDATOR } from './shared';
export const MQTT_SETTINGS_VALIDATOR = new Schema({
export const createMqttSettingsValidator = (mqttSettings: MqttSettings) =>
new Schema({
...(mqttSettings.enabled && {
host: [{ required: true, message: 'Host is required' }, IP_OR_HOSTNAME_VALIDATOR],
base: { required: true, message: 'Base is required' },
port: [
{ 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' }
],
keep_alive: [
{ required: true, message: 'Keep alive is required' },
{ type: 'number', min: 1, max: 86400, message: 'Keep alive must be between 1 and 86400' }
],
max_topic_length: [
{ required: true, message: 'Max topic length is required' },
{ type: 'number', min: 16, max: 1024, message: 'Max topic length must be between 16 and 1024' }
publish_time_heartbeat: [
{ required: true, message: 'Heartbeat is required' },
{ type: 'number', min: 10, max: 86400, message: 'Heartbeat must be between 10 and 86400' }
]
});
})
});

View File

@@ -14,6 +14,7 @@
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"useUnknownInCatchVariables": false,
"jsx": "react-jsx"
},
"include": ["src"]

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,410 @@
/*!
* @file Adafruit_NeoPixel.h
*
* This is part of Adafruit's NeoPixel library for the Arduino platform,
* allowing a broad range of microcontroller boards (most AVR boards,
* many ARM devices, ESP8266 and ESP32, among others) to control Adafruit
* NeoPixels, FLORA RGB Smart Pixels and compatible devices -- WS2811,
* WS2812, WS2812B, SK6812, etc.
*
* Adafruit invests time and resources providing this open source code,
* please support Adafruit and open-source hardware by purchasing products
* from Adafruit!
*
* Written by Phil "Paint Your Dragon" Burgess for Adafruit Industries,
* with contributions by PJRC, Michael Miller and other members of the
* open source community.
*
* This file is part of the Adafruit_NeoPixel library.
*
* Adafruit_NeoPixel is free software: you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* Adafruit_NeoPixel is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with NeoPixel. If not, see
* <http://www.gnu.org/licenses/>.
*
*/
#ifndef ADAFRUIT_NEOPIXEL_H
#define ADAFRUIT_NEOPIXEL_H
#ifdef ARDUINO
#if (ARDUINO >= 100)
#include <Arduino.h>
#else
#include <WProgram.h>
#include <pins_arduino.h>
#endif
#ifdef USE_TINYUSB // For Serial when selecting TinyUSB
#include <Adafruit_TinyUSB.h>
#endif
#endif
#ifdef TARGET_LPC1768
#include <Arduino.h>
#endif
#if defined(ARDUINO_ARCH_RP2040)
#include <stdlib.h>
#include "hardware/pio.h"
#include "hardware/clocks.h"
#include "rp2040_pio.h"
#endif
// The order of primary colors in the NeoPixel data stream can vary among
// device types, manufacturers and even different revisions of the same
// item. The third parameter to the Adafruit_NeoPixel constructor encodes
// the per-pixel byte offsets of the red, green and blue primaries (plus
// white, if present) in the data stream -- the following #defines provide
// an easier-to-use named version for each permutation. e.g. NEO_GRB
// indicates a NeoPixel-compatible device expecting three bytes per pixel,
// with the first byte transmitted containing the green value, second
// containing red and third containing blue. The in-memory representation
// of a chain of NeoPixels is the same as the data-stream order; no
// re-ordering of bytes is required when issuing data to the chain.
// Most of these values won't exist in real-world devices, but it's done
// this way so we're ready for it (also, if using the WS2811 driver IC,
// one might have their pixels set up in any weird permutation).
// Bits 5,4 of this value are the offset (0-3) from the first byte of a
// pixel to the location of the red color byte. Bits 3,2 are the green
// offset and 1,0 are the blue offset. If it is an RGBW-type device
// (supporting a white primary in addition to R,G,B), bits 7,6 are the
// offset to the white byte...otherwise, bits 7,6 are set to the same value
// as 5,4 (red) to indicate an RGB (not RGBW) device.
// i.e. binary representation:
// 0bWWRRGGBB for RGBW devices
// 0bRRRRGGBB for RGB
// RGB NeoPixel permutations; white and red offsets are always same
// Offset: W R G B
#define NEO_RGB ((0 << 6) | (0 << 4) | (1 << 2) | (2)) ///< Transmit as R,G,B
#define NEO_RBG ((0 << 6) | (0 << 4) | (2 << 2) | (1)) ///< Transmit as R,B,G
#define NEO_GRB ((1 << 6) | (1 << 4) | (0 << 2) | (2)) ///< Transmit as G,R,B
#define NEO_GBR ((2 << 6) | (2 << 4) | (0 << 2) | (1)) ///< Transmit as G,B,R
#define NEO_BRG ((1 << 6) | (1 << 4) | (2 << 2) | (0)) ///< Transmit as B,R,G
#define NEO_BGR ((2 << 6) | (2 << 4) | (1 << 2) | (0)) ///< Transmit as B,G,R
// RGBW NeoPixel permutations; all 4 offsets are distinct
// Offset: W R G B
#define NEO_WRGB ((0 << 6) | (1 << 4) | (2 << 2) | (3)) ///< Transmit as W,R,G,B
#define NEO_WRBG ((0 << 6) | (1 << 4) | (3 << 2) | (2)) ///< Transmit as W,R,B,G
#define NEO_WGRB ((0 << 6) | (2 << 4) | (1 << 2) | (3)) ///< Transmit as W,G,R,B
#define NEO_WGBR ((0 << 6) | (3 << 4) | (1 << 2) | (2)) ///< Transmit as W,G,B,R
#define NEO_WBRG ((0 << 6) | (2 << 4) | (3 << 2) | (1)) ///< Transmit as W,B,R,G
#define NEO_WBGR ((0 << 6) | (3 << 4) | (2 << 2) | (1)) ///< Transmit as W,B,G,R
#define NEO_RWGB ((1 << 6) | (0 << 4) | (2 << 2) | (3)) ///< Transmit as R,W,G,B
#define NEO_RWBG ((1 << 6) | (0 << 4) | (3 << 2) | (2)) ///< Transmit as R,W,B,G
#define NEO_RGWB ((2 << 6) | (0 << 4) | (1 << 2) | (3)) ///< Transmit as R,G,W,B
#define NEO_RGBW ((3 << 6) | (0 << 4) | (1 << 2) | (2)) ///< Transmit as R,G,B,W
#define NEO_RBWG ((2 << 6) | (0 << 4) | (3 << 2) | (1)) ///< Transmit as R,B,W,G
#define NEO_RBGW ((3 << 6) | (0 << 4) | (2 << 2) | (1)) ///< Transmit as R,B,G,W
#define NEO_GWRB ((1 << 6) | (2 << 4) | (0 << 2) | (3)) ///< Transmit as G,W,R,B
#define NEO_GWBR ((1 << 6) | (3 << 4) | (0 << 2) | (2)) ///< Transmit as G,W,B,R
#define NEO_GRWB ((2 << 6) | (1 << 4) | (0 << 2) | (3)) ///< Transmit as G,R,W,B
#define NEO_GRBW ((3 << 6) | (1 << 4) | (0 << 2) | (2)) ///< Transmit as G,R,B,W
#define NEO_GBWR ((2 << 6) | (3 << 4) | (0 << 2) | (1)) ///< Transmit as G,B,W,R
#define NEO_GBRW ((3 << 6) | (2 << 4) | (0 << 2) | (1)) ///< Transmit as G,B,R,W
#define NEO_BWRG ((1 << 6) | (2 << 4) | (3 << 2) | (0)) ///< Transmit as B,W,R,G
#define NEO_BWGR ((1 << 6) | (3 << 4) | (2 << 2) | (0)) ///< Transmit as B,W,G,R
#define NEO_BRWG ((2 << 6) | (1 << 4) | (3 << 2) | (0)) ///< Transmit as B,R,W,G
#define NEO_BRGW ((3 << 6) | (1 << 4) | (2 << 2) | (0)) ///< Transmit as B,R,G,W
#define NEO_BGWR ((2 << 6) | (3 << 4) | (1 << 2) | (0)) ///< Transmit as B,G,W,R
#define NEO_BGRW ((3 << 6) | (2 << 4) | (1 << 2) | (0)) ///< Transmit as B,G,R,W
// Add NEO_KHZ400 to the color order value to indicate a 400 KHz device.
// All but the earliest v1 NeoPixels expect an 800 KHz data stream, this is
// the default if unspecified. Because flash space is very limited on ATtiny
// devices (e.g. Trinket, Gemma), v1 NeoPixels aren't handled by default on
// those chips, though it can be enabled by removing the ifndef/endif below,
// but code will be bigger. Conversely, can disable the NEO_KHZ400 line on
// other MCUs to remove v1 support and save a little space.
#define NEO_KHZ800 0x0000 ///< 800 KHz data transmission
#ifndef __AVR_ATtiny85__
#define NEO_KHZ400 0x0100 ///< 400 KHz data transmission
#endif
// If 400 KHz support is enabled, the third parameter to the constructor
// requires a 16-bit value (in order to select 400 vs 800 KHz speed).
// If only 800 KHz is enabled (as is default on ATtiny), an 8-bit value
// is sufficient to encode pixel color order, saving some space.
#ifdef NEO_KHZ400
typedef uint16_t neoPixelType; ///< 3rd arg to Adafruit_NeoPixel constructor
#else
typedef uint8_t neoPixelType; ///< 3rd arg to Adafruit_NeoPixel constructor
#endif
// These two tables are declared outside the Adafruit_NeoPixel class
// because some boards may require oldschool compilers that don't
// handle the C++11 constexpr keyword.
/* A PROGMEM (flash mem) table containing 8-bit unsigned sine wave (0-255).
Copy & paste this snippet into a Python REPL to regenerate:
import math
for x in range(256):
print("{:3},".format(int((math.sin(x/128.0*math.pi)+1.0)*127.5+0.5))),
if x&15 == 15: print
*/
static const uint8_t PROGMEM _NeoPixelSineTable[256] = {
128, 131, 134, 137, 140, 143, 146, 149, 152, 155, 158, 162, 165, 167, 170,
173, 176, 179, 182, 185, 188, 190, 193, 196, 198, 201, 203, 206, 208, 211,
213, 215, 218, 220, 222, 224, 226, 228, 230, 232, 234, 235, 237, 238, 240,
241, 243, 244, 245, 246, 248, 249, 250, 250, 251, 252, 253, 253, 254, 254,
254, 255, 255, 255, 255, 255, 255, 255, 254, 254, 254, 253, 253, 252, 251,
250, 250, 249, 248, 246, 245, 244, 243, 241, 240, 238, 237, 235, 234, 232,
230, 228, 226, 224, 222, 220, 218, 215, 213, 211, 208, 206, 203, 201, 198,
196, 193, 190, 188, 185, 182, 179, 176, 173, 170, 167, 165, 162, 158, 155,
152, 149, 146, 143, 140, 137, 134, 131, 128, 124, 121, 118, 115, 112, 109,
106, 103, 100, 97, 93, 90, 88, 85, 82, 79, 76, 73, 70, 67, 65,
62, 59, 57, 54, 52, 49, 47, 44, 42, 40, 37, 35, 33, 31, 29,
27, 25, 23, 21, 20, 18, 17, 15, 14, 12, 11, 10, 9, 7, 6,
5, 5, 4, 3, 2, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0,
0, 1, 1, 1, 2, 2, 3, 4, 5, 5, 6, 7, 9, 10, 11,
12, 14, 15, 17, 18, 20, 21, 23, 25, 27, 29, 31, 33, 35, 37,
40, 42, 44, 47, 49, 52, 54, 57, 59, 62, 65, 67, 70, 73, 76,
79, 82, 85, 88, 90, 93, 97, 100, 103, 106, 109, 112, 115, 118, 121,
124};
/* Similar to above, but for an 8-bit gamma-correction table.
Copy & paste this snippet into a Python REPL to regenerate:
import math
gamma=2.6
for x in range(256):
print("{:3},".format(int(math.pow((x)/255.0,gamma)*255.0+0.5))),
if x&15 == 15: print
*/
static const uint8_t PROGMEM _NeoPixelGammaTable[256] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3,
3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6,
6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10,
11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16, 17,
17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25,
25, 26, 27, 27, 28, 29, 29, 30, 31, 31, 32, 33, 34, 34, 35,
36, 37, 38, 38, 39, 40, 41, 42, 42, 43, 44, 45, 46, 47, 48,
49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
64, 65, 66, 68, 69, 70, 71, 72, 73, 75, 76, 77, 78, 80, 81,
82, 84, 85, 86, 88, 89, 90, 92, 93, 94, 96, 97, 99, 100, 102,
103, 105, 106, 108, 109, 111, 112, 114, 115, 117, 119, 120, 122, 124, 125,
127, 129, 130, 132, 134, 136, 137, 139, 141, 143, 145, 146, 148, 150, 152,
154, 156, 158, 160, 162, 164, 166, 168, 170, 172, 174, 176, 178, 180, 182,
184, 186, 188, 191, 193, 195, 197, 199, 202, 204, 206, 209, 211, 213, 215,
218, 220, 223, 225, 227, 230, 232, 235, 237, 240, 242, 245, 247, 250, 252,
255};
/*!
@brief Class that stores state and functions for interacting with
Adafruit NeoPixels and compatible devices.
*/
class Adafruit_NeoPixel {
public:
// Constructor: number of LEDs, pin number, LED type
Adafruit_NeoPixel(uint16_t n, int16_t pin = 6,
neoPixelType type = NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel(void);
~Adafruit_NeoPixel();
void begin(void);
void show(void);
void setPin(int16_t p);
void setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b);
void setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b, uint8_t w);
void setPixelColor(uint16_t n, uint32_t c);
void fill(uint32_t c = 0, uint16_t first = 0, uint16_t count = 0);
void setBrightness(uint8_t);
void clear(void);
void updateLength(uint16_t n);
void updateType(neoPixelType t);
/*!
@brief Check whether a call to show() will start sending data
immediately or will 'block' for a required interval. NeoPixels
require a short quiet time (about 300 microseconds) after the
last bit is received before the data 'latches' and new data can
start being received. Usually one's sketch is implicitly using
this time to generate a new frame of animation...but if it
finishes very quickly, this function could be used to see if
there's some idle time available for some low-priority
concurrent task.
@return 1 or true if show() will start sending immediately, 0 or false
if show() would block (meaning some idle time is available).
*/
bool canShow(void) {
// It's normal and possible for endTime to exceed micros() if the
// 32-bit clock counter has rolled over (about every 70 minutes).
// Since both are uint32_t, a negative delta correctly maps back to
// positive space, and it would seem like the subtraction below would
// suffice. But a problem arises if code invokes show() very
// infrequently...the micros() counter may roll over MULTIPLE times in
// that interval, the delta calculation is no longer correct and the
// next update may stall for a very long time. The check below resets
// the latch counter if a rollover has occurred. This can cause an
// extra delay of up to 300 microseconds in the rare case where a
// show() call happens precisely around the rollover, but that's
// neither likely nor especially harmful, vs. other code that might
// stall for 30+ minutes, or having to document and frequently remind
// and/or provide tech support explaining an unintuitive need for
// show() calls at least once an hour.
uint32_t now = micros();
if (endTime > now) {
endTime = now;
}
return (now - endTime) >= 300L;
}
/*!
@brief Get a pointer directly to the NeoPixel data buffer in RAM.
Pixel data is stored in a device-native format (a la the NEO_*
constants) and is not translated here. Applications that access
this buffer will need to be aware of the specific data format
and handle colors appropriately.
@return Pointer to NeoPixel buffer (uint8_t* array).
@note This is for high-performance applications where calling
setPixelColor() on every single pixel would be too slow (e.g.
POV or light-painting projects). There is no bounds checking
on the array, creating tremendous potential for mayhem if one
writes past the ends of the buffer. Great power, great
responsibility and all that.
*/
uint8_t *getPixels(void) const { return pixels; };
uint8_t getBrightness(void) const;
/*!
@brief Retrieve the pin number used for NeoPixel data output.
@return Arduino pin number (-1 if not set).
*/
int16_t getPin(void) const { return pin; };
/*!
@brief Return the number of pixels in an Adafruit_NeoPixel strip object.
@return Pixel count (0 if not set).
*/
uint16_t numPixels(void) const { return numLEDs; }
uint32_t getPixelColor(uint16_t n) const;
/*!
@brief An 8-bit integer sine wave function, not directly compatible
with standard trigonometric units like radians or degrees.
@param x Input angle, 0-255; 256 would loop back to zero, completing
the circle (equivalent to 360 degrees or 2 pi radians).
One can therefore use an unsigned 8-bit variable and simply
add or subtract, allowing it to overflow/underflow and it
still does the expected contiguous thing.
@return Sine result, 0 to 255, or -128 to +127 if type-converted to
a signed int8_t, but you'll most likely want unsigned as this
output is often used for pixel brightness in animation effects.
*/
static uint8_t sine8(uint8_t x) {
return pgm_read_byte(&_NeoPixelSineTable[x]); // 0-255 in, 0-255 out
}
/*!
@brief An 8-bit gamma-correction function for basic pixel brightness
adjustment. Makes color transitions appear more perceptially
correct.
@param x Input brightness, 0 (minimum or off/black) to 255 (maximum).
@return Gamma-adjusted brightness, can then be passed to one of the
setPixelColor() functions. This uses a fixed gamma correction
exponent of 2.6, which seems reasonably okay for average
NeoPixels in average tasks. If you need finer control you'll
need to provide your own gamma-correction function instead.
*/
static uint8_t gamma8(uint8_t x) {
return pgm_read_byte(&_NeoPixelGammaTable[x]); // 0-255 in, 0-255 out
}
/*!
@brief Convert separate red, green and blue values into a single
"packed" 32-bit RGB color.
@param r Red brightness, 0 to 255.
@param g Green brightness, 0 to 255.
@param b Blue brightness, 0 to 255.
@return 32-bit packed RGB value, which can then be assigned to a
variable for later use or passed to the setPixelColor()
function. Packed RGB format is predictable, regardless of
LED strand color order.
*/
static uint32_t Color(uint8_t r, uint8_t g, uint8_t b) {
return ((uint32_t)r << 16) | ((uint32_t)g << 8) | b;
}
/*!
@brief Convert separate red, green, blue and white values into a
single "packed" 32-bit WRGB color.
@param r Red brightness, 0 to 255.
@param g Green brightness, 0 to 255.
@param b Blue brightness, 0 to 255.
@param w White brightness, 0 to 255.
@return 32-bit packed WRGB value, which can then be assigned to a
variable for later use or passed to the setPixelColor()
function. Packed WRGB format is predictable, regardless of
LED strand color order.
*/
static uint32_t Color(uint8_t r, uint8_t g, uint8_t b, uint8_t w) {
return ((uint32_t)w << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b;
}
static uint32_t ColorHSV(uint16_t hue, uint8_t sat = 255, uint8_t val = 255);
/*!
@brief A gamma-correction function for 32-bit packed RGB or WRGB
colors. Makes color transitions appear more perceptially
correct.
@param x 32-bit packed RGB or WRGB color.
@return Gamma-adjusted packed color, can then be passed in one of the
setPixelColor() functions. Like gamma8(), this uses a fixed
gamma correction exponent of 2.6, which seems reasonably okay
for average NeoPixels in average tasks. If you need finer
control you'll need to provide your own gamma-correction
function instead.
*/
static uint32_t gamma32(uint32_t x);
void rainbow(uint16_t first_hue = 0, int8_t reps = 1,
uint8_t saturation = 255, uint8_t brightness = 255,
bool gammify = true);
private:
#if defined(ARDUINO_ARCH_RP2040)
void rp2040Init(uint8_t pin, bool is800KHz);
void rp2040Show(uint8_t pin, uint8_t *pixels, uint32_t numBytes, bool is800KHz);
#endif
protected:
#ifdef NEO_KHZ400 // If 400 KHz NeoPixel support enabled...
bool is800KHz; ///< true if 800 KHz pixels
#endif
bool begun; ///< true if begin() previously called
uint16_t numLEDs; ///< Number of RGB LEDs in strip
uint16_t numBytes; ///< Size of 'pixels' buffer below
int16_t pin; ///< Output pin number (-1 if not yet set)
uint8_t brightness; ///< Strip brightness 0-255 (stored as +1)
uint8_t *pixels; ///< Holds LED color values (3 or 4 bytes each)
uint8_t rOffset; ///< Red index within each 3- or 4-byte pixel
uint8_t gOffset; ///< Index of green byte
uint8_t bOffset; ///< Index of blue byte
uint8_t wOffset; ///< Index of white (==rOffset if no white)
uint32_t endTime; ///< Latch timing reference
#ifdef __AVR__
volatile uint8_t *port; ///< Output PORT register
uint8_t pinMask; ///< Output PORT bitmask
#endif
#if defined(ARDUINO_ARCH_STM32) || defined(ARDUINO_ARCH_ARDUINO_CORE_STM32)
GPIO_TypeDef *gpioPort; ///< Output GPIO PORT
uint32_t gpioPin; ///< Output GPIO PIN
#endif
#if defined(ARDUINO_ARCH_RP2040)
PIO pio = pio0;
int sm = 0;
bool init = true;
#endif
};
#endif // ADAFRUIT_NEOPIXEL_H

View File

@@ -0,0 +1,13 @@
# Contribution Guidelines
This library is the culmination of the expertise of many members of the open source community who have dedicated their time and hard work. The best way to ask for help or propose a new idea is to [create a new issue](https://github.com/adafruit/Adafruit_NeoPixel/issues/new) while creating a Pull Request with your code changes allows you to share your own innovations with the rest of the community.
The following are some guidelines to observe when creating issues or PRs:
- Be friendly; it is important that we can all enjoy a safe space as we are all working on the same project and it is okay for people to have different ideas
- [Use code blocks](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet#code); it helps us help you when we can read your code! On that note also refrain from pasting more than 30 lines of code in a post, instead [create a gist](https://gist.github.com/) if you need to share large snippets
- Use reasonable titles; refrain from using overly long or capitalized titles as they are usually annoying and do little to encourage others to help :smile:
- Be detailed; refrain from mentioning code problems without sharing your source code and always give information regarding your board and version of the library

View File

@@ -0,0 +1,165 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

View File

@@ -0,0 +1,157 @@
# Adafruit NeoPixel Library [![Build Status](https://github.com/adafruit/Adafruit_NeoPixel/workflows/Arduino%20Library%20CI/badge.svg)](https://github.com/adafruit/Adafruit_NeoPixel/actions)[![Documentation](https://github.com/adafruit/ci-arduino/blob/master/assets/doxygen_badge.svg)](http://adafruit.github.io/Adafruit_NeoPixel/html/index.html)
Arduino library for controlling single-wire-based LED pixels and strip such as the [Adafruit 60 LED/meter Digital LED strip][strip], the [Adafruit FLORA RGB Smart Pixel][flora], the [Adafruit Breadboard-friendly RGB Smart Pixel][pixel], the [Adafruit NeoPixel Stick][stick], and the [Adafruit NeoPixel Shield][shield].
After downloading, rename folder to 'Adafruit_NeoPixel' and install in Arduino Libraries folder. Restart Arduino IDE, then open File->Sketchbook->Library->Adafruit_NeoPixel->strandtest sketch.
Compatibility notes: Port A is not supported on any AVR processors at this time
[flora]: http://adafruit.com/products/1060
[strip]: http://adafruit.com/products/1138
[pixel]: http://adafruit.com/products/1312
[stick]: http://adafruit.com/products/1426
[shield]: http://adafruit.com/products/1430
---
## Installation
### First Method
![image](https://user-images.githubusercontent.com/36513474/68967967-3e37f480-0803-11ea-91d9-601848c306ee.png)
1. In the Arduino IDE, navigate to Sketch > Include Library > Manage Libraries
1. Then the Library Manager will open and you will find a list of libraries that are already installed or ready for installation.
1. Then search for Neopixel strip using the search bar.
1. Click on the text area and then select the specific version and install it.
### Second Method
1. Navigate to the [Releases page](https://github.com/adafruit/Adafruit_NeoPixel/releases).
1. Download the latest release.
1. Extract the zip file
1. In the Arduino IDE, navigate to Sketch > Include Library > Add .ZIP Library
## Features
- ### Simple to use
Controlling NeoPixels “from scratch” is quite a challenge, so we provide a library letting you focus on the fun and interesting bits.
- ### Give back
The library is free; you dont have to pay for anything. Adafruit invests time and resources providing this open source code, please support Adafruit and open-source hardware by purchasing products from Adafruit!
- ### Supported Chipsets
We have included code for the following chips - sometimes these break for exciting reasons that we can't control in which case please open an issue!
- AVR ATmega and ATtiny (any 8-bit) - 8 MHz, 12 MHz and 16 MHz
- Teensy 3.x and LC
- Arduino Due
- Arduino 101
- ATSAMD21 (Arduino Zero/M0 and other SAMD21 boards) @ 48 MHz
- ATSAMD51 @ 120 MHz
- Adafruit STM32 Feather @ 120 MHz
- ESP8266 any speed
- ESP32 any speed
- Nordic nRF52 (Adafruit Feather nRF52), nRF51 (micro:bit)
- Infineon XMC1100 BootKit @ 32 MHz
- Infineon XMC1100 2Go @ 32 MHz
- Infineon XMC1300 BootKit @ 32 MHz
- Infineon XMC4700 RelaxKit, XMC4800 RelaxKit, XMC4800 IoT Amazon FreeRTOS Kit @ 144 MHz
Check forks for other architectures not listed here!
- ### GNU Lesser General Public License
Adafruit_NeoPixel is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
## Functions
- begin()
- updateLength()
- updateType()
- show()
- delay_ns()
- setPin()
- setPixelColor()
- fill()
- ColorHSV()
- getPixelColor()
- setBrightness()
- getBrightness()
- clear()
- gamma32()
## Examples
There are many examples implemented in this library. One of the examples is below. You can find other examples [here](https://github.com/adafruit/Adafruit_NeoPixel/tree/master/examples)
### Simple
```Cpp
#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
#include <avr/power.h>
#endif
#define PIN 6
#define NUMPIXELS 16
Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
#define DELAYVAL 500
void setup() {
#if defined(__AVR_ATtiny85__) && (F_CPU == 16000000)
clock_prescale_set(clock_div_1);
#endif
pixels.begin();
}
void loop() {
pixels.clear();
for(int i=0; i<NUMPIXELS; i++) {
pixels.setPixelColor(i, pixels.Color(0, 150, 0));
pixels.show();
delay(DELAYVAL);
}
}
```
## Contributing
If you want to contribute to this project:
- Report bugs and errors
- Ask for enhancements
- Create issues and pull requests
- Tell others about this library
- Contribute new protocols
Please read [CONTRIBUTING.md](https://github.com/adafruit/Adafruit_NeoPixel/blob/master/CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us.
### Roadmap
The PRIME DIRECTIVE is to maintain backward compatibility with existing Arduino sketches -- many are hosted elsewhere and don't track changes here, some are in print and can never be changed!
Please don't reformat code for the sake of reformatting code. The resulting large "visual diff" makes it impossible to untangle actual bug fixes from merely rearranged lines. (Exception for first item in wishlist below.)
Things I'd Like To Do But There's No Official Timeline So Please Don't Count On Any Of This Ever Being Canonical:
- For the show() function (with all the delicate pixel timing stuff), break out each architecture into separate source files rather than the current unmaintainable tangle of #ifdef statements!
- Please don't use updateLength() or updateType() in new code. They should not have been implemented this way (use the C++ 'new' operator with the regular constructor instead) and are only sticking around because of the Prime Directive. setPin() is OK for now though, it's a trick we can use to 'recycle' pixel memory across multiple strips.
- In the M0 and M4 code, use the hardware systick counter for bit timing rather than hand-tweaked NOPs (a temporary kludge at the time because I wasn't reading systick correctly). (As of 1.4.2, systick is used on M4 devices and it appears to be overclock-compatible. Not for M0 yet, which is why this item is still here.)
- As currently written, brightness scaling is still a "destructive" operation -- pixel values are altered in RAM and the original value as set can't be accurately read back, only approximated, which has been confusing and frustrating to users. It was done this way at the time because NeoPixel timing is strict, AVR microcontrollers (all we had at the time) are limited, and assembly language is hard. All the 32-bit architectures should have no problem handling nondestructive brightness scaling -- calculating each byte immediately before it's sent out the wire, maintaining the original set value in RAM -- the work just hasn't been done. There's a fair chance even the AVR code could manage it with some intense focus. (The DotStar library achieves nondestructive brightness scaling because it doesn't have to manage data timing so carefully...every architecture, even ATtiny, just takes whatever cycles it needs for the multiply/shift operations.)
## Credits
This library is written by Phil "Paint Your Dragon" Burgess for Adafruit Industries, with contributions by PJRC, Michael Miller and other members of the open source community.
## License
Adafruit_NeoPixel is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
Adafruit_NeoPixel is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the [GNU Lesser General Public License](https://www.gnu.org/licenses/lgpl-3.0.en.html) for more details.
You should have received a copy of the GNU Lesser General Public License along with NeoPixel. If not, see [this](https://www.gnu.org/licenses/)

178
lib/Adafruit_NeoPixel/esp.c Normal file
View File

@@ -0,0 +1,178 @@
// Implements the RMT peripheral on Espressif SoCs
// Copyright (c) 2020 Lucian Copeland for Adafruit Industries
/* Uses code from Espressif RGB LED Strip demo and drivers
* Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#if defined(ESP32)
#include <Arduino.h>
#include "driver/rmt.h"
#if defined(ESP_IDF_VERSION)
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0)
#define HAS_ESP_IDF_4
#endif
#endif
// This code is adapted from the ESP-IDF v3.4 RMT "led_strip" example, altered
// to work with the Arduino version of the ESP-IDF (3.2)
#define WS2812_T0H_NS (400)
#define WS2812_T0L_NS (850)
#define WS2812_T1H_NS (800)
#define WS2812_T1L_NS (450)
#define WS2811_T0H_NS (500)
#define WS2811_T0L_NS (2000)
#define WS2811_T1H_NS (1200)
#define WS2811_T1L_NS (1300)
static uint32_t t0h_ticks = 0;
static uint32_t t1h_ticks = 0;
static uint32_t t0l_ticks = 0;
static uint32_t t1l_ticks = 0;
// Limit the number of RMT channels available for the Neopixels. Defaults to all
// channels (8 on ESP32, 4 on ESP32-S2 and S3). Redefining this value will free
// any channels with a higher number for other uses, such as IR send-and-recieve
// libraries. Redefine as 1 to restrict Neopixels to only a single channel.
#define ADAFRUIT_RMT_CHANNEL_MAX RMT_CHANNEL_MAX
#define RMT_LL_HW_BASE (&RMT)
bool rmt_reserved_channels[ADAFRUIT_RMT_CHANNEL_MAX];
static void IRAM_ATTR ws2812_rmt_adapter(const void *src, rmt_item32_t *dest, size_t src_size,
size_t wanted_num, size_t *translated_size, size_t *item_num)
{
if (src == NULL || dest == NULL) {
*translated_size = 0;
*item_num = 0;
return;
}
const rmt_item32_t bit0 = {{{ t0h_ticks, 1, t0l_ticks, 0 }}}; //Logical 0
const rmt_item32_t bit1 = {{{ t1h_ticks, 1, t1l_ticks, 0 }}}; //Logical 1
size_t size = 0;
size_t num = 0;
uint8_t *psrc = (uint8_t *)src;
rmt_item32_t *pdest = dest;
while (size < src_size && num < wanted_num) {
for (int i = 0; i < 8; i++) {
// MSB first
if (*psrc & (1 << (7 - i))) {
pdest->val = bit1.val;
} else {
pdest->val = bit0.val;
}
num++;
pdest++;
}
size++;
psrc++;
}
*translated_size = size;
*item_num = num;
}
void espShow(uint8_t pin, uint8_t *pixels, uint32_t numBytes, boolean is800KHz) {
// Reserve channel
rmt_channel_t channel = ADAFRUIT_RMT_CHANNEL_MAX;
for (size_t i = 0; i < ADAFRUIT_RMT_CHANNEL_MAX; i++) {
if (!rmt_reserved_channels[i]) {
rmt_reserved_channels[i] = true;
channel = i;
break;
}
}
if (channel == ADAFRUIT_RMT_CHANNEL_MAX) {
// Ran out of channels!
return;
}
#if defined(HAS_ESP_IDF_4)
rmt_config_t config = RMT_DEFAULT_CONFIG_TX(pin, channel);
config.clk_div = 2;
#else
// Match default TX config from ESP-IDF version 3.4
rmt_config_t config = {
.rmt_mode = RMT_MODE_TX,
.channel = channel,
.gpio_num = pin,
.clk_div = 2,
.mem_block_num = 1,
.tx_config = {
.carrier_freq_hz = 38000,
.carrier_level = RMT_CARRIER_LEVEL_HIGH,
.idle_level = RMT_IDLE_LEVEL_LOW,
.carrier_duty_percent = 33,
.carrier_en = false,
.loop_en = false,
.idle_output_en = true,
}
};
#endif
rmt_config(&config);
rmt_driver_install(config.channel, 0, 0);
// Convert NS timings to ticks
uint32_t counter_clk_hz = 0;
#if defined(HAS_ESP_IDF_4)
rmt_get_counter_clock(channel, &counter_clk_hz);
#else
// this emulates the rmt_get_counter_clock() function from ESP-IDF 3.4
if (RMT_LL_HW_BASE->conf_ch[config.channel].conf1.ref_always_on == RMT_BASECLK_REF) {
uint32_t div_cnt = RMT_LL_HW_BASE->conf_ch[config.channel].conf0.div_cnt;
uint32_t div = div_cnt == 0 ? 256 : div_cnt;
counter_clk_hz = REF_CLK_FREQ / (div);
} else {
uint32_t div_cnt = RMT_LL_HW_BASE->conf_ch[config.channel].conf0.div_cnt;
uint32_t div = div_cnt == 0 ? 256 : div_cnt;
counter_clk_hz = APB_CLK_FREQ / (div);
}
#endif
// NS to tick converter
float ratio = (float)counter_clk_hz / 1e9;
if (is800KHz) {
t0h_ticks = (uint32_t)(ratio * WS2812_T0H_NS);
t0l_ticks = (uint32_t)(ratio * WS2812_T0L_NS);
t1h_ticks = (uint32_t)(ratio * WS2812_T1H_NS);
t1l_ticks = (uint32_t)(ratio * WS2812_T1L_NS);
} else {
t0h_ticks = (uint32_t)(ratio * WS2811_T0H_NS);
t0l_ticks = (uint32_t)(ratio * WS2811_T0L_NS);
t1h_ticks = (uint32_t)(ratio * WS2811_T1H_NS);
t1l_ticks = (uint32_t)(ratio * WS2811_T1L_NS);
}
// Initialize automatic timing translator
rmt_translator_init(config.channel, ws2812_rmt_adapter);
// Write and wait to finish
rmt_write_sample(config.channel, pixels, (size_t)numBytes, true);
rmt_wait_tx_done(config.channel, pdMS_TO_TICKS(100));
// Free channel again
rmt_driver_uninstall(config.channel);
rmt_reserved_channels[channel] = false;
gpio_set_direction(pin, GPIO_MODE_OUTPUT);
}
#endif

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