786 Commits

Author SHA1 Message Date
Proddy
18e9b99413 Merge remote-tracking branch 'origin/dev' into main 2022-05-29 16:16:38 +02:00
Proddy
e65f5072fe Merge pull request #527 from proddy/dev
various minor changes, including managing hidden entities in customization screen
2022-05-29 16:11:53 +02:00
Proddy
435218888a #513 2022-05-29 14:36:13 +02:00
Proddy
3e7b743dfa updated changelog for 3.4.1 2022-05-29 13:58:01 +02:00
Proddy
c693ef6307 update packages 2022-05-29 13:57:54 +02:00
Proddy
e4447ee1b9 improved icon toggling 2022-05-28 20:23:16 +02:00
Proddy
356390c92b fix standalone server for handling customizations 2022-05-28 15:27:08 +02:00
Proddy
55133b028a typo 2022-05-27 17:18:50 +02:00
Proddy
1fbd69b718 fixes to customizations 2022-05-27 17:16:23 +02:00
Proddy
1e082f941a customizations, swap shortname and fullname, using id as unique shortname 2022-05-27 09:49:32 +02:00
Proddy
8824f4f3da added formatName so hidden entities render nicely 2022-05-26 17:49:43 +02:00
Proddy
f8bf6b5cc8 fix isCmdOnly() function for commands like reset 2022-05-26 17:49:25 +02:00
Proddy
be844a5184 if no fullname, use shortname in customizations json 2022-05-26 17:49:04 +02:00
Proddy
6ec67f7417 rename ha_climate_config_creation 2022-05-26 17:48:34 +02:00
Proddy
2c468c7225 formatting 2022-05-26 17:48:18 +02:00
Proddy
af7cd7b009 rename generate_values_web_customization 2022-05-26 17:48:10 +02:00
Proddy
cba081379e rename generate_values_web_customization 2022-05-26 17:48:01 +02:00
Proddy
a9064baefc 3.4.1b1 2022-05-26 17:47:15 +02:00
Proddy
31627bb704 remove debug_flags 2022-05-26 17:47:04 +02:00
Proddy
c4cfabfbaf update test data for customizations 2022-05-26 17:46:51 +02:00
Proddy
9d25f8049c Merge pull request #525 from MichaelDvP/dev
fixes #522, #523, #524
2022-05-26 15:17:51 +02:00
MichaelDvP
2d50f18dcf changelog from 3.4.0 2022-05-26 13:34:51 +02:00
MichaelDvP
89b0711464 changelog 2022-05-26 13:05:37 +02:00
MichaelDvP
34cb3ad375 fix #524, free memory of json response 2022-05-26 12:43:10 +02:00
MichaelDvP
d2609e4291 fix #523, rename 'climate' to more explaning name 2022-05-26 12:42:55 +02:00
MichaelDvP
77a8857e2f fix #522, device-flag for IVT controller 2022-05-26 12:42:38 +02:00
Proddy
60714b0b0b Merge pull request #521 from proddy/dev
React18 modifications
2022-05-24 22:50:52 +02:00
Proddy
c32cdf5d98 React18 modifications 2022-05-24 22:50:18 +02:00
Proddy
472b97e89e update to 3.4.1b0 2022-05-23 21:34:00 +02:00
Proddy
a47e0e8266 update for 3.4.0 2022-05-23 21:20:45 +02:00
Proddy
f412ddc716 Merge remote-tracking branch 'origin/dev' into main 2022-05-23 21:20:36 +02:00
Proddy
859c5950f4 Merge pull request #518 from proddy/dev
autoformatting
2022-05-23 21:08:31 +02:00
Proddy
b9f63bcfb1 autoformatting 2022-05-23 21:08:00 +02:00
Proddy
86b79a2685 package update 2022-05-23 21:07:53 +02:00
Proddy
7f587070e5 Merge pull request #516 from proddy/dev
upgrade to react18
2022-05-23 17:56:59 +02:00
Proddy
e121b480ef Merge pull request #515 from tp1de/dev
emergencyops and emergencytemp for RC310
2022-05-23 09:22:19 +02:00
Proddy
22668d76ee upgrade to react18 2022-05-22 13:57:17 +02:00
tp1de
2c0c5ba425 emergencyops and emergencytemp for RC310 2022-05-21 13:57:31 +02:00
Proddy
e8387b363f Merge pull request #514 from tp1de/dev
wwmaxtemp for RC310
2022-05-21 11:23:13 +02:00
tp1de
5ab22af9c3 correct boilers.h 2022-05-20 16:55:16 +02:00
tp1de
4263860760 Merge branch 'dev' of https://github.com/tp1de/EMS-ESP32 into dev 2022-05-20 15:14:10 +02:00
tp1de
c637dcbbd8 wwmaxtemp for RC310 2022-05-20 13:14:09 +02:00
Proddy
6c68f2950a Merge pull request #512 from proddy/dev
text changes, new screenshots
2022-05-20 12:48:44 +02:00
Proddy
9310f606ad change tooltips #504 2022-05-20 12:47:54 +02:00
Proddy
7ea6c542cf updated screenshots 2022-05-20 12:40:34 +02:00
Proddy
5b4c5b063d increase # devices supported 2022-05-20 12:40:27 +02:00
Proddy
8afb5b3a7c formatting 2022-05-20 12:40:07 +02:00
Proddy
8fbe2199f4 Merge pull request #510 from tp1de/dev
New entities for RC310 EMS+ Heating systems
2022-05-20 12:35:03 +02:00
Proddy
62a5254add Merge pull request #506 from MichaelDvP/dev
summermode for heatpump and HM200 hybridmodule
2022-05-20 10:42:27 +02:00
Proddy
ab5ba6fc80 Merge pull request #511 from proddy/dev
minor updates
2022-05-20 09:59:34 +02:00
Proddy
f9d46904f6 updated URL for api/system/info 2022-05-20 09:59:15 +02:00
Proddy
6545dbd483 updated packages 2022-05-20 09:58:53 +02:00
Proddy
353853af14 max must be a integer (changed 19.9 to 20) 2022-05-20 09:58:43 +02:00
tp1de
6fef3990a7 locale_EN.h 2022-05-20 00:13:56 +02:00
tp1de
571f5577d1 wwchargeoptimization UBAParameterWWPlus 2022-05-20 00:10:58 +02:00
Thomas
e2de89c763 Update locale_EN.h 2022-05-19 23:29:20 +02:00
tp1de
c481e1fe0f wwcomfort1 for UBAParameterWWPlus (RC310) 2022-05-19 16:59:03 +02:00
tp1de
9034c16e27 wwFlowTempOffset RC310 2022-05-18 18:11:17 +02:00
MichaelDvP
33e58ec45a RC30 designtemp write, 2.fix #496 2022-05-18 07:05:11 +02:00
MichaelDvP
e175b5a5a2 heatpump summermode -> heatpump operatingsmode 2022-05-17 20:53:22 +02:00
MichaelDvP
6072606918 heatpump summer mode, fix #503 2022-05-17 12:50:34 +02:00
MichaelDvP
3a04e891b4 Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev 2022-05-17 12:21:51 +02:00
Proddy
862cc8d982 Merge pull request #502 from proddy/dev
#501
2022-05-16 21:30:40 +02:00
Proddy
fc29a3ad95 replace api/settings and customizatons with secure REST calls #501 2022-05-16 21:30:06 +02:00
Proddy
ae9af3bf0b update packages 2022-05-16 21:29:39 +02:00
Proddy
1bdaaf0d0e Merge branch 'emsesp:dev' into dev 2022-05-15 10:44:52 +02:00
MichaelDvP
f3e99f9092 Add HM200 hybrid manager as heatpump 2022-05-15 08:58:28 +02:00
Proddy
ba315f2159 Merge pull request #497 from MichaelDvP/dev
fix #496, RC30 design temp
2022-05-14 21:06:39 +02:00
MichaelDvP
1863e57f5b fix #496, RC30 design temp 2022-05-12 18:21:30 +02:00
Proddy
7e26c0633a Merge branch 'emsesp:dev' into dev 2022-05-08 20:14:16 +02:00
Proddy
226a557d5c prevent build failing 2022-05-08 20:13:48 +02:00
Proddy
08021138ae Merge branch 'emsesp:dev' into dev 2022-05-08 20:11:32 +02:00
Proddy
db8c30ddbb fix for customizations after upload new file 2022-05-08 17:02:38 +02:00
Proddy
0e9202ae4d change to saving customization file after upload 2022-05-08 17:01:38 +02:00
Proddy
85f6628718 Merge pull request #495 from proddy/dev
standalone compiling fix
2022-05-08 15:02:25 +02:00
Proddy
8dab64b9bb Merge branch 'emsesp:dev' into dev 2022-05-08 15:01:44 +02:00
Proddy
8b521aa572 fix standalone compiling 2022-05-08 15:01:04 +02:00
Proddy
663fd22e03 Merge pull request #494 from proddy/dev
Feature: upload customization settings from a file #256
2022-05-08 14:59:16 +02:00
Proddy
1f933fb26a updates for #256 2022-05-08 14:58:41 +02:00
Proddy
0faa56f5ff Merge branch 'emsesp:dev' into dev 2022-05-08 12:57:33 +02:00
Proddy
9c2aac31b6 Merge pull request #493 from MichaelDvP/dev
api entitiy attributes with hcs
2022-05-08 12:57:12 +02:00
MichaelDvP
c0aa263f2b parse_command checks prefix-numbers and prefix id 2022-05-08 11:19:12 +02:00
Proddy
87c9d4d823 fix duplication 2022-05-07 18:41:42 +02:00
Proddy
fd53999777 Merge branch 'dev' of https://github.com/proddy/EMS-ESP32 into dev 2022-05-07 18:40:21 +02:00
Proddy
ce60bd10b6 telnet and analog on by default 2022-05-07 17:50:36 +02:00
Proddy
ee0492771d text change 2022-05-07 17:50:22 +02:00
Proddy
964e75e191 minor change 2022-05-07 17:50:10 +02:00
Proddy
cb16be6b1d remove id from User 2022-05-07 17:50:00 +02:00
Proddy
84a2a7340c comments 2022-05-07 17:49:49 +02:00
Proddy
a383925661 fix axios error reporting 2022-05-07 17:49:36 +02:00
Proddy
c49928bf3b remove id in User 2022-05-07 17:49:27 +02:00
Proddy
02fc57961b remove id 2022-05-07 17:49:15 +02:00
Proddy
9923b60d64 Feature: upload customization settings from a file #256 2022-05-07 17:49:02 +02:00
Proddy
f243162724 comments 2022-05-07 17:46:49 +02:00
Proddy
b35ab94509 upload script example 2022-05-07 17:46:31 +02:00
Proddy
4f399b51f5 package updates 2022-05-07 17:46:13 +02:00
MichaelDvP
6277fbbea1 api entitiy attributes with hcs 2022-05-06 13:32:18 +02:00
Proddy
1b21e02b7a Merge pull request #492 from MichaelDvP/dev
some small fixes
2022-05-06 12:42:43 +02:00
MichaelDvP
1a5b012545 csv utf-8 with semicolon and Intl.Number format 2022-05-06 12:15:25 +02:00
MichaelDvP
b2429e3cff charset for plain text output 2022-05-06 07:59:48 +02:00
MichaelDvP
90aa385326 shower-times in system/settings 2022-05-06 07:59:07 +02:00
MichaelDvP
39d9eec890 mdns in lib_standanlone 2022-05-06 07:58:22 +02:00
Proddy
b3f0ba348a Merge pull request #489 from MichaelDvP/dev
options for mDNS and dashboard boolean format
2022-05-04 15:54:11 +02:00
MichaelDvP
5d2f648d03 add option for rendering booleans on Dashboard #456 2022-05-04 15:46:23 +02:00
MichaelDvP
e00da5a721 add option to switch mDNS off/on 2022-05-04 14:14:17 +02:00
Proddy
d344924a3c Merge pull request #488 from proddy/dev
remove comments, render nicer on mobiles
2022-05-03 22:21:34 +02:00
Proddy
7f405e5212 Merge branch 'emsesp:dev' into dev 2022-05-03 22:21:13 +02:00
Proddy
c2f38396eb Merge branch 'dev' of https://github.com/proddy/EMS-ESP32 into dev 2022-05-03 22:20:57 +02:00
Proddy
501726c6ad remove comments, render nicer on mobiles 2022-05-03 22:20:55 +02:00
Proddy
d07f18bd41 Merge pull request #487 from proddy/dev
table formatting again
2022-05-03 19:51:00 +02:00
Proddy
931782df1c Merge branch 'emsesp:dev' into dev 2022-05-03 19:50:42 +02:00
Proddy
8e65e31ed6 table formatting 2022-05-03 19:50:07 +02:00
Proddy
8a4decd48b Merge pull request #486 from proddy/dev
minor formatting changes to table headers
2022-05-02 15:12:43 +02:00
Proddy
66b3572dfe Merge branch 'emsesp:dev' into dev 2022-05-02 15:12:19 +02:00
Proddy
7172ed303e make table headers consistent 2022-05-02 15:11:57 +02:00
Proddy
883c81320b package updates 2022-05-02 15:11:42 +02:00
Proddy
7ad1251c89 Merge pull request #485 from proddy/dev
attributes via API #462
2022-05-02 13:10:40 +02:00
Proddy
75317d278d Merge branch 'emsesp:dev' into dev 2022-05-02 13:10:13 +02:00
Proddy
5438d7bbd9 API extract individual attributes - #462 2022-05-02 13:08:31 +02:00
Proddy
2ae5d4dcf5 Merge pull request #484 from proddy/dev
added shower commands & some refactoring
2022-05-01 23:00:28 +02:00
Proddy
473b870035 Merge branch 'emsesp:dev' into dev 2022-05-01 22:59:56 +02:00
Proddy
5e9e995e4b added shower trigger and coldshot times - #436 2022-05-01 22:59:35 +02:00
Proddy
270b81fafd static code analysis warning fixes 2022-05-01 22:59:17 +02:00
Proddy
191e47eb14 rename value to stop static code analysis complaining 2022-05-01 22:58:50 +02:00
Proddy
20ff1725e7 Merge pull request #483 from proddy/dev
update react dropzone
2022-05-01 17:54:29 +02:00
Proddy
a0759f68b2 Merge branch 'emsesp:dev' into dev 2022-05-01 17:53:59 +02:00
proddy
08135baae6 update dropzone 2022-05-01 17:53:41 +02:00
Proddy
bf40d8feaf Merge pull request #482 from proddy/dev
make value sortable, it doesn't really make sense but it fixes the formatting alignment issues
2022-05-01 16:41:29 +02:00
Proddy
2c54cafdb6 Merge branch 'emsesp:dev' into dev 2022-05-01 16:40:40 +02:00
Proddy
3ff75db00a add more minor formatting 2022-05-01 16:35:45 +02:00
Proddy
469677aba5 Merge pull request #481 from proddy/dev
table formatting, upgrade packages & arduinojson, fix mqtt retry
2022-05-01 16:04:46 +02:00
Proddy
da2c81818e table formatting 2022-05-01 16:02:48 +02:00
Proddy
c1a71afd77 update ArduinoJson to 6.19.4 2022-05-01 15:54:49 +02:00
Proddy
7fe2b843e6 b14 2022-05-01 15:54:07 +02:00
Proddy
2c490e4148 possible fix for #476 2022-05-01 15:43:36 +02:00
Proddy
6241999d2f more formatting 2022-05-01 15:38:58 +02:00
Proddy
6b315d52a8 update axioserror 2022-05-01 13:12:40 +02:00
Proddy
0143e89e27 table formatting 2022-05-01 13:12:30 +02:00
Proddy
7130513593 update packages 2022-05-01 13:12:05 +02:00
Proddy
a721826821 fix Updating table component in the WebUI to support sorting & filtering #470 2022-05-01 13:00:48 +02:00
Proddy
cc42ac3584 Merge pull request #478 from MichaelDvP/dev
fix #476 and some names
2022-04-25 09:02:54 -04:00
MichaelDvP
4bba52a09e native boolean values in commands, fix #476 2022-04-25 14:38:12 +02:00
MichaelDvP
00abae10ac network security names fixed 2022-04-25 14:37:15 +02:00
MichaelDvP
eafec3045d hp full names lower case (see #450) 2022-04-25 14:36:21 +02:00
Proddy
343d0a7baa fix arduino core to 3.5.0 for ci jobs 2022-04-24 19:28:40 -04:00
Proddy
b688f6f7d0 b13 2022-04-24 19:21:44 -04:00
Proddy
9cc03948fa Merge pull request #475 from proddy/dev
Changes to Table (sorting, format, search)
2022-04-24 19:20:16 -04:00
proddy
c80d5c6bbf fix sort when click on icon and 3-way sort cycle 2022-04-24 19:18:19 -04:00
proddy
271b36c602 update packages 2022-04-24 19:18:02 -04:00
proddy
b2a885bf3f hide web also deselects favourite 2022-04-24 14:20:49 -04:00
proddy
c0c33d80c7 fix typo in sorting 2022-04-24 14:20:30 -04:00
proddy
9ff497914e fix to arduino 3.5.0 2022-04-24 14:20:16 -04:00
proddy
726b4fcc0b more updates to tables 2022-04-20 18:06:35 -04:00
proddy
56860da4af remove TODO on sorting 2022-04-20 18:06:26 -04:00
Proddy
929a97622c Merge branch 'dev' of https://github.com/proddy/EMS-ESP32 into dev 2022-04-19 17:22:21 +02:00
Proddy
09d25c04bc add search 2022-04-19 17:22:20 +02:00
Proddy
15f5f55833 Merge branch 'emsesp:dev' into dev 2022-04-19 12:25:11 +02:00
Proddy
a243b0a853 Merge pull request #472 from bavo/fix-selected-temp-range
Increase range for the selected_room_temperature variable
2022-04-19 12:24:56 +02:00
bavo
d76a838165 Increase range for the selected_room_temperature variable 2022-04-19 12:14:38 +02:00
Proddy
22595b0f24 show resize column header bars, disable default sorting 2022-04-18 12:54:44 +02:00
Proddy
9f6bbac8ce formatting 2022-04-18 11:05:38 +02:00
Proddy
101f94bea6 Merge pull request #5 from proddy/newtable
Newtable
2022-04-17 20:58:26 +02:00
proddy
52847b06f4 text changes 2022-04-17 20:53:53 +02:00
proddy
3c9cad2717 add set all enabled button 2022-04-17 20:52:24 +02:00
proddy
3acb9c456e formatting 2022-04-16 18:16:01 +02:00
Proddy
615e86d177 update 2022-04-16 16:58:49 +02:00
proddy
9d7820d155 togglebutton 2022-04-16 15:31:46 +02:00
Proddy
275044bd78 customizations table part 1 2022-04-16 09:04:34 +02:00
Proddy
2f21c896a6 fix dallas sensor checking 2022-04-15 18:40:35 +02:00
Proddy
04a374c380 more tables 2022-04-15 16:22:05 +02:00
Proddy
4b3b9524ef changes to backend, some refactoring 2022-04-15 13:04:52 +02:00
Proddy
9e293136b9 Merge branch 'dev' of https://github.com/proddy/EMS-ESP32 into newtable 2022-04-15 08:11:20 +02:00
Proddy
45cda9b0cc Merge pull request #471 from MichaelDvP/dev
Additions for #437, #459, #464
2022-04-14 18:26:46 +02:00
Proddy
cd5fef6891 add export button 2022-04-14 18:25:50 +02:00
MichaelDvP
a9a11f464b add comment to boiler: hybrid heatpump 2022-04-14 11:59:44 +02:00
Proddy
2a4288e11d initial commit with new table code 2022-04-14 11:44:01 +02:00
MichaelDvP
47410ce6cc Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev_ 2022-04-14 07:38:32 +02:00
Proddy
fdefd02812 Merge pull request #467 from kwertie01/feature/moduline400WWandTimer
Feature Moduline 400 WW settings and timer clock program
2022-04-13 23:21:51 +02:00
Friso
4cdeef212d fixed T1 to T4 for RC20 2022-04-13 11:18:28 +02:00
Friso
258db5c7e8 disable usage of T0 for RC30 in set_switchtime 2022-04-13 11:09:31 +02:00
Friso
b59ef15cae fixed set_switchtemp, process_RC20Timer 2022-04-13 09:17:22 +02:00
MichaelDvP
94f5d4d503 fix set_switchtime 2022-04-12 18:56:23 +02:00
Friso
9ed2a99f50 fix duplicate functions after rebase 2022-04-12 07:00:41 +02:00
Friso
9f86444e0a Merge branch 'feature/moduline400WWandTimer' of https://github.com/kwertie01/EMS-ESP32 into feature/moduline400WWandTimer 2022-04-12 06:35:33 +02:00
kwertie01
7bad5d10d8 fix sunday in dayofweek enum 2022-04-12 06:34:13 +02:00
kwertie01
f23bf270d0 fix wrong offset in set_party 2022-04-12 06:34:13 +02:00
kwertie01
8839909425 Added Moduline 400 WW settings and clock program 2022-04-12 06:34:13 +02:00
MichaelDvP
ec1b0b3641 add IPM ww-parameters to mixer 2022-04-11 15:03:07 +02:00
kwertie01
5882218cff fix sunday in dayofweek enum 2022-04-11 11:59:29 +02:00
kwertie01
cffc2695dd fix wrong offset in set_party 2022-04-11 11:57:42 +02:00
kwertie01
49eb049533 Added Moduline 400 WW settings and clock program 2022-04-11 11:43:15 +02:00
MichaelDvP
4009a1a25c b12, cleanup, formatting, small fixes 2022-04-11 10:20:40 +02:00
MichaelDvP
eec4d7863d move Hybrid settings to thermostat 2022-04-10 18:25:11 +02:00
MichaelDvP
0d79138e0b product-id 100 is mixer with max wwc10, add handlers_ignored 2022-04-10 14:37:43 +02:00
MichaelDvP
75914da36a thermostat switchpoints allow T1-T4 2022-04-10 14:35:55 +02:00
MichaelDvP
cf876acc5d rename hybrid entities 2022-04-10 14:34:10 +02:00
MichaelDvP
6d6cb755e2 update hybrid heatpump #459 2022-04-10 10:03:36 +02:00
Proddy
555b1ad996 Merge pull request #460 from MichaelDvP/dev
add Hybrid Heatpump
2022-04-08 17:25:52 +02:00
MichaelDvP
15a8d6dbf1 add Hybrid Heatpump 2022-04-08 15:30:34 +02:00
Proddy
c3256824b2 Merge pull request #458 from MichaelDvP/dev
mqtt HA uses only json, disable single
2022-04-08 13:42:15 +02:00
MichaelDvP
9c68142a0d mqtt HA uses only json, disable single 2022-04-08 11:58:48 +02:00
Proddy
b496f7731a Merge pull request #457 from MichaelDvP/dev
Changes #450
2022-04-08 11:13:00 +02:00
MichaelDvP
d203092ba7 Don't show banner while uploading 2022-04-08 10:03:05 +02:00
MichaelDvP
f771bb8043 add clock to controller #439 2022-04-08 10:02:24 +02:00
MichaelDvP
f9b2a71b86 tag to wwstarts2, thermostat clock format, formatting 2022-04-08 10:01:46 +02:00
Proddy
8479be5bd4 Merge pull request #455 from proddy/dev
mqtt clean session default off #441
2022-04-08 07:47:01 +02:00
Proddy
4eddad2cf1 mqtt clean session default off #441 2022-04-08 07:46:18 +02:00
Proddy
8b31e8a9c4 Merge pull request #453 from proddy/dev
updated changelog for Moduline updates
2022-04-06 20:23:35 +02:00
Proddy
d5245f7e7c added missing v3.3.1 to the changelog history 2022-04-06 20:22:14 +02:00
Proddy
56bb5433a0 updated packages 2022-04-06 20:20:24 +02:00
Proddy
3d8396a35a added moduline 400 additions 2022-04-06 20:20:18 +02:00
Proddy
1a58f76ab7 Merge pull request #449 from kwertie01/feature/moduline400
added moduline 400 installation parameters
2022-04-06 10:15:26 +02:00
Proddy
4279962e59 Merge pull request #452 from proddy/dev
saving customizations would overwrite previous settings
2022-04-05 23:29:30 +02:00
Proddy
503df0842b fix saving customizatons #450 2022-04-05 23:28:34 +02:00
Proddy
4a93adb1f8 remove aliases 2022-04-05 23:28:10 +02:00
kwertie01
2674b3a20e added moduline 400 installation parameters 2022-04-05 08:23:30 +02:00
Proddy
3d072f73b7 Merge pull request #447 from proddy/dev
changes per #444
2022-04-04 21:47:12 +02:00
Proddy
4fc5932899 only send back changed entities, put in limit at 50 #444 2022-04-04 21:44:51 +02:00
Proddy
077c20fd34 minor formatting 2022-04-04 21:44:23 +02:00
Proddy
d9716ceb42 added debug line for customization 2022-04-04 21:43:30 +02:00
Proddy
51af4b32d2 removed comments 2022-04-04 21:43:08 +02:00
Proddy
08cae822f1 Merge pull request #445 from MichaelDvP/dev
Status of changes for the last issues
2022-04-04 15:24:59 +02:00
MichaelDvP
30d6fc0fc1 customization buffer 4k, log(debug) buffer size 2022-04-04 12:03:39 +02:00
MichaelDvP
462bf81be1 ntp status and time button 2022-04-04 11:58:53 +02:00
MichaelDvP
26758b965d ivt clock check dst 2022-04-04 11:11:37 +02:00
MichaelDvP
917e4f5cbf check/format solar 2022-04-04 11:10:52 +02:00
MichaelDvP
f02621d1d8 formatting 2022-04-03 19:18:05 +02:00
MichaelDvP
45e0998172 Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev 2022-04-03 18:35:00 +02:00
MichaelDvP
755368440b customization advice with selected icons 2022-04-03 18:34:38 +02:00
MichaelDvP
728ccaefa7 dont set ivt clock automaically 2022-04-03 18:34:09 +02:00
Proddy
ecaa0ec4bb Merge pull request #442 from proddy/dev
fix changing masks for customizations
2022-04-03 17:50:13 +02:00
MichaelDvP
d94eb26fa8 solar mqtt 2022-04-03 13:49:17 +02:00
Proddy
9774661a40 send complete list of entites to server 2022-04-03 13:44:54 +02:00
Proddy
38128f7864 changes to mask_entity() 2022-04-03 13:44:37 +02:00
Proddy
1f8213e315 changes for handling entities in customization 2022-04-03 13:44:02 +02:00
Proddy
fce9a25280 add const function 2022-04-03 13:43:37 +02:00
Proddy
b2de5d47dd small code changes 2022-04-03 13:43:24 +02:00
Proddy
77f583f91f only add entities who's mask has changed to list 2022-04-03 13:43:08 +02:00
Proddy
4a56726f61 bump version 2022-04-03 13:42:32 +02:00
Proddy
4f3dacf81a make deviceentity.w mandatory 2022-04-03 13:42:18 +02:00
Proddy
14593f6a51 reset some test data 2022-04-03 13:41:57 +02:00
Proddy
ae16d01734 add comment about has_cmd 2022-04-03 13:41:38 +02:00
MichaelDvP
6069af3e90 ISM2 missing values, ISM2dhw in solar class 2022-04-03 09:43:53 +02:00
MichaelDvP
4c2408dfda add ISM2 and DHW module #437 2022-04-02 19:10:04 +02:00
Proddy
0bd122b643 Merge pull request #435 from MichaelDvP/dev
check readonly before set datetime to thermostat
2022-04-02 10:19:13 +02:00
MichaelDvP
d747ba0b14 check readonly before set datetime to thermostat 2022-04-02 10:12:08 +02:00
Proddy
91484fd09f Merge pull request #434 from proddy/dev
smaller icons in customization table
2022-04-01 09:17:12 +02:00
Proddy
d427a4bd6c smaller icons 2022-04-01 09:16:12 +02:00
Proddy
b455827c17 Merge branch 'emsesp:dev' into dev 2022-04-01 09:15:48 +02:00
Proddy
b6363957a8 Merge pull request #432 from MichaelDvP/dev
fix/optimization for mode/temperature set
2022-04-01 09:12:25 +02:00
Proddy
ad27f76c4a update packages 2022-03-31 21:50:19 +02:00
MichaelDvP
eeec7142c7 fix mode preset after change for different thermostats 2022-03-31 13:59:57 +02:00
MichaelDvP
efd758d627 seltemp command, check mode first for RC30_n and Junkers 2022-03-31 12:44:39 +02:00
Proddy
608d5e332d Merge pull request #431 from proddy/dev
minor updates to customization test data and layout
2022-03-30 21:05:29 +02:00
proddy
cf4fa9e76d formatting icon table 2022-03-30 20:59:57 +02:00
proddy
2ebb77ff37 don't prefix a count with more than 1 thermostats 2022-03-30 20:59:47 +02:00
proddy
744154ccf4 make customization change test data 2022-03-30 20:59:30 +02:00
Proddy
90c38d0403 Merge pull request #430 from MichaelDvP/dev
Errormessage
2022-03-30 17:51:53 +02:00
MichaelDvP
55d7ef1036 boiler Errormessage 0xBF to trigger 0xC2 reading 2022-03-30 15:56:05 +02:00
MichaelDvP
7c37c9a4c8 recreate ha config on readonly changes 2022-03-30 15:55:24 +02:00
Proddy
54d09ceb51 Merge pull request #429 from proddy/dev
updates to customization screen
2022-03-30 13:16:40 +02:00
proddy
84e21ab992 layout changes to help text 2022-03-30 13:15:48 +02:00
proddy
cb9ddb299a show commands differently 2022-03-30 13:15:38 +02:00
proddy
9e5fdb7cb1 use boolean rendering to fix Web errors with enums 2022-03-30 13:15:25 +02:00
Proddy
97c1d6d73a Merge pull request #427 from MichaelDvP/dev
add NTP status to mqtt/ha (heartbeat) and log
2022-03-29 20:28:47 +02:00
Proddy
0f0fc2a8f7 Merge pull request #426 from proddy/dev
#418
2022-03-29 18:41:24 +02:00
Proddy
c41344e2a5 Merge branch 'emsesp:dev' into dev 2022-03-29 18:40:59 +02:00
MichaelDvP
07b438f6f0 add NTP status to mqtt/ha (heartbeat) and log 2022-03-29 18:40:32 +02:00
proddy
2bf7bf3071 Merge branch 'dev' of https://github.com/proddy/EMS-ESP32 into dev 2022-03-29 18:38:25 +02:00
proddy
5096964825 put total at start of description #418 2022-03-29 18:38:23 +02:00
Proddy
b96c2a731c Merge pull request #425 from proddy/dev
more icons to dashboard
2022-03-29 18:33:58 +02:00
Proddy
4f1b768b6c Merge branch 'emsesp:dev' into dev 2022-03-29 18:33:17 +02:00
proddy
d3002ce415 show all mask icons 2022-03-29 18:32:47 +02:00
proddy
a5ba70a6d7 some cosmetic changes 2022-03-29 18:32:28 +02:00
proddy
4118a576de enum for dv flags 2022-03-29 18:15:55 +02:00
proddy
4bcbcf4e3a update test data 2022-03-29 18:15:38 +02:00
proddy
9775cc796a update packages 2022-03-29 18:15:29 +02:00
Proddy
d269029bb1 Merge pull request #424 from MichaelDvP/dev
try to fix #408 and #412
2022-03-29 16:35:12 +02:00
MichaelDvP
786a94b448 try to fix #408 and #412 2022-03-29 15:30:39 +02:00
Proddy
80c87c8c43 Merge pull request #423 from MichaelDvP/dev
remove `(hidden)` text, force exclude from web icon
2022-03-29 12:16:16 +02:00
MichaelDvP
39d78fb444 remove (hidden) text, force exclude from web icon 2022-03-29 10:38:05 +02:00
Proddy
ac1de4c995 Merge pull request #422 from proddy/dev
Michael's PR merged (dv sort, remove id)
2022-03-29 09:06:53 +02:00
proddy
6f347bd49e Michael's PR merged (dv sort, remove id) 2022-03-29 09:06:09 +02:00
Proddy
cc31143a6c Merge pull request #421 from proddy/dev
fixes #411
2022-03-28 17:52:44 +02:00
proddy
86430c6408 updated packages 2022-03-28 17:52:07 +02:00
proddy
1cc031b27b minor optimizations 2022-03-28 17:50:51 +02:00
proddy
592c5ca778 fix devicevalue sorting the Michael way (thanks) 2022-03-28 17:50:33 +02:00
proddy
bad6346e7a update test data 2022-03-28 17:50:06 +02:00
proddy
cf10791c95 remove tooltip, add icons for fav and cmd 2022-03-28 17:49:57 +02:00
proddy
3f68c0001e formatting 2022-03-28 17:49:38 +02:00
Proddy
07ec5c3b12 Merge pull request #417 from MichaelDvP/dev
fix #412, daylightsaving with manual time setting
2022-03-28 10:38:53 +02:00
MichaelDvP
2d8f97ff35 fix #412, daylightsaving with manual time setting 2022-03-28 09:47:56 +02:00
Proddy
28ae1ae3d4 Merge pull request #416 from proddy/dev
Dev
2022-03-27 23:19:52 +02:00
Proddy
b9e1c0ba56 Merge branch 'emsesp:dev' into dev 2022-03-27 23:18:14 +02:00
proddy
317cc8eeee ignore lint error so build works 2022-03-27 23:16:12 +02:00
proddy
3ddee386b2 code optimization 2022-03-27 23:16:02 +02:00
Proddy
9f946f3514 Merge pull request #414 from proddy/dev
code optimizations
2022-03-27 22:48:27 +02:00
Proddy
904198013e Merge branch 'emsesp:dev' into dev 2022-03-27 22:47:51 +02:00
proddy
ac7c7cee84 code optimizations 2022-03-27 22:47:16 +02:00
Proddy
6c3d33d4a1 Merge pull request #413 from proddy/dev
rename exclude_entities to masked_entities - fixes for #411
2022-03-27 22:19:35 +02:00
proddy
3853c6ca18 added tooltip back to be consistent with other table actions 2022-03-27 22:14:20 +02:00
proddy
f3b7d33372 bump b10 2022-03-27 22:13:13 +02:00
proddy
7a67577aa1 added w (writeable) 2022-03-27 22:13:04 +02:00
proddy
fe3e02cb2e added tooltip 2022-03-27 22:12:49 +02:00
proddy
821d7845f0 updated b10 2022-03-27 22:12:21 +02:00
proddy
f7709e19fd added w (Writable) to generate_values_web and sort by favorite 2022-03-27 22:12:11 +02:00
proddy
fdfc5e0e68 include name of device in options 2022-03-27 19:57:11 +02:00
proddy
b331a07b69 add masks to customize screen 2022-03-27 19:56:54 +02:00
proddy
f5bf566e66 remove aria-label 2022-03-27 19:56:35 +02:00
proddy
f80d796333 use mask flags 2022-03-27 16:21:12 +02:00
proddy
afd55c52e5 formatting 2022-03-27 16:20:42 +02:00
proddy
b4cc300190 fix lint warning 2022-03-27 16:20:32 +02:00
proddy
03af305761 send state to WebUI (only the high nibble mask bits) 2022-03-27 16:20:19 +02:00
proddy
079f4e5ac0 replace exclude_entities with masked_entities 2022-03-27 16:19:55 +02:00
proddy
ff075a4f56 use m for mask instead of x 2022-03-27 16:18:56 +02:00
Proddy
b031e1a232 Merge pull request #409 from MichaelDvP:dev
Customizations to names with more options
2022-03-26 21:29:21 +00:00
proddy
175412774c pass cmd string as reference 2022-03-26 22:28:50 +01:00
proddy
d0efe8eb47 changes for standalone compilation 2022-03-26 22:17:25 +01:00
proddy
be0d5557a6 update packages 2022-03-26 22:17:10 +01:00
MichaelDvP
24f6fcd2d4 version b9, update changelog 2022-03-23 12:56:47 +01:00
MichaelDvP
9bef53c16c add #386 and #408, sync time with thermostat 2022-03-23 12:36:54 +01:00
MichaelDvP
9b70985d32 readonly to HA config 2022-03-23 12:22:49 +01:00
MichaelDvP
a0a3d8ef3a min/max to web value edit 2022-03-21 20:21:55 +01:00
MichaelDvP
4ae406b3e1 Customizations to names with more options 2022-03-21 19:24:11 +01:00
MichaelDvP
51f2009a2c npm update 2022-03-21 14:56:41 +01:00
MichaelDvP
8d172e0b57 set mode in advance after command, #395 2022-03-21 13:55:01 +01:00
MichaelDvP
4a132e769c fix publish reset 2022-03-21 13:50:54 +01:00
MichaelDvP
786110359a fix #339 maxJsonBuffrSize 2022-03-21 13:44:40 +01:00
MichaelDvP
3b5560b741 fix console show with empty full name 2022-03-21 13:40:54 +01:00
proddy
0351f4fbb3 update packages 2022-03-19 12:05:38 +01:00
Proddy
a695f85359 Merge pull request #401 from MichaelDvP/dev_
add RC35 values #398
2022-03-14 15:06:21 +01:00
MichaelDvP
d0aa601301 add RC35 values #398 2022-03-13 19:25:48 +01:00
proddy
508e98e3b6 update packages again 2022-03-12 17:40:59 +01:00
proddy
1227772696 remove comments 2022-03-12 17:40:51 +01:00
Proddy
0aa5c73635 Update README.md
added sonarcloud badge
2022-03-11 17:00:09 +01:00
Proddy
afda97870a Merge pull request #394 from MichaelDvP/dev
adds for #392 and #393
2022-03-11 16:57:02 +01:00
MichaelDvP
d56cdeec77 add burner stage 2 working time, #392 2022-03-11 13:25:18 +01:00
MichaelDvP
657914db26 add devices #393 2022-03-11 13:04:47 +01:00
Proddy
f539a236f1 Merge pull request #391 from MichaelDvP/dev
fix #390, fetching summer2_ids
2022-03-11 08:51:45 +01:00
MichaelDvP
52a9e500df fix #390, fetching summer2_ids 2022-03-11 07:20:52 +01:00
proddy
df0da84b9f updated packages again, why not 2022-03-10 09:53:56 +01:00
proddy
daaad1ce42 remove obsolete render function 2022-03-10 09:53:28 +01:00
Proddy
26b7075696 Merge pull request #389 from proddy/dev
update arduinojson 6.19.3
2022-03-09 09:15:03 +01:00
proddy
b526734e4b update arduinojson 6.19.3 2022-03-08 18:19:26 +01:00
Proddy
eaca7df527 Merge pull request #385 from MichaelDvP/dev
show correct hostname in startup message
2022-03-08 18:00:03 +01:00
MichaelDvP
1413bb7fbf fix typo in loading dallas names 2022-03-08 17:04:11 +01:00
MichaelDvP
5a09de002a show correct hostname in startup message 2022-03-08 12:02:18 +01:00
MichaelDvP
a2f8435204 fix mDNS on network reconnect 2022-03-08 07:07:52 +01:00
Proddy
47aab98964 Merge pull request #384 from proddy/dev
update static code analysis from codacy to sonar #381
2022-03-07 12:50:42 +01:00
proddy
4f696a4947 bufsize const in messagetime() 2022-03-07 12:46:27 +01:00
proddy
828e769b3b add bufsize to messagetime() 2022-03-07 12:30:50 +01:00
proddy
69dc26005a fix strcpy length 2022-03-06 11:33:59 +01:00
proddy
f19bf17d21 3.4.0b8 2022-03-05 17:27:22 +01:00
proddy
d09e2237ee sonar recommendations 2022-03-05 16:21:00 +01:00
proddy
10830dee36 update build dir 2022-03-05 14:51:18 +01:00
proddy
5f7f670517 add GH action 2022-03-05 14:40:29 +01:00
proddy
c1edbbf047 sonar fixes 2022-03-05 14:25:35 +01:00
Proddy
e247b9e5f9 Merge pull request #379 from MichaelDvP:dev_
climate hidden, customization of hidden and commands, cleanup
2022-02-27 14:24:27 +01:00
MichaelDvP
c1fd964344 climate hidden, customization of hidden and commands, cleanup 2022-02-27 13:10:53 +01:00
proddy
f206ce7114 auto formatting 2022-02-26 18:12:42 +01:00
proddy
7438385729 upgrade packages 2022-02-26 18:12:33 +01:00
Proddy
142ca1e32c Merge pull request #378 from MichaelDvP:dev_
check selTemp, roomTemp visibility for climate creation
2022-02-26 17:58:53 +01:00
MichaelDvP
cde761fadd check selTemp, roomTemp visibility for climate creation 2022-02-26 17:15:42 +01:00
Proddy
16eb3f021d Merge pull request #375 from MichaelDvP/dev_
move HA climate to mqtt
2022-02-26 15:55:38 +01:00
MichaelDvP
9c15ddf952 HA climate as extra value, can be disabled 2022-02-26 14:23:45 +01:00
MichaelDvP
24216d7b4f move HA climate to mqtt 2022-02-23 10:29:56 +01:00
MichaelDvP
833ddf15d5 Merge pull request #369 from proddy/dev
optimized create/remove mqtt discovery topics
2022-02-22 08:27:17 +01:00
proddy
63a94dcef1 fix HA climate 2022-02-21 10:29:10 +01:00
Proddy
807bf4c061 Merge pull request #370 from MichaelDvP/dev_
add ww to thermostat single publish
2022-02-20 20:36:12 +01:00
MichaelDvP
a4ba130e5f add ww to thermostat single publish 2022-02-20 16:33:17 +01:00
proddy
d06145bb3a formatting 2022-02-20 13:41:12 +01:00
proddy
935e04b266 fixes Excluding thermostat entities should also remove the climate discovery topic #368 2022-02-20 13:40:58 +01:00
proddy
35fec3150b remove obsolete create_device_config and icons 2022-02-20 13:39:36 +01:00
proddy
493238e696 bump 3.4.0b7 2022-02-20 13:38:59 +01:00
proddy
cffc44b471 formatting 2022-02-20 13:38:42 +01:00
proddy
fcab3b1d46 update packages 2022-02-20 13:38:28 +01:00
proddy
1badaa725e update test for ha 2022-02-20 13:38:18 +01:00
Proddy
76579c426e Merge pull request #367 from MichaelDvP/dev_
add missing subscriptions to dallas/analogsensor
2022-02-19 15:15:25 +01:00
MichaelDvP
5f69395522 add missing subscriptions to dallas/analog 2022-02-19 12:25:01 +01:00
Proddy
9ceec6e306 Merge pull request #365 from MichaelDvP/dev_
publish all mixers to one json
2022-02-19 11:02:27 +01:00
MichaelDvP
a67913d660 publish all mixers to one json 2022-02-19 10:54:27 +01:00
Proddy
7da6b1925d Merge pull request #364 from proddy:dev
fix static code analysis warnings
2022-02-18 21:03:15 +01:00
proddy
fb05558ab9 fix static analysis warnings 2022-02-18 20:28:15 +01:00
Proddy
33835fef7b Merge pull request #362 from MichaelDvP/dev_mqtt
Fahrenheit for HA-climate min/max
2022-02-17 10:47:20 +01:00
proddy
f1f089baa0 v is always <= 255 2022-02-17 10:46:21 +01:00
proddy
d8f32d6ade hard code 128 as topic length to prevent compiler warnings 2022-02-17 10:46:02 +01:00
proddy
ca04ebccd2 formatting warning 2022-02-17 10:45:44 +01:00
proddy
d11a67527f make standalone build compile again 2022-02-17 10:45:29 +01:00
proddy
625fb41352 update packages to fix build 2022-02-17 10:44:56 +01:00
MichaelDvP
e0e90e3cab Fahrenheit for HA-climate min/max 2022-02-17 10:38:22 +01:00
Proddy
4c4a6f668d Merge pull request #361 from MichaelDvP/dev_mqtt
mqtt, analog, fahrenheit and other changes
2022-02-17 10:04:25 +01:00
MichaelDvP
b7d8447f73 version 3.4.0b6 2022-02-17 09:17:17 +01:00
MichaelDvP
18651bdaf4 changelog, typo 2022-02-16 21:00:57 +01:00
MichaelDvP
9046a6578a telegram read length depends on ems/ems+ 2022-02-16 19:37:14 +01:00
MichaelDvP
4219842088 fetch devices one by one 2022-02-16 19:36:17 +01:00
MichaelDvP
3b41d6fff6 fahrenheit uom 2022-02-16 19:14:15 +01:00
MichaelDvP
b2eaca27de remove unused flag 2022-02-16 18:58:45 +01:00
MichaelDvP
9ccb04489b log detection of devcies without values 2022-02-16 18:58:15 +01:00
MichaelDvP
6b164b5487 burner up to 130% 2022-02-16 18:57:43 +01:00
MichaelDvP
073493cba2 analogsensors, outputs PWM, DAC, digital 2022-02-16 18:35:25 +01:00
MichaelDvP
7f5e0f7244 Mqtt: remove all HA if not active, timeout QoS, option single2cmd 2022-02-16 17:59:53 +01:00
MichaelDvP
7bb6f55153 solar fix SM10 energy, remove unknowns 2022-02-16 17:02:54 +01:00
MichaelDvP
7f21bea8a6 Thermostat: RC20 temperatures, RC300 roominflfactor, fetch monitor 2022-02-16 15:47:49 +01:00
Proddy
802e7a080f Merge branch 'dev' into dev 2022-02-13 22:31:09 +01:00
proddy
a7c1a75996 added serial debug flag 2022-02-13 21:44:36 +01:00
proddy
70803b1a6d text change 2022-02-13 21:44:25 +01:00
proddy
005b9a88c3 update packages 2022-02-13 21:44:16 +01:00
proddy
7214b5beea fixes Excluding entities from the Customization page does not remove the MQTT Discovery entry #357 2022-02-13 21:44:02 +01:00
Proddy
e2163546fe Merge branch 'emsesp:dev' into dev 2022-02-13 13:01:18 +01:00
proddy
17bc9c231a dropzone minor upgrade 2022-02-13 09:59:39 +01:00
proddy
0b4e9da5d5 fix mqtt nested thermostat data 2022-02-13 09:59:20 +01:00
Proddy
002fa82afb Merge branch 'emsesp:dev' into dev 2022-02-12 17:38:38 +01:00
proddy
482baef360 update with latest fixes 2022-02-12 15:08:03 +01:00
proddy
20b876bdd6 fixes #354 2022-02-12 15:05:22 +01:00
proddy
d00ac1fa86 formatting 2022-02-12 15:04:37 +01:00
proddy
02e5b6e975 extend test for nested mqtt 2022-02-12 15:04:06 +01:00
proddy
1f17eda56f update packages 2022-02-12 15:03:45 +01:00
proddy
718f9a4f11 fixes #314 2022-02-12 15:03:31 +01:00
Proddy
668276e7d0 Merge branch 'emsesp:dev' into dev 2022-02-10 22:10:40 +01:00
proddy
3de769c7e7 add comment 2022-02-07 09:02:55 +01:00
proddy
984bbd493d move common time functions 2022-02-07 09:02:48 +01:00
proddy
74eabba641 remove uptime. we show it in 2 places already in the app 2022-02-07 09:02:17 +01:00
proddy
81d54ca69f tidy up sequence of services, start log and serial console first, catch any board profile errors first 2022-02-06 15:48:57 +01:00
proddy
ddee63b718 fix osx firmware upload - #345 2022-02-06 15:47:20 +01:00
Proddy
99743e8e0f Merge pull request #352 from MichaelDvP/dev_
fix telegram_last dest read flag
2022-02-06 12:19:12 +01:00
MichaelDvP
7d11539827 fix telegram_last dest read flag 2022-02-06 10:44:47 +01:00
proddy
7079098810 remove txmode off as option 2022-02-05 13:15:27 +01:00
proddy
e12ac26406 bump to b3 2022-02-05 13:12:03 +01:00
proddy
64e755bc15 update packages 2022-02-05 13:11:58 +01:00
proddy
a65b6bf19d fix compile warning 2022-02-05 13:11:42 +01:00
Proddy
5143040ba9 Merge pull request #332 from MichaelDvP/dev_
fix #329, numberformat in Edge, Chrome
2022-02-05 12:51:14 +01:00
Proddy
01c75ad5c0 Merge pull request #348 from MichaelDvP/dev_327
fix #327, analogsensor sending multiple mqtt messages
2022-02-05 12:50:43 +01:00
Proddy
43d0197a60 Merge pull request #347 from MichaelDvP/dev_340
fix #340, refresh only device/sensor-data if open
2022-02-05 12:48:18 +01:00
Proddy
35013a71d4 Merge pull request #346 from MichaelDvP/dev_336
fix #336, map Junkers hc3/4 to masterthermostat
2022-02-05 12:47:54 +01:00
Proddy
381acbf00f Merge pull request #323 from MichaelDvP/dev
always show RC300 tempautotemp with minimum to -1°C #321
2022-02-05 12:32:52 +01:00
proddy
47fb13aa4a allow larger exclude lists 2022-02-05 09:51:34 +01:00
proddy
f5aca6aa93 fix test compiling 2022-02-05 09:51:16 +01:00
MichaelDvP
95f4670b47 use numberValue for input to make all number inputs uniform 2022-01-31 17:00:11 +01:00
MichaelDvP
acc2412742 fix #327, analogsensor sending multiple mqtt messages 2022-01-31 13:39:30 +01:00
MichaelDvP
77df8cc69b fix #340, refresh only device/sensor-data if open 2022-01-31 10:23:10 +01:00
MichaelDvP
eaf651a4e2 fix #336, map Junkers hc3/4 to masterthermostat 2022-01-31 10:12:50 +01:00
proddy
3d1a050e22 HA roomtemp optional - #325 2022-01-26 23:18:36 +01:00
MichaelDvP
2db053ab04 fix #329, numberformat in Edge, Chrome 2022-01-26 21:32:45 +01:00
Proddy
915418eca8 Merge pull request #330 from MichaelDvP/dev_
fixes #327 #329
2022-01-26 20:27:02 +01:00
MichaelDvP
508a707c6e add steps to web input of numbers, #329 2022-01-26 18:35:55 +01:00
MichaelDvP
233d82805b add analog counter command, fix #327 "value" 2022-01-26 18:35:12 +01:00
Proddy
8c4e5e5185 Merge pull request #324 from MichaelDvP/dev_ 2022-01-25 13:24:29 +01:00
MichaelDvP
d3ca556914 fix #300 moduline 300 seltemp, next try 2022-01-25 12:26:55 +01:00
MichaelDvP
325a92f40d always show RC300 tempautotemp with minimum to -1°C #321 2022-01-25 08:08:46 +01:00
proddy
a89481cf14 bump mui libs 2022-01-24 20:59:11 +01:00
proddy
65be8ba6f7 auto formatting (npm run format) 2022-01-24 20:58:54 +01:00
proddy
a2b9b9e0c8 update with latest system/info changes 2022-01-24 20:58:35 +01:00
proddy
20bd5327f8 bump 3.4.0b1 2022-01-24 20:58:21 +01:00
proddy
4ac045afcf Please ad "ENTITIES" like on dashboard within api call ems/api/system per device #322 2022-01-24 20:58:10 +01:00
Proddy
4569ee50a5 Merge pull request #320 from MichaelDvP/dev
helptext for string commands
2022-01-24 20:31:13 +01:00
MichaelDvP
cd2ea1d5fc helptext for string commands 2022-01-24 18:02:57 +01:00
Proddy
ab34f7c056 Merge pull request #319 from MichaelDvP/dev 2022-01-24 13:30:09 +01:00
MichaelDvP
a27a5ebf4c sync DE-string (not completly translated) 2022-01-24 13:14:28 +01:00
MichaelDvP
be20fcf021 remove unused strings 2022-01-24 13:06:07 +01:00
MichaelDvP
f62317a338 system info: lower case and underscores 2022-01-24 12:54:41 +01:00
MichaelDvP
d27243eb34 rename Wired->Ethernet 2022-01-24 12:51:13 +01:00
MichaelDvP
8f5e26acd1 fix read command with length 2022-01-24 12:50:27 +01:00
MichaelDvP
e02f20d74e fix refresh sensorData 2022-01-24 12:49:57 +01:00
MichaelDvP
b0111d6653 formatting 2022-01-24 12:49:16 +01:00
proddy
a38d8c14fa Function parameter 'sensor' should be passed by const reference 2022-01-23 18:08:52 +01:00
proddy
77e1898512 Merge remote-tracking branch 'origin/v3.4' into dev 2022-01-23 17:56:52 +01:00
proddy
29110e96e5 Merge remote-tracking branch 'origin/dev' 2022-01-20 10:51:40 +01:00
Proddy
02e2b51814 Merge pull request #307 from MichaelDvP/dev
check received status before toggling fetch on empty telegram
2022-01-20 10:04:03 +01:00
MichaelDvP
e9588cc7a1 check received status before toggling fetch on empty telegram 2022-01-20 08:41:41 +01:00
Proddy
3d8e1b8f86 Merge pull request #245 from proddy:dev
Add back RC30 - #243
2021-12-14 20:23:34 +00:00
proddy
312f969364 add back RC30 - Setting mode on a RC30/Moduline400 via MQTT in HA doesn't work #243 2021-12-14 21:19:54 +01:00
Proddy
0f41079803 Merge pull request #235 from pswid:dev
fix overlapping 0xC2 and overflow of offset
2021-12-14 19:49:37 +00:00
pswid
7f30e8dadc fix overlaping while reading sequence of EMS1.0 telegrams 2021-12-14 10:32:31 +01:00
pswid
9cd20cfc05 fix overlaping while reading sequence of EMS1.0 telegrams 2021-12-14 10:29:15 +01:00
pswid
1343bbf6ea fix overlapping 0xC2 and overflow of offset 2021-12-03 11:50:52 +01:00
Proddy
90c39d1f85 Merge pull request #234 from pswid/dev 2021-12-03 09:38:40 +01:00
pswid
92da61376b Update boiler.cpp 2021-12-03 09:04:39 +01:00
pswid
69976c2caf Update CHANGELOG_LATEST.md 2021-12-03 09:02:36 +01:00
pswid
3c6fd0c83a Update boiler.cpp 2021-12-03 08:58:19 +01:00
proddy
b0a09747d4 3.3.1b0 2021-11-28 23:06:55 +01:00
proddy
b65866217a 3.4.0 2021-11-28 23:03:28 +01:00
proddy
611e3b1243 Merge remote-tracking branch 'origin/dev' 2021-11-28 23:03:15 +01:00
Proddy
84d3d42306 Merge pull request #220 from proddy:dev
minor updates
2021-11-25 09:16:03 +01:00
proddy
61e2739ef7 updated for b11 2021-11-25 09:13:48 +01:00
proddy
a3391afd27 bump to b11 2021-11-25 09:13:38 +01:00
proddy
ce9b7d1468 added test for lastcode 2021-11-25 09:13:31 +01:00
proddy
1b86314a14 formatting 2021-11-25 09:13:22 +01:00
proddy
b2a0519f83 change font type and size 2021-11-25 09:13:09 +01:00
proddy
3dec4bda8c fix double click 2021-11-25 09:12:57 +01:00
Proddy
5de529cbb2 Merge pull request #218 from pswid:dev
timestamp in boiler last error code
2021-11-25 08:49:44 +01:00
pswid
4fd1d8e08a Boiler last error code change 2021-11-23 19:11:35 +01:00
pswid
0847ccc602 Revert "fix boiler last error code timestamp"
This reverts commit 969803569e.
2021-11-23 19:09:46 +01:00
pswid
969803569e fix boiler last error code timestamp 2021-11-23 18:59:37 +01:00
Proddy
6532abd870 Merge pull request #217 from proddy:dev
text changes, renamed status to bus_status in heartbeat, improved WebUI table layout, added Ethernet phy to custom profile board
2021-11-22 09:11:41 +01:00
proddy
07dabb4ceb fix name of status -> bus_status in heartbeat 2021-11-22 08:49:53 +01:00
proddy
1c6a683015 rename bus to bus_status in info command 2021-11-22 08:49:33 +01:00
proddy
bb38458ac4 stripped table rows 2021-11-22 00:36:15 +01:00
proddy
53de2ca25b added ethernet phy type as an option in settings - #210 2021-11-21 13:51:02 +01:00
Proddy
5a9122f8be Merge pull request #215 from proddy/dev 2021-11-20 21:25:02 +01:00
proddy
dc84f91044 debug comments 2021-11-20 21:23:04 +01:00
proddy
4be6626470 fixed edge case in shower logic when state didn't change 2021-11-20 21:22:55 +01:00
proddy
7e4494aae1 rename status to bus_status in MQTT heartbeat 2021-11-20 21:22:28 +01:00
proddy
50e54e6a1c fix removing old HA config topics 2021-11-20 21:22:10 +01:00
Proddy
30b111b986 Merge pull request #208 from proddy:dev
fixes #207
2021-11-19 13:00:58 +01:00
proddy
a63f2e6131 fixes #207 - allow both data and value as MQTT keys 2021-11-19 12:59:52 +01:00
proddy
51d487b938 formatting 2021-11-19 12:57:48 +01:00
Proddy
96180c837d Merge pull request #203 from proddy:dev
Add Network to MQTT topic info and system/info command
2021-11-18 12:11:40 +01:00
proddy
da20cf1ed2 add Network to MQTT info topic and system/info command 2021-11-18 12:10:20 +01:00
proddy
bffad5e3c8 allow thermostat hc3 on device_id 0x1A - #200 2021-11-17 22:22:34 +01:00
Proddy
74287ebb99 Merge pull request #201 from proddy:dev
fixes #199 - change value validation fails
2021-11-17 19:03:27 +01:00
proddy
8b40c92f7e fixes #199 - change value formats degrees to strings 2021-11-17 19:01:29 +01:00
Proddy
5f6033cac1 Merge pull request #197 from proddy/dev
fixes #196 (HA missing entities), add row click to devices, change max limit on integer types to avoid NaN, refactored generation of device value JSON objects
2021-11-15 17:45:52 +01:00
proddy
24582407d4 b7 2021-11-15 15:27:25 +01:00
proddy
22c9e3ee1f tidy up generate_values_json_web 2021-11-15 15:23:01 +01:00
proddy
cb2c898b7e click row to edit 2021-11-15 15:22:46 +01:00
proddy
c0bf623266 rename publish_ha* functions 2021-11-15 14:28:14 +01:00
proddy
18f22d3951 add state to dv, using DeviceValueState 2021-11-15 14:27:53 +01:00
proddy
e2dad610b0 publish HA config topic after device values 2021-11-15 14:27:18 +01:00
proddy
5ed3cbee2e add days 2021-11-15 14:26:42 +01:00
proddy
27712badb6 allow empty payloads, refactor to also delete a HA topic 2021-11-15 14:26:30 +01:00
proddy
72c032adff replace read_flash_string 2021-11-15 14:25:49 +01:00
proddy
7197df9812 replace read_flash_string 2021-11-15 14:25:04 +01:00
proddy
898e2e5f21 bump b7 2021-11-15 14:24:33 +01:00
proddy
cde3b7541f update test for 196 2021-11-15 14:24:21 +01:00
proddy
822f55497e tidy up, mention HA enttity fix 2021-11-15 14:23:58 +01:00
Proddy
7c16870294 Merge pull request #194 from kpschaper/bugfix_settemp
bugfix revert auto mode -> the mode is hardcoded for the thermostat_ha_cmd #183
2021-11-14 18:17:18 +01:00
Proddy
e368f422f4 Merge pull request #195 from proddy/dev
Fix console UOM, set temperature to show 1 decimal place in web
2021-11-14 18:04:39 +01:00
proddy
9d9ac4ed9e force temperatures in degrees to always show 1 decimal place 2021-11-14 16:14:29 +01:00
proddy
d7576ebda1 fix bug showing UOM in Console 2021-11-14 16:14:04 +01:00
proddy
fd810ff01c add comments on format param 2021-11-14 16:10:32 +01:00
proddy
999a05f7ff bump to b6 2021-11-14 16:10:20 +01:00
proddy
a19f2f19c5 update test data 2021-11-14 16:10:10 +01:00
Koen Schaper
f54ccd40e6 revert auto mode -> the mode is hardcoded for the thermostat_ha_cmd #183 2021-11-14 15:32:12 +01:00
Proddy
5893487d4a Merge pull request #189 from proddy:dev
fix MQTT non-JSON payloads
2021-11-14 12:56:20 +01:00
Proddy
3c8e20d4e4 Merge pull request #193 from kpschaper/fix_rc10_mode
Mode update for #183
2021-11-14 12:55:37 +01:00
Koen Schaper
bf68a5523b Update changelog 2021-11-14 12:14:55 +01:00
Koen Schaper
9c5c27152c Allow mode "off" to be set from home assistant + remove non available "auto" mode from home assistant 2021-11-14 11:11:37 +01:00
proddy
c229371c68 fix typo 2021-11-14 10:26:13 +01:00
proddy
805cef68a2 fix handling on non json payloads - #173 2021-11-13 15:59:04 +01:00
proddy
09addcb975 with the release of espressif32 3.4.0 we don't need the delay anymore for telnet 2021-11-12 14:33:22 +01:00
Proddy
409d382ff9 Merge pull request #188 from MichaelDvP:dev
Mode update for #183
2021-11-11 20:04:25 +01:00
MichaelDvP
27bfc14438 set mode and heatingPID for RC10 #183 2021-11-11 17:30:17 +01:00
MichaelDvP
6c20a5f4f9 prevent double messages in weblog 2021-11-11 08:22:20 +01:00
MichaelDvP
ffd61a9f67 removed unused pragma 2021-11-11 08:21:12 +01:00
MichaelDvP
4f2da0347c add hc to console calls 2021-11-10 14:06:00 +01:00
MichaelDvP
3c13c144d5 fix wwc ids 2021-11-10 14:05:31 +01:00
proddy
d4eaedef3d text changes 2021-11-10 12:31:01 +01:00
proddy
a8f1892d48 rename system/info text, added MQTT status 2021-11-10 12:27:18 +01:00
proddy
e347ac5742 formatting and text changes 2021-11-10 12:26:59 +01:00
proddy
5f2a9b093d improve error handling, fix crash on empty /api URL 2021-11-10 12:26:29 +01:00
proddy
e821e8d082 update 2021-11-10 12:25:38 +01:00
proddy
05f56be2d8 bump version to 3.3.0b4 2021-11-09 21:59:44 +01:00
proddy
88f78f6541 fix authentication check for GET commands that need admin - Refactor MQTT subscriptions and API calls #173 2021-11-09 20:54:41 +01:00
MichaelDvP
234533f241 fix RC35 program no. #182 2021-11-09 15:44:41 +01:00
MichaelDvP
b1f72b0e3e fix crash on empty mqtt-payload 2021-11-08 18:30:33 +01:00
MichaelDvP
a3022f6f20 RC35 switchtime for prog 1 and prog 2 2021-11-08 18:29:25 +01:00
MichaelDvP
95f7583511 fix wrong RC10 tag 2021-11-08 07:57:08 +01:00
MichaelDvP
578ba386e6 add moduline200 values #183, RC35 holidays/vacations #182 2021-11-07 20:49:58 +01:00
proddy
df7be9d11e update to 3.3.0b3 2021-11-07 18:24:33 +01:00
Proddy
72a59917fb Merge pull request #185 from kpschaper/fix_shower_alert
Fix shower timer; doing_cold_shot is never being reset
2021-11-07 17:42:49 +01:00
Koen Schaper
9406c76e55 fix shower timer; doing_cold_shot is never being reset + remove unnecessary conditions 2021-11-07 16:34:40 +01:00
proddy
ea550b1656 read command checks device id - #184 2021-11-07 13:50:39 +01:00
proddy
d20741c0f0 add a little more buffer for concurrent web sockets 2021-11-07 13:49:55 +01:00
Proddy
91b7fd59d1 Merge pull request #181 from kpschaper/moduline200
Show mode (off / heat) in Home Assistant for Moduline 200
2021-11-06 20:56:57 +01:00
proddy
95e81ad824 auto-fetch 0x14 which is not broadcasted on all boilers - https://github.com/emsesp/EMS-ESP32/issues/160#issuecomment-962444738 2021-11-06 13:31:32 +01:00
Koen Schaper
316832fc4f Show mode (off / heat) in Home Assistant for Moduline 200 2021-11-06 10:07:04 +01:00
proddy
23b6d81c47 3.3.0b2 2021-11-05 21:48:11 +01:00
proddy
8284520733 increase tcp event queue from 32 to 64 - fixes Launching the WebUI from crashes EMS-ESP on some environments #177 2021-11-05 21:46:15 +01:00
MichaelDvP
57f53818e1 add RC20_N write extmintemp, formatting 2021-11-05 14:14:04 +01:00
proddy
7bca3fb2ed rename "name" to "entity" for API key - Refactor MQTT subscriptions and API calls #173 2021-11-05 13:22:39 +01:00
MichaelDvP
ae4a1358af show rf sensor temperature 2021-11-05 11:57:58 +01:00
MichaelDvP
95e3a11a11 add missing RC25 parameters 2021-11-05 11:53:41 +01:00
MichaelDvP
f1a859c650 show devices with product-id zero, m200 wait improved 2021-11-05 11:53:02 +01:00
MichaelDvP
8d9fd95e85 remove useless extra subscriptions #173 2021-11-04 13:13:41 +01:00
proddy
452921d198 MQTT subscribe to devices - Refactor MQTT subscriptions and API calls #173 2021-11-03 22:12:21 +01:00
proddy
3e0f6f55fb bump to 3.3.0b1 2021-11-03 22:11:44 +01:00
MichaelDvP
0e480bbd94 add known devices without product-id or version-info #174 2021-11-03 21:08:52 +01:00
proddy
7a079d866f put/# back - Refactor MQTT subscriptions and API calls #173 2021-11-03 19:22:42 +01:00
MichaelDvP
af2710125e mqtt subscriptions include device #173 2021-11-03 18:43:46 +01:00
MichaelDvP
fb3de2e36d add RC300 wwdisinfect #175 2021-11-03 12:17:00 +01:00
proddy
bf40222105 validate devices to see if they are present - Refactor MQTT subscriptions #173 2021-11-02 21:00:16 +01:00
proddy
e1419edb15 more fixes - Refactor MQTT subscriptions #173 2021-11-02 18:15:57 +01:00
proddy
131b936a69 siwtch info with list - #176 2021-11-02 16:02:49 +01:00
proddy
5850a82d80 error handling improvements - Refactor MQTT subscriptions #173 2021-11-02 13:59:36 +01:00
proddy
b76b6be3d1 logic cleanup 2021-11-02 10:50:29 +01:00
proddy
b8f69eeaa8 lint warning fix 2021-11-02 10:47:46 +01:00
proddy
d78fb53845 fix for mqtt base paths - Refactor MQTT subscriptions #173 2021-11-02 10:42:34 +01:00
proddy
54889fec41 fix issue where OK was not sent on successfull API call 2021-11-02 10:42:20 +01:00
proddy
2c5c4d6e04 update test for different mqtt base paths 2021-11-02 10:41:56 +01:00
proddy
e6a44c9c82 be able to set mqtt base 2021-11-02 10:41:39 +01:00
proddy
7cba52d77e bump to 3.3.0 2021-11-01 23:32:28 +01:00
proddy
a79a67e4b2 Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev 2021-11-01 23:31:33 +01:00
proddy
01bace4048 Refactor MQTT subscriptions #173 2021-11-01 23:31:30 +01:00
MichaelDvP
d5f8419157 do not fetch CRF200 monitor #138 2021-10-30 20:31:49 +02:00
Proddy
40a7026d4c update example with ESP32 core debug levels 2021-10-26 13:06:29 +02:00
proddy
47cb296cc4 updates to #168 2021-10-25 13:01:56 +02:00
MichaelDvP
be27033d41 typo 2021-10-24 18:32:50 +02:00
MichaelDvP
3001a2d66f fix #169, prod-id:111 settings use telegram 0x179 2021-10-24 18:15:39 +02:00
proddy
becaff0711 updated with #168 2021-10-22 22:17:15 +02:00
proddy
aaab9f409f text change 2021-10-22 22:15:30 +02:00
proddy
068bb5cbeb fix obsolete JS in padding & justification 2021-10-22 22:15:23 +02:00
proddy
337c07d7bc firmware version checker in Web - #168 2021-10-22 22:14:39 +02:00
proddy
c387f65b4a some minor refactor 2021-10-21 22:56:56 +02:00
proddy
df13081f97 update URL 2021-10-21 22:56:45 +02:00
proddy
50f6d0ab26 fixes #166 2021-10-21 22:56:35 +02:00
proddy
fb7bafdb87 fix display of mqtt subscriptions 2021-10-20 09:34:50 +02:00
proddy
b3472c3919 add test for 'send' command 2021-10-20 09:21:12 +02:00
proddy
4f47712d52 minor formatting 2021-10-20 09:21:03 +02:00
MichaelDvP
547ccb96c9 Thermostat 0x65 to RC20_N, #160 2021-10-20 09:00:57 +02:00
proddy
7f3ff434ea add test for Dan "ems-esp/boiler/wwcircpump with payload off" 2021-10-19 18:36:35 +02:00
proddy
aad4b0ade3 change text about MQTT subscribe formats 2021-10-19 18:35:48 +02:00
proddy
d587f44ec9 updated 2021-10-19 18:35:33 +02:00
proddy
f30d3cf637 update test data for standalone 2021-10-19 18:35:24 +02:00
proddy
8fed47f39b fix case fall through for HA state_class 2021-10-19 17:40:46 +02:00
proddy
48cedbd0fb fix standalone building 2021-10-19 17:40:12 +02:00
proddy
752530a381 auto formatting 2021-10-19 17:39:44 +02:00
proddy
45fc6daa4a Add support for mDNS #161 2021-10-18 13:02:22 +02:00
MichaelDvP
e17ce9c3b5 Junkers modetype #163 2021-10-18 12:05:04 +02:00
MichaelDvP
2657b9d1a5 do not fetch broadcasted telegrams 0x14/0x19 (#160) 2021-10-18 11:29:50 +02:00
MichaelDvP
b77a56ade2 timeout 60s for KM200 wait #160, reset-reason to log 2021-10-18 11:28:43 +02:00
MichaelDvP
4d5f588748 reset reason to system info 2021-10-18 11:25:41 +02:00
Proddy
6582ba6317 added another Buderus RC10 - #160 2021-10-17 21:40:26 +02:00
Proddy
b3453d9d02 no need to recompile C++ code if Web code doesn't change 2021-10-16 12:50:56 +02:00
MichaelDvP
e4b73140c8 enlarge filesystem buffer 2021-10-14 18:55:50 +02:00
Proddy
235f789228 increase max dallas sensors from 10 to 20 - #157 2021-10-14 10:09:52 +02:00
Proddy
c029cf79f7 use smaller json key names, increase buffer size. fixes #157 2021-10-14 10:09:28 +02:00
Proddy
0b796a85a8 use DeviceValueUOM::TIMES for all 'starts' which count numerically 2021-10-12 15:00:42 +02:00
Proddy
d8191f79a4 removed # from comments 2021-10-12 15:00:16 +02:00
Proddy
6a259f7cca removed # from comments 2021-10-12 15:00:05 +02:00
Proddy
22a2b92022 added number formatting and pluralization for uom times and seconds 2021-10-12 14:59:53 +02:00
Proddy
25616ae3b4 added time to UOM and replaced seconds with second (pluralization handled in code) 2021-10-12 14:59:27 +02:00
Proddy
372aee30cd removed # in front of telegram count 2021-10-12 14:58:51 +02:00
Proddy
0c5023323a removed # (e.g in front of starts) 2021-10-12 14:58:30 +02:00
Proddy
2d1126b9e4 updated comment 2021-10-12 14:57:42 +02:00
Proddy
3ecf92fa41 add times as a new HA UOM - #156 2021-10-12 12:59:00 +02:00
Proddy
50befd8991 add HA device_class 2021-10-12 12:58:29 +02:00
Proddy
676268d7af rename rxread/txread to rxreads/txreads 2021-10-12 12:58:05 +02:00
Proddy
f2457a7050 auto-formatting 2021-10-11 16:53:00 +02:00
Proddy
ded90dc4ce minor text changes 2021-10-11 16:42:47 +02:00
Proddy
cddadcfae2 changes to allow restart command from API to complete before rebooting 2021-10-11 16:42:36 +02:00
Proddy
3113d392ac minor text changes 2021-10-11 16:41:44 +02:00
Proddy
6433d5f744 formatting 2021-10-11 16:41:24 +02:00
Proddy
f42c265714 minor text changes 2021-10-11 16:41:14 +02:00
Proddy
d6711ac850 always show a maintenancemessage even if there isn't one, so its not missing in the MQTT boiler_data payload 2021-10-10 20:29:03 +02:00
Proddy
9d7c2de1d5 rename HA icon mdi:flash-circle to mdi:lightning-bolt-circle 2021-10-10 20:11:52 +02:00
MichaelDvP
b1e1c44e77 add divider #136 2021-10-10 17:43:47 +02:00
MichaelDvP
85f54dd210 add current room influence #136 2021-10-10 09:18:52 +02:00
MichaelDvP
eea32ad134 add RC30 ww parameters, #117 2021-10-01 09:39:57 +02:00
MichaelDvP
4f24035082 add id to solar ww-circuit 2021-10-01 09:24:38 +02:00
MichaelDvP
0b0b1d9ca4 rename internal names wW/warmwater to ww 2021-09-29 14:42:34 +02:00
MichaelDvP
98828ed848 another typo 2021-09-29 12:55:51 +02:00
MichaelDvP
7b0f7cd32c fix typo wW->ww 2021-09-29 12:43:07 +02:00
Proddy
67cb778039 Merge pull request #135 from Sunbuzz/ft_add_connect
Added CONNECT device 0x236 "Wireless sensor base"
2021-09-29 12:11:25 +02:00
MichaelDvP
9f49afae0a read wwSelTemp/wwDisinfectionTemp from 0xEA, #96 2021-09-29 11:57:24 +02:00
sunbuzz
49c7c7aa2d Added CONNECT device 0x236 "Wireless sensor base" 2021-09-29 11:23:22 +02:00
Proddy
42d89d1d10 Merge pull request #134 from proddy/dev
Dev
2021-09-28 16:18:30 +02:00
proddy
24fae0d03e updated comment 2021-09-28 16:15:53 +02:00
proddy
2c337f1d03 fixed #129 2021-09-28 16:15:47 +02:00
MichaelDvP
fcc521d5ed enum thermostat programs, add junkers remote, program 2021-09-28 14:06:26 +02:00
MichaelDvP
91005876eb use device names from flash 2021-09-28 13:08:56 +02:00
proddy
da5b4aa79d add test for MQTT heatingactivated 2021-09-25 22:04:42 +02:00
proddy
ced440392b fix possible buffer overflow 2021-09-25 21:52:19 +02:00
proddy
33d7ba1fda reduce variable scope 2021-09-25 21:52:03 +02:00
proddy
a6095fc305 init poolShuntStatus__ as its not done in the constructor 2021-09-25 21:51:47 +02:00
proddy
84cc964a7a 3.2.2b8 2021-09-25 18:41:16 +02:00
Proddy
9e856b28a9 Merge pull request #130 from MichaelDvP/dev
use TAG_DEVICE_DATA_WW
2021-09-25 18:13:37 +02:00
MichaelDvP
9378fdf2b6 use TAG_DEVICE_DATA_WW 2021-09-25 10:00:06 +02:00
proddy
6a134dda1f Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev 2021-09-23 10:08:08 +02:00
MichaelDvP
4f927ee571 remove unused type from form 2021-09-23 08:48:59 +02:00
MichaelDvP
b111869422 remove invalid UOMs 2021-09-23 08:30:31 +02:00
proddy
89a249eae4 add breaking changes 2021-09-22 20:18:37 +02:00
MichaelDvP
7942d52843 npm audit fix 2021-09-22 15:13:11 +02:00
MichaelDvP
b6310302d2 add C2 error message, fix a uom 2021-09-22 15:12:58 +02:00
MichaelDvP
87774e73e1 fix some thermstat uoms 2021-09-22 15:12:05 +02:00
MichaelDvP
548b5ff4a1 remove unused check 2021-09-22 15:11:32 +02:00
proddy
80fedf3fa3 fix not showing dv is UOM is NONE 2021-09-22 14:53:50 +02:00
proddy
8523cdffa3 Merge branch 'ft_reacthooks' into dev 2021-09-21 18:11:55 +02:00
proddy
29f2335935 remove DeviceValueUOM::TEXT 2021-09-21 18:06:55 +02:00
proddy
eba6324d18 more API tests 2021-09-21 18:06:36 +02:00
proddy
c3874d7c95 remove DeviceValueUOM::TEXT 2021-09-21 18:06:25 +02:00
proddy
3086342d2b fix bug in nested json and added output targets 2021-09-21 18:05:36 +02:00
proddy
f836209249 added output target when rendering json 2021-09-21 18:04:48 +02:00
proddy
d4b06cf0c0 formatting 2021-09-21 18:04:25 +02:00
proddy
54199affc1 removed DeviceValueUOM::TEXT 2021-09-21 18:04:07 +02:00
proddy
4d88c6a90b add comments about nesting 2021-09-21 18:03:43 +02:00
proddy
906813b8f5 don't show handlers if there arn't any 2021-09-21 18:03:30 +02:00
proddy
30d1e7ecb4 bump version 2021-09-21 18:03:14 +02:00
proddy
8fab4e29bf use float instead of double 2021-09-21 18:03:05 +02:00
proddy
252554ea87 added comment about nesting 2021-09-21 18:02:52 +02:00
proddy
ba3d49172c updated packages 2021-09-21 18:02:40 +02:00
proddy
6ef8ff757a add check to prevent crash on null strings 2021-09-21 18:02:14 +02:00
MichaelDvP
fcc2c0b3de fastheatup as percent-value, enlarge parse-buffer #122 2021-09-21 08:04:11 +02:00
proddy
16e390f849 v3.3 2021-09-20 21:44:23 +02:00
proddy
a31cf53863 replace class components (HOCs) with React Hooks 2021-09-20 21:43:56 +02:00
proddy
09d8a6360b only use UOM of NONE for hidden Device Values 2021-09-20 21:43:05 +02:00
proddy
b1f59d4727 ignore UOM if its TEXT 2021-09-20 21:42:45 +02:00
proddy
d8add7edcb rename DeviceValueType::TEXT to STRING and ignore UOM if its NONE 2021-09-20 21:42:24 +02:00
proddy
1a71921fd6 remove debug 2021-09-20 21:41:48 +02:00
proddy
6d12fff4fe add API test 2021-09-20 21:41:29 +02:00
proddy
769301c804 rename boiltemp to 'actual boiler temperature' https://github.com/emsesp/EMS-ESP32/discussions/115 2021-09-20 13:52:37 +02:00
MichaelDvP
6a828e9ca5 Merge branch 'dev_' into dev 2021-09-20 09:04:57 +02:00
MichaelDvP
2516d2d6de add boiler disinfect command 2021-09-20 08:53:03 +02:00
MichaelDvP
e92a3ad025 add MM10 valvetime 2021-09-20 08:51:16 +02:00
MichaelDvP
5686094151 factor for charge duration 2021-09-20 08:49:57 +02:00
proddy
915749dd69 updated 2021-09-19 21:38:09 +02:00
proddy
258bc2b544 increase json before from 4KB to 16KB (EMSESP_JSON_SIZE_XXLARGE_DYN) 2021-09-19 21:33:49 +02:00
proddy
a9748f5b46 snprintf_P to snprintf() again 2021-09-19 21:33:15 +02:00
proddy
56f1c7946e snprintf_P rename 2021-09-19 21:32:51 +02:00
proddy
88a427578f rename snprintf_P, rename unit to uom 2021-09-19 21:32:35 +02:00
proddy
0ebd9e1fe1 make sure all std::strings are consts 2021-09-19 21:31:23 +02:00
proddy
bea0b4ba01 add entities command, only show master thermostat if >1 thermostats 2021-09-19 21:31:02 +02:00
proddy
f8bee9b5c5 formatting 2021-09-19 21:29:53 +02:00
proddy
500e089b97 for standalone ncrease mem due to compiler 2021-09-19 21:29:42 +02:00
proddy
2adef52abf add "entitied" as new command 2021-09-19 21:29:20 +02:00
proddy
e01bfe1bdf rename duplicate function publish_mqtt_ha_sensor 2021-09-19 21:29:03 +02:00
proddy
418b1b3d68 bump 2021-09-19 21:28:30 +02:00
proddy
401929d992 round2 use float instead of double (6 point precision is enough) 2021-09-19 21:28:21 +02:00
proddy
ee34dd72f7 rename snprintf_P to snprintf 2021-09-19 21:27:39 +02:00
proddy
b214d7a662 extended test for ha and entities command 2021-09-19 21:26:48 +02:00
proddy
079a40cd32 update to 16.18.4 2021-09-19 21:26:15 +02:00
proddy
adebea1561 add pseudo for snprintf_P 2021-09-19 21:25:54 +02:00
proddy
28bf7c3225 formatting 2021-09-19 11:21:01 +02:00
proddy
78b2efd148 add rounding test 2021-09-19 11:20:54 +02:00
MichaelDvP
11590061a3 fix wwcharge-command #112 2021-09-18 13:58:08 +02:00
proddy
09847ee33c fix: only set thermostat commands for master thermostat #110 2021-09-18 09:52:02 +02:00
MichaelDvP
db34785be0 add missing offset to telgram::to_string() 2021-09-16 17:14:22 +02:00
proddy
538a9cf642 mention RC25 support 2021-09-15 20:56:52 +02:00
proddy
d0346e436a remove PSTR() 2021-09-15 18:04:58 +02:00
Proddy
442202d978 Merge pull request #109 from MichaelDvP/dev
add RC300 second summermode telegram
2021-09-15 17:53:04 +02:00
MichaelDvP
3b2a89d9f4 solar turnOnOffDiff allow setting decimals #107 2021-09-15 10:54:57 +02:00
MichaelDvP
0bf366ac75 Divider for solar pumpOnOffDiff #107 2021-09-15 08:27:50 +02:00
proddy
c62e73d21e added HA state_class https://github.com/emsesp/EMS-ESP/issues/776 2021-09-13 20:45:56 +02:00
MichaelDvP
2889899bfd also add summertemp setting 2021-09-12 19:03:59 +02:00
MichaelDvP
f18ac2e48b add RC300 second summermode telegram 2021-09-12 18:48:33 +02:00
MichaelDvP
1d259d14c8 thermostat RC25 additions 2021-09-09 18:44:43 +02:00
MichaelDvP
b8c07e31cf mixer: add id to pool, sort registering, add commands 2021-09-09 11:33:57 +02:00
proddy
3f27ed7b18 auto-formatting 2021-09-08 19:53:07 +02:00
Proddy
d7678b340f Merge pull request #104 from Sunbuzz/pool
Pool
2021-09-08 12:18:21 +02:00
MichaelDvP
b697356465 add RC25, #106 2021-09-07 08:25:20 +02:00
sunbuzz
1b9a2f21d2 Increased EMSESP_JSON_SIZE_XXLARGE_DYN to 16384 (at 10635 right now) + small fixes 2021-09-03 15:01:05 +02:00
sunbuzz
e26451fcc0 reverted to TAG_NONE, added HP activities, added some json space 2021-09-02 15:25:37 +02:00
sunbuzz
17db542775 Fixed support for M P mixer i ha_config 2021-09-02 12:58:23 +02:00
sunbuzz
dd865a2db5 Fixed issues regarding pull request "Pool #104" 2021-09-02 12:40:39 +02:00
sunbuzz
834c7cb87f Added support for MP100 pool mixer (telegram 05BA), added brand IVT, added some parameters to telegram 0x48D & 0x48F 2021-09-01 22:06:50 +02:00
sunbuzz
a9a015cb5b Added support for MP100 pool mixer (telegram 05BA), added brand IVT, added some parameters to telegram 0x48D & 0x48F 2021-09-01 22:05:18 +02:00
MichaelDvP
94a71998a7 add some thermostat values 2021-08-30 14:11:55 +02:00
proddy
aa212924bc 3.2.2b2 2021-08-28 16:41:03 +02:00
Proddy
edef2f1bf6 Merge pull request #102 from Sunbuzz/pool
Added pool data to telegrams 0x494 & 0x495
2021-08-28 16:36:56 +02:00
proddy
912f53762e fix formatting (WARNING didnt fit) 2021-08-28 15:10:29 +02:00
proddy
02c04c8f41 updated images 2021-08-28 15:04:30 +02:00
sunbuzz
3ad90a6e34 Added pool data to telegrams 0x494 & 0x495 2021-08-28 12:03:05 +02:00
proddy
0f83870db0 auto-formatting 2021-08-26 10:42:40 +02:00
proddy
7529f3a73e minor text changes 2021-08-26 10:42:34 +02:00
MichaelDvP
77d559ac8c syslog re-add colon after message.id 2021-08-26 08:02:46 +02:00
MichaelDvP
1311cad913 add some boiler and thermostat parameters 2021-08-23 16:14:06 +02:00
MichaelDvP
4d1ba9bede remove tx-delay, wait for KM200 poll, v3.2.2b1 2021-08-23 13:43:32 +02:00
MichaelDvP
6b07651ed3 remove weblog download filter 2021-08-21 10:58:47 +02:00
MichaelDvP
50ddfc0437 store weblog settings, no log filtering, prevent double messages 2021-08-20 17:42:07 +02:00
MichaelDvP
c0ac485772 add date/time to Easy thermostat 2021-08-20 10:04:43 +02:00
MichaelDvP
8d4ae6971d fix #100 add missing hamode to EasyMonitor 2021-08-20 09:48:01 +02:00
Proddy
88d0dfee2d update changelog 2021-08-19 20:58:05 +02:00
Proddy
551a479c6b fix custom build options 2021-08-19 20:12:43 +02:00
MichaelDvP
3f85541c9a add system commands for syslog level and watch 2021-08-19 16:19:32 +02:00
MichaelDvP
74cdb610d8 fix#99, mqtt reconnect for IPv4 and IPv6 2021-08-19 15:27:41 +02:00
MichaelDvP
165dd3b418 fix #99, mqtt reconnect after wifi drop 2021-08-18 20:47:37 +02:00
MichaelDvP
7421f3e345 fix crash on response for empty telegrams 2021-08-17 16:06:20 +02:00
MichaelDvP
d00559bcd5 Fix compact weblog level-lable for trace 2021-08-16 07:32:35 +02:00
MichaelDvP
9774051fab Syslog BOM only for utf-8 messages, #91
Tested with extra message with udf-characters:
```
Aug 15 09:30:12 ems-esp32 emsesp 000+00:00:00.000 I 0 Starting Syslog
Aug 15 09:30:13 ems-esp32 emsesp 000+00:00:00.000 I 2 EMS Device library loaded with 79 records
Aug 15 09:30:13 ems-esp32 emsesp <BOM>000+00:00:00.000 I 3 Testing syslog with udf-8-chars: €öäü߀öäü߀öäüß
Aug 15 09:30:13 ems-esp32 emsesp 000+00:00:03.741 I 5 Starting NTP
```
2021-08-15 10:24:57 +02:00
proddy
c50692bae0 version 3.2.2b0 2021-08-14 13:45:26 +02:00
proddy
6348283001 fomatting 2021-08-14 13:41:35 +02:00
proddy
1cbd34d94e rename get_toggle_fetch -> is_fetch 2021-08-14 13:41:11 +02:00
proddy
f9d768a7a7 added clang-tidy 2021-08-14 13:40:21 +02:00
proddy
6c1bfccfaf text change 2021-08-10 09:23:04 +02:00
proddy
2ca0a0c634 v3.2.1 merged from dev 2021-08-08 14:46:14 +02:00
proddy
30ce3f1dc3 3.2.1 2021-08-08 14:45:24 +02:00
proddy
15541c52ce ww selected temperature sets the boilers target temp, not the wwSetTemp 2021-08-08 14:29:20 +02:00
proddy
20c3e8c7bd no need to set new temp in condition 2021-08-08 09:15:03 +02:00
proddy
59797fb89c comment cleanup 2021-08-07 21:47:38 +02:00
proddy
849cc85398 added FSTR_ and MAKE_STR for non-flash macros 2021-08-07 21:47:24 +02:00
proddy
dd318a1c8e always show tags, don't treat BOILER as a special case 2021-08-07 21:46:59 +02:00
proddy
2edf2a4231 disable mqtt function 2021-08-07 21:46:38 +02:00
proddy
d58ee1e693 renamed ww names 2021-08-07 21:46:22 +02:00
proddy
088cb7fe40 support for device in json body 2021-08-07 21:46:09 +02:00
proddy
38498a5587 updated API tests 2021-08-07 21:45:52 +02:00
proddy
6e3b30b03c renamed product ID, changed case for all ww names 2021-08-07 21:45:43 +02:00
proddy
4e9cf72816 3.2.1 2021-08-07 21:45:12 +02:00
565 changed files with 42589 additions and 41292 deletions

152
.clang-tidy Normal file
View File

@@ -0,0 +1,152 @@
---
Checks: >-
*,
-abseil-*,
-android-*,
-boost-*,
-bugprone-branch-clone,
-bugprone-narrowing-conversions,
-bugprone-signed-char-misuse,
-bugprone-too-small-loop-variable,
-cert-dcl50-cpp,
-cert-err58-cpp,
-cert-oop57-cpp,
-cert-str34-c,
-clang-analyzer-optin.cplusplus.UninitializedObject,
-clang-analyzer-osx.*,
-clang-diagnostic-delete-abstract-non-virtual-dtor,
-clang-diagnostic-delete-non-abstract-non-virtual-dtor,
-clang-diagnostic-shadow-field,
-clang-diagnostic-sign-compare,
-clang-diagnostic-unused-variable,
-clang-diagnostic-unused-const-variable,
-cppcoreguidelines-avoid-c-arrays,
-cppcoreguidelines-avoid-goto,
-cppcoreguidelines-avoid-magic-numbers,
-cppcoreguidelines-init-variables,
-cppcoreguidelines-macro-usage,
-cppcoreguidelines-narrowing-conversions,
-cppcoreguidelines-non-private-member-variables-in-classes,
-cppcoreguidelines-owning-memory,
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
-cppcoreguidelines-pro-bounds-constant-array-index,
-cppcoreguidelines-pro-bounds-pointer-arithmetic,
-cppcoreguidelines-pro-type-const-cast,
-cppcoreguidelines-pro-type-cstyle-cast,
-cppcoreguidelines-pro-type-member-init,
-cppcoreguidelines-pro-type-reinterpret-cast,
-cppcoreguidelines-pro-type-static-cast-downcast,
-cppcoreguidelines-pro-type-union-access,
-cppcoreguidelines-pro-type-vararg,
-cppcoreguidelines-special-member-functions,
-fuchsia-default-arguments,
-fuchsia-multiple-inheritance,
-fuchsia-overloaded-operator,
-fuchsia-statically-constructed-objects,
-fuchsia-default-arguments-declarations,
-fuchsia-default-arguments-calls,
-google-build-using-namespace,
-google-explicit-constructor,
-google-readability-braces-around-statements,
-google-readability-casting,
-google-readability-todo,
-google-runtime-references,
-hicpp-*,
-llvm-else-after-return,
-llvm-header-guard,
-llvm-include-order,
-llvm-qualified-auto,
-llvmlibc-*,
-misc-non-private-member-variables-in-classes,
-misc-no-recursion,
-misc-unused-parameters,
-modernize-avoid-c-arrays,
-modernize-return-braced-init-list,
-modernize-use-auto,
-modernize-use-default-member-init,
-modernize-use-equals-default,
-modernize-use-trailing-return-type,
-mpi-*,
-objc-*,
-readability-braces-around-statements,
-readability-const-return-type,
-readability-convert-member-functions-to-static,
-readability-else-after-return,
-readability-implicit-bool-conversion,
-readability-isolate-declaration,
-readability-magic-numbers,
-readability-make-member-function-const,
-readability-named-parameter,
-readability-qualified-auto,
-readability-redundant-access-specifiers,
-readability-redundant-member-init,
-readability-redundant-string-init,
-readability-uppercase-literal-suffix,
-readability-use-anyofallof,
-warnings-as-errors
WarningsAsErrors: '*'
AnalyzeTemporaryDtors: false
FormatStyle: google
CheckOptions:
- key: google-readability-braces-around-statements.ShortStatementLines
value: '1'
- key: google-readability-function-size.StatementThreshold
value: '800'
- key: google-readability-namespace-comments.ShortNamespaceLines
value: '10'
- key: google-readability-namespace-comments.SpacesBeforeComments
value: '2'
- key: modernize-loop-convert.MaxCopySize
value: '16'
- key: modernize-loop-convert.MinConfidence
value: reasonable
- key: modernize-loop-convert.NamingStyle
value: CamelCase
- key: modernize-pass-by-value.IncludeStyle
value: llvm
- key: modernize-replace-auto-ptr.IncludeStyle
value: llvm
- key: modernize-use-nullptr.NullMacros
value: 'NULL'
- key: readability-identifier-naming.LocalVariableCase
value: 'lower_case'
- key: readability-identifier-naming.ClassCase
value: 'CamelCase'
- key: readability-identifier-naming.StructCase
value: 'CamelCase'
- key: readability-identifier-naming.EnumCase
value: 'CamelCase'
- key: readability-identifier-naming.EnumConstantCase
value: 'UPPER_CASE'
- key: readability-identifier-naming.StaticConstantCase
value: 'UPPER_CASE'
- key: readability-identifier-naming.StaticVariableCase
value: 'UPPER_CASE'
- key: readability-identifier-naming.GlobalConstantCase
value: 'UPPER_CASE'
- key: readability-identifier-naming.ParameterCase
value: 'lower_case'
- key: readability-identifier-naming.PrivateMemberPrefix
value: 'NO_PRIVATE_MEMBERS_ALWAYS_USE_PROTECTED'
- key: readability-identifier-naming.PrivateMethodPrefix
value: 'NO_PRIVATE_METHODS_ALWAYS_USE_PROTECTED'
- key: readability-identifier-naming.ClassMemberCase
value: 'lower_case'
- key: readability-identifier-naming.ClassMemberCase
value: 'lower_case'
- key: readability-identifier-naming.ProtectedMemberCase
value: 'lower_case'
- key: readability-identifier-naming.ProtectedMemberSuffix
value: '_'
- key: readability-identifier-naming.FunctionCase
value: 'lower_case'
- key: readability-identifier-naming.ClassMethodCase
value: 'lower_case'
- key: readability-identifier-naming.ProtectedMethodCase
value: 'lower_case'
- key: readability-identifier-naming.ProtectedMethodSuffix
value: '_'
- key: readability-identifier-naming.VirtualMethodCase
value: 'lower_case'
- key: readability-identifier-naming.VirtualMethodSuffix
value: ''

View File

@@ -29,7 +29,7 @@ assignees: ''
*If applicable, add screenshots to help explain your problem.*
**Device information**
*Copy-paste here the information as it is outputted by the device. You can get this information by from http://ems-esp.local/api?device=system&cmd=info*
*Copy-paste here the information as it is outputted by the device. You can get this information by from http://ems-esp.local/api/system*
**Additional context**
*Add any other context about the problem here.*

View File

@@ -23,7 +23,7 @@ assignees: ''
*If applicable, add screenshots to help explain your problem.*
**Device information**
*Copy-paste here the information as it is outputted by the device. You can get this information from http://ems-esp.local/api?device=system&cmd=info*
*Copy-paste here the information as it is outputted by the device. You can get this information from http://ems-esp.local/api/system*
**Additional context**
*Add any other context about the problem here.*

View File

@@ -48,7 +48,7 @@ jobs:
uses: "marvinpinto/action-automatic-releases@latest"
with:
repo_token: "${{ secrets.GITHUB_TOKEN }}"
title: ESP32 Development Build v${{steps.build_info.outputs.version}}
title: Development Build v${{steps.build_info.outputs.version}}
automatic_release_tag: "latest"
prerelease: true
files: |

57
.github/workflows/sonar_check.yml vendored Normal file
View File

@@ -0,0 +1,57 @@
name: Sonar Check
on:
push:
branches:
- dev
pull_request:
types: [opened, synchronize, reopened]
jobs:
build:
name: Build
runs-on: ubuntu-latest
if: github.repository_owner == 'emsesp'
# if: github.repository == 'emsesp/EMS-ESP32'
env:
# https://binaries.sonarsource.com/?prefix=Distribution/sonar-scanner-cli/
SONAR_SCANNER_VERSION: 4.7.0.2747
SONAR_SERVER_URL: "https://sonarcloud.io"
BUILD_WRAPPER_OUT_DIR: build_wrapper_output_directory
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
- name: Set up JDK 11
uses: actions/setup-java@v1
with:
java-version: 11
- name: Cache SonarCloud packages
uses: actions/cache@v1
with:
path: ~/.sonar/cache
key: ${{ runner.os }}-sonar
restore-keys: ${{ runner.os }}-sonar
- name: Download and set up sonar-scanner
env:
SONAR_SCANNER_DOWNLOAD_URL: https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-${{ env.SONAR_SCANNER_VERSION }}-linux.zip
run: |
mkdir -p $HOME/.sonar
curl -sSLo $HOME/.sonar/sonar-scanner.zip ${{ env.SONAR_SCANNER_DOWNLOAD_URL }}
unzip -o $HOME/.sonar/sonar-scanner.zip -d $HOME/.sonar/
echo "$HOME/.sonar/sonar-scanner-${{ env.SONAR_SCANNER_VERSION }}-linux/bin" >> $GITHUB_PATH
- name: Download and set up build-wrapper
env:
BUILD_WRAPPER_DOWNLOAD_URL: ${{ env.SONAR_SERVER_URL }}/static/cpp/build-wrapper-linux-x86.zip
run: |
curl -sSLo $HOME/.sonar/build-wrapper-linux-x86.zip ${{ env.BUILD_WRAPPER_DOWNLOAD_URL }}
unzip -o $HOME/.sonar/build-wrapper-linux-x86.zip -d $HOME/.sonar/
echo "$HOME/.sonar/build-wrapper-linux-x86" >> $GITHUB_PATH
- name: Run build-wrapper
run: |
make clean
build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} make clean all
- name: Run sonar-scanner
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: |
sonar-scanner

7
.gitignore vendored
View File

@@ -28,3 +28,10 @@ emsesp
node_modules
/interface/.eslintcache
test.sh
scripts/__pycache__
.temp
# sonar
.scannerwork/
sonar/
build_wrapper_output_directory/

View File

@@ -5,6 +5,199 @@ 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).
# Changelog
## [3.4.1] May 29 2022
### Fixed
- Fix memory leak in api [#524](https://github.com/emsesp/EMS-ESP32/issues/524)
### Changed
- Controller data in web-ui only for IVT [#522](https://github.com/emsesp/EMS-ESP32/issues/522)
- Rename hidden `climate` to a more explaining name [#523](https://github.com/emsesp/EMS-ESP32/issues/523)
- Minor changes to the Customizations web page [#527](https://github.com/emsesp/EMS-ESP32/pull/527)
# [3.4.0] May 23 2022
## Added
- WebUI optimizations, updated look&feel and better performance [#124](https://github.com/emsesp/EMS-ESP32/issues/124)
- Auto refresh of WebUI after successful firmware upload [#178](https://github.com/emsesp/EMS-ESP32/issues/178)
- New Customization Service in WebUI. First feature is the ability to enable/disabled Enitites (device values) from EMS devices [#206](https://github.com/emsesp/EMS-ESP32/issues/206)
- Option to disable Telnet Console [#209](https://github.com/emsesp/EMS-ESP32/issues/209)
- Added Hide SSID, Max Clients and Preferred Channel to Access Point
- Merged in MichaelDvP's changes like Fahrenheit conversion, publish single (for IOBroker) and a few other critical optimizations
- Enabled bi-directional read/write with Home Assistant, so values can be changed automatically from the UI without scripting [#265](https://github.com/emsesp/EMS-ESP32/issues/265)
- Added GC7000F Boiler [#270](https://github.com/emsesp/EMS-ESP32/issues/270)
- Revised LED flash sequence on boot up to show system health (1 flash=no ems, 2 flashes=no wifi) [#224](https://github.com/emsesp/EMS-ESP32/issues/224)
- Analog Sensor support [#271](https://github.com/emsesp/EMS-ESP32/issues/271)
- Solar cylinder priority [#247](https://github.com/emsesp/EMS-ESP32/issues/247)
- Read only mode in Settings, where EMS Tx/Write commands are blocked [#286](https://github.com/emsesp/EMS-ESP32/issues/286)
- Added 8700i Boiler device
- Added Cascade CM10 Controller device
- Add Olimex ESP32-POE-ISO to board profiles plus settings to customize Ethernet modules [#301](https://github.com/emsesp/EMS-ESP32/issues/301)
- Help text for string commands in WebUI [#320](https://github.com/emsesp/EMS-ESP32/issues/320)
- Germany translations (at compile time)
- #entities added to system/info` endpoint [#322](https://github.com/emsesp/EMS-ESP32/issues/322)
- analog outputs digital/pwm/dac
- remove MQTT retained configs if discovery is disabled
- timeout 10 min for MQTT-QoS wait
- Moduline 300 auto-temperatures T1-T4, RC300 romminfluencefactor
- RC35 parameters [#392](https://github.com/emsesp/EMS-ESP32/issues/392), [#398](https://github.com/emsesp/EMS-ESP32/issues/398)
- sync time with thermostat [#386](https://github.com/emsesp/EMS-ESP32/issues/386), [#408](https://github.com/emsesp/EMS-ESP32/issues/408)
- set mode has immediate effect [#395](https://github.com/emsesp/EMS-ESP32/issues/395)
- min/max in web value setting
- Extend customization to select if an entity is to be shown in the WebUI or forced as read-only [#317](https://github.com/emsesp/EMS-ESP32/issues/317)
- Added Moduline 400 installation parameters [PR #449 by @kwertie01](https://github.com/emsesp/EMS-ESP32/pull/449)
- Read time from IVT-controller [#439](https://github.com/emsesp/EMS-ESP32/issues/439)
- Hybrid Heatpump product-id 168 [#459](https://github.com/emsesp/EMS-ESP32/issues/459), thermostat settings
- Junkers ISM2 and IPM in warm water mode [#437](https://github.com/emsesp/EMS-ESP32/issues/437)
- Added Shower Alert trigger time and cold shot time [#436](https://github.com/emsesp/EMS-ESP32/issues/436)
- Improved Table layout in Web UI (searching, filtering, sorting, exporting to CSV)
- API fetch individual attributes from an entity [#462](https://github.com/emsesp/EMS-ESP32/issues/462)
- Option to disable mDNS
- Option for rendering booleans on dashboard [#456](https://github.com/emsesp/EMS-ESP32/issues/456)
- Upload customization settings from a file [#256](https://github.com/emsesp/EMS-ESP32/issues/256)
## Fixed
- lastcode broke MQTT JSON structure [#228](https://github.com/emsesp/EMS-ESP32/issues/228)
- fixed issue with overlapping while reading sequence of EMS1.0 telegrams
- fixed redundant telegram readings (because of offset overflow)
- added missing RC30/Moduline 400 [#243](https://github.com/emsesp/EMS-ESP32/issues/243)
- Correct modes for RC25 [#106](https://github.com/emsesp/EMS-ESP32/issues/106)
- Clean up old HA config's in MQTT before publishing data. This will prevent HA giving the 'dict' warnings [#229](https://github.com/emsesp/EMS-ESP32/issues/229)
- RC25 temperature setting [#272](https://github.com/emsesp/EMS-ESP32/issues/272)
- Buderus RC25 - "hc1 mode type" incorrect value [#273](https://github.com/emsesp/EMS-ESP32/issues/273)
- Increased number of Mixers and Heating Circuits [#294](https://github.com/emsesp/EMS-ESP32/issues/294)
- Check receive status before removing a telegram fetch [#268](https://github.com/emsesp/EMS-ESP32/issues/268), [#282](https://github.com/emsesp/EMS-ESP32/issues/282)
- Fix uploading firmware on OSX [#345](https://github.com/emsesp/EMS-ESP32/issues/345)
- Non-nested MQTT would corrupt the json [#354](https://github.com/emsesp/EMS-ESP32/issues/354)
- Burner selected max power can have a value higher than 100% [#314](https://github.com/emsesp/EMS-ESP32/issues/314)
- some missing fahrenheit calculations
- limited number of exclusions [#339](https://github.com/emsesp/EMS-ESP32/issues/339)
- MQTT sometimes would not reconnect after a WiFi outage
## Changed
- Use flash system to show system health (1 flash=no ems, 2 flashes=no wifi) [#224](https://github.com/emsesp/EMS-ESP32/issues/224)
- Renamed Dallas Sensor to Temperature Sensor in UI
- Dallas Format removed. Use the name to give each sensor an alias
- No longer MQTT subscribes to topic `/thermostat_hc<n>` as it supports a path similar to the API endpoint construct
- Show Sensors quality in WebUI
- Controller not shown in WebUI dashboard
- renamed "Home Assistant Integration" to "MQTT Discovery" in MQTT Settings [#290](https://github.com/emsesp/EMS-ESP32/issues/290)
- Show ems tx reads and writes separately
- Show ems device handlers separated for received, fetched and pending handlers.
- Wired renamed to Ethernet
- removed system/pin command, new commands in analogsensors
- system/info device-info split to name/version/brand
- exclude list uses short-names, possible flags for web/api/mqtt excludes, readonly and favorite (selection not yet implemented)
- thermostat clock formate date-time: dd.mm.yyyy hh:mm
- RC300 summermode as other thermostats `winter/summer` instead of `off/on`
## **BREAKING CHANGES:**
- Settings:
- order of Boolean Format has changed in Application Settings - check your settings
- Dallas Format setting removed. Now customize name of each Dallas sensor via the UI
- MQTT/API
- Boiler `wwheat` renamed to `ww3wayon` [#211](https://github.com/emsesp/EMS-ESP32/issues/211)
- Boiler `ww` tag renamed to `dhw`. Any custom Home Assistant lovelace dashboards will need updating.
- Renamed description of `wwtapactivated` to "turn on/off DHW". Otherwise would have looked like "boiler_dhw_turn_on_off_dhw" in HA.
- `/api/system/info` endpoint has updated keys. Now lowercase, no underscores and not capitalized. Replace "handlers" with "handlers received", "handlers fetched" and "handlers pending".
# [3.3.1] January 20 2022
- lastcode broke MQTT JSON structure [#228](https://github.com/emsesp/EMS-ESP32/issues/228)
- overlapping while reading sequence of EMS1.0 telegrams
- redundant telegram readings (because of offset overflow)
- added missing RC30/Moduline400 [#243](https://github.com/emsesp/EMS-ESP32/issues/243)
- check received status before toggling fetch on empty telegram [#268][#282]
# [3.3.0] November 28 2021
## Added
- Add system commands for syslog level and watch [#98](https://github.com/emsesp/EMS-ESP32/issues/98)
- Added pool data to telegrams 0x494 & 0x495 [#102](https://github.com/emsesp/EMS-ESP32/issues/102)
- Add RC300 second summermode telegram [#108](https://github.com/emsesp/EMS-ESP32/issues/108)
- Add support for the RC25 thermostat [#106](https://github.com/emsesp/EMS-ESP32/issues/106)
- Add new command 'entities' for a device, e.g. http://ems-esp/api/boiler/entities to show the shortname, description and HA Entity name (if HA enabled) [#116](https://github.com/emsesp/EMS-ESP32/issues/116)
- Support for Junkers program and remote (fb10/fb110) temperature
- Home Assistant `state_class` attribute for Wh, kWh, W and KW [#129](https://github.com/emsesp/EMS-ESP32/issues/129)
- Add current room influence for RC300 [#136](https://github.com/emsesp/EMS-ESP32/issues/136)
- Added Home Assistant device_class to sensor entities
- Added another Buderus RC10 thermostat with Product ID 65 [#160](https://github.com/emsesp/EMS-ESP32/issues/160)
- Added support for mDNS [#161](https://github.com/emsesp/EMS-ESP32/issues/161)
- Added last system ESP32 reset code to log (and `system info` output)
- Firmware Checker in WebUI [#168](https://github.com/emsesp/EMS-ESP32/issues/168)
- Added new MQTT setting for enabling 'response' topic
- Support for non-standard Thermostats like Tado [#174](https://github.com/emsesp/EMS-ESP32/issues/174)
- Include MQTT connection status in 'api/system/info'
- Include Network status in 'api/system/info' and also the MQTT topic `info` [#202](https://github.com/emsesp/EMS-ESP32/issues/202)
- Added Ethernet PHY module as an option in the Board Profile [#210](https://github.com/emsesp/EMS-ESP32/issues/210)
## Fixed
- MQTT reconnecting after WiFi reconnect [#99](https://github.com/emsesp/EMS-ESP32/issues/99)
- Manually Controlling Solar Circuit [#107](https://github.com/emsesp/EMS-ESP32/issues/107)
- Fix thermostat commands not defaulting to the master thermostat [#110](https://github.com/emsesp/EMS-ESP32/issues/110)
- Enlarge parse-buffer for long names like `cylinderpumpmodulation`
- MQTT not subscribing to all device entities [#166](https://github.com/emsesp/EMS-ESP32/issues/166)
- Help fix issues with WebUI unable to fully load UI over Ethernet [#177](https://github.com/emsesp/EMS-ESP32/issues/177)
- Shower alert never reset after limit reached when enabled [(PR #185)]
- Remove HA entity entries when a device value goes dormant [#196](https://github.com/emsesp/EMS-ESP32/issues/196)
- deciphering last error code dates on 0xC2 telegram [#204](https://github.com/emsesp/EMS-ESP32/issues/204)
## Changed
- Syslog BOM only for utf-8 messages [#91](https://github.com/emsesp/EMS-ESP32/issues/91)
- Check for KM200 by device-id 0x48, remove tx-delay [#90](https://github.com/emsesp/EMS-ESP32/issues/90)
- rename `fastheatupfactor` to `fastheatup` and add percent [#122](https://github.com/emsesp/EMS-ESP32/issues/122)
- "unit" renamed to "uom" in API call to recall a Device Value
- initial backend React changes to replace the class components (HOCs) with React Hooks
- Use program-names instead of numbers
- Boiler's maintenancemessage always published in MQTT (to prevent HA missing entity)
- Unit of Measure 'times' added to MQTT Fails, Rx fails, Rx received, Tx fails, Tx reads & Tx writes
- Improved API. Restful HTTP API works in the same way as MQTT calls
- Removed settings for MQTT subscribe format [#173](https://github.com/emsesp/EMS-ESP32/issues/173)
- Improve Nefit Moduline 200 functionality [#183](https://github.com/emsesp/EMS-ESP32/issues/183)
- `status` in the MQTT heartbeat renamed to `bus_status`
- Layout changes in the WebUI, showing stripped table rows in Dashboard
- Alternative font for log window [#219](https://github.com/emsesp/EMS-ESP32/issues/219)
## **BREAKING CHANGES**
- API: "unit" renamed to "uom" in API call to recall a Device Value
- HA: `sensor.boiler_boiler_temperature` renamed to `sensor.actual_boiler_temperature`
- HA: `binary_sensor.boiler_ww_disinfecting` renamed to `binary_sensor.boiler_ww_disinfection`
- HA: # removed from counts in MQTT Fails, Rx fails, Rx received, Tx fails, Tx reads & Tx writes
- `txread` renamed to `txreads` and `txwrite` renamed to `txwrites` in MQTT heartbeat payload
- 'dallas sensors' in api/system/info moved to the "System" section. Renamed "uptime (seconds)" and "reset reason"
- `status` in the MQTT heartbeat renamed to `bus_status`
# [3.2.1] August 8 2021
## Added
- json body in API can now take device, name, cmd, hc and id
- added example of how to use API directly to control values from Home Assistant
- API calls are shown in debug log (For troubleshooting)
## Fixed
- fixed issue with Home Assistant entity naming where boiler's ww was duplicated in entity name
- fixed issue where wwSetTemp was written too instead of wwSelTemp
## Changed
- fixed case on mqtt names, like 'wwtankmiddletemp'
- renamed Product ID to 'EMS Product ID' in Home Assistant
- removed brackets around tags, e.g. (hc1) selected room temperature" is now just "hc1 selected room temperature"
# [3.2.0] August 6 2021
## Added
@@ -43,7 +236,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
# [3.1.1] June 26 2021
## Changed
## Added
- new command called `commands` which lists all available commands. `ems-esp/api/{device}/commands`
- More Home Assistant icons to match the UOMs
@@ -65,7 +258,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
# [3.1.0] May 4 2021
## Changed
## Added
- Mock API to simulate an ESP, for testing web
- Able to write values from the Web UI

View File

@@ -1,7 +0,0 @@
# Changelog
## Added
## Fixed
## Changed

View File

@@ -1,5 +1,3 @@
<img src="/media/EMS-ESP_logo_dark.png" alt="Logo" align="right" height="76"/>
# Contributing
**Any contribution helps EMS-ESP get better for the entire community!**
@@ -32,8 +30,8 @@ This document describes rules that are in effect for this repository, meant for
6. Issues with feature requests should be discussed for viability/desirability.
7. Feature requests or changes that are meant to address a very specific/limited use case, especially if at the expense of increased code complexity, may be denied, or may be required to be redesigned, generalized, or simplified.
8. Feature requests that are not accompanied by a PR:
- could be closed immediately (denied).
- could be closed after some predetermined period of time (left as candidate for somebody to pick up).
- could be closed immediately (denied).
- could be closed after some predetermined period of time (left as candidate for somebody to pick up).
9. In some cases, feedback may be requested from the issue reporter, either as additional info for clarification, additional testing, or other. If no feedback is provided, the issue may be closed by a contributor or after 40 days by the STALE bot.
## Pull requests
@@ -94,7 +92,7 @@ References:
- <https://www.conventionalcommits.org/>
--------------------------------------
---
## Contributor License Agreement (CLA)
@@ -123,7 +121,7 @@ By making a contribution to this project, I certify that:
This Contributor License Agreement (CLA) was adopted on April 1st, 2019.
The text of this license is available under the [Creative Commons Attribution-ShareAlike 3.0 Unported License](http://creativecommons.org/licenses/by-sa/3.0/). It is based on the Linux [Developer Certificate Of Origin](http://elinux.org/Developer_Certificate_Of_Origin), but is modified to explicitly use the GPL-3.0 license and not mention sign-off (due to GitHub.com keeps an historial, with your user name, of PRs' commits and all editions on PR's comments).
The text of this license is available under the [Creative Commons Attribution-ShareAlike 3.0 Unported License](http://creativecommons.org/licenses/by-sa/3.0/). It is based on the Linux [Developer Certificate Of Origin](http://elinux.org/Developer_Certificate_Of_Origin), but is modified to explicitly use the GPL-3.0 license and not mention sign-off (due to GitHub.com keeps an historial, with your user name, of PRs' commits and all editions on PR's comments).
**Why a CLA ?**
@@ -133,9 +131,9 @@ A CLA is a legal document in which you state _you are entitled to contribute the
CLA is a safety because it also ensures that once you have provided a contribution, you cannot try to withdraw permission for its use at a later date. People can therefore use that software, confident that they will not be asked to stop using pieces of the code at a later date.
A __license__ grants "outbound" rights to the user of project.
A **license** grants "outbound" rights to the user of project.
A __CLA__ enables a contributor to grant "inbound" rights to a project.
A **CLA** enables a contributor to grant "inbound" rights to a project.
<Other>
<A table should be maintained for relating maintainers and components. When triaging, this is essential to figure out if someone in particular should be consulted about specific changes.>

View File

@@ -18,7 +18,7 @@ MAKEFLAGS+="j "
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
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
LIBRARIES :=
CPPCHECK = cppcheck
@@ -33,7 +33,7 @@ CXX_STANDARD := -std=c++11
#----------------------------------------------------------------------
# Defined Symbols
#----------------------------------------------------------------------
DEFINES += -DARDUINOJSON_ENABLE_STD_STRING=1 -DARDUINOJSON_ENABLE_ARDUINO_STRING -DEMSESP_DEBUG -DEMSESP_STANDALONE -DEMSESP_DEFAULT_BOARD_PROFILE=\"LOLIN\"
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\"
#----------------------------------------------------------------------
# Sources & Files
@@ -73,8 +73,8 @@ CPPFLAGS += -Os
CFLAGS += $(CPPFLAGS)
CFLAGS += -Wall
CFLAGS += -Wno-unused -Wno-restrict
CFLAGS += -Wextra
CFLAGS += -Wno-unused-parameter
CXXFLAGS += $(CFLAGS) -MMD
@@ -113,6 +113,7 @@ COMPILE.cpp = $(CXX) $(CXX_STANDARD) $(CXXFLAGS) $(DEPFLAGS) -c $< -o $@
#----------------------------------------------------------------------
# Targets
#----------------------------------------------------------------------
.PHONY: all
all: $(OUTPUT)
$(OUTPUT): $(OBJS)
@@ -138,6 +139,7 @@ cppcheck: $(SOURCES)
run: $(OUTPUT)
@$<
.PHONY: clean
clean:
@$(RM) -r $(BUILD) $(OUTPUT)

View File

@@ -15,6 +15,7 @@ This project is the specifically for the ESP32. Compared with the previous ESP82
[![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)
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=emsesp_EMS-ESP32&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=emsesp_EMS-ESP32)
[![Codacy Badge](https://app.codacy.com/project/badge/Grade/9441142f49424ef891e8f5251866ee6b)](https://www.codacy.com/gh/emsesp/EMS-ESP32/dashboard?utm_source=github.com&utm_medium=referral&utm_content=emsesp/EMS-ESP32&utm_campaign=Badge_Grade)
[![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)
@@ -33,16 +34,16 @@ Note, EMS-ESP requires a small hardware circuit that can convert the EMS bus dat
# **Features**
- A multi-user secure web interface to change settings and monitor the data
- A console, accessible via Serial and Telnet for more monitoring
- Native support for Home Assistant via [MQTT Discovery](https://www.home-assistant.io/docs/mqtt/discovery/)
- A multi-user 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/)
- 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 [80 EMS devices](https://emsesp.github.io/docs/#/Supported-EMS-Devices) (boilers, thermostats, solar modules, mixer modules, heat pumps, gateways)
- Support for more than [100 EMS devices](https://emsesp.github.io/docs/#/Supported-EMS-Devices) (boilers, thermostats, solar modules, mixer modules, heat pumps, gateways)
## **Demo**
See a live demo [here](https://ems-esp.derbyshire.nl) using fake data. Log in with any username/password.
See a demo [here](https://ems-esp.derbyshire.nl). Log in with any username/password.
# **Screenshots**

View File

@@ -38,7 +38,7 @@ build_flags =
-D FACTORY_MQTT_PASSWORD=\"\"
-D FACTORY_MQTT_CLIENT_ID=\"ems-esp\"
-D FACTORY_MQTT_KEEP_ALIVE=60
-D FACTORY_MQTT_CLEAN_SESSION=true
-D FACTORY_MQTT_CLEAN_SESSION=false
-D FACTORY_MQTT_MAX_TOPIC_LENGTH=128
; JWT Secret

View File

@@ -1,3 +1,6 @@
# This enables lint extensions
EXTEND_ESLINT=true
# This is the name of your project. It appears on the sign-in page and in the menu bar.
REACT_APP_PROJECT_NAME=EMS-ESP

View File

@@ -1,5 +0,0 @@
# Change the IP address to that of your ESP device to enable local development of the UI
# REACT_APP_HTTP_ROOT=http://localhost:3000
# REACT_APP_WEB_SOCKET_ROOT=ws://localhost:3000

View File

@@ -1,2 +0,0 @@
# don't ever lint node_modules
node_modules

View File

@@ -1,27 +0,0 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"plugins": [
"@typescript-eslint",
"prettier"
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"prettier"
],
"rules": {
// 0 = ignore, 1 = warning, 2 = error
"no-console": 0,
"prettier/prettier": ["error", { endOfLine: "auto" }],
"explicit-function-return-type": 0,
"@typescript-eslint/explicit-module-boundary-types": 0,
"@typescript-eslint/no-var-requires": 0,
"@typescript-eslint/ban-types": 0,
"@typescript-eslint/no-non-null-asserted-optional-chain": 0,
"@typescript-eslint/no-non-null-assertion": 0,
"@typescript-eslint/no-explicit-any": 0
}
}

View File

@@ -2,5 +2,5 @@
"singleQuote": true,
"semi": true,
"trailingComma": "none",
"printWidth": 80
"printWidth": 120
}

View File

@@ -1,52 +1,30 @@
const ManifestPlugin = require('webpack-manifest-plugin');
const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
const { WebpackManifestPlugin } = require('webpack-manifest-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CompressionPlugin = require('compression-webpack-plugin');
const ProgmemGenerator = require('./progmem-generator.js');
const TerserPlugin = require('terser-webpack-plugin');
module.exports = function override(config, env) {
const hosted = process.env.REACT_APP_HOSTED;
if (env === 'production' && !hosted) {
console.log('Custom webpack...');
// rename the output file, we need it's path to be short for LittleFS
// rename the ouput file, we need it's path to be short, for embedded FS
config.output.filename = 'js/[id].[chunkhash:4].js';
config.output.chunkFilename = 'js/[id].[chunkhash:4].js';
// take out the manifest and service worker plugins
config.plugins = config.plugins.filter(
(plugin) => !(plugin instanceof ManifestPlugin)
);
config.plugins = config.plugins.filter(
(plugin) => !(plugin instanceof WorkboxWebpackPlugin.GenerateSW)
);
// take out the manifest plugin
config.plugins = config.plugins.filter((plugin) => !(plugin instanceof WebpackManifestPlugin));
// shorten css filenames
const miniCssExtractPlugin = config.plugins.find(
(plugin) => plugin instanceof MiniCssExtractPlugin
);
const miniCssExtractPlugin = config.plugins.find((plugin) => plugin instanceof MiniCssExtractPlugin);
miniCssExtractPlugin.options.filename = 'css/[id].[contenthash:4].css';
miniCssExtractPlugin.options.chunkFilename =
'css/[id].[contenthash:4].c.css';
miniCssExtractPlugin.options.chunkFilename = 'css/[id].[contenthash:4].c.css';
// don't emit license file
const terserPlugin = config.optimization.minimizer.find((plugin) => plugin instanceof TerserPlugin);
terserPlugin.options.extractComments = false;
// build progmem data files
config.plugins.push(
new ProgmemGenerator({
outputPath: '../lib/framework/WWWData.h',
bytesPerLine: 20
})
);
// add compression plugin, compress javascript
config.plugins.push(
new CompressionPlugin({
filename: '[path].gz[query]',
algorithm: 'gzip',
test: /\.(js)$/,
deleteOriginalAssets: true
})
);
config.plugins.push(new ProgmemGenerator({ outputPath: '../lib/framework/WWWData.h', bytesPerLine: 20 }));
}
return config;
};

38042
interface/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,41 +1,42 @@
{
"name": "emsesp-react",
"version": "0.1.0",
"name": "EMS-ESP",
"version": "3.4.0",
"private": true,
"proxy": "http://localhost:3080",
"dependencies": {
"@material-ui/core": "^4.11.4",
"@material-ui/icons": "^4.11.2",
"@msgpack/msgpack": "^2.7.0",
"@types/lodash": "^4.14.168",
"@types/node": "^15.0.1",
"@types/react": "^17.0.4",
"@types/react-dom": "^17.0.3",
"@types/react-material-ui-form-validator": "^2.1.0",
"@types/react-router": "^5.1.13",
"@types/react-router-dom": "^5.1.7",
"compression-webpack-plugin": "^5.0.2",
"env-cmd": "^10.1.0",
"express": "^4.17.1",
"@emotion/react": "^11.9.0",
"@emotion/styled": "^11.8.1",
"@msgpack/msgpack": "^2.7.2",
"@mui/icons-material": "^5.8.0",
"@mui/material": "^5.8.1",
"@table-library/react-table-library": "^3.1.4",
"@types/lodash": "^4.14.182",
"@types/node": "^17.0.36",
"@types/react": "^18.0.9",
"@types/react-dom": "^18.0.5",
"@types/react-router-dom": "^5.3.3",
"async-validator": "^4.1.1",
"axios": "^0.27.2",
"http-proxy-middleware": "^2.0.6",
"jwt-decode": "^3.1.2",
"lodash": "^4.17.21",
"mime-types": "^2.1.30",
"notistack": "^1.0.6",
"notistack": "^2.0.5",
"parse-ms": "^3.0.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-dropzone": "^11.3.2",
"react-form-validator-core": "^1.1.1",
"react-material-ui-form-validator": "^2.1.4",
"react-router": "^5.2.0",
"react-router-dom": "^5.2.0",
"react-scripts": "4.0.3",
"react": "^18.1.0",
"react-app-rewired": "^2.2.1",
"react-dom": "^18.1.0",
"react-dropzone": "^14.2.1",
"react-icons": "^4.3.1",
"react-router-dom": "^6.3.0",
"react-scripts": "5.0.1",
"sockette": "^2.0.6",
"typescript": "4.2.4",
"zlib": "^1.0.5"
"typescript": "^4.7.2"
},
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-scripts eject",
"format": "prettier --write '**/*.{ts,tsx,js,css,json,md}'",
"build-hosted": "env-cmd -f .env.hosted npm run build",
"build-localhost": "PUBLIC_URL=/ react-app-rewired build",
@@ -44,7 +45,44 @@
"lint": "eslint . --ext .ts,.tsx"
},
"eslintConfig": {
"extends": "react-app"
"extends": [
"react-app",
"react-app/jest"
],
"rules": {
"eol-last": 1,
"react/jsx-closing-bracket-location": 1,
"react/jsx-closing-tag-location": 1,
"react/jsx-wrap-multilines": 1,
"react/jsx-curly-newline": 1,
"no-multiple-empty-lines": [
1,
{
"max": 1
}
],
"no-trailing-spaces": 1,
"semi": 1,
"no-extra-semi": 1,
"react/jsx-max-props-per-line": [
1,
{
"when": "multiline"
}
],
"react/jsx-first-prop-new-line": [
1,
"multiline"
],
"@typescript-eslint/no-shadow": 1,
"max-len": [
1,
{
"code": 200
}
],
"arrow-parens": 1
}
},
"browserslist": {
"production": [
@@ -59,13 +97,7 @@
]
},
"devDependencies": {
"concurrently": "^6.0.1",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^3.4.0",
"http-proxy-middleware": "^1.1.1",
"nodemon": "^2.0.7",
"npm-run-all": "^4.1.5",
"prettier": "^2.0.5",
"react-app-rewired": "^2.1.8"
"nodemon": "^2.0.16",
"npm-run-all": "^4.1.5"
}
}

View File

@@ -1,11 +1,5 @@
const { resolve, relative, sep } = require('path');
const {
readdirSync,
existsSync,
unlinkSync,
readFileSync,
createWriteStream
} = require('fs');
const { readdirSync, existsSync, unlinkSync, readFileSync, createWriteStream } = require('fs');
var zlib = require('zlib');
var mime = require('mime-types');
@@ -36,112 +30,91 @@ function cleanAndOpen(path) {
class ProgmemGenerator {
constructor(options = {}) {
const {
outputPath,
bytesPerLine = 20,
indent = ' ',
includes = ARDUINO_INCLUDES
} = options;
const { outputPath, bytesPerLine = 20, indent = ' ', includes = ARDUINO_INCLUDES } = options;
this.options = { outputPath, bytesPerLine, indent, includes };
}
apply(compiler) {
compiler.hooks.emit.tapAsync(
{ name: 'ProgmemGenerator' },
(compilation, callback) => {
const { outputPath, bytesPerLine, indent, includes } = this.options;
const fileInfo = [];
const writeStream = cleanAndOpen(
resolve(compilation.options.context, outputPath)
);
try {
const writeIncludes = () => {
writeStream.write(includes);
};
compiler.hooks.emit.tapAsync({ name: 'ProgmemGenerator' }, (compilation, callback) => {
const { outputPath, bytesPerLine, indent, includes } = this.options;
const fileInfo = [];
const writeStream = cleanAndOpen(resolve(compilation.options.context, outputPath));
try {
const writeIncludes = () => {
writeStream.write(includes);
};
const writeFile = (relativeFilePath, buffer) => {
const variable = 'ESP_REACT_DATA_' + fileInfo.length;
const mimeType = mime.lookup(relativeFilePath);
var size = 0;
writeStream.write('const uint8_t ' + variable + '[] PROGMEM = {');
const zipBuffer = zlib.gzipSync(buffer);
zipBuffer.forEach((b) => {
if (!(size % bytesPerLine)) {
writeStream.write('\n');
writeStream.write(indent);
}
writeStream.write(
'0x' + ('00' + b.toString(16).toUpperCase()).substr(-2) + ','
);
size++;
});
if (size % bytesPerLine) {
const writeFile = (relativeFilePath, buffer) => {
const variable = 'ESP_REACT_DATA_' + fileInfo.length;
const mimeType = mime.lookup(relativeFilePath);
var size = 0;
writeStream.write('const uint8_t ' + variable + '[] PROGMEM = {');
const zipBuffer = zlib.gzipSync(buffer);
zipBuffer.forEach((b) => {
if (!(size % bytesPerLine)) {
writeStream.write('\n');
writeStream.write(indent);
}
writeStream.write('};\n\n');
fileInfo.push({
uri: '/' + relativeFilePath.replace(sep, '/'),
mimeType,
variable,
size
});
};
writeStream.write('0x' + ('00' + b.toString(16).toUpperCase()).substr(-2) + ',');
size++;
});
if (size % bytesPerLine) {
writeStream.write('\n');
}
writeStream.write('};\n\n');
fileInfo.push({
uri: '/' + relativeFilePath.replace(sep, '/'),
mimeType,
variable,
size
});
};
const writeFiles = () => {
// process static files
const buildPath = compilation.options.output.path;
for (const filePath of getFilesSync(buildPath)) {
const readStream = readFileSync(filePath);
const relativeFilePath = relative(buildPath, filePath);
writeFile(relativeFilePath, readStream);
}
// process assets
const { assets } = compilation;
Object.keys(assets).forEach((relativeFilePath) => {
writeFile(
relativeFilePath,
coherseToBuffer(assets[relativeFilePath].source())
);
});
};
const writeFiles = () => {
// process static files
const buildPath = compilation.options.output.path;
for (const filePath of getFilesSync(buildPath)) {
const readStream = readFileSync(filePath);
const relativeFilePath = relative(buildPath, filePath);
writeFile(relativeFilePath, readStream);
}
// process assets
const { assets } = compilation;
Object.keys(assets).forEach((relativeFilePath) => {
writeFile(relativeFilePath, coherseToBuffer(assets[relativeFilePath].source()));
});
};
const generateWWWClass = () => {
return `typedef std::function<void(const String& uri, const String& contentType, const uint8_t * content, size_t len)> RouteRegistrationHandler;
const generateWWWClass = () => {
// eslint-disable-next-line max-len
return `typedef std::function<void(const String& uri, const String& contentType, const uint8_t * content, size_t len)> RouteRegistrationHandler;
class WWWData {
${indent}public:
${indent.repeat(
2
)}static void registerRoutes(RouteRegistrationHandler handler) {
${indent.repeat(2)}static void registerRoutes(RouteRegistrationHandler handler) {
${fileInfo
.map(
(file) =>
`${indent.repeat(3)}handler("${file.uri}", "${file.mimeType}", ${
file.variable
}, ${file.size});`
)
.map((file) => `${indent.repeat(3)}handler("${file.uri}", "${file.mimeType}", ${file.variable}, ${file.size});`)
.join('\n')}
${indent.repeat(2)}}
};
`;
};
};
const writeWWWClass = () => {
writeStream.write(generateWWWClass());
};
const writeWWWClass = () => {
writeStream.write(generateWWWClass());
};
writeIncludes();
writeFiles();
writeWWWClass();
writeIncludes();
writeFiles();
writeWWWClass();
writeStream.on('finish', () => {
callback();
});
} finally {
writeStream.end();
}
writeStream.on('finish', () => {
callback();
});
} finally {
writeStream.end();
}
);
});
}
}

View File

@@ -1,28 +1,24 @@
/* Just supporting latin due to size constrains on the esp chip */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 300;
src: local('Roboto Light'), local('Roboto-Light'),
url(../fonts/li.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+2212, U+2215;
}
/*
* Just supporting latin due to size constrains on the esp chip
*
* The framework only makes use of 400 (regular) + 500 (medium) weight fonts.
*
* If using light or strong typography variants you will need to add additional fonts.
*/
@font-face {
font-family: 'Roboto';
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+2212, U+2215;
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;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
src: local('Roboto Medium'), local('Roboto-Medium'),
url(../fonts/me.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+2212, U+2215;
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;
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,16 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="%PUBLIC_URL%/css/roboto.css">
<link rel="manifest" href="%PUBLIC_URL%/app/manifest.json">
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, user-scalable=0, maximum-scale=1, minimum-scale=1"
/>
<link rel="stylesheet" href="%PUBLIC_URL%/css/roboto.css" />
<link rel="manifest" href="%PUBLIC_URL%/app/manifest.json" />
<title>EMS-ESP</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<noscript> You need to enable JavaScript to run this app. </noscript>
<div id="root"></div>
</body>
</html>

View File

@@ -1,57 +1,45 @@
import React, { Component, RefObject } from 'react';
import { Redirect, Route, Switch } from 'react-router';
import { FC, createRef, createContext, useContext, RefObject } from 'react';
import { SnackbarProvider } from 'notistack';
import { IconButton } from '@material-ui/core';
import CloseIcon from '@material-ui/icons/Close';
import { IconButton } from '@mui/material';
import CloseIcon from '@mui/icons-material/Close';
import { FeaturesLoader } from './contexts/features';
import CustomTheme from './CustomTheme';
import AppRouting from './AppRouting';
import CustomMuiTheme from './CustomMuiTheme';
import { PROJECT_NAME } from './api';
import FeaturesWrapper from './features/FeaturesWrapper';
// this redirect forces a call to authenticationContext.refresh() which invalidates the JWT if it is invalid.
const unauthorizedRedirect = () => <Redirect to="/" />;
const App: FC = () => {
const notistackRef: RefObject<any> = createRef();
class App extends Component {
notistackRef: RefObject<any> = React.createRef();
componentDidMount() {
document.title = PROJECT_NAME;
}
onClickDismiss = (key: string | number | undefined) => () => {
this.notistackRef.current.closeSnackbar(key);
const onClickDismiss = (key: string | number | undefined) => () => {
notistackRef.current.closeSnackbar(key);
};
render() {
return (
<CustomMuiTheme>
const ColorModeContext = createContext({ toggleColorMode: () => {} });
const colorMode = useContext(ColorModeContext);
return (
<ColorModeContext.Provider value={colorMode}>
<CustomTheme>
<SnackbarProvider
autoHideDuration={3000}
maxSnack={3}
anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
ref={this.notistackRef}
ref={notistackRef}
action={(key) => (
<IconButton onClick={this.onClickDismiss(key)} size="small">
<IconButton onClick={onClickDismiss(key)} size="small">
<CloseIcon />
</IconButton>
)}
>
<FeaturesWrapper>
<Switch>
<Route
exact
path="/unauthorized"
component={unauthorizedRedirect}
/>
<Route component={AppRouting} />
</Switch>
</FeaturesWrapper>
<FeaturesLoader>
<AppRouting />
</FeaturesLoader>
</SnackbarProvider>
</CustomMuiTheme>
);
}
}
</CustomTheme>
</ColorModeContext.Provider>
);
};
export default App;

View File

@@ -1,67 +1,74 @@
import React, { Component } from 'react';
import { Switch, Redirect } from 'react-router';
import { FC, useContext, useEffect } from 'react';
import { Navigate, Routes, Route, useLocation } from 'react-router-dom';
import { useSnackbar, VariantType } from 'notistack';
import * as Authentication from './authentication/Authentication';
import AuthenticationWrapper from './authentication/AuthenticationWrapper';
import UnauthenticatedRoute from './authentication/UnauthenticatedRoute';
import AuthenticatedRoute from './authentication/AuthenticatedRoute';
import { Authentication, AuthenticationContext } from './contexts/authentication';
import { FeaturesContext } from './contexts/features';
import { RequireAuthenticated, RequireUnauthenticated } from './components';
import SignIn from './SignIn';
import ProjectRouting from './project/ProjectRouting';
import NetworkConnection from './network/NetworkConnection';
import AccessPoint from './ap/AccessPoint';
import NetworkTime from './ntp/NetworkTime';
import Security from './security/Security';
import System from './system/System';
import AuthenticatedRouting from './AuthenticatedRouting';
import { PROJECT_PATH } from './api';
import Mqtt from './mqtt/Mqtt';
import { withFeatures, WithFeaturesProps } from './features/FeaturesContext';
import { Features } from './features/types';
export const getDefaultRoute = (features: Features) =>
features.project ? `/${PROJECT_PATH}/` : '/network/';
class AppRouting extends Component<WithFeaturesProps> {
componentDidMount() {
Authentication.clearLoginRedirect();
}
render() {
const { features } = this.props;
return (
<AuthenticationWrapper>
<Switch>
{features.security && (
<UnauthenticatedRoute exact path="/" component={SignIn} />
)}
{features.project && (
<AuthenticatedRoute
exact
path={`/${PROJECT_PATH}/*`}
component={ProjectRouting}
/>
)}
<AuthenticatedRoute
exact
path="/network/*"
component={NetworkConnection}
/>
<AuthenticatedRoute exact path="/ap/*" component={AccessPoint} />
{features.ntp && (
<AuthenticatedRoute exact path="/ntp/*" component={NetworkTime} />
)}
{features.mqtt && (
<AuthenticatedRoute exact path="/mqtt/*" component={Mqtt} />
)}
{features.security && (
<AuthenticatedRoute exact path="/security/*" component={Security} />
)}
<AuthenticatedRoute exact path="/system/*" component={System} />
<Redirect to={getDefaultRoute(features)} />
</Switch>
</AuthenticationWrapper>
);
}
interface SecurityRedirectProps {
message: string;
variant?: VariantType;
signOut?: boolean;
}
export default withFeatures(AppRouting);
const RootRedirect: FC<SecurityRedirectProps> = ({ message, variant, signOut }) => {
const authenticationContext = useContext(AuthenticationContext);
const { enqueueSnackbar } = useSnackbar();
useEffect(() => {
signOut && authenticationContext.signOut(false);
enqueueSnackbar(message, { variant });
}, [message, variant, signOut, authenticationContext, enqueueSnackbar]);
return <Navigate to="/" />;
};
export const RemoveTrailingSlashes = () => {
const location = useLocation();
return (
location.pathname.match('/.*/$') && (
<Navigate
to={{
pathname: location.pathname.replace(/\/+$/, ''),
search: location.search
}}
/>
)
);
};
const AppRouting: FC = () => {
const { features } = useContext(FeaturesContext);
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" />} />
{features.security && (
<Route
path="/"
element={
<RequireUnauthenticated>
<SignIn />
</RequireUnauthenticated>
}
/>
)}
<Route
path="/*"
element={
<RequireAuthenticated>
<AuthenticatedRouting />
</RequireAuthenticated>
}
/>
</Routes>
</Authentication>
);
};
export default AppRouting;

View File

@@ -0,0 +1,66 @@
import { FC, useCallback, useContext, useEffect } from 'react';
import { Navigate, Routes, Route, useNavigate, useLocation } from 'react-router-dom';
import { AxiosError } from 'axios';
import { FeaturesContext } from './contexts/features';
import * as AuthenticationApi from './api/authentication';
import { PROJECT_PATH } from './api/env';
import { AXIOS } from './api/endpoints';
import { Layout, RequireAdmin } from './components';
import ProjectRouting from './project/ProjectRouting';
import NetworkConnection from './framework/network/NetworkConnection';
import AccessPoint from './framework/ap/AccessPoint';
import NetworkTime from './framework/ntp/NetworkTime';
import Mqtt from './framework/mqtt/Mqtt';
import System from './framework/system/System';
import Security from './framework/security/Security';
const AuthenticatedRouting: FC = () => {
const { features } = useContext(FeaturesContext);
const location = useLocation();
const navigate = useNavigate();
const handleApiResponseError = useCallback(
(error: AxiosError) => {
if (error.response && error.response.status === 401) {
AuthenticationApi.storeLoginRedirect(location);
navigate('/unauthorized');
}
return Promise.reject(error);
},
[location, navigate]
);
useEffect(() => {
const axiosHandlerId = AXIOS.interceptors.response.use((response) => response, handleApiResponseError);
return () => AXIOS.interceptors.response.eject(axiosHandlerId);
}, [handleApiResponseError]);
return (
<Layout>
<Routes>
{features.project && <Route path={`/${PROJECT_PATH}/*`} element={<ProjectRouting />} />}
<Route path="/network/*" element={<NetworkConnection />} />
<Route path="/ap/*" element={<AccessPoint />} />
{features.ntp && <Route path="/ntp/*" element={<NetworkTime />} />}
{features.mqtt && <Route path="/mqtt/*" element={<Mqtt />} />}
{features.security && (
<Route
path="/security/*"
element={
<RequireAdmin>
<Security />
</RequireAdmin>
}
/>
)}
<Route path="/system/*" element={<System />} />
<Route path="/*" element={<Navigate to={AuthenticationApi.getDefaultRoute(features)} />} />
</Routes>
</Layout>
);
};
export default AuthenticatedRouting;

View File

@@ -1,46 +0,0 @@
import { Component } from 'react';
import { CssBaseline } from '@material-ui/core';
import {
MuiThemeProvider,
createMuiTheme,
StylesProvider
} from '@material-ui/core/styles';
import { blueGrey, orange, red, green } from '@material-ui/core/colors';
const theme = createMuiTheme({
palette: {
type: 'dark',
primary: {
main: '#33bfff'
},
secondary: {
main: '#3d5afe'
},
info: {
main: blueGrey[500]
},
warning: {
main: orange[500]
},
error: {
main: red[500]
},
success: {
main: green[500]
}
}
});
export default class CustomMuiTheme extends Component {
render() {
return (
<StylesProvider>
<MuiThemeProvider theme={theme}>
<CssBaseline />
{this.props.children}
</MuiThemeProvider>
</StylesProvider>
);
}
}

View File

@@ -0,0 +1,33 @@
import { FC } from 'react';
import { CssBaseline } from '@mui/material';
import { createTheme, responsiveFontSizes, ThemeProvider } from '@mui/material/styles';
import { blueGrey, blue } from '@mui/material/colors';
import { RequiredChildrenProps } from './utils';
const theme = responsiveFontSizes(
createTheme({
typography: {
fontSize: 13
},
palette: {
mode: 'dark',
secondary: {
main: blue[500]
},
info: {
main: blueGrey[500]
}
}
})
);
const CustomTheme: FC<RequiredChildrenProps> = ({ children }) => (
<ThemeProvider theme={theme}>
<CssBaseline />
{children}
</ThemeProvider>
);
export default CustomTheme;

View File

@@ -1,165 +1,117 @@
import React, { Component } from 'react';
import { withSnackbar, WithSnackbarProps } from 'notistack';
import { TextValidator, ValidatorForm } from 'react-material-ui-form-validator';
import { FC, useContext, useState } from 'react';
import { ValidateFieldsError } from 'async-validator';
import { useSnackbar } from 'notistack';
import {
withStyles,
createStyles,
Theme,
WithStyles
} from '@material-ui/core/styles';
import { Paper, Typography, Fab } from '@material-ui/core';
import ForwardIcon from '@material-ui/icons/Forward';
import { Box, Fab, Paper, Typography } from '@mui/material';
import ForwardIcon from '@mui/icons-material/Forward';
import {
withAuthenticationContext,
AuthenticationContextProps
} from './authentication/AuthenticationContext';
import { PasswordValidator } from './components';
import { PROJECT_NAME, SIGN_IN_ENDPOINT } from './api';
import * as AuthenticationApi from './api/authentication';
import { PROJECT_NAME } from './api/env';
import { AuthenticationContext } from './contexts/authentication';
const styles = (theme: Theme) =>
createStyles({
signInPage: {
display: 'flex',
height: '100vh',
margin: 'auto',
padding: theme.spacing(2),
justifyContent: 'center',
flexDirection: 'column',
maxWidth: theme.breakpoints.values.sm
},
signInPanel: {
textAlign: 'center',
padding: theme.spacing(2),
paddingTop: '200px',
backgroundImage: 'url("/app/icon.png")',
backgroundRepeat: 'no-repeat',
backgroundPosition: '50% ' + theme.spacing(2) + 'px',
backgroundSize: 'auto 150px',
width: '100%'
},
extendedIcon: {
marginRight: theme.spacing(0.5)
},
button: {
marginRight: theme.spacing(2),
marginTop: theme.spacing(2)
}
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';
const SignIn: FC = () => {
const authenticationContext = useContext(AuthenticationContext);
const { enqueueSnackbar } = useSnackbar();
const [signInRequest, setSignInRequest] = useState<SignInRequest>({
username: '',
password: ''
});
const [processing, setProcessing] = useState<boolean>(false);
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
type SignInProps = WithSnackbarProps &
WithStyles<typeof styles> &
AuthenticationContextProps;
const updateLoginRequestValue = updateValue(setSignInRequest);
interface SignInState {
username: string;
password: string;
processing: boolean;
}
class SignIn extends Component<SignInProps, SignInState> {
constructor(props: SignInProps) {
super(props);
this.state = {
username: '',
password: '',
processing: false
};
}
updateInputElement = (event: React.ChangeEvent<HTMLInputElement>): void => {
const { name, value } = event.currentTarget;
this.setState((prevState) => ({
...prevState,
[name]: value
}));
const validateAndSignIn = async () => {
setProcessing(true);
try {
await validate(SIGN_IN_REQUEST_VALIDATOR, signInRequest);
signIn();
} catch (errors: any) {
setFieldErrors(errors);
setProcessing(false);
}
};
onSubmit = () => {
const { username, password } = this.state;
const { authenticationContext } = this.props;
this.setState({ processing: true });
fetch(SIGN_IN_ENDPOINT, {
method: 'POST',
body: JSON.stringify({ username, password }),
headers: new Headers({
'Content-Type': 'application/json'
})
})
.then((response) => {
if (response.status === 200) {
return response.json();
} else if (response.status === 401) {
throw Error('Invalid credentials.');
} else {
throw Error('Invalid status code: ' + response.status);
const signIn = async () => {
try {
const { data: loginResponse } = await AuthenticationApi.signIn(signInRequest);
authenticationContext.signIn(loginResponse.access_token);
} catch (error: unknown) {
if (error instanceof AxiosError) {
if (error.response?.status === 401) {
enqueueSnackbar('Invalid login details', { variant: 'warning' });
}
})
.then((json) => {
authenticationContext.signIn(json.access_token);
})
.catch((error) => {
this.props.enqueueSnackbar(error.message, {
variant: 'warning'
});
this.setState({ processing: false });
});
} else {
enqueueSnackbar(extractErrorMessage(error, 'Unexpected error, please try again'), { variant: 'error' });
}
setProcessing(false);
}
};
render() {
const { username, password, processing } = this.state;
const { classes } = this.props;
return (
<div className={classes.signInPage}>
<Paper className={classes.signInPanel}>
<Typography variant="h4">{PROJECT_NAME}</Typography>
<ValidatorForm onSubmit={this.onSubmit}>
<TextValidator
disabled={processing}
validators={['required']}
errorMessages={['Username is required']}
name="username"
label="Username"
fullWidth
variant="outlined"
value={username}
onChange={this.updateInputElement}
margin="normal"
inputProps={{
autoCapitalize: 'none',
autoCorrect: 'off'
}}
/>
<PasswordValidator
disabled={processing}
validators={['required']}
errorMessages={['Password is required']}
name="password"
label="Password"
fullWidth
variant="outlined"
value={password}
onChange={this.updateInputElement}
margin="normal"
/>
<Fab
variant="extended"
color="primary"
className={classes.button}
type="submit"
disabled={processing}
>
<ForwardIcon className={classes.extendedIcon} />
Sign In
</Fab>
</ValidatorForm>
</Paper>
</div>
);
}
}
const submitOnEnter = onEnterCallback(signIn);
export default withAuthenticationContext(
withSnackbar(withStyles(styles)(SignIn))
);
return (
<Box
display="flex"
height="100vh"
margin="auto"
padding={2}
justifyContent="center"
flexDirection="column"
maxWidth={(theme) => theme.breakpoints.values.sm}
>
<Paper
sx={(theme) => ({
textAlign: 'center',
padding: theme.spacing(2),
paddingTop: '200px',
backgroundImage: 'url("/app/icon.png")',
backgroundRepeat: 'no-repeat',
backgroundPosition: '50% ' + theme.spacing(2),
backgroundSize: 'auto 150px',
width: '100%'
})}
>
<Typography variant="h4">{PROJECT_NAME}</Typography>
<ValidatedTextField
fieldErrors={fieldErrors}
disabled={processing}
name="username"
label="Username"
value={signInRequest.username}
onChange={updateLoginRequestValue}
margin="normal"
variant="outlined"
fullWidth
/>
<ValidatedTextField
fieldErrors={fieldErrors}
disabled={processing}
type="password"
name="password"
label="Password"
value={signInRequest.password}
onChange={updateLoginRequestValue}
onKeyDown={submitOnEnter}
margin="normal"
variant="outlined"
fullWidth
/>
<Fab variant="extended" color="primary" sx={{ mt: 2 }} onClick={validateAndSignIn} disabled={processing}>
<ForwardIcon sx={{ mr: 1 }} />
Sign In
</Fab>
</Paper>
</Box>
);
};
export default SignIn;

View File

@@ -1,8 +0,0 @@
import { APSettings, APProvisionMode } from './types';
export const isAPEnabled = ({ provision_mode }: APSettings) => {
return (
provision_mode === APProvisionMode.AP_MODE_ALWAYS ||
provision_mode === APProvisionMode.AP_MODE_DISCONNECTED
);
};

View File

@@ -1,33 +0,0 @@
import { Component } from 'react';
import { AP_SETTINGS_ENDPOINT } from '../api';
import {
restController,
RestControllerProps,
RestFormLoader,
SectionContent
} from '../components';
import APSettingsForm from './APSettingsForm';
import { APSettings } from './types';
type APSettingsControllerProps = RestControllerProps<APSettings>;
class APSettingsController extends Component<APSettingsControllerProps> {
componentDidMount() {
this.props.loadData();
}
render() {
return (
<SectionContent title="Access Point Settings" titleGutter>
<RestFormLoader
{...this.props}
render={(formProps) => <APSettingsForm {...formProps} />}
/>
</SectionContent>
);
}
}
export default restController(AP_SETTINGS_ENDPOINT, APSettingsController);

View File

@@ -1,134 +0,0 @@
import React, { Fragment } from 'react';
import {
TextValidator,
ValidatorForm,
SelectValidator
} from 'react-material-ui-form-validator';
import MenuItem from '@material-ui/core/MenuItem';
import SaveIcon from '@material-ui/icons/Save';
import {
PasswordValidator,
RestFormProps,
FormActions,
FormButton
} from '../components';
import { isAPEnabled } from './APModes';
import { APSettings, APProvisionMode } from './types';
import { isIP } from '../validators';
type APSettingsFormProps = RestFormProps<APSettings>;
class APSettingsForm extends React.Component<APSettingsFormProps> {
componentDidMount() {
ValidatorForm.addValidationRule('isIP', isIP);
}
render() {
const { data, handleValueChange, saveData } = this.props;
return (
<ValidatorForm onSubmit={saveData} ref="APSettingsForm">
<SelectValidator
name="provision_mode"
label="Provide Access Point&hellip;"
value={data.provision_mode}
fullWidth
variant="outlined"
onChange={handleValueChange('provision_mode')}
margin="normal"
>
<MenuItem value={APProvisionMode.AP_MODE_ALWAYS}>Always</MenuItem>
<MenuItem value={APProvisionMode.AP_MODE_DISCONNECTED}>
When Network Disconnected
</MenuItem>
<MenuItem value={APProvisionMode.AP_NEVER}>Never</MenuItem>
</SelectValidator>
{isAPEnabled(data) && (
<Fragment>
<TextValidator
validators={['required', 'matchRegexp:^.{1,32}$']}
errorMessages={[
'Access Point SSID is required',
'Access Point SSID must be 32 characters or less'
]}
name="ssid"
label="Access Point SSID"
fullWidth
variant="outlined"
value={data.ssid}
onChange={handleValueChange('ssid')}
margin="normal"
/>
<PasswordValidator
validators={['required', 'matchRegexp:^.{8,64}$']}
errorMessages={[
'Access Point Password is required',
'Access Point Password must be 8-64 characters'
]}
name="password"
label="Access Point Password"
fullWidth
variant="outlined"
value={data.password}
onChange={handleValueChange('password')}
margin="normal"
/>
<TextValidator
validators={['required', 'isIP']}
errorMessages={['Local IP is required', 'Must be an IP address']}
name="local_ip"
label="Local IP"
fullWidth
variant="outlined"
value={data.local_ip}
onChange={handleValueChange('local_ip')}
margin="normal"
/>
<TextValidator
validators={['required', 'isIP']}
errorMessages={[
'Gateway IP is required',
'Must be an IP address'
]}
name="gateway_ip"
label="Gateway"
fullWidth
variant="outlined"
value={data.gateway_ip}
onChange={handleValueChange('gateway_ip')}
margin="normal"
/>
<TextValidator
validators={['required', 'isIP']}
errorMessages={[
'Subnet mask is required',
'Must be an IP address'
]}
name="subnet_mask"
label="Subnet"
fullWidth
variant="outlined"
value={data.subnet_mask}
onChange={handleValueChange('subnet_mask')}
margin="normal"
/>
</Fragment>
)}
<FormActions>
<FormButton
startIcon={<SaveIcon />}
variant="contained"
color="primary"
type="submit"
>
Save
</FormButton>
</FormActions>
</ValidatorForm>
);
}
}
export default APSettingsForm;

View File

@@ -1,28 +0,0 @@
import { Theme } from '@material-ui/core';
import { APStatus, APNetworkStatus } from './types';
export const apStatusHighlight = ({ status }: APStatus, theme: Theme) => {
switch (status) {
case APNetworkStatus.ACTIVE:
return theme.palette.success.main;
case APNetworkStatus.INACTIVE:
return theme.palette.info.main;
case APNetworkStatus.LINGERING:
return theme.palette.warning.main;
default:
return theme.palette.warning.main;
}
};
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';
}
};

View File

@@ -1,33 +0,0 @@
import { Component } from 'react';
import {
restController,
RestControllerProps,
RestFormLoader,
SectionContent
} from '../components';
import { AP_STATUS_ENDPOINT } from '../api';
import APStatusForm from './APStatusForm';
import { APStatus } from './types';
type APStatusControllerProps = RestControllerProps<APStatus>;
class APStatusController extends Component<APStatusControllerProps> {
componentDidMount() {
this.props.loadData();
}
render() {
return (
<SectionContent title="Access Point Status">
<RestFormLoader
{...this.props}
render={(formProps) => <APStatusForm {...formProps} />}
/>
</SectionContent>
);
}
}
export default restController(AP_STATUS_ENDPOINT, APStatusController);

View File

@@ -1,91 +0,0 @@
import React, { Component, Fragment } from 'react';
import { WithTheme, withTheme } from '@material-ui/core/styles';
import {
Avatar,
Divider,
List,
ListItem,
ListItemAvatar,
ListItemText
} from '@material-ui/core';
import SettingsInputAntennaIcon from '@material-ui/icons/SettingsInputAntenna';
import DeviceHubIcon from '@material-ui/icons/DeviceHub';
import ComputerIcon from '@material-ui/icons/Computer';
import RefreshIcon from '@material-ui/icons/Refresh';
import {
RestFormProps,
FormActions,
FormButton,
HighlightAvatar
} from '../components';
import { apStatusHighlight, apStatus } from './APStatus';
import { APStatus } from './types';
type APStatusFormProps = RestFormProps<APStatus> & WithTheme;
class APStatusForm extends Component<APStatusFormProps> {
createListItems() {
const { data, theme } = this.props;
return (
<Fragment>
<ListItem>
<ListItemAvatar>
<HighlightAvatar color={apStatusHighlight(data, theme)}>
<SettingsInputAntennaIcon />
</HighlightAvatar>
</ListItemAvatar>
<ListItemText primary="Status" secondary={apStatus(data)} />
</ListItem>
<Divider variant="inset" component="li" />
<ListItem>
<ListItemAvatar>
<Avatar>IP</Avatar>
</ListItemAvatar>
<ListItemText primary="IP Address" secondary={data.ip_address} />
</ListItem>
<Divider variant="inset" component="li" />
<ListItem>
<ListItemAvatar>
<Avatar>
<DeviceHubIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary="MAC Address" secondary={data.mac_address} />
</ListItem>
<Divider variant="inset" component="li" />
<ListItem>
<ListItemAvatar>
<Avatar>
<ComputerIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary="AP Clients" secondary={data.station_num} />
</ListItem>
<Divider variant="inset" component="li" />
</Fragment>
);
}
render() {
return (
<Fragment>
<List>{this.createListItems()}</List>
<FormActions>
<FormButton
startIcon={<RefreshIcon />}
variant="contained"
color="secondary"
onClick={this.props.loadData}
>
Refresh
</FormButton>
</FormActions>
</Fragment>
);
}
}
export default withTheme(APStatusForm);

View File

@@ -1,57 +0,0 @@
import { Component } from 'react';
import { Redirect, Switch, RouteComponentProps } from 'react-router-dom';
import { Tabs, Tab } from '@material-ui/core';
import {
AuthenticatedContextProps,
withAuthenticatedContext,
AuthenticatedRoute
} from '../authentication';
import { MenuAppBar } from '../components';
import APSettingsController from './APSettingsController';
import APStatusController from './APStatusController';
type AccessPointProps = AuthenticatedContextProps & RouteComponentProps;
class AccessPoint extends Component<AccessPointProps> {
handleTabChange = (path: string) => {
this.props.history.push(path);
};
render() {
const { authenticatedContext } = this.props;
return (
<MenuAppBar sectionTitle="Access Point">
<Tabs
value={this.props.match.url}
onChange={(e, path) => this.handleTabChange(path)}
variant="fullWidth"
>
<Tab value="/ap/status" label="Access Point Status" />
<Tab
value="/ap/settings"
label="Access Point Settings"
disabled={!authenticatedContext.me.admin}
/>
</Tabs>
<Switch>
<AuthenticatedRoute
exact
path="/ap/status"
component={APStatusController}
/>
<AuthenticatedRoute
exact
path="/ap/settings"
component={APSettingsController}
/>
<Redirect to="/ap/status" />
</Switch>
</MenuAppBar>
);
}
}
export default withAuthenticatedContext(AccessPoint);

View File

@@ -1,24 +0,0 @@
import { ENDPOINT_ROOT } from './Env';
export const FEATURES_ENDPOINT = ENDPOINT_ROOT + 'features';
export const NTP_STATUS_ENDPOINT = ENDPOINT_ROOT + 'ntpStatus';
export const NTP_SETTINGS_ENDPOINT = ENDPOINT_ROOT + 'ntpSettings';
export const TIME_ENDPOINT = ENDPOINT_ROOT + 'time';
export const AP_SETTINGS_ENDPOINT = ENDPOINT_ROOT + 'apSettings';
export const AP_STATUS_ENDPOINT = ENDPOINT_ROOT + 'apStatus';
export const SCAN_NETWORKS_ENDPOINT = ENDPOINT_ROOT + 'scanNetworks';
export const LIST_NETWORKS_ENDPOINT = ENDPOINT_ROOT + 'listNetworks';
export const NETWORK_SETTINGS_ENDPOINT = ENDPOINT_ROOT + 'networkSettings';
export const NETWORK_STATUS_ENDPOINT = ENDPOINT_ROOT + 'networkStatus';
export const OTA_SETTINGS_ENDPOINT = ENDPOINT_ROOT + 'otaSettings';
export const UPLOAD_FIRMWARE_ENDPOINT = ENDPOINT_ROOT + 'uploadFirmware';
export const MQTT_SETTINGS_ENDPOINT = ENDPOINT_ROOT + 'mqttSettings';
export const MQTT_STATUS_ENDPOINT = ENDPOINT_ROOT + 'mqttStatus';
export const SYSTEM_STATUS_ENDPOINT = ENDPOINT_ROOT + 'systemStatus';
export const SIGN_IN_ENDPOINT = ENDPOINT_ROOT + 'signIn';
export const VERIFY_AUTHORIZATION_ENDPOINT =
ENDPOINT_ROOT + 'verifyAuthorization';
export const SECURITY_SETTINGS_ENDPOINT = ENDPOINT_ROOT + 'securitySettings';
export const GENERATE_TOKEN_ENDPOINT = ENDPOINT_ROOT + 'generateToken';
export const RESTART_ENDPOINT = ENDPOINT_ROOT + 'restart';
export const FACTORY_RESET_ENDPOINT = ENDPOINT_ROOT + 'factoryReset';

View File

@@ -1,26 +0,0 @@
export const PROJECT_NAME = process.env.REACT_APP_PROJECT_NAME!;
export const PROJECT_PATH = process.env.REACT_APP_PROJECT_PATH!;
export const ENDPOINT_ROOT = calculateEndpointRoot('/rest/');
export const WEB_SOCKET_ROOT = calculateWebSocketRoot('/ws/');
export const EVENT_SOURCE_ROOT = calculateEndpointRoot('/es/');
export const API_ENDPOINT_ROOT = calculateEndpointRoot('/api/');
function calculateEndpointRoot(endpointPath: string) {
const httpRoot = process.env.REACT_APP_HTTP_ROOT;
if (httpRoot) {
return httpRoot + endpointPath;
}
const location = window.location;
return location.protocol + '//' + location.host + endpointPath;
}
function calculateWebSocketRoot(webSocketPath: string) {
const webSocketRoot = process.env.REACT_APP_WEB_SOCKET_ROOT;
if (webSocketRoot) {
return webSocketRoot + webSocketPath;
}
const location = window.location;
const webProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
return webProtocol + '//' + location.host + webSocketPath;
}

16
interface/src/api/ap.ts Normal file
View File

@@ -0,0 +1,16 @@
import { AxiosPromise } from 'axios';
import { APSettings, APStatus } from '../types';
import { AXIOS } from './endpoints';
export function readAPStatus(): AxiosPromise<APStatus> {
return AXIOS.get('/apStatus');
}
export function readAPSettings(): AxiosPromise<APSettings> {
return AXIOS.get('/apSettings');
}
export function updateAPSettings(apSettings: APSettings): AxiosPromise<APSettings> {
return AXIOS.post('/apSettings', apSettings);
}

View File

@@ -0,0 +1,64 @@
import { AxiosPromise } from 'axios';
import * as H from 'history';
import jwtDecode from 'jwt-decode';
import { Path } from 'react-router-dom';
import { Features, Me, SignInRequest, SignInResponse } from '../types';
import { ACCESS_TOKEN, AXIOS } from './endpoints';
import { PROJECT_PATH } from './env';
export const SIGN_IN_PATHNAME = 'loginPathname';
export const SIGN_IN_SEARCH = 'loginSearch';
export const getDefaultRoute = (features: Features) => (features.project ? `/${PROJECT_PATH}` : '/wifi');
export function verifyAuthorization(): AxiosPromise<void> {
return AXIOS.get('/verifyAuthorization');
}
export function signIn(request: SignInRequest): AxiosPromise<SignInResponse> {
return AXIOS.post('/signIn', request);
}
/**
* Fallback to sessionStorage if localStorage is absent. WebView may not have local storage enabled.
*/
export function getStorage() {
return localStorage || sessionStorage;
}
export function storeLoginRedirect(location?: H.Location) {
if (location) {
getStorage().setItem(SIGN_IN_PATHNAME, location.pathname);
getStorage().setItem(SIGN_IN_SEARCH, location.search);
}
}
export function clearLoginRedirect() {
getStorage().removeItem(SIGN_IN_PATHNAME);
getStorage().removeItem(SIGN_IN_SEARCH);
}
export function fetchLoginRedirect(features: Features): Partial<Path> {
const signInPathname = getStorage().getItem(SIGN_IN_PATHNAME);
const signInSearch = getStorage().getItem(SIGN_IN_SEARCH);
clearLoginRedirect();
return {
pathname: signInPathname || getDefaultRoute(features),
search: (signInPathname && signInSearch) || undefined
};
}
export const clearAccessToken = () => localStorage.removeItem(ACCESS_TOKEN);
export const decodeMeJWT = (accessToken: string): Me => jwtDecode(accessToken) as Me;
export function addAccessTokenParameter(url: string) {
const accessToken = getStorage().getItem(ACCESS_TOKEN);
if (!accessToken) {
return url;
}
const parsedUrl = new URL(url);
parsedUrl.searchParams.set(ACCESS_TOKEN, accessToken);
return parsedUrl.toString();
}

View File

@@ -0,0 +1,105 @@
import axios, { AxiosPromise, CancelToken } from 'axios';
import { decode } from '@msgpack/msgpack';
export const WS_BASE_URL = '/ws/';
export const API_BASE_URL = '/rest/';
export const ES_BASE_URL = '/es/';
export const EMSESP_API_BASE_URL = '/api/';
export const ACCESS_TOKEN = 'access_token';
export const WEB_SOCKET_ROOT = calculateWebSocketRoot(WS_BASE_URL);
export const EVENT_SOURCE_ROOT = calculateEventSourceRoot(ES_BASE_URL);
export const AXIOS = axios.create({
baseURL: API_BASE_URL,
headers: {
'Content-Type': 'application/json'
},
transformRequest: [
(data, headers) => {
if (headers) {
if (localStorage.getItem(ACCESS_TOKEN)) {
headers.Authorization = 'Bearer ' + localStorage.getItem(ACCESS_TOKEN);
}
if (headers['Content-Type'] !== 'application/json') {
return data;
}
}
return JSON.stringify(data);
}
]
});
export const AXIOS_API = axios.create({
baseURL: EMSESP_API_BASE_URL,
headers: {
'Content-Type': 'application/json'
},
transformRequest: [
(data, headers) => {
if (headers) {
if (localStorage.getItem(ACCESS_TOKEN)) {
headers.Authorization = 'Bearer ' + localStorage.getItem(ACCESS_TOKEN);
}
if (headers['Content-Type'] !== 'application/json') {
return data;
}
}
return JSON.stringify(data);
}
]
});
export const AXIOS_BIN = axios.create({
baseURL: API_BASE_URL,
headers: {
'Content-Type': 'application/json'
},
responseType: 'arraybuffer',
transformRequest: [
(data, headers) => {
if (headers) {
if (localStorage.getItem(ACCESS_TOKEN)) {
headers.Authorization = 'Bearer ' + localStorage.getItem(ACCESS_TOKEN);
}
if (headers['Content-Type'] !== 'application/json') {
return data;
}
}
return JSON.stringify(data);
}
],
transformResponse: [
(data) => {
return decode(data);
}
]
});
function calculateWebSocketRoot(webSocketPath: string) {
const location = window.location;
const webProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
return webProtocol + '//' + location.host + webSocketPath;
}
function calculateEventSourceRoot(endpointPath: string) {
const location = window.location;
return location.protocol + '//' + location.host + endpointPath;
}
export interface FileUploadConfig {
cancelToken?: CancelToken;
onUploadProgress?: (progressEvent: ProgressEvent) => void;
}
export const startUploadFile = (url: string, file: File, config?: FileUploadConfig): AxiosPromise<void> => {
const formData = new FormData();
formData.append('file', file);
return AXIOS.post(url, formData, {
headers: {
'Content-Type': 'multipart/form-data'
},
...(config || {})
});
};

2
interface/src/api/env.ts Normal file
View File

@@ -0,0 +1,2 @@
export const PROJECT_NAME = process.env.REACT_APP_PROJECT_NAME || 'EMS-ESP';
export const PROJECT_PATH = process.env.REACT_APP_PROJECT_PATH || 'project';

View File

@@ -0,0 +1,9 @@
import { AxiosPromise } from 'axios';
import { Features } from '../types';
import { AXIOS } from './endpoints';
export function readFeatures(): AxiosPromise<Features> {
return AXIOS.get('/features');
}

View File

@@ -1,2 +0,0 @@
export * from './Env';
export * from './Endpoints';

16
interface/src/api/mqtt.ts Normal file
View File

@@ -0,0 +1,16 @@
import { AxiosPromise } from 'axios';
import { MqttSettings, MqttStatus } from '../types';
import { AXIOS } from './endpoints';
export function readMqttStatus(): AxiosPromise<MqttStatus> {
return AXIOS.get('/mqttStatus');
}
export function readMqttSettings(): AxiosPromise<MqttSettings> {
return AXIOS.get('/mqttSettings');
}
export function updateMqttSettings(ntpSettings: MqttSettings): AxiosPromise<MqttSettings> {
return AXIOS.post('/mqttSettings', ntpSettings);
}

View File

@@ -0,0 +1,25 @@
import { AxiosPromise } from 'axios';
import { WiFiNetworkList, NetworkSettings, NetworkStatus } from '../types';
import { AXIOS } from './endpoints';
export function readNetworkStatus(): AxiosPromise<NetworkStatus> {
return AXIOS.get('/networkStatus');
}
export function scanNetworks(): AxiosPromise<void> {
return AXIOS.get('/scanNetworks');
}
export function listNetworks(): AxiosPromise<WiFiNetworkList> {
return AXIOS.get('/listNetworks');
}
export function readNetworkSettings(): AxiosPromise<NetworkSettings> {
return AXIOS.get('/networkSettings');
}
export function updateNetworkSettings(wifiSettings: NetworkSettings): AxiosPromise<NetworkSettings> {
return AXIOS.post('/networkSettings', wifiSettings);
}

20
interface/src/api/ntp.ts Normal file
View File

@@ -0,0 +1,20 @@
import { AxiosPromise } from 'axios';
import { NTPSettings, NTPStatus, Time } from '../types';
import { AXIOS } from './endpoints';
export function readNTPStatus(): AxiosPromise<NTPStatus> {
return AXIOS.get('/ntpStatus');
}
export function readNTPSettings(): AxiosPromise<NTPSettings> {
return AXIOS.get('/ntpSettings');
}
export function updateNTPSettings(ntpSettings: NTPSettings): AxiosPromise<NTPSettings> {
return AXIOS.post('/ntpSettings', ntpSettings);
}
export function updateTime(time: Time): AxiosPromise<Time> {
return AXIOS.post('/time', time);
}

View File

@@ -0,0 +1,17 @@
import { AxiosPromise } from 'axios';
import { SecuritySettings, Token } from '../types';
import { AXIOS } from './endpoints';
export function readSecuritySettings(): AxiosPromise<SecuritySettings> {
return AXIOS.get('/securitySettings');
}
export function updateSecuritySettings(securitySettings: SecuritySettings): AxiosPromise<SecuritySettings> {
return AXIOS.post('/securitySettings', securitySettings);
}
export function generateToken(username?: string): AxiosPromise<Token> {
return AXIOS.get('/generateToken', { params: { username } });
}

View File

@@ -0,0 +1,41 @@
import { AxiosPromise } from 'axios';
import { OTASettings, SystemStatus, LogSettings, LogEntries } from '../types';
import { AXIOS, AXIOS_BIN, FileUploadConfig, startUploadFile } from './endpoints';
export function readSystemStatus(timeout?: number): AxiosPromise<SystemStatus> {
return AXIOS.get('/systemStatus', { timeout });
}
export function restart(): AxiosPromise<void> {
return AXIOS.post('/restart');
}
export function factoryReset(): AxiosPromise<void> {
return AXIOS.post('/factoryReset');
}
export function readOTASettings(): AxiosPromise<OTASettings> {
return AXIOS.get('/otaSettings');
}
export function updateOTASettings(otaSettings: OTASettings): AxiosPromise<OTASettings> {
return AXIOS.post('/otaSettings', otaSettings);
}
export const uploadFile = (file: File, config?: FileUploadConfig): AxiosPromise<void> =>
startUploadFile('/uploadFile', file, config);
export function readLogSettings(): AxiosPromise<LogSettings> {
return AXIOS.get('/logSettings');
}
export function updateLogSettings(logSettings: LogSettings): AxiosPromise<LogSettings> {
return AXIOS.post('/logSettings', logSettings);
}
export function readLogEntries(): AxiosPromise<LogEntries> {
return AXIOS_BIN.get('/fetchLog');
}

View File

@@ -1,56 +0,0 @@
import * as React from 'react';
import {
Redirect,
Route,
RouteProps,
RouteComponentProps
} from 'react-router-dom';
import { withSnackbar, WithSnackbarProps } from 'notistack';
import * as Authentication from './Authentication';
import {
withAuthenticationContext,
AuthenticationContextProps,
AuthenticatedContext,
AuthenticatedContextValue
} from './AuthenticationContext';
interface AuthenticatedRouteProps
extends RouteProps,
WithSnackbarProps,
AuthenticationContextProps {
component:
| React.ComponentType<RouteComponentProps<any>>
| React.ComponentType<any>;
}
type RenderComponent = (props: RouteComponentProps<any>) => React.ReactNode;
export class AuthenticatedRoute extends React.Component<AuthenticatedRouteProps> {
render() {
const {
enqueueSnackbar,
authenticationContext,
component: Component,
...rest
} = this.props;
const { location } = this.props;
const renderComponent: RenderComponent = (props) => {
if (authenticationContext.me) {
return (
<AuthenticatedContext.Provider
value={authenticationContext as AuthenticatedContextValue}
>
<Component {...props} />
</AuthenticatedContext.Provider>
);
}
Authentication.storeLoginRedirect(location);
enqueueSnackbar('Please sign in to continue', { variant: 'info' });
return <Redirect to="/" />;
};
return <Route {...rest} render={renderComponent} />;
}
}
export default withSnackbar(withAuthenticationContext(AuthenticatedRoute));

View File

@@ -1,129 +0,0 @@
import * as H from 'history';
import history from '../history';
import { Features } from '../features/types';
import { getDefaultRoute } from '../AppRouting';
export const ACCESS_TOKEN = 'access_token';
export const SIGN_IN_PATHNAME = 'signInPathname';
export const SIGN_IN_SEARCH = 'signInSearch';
/**
* Fallback to sessionStorage if localStorage is absent. WebView may not have local storage enabled.
*/
export function getStorage() {
return localStorage || sessionStorage;
}
export function storeLoginRedirect(location?: H.Location) {
if (location) {
getStorage().setItem(SIGN_IN_PATHNAME, location.pathname);
getStorage().setItem(SIGN_IN_SEARCH, location.search);
}
}
export function clearLoginRedirect() {
getStorage().removeItem(SIGN_IN_PATHNAME);
getStorage().removeItem(SIGN_IN_SEARCH);
}
export function fetchLoginRedirect(
features: Features
): H.LocationDescriptorObject {
const signInPathname = getStorage().getItem(SIGN_IN_PATHNAME);
const signInSearch = getStorage().getItem(SIGN_IN_SEARCH);
clearLoginRedirect();
return {
pathname: signInPathname || getDefaultRoute(features),
search: (signInPathname && signInSearch) || undefined
};
}
/**
* Wraps the normal fetch routine with one with provides the access token if present.
*/
export function authorizedFetch(
url: RequestInfo,
params?: RequestInit
): Promise<Response> {
const accessToken = getStorage().getItem(ACCESS_TOKEN);
if (accessToken) {
params = params || {};
params.credentials = 'include';
params.headers = {
...params.headers,
Authorization: 'Bearer ' + accessToken
};
}
return fetch(url, params);
}
/**
* fetch() does not yet support upload progress, this wrapper allows us to configure the xhr request
* for a single file upload and takes care of adding the Authorization header and redirecting on
* authorization errors as we do for normal fetch operations.
*/
export function redirectingAuthorizedUpload(
xhr: XMLHttpRequest,
url: string,
file: File,
onProgress: (event: ProgressEvent<EventTarget>) => void
): Promise<void> {
return new Promise((resolve, reject) => {
xhr.open('POST', url, true);
const accessToken = getStorage().getItem(ACCESS_TOKEN);
if (accessToken) {
xhr.withCredentials = true;
xhr.setRequestHeader('Authorization', 'Bearer ' + accessToken);
}
xhr.upload.onprogress = onProgress;
xhr.onload = function () {
if (xhr.status === 401 || xhr.status === 403) {
history.push('/unauthorized');
} else {
resolve();
}
};
xhr.onerror = function () {
reject(new DOMException('Error', 'UploadError'));
};
xhr.onabort = function () {
reject(new DOMException('Aborted', 'AbortError'));
};
const formData = new FormData();
formData.append('file', file);
xhr.send(formData);
});
}
/**
* Wraps the normal fetch routine which redirects on 401 response.
*/
export function redirectingAuthorizedFetch(
url: RequestInfo,
params?: RequestInit
): Promise<Response> {
return new Promise<Response>((resolve, reject) => {
authorizedFetch(url, params)
.then((response) => {
if (response.status === 401 || response.status === 403) {
history.push('/unauthorized');
} else {
resolve(response);
}
})
.catch((error) => {
reject(error);
});
});
}
export function addAccessTokenParameter(url: string) {
const accessToken = getStorage().getItem(ACCESS_TOKEN);
if (!accessToken) {
return url;
}
const parsedUrl = new URL(url);
parsedUrl.searchParams.set(ACCESS_TOKEN, accessToken);
return parsedUrl.toString();
}

View File

@@ -1,77 +0,0 @@
import * as React from 'react';
export interface Me {
username: string;
admin: boolean;
}
export interface AuthenticationContextValue {
refresh: () => void;
signIn: (accessToken: string) => void;
signOut: () => void;
me?: Me;
}
const AuthenticationContextDefaultValue = {} as AuthenticationContextValue;
export const AuthenticationContext = React.createContext(
AuthenticationContextDefaultValue
);
export interface AuthenticationContextProps {
authenticationContext: AuthenticationContextValue;
}
export function withAuthenticationContext<T extends AuthenticationContextProps>(
Component: React.ComponentType<T>
) {
return class extends React.Component<
Omit<T, keyof AuthenticationContextProps>
> {
render() {
return (
<AuthenticationContext.Consumer>
{(authenticationContext) => (
<Component
{...(this.props as T)}
authenticationContext={authenticationContext}
/>
)}
</AuthenticationContext.Consumer>
);
}
};
}
export interface AuthenticatedContextValue extends AuthenticationContextValue {
me: Me;
}
const AuthenticatedContextDefaultValue = {} as AuthenticatedContextValue;
export const AuthenticatedContext = React.createContext(
AuthenticatedContextDefaultValue
);
export interface AuthenticatedContextProps {
authenticatedContext: AuthenticatedContextValue;
}
export function withAuthenticatedContext<T extends AuthenticatedContextProps>(
Component: React.ComponentType<T>
) {
return class extends React.Component<
Omit<T, keyof AuthenticatedContextProps>
> {
render() {
return (
<AuthenticatedContext.Consumer>
{(authenticatedContext) => (
<Component
{...(this.props as T)}
authenticatedContext={authenticatedContext}
/>
)}
</AuthenticatedContext.Consumer>
);
}
};
}

View File

@@ -1,135 +0,0 @@
import * as React from 'react';
import { withSnackbar, WithSnackbarProps } from 'notistack';
import jwtDecode from 'jwt-decode';
import history from '../history';
import { VERIFY_AUTHORIZATION_ENDPOINT } from '../api';
import { ACCESS_TOKEN, authorizedFetch, getStorage } from './Authentication';
import {
AuthenticationContext,
AuthenticationContextValue,
Me
} from './AuthenticationContext';
import FullScreenLoading from '../components/FullScreenLoading';
import { withFeatures, WithFeaturesProps } from '../features/FeaturesContext';
export const decodeMeJWT = (accessToken: string): Me =>
jwtDecode(accessToken) as Me;
interface AuthenticationWrapperState {
context: AuthenticationContextValue;
initialized: boolean;
}
type AuthenticationWrapperProps = WithSnackbarProps & WithFeaturesProps;
class AuthenticationWrapper extends React.Component<
AuthenticationWrapperProps,
AuthenticationWrapperState
> {
constructor(props: AuthenticationWrapperProps) {
super(props);
this.state = {
context: {
refresh: this.refresh,
signIn: this.signIn,
signOut: this.signOut
},
initialized: false
};
}
componentDidMount() {
this.refresh();
}
render() {
return (
<React.Fragment>
{this.state.initialized
? this.renderContent()
: this.renderContentLoading()}
</React.Fragment>
);
}
renderContent() {
return (
<AuthenticationContext.Provider value={this.state.context}>
{this.props.children}
</AuthenticationContext.Provider>
);
}
renderContentLoading() {
return <FullScreenLoading />;
}
refresh = () => {
// commented out, always need security - proddy
// if (!this.props.features.security) {
// this.setState({ initialized: true, context: { ...this.state.context, me: { admin: true, username: "admin" } } });
// return;
// }
const accessToken = getStorage().getItem(ACCESS_TOKEN);
if (accessToken) {
authorizedFetch(VERIFY_AUTHORIZATION_ENDPOINT)
.then((response) => {
const me =
response.status === 200 ? decodeMeJWT(accessToken) : undefined;
this.setState({
initialized: true,
context: { ...this.state.context, me }
});
})
.catch((error) => {
this.setState({
initialized: true,
context: { ...this.state.context, me: undefined }
});
this.props.enqueueSnackbar(
'Error verifying authorization: ' + error.message,
{
variant: 'error'
}
);
});
} else {
this.setState({
initialized: true,
context: { ...this.state.context, me: undefined }
});
}
};
signIn = (accessToken: string) => {
try {
getStorage().setItem(ACCESS_TOKEN, accessToken);
const me: Me = decodeMeJWT(accessToken);
this.setState({ context: { ...this.state.context, me } });
this.props.enqueueSnackbar(`Logged in as ${me.username}`, {
variant: 'success'
});
} catch (err) {
this.setState({
initialized: true,
context: { ...this.state.context, me: undefined }
});
throw new Error('Failed to parse JWT ' + err.message);
}
};
signOut = () => {
getStorage().removeItem(ACCESS_TOKEN);
this.setState({
context: {
...this.state.context,
me: undefined
}
});
this.props.enqueueSnackbar('You have signed out', { variant: 'success' });
history.push('/');
};
}
export default withFeatures(withSnackbar(AuthenticationWrapper));

View File

@@ -1,47 +0,0 @@
import * as React from 'react';
import {
Redirect,
Route,
RouteProps,
RouteComponentProps
} from 'react-router-dom';
import {
withAuthenticationContext,
AuthenticationContextProps
} from './AuthenticationContext';
import * as Authentication from './Authentication';
import { WithFeaturesProps, withFeatures } from '../features/FeaturesContext';
interface UnauthenticatedRouteProps
extends RouteProps,
AuthenticationContextProps,
WithFeaturesProps {
component:
| React.ComponentType<RouteComponentProps<any>>
| React.ComponentType<any>;
}
type RenderComponent = (props: RouteComponentProps<any>) => React.ReactNode;
class UnauthenticatedRoute extends Route<UnauthenticatedRouteProps> {
public render() {
const {
authenticationContext,
component: Component,
features,
...rest
} = this.props;
const renderComponent: RenderComponent = (props) => {
if (authenticationContext.me) {
return <Redirect to={Authentication.fetchLoginRedirect(features)} />;
}
if (Component) {
return <Component {...props} />;
}
};
return <Route {...rest} render={renderComponent} />;
}
}
export default withFeatures(withAuthenticationContext(UnauthenticatedRoute));

View File

@@ -1,6 +0,0 @@
export { default as AuthenticatedRoute } from './AuthenticatedRoute';
export { default as AuthenticationWrapper } from './AuthenticationWrapper';
export { default as UnauthenticatedRoute } from './UnauthenticatedRoute';
export * from './Authentication';
export * from './AuthenticationContext';

View File

@@ -1,59 +0,0 @@
import React, { FC } from 'react';
import { makeStyles } from '@material-ui/styles';
import { Paper, Typography, Box, CssBaseline } from '@material-ui/core';
import WarningIcon from '@material-ui/icons/Warning';
const styles = makeStyles({
siteErrorPage: {
display: 'flex',
height: '100vh',
justifyContent: 'center',
flexDirection: 'column'
},
siteErrorPagePanel: {
textAlign: 'center',
padding: '280px 0 40px 0',
backgroundImage: 'url("/app/icon.png")',
backgroundRepeat: 'no-repeat',
backgroundPosition: '50% 40px',
backgroundSize: '200px auto',
width: '100%'
}
});
interface ApplicationErrorProps {
error?: string;
}
const ApplicationError: FC<ApplicationErrorProps> = ({ error }) => {
const classes = styles();
return (
<div className={classes.siteErrorPage}>
<CssBaseline />
<Paper className={classes.siteErrorPagePanel} elevation={10}>
<Box
display="flex"
flexDirection="row"
justifyContent="center"
alignItems="center"
mb={2}
>
<WarningIcon fontSize="large" color="error" />
<Box ml={2}>
<Typography variant="h4">Application error</Typography>
</Box>
</Box>
<Typography variant="subtitle1" gutterBottom>
Failed to configure the application, please refresh to try again.
</Typography>
{error && (
<Typography variant="subtitle2" gutterBottom>
Error: {error}
</Typography>
)}
</Paper>
</div>
);
};
export default ApplicationError;

View File

@@ -0,0 +1,26 @@
import { FC } from 'react';
import { Box, BoxProps } from '@mui/material';
const ButtonRow: FC<BoxProps> = ({ children, ...rest }) => {
return (
<Box
sx={{
'& button, & a, & .MuiCard-root': {
mt: 2,
mx: 0.6,
'&:last-child': {
mr: 0
},
'&:first-of-type': {
ml: 0
}
}
}}
{...rest}
>
{children}
</Box>
);
};
export default ButtonRow;

View File

@@ -1,11 +0,0 @@
import { Button, styled } from '@material-ui/core';
const ErrorButton = styled(Button)(({ theme }) => ({
color: theme.palette.getContrastText(theme.palette.error.main),
backgroundColor: theme.palette.error.main,
'&:hover': {
backgroundColor: theme.palette.error.dark
}
}));
export default ErrorButton;

View File

@@ -1,7 +0,0 @@
import { styled, Box } from '@material-ui/core';
const FormActions = styled(Box)(({ theme }) => ({
marginTop: theme.spacing(1)
}));
export default FormActions;

View File

@@ -1,13 +0,0 @@
import { Button, styled } from '@material-ui/core';
const FormButton = styled(Button)(({ theme }) => ({
margin: theme.spacing(0, 1),
'&:last-child': {
marginRight: 0
},
'&:first-child': {
marginLeft: 0
}
}));
export default FormButton;

View File

@@ -1,32 +0,0 @@
import React from 'react';
import CircularProgress from '@material-ui/core/CircularProgress';
import { Typography, Theme } from '@material-ui/core';
import { makeStyles, createStyles } from '@material-ui/styles';
const useStyles = makeStyles((theme: Theme) =>
createStyles({
fullScreenLoading: {
padding: theme.spacing(2),
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
height: '100vh',
flexDirection: 'column'
},
progress: {
margin: theme.spacing(4)
}
})
);
const FullScreenLoading = () => {
const classes = useStyles();
return (
<div className={classes.fullScreenLoading}>
<CircularProgress className={classes.progress} size={100} />
<Typography variant="h4">Loading&hellip;</Typography>
</div>
);
};
export default FullScreenLoading;

View File

@@ -1,19 +0,0 @@
import { Avatar, makeStyles } from '@material-ui/core';
import { FC } from 'react';
interface HighlightAvatarProps {
color: string;
}
const useStyles = makeStyles({
root: (props: HighlightAvatarProps) => ({
backgroundColor: props.color
})
});
const HighlightAvatar: FC<HighlightAvatarProps> = (props) => {
const classes = useStyles(props);
return <Avatar className={classes.root}>{props.children}</Avatar>;
};
export default HighlightAvatar;

View File

@@ -1,390 +0,0 @@
import React, { RefObject, Fragment } from 'react';
import { Link, withRouter, RouteComponentProps } from 'react-router-dom';
import {
Drawer,
AppBar,
Toolbar,
Avatar,
Divider,
Button,
Box,
IconButton
} from '@material-ui/core';
import {
ClickAwayListener,
Popper,
Hidden,
Typography
} from '@material-ui/core';
import {
List,
ListItem,
ListItemIcon,
ListItemText,
ListItemAvatar
} from '@material-ui/core';
import { Card, CardContent, CardActions } from '@material-ui/core';
import {
withStyles,
createStyles,
Theme,
WithTheme,
WithStyles,
withTheme
} from '@material-ui/core/styles';
import SettingsEthernetIcon from '@material-ui/icons/SettingsEthernet';
import SettingsIcon from '@material-ui/icons/Settings';
import AccessTimeIcon from '@material-ui/icons/AccessTime';
import AccountCircleIcon from '@material-ui/icons/AccountCircle';
import SettingsInputAntennaIcon from '@material-ui/icons/SettingsInputAntenna';
import DeviceHubIcon from '@material-ui/icons/DeviceHub';
import LockIcon from '@material-ui/icons/Lock';
import MenuIcon from '@material-ui/icons/Menu';
import ProjectMenu from '../project/ProjectMenu';
import { PROJECT_NAME } from '../api';
import {
withAuthenticatedContext,
AuthenticatedContextProps
} from '../authentication';
import { withFeatures, WithFeaturesProps } from '../features/FeaturesContext';
const drawerWidth = 290;
const styles = (theme: Theme) =>
createStyles({
root: {
display: 'flex'
},
drawer: {
[theme.breakpoints.up('md')]: {
width: drawerWidth,
flexShrink: 0
}
},
title: {
flexGrow: 1
},
appBar: {
marginLeft: drawerWidth,
[theme.breakpoints.up('md')]: {
width: `calc(100% - ${drawerWidth}px)`
}
},
toolbarImage: {
[theme.breakpoints.up('xs')]: {
height: 24,
marginRight: theme.spacing(2)
},
[theme.breakpoints.up('sm')]: {
height: 36,
marginRight: theme.spacing(3)
}
},
menuButton: {
marginRight: theme.spacing(2),
[theme.breakpoints.up('md')]: {
display: 'none'
}
},
toolbar: theme.mixins.toolbar,
drawerPaper: {
width: drawerWidth
},
content: {
flexGrow: 1
},
authMenu: {
zIndex: theme.zIndex.tooltip,
maxWidth: 400
},
authMenuActions: {
padding: theme.spacing(2),
'& > * + *': {
marginLeft: theme.spacing(2)
}
}
});
interface MenuAppBarState {
mobileOpen: boolean;
authMenuOpen: boolean;
}
interface MenuAppBarProps
extends WithFeaturesProps,
AuthenticatedContextProps,
WithTheme,
WithStyles<typeof styles>,
RouteComponentProps {
sectionTitle: string;
}
class MenuAppBar extends React.Component<MenuAppBarProps, MenuAppBarState> {
constructor(props: MenuAppBarProps) {
super(props);
this.state = {
mobileOpen: false,
authMenuOpen: false
};
}
anchorRef: RefObject<HTMLButtonElement> = React.createRef();
handleToggle = () => {
this.setState({ authMenuOpen: !this.state.authMenuOpen });
};
handleClose = (event: React.MouseEvent<Document>) => {
if (
this.anchorRef.current &&
this.anchorRef.current.contains(event.currentTarget)
) {
return;
}
this.setState({ authMenuOpen: false });
};
handleDrawerToggle = () => {
this.setState({ mobileOpen: !this.state.mobileOpen });
};
render() {
const {
classes,
theme,
children,
sectionTitle,
authenticatedContext,
features
} = this.props;
const { mobileOpen, authMenuOpen } = this.state;
const path = this.props.match.url;
const drawer = (
<div>
<Toolbar>
<Box display="flex">
<img
src="/app/icon.png"
className={classes.toolbarImage}
alt={PROJECT_NAME}
/>
</Box>
<Typography variant="h6" color="textPrimary">
{PROJECT_NAME}
</Typography>
<Divider absolute />
</Toolbar>
{features.project && (
<Fragment>
<ProjectMenu />
<Divider />
</Fragment>
)}
<List>
<ListItem
to="/network/"
selected={path.startsWith('/network/')}
button
component={Link}
>
<ListItemIcon>
<SettingsEthernetIcon />
</ListItemIcon>
<ListItemText primary="Network Connection" />
</ListItem>
<ListItem
to="/ap/"
selected={path.startsWith('/ap/')}
button
component={Link}
>
<ListItemIcon>
<SettingsInputAntennaIcon />
</ListItemIcon>
<ListItemText primary="Access Point" />
</ListItem>
{features.ntp && (
<ListItem
to="/ntp/"
selected={path.startsWith('/ntp/')}
button
component={Link}
>
<ListItemIcon>
<AccessTimeIcon />
</ListItemIcon>
<ListItemText primary="Network Time" />
</ListItem>
)}
{features.mqtt && (
<ListItem
to="/mqtt/"
selected={path.startsWith('/mqtt/')}
button
component={Link}
>
<ListItemIcon>
<DeviceHubIcon />
</ListItemIcon>
<ListItemText primary="MQTT" />
</ListItem>
)}
{features.security && (
<ListItem
to="/security/"
selected={path.startsWith('/security/')}
button
component={Link}
disabled={!authenticatedContext.me.admin}
>
<ListItemIcon>
<LockIcon />
</ListItemIcon>
<ListItemText primary="Security" />
</ListItem>
)}
<ListItem
to="/system/"
selected={path.startsWith('/system/')}
button
component={Link}
>
<ListItemIcon>
<SettingsIcon />
</ListItemIcon>
<ListItemText primary="System" />
</ListItem>
</List>
</div>
);
const userMenu = (
<div>
<IconButton
ref={this.anchorRef}
aria-owns={authMenuOpen ? 'menu-list-grow' : undefined}
aria-haspopup="true"
onClick={this.handleToggle}
color="inherit"
>
<AccountCircleIcon />
</IconButton>
<Popper
open={authMenuOpen}
anchorEl={this.anchorRef.current}
transition
className={classes.authMenu}
>
<ClickAwayListener onClickAway={this.handleClose}>
<Card id="menu-list-grow">
<CardContent>
<List disablePadding>
<ListItem disableGutters>
<ListItemAvatar>
<Avatar>
<AccountCircleIcon />
</Avatar>
</ListItemAvatar>
<ListItemText
primary={
'Signed in as: ' + authenticatedContext.me.username
}
secondary={
authenticatedContext.me.admin ? 'Admin User' : undefined
}
/>
</ListItem>
</List>
</CardContent>
<Divider />
<CardActions className={classes.authMenuActions}>
<Button
variant="contained"
fullWidth
color="primary"
onClick={authenticatedContext.signOut}
>
Sign Out
</Button>
</CardActions>
</Card>
</ClickAwayListener>
</Popper>
</div>
);
return (
<div className={classes.root}>
<AppBar position="fixed" className={classes.appBar} elevation={0}>
<Toolbar>
<IconButton
color="inherit"
aria-label="Open drawer"
edge="start"
onClick={this.handleDrawerToggle}
className={classes.menuButton}
>
<MenuIcon />
</IconButton>
<Typography
variant="h6"
color="inherit"
noWrap
className={classes.title}
>
{sectionTitle}
</Typography>
{features.security && userMenu}
</Toolbar>
</AppBar>
<nav className={classes.drawer}>
<Hidden mdUp implementation="css">
<Drawer
variant="temporary"
anchor={theme.direction === 'rtl' ? 'right' : 'left'}
open={mobileOpen}
onClose={this.handleDrawerToggle}
classes={{
paper: classes.drawerPaper
}}
ModalProps={{
keepMounted: true
}}
>
{drawer}
</Drawer>
</Hidden>
<Hidden smDown implementation="css">
<Drawer
classes={{
paper: classes.drawerPaper
}}
variant="permanent"
open
>
{drawer}
</Drawer>
</Hidden>
</nav>
<main className={classes.content}>
<div className={classes.toolbar} />
{children}
</main>
</div>
);
}
}
export default withRouter(
withTheme(
withFeatures(withAuthenticatedContext(withStyles(styles)(MenuAppBar)))
)
);

View File

@@ -0,0 +1,47 @@
import { FC } from 'react';
import { Box, BoxProps, SvgIconProps, Theme, Typography, useTheme } from '@mui/material';
import CheckCircleOutlineOutlinedIcon from '@mui/icons-material/CheckCircleOutlineOutlined';
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
import ReportProblemOutlinedIcon from '@mui/icons-material/ReportProblemOutlined';
import ErrorIcon from '@mui/icons-material/Error';
type MessageBoxLevel = 'warning' | 'success' | 'info' | 'error';
export interface MessageBoxProps extends BoxProps {
level: MessageBoxLevel;
message: string;
}
const LEVEL_ICONS: { [type in MessageBoxLevel]: React.ComponentType<SvgIconProps> } = {
success: CheckCircleOutlineOutlinedIcon,
info: InfoOutlinedIcon,
warning: ReportProblemOutlinedIcon,
error: ErrorIcon
};
const LEVEL_BACKGROUNDS: { [type in MessageBoxLevel]: (theme: Theme) => string } = {
success: (theme: Theme) => theme.palette.success.dark,
info: (theme: Theme) => theme.palette.info.main,
warning: (theme: Theme) => theme.palette.warning.dark,
error: (theme: Theme) => theme.palette.error.dark
};
const MessageBox: FC<MessageBoxProps> = ({ level, message, sx, children, ...rest }) => {
const theme = useTheme();
const Icon = LEVEL_ICONS[level];
const backgroundColor = LEVEL_BACKGROUNDS[level](theme);
const color = 'white';
return (
<Box p={2} display="flex" alignItems="center" borderRadius={1} sx={{ backgroundColor, color, ...sx }} {...rest}>
<Icon />
<Typography sx={{ ml: 2, flexGrow: 1 }} variant="body1">
{message}
</Typography>
{children}
</Box>
);
};
export default MessageBox;

View File

@@ -1,64 +0,0 @@
import React from 'react';
import {
TextValidator,
ValidatorComponentProps
} from 'react-material-ui-form-validator';
import { withStyles, WithStyles, createStyles } from '@material-ui/core/styles';
import { InputAdornment, IconButton } from '@material-ui/core';
import { Visibility, VisibilityOff } from '@material-ui/icons';
const styles = createStyles({
input: {
'&::-ms-reveal': {
display: 'none'
}
}
});
type PasswordValidatorProps = WithStyles<typeof styles> &
Exclude<ValidatorComponentProps, 'type' | 'InputProps'>;
interface PasswordValidatorState {
showPassword: boolean;
}
class PasswordValidator extends React.Component<
PasswordValidatorProps,
PasswordValidatorState
> {
state = {
showPassword: false
};
toggleShowPassword = () => {
this.setState({
showPassword: !this.state.showPassword
});
};
render() {
const { classes, ...rest } = this.props;
return (
<TextValidator
{...rest}
type={this.state.showPassword ? 'text' : 'password'}
InputProps={{
classes,
endAdornment: (
<InputAdornment position="end">
<IconButton
aria-label="Toggle password visibility"
onClick={this.toggleShowPassword}
>
{this.state.showPassword ? <Visibility /> : <VisibilityOff />}
</IconButton>
</InputAdornment>
)
}}
/>
);
}
}
export default withStyles(styles)(PasswordValidator);

View File

@@ -1,141 +0,0 @@
import React from 'react';
import { withSnackbar, WithSnackbarProps } from 'notistack';
import { redirectingAuthorizedFetch } from '../authentication';
export interface RestControllerProps<D> extends WithSnackbarProps {
handleValueChange: (
name: keyof D
) => (event: React.ChangeEvent<HTMLInputElement>) => void;
setData: (data: D, callback?: () => void) => void;
saveData: () => void;
loadData: () => void;
data?: D;
loading: boolean;
errorMessage?: string;
}
export const extractEventValue = (
event: React.ChangeEvent<HTMLInputElement>
) => {
switch (event.target.type) {
case 'number':
return event.target.valueAsNumber;
case 'checkbox':
return event.target.checked;
default:
return event.target.value;
}
};
interface RestControllerState<D> {
data?: D;
loading: boolean;
errorMessage?: string;
}
export function restController<D, P extends RestControllerProps<D>>(
endpointUrl: string,
RestController: React.ComponentType<P & RestControllerProps<D>>
) {
return withSnackbar(
class extends React.Component<
Omit<P, keyof RestControllerProps<D>> & WithSnackbarProps,
RestControllerState<D>
> {
state: RestControllerState<D> = {
data: undefined,
loading: false,
errorMessage: undefined
};
setData = (data: D, callback?: () => void) => {
this.setState(
{
data,
loading: false,
errorMessage: undefined
},
callback
);
};
loadData = () => {
this.setState({
data: undefined,
loading: true,
errorMessage: undefined
});
redirectingAuthorizedFetch(endpointUrl)
.then((response) => {
if (response.status === 200) {
return response.json();
}
throw Error('Invalid status code: ' + response.status);
})
.then((json) => {
this.setState({ data: json, loading: false });
})
.catch((error) => {
const errorMessage = error.message || 'Unknown error';
this.props.enqueueSnackbar('Problem fetching: ' + errorMessage, {
variant: 'error'
});
this.setState({ data: undefined, loading: false, errorMessage });
});
};
saveData = () => {
this.setState({ loading: true });
redirectingAuthorizedFetch(endpointUrl, {
method: 'POST',
body: JSON.stringify(this.state.data),
headers: {
'Content-Type': 'application/json'
}
})
.then((response) => {
if (response.status === 200) {
return response.json();
}
throw Error('Invalid status code: ' + response.status);
})
.then((json) => {
this.props.enqueueSnackbar('Update successful.', {
variant: 'success'
});
this.setState({ data: json, loading: false });
})
.catch((error) => {
const errorMessage = error.message || 'Unknown error';
this.props.enqueueSnackbar('Problem updating: ' + errorMessage, {
variant: 'error'
});
this.setState({ data: undefined, loading: false, errorMessage });
});
};
handleValueChange = (name: keyof D) => (
event: React.ChangeEvent<HTMLInputElement>
) => {
const data = { ...this.state.data!, [name]: extractEventValue(event) };
this.setState({ data });
};
render() {
return (
<RestController
{...this.state}
{...(this.props as P)}
handleValueChange={this.handleValueChange}
setData={this.setData}
saveData={this.saveData}
loadData={this.loadData}
/>
);
}
}
);
}

View File

@@ -1,64 +0,0 @@
import React from 'react';
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles';
import { Button, LinearProgress, Typography } from '@material-ui/core';
import { RestControllerProps } from '.';
const useStyles = makeStyles((theme: Theme) =>
createStyles({
loadingSettings: {
margin: theme.spacing(0.5)
},
loadingSettingsDetails: {
margin: theme.spacing(4),
textAlign: 'center'
},
button: {
marginRight: theme.spacing(2),
marginTop: theme.spacing(2)
}
})
);
export type RestFormProps<D> = Omit<
RestControllerProps<D>,
'loading' | 'errorMessage'
> & { data: D };
interface RestFormLoaderProps<D> extends RestControllerProps<D> {
render: (props: RestFormProps<D>) => JSX.Element;
}
export default function RestFormLoader<D>(props: RestFormLoaderProps<D>) {
const { loading, errorMessage, loadData, render, data, ...rest } = props;
const classes = useStyles();
if (loading || !data) {
return (
<div className={classes.loadingSettings}>
<LinearProgress className={classes.loadingSettingsDetails} />
<Typography variant="h6" className={classes.loadingSettingsDetails}>
Loading&hellip;
</Typography>
</div>
);
}
if (errorMessage) {
return (
<div className={classes.loadingSettings}>
<Typography variant="h6" className={classes.loadingSettingsDetails}>
{errorMessage}
</Typography>
<Button
variant="contained"
color="secondary"
className={classes.button}
onClick={loadData}
>
Retry
</Button>
</div>
);
}
return render({ ...rest, loadData, data });
}

View File

@@ -1,31 +1,20 @@
import React from 'react';
import { FC } from 'react';
import { Typography, Paper } from '@material-ui/core';
import { createStyles, Theme, makeStyles } from '@material-ui/core/styles';
import { Paper, Divider } from '@mui/material';
const useStyles = makeStyles((theme: Theme) =>
createStyles({
content: {
padding: theme.spacing(2),
margin: theme.spacing(3)
}
})
);
import { RequiredChildrenProps } from '../utils';
interface SectionContentProps {
interface SectionContentProps extends RequiredChildrenProps {
title: string;
titleGutter?: boolean;
id?: string;
}
const SectionContent: React.FC<SectionContentProps> = (props) => {
const { children, title, titleGutter, id } = props;
const classes = useStyles();
const SectionContent: FC<SectionContentProps> = (props) => {
const { children, title, id } = props;
return (
<Paper id={id} className={classes.content}>
<Typography variant="h6" gutterBottom={titleGutter}>
{title}
</Typography>
<Paper id={id} sx={{ p: 2, m: 2 }}>
<Divider sx={{ pb: 2, borderColor: 'primary.main', fontSize: 20, color: 'primary.main' }}>{title}</Divider>
{children}
</Paper>
);

View File

@@ -1,128 +0,0 @@
import React, { FC, Fragment } from 'react';
import { useDropzone, DropzoneState } from 'react-dropzone';
import { makeStyles, createStyles } from '@material-ui/styles';
import CloudUploadIcon from '@material-ui/icons/CloudUpload';
import CancelIcon from '@material-ui/icons/Cancel';
import {
Theme,
Box,
Typography,
LinearProgress,
Button
} from '@material-ui/core';
interface SingleUploadStyleProps extends DropzoneState {
uploading: boolean;
}
const progressPercentage = (progress: ProgressEvent) =>
Math.round((progress.loaded * 100) / progress.total);
const getBorderColor = (theme: Theme, props: SingleUploadStyleProps) => {
if (props.isDragAccept) {
return theme.palette.success.main;
}
if (props.isDragReject) {
return theme.palette.error.main;
}
if (props.isDragActive) {
return theme.palette.info.main;
}
return theme.palette.grey[700];
};
const useStyles = makeStyles((theme: Theme) =>
createStyles({
dropzone: {
padding: theme.spacing(8, 2),
borderWidth: 2,
borderRadius: 2,
borderStyle: 'dashed',
color: theme.palette.grey[700],
transition: 'border .24s ease-in-out',
cursor: (props: SingleUploadStyleProps) =>
props.uploading ? 'default' : 'pointer',
width: '100%',
borderColor: (props: SingleUploadStyleProps) =>
getBorderColor(theme, props)
}
})
);
export interface SingleUploadProps {
onDrop: (acceptedFiles: File[]) => void;
onCancel: () => void;
accept?: string | string[];
uploading: boolean;
progress?: ProgressEvent;
}
const SingleUpload: FC<SingleUploadProps> = ({
onDrop,
onCancel,
accept,
uploading,
progress
}) => {
const dropzoneState = useDropzone({
onDrop,
accept,
disabled: uploading,
multiple: false
});
const { getRootProps, getInputProps } = dropzoneState;
const classes = useStyles({ ...dropzoneState, uploading });
const renderProgressText = () => {
if (uploading) {
if (progress?.lengthComputable) {
return `Uploading: ${progressPercentage(progress)}%`;
}
return 'Uploading\u2026';
}
return 'Drop file or click here';
};
const renderProgress = (progress?: ProgressEvent) => (
<LinearProgress
variant={
!progress || progress.lengthComputable ? 'determinate' : 'indeterminate'
}
value={
!progress
? 0
: progress.lengthComputable
? progressPercentage(progress)
: 0
}
/>
);
return (
<div {...getRootProps({ className: classes.dropzone })}>
<input {...getInputProps()} />
<Box flexDirection="column" display="flex" alignItems="center">
<CloudUploadIcon fontSize="large" />
<Typography variant="h6">{renderProgressText()}</Typography>
{uploading && (
<Fragment>
<Box width="100%" p={2}>
{renderProgress(progress)}
</Box>
<Button
startIcon={<CancelIcon />}
variant="contained"
color="secondary"
onClick={onCancel}
>
Cancel
</Button>
</Fragment>
)}
</Box>
</div>
);
};
export default SingleUpload;

View File

@@ -1,158 +0,0 @@
import React from 'react';
import Sockette from 'sockette';
import throttle from 'lodash/throttle';
import { withSnackbar, WithSnackbarProps } from 'notistack';
import { addAccessTokenParameter } from '../authentication';
import { extractEventValue } from '.';
export interface WebSocketControllerProps<D> extends WithSnackbarProps {
handleValueChange: (
name: keyof D
) => (event: React.ChangeEvent<HTMLInputElement>) => void;
setData: (data: D, callback?: () => void) => void;
saveData: () => void;
saveDataAndClear(): () => void;
connected: boolean;
data?: D;
}
interface WebSocketControllerState<D> {
ws: Sockette;
connected: boolean;
clientId?: string;
data?: D;
}
enum WebSocketMessageType {
ID = 'id',
PAYLOAD = 'payload'
}
interface WebSocketIdMessage {
type: typeof WebSocketMessageType.ID;
id: string;
}
interface WebSocketPayloadMessage<D> {
type: typeof WebSocketMessageType.PAYLOAD;
origin_id: string;
payload: D;
}
export type WebSocketMessage<D> =
| WebSocketIdMessage
| WebSocketPayloadMessage<D>;
export function webSocketController<D, P extends WebSocketControllerProps<D>>(
wsUrl: string,
wsThrottle: number,
WebSocketController: React.ComponentType<P & WebSocketControllerProps<D>>
) {
return withSnackbar(
class extends React.Component<
Omit<P, keyof WebSocketControllerProps<D>> & WithSnackbarProps,
WebSocketControllerState<D>
> {
constructor(
props: Omit<P, keyof WebSocketControllerProps<D>> & WithSnackbarProps
) {
super(props);
this.state = {
ws: new Sockette(addAccessTokenParameter(wsUrl), {
onmessage: this.onMessage,
onopen: this.onOpen,
onclose: this.onClose
}),
connected: false
};
}
componentWillUnmount() {
this.state.ws.close();
}
onMessage = (event: MessageEvent) => {
const rawData = event.data;
if (typeof rawData === 'string' || rawData instanceof String) {
this.handleMessage(
JSON.parse(rawData as string) as WebSocketMessage<D>
);
}
};
handleMessage = (message: WebSocketMessage<D>) => {
const { clientId, data } = this.state;
switch (message.type) {
case WebSocketMessageType.ID:
this.setState({ clientId: message.id });
break;
case WebSocketMessageType.PAYLOAD:
if (clientId && (!data || clientId !== message.origin_id)) {
this.setState({ data: message.payload });
}
break;
}
};
onOpen = () => {
this.setState({ connected: true });
};
onClose = () => {
this.setState({
connected: false,
clientId: undefined,
data: undefined
});
};
setData = (data: D, callback?: () => void) => {
this.setState({ data }, callback);
};
saveData = throttle(() => {
const { ws, connected, data } = this.state;
if (connected) {
ws.json(data);
}
}, wsThrottle);
saveDataAndClear = throttle(() => {
const { ws, connected, data } = this.state;
if (connected) {
this.setState(
{
data: undefined
},
() => ws.json(data)
);
}
}, wsThrottle);
handleValueChange = (name: keyof D) => (
event: React.ChangeEvent<HTMLInputElement>
) => {
const data = { ...this.state.data!, [name]: extractEventValue(event) };
this.setState({ data });
};
render() {
return (
<WebSocketController
{...(this.props as P)}
handleValueChange={this.handleValueChange}
setData={this.setData}
saveData={this.saveData}
saveDataAndClear={this.saveDataAndClear}
connected={this.state.connected}
data={this.state.data}
/>
);
}
}
);
}

View File

@@ -1,43 +0,0 @@
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles';
import { LinearProgress, Typography } from '@material-ui/core';
import { WebSocketControllerProps } from '.';
const useStyles = makeStyles((theme: Theme) =>
createStyles({
loadingSettings: {
margin: theme.spacing(0.5)
},
loadingSettingsDetails: {
margin: theme.spacing(4),
textAlign: 'center'
}
})
);
export type WebSocketFormProps<D> = Omit<
WebSocketControllerProps<D>,
'connected'
> & { data: D };
interface WebSocketFormLoaderProps<D> extends WebSocketControllerProps<D> {
render: (props: WebSocketFormProps<D>) => JSX.Element;
}
export default function WebSocketFormLoader<D>(
props: WebSocketFormLoaderProps<D>
) {
const { connected, render, data, ...rest } = props;
const classes = useStyles();
if (!connected || !data) {
return (
<div className={classes.loadingSettings}>
<LinearProgress className={classes.loadingSettingsDetails} />
<Typography variant="h6" className={classes.loadingSettingsDetails}>
Connecting to WebSocket...
</Typography>
</div>
);
}
return render({ ...rest, data });
}

View File

@@ -1,14 +0,0 @@
import { useLayoutEffect, useState } from 'react';
export function useWindowSize() {
const [size, setSize] = useState([0, 0]);
useLayoutEffect(() => {
function updateSize() {
setSize([window.innerWidth, window.innerHeight]);
}
window.addEventListener('resize', updateSize);
updateSize();
return () => window.removeEventListener('resize', updateSize);
}, []);
return size;
}

View File

@@ -1,19 +1,8 @@
export { default as BlockFormControlLabel } from './BlockFormControlLabel';
export { default as FormActions } from './FormActions';
export { default as FormButton } from './FormButton';
export { default as HighlightAvatar } from './HighlightAvatar';
export { default as MenuAppBar } from './MenuAppBar';
export { default as PasswordValidator } from './PasswordValidator';
export { default as RestFormLoader } from './RestFormLoader';
export * from './inputs';
export * from './layout';
export * from './loading';
export * from './routing';
export * from './upload';
export { default as SectionContent } from './SectionContent';
export { default as WebSocketFormLoader } from './WebSocketFormLoader';
export { default as ErrorButton } from './ErrorButton';
export { default as SingleUpload } from './SingleUpload';
export * from './RestFormLoader';
export * from './RestController';
export * from './WebSocketFormLoader';
export * from './WebSocketController';
export * from './WindowSize';
export { default as ButtonRow } from './ButtonRow';
export { default as MessageBox } from './MessageBox';

View File

@@ -1,5 +1,5 @@
import { FC } from 'react';
import { FormControlLabel, FormControlLabelProps } from '@material-ui/core';
import { FormControlLabel, FormControlLabelProps } from '@mui/material';
const BlockFormControlLabel: FC<FormControlLabelProps> = (props) => (
<div>

View File

@@ -0,0 +1,36 @@
import { FC, useState } from 'react';
import { IconButton, InputAdornment } from '@mui/material';
import VisibilityIcon from '@mui/icons-material/Visibility';
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
import ValidatedTextField, { ValidatedTextFieldProps } from './ValidatedTextField';
type ValidatedPasswordFieldProps = Omit<ValidatedTextFieldProps, 'type'>;
const ValidatedPasswordField: FC<ValidatedPasswordFieldProps> = ({ InputProps, ...props }) => {
const [showPassword, setShowPassword] = useState<boolean>(false);
return (
<ValidatedTextField
{...props}
type={showPassword ? 'text' : 'password'}
InputProps={{
...InputProps,
endAdornment: (
<InputAdornment position="end">
<IconButton
aria-label="toggle password visibility"
onClick={() => setShowPassword(!showPassword)}
edge="end"
>
{showPassword ? <VisibilityIcon /> : <VisibilityOffIcon />}
</IconButton>
</InputAdornment>
)
}}
/>
);
};
export default ValidatedPasswordField;

View File

@@ -0,0 +1,24 @@
import { FC } from 'react';
import { ValidateFieldsError } from 'async-validator';
import { FormHelperText, TextField, TextFieldProps } from '@mui/material';
interface ValidatedFieldProps {
fieldErrors?: ValidateFieldsError;
name: string;
}
export type ValidatedTextFieldProps = ValidatedFieldProps & TextFieldProps;
const ValidatedTextField: FC<ValidatedTextFieldProps> = ({ fieldErrors, ...rest }) => {
const errors = fieldErrors && fieldErrors[rest.name];
const renderErrors = () => errors && errors.map((e, i) => <FormHelperText key={i}>{e.message}</FormHelperText>);
return (
<>
<TextField error={!!errors} {...rest} />
{renderErrors()}
</>
);
};
export default ValidatedTextField;

View File

@@ -0,0 +1,3 @@
export { default as BlockFormControlLabel } from './BlockFormControlLabel';
export { default as ValidatedPasswordField } from './ValidatedPasswordField';
export { default as ValidatedTextField } from './ValidatedTextField';

View File

@@ -0,0 +1,38 @@
import { FC, useState, useEffect } from 'react';
import { useLocation } from 'react-router-dom';
import { Box, Toolbar } from '@mui/material';
import { PROJECT_NAME } from '../../api/env';
import { RequiredChildrenProps } from '../../utils';
import LayoutDrawer from './LayoutDrawer';
import LayoutAppBar from './LayoutAppBar';
import { LayoutContext } from './context';
export const DRAWER_WIDTH = 240;
const Layout: FC<RequiredChildrenProps> = ({ children }) => {
const [mobileOpen, setMobileOpen] = useState(false);
const [title, setTitle] = useState(PROJECT_NAME);
const { pathname } = useLocation();
const handleDrawerToggle = () => {
setMobileOpen(!mobileOpen);
};
useEffect(() => setMobileOpen(false), [pathname]);
return (
<LayoutContext.Provider value={{ title, setTitle }}>
<LayoutAppBar title={title} onToggleDrawer={handleDrawerToggle} />
<LayoutDrawer mobileOpen={mobileOpen} onClose={handleDrawerToggle} />
<Box component="main" sx={{ marginLeft: { md: `${DRAWER_WIDTH}px` } }}>
<Toolbar />
{children}
</Box>
</LayoutContext.Provider>
);
};
export default Layout;

View File

@@ -0,0 +1,50 @@
import { FC, useContext } from 'react';
import { AppBar, Box, IconButton, Toolbar, Typography } from '@mui/material';
import MenuIcon from '@mui/icons-material/Menu';
import LayoutAuthMenu from './LayoutAuthMenu';
import { FeaturesContext } from '../../contexts/features';
export const DRAWER_WIDTH = 240;
interface LayoutAppBarProps {
title: string;
onToggleDrawer: () => void;
}
const LayoutAppBar: FC<LayoutAppBarProps> = ({ title, onToggleDrawer }) => {
const { features } = useContext(FeaturesContext);
return (
<AppBar
position="fixed"
sx={{
width: { md: `calc(100% - ${DRAWER_WIDTH}px)` },
ml: { md: `${DRAWER_WIDTH}px` },
boxShadow: 'none',
backgroundColor: '#2e586a'
}}
>
<Toolbar>
<IconButton
color="inherit"
aria-label="open drawer"
edge="start"
onClick={onToggleDrawer}
sx={{ mr: 2, display: { md: 'none' } }}
>
<MenuIcon />
</IconButton>
<Typography variant="h6" noWrap component="div">
{title}
</Typography>
<Box flexGrow={1} />
{features.security && <LayoutAuthMenu />}
</Toolbar>
</AppBar>
);
};
export default LayoutAppBar;

View File

@@ -0,0 +1,73 @@
import { FC, useState, useContext } from 'react';
import { Box, Button, Divider, IconButton, Popover, Typography, Avatar, styled, TypographyProps } from '@mui/material';
import PersonIcon from '@mui/icons-material/Person';
import AccountCircleIcon from '@mui/icons-material/AccountCircle';
import { AuthenticatedContext } from '../../contexts/authentication';
const ItemTypography = styled(Typography)<TypographyProps>({
maxWidth: '250px',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis'
});
const LayoutAuthMenu: FC = () => {
const { me, signOut } = useContext(AuthenticatedContext);
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
const open = Boolean(anchorEl);
const id = anchorEl ? 'app-menu-popover' : undefined;
return (
<>
<IconButton id="open-auth-menu" sx={{ padding: 0 }} aria-describedby={id} color="inherit" onClick={handleClick}>
<AccountCircleIcon />
</IconButton>
<Popover
id="app-menu-popover"
sx={{ mt: 1 }}
open={open}
anchorEl={anchorEl}
onClose={handleClose}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'center'
}}
transformOrigin={{
vertical: 'top',
horizontal: 'center'
}}
>
<Box display="flex" flexDirection="row" alignItems="center" p={2}>
<Avatar sx={{ width: 80, height: 80 }}>
<PersonIcon fontSize="large" />
</Avatar>
<Box pl={2}>
<ItemTypography variant="h6">{me.username}</ItemTypography>
<ItemTypography variant="body1">{me.admin ? 'Admin User' : 'Guest User'}</ItemTypography>
</Box>
</Box>
<Divider />
<Box p={1.5}>
<Button variant="outlined" fullWidth color="primary" onClick={() => signOut(true)}>
Sign Out
</Button>
</Box>
</Popover>
</>
);
};
export default LayoutAuthMenu;

View File

@@ -0,0 +1,73 @@
import { FC } from 'react';
import { Box, Divider, Drawer, Toolbar, Typography, styled } from '@mui/material';
import { PROJECT_NAME } from '../../api/env';
import LayoutMenu from './LayoutMenu';
import { DRAWER_WIDTH } from './Layout';
const LayoutDrawerLogo = styled('img')(({ theme }) => ({
[theme.breakpoints.down('sm')]: {
height: 24,
marginRight: theme.spacing(2)
},
[theme.breakpoints.up('sm')]: {
height: 36,
marginRight: theme.spacing(2)
}
}));
interface LayoutDrawerProps {
mobileOpen: boolean;
onClose: () => void;
}
const LayoutDrawer: FC<LayoutDrawerProps> = ({ mobileOpen, onClose }) => {
const drawer = (
<>
<Toolbar disableGutters>
<Box display="flex" alignItems="center" px={2}>
<LayoutDrawerLogo src="/app/icon.png" alt={PROJECT_NAME} />
<Typography variant="h6" color="textPrimary">
{PROJECT_NAME}
</Typography>
</Box>
<Divider absolute />
</Toolbar>
<Divider />
<LayoutMenu />
</>
);
return (
<Box component="nav" sx={{ width: { md: DRAWER_WIDTH }, flexShrink: { md: 0 } }}>
<Drawer
variant="temporary"
open={mobileOpen}
onClose={onClose}
ModalProps={{
keepMounted: true
}}
sx={{
display: { xs: 'block', md: 'none' },
'& .MuiDrawer-paper': { boxSizing: 'border-box', width: DRAWER_WIDTH }
}}
>
{drawer}
</Drawer>
<Drawer
variant="permanent"
sx={{
display: { xs: 'none', md: 'block' },
'& .MuiDrawer-paper': { boxSizing: 'border-box', width: DRAWER_WIDTH }
}}
open
>
{drawer}
</Drawer>
</Box>
);
};
export default LayoutDrawer;

View File

@@ -0,0 +1,42 @@
import { FC, useContext } from 'react';
import { Divider, List } from '@mui/material';
import SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna';
import AccessTimeIcon from '@mui/icons-material/AccessTime';
import DeviceHubIcon from '@mui/icons-material/DeviceHub';
import SettingsIcon from '@mui/icons-material/Settings';
import LockIcon from '@mui/icons-material/Lock';
import SettingsEthernetIcon from '@mui/icons-material/SettingsEthernet';
import { FeaturesContext } from '../../contexts/features';
import ProjectMenu from '../../project/ProjectMenu';
import LayoutMenuItem from './LayoutMenuItem';
import { AuthenticatedContext } from '../../contexts/authentication';
const LayoutMenu: FC = () => {
const { features } = useContext(FeaturesContext);
const authenticatedContext = useContext(AuthenticatedContext);
return (
<>
{features.project && (
<List disablePadding component="nav">
<ProjectMenu />
<Divider />
</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" />}
{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" />
</List>
</>
);
};
export default LayoutMenu;

View File

@@ -0,0 +1,32 @@
import { FC } from 'react';
import { Link, useLocation } from 'react-router-dom';
import { ListItem, ListItemButton, ListItemIcon, ListItemText, SvgIconProps } from '@mui/material';
import { grey } from '@mui/material/colors';
import { routeMatches } from '../../utils';
interface LayoutMenuItemProps {
icon: React.ComponentType<SvgIconProps>;
label: string;
to: string;
disabled?: boolean;
}
const LayoutMenuItem: FC<LayoutMenuItemProps> = ({ icon: Icon, label, to, disabled }) => {
const { pathname } = useLocation();
return (
<ListItem disablePadding selected={routeMatches(to, pathname)}>
<ListItemButton component={Link} to={to} disabled={disabled}>
<ListItemIcon sx={{ color: grey[500] }}>
<Icon />
</ListItemIcon>
<ListItemText>{label}</ListItemText>
</ListItemButton>
</ListItem>
);
};
export default LayoutMenuItem;

View File

@@ -0,0 +1,25 @@
import { useRef, useEffect, createContext, useContext } from 'react';
export interface LayoutContextValue {
title: string;
setTitle: (title: string) => void;
}
const LayoutContextDefaultValue = {} as LayoutContextValue;
export const LayoutContext = createContext(LayoutContextDefaultValue);
export const useLayoutTitle = (myTitle: string) => {
const { title, setTitle } = useContext(LayoutContext);
const previousTitle = useRef(title);
useEffect(() => {
setTitle(myTitle);
}, [setTitle, myTitle]);
useEffect(
() => () => {
setTitle(previousTitle.current);
},
[setTitle]
);
};

View File

@@ -0,0 +1,2 @@
export * from './context';
export { default as Layout } from './Layout';

View File

@@ -0,0 +1,43 @@
import { FC } from 'react';
import { Box, Paper, Typography } from '@mui/material';
import WarningIcon from '@mui/icons-material/Warning';
interface ApplicationErrorProps {
message?: string;
}
const ApplicationError: FC<ApplicationErrorProps> = ({ message }) => (
<Box display="flex" height="100vh" justifyContent="center" flexDirection="column">
<Paper
elevation={10}
sx={{
textAlign: 'center',
padding: '280px 0 40px 0',
backgroundImage: 'url("/app/icon.png")',
backgroundRepeat: 'no-repeat',
backgroundPosition: '50% 40px',
backgroundSize: '200px auto',
width: '100%',
borderRadius: 0
}}
>
<Box display="flex" flexDirection="row" justifyContent="center" alignItems="center" mb={2}>
<WarningIcon fontSize="large" color="error" />
<Box ml={2}>
<Typography variant="h4">Application Error</Typography>
</Box>
</Box>
<Typography variant="subtitle1" gutterBottom>
Failed to configure the application, please refresh to try again.
</Typography>
{message && (
<Typography variant="subtitle2" gutterBottom>
{message}
</Typography>
)}
</Paper>
</Box>
);
export default ApplicationError;

View File

@@ -0,0 +1,38 @@
import { FC } from 'react';
import { Box, Button, CircularProgress, Typography } from '@mui/material';
import RefreshIcon from '@mui/icons-material/Refresh';
import { MessageBox } from '..';
interface FormLoaderProps {
message?: string;
errorMessage?: string;
onRetry?: () => void;
}
const FormLoader: FC<FormLoaderProps> = ({ errorMessage, onRetry, message = 'Loading…' }) => {
if (errorMessage) {
return (
<MessageBox my={2} level="error" message={errorMessage}>
{onRetry && (
<Button startIcon={<RefreshIcon />} variant="contained" color="error" onClick={onRetry}>
Retry
</Button>
)}
</MessageBox>
);
}
return (
<Box m={2} py={2} display="flex" alignItems="center" flexDirection="column">
<Box py={2}>
<CircularProgress size={100} />
</Box>
<Typography variant="h6" fontWeight={400} textAlign="center">
{message}
</Typography>
</Box>
);
};
export default FormLoader;

View File

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

View File

@@ -0,0 +1,3 @@
export { default as ApplicationError } from './ApplicationError';
export { default as LoadingSpinner } from './LoadingSpinner';
export { default as FormLoader } from './FormLoader';

View File

@@ -0,0 +1,12 @@
import { FC, useContext } from 'react';
import { Navigate } from 'react-router-dom';
import { AuthenticatedContext } from '../../contexts/authentication';
import { RequiredChildrenProps } from '../../utils';
const RequireAdmin: FC<RequiredChildrenProps> = ({ children }) => {
const authenticatedContext = useContext(AuthenticatedContext);
return authenticatedContext.me.admin ? <>{children}</> : <Navigate replace to="/" />;
};
export default RequireAdmin;

View File

@@ -0,0 +1,32 @@
import { FC, useContext, useEffect } from 'react';
import { Navigate, useLocation } from 'react-router-dom';
import {
AuthenticatedContext,
AuthenticatedContextValue,
AuthenticationContext
} from '../../contexts/authentication/context';
import { storeLoginRedirect } from '../../api/authentication';
import { RequiredChildrenProps } from '../../utils';
const RequireAuthenticated: FC<RequiredChildrenProps> = ({ children }) => {
const authenticationContext = useContext(AuthenticationContext);
const location = useLocation();
useEffect(() => {
if (!authenticationContext.me) {
storeLoginRedirect(location);
}
});
return authenticationContext.me ? (
<AuthenticatedContext.Provider value={authenticationContext as AuthenticatedContextValue}>
{children}
</AuthenticatedContext.Provider>
) : (
<Navigate to="/unauthorized" />
);
};
export default RequireAuthenticated;

View File

@@ -0,0 +1,16 @@
import { FC, useContext } from 'react';
import { Navigate } from 'react-router-dom';
import * as AuthenticationApi from '../../api/authentication';
import { AuthenticationContext } from '../../contexts/authentication';
import { RequiredChildrenProps } from '../../utils';
import { FeaturesContext } from '../../contexts/features';
const RequireUnauthenticated: FC<RequiredChildrenProps> = ({ children }) => {
const { features } = useContext(FeaturesContext);
const authenticationContext = useContext(AuthenticationContext);
return authenticationContext.me ? <Navigate to={AuthenticationApi.fetchLoginRedirect(features)} /> : <>{children}</>;
};
export default RequireUnauthenticated;

View File

@@ -0,0 +1,29 @@
import React, { FC } from 'react';
import { useNavigate } from 'react-router-dom';
import { Tabs, useMediaQuery, useTheme } from '@mui/material';
import { RequiredChildrenProps } from '../../utils';
interface RouterTabsProps extends RequiredChildrenProps {
value: string | false;
}
const RouterTabs: FC<RouterTabsProps> = ({ value, children }) => {
const navigate = useNavigate();
const theme = useTheme();
const smallDown = useMediaQuery(theme.breakpoints.down('sm'));
const handleTabChange = (event: React.ChangeEvent<{}>, path: string) => {
navigate(path);
};
return (
<Tabs value={value} onChange={handleTabChange} variant={smallDown ? 'scrollable' : 'fullWidth'}>
{children}
</Tabs>
);
};
export default RouterTabs;

View File

@@ -0,0 +1,6 @@
export { default as RouterTabs } from './RouterTabs';
export { default as RequireAdmin } from './RequireAdmin';
export { default as RequireAuthenticated } from './RequireAuthenticated';
export { default as RequireUnauthenticated } from './RequireUnauthenticated';
export * from './useRouterTab';

View File

@@ -0,0 +1,9 @@
import { useMatch, useResolvedPath } from 'react-router-dom';
export const useRouterTab = () => {
const routerTabPath = useResolvedPath(':tab');
const routerTabPathMatch = useMatch(routerTabPath.pathname);
const routerTab = routerTabPathMatch?.params?.tab || false;
return { routerTab } as const;
};

View File

@@ -0,0 +1,93 @@
import { FC, Fragment } from 'react';
import { useDropzone, DropzoneState } from 'react-dropzone';
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);
const getBorderColor = (theme: Theme, props: DropzoneState) => {
if (props.isDragAccept) {
return theme.palette.success.main;
}
if (props.isDragReject) {
return theme.palette.error.main;
}
if (props.isDragActive) {
return theme.palette.info.main;
}
return theme.palette.grey[700];
};
export interface SingleUploadProps {
onDrop: (acceptedFiles: File[]) => void;
onCancel: () => void;
uploading: boolean;
progress?: ProgressEvent;
}
const SingleUpload: FC<SingleUploadProps> = ({ onDrop, onCancel, uploading, progress }) => {
const dropzoneState = useDropzone({
onDrop,
accept: {
'application/octet-stream': ['.bin'],
'application/json': ['.json']
},
disabled: uploading,
multiple: false
});
const { getRootProps, getInputProps } = dropzoneState;
const theme = useTheme();
const progressText = () => {
if (uploading) {
if (progress?.lengthComputable) {
return `Uploading: ${progressPercentage(progress)}%`;
}
return 'Uploading\u2026';
}
return 'Drop file or click here';
};
return (
<Box
{...getRootProps({
sx: {
py: 8,
px: 2,
borderWidth: 2,
borderRadius: 2,
borderStyle: 'dashed',
color: theme.palette.grey[700],
transition: 'border .24s ease-in-out',
width: '100%',
cursor: uploading ? 'default' : 'pointer',
borderColor: getBorderColor(theme, dropzoneState)
}
})}
>
<input {...getInputProps()} />
<Box flexDirection="column" display="flex" alignItems="center">
<CloudUploadIcon fontSize="large" />
<Typography variant="h6">{progressText()}</Typography>
{uploading && (
<Fragment>
<Box width="100%" p={2}>
<LinearProgress
variant={!progress || progress.lengthComputable ? 'determinate' : 'indeterminate'}
value={!progress ? 0 : progress.lengthComputable ? progressPercentage(progress) : 0}
/>
</Box>
<Button startIcon={<CancelIcon />} variant="outlined" color="secondary" onClick={onCancel}>
Cancel
</Button>
</Fragment>
)}
</Box>
</Box>
);
};
export default SingleUpload;

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