From 58ddee98f49c97f1fb696410e3a1427aa856fa64 Mon Sep 17 00:00:00 2001 From: proddy Date: Wed, 2 Oct 2024 18:53:08 +0200 Subject: [PATCH 01/66] mock dashboard data --- mock-api/api_test.http | 4 ++ mock-api/rest_server.ts | 122 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 125 insertions(+), 1 deletion(-) diff --git a/mock-api/api_test.http b/mock-api/api_test.http index 84d22baed..de23c07f9 100755 --- a/mock-api/api_test.http +++ b/mock-api/api_test.http @@ -139,6 +139,10 @@ GET {{host_standalone}}/api/system/info ### +GET {{host_standalone}}/rest/dashboardData + +### + POST {{host_standalone2}}/rest/uploadFile Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW diff --git a/mock-api/rest_server.ts b/mock-api/rest_server.ts index ee91e53f8..7a915008f 100644 --- a/mock-api/rest_server.ts +++ b/mock-api/rest_server.ts @@ -457,6 +457,8 @@ const EMSESP_DEVICEDATA_ENDPOINT2 = REST_ENDPOINT_ROOT + 'deviceData/:id?'; const EMSESP_DEVICEENTITIES_ENDPOINT1 = REST_ENDPOINT_ROOT + 'deviceEntities'; const EMSESP_DEVICEENTITIES_ENDPOINT2 = REST_ENDPOINT_ROOT + 'deviceEntities/:id?'; +const EMSESP_DASHBOARD_DATA_ENDPOINT = REST_ENDPOINT_ROOT + 'dashboardData'; + const EMSESP_BOARDPROFILE_ENDPOINT = REST_ENDPOINT_ROOT + 'boardProfile'; const EMSESP_WRITE_DEVICEVALUE_ENDPOINT = REST_ENDPOINT_ROOT + 'writeDeviceValue'; const EMSESP_WRITE_DEVICENAME_ENDPOINT = REST_ENDPOINT_ROOT + 'writeDeviceName'; @@ -805,6 +807,26 @@ const emsesp_coredata = { ] }; +const emsesp_coredata2 = { + connected: true, + // connected: false, + // devices: [] + devices: [ + { + id: 4, + t: 6, + tn: 'Thermostat', + b: 'Nefit', + n: 'Moduline 1000', + d: 16, + p: 165, + v: '04.01', + e: 3, + url: 'thermostat' + } + ] +}; + const emsesp_coredata_custom = { id: 99, t: 4, @@ -4231,6 +4253,39 @@ function deviceEntities(id: number) { return new Response(encoder.encode(emsesp_deviceentities_none), { headers }); } +// prepare dashboard data +function getDashboardEntityData(id: number) { + let device_data = {}; + if (id == 1) device_data = emsesp_devicedata_1; + else if (id == 2) device_data = emsesp_devicedata_2; + else if (id == 3) device_data = emsesp_devicedata_3; + else if (id == 4) device_data = emsesp_devicedata_4; + else if (id == 5) device_data = emsesp_devicedata_5; + else if (id == 6) device_data = emsesp_devicedata_6; + else if (id == 7) device_data = emsesp_devicedata_7; + else if (id == 8) device_data = emsesp_devicedata_8; + else if (id == 9) device_data = emsesp_devicedata_9; + else if (id == 10) device_data = emsesp_devicedata_10; + else if (id == 99) device_data = emsesp_devicedata_99; + + // filter device_data, just want id, v, u + // and only favorite items (bit 8 set), only for non-Custom Entities + // and replace id by striping off the 2-char mask + let new_data = (device_data as any).data + .map(({ id, c, m, x, s, h, l, ...rest }) => ({ + ...rest, + id2: id + })) + .filter((item) => id === 99 || parseInt(item.id2.slice(0, 2), 16) & 0x08) + .map((item) => ({ + n: item.id2.slice(2), // name + v: item.v, // value + u: item.u // uom + })); + + return new_data; +} + // Router starts here... router // EMS-ESP Settings @@ -4242,7 +4297,7 @@ router // return status(205); // restart needed }) - // Device Dashboard Data + // Device Data .get(EMSESP_CORE_DATA_ENDPOINT, () => { // sort by type, like its done in the C++ code let sorted_devices = [...emsesp_coredata.devices].sort((a, b) => a.t - b.t); @@ -4268,6 +4323,71 @@ router .get(EMSESP_DEVICEENTITIES_ENDPOINT2, ({ params }) => params.id ? deviceEntities(Number(params.id)) : status(404) ) + .get(EMSESP_DASHBOARD_DATA_ENDPOINT, () => { + // builds a JSON with id, t = typeID, tn = typeName, n=Name, data = [{n, v, u}] + let dashboard_data = []; + let dashboard_object = {}; + + // pick EMS devices from coredata + for (const element of emsesp_coredata.devices) { + const id = element.id; + + dashboard_object = { + id: id, + t: element.t, + tn: element.tn, + n: element.n, + data: getDashboardEntityData(id) + }; + + dashboard_data.push(dashboard_object); // add to dashboard_data + } + + // add the custom entity data + dashboard_object = { + id: 99, + t: 99, + tn: 'custom', + n: 'Custom Entities', + data: getDashboardEntityData(99) + }; + dashboard_data.push(dashboard_object); // add to dashboard_data + + // add temperature sensor data + let sensor_data = {}; + sensor_data = emsesp_sensordata.ts.map((item) => ({ + n: item.n ? item.n : item.id, // name may not be set + v: item.t ? item.t : undefined, // can have no value + u: item.u + })); + dashboard_object = { + id: 98, + t: 98, + tn: 'ts', + n: 'Temperature Sensors', + data: sensor_data + }; + dashboard_data.push(dashboard_object); // add to dashboard_data + + // add analog sensor data + sensor_data = emsesp_sensordata.as.map((item) => ({ + n: item.n, + v: item.v, + u: item.u + })); + dashboard_object = { + id: 97, + t: 97, + tn: 'as', + n: 'Analog Sensors', + data: sensor_data + }; + dashboard_data.push(dashboard_object); // add to dashboard_data + + console.log('dashboard_data: ', dashboard_data); + + return new Response(encoder.encode(dashboard_data), { headers }); // msgpack it + }) // Customizations .post(EMSESP_CUSTOMIZATION_ENTITIES_ENDPOINT, async (request: any) => { From fa4281cc6338c7a31c7838d36b0faf7168278a6c Mon Sep 17 00:00:00 2001 From: proddy Date: Wed, 2 Oct 2024 18:53:23 +0200 Subject: [PATCH 02/66] package update --- interface/package.json | 8 +- interface/yarn.lock | 301 ++++++++++++++++++++++++++++++++++------- 2 files changed, 259 insertions(+), 50 deletions(-) diff --git a/interface/package.json b/interface/package.json index f10624f8a..2b2351c04 100644 --- a/interface/package.json +++ b/interface/package.json @@ -24,8 +24,8 @@ "@alova/adapter-xhr": "2.0.7", "@emotion/react": "^11.13.3", "@emotion/styled": "^11.13.0", - "@mui/icons-material": "^6.1.1", - "@mui/material": "^6.1.1", + "@mui/icons-material": "^6.1.2", + "@mui/material": "^6.1.2", "@table-library/react-table-library": "4.1.7", "alova": "3.0.17", "async-validator": "^4.2.5", @@ -41,14 +41,14 @@ "typescript": "^5.6.2" }, "devDependencies": { - "@babel/core": "^7.25.2", + "@babel/core": "^7.25.7", "@eslint/js": "^9.11.1", "@preact/compat": "^18.3.1", "@preact/preset-vite": "^2.9.1", "@trivago/prettier-plugin-sort-imports": "^4.3.0", "@types/formidable": "^3", "@types/node": "^22.7.4", - "@types/react": "^18.3.10", + "@types/react": "^18.3.11", "@types/react-dom": "^18.3.0", "@types/react-router-dom": "^5.3.3", "concurrently": "^9.0.1", diff --git a/interface/yarn.lock b/interface/yarn.lock index 2948ca9ed..a2062e721 100644 --- a/interface/yarn.lock +++ b/interface/yarn.lock @@ -43,6 +43,16 @@ __metadata: languageName: node linkType: hard +"@babel/code-frame@npm:^7.25.7": + version: 7.25.7 + resolution: "@babel/code-frame@npm:7.25.7" + dependencies: + "@babel/highlight": "npm:^7.25.7" + picocolors: "npm:^1.0.0" + checksum: 10c0/14825c298bdec914caf3d24d1383b6d4cd6b030714686004992f4fc251831ecf432236652896f99d5d341f17170ae9a07b58d8d7b15aa0df8cfa1c5a7d5474bc + languageName: node + linkType: hard + "@babel/compat-data@npm:^7.25.2": version: 7.25.4 resolution: "@babel/compat-data@npm:7.25.4" @@ -50,7 +60,14 @@ __metadata: languageName: node linkType: hard -"@babel/core@npm:^7.22.1, @babel/core@npm:^7.25.2": +"@babel/compat-data@npm:^7.25.7": + version: 7.25.7 + resolution: "@babel/compat-data@npm:7.25.7" + checksum: 10c0/e5cc915abdd18d021236474a96606b2d4a915c4fb620c1ad776b8a08d91111e788cb3b7e9bad43593d4e0bfa4f06894357bcb0984102de1861b9e7322b6bc9f8 + languageName: node + linkType: hard + +"@babel/core@npm:^7.22.1": version: 7.25.2 resolution: "@babel/core@npm:7.25.2" dependencies: @@ -73,6 +90,29 @@ __metadata: languageName: node linkType: hard +"@babel/core@npm:^7.25.7": + version: 7.25.7 + resolution: "@babel/core@npm:7.25.7" + dependencies: + "@ampproject/remapping": "npm:^2.2.0" + "@babel/code-frame": "npm:^7.25.7" + "@babel/generator": "npm:^7.25.7" + "@babel/helper-compilation-targets": "npm:^7.25.7" + "@babel/helper-module-transforms": "npm:^7.25.7" + "@babel/helpers": "npm:^7.25.7" + "@babel/parser": "npm:^7.25.7" + "@babel/template": "npm:^7.25.7" + "@babel/traverse": "npm:^7.25.7" + "@babel/types": "npm:^7.25.7" + convert-source-map: "npm:^2.0.0" + debug: "npm:^4.1.0" + gensync: "npm:^1.0.0-beta.2" + json5: "npm:^2.2.3" + semver: "npm:^6.3.1" + checksum: 10c0/dad20af39624086afc3a0910bd97ae712c9ad0e9dda09fc5da93876e8ea1802b63ddd81c44f4aa8a9834db46de801eaab1ce9b81ab54b4fe907ae052c24de136 + languageName: node + linkType: hard + "@babel/generator@npm:7.17.7": version: 7.17.7 resolution: "@babel/generator@npm:7.17.7" @@ -96,6 +136,18 @@ __metadata: languageName: node linkType: hard +"@babel/generator@npm:^7.25.7": + version: 7.25.7 + resolution: "@babel/generator@npm:7.25.7" + dependencies: + "@babel/types": "npm:^7.25.7" + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.25" + jsesc: "npm:^3.0.2" + checksum: 10c0/c03a26c79864d60d04ce36b649c3fa0d6fd7b2bf6a22e22854a0457aa09206508392dd73ee40e7bc8d50b3602f9ff068afa47770cda091d332e7db1ca382ee96 + languageName: node + linkType: hard + "@babel/helper-annotate-as-pure@npm:^7.24.7": version: 7.24.7 resolution: "@babel/helper-annotate-as-pure@npm:7.24.7" @@ -118,6 +170,19 @@ __metadata: languageName: node linkType: hard +"@babel/helper-compilation-targets@npm:^7.25.7": + version: 7.25.7 + resolution: "@babel/helper-compilation-targets@npm:7.25.7" + dependencies: + "@babel/compat-data": "npm:^7.25.7" + "@babel/helper-validator-option": "npm:^7.25.7" + browserslist: "npm:^4.24.0" + lru-cache: "npm:^5.1.1" + semver: "npm:^6.3.1" + checksum: 10c0/705be7e5274a3fdade68e3e2cf42e2b600316ab52794e13b91299a16f16c926f15886b6e9d6df20eb943ccc1cdba5a363d4766f8d01e47b8e6f4e01175f5e66c + languageName: node + linkType: hard + "@babel/helper-environment-visitor@npm:^7.22.20": version: 7.24.7 resolution: "@babel/helper-environment-visitor@npm:7.24.7" @@ -156,6 +221,16 @@ __metadata: languageName: node linkType: hard +"@babel/helper-module-imports@npm:^7.25.7": + version: 7.25.7 + resolution: "@babel/helper-module-imports@npm:7.25.7" + dependencies: + "@babel/traverse": "npm:^7.25.7" + "@babel/types": "npm:^7.25.7" + checksum: 10c0/0fd0c3673835e5bf75558e184bcadc47c1f6dd2fe2016d53ebe1e5a6ae931a44e093015c2f9a6651c1a89f25c76d9246710c2b0b460b95ee069c464f2837fa2c + languageName: node + linkType: hard + "@babel/helper-module-transforms@npm:^7.25.2": version: 7.25.2 resolution: "@babel/helper-module-transforms@npm:7.25.2" @@ -170,6 +245,20 @@ __metadata: languageName: node linkType: hard +"@babel/helper-module-transforms@npm:^7.25.7": + version: 7.25.7 + resolution: "@babel/helper-module-transforms@npm:7.25.7" + dependencies: + "@babel/helper-module-imports": "npm:^7.25.7" + "@babel/helper-simple-access": "npm:^7.25.7" + "@babel/helper-validator-identifier": "npm:^7.25.7" + "@babel/traverse": "npm:^7.25.7" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/f37fa7d1d4df21690535b278468cbd5faf0133a3080f282000cfa4f3ffc9462a1458f866b04b6a2f2d1eec4691236cba9a867da61270dab3ab19846e62f05090 + languageName: node + linkType: hard + "@babel/helper-plugin-utils@npm:^7.24.7, @babel/helper-plugin-utils@npm:^7.24.8": version: 7.24.8 resolution: "@babel/helper-plugin-utils@npm:7.24.8" @@ -187,6 +276,16 @@ __metadata: languageName: node linkType: hard +"@babel/helper-simple-access@npm:^7.25.7": + version: 7.25.7 + resolution: "@babel/helper-simple-access@npm:7.25.7" + dependencies: + "@babel/traverse": "npm:^7.25.7" + "@babel/types": "npm:^7.25.7" + checksum: 10c0/eed1b499bfb4f613c18debd61517e3de77b6da2727ca025aa05ac81599e0269f1dddb5237db04e8bb598115d015874752e0a7f11ff38672d74a4976097417059 + languageName: node + linkType: hard + "@babel/helper-split-export-declaration@npm:^7.22.6": version: 7.24.7 resolution: "@babel/helper-split-export-declaration@npm:7.24.7" @@ -203,6 +302,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-string-parser@npm:^7.25.7": + version: 7.25.7 + resolution: "@babel/helper-string-parser@npm:7.25.7" + checksum: 10c0/73ef2ceb81f8294678a0afe8ab0103729c0370cac2e830e0d5128b03be5f6a2635838af31d391d763e3c5a4460ed96f42fd7c9b552130670d525be665913bc4c + languageName: node + linkType: hard + "@babel/helper-validator-identifier@npm:^7.16.7, @babel/helper-validator-identifier@npm:^7.24.7": version: 7.24.7 resolution: "@babel/helper-validator-identifier@npm:7.24.7" @@ -210,6 +316,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-validator-identifier@npm:^7.25.7": + version: 7.25.7 + resolution: "@babel/helper-validator-identifier@npm:7.25.7" + checksum: 10c0/07438e5bf01ab2882a15027fdf39ac3b0ba1b251774a5130917907014684e2f70fef8fd620137ca062c4c4eedc388508d2ea7a3a7d9936a32785f4fe116c68c0 + languageName: node + linkType: hard + "@babel/helper-validator-option@npm:^7.24.8": version: 7.24.8 resolution: "@babel/helper-validator-option@npm:7.24.8" @@ -217,6 +330,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-validator-option@npm:^7.25.7": + version: 7.25.7 + resolution: "@babel/helper-validator-option@npm:7.25.7" + checksum: 10c0/12ed418c8e3ed9ed44c8c80d823f4e42d399b5eb2e423adccb975e31a31a008cd3b5d8eab688b31f740caff4a1bb28fe06ea2fa7d635aee34cc0ad6995d50f0a + languageName: node + linkType: hard + "@babel/helpers@npm:^7.25.0": version: 7.25.6 resolution: "@babel/helpers@npm:7.25.6" @@ -227,6 +347,16 @@ __metadata: languageName: node linkType: hard +"@babel/helpers@npm:^7.25.7": + version: 7.25.7 + resolution: "@babel/helpers@npm:7.25.7" + dependencies: + "@babel/template": "npm:^7.25.7" + "@babel/types": "npm:^7.25.7" + checksum: 10c0/3b3ae9e373bd785414195ef8f59976a69d5a6ebe0ef2165fdcc5165e5c3ee09e0fcee94bb457df2ddb8c0532e4146d0a9b7a96b3497399a4bff4ffe196b30228 + languageName: node + linkType: hard + "@babel/highlight@npm:^7.24.7": version: 7.24.7 resolution: "@babel/highlight@npm:7.24.7" @@ -239,6 +369,18 @@ __metadata: languageName: node linkType: hard +"@babel/highlight@npm:^7.25.7": + version: 7.25.7 + resolution: "@babel/highlight@npm:7.25.7" + dependencies: + "@babel/helper-validator-identifier": "npm:^7.25.7" + chalk: "npm:^2.4.2" + js-tokens: "npm:^4.0.0" + picocolors: "npm:^1.0.0" + checksum: 10c0/1f5894fdb0a0af6101fb2822369b2eeeae32cbeae2ef73ff73fc6a0a4a20471565cd9cfa589f54ed69df66adeca7c57266031ca9134b7bd244d023a488d419aa + languageName: node + linkType: hard + "@babel/parser@npm:^7.20.5, @babel/parser@npm:^7.23.0, @babel/parser@npm:^7.25.0, @babel/parser@npm:^7.25.6": version: 7.25.6 resolution: "@babel/parser@npm:7.25.6" @@ -250,6 +392,17 @@ __metadata: languageName: node linkType: hard +"@babel/parser@npm:^7.25.7": + version: 7.25.7 + resolution: "@babel/parser@npm:7.25.7" + dependencies: + "@babel/types": "npm:^7.25.7" + bin: + parser: ./bin/babel-parser.js + checksum: 10c0/b771469bb6b636c18a8d642b9df3c73913c3860a979591e1a29a98659efd38b81d3e393047b5251fe382d4c82c681c12da9ce91c98d69316d2604d155a214bcf + languageName: node + linkType: hard + "@babel/plugin-syntax-jsx@npm:^7.24.7": version: 7.24.7 resolution: "@babel/plugin-syntax-jsx@npm:7.24.7" @@ -307,6 +460,17 @@ __metadata: languageName: node linkType: hard +"@babel/template@npm:^7.25.7": + version: 7.25.7 + resolution: "@babel/template@npm:7.25.7" + dependencies: + "@babel/code-frame": "npm:^7.25.7" + "@babel/parser": "npm:^7.25.7" + "@babel/types": "npm:^7.25.7" + checksum: 10c0/8ae9e36e4330ee83d4832531d1d9bec7dc2ef6a2a8afa1ef1229506fd60667abcb17f306d1c3d7e582251270597022990c845d5d69e7add70a5aea66720decb9 + languageName: node + linkType: hard + "@babel/traverse@npm:7.23.2": version: 7.23.2 resolution: "@babel/traverse@npm:7.23.2" @@ -340,6 +504,21 @@ __metadata: languageName: node linkType: hard +"@babel/traverse@npm:^7.25.7": + version: 7.25.7 + resolution: "@babel/traverse@npm:7.25.7" + dependencies: + "@babel/code-frame": "npm:^7.25.7" + "@babel/generator": "npm:^7.25.7" + "@babel/parser": "npm:^7.25.7" + "@babel/template": "npm:^7.25.7" + "@babel/types": "npm:^7.25.7" + debug: "npm:^4.3.1" + globals: "npm:^11.1.0" + checksum: 10c0/75d73e52c507a7a7a4c7971d6bf4f8f26fdd094e0d3a0193d77edf6a5efa36fc3db91ec5cc48e8b94e6eb5d5ad21af0a1040e71309172851209415fd105efb1a + languageName: node + linkType: hard + "@babel/types@npm:7.17.0": version: 7.17.0 resolution: "@babel/types@npm:7.17.0" @@ -361,6 +540,17 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.25.7": + version: 7.25.7 + resolution: "@babel/types@npm:7.25.7" + dependencies: + "@babel/helper-string-parser": "npm:^7.25.7" + "@babel/helper-validator-identifier": "npm:^7.25.7" + to-fast-properties: "npm:^2.0.0" + checksum: 10c0/e03e1e2e08600fa1e8eb90632ac9c253dd748176c8d670d85f85b0dc83a0573b26ae748a1cbcb81f401903a3d95f43c3f4f8d516a5ed779929db27de56289633 + languageName: node + linkType: hard + "@emotion/babel-plugin@npm:^11.12.0": version: 11.12.0 resolution: "@emotion/babel-plugin@npm:11.12.0" @@ -831,38 +1021,38 @@ __metadata: languageName: node linkType: hard -"@mui/core-downloads-tracker@npm:^6.1.1": - version: 6.1.1 - resolution: "@mui/core-downloads-tracker@npm:6.1.1" - checksum: 10c0/ca837b75b8d2147a5a67785725b1158eff1240dbca65effbc9c4387fc7c342416bff332b0678378292738aa1b0224b866979c623a5da426904a43df394782286 +"@mui/core-downloads-tracker@npm:^6.1.2": + version: 6.1.2 + resolution: "@mui/core-downloads-tracker@npm:6.1.2" + checksum: 10c0/a685ac90a614be07c07bba752a6772be1caf06329e278c731f2a60b7712381c467adb3f5e0cfe7f2bc51b744ba76138f451853ff767c4739fa6379c8bc49d407 languageName: node linkType: hard -"@mui/icons-material@npm:^6.1.1": - version: 6.1.1 - resolution: "@mui/icons-material@npm:6.1.1" +"@mui/icons-material@npm:^6.1.2": + version: 6.1.2 + resolution: "@mui/icons-material@npm:6.1.2" dependencies: "@babel/runtime": "npm:^7.25.6" peerDependencies: - "@mui/material": ^6.1.1 + "@mui/material": ^6.1.2 "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 react: ^17.0.0 || ^18.0.0 || ^19.0.0 peerDependenciesMeta: "@types/react": optional: true - checksum: 10c0/fc9f468ebf7ee9238606c915b648a4ba461d1ff241926131cd0de173d9092d8be6b2dc84a9d1bd8abcc6a349793e5501bd9d90487af7e946395a9b5ff104d498 + checksum: 10c0/ae5f1134e3ac45914c2bfef76a31848a8dc655237a9e478959580f778c31d77e6e54a588ec0ec552d8c6a904298f02d7cf482a5d6df294389d9ea9da8109ef94 languageName: node linkType: hard -"@mui/material@npm:^6.1.1": - version: 6.1.1 - resolution: "@mui/material@npm:6.1.1" +"@mui/material@npm:^6.1.2": + version: 6.1.2 + resolution: "@mui/material@npm:6.1.2" dependencies: "@babel/runtime": "npm:^7.25.6" - "@mui/core-downloads-tracker": "npm:^6.1.1" - "@mui/system": "npm:^6.1.1" + "@mui/core-downloads-tracker": "npm:^6.1.2" + "@mui/system": "npm:^6.1.2" "@mui/types": "npm:^7.2.17" - "@mui/utils": "npm:^6.1.1" + "@mui/utils": "npm:^6.1.2" "@popperjs/core": "npm:^2.11.8" "@types/react-transition-group": "npm:^4.4.11" clsx: "npm:^2.1.1" @@ -873,7 +1063,7 @@ __metadata: peerDependencies: "@emotion/react": ^11.5.0 "@emotion/styled": ^11.3.0 - "@mui/material-pigment-css": ^6.1.1 + "@mui/material-pigment-css": ^6.1.2 "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 react: ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -886,16 +1076,16 @@ __metadata: optional: true "@types/react": optional: true - checksum: 10c0/ee47e8ac90db10e104fb3176512f5dd762e4cbf534d6377a0b515d738544eb56f3d4c8c54a1ddc029526b56443fc57ed3637164424245a1f3214d8ba4ff4b5eb + checksum: 10c0/7825be6ec47f82b3b3469fd0724b818bcc488f9b42de275fed38de314c216dfcefeec1cd23a0e8f8e6355ca51fbb2554ee4011834d6188a9245a8d60e7be2398 languageName: node linkType: hard -"@mui/private-theming@npm:^6.1.1": - version: 6.1.1 - resolution: "@mui/private-theming@npm:6.1.1" +"@mui/private-theming@npm:^6.1.2": + version: 6.1.2 + resolution: "@mui/private-theming@npm:6.1.2" dependencies: "@babel/runtime": "npm:^7.25.6" - "@mui/utils": "npm:^6.1.1" + "@mui/utils": "npm:^6.1.2" prop-types: "npm:^15.8.1" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -903,13 +1093,13 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: 10c0/a4374cb55f6ca1959aa4cb38c28ace2d8e1e73f97cd0eb3c383a0de977df4a5e2966c5f0ab4d04c596ebf9c224aa196301162f3d1e72a43e20ae6cd06dadfc05 + checksum: 10c0/b8f8ee447dde7ecd34b9b96c19d68cff6771a7d5a133b85c3ff2dcb19fc8f2375375de6695c439dcfb6cb94e28d0a7c0889e132bfa467700a883e0dd834e8080 languageName: node linkType: hard -"@mui/styled-engine@npm:^6.1.1": - version: 6.1.1 - resolution: "@mui/styled-engine@npm:6.1.1" +"@mui/styled-engine@npm:^6.1.2": + version: 6.1.2 + resolution: "@mui/styled-engine@npm:6.1.2" dependencies: "@babel/runtime": "npm:^7.25.6" "@emotion/cache": "npm:^11.13.1" @@ -925,19 +1115,19 @@ __metadata: optional: true "@emotion/styled": optional: true - checksum: 10c0/5711ca945b9f93845df477da0a82107e8426d7e4a893d464ab968cbdfc6660960056c785c97a3752d1d6829c264bac78754b5585e4ab8a0e3e1738c505fa8770 + checksum: 10c0/cad5a768d039a8e6652c5f33371ff68e82afedec75f340a0c4154ca4bf29877f23e89c041b0cbdad7d81c4fe4e356882d0de3d24157a05323a88e9703ed85d30 languageName: node linkType: hard -"@mui/system@npm:^6.1.1": - version: 6.1.1 - resolution: "@mui/system@npm:6.1.1" +"@mui/system@npm:^6.1.2": + version: 6.1.2 + resolution: "@mui/system@npm:6.1.2" dependencies: "@babel/runtime": "npm:^7.25.6" - "@mui/private-theming": "npm:^6.1.1" - "@mui/styled-engine": "npm:^6.1.1" + "@mui/private-theming": "npm:^6.1.2" + "@mui/styled-engine": "npm:^6.1.2" "@mui/types": "npm:^7.2.17" - "@mui/utils": "npm:^6.1.1" + "@mui/utils": "npm:^6.1.2" clsx: "npm:^2.1.1" csstype: "npm:^3.1.3" prop-types: "npm:^15.8.1" @@ -953,7 +1143,7 @@ __metadata: optional: true "@types/react": optional: true - checksum: 10c0/9c7d63b369e4885066f23beac927dc5c8aad78bca32e66044744cb04320fdcd4fe2136de92135ae740b3ba8f369bcd3e75ca95fa7617d3021fd0d358b8aade9f + checksum: 10c0/b9240574f5db5a79d1ad1fc5dead2fc2b0dcaee2bc5515e6b8ea88cddd374f58adb18611fee65ae38ef165434712de425a6a5fc4d8d717d9b4f5a3716e2c3c46 languageName: node linkType: hard @@ -969,13 +1159,13 @@ __metadata: languageName: node linkType: hard -"@mui/utils@npm:^6.1.1": - version: 6.1.1 - resolution: "@mui/utils@npm:6.1.1" +"@mui/utils@npm:^6.1.2": + version: 6.1.2 + resolution: "@mui/utils@npm:6.1.2" dependencies: "@babel/runtime": "npm:^7.25.6" "@mui/types": "npm:^7.2.17" - "@types/prop-types": "npm:^15.7.12" + "@types/prop-types": "npm:^15.7.13" clsx: "npm:^2.1.1" prop-types: "npm:^15.8.1" react-is: "npm:^18.3.1" @@ -985,7 +1175,7 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: 10c0/4278d1810b6bf9cb366201414885cdcbfcee383030efc90444d5446a9bcc28ec5c5b299ca2a2b0280721f6d51116f29142119cc5c9c4ec86aafd9d5329aa01a6 + checksum: 10c0/ed56688610f3c04ad219066b5bd94b16c2c1318e2fc4d49e6d6d7b3800d10b4751185cbcc88431a3a1070a236f61e41238c06ef2998a3d65724ac6245a8b64e6 languageName: node linkType: hard @@ -1446,7 +1636,7 @@ __metadata: languageName: node linkType: hard -"@types/prop-types@npm:*, @types/prop-types@npm:^15.7.12": +"@types/prop-types@npm:*, @types/prop-types@npm:^15.7.13": version: 15.7.13 resolution: "@types/prop-types@npm:15.7.13" checksum: 10c0/1b20fc67281902c6743379960247bc161f3f0406ffc0df8e7058745a85ea1538612109db0406290512947f9632fe9e10e7337bf0ce6338a91d6c948df16a7c61 @@ -1492,7 +1682,7 @@ __metadata: languageName: node linkType: hard -"@types/react@npm:*, @types/react@npm:^18.3.10": +"@types/react@npm:*": version: 18.3.10 resolution: "@types/react@npm:18.3.10" dependencies: @@ -1502,6 +1692,16 @@ __metadata: languageName: node linkType: hard +"@types/react@npm:^18.3.11": + version: 18.3.11 + resolution: "@types/react@npm:18.3.11" + dependencies: + "@types/prop-types": "npm:*" + csstype: "npm:^3.0.2" + checksum: 10c0/ce80512246ca5bda69db85b9f4f1835189334acfb6b2c4f3eda8cabff1ff1a3ea9ce4f3b895bdbc18c94140aa45592331aa3fdeb557f525c1b048de7ce84fc0e + languageName: node + linkType: hard + "@types/responselike@npm:^1.0.0": version: 1.0.3 resolution: "@types/responselike@npm:1.0.3" @@ -1641,19 +1841,19 @@ __metadata: resolution: "EMS-ESP@workspace:." dependencies: "@alova/adapter-xhr": "npm:2.0.7" - "@babel/core": "npm:^7.25.2" + "@babel/core": "npm:^7.25.7" "@emotion/react": "npm:^11.13.3" "@emotion/styled": "npm:^11.13.0" "@eslint/js": "npm:^9.11.1" - "@mui/icons-material": "npm:^6.1.1" - "@mui/material": "npm:^6.1.1" + "@mui/icons-material": "npm:^6.1.2" + "@mui/material": "npm:^6.1.2" "@preact/compat": "npm:^18.3.1" "@preact/preset-vite": "npm:^2.9.1" "@table-library/react-table-library": "npm:4.1.7" "@trivago/prettier-plugin-sort-imports": "npm:^4.3.0" "@types/formidable": "npm:^3" "@types/node": "npm:^22.7.4" - "@types/react": "npm:^18.3.10" + "@types/react": "npm:^18.3.11" "@types/react-dom": "npm:^18.3.0" "@types/react-router-dom": "npm:^5.3.3" alova: "npm:3.0.17" @@ -1989,7 +2189,7 @@ __metadata: languageName: node linkType: hard -"browserslist@npm:^4.23.1": +"browserslist@npm:^4.23.1, browserslist@npm:^4.24.0": version: 4.24.0 resolution: "browserslist@npm:4.24.0" dependencies: @@ -4529,6 +4729,15 @@ __metadata: languageName: node linkType: hard +"jsesc@npm:^3.0.2": + version: 3.0.2 + resolution: "jsesc@npm:3.0.2" + bin: + jsesc: bin/jsesc + checksum: 10c0/ef22148f9e793180b14d8a145ee6f9f60f301abf443288117b4b6c53d0ecd58354898dc506ccbb553a5f7827965cd38bc5fb726575aae93c5e8915e2de8290e1 + languageName: node + linkType: hard + "json-buffer@npm:3.0.0": version: 3.0.0 resolution: "json-buffer@npm:3.0.0" From f9f87ddc0e0e35283253809deb41d51b81e7e9ea Mon Sep 17 00:00:00 2001 From: proddy Date: Wed, 2 Oct 2024 18:53:36 +0200 Subject: [PATCH 03/66] add comment --- src/emsdevice.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/emsdevice.cpp b/src/emsdevice.cpp index 9fda392c5..545f2b470 100644 --- a/src/emsdevice.cpp +++ b/src/emsdevice.cpp @@ -1022,7 +1022,7 @@ void EMSdevice::generate_values_web(JsonObject output) { } // as generate_values_web() but stripped down to only show all entities and their state -// this is used only for WebCustomizationService::device_entities() +// this is used only for WebCustomizationService::device_entities() (rest/deviceEntities?id=n) void EMSdevice::generate_values_web_customization(JsonArray output) { for (auto & dv : devicevalues_) { // also show commands and entities that have an empty fullname From fb0d9454ef18d9db6a6ed9cb69bbaf29f28e7c8e Mon Sep 17 00:00:00 2001 From: proddy Date: Thu, 3 Oct 2024 18:06:46 +0200 Subject: [PATCH 04/66] dashboard v.01 --- interface/src/AuthenticatedRouting.tsx | 2 + interface/src/api/app.ts | 9 +- interface/src/app/main/Dashboard.tsx | 193 ++++++++++++++++++ interface/src/app/main/types.ts | 13 ++ .../src/components/layout/LayoutMenu.tsx | 2 + .../src/components/routing/authentication.ts | 2 +- mock-api/rest_server.ts | 48 +++-- 7 files changed, 246 insertions(+), 23 deletions(-) create mode 100644 interface/src/app/main/Dashboard.tsx diff --git a/interface/src/AuthenticatedRouting.tsx b/interface/src/AuthenticatedRouting.tsx index 3942e428b..adf39beb1 100644 --- a/interface/src/AuthenticatedRouting.tsx +++ b/interface/src/AuthenticatedRouting.tsx @@ -3,6 +3,7 @@ import { Navigate, Route, Routes } from 'react-router-dom'; import CustomEntities from 'app/main/CustomEntities'; import Customizations from 'app/main/Customizations'; +import Dashboard from 'app/main/Dashboard'; import Devices from 'app/main/Devices'; import Help from 'app/main/Help'; import Modules from 'app/main/Modules'; @@ -32,6 +33,7 @@ const AuthenticatedRouting = () => { return ( + } /> } /> } /> } /> diff --git a/interface/src/api/app.ts b/interface/src/api/app.ts index a856d2096..a56f4db53 100644 --- a/interface/src/api/app.ts +++ b/interface/src/api/app.ts @@ -5,6 +5,7 @@ import type { Action, Activity, CoreData, + DashboardData, DeviceData, DeviceEntity, Entities, @@ -19,7 +20,13 @@ import type { WriteTemperatureSensor } from '../app/main/types'; -// DashboardDevices +// Dashboard +export const readDashboard = () => + alovaInstance.Get('/rest/dashboardData', { + responseType: 'arraybuffer' // uses msgpack + }); + +// Devices export const readCoreData = () => alovaInstance.Get(`/rest/coreData`); export const readDeviceData = (id: number) => alovaInstance.Get('/rest/deviceData', { diff --git a/interface/src/app/main/Dashboard.tsx b/interface/src/app/main/Dashboard.tsx new file mode 100644 index 000000000..cc9dff0bf --- /dev/null +++ b/interface/src/app/main/Dashboard.tsx @@ -0,0 +1,193 @@ +import { useEffect, useState } from 'react'; + +import ChevronRightIcon from '@mui/icons-material/ChevronRight'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import UnfoldLessIcon from '@mui/icons-material/UnfoldLess'; +import UnfoldMoreIcon from '@mui/icons-material/UnfoldMore'; +import { Box, ToggleButton, ToggleButtonGroup, Typography } from '@mui/material'; + +import { + Body, + Cell, + Header, + HeaderCell, + HeaderRow, + Row, + Table +} from '@table-library/react-table-library/table'; +import { useTheme } from '@table-library/react-table-library/theme'; +import { CellTree, useTree } from '@table-library/react-table-library/tree'; +import { useAutoRequest } from 'alova/client'; +import { FormLoader, SectionContent, useLayoutTitle } from 'components'; +import { useI18nContext } from 'i18n/i18n-react'; + +import { readDashboard } from '../../api/app'; +import { formatValue } from './deviceValue'; +import type { DashboardItem } from './types'; + +const Dashboard = () => { + const { LL } = useI18nContext(); + + useLayoutTitle('Dashboard'); // TODO translate + + const [firstLoad, setFirstLoad] = useState(true); + const [showAll, setShowAll] = useState(true); + + const { + data, + send: fetchDashboard, + error + } = useAutoRequest(readDashboard, { + initialData: [], + pollingTime: 1500 + }); + + const dashboard_theme = useTheme({ + Table: ` + --data-table-library_grid-template-columns: minmax(80px, auto) 120px; + `, + BaseRow: ` + font-size: 14px; + .td { + height: 32px; + } + `, + HeaderRow: ` + text-transform: uppercase; + background-color: black; + color: #90CAF9; + .th { + border-bottom: 1px solid #565656; + height: 36px; + } + `, + Row: ` + background-color: #1e1e1e; + position: relative; + cursor: pointer; + .td { + height: 24px; + } + &:hover .td { + border-top: 1px solid #177ac9; + border-bottom: 1px solid #177ac9; + } + ` + }); + + function onTreeChange(action, state) { + // do nothing for now + } + + const tree = useTree( + { nodes: data }, + { + onChange: onTreeChange + }, + { + treeIcon: { + margin: '4px', + iconDefault: null, + iconRight: , + iconDown: + }, + indentation: 28 + } + ); + + // auto expand on first load + useEffect(() => { + if (data.length && firstLoad && !tree.state.ids.length) { + tree.fns.onToggleAll({}); + setFirstLoad(false); + } + }, [data]); + + const showName = (di: DashboardItem) => { + if (di.id < 100) { + if (di.nodes?.length) { + return ( +
+ {di.n} ({di.nodes?.length}) +
+ ); + } + return
{di.n}
; + } + + return
{di.n}
; + }; + + const handleShowAll = ( + event: React.MouseEvent, + toggle: boolean | null + ) => { + if (toggle !== null) { + tree.fns.onToggleAll({}); + setShowAll(toggle); + } + }; + + const renderContent = () => { + if (!data) { + return ; + } + + return ( + <> + + + Use Customizations to mark your favorite EMS entities + + + + + + + + + + + + {(tableList: DashboardItem[]) => ( + <> +
+ + Name + Value + +
+ + {tableList.map((di: DashboardItem) => ( + + {di.nodes?.length === 0 ? ( + {showName(di)} + ) : ( + {showName(di)} + )} + {formatValue(LL, di.v, di.u)} + + ))} + + + )} +
+ + ); + }; + + return {renderContent()}; +}; + +export default Dashboard; diff --git a/interface/src/app/main/types.ts b/interface/src/app/main/types.ts index 3681f8e09..30efb7e59 100644 --- a/interface/src/app/main/types.ts +++ b/interface/src/app/main/types.ts @@ -114,6 +114,18 @@ export interface CoreData { devices: Device[]; } +export interface DashboardItem { + id: number; // unique index + n: string; // name + v?: unknown; // value + u: number; // uom + nodes?: DashboardItem[]; // nodes +} + +export interface DashboardData { + data: DashboardItem[]; +} + export interface DeviceValue { id: string; // index, contains mask+name v: unknown; // value, Number or String @@ -125,6 +137,7 @@ export interface DeviceValue { m?: number; // min, optional x?: number; // max, optional } + export interface DeviceData { data: DeviceValue[]; } diff --git a/interface/src/components/layout/LayoutMenu.tsx b/interface/src/components/layout/LayoutMenu.tsx index 4b9051d03..1817abc2e 100644 --- a/interface/src/components/layout/LayoutMenu.tsx +++ b/interface/src/components/layout/LayoutMenu.tsx @@ -11,6 +11,7 @@ import PersonIcon from '@mui/icons-material/Person'; import PlaylistAddIcon from '@mui/icons-material/PlaylistAdd'; import SensorsIcon from '@mui/icons-material/Sensors'; import SettingsIcon from '@mui/icons-material/Settings'; +import StarIcon from '@mui/icons-material/Star'; import { Avatar, Box, @@ -51,6 +52,7 @@ const LayoutMenu = () => { return ( <> + diff --git a/interface/src/components/routing/authentication.ts b/interface/src/components/routing/authentication.ts index 822417b88..ae93fa88e 100644 --- a/interface/src/components/routing/authentication.ts +++ b/interface/src/components/routing/authentication.ts @@ -35,7 +35,7 @@ export function fetchLoginRedirect(): Partial { const signInSearch = getStorage().getItem(SIGN_IN_SEARCH); clearLoginRedirect(); return { - pathname: signInPathname || `/devices`, + pathname: signInPathname || `/dashboard`, search: (signInPathname && signInSearch) || undefined }; } diff --git a/mock-api/rest_server.ts b/mock-api/rest_server.ts index 7a915008f..b57af9db4 100644 --- a/mock-api/rest_server.ts +++ b/mock-api/rest_server.ts @@ -4277,7 +4277,8 @@ function getDashboardEntityData(id: number) { id2: id })) .filter((item) => id === 99 || parseInt(item.id2.slice(0, 2), 16) & 0x08) - .map((item) => ({ + .map((item, index) => ({ + id: id * 100 + index, // unique id n: item.id2.slice(2), // name v: item.v, // value u: item.u // uom @@ -4324,7 +4325,6 @@ router params.id ? deviceEntities(Number(params.id)) : status(404) ) .get(EMSESP_DASHBOARD_DATA_ENDPOINT, () => { - // builds a JSON with id, t = typeID, tn = typeName, n=Name, data = [{n, v, u}] let dashboard_data = []; let dashboard_object = {}; @@ -4334,57 +4334,63 @@ router dashboard_object = { id: id, - t: element.t, - tn: element.tn, n: element.n, - data: getDashboardEntityData(id) + nodes: getDashboardEntityData(id) }; - dashboard_data.push(dashboard_object); // add to dashboard_data + // only add to dashboard if we have values + if (dashboard_object.nodes.length > 0) { + dashboard_data.push(dashboard_object); + } } // add the custom entity data dashboard_object = { id: 99, - t: 99, - tn: 'custom', n: 'Custom Entities', - data: getDashboardEntityData(99) + nodes: getDashboardEntityData(99) }; - dashboard_data.push(dashboard_object); // add to dashboard_data + // only add to dashboard if we have values + if (dashboard_object.nodes.length > 0) { + dashboard_data.push(dashboard_object); + } // add temperature sensor data let sensor_data = {}; - sensor_data = emsesp_sensordata.ts.map((item) => ({ + sensor_data = emsesp_sensordata.ts.map((item, index) => ({ + id: 980 + index, n: item.n ? item.n : item.id, // name may not be set v: item.t ? item.t : undefined, // can have no value u: item.u })); dashboard_object = { id: 98, - t: 98, - tn: 'ts', n: 'Temperature Sensors', - data: sensor_data + nodes: sensor_data }; - dashboard_data.push(dashboard_object); // add to dashboard_data + // only add to dashboard if we have values + if (dashboard_object.nodes.length > 0) { + dashboard_data.push(dashboard_object); + } // add analog sensor data - sensor_data = emsesp_sensordata.as.map((item) => ({ + sensor_data = emsesp_sensordata.as.map((item, index) => ({ + id: 970 + index, n: item.n, v: item.v, u: item.u })); dashboard_object = { id: 97, - t: 97, - tn: 'as', n: 'Analog Sensors', - data: sensor_data + nodes: sensor_data }; - dashboard_data.push(dashboard_object); // add to dashboard_data + // only add to dashboard if we have values + if (dashboard_object.nodes.length > 0) { + dashboard_data.push(dashboard_object); + } - console.log('dashboard_data: ', dashboard_data); + // console.log('dashboard_data: ', dashboard_data); return new Response(encoder.encode(dashboard_data), { headers }); // msgpack it }) From 01db9db6ae5d6d785f633f579a6cd9a8d0d8ab39 Mon Sep 17 00:00:00 2001 From: proddy Date: Thu, 3 Oct 2024 21:09:50 +0200 Subject: [PATCH 05/66] update dashboard for analog --- interface/src/app/main/Dashboard.tsx | 85 ++++++++++----------- mock-api/rest_server.ts | 107 ++++++++++++++------------- 2 files changed, 98 insertions(+), 94 deletions(-) diff --git a/interface/src/app/main/Dashboard.tsx b/interface/src/app/main/Dashboard.tsx index cc9dff0bf..a66acced7 100644 --- a/interface/src/app/main/Dashboard.tsx +++ b/interface/src/app/main/Dashboard.tsx @@ -6,15 +6,7 @@ import UnfoldLessIcon from '@mui/icons-material/UnfoldLess'; import UnfoldMoreIcon from '@mui/icons-material/UnfoldMore'; import { Box, ToggleButton, ToggleButtonGroup, Typography } from '@mui/material'; -import { - Body, - Cell, - Header, - HeaderCell, - HeaderRow, - Row, - Table -} from '@table-library/react-table-library/table'; +import { Body, Cell, Row, Table } from '@table-library/react-table-library/table'; import { useTheme } from '@table-library/react-table-library/theme'; import { CellTree, useTree } from '@table-library/react-table-library/tree'; import { useAutoRequest } from 'alova/client'; @@ -52,19 +44,10 @@ const Dashboard = () => { height: 32px; } `, - HeaderRow: ` - text-transform: uppercase; - background-color: black; - color: #90CAF9; - .th { - border-bottom: 1px solid #565656; - height: 36px; - } - `, Row: ` background-color: #1e1e1e; - position: relative; - cursor: pointer; + // position: relative; + // cursor: pointer; .td { height: 24px; } @@ -107,9 +90,10 @@ const Dashboard = () => { if (di.id < 100) { if (di.nodes?.length) { return ( -
- {di.n} ({di.nodes?.length}) -
+ + {di.n} +  ({di.nodes?.length}) + ); } return
{di.n}
; @@ -133,13 +117,22 @@ const Dashboard = () => { return ; } + if (data.length === 0) { + return ( + + {/* TODO translate */} + No entities found. + + ); + } + return ( <> - - - Use Customizations to mark your favorite EMS entities - - + + The dashboard shows all EMS entities that are marked as favorite, and the + sensors. + + { - - {(tableList: DashboardItem[]) => ( - <> -
- - Name - Value - -
+
+ {(tableList: DashboardItem[]) => ( {tableList.map((di: DashboardItem) => ( @@ -180,9 +177,9 @@ const Dashboard = () => { ))} - - )} -
+ )} + + ); }; diff --git a/mock-api/rest_server.ts b/mock-api/rest_server.ts index b57af9db4..9837ed0b8 100644 --- a/mock-api/rest_server.ts +++ b/mock-api/rest_server.ts @@ -4327,71 +4327,78 @@ router .get(EMSESP_DASHBOARD_DATA_ENDPOINT, () => { let dashboard_data = []; let dashboard_object = {}; + let fake = false; + // let fake = true; - // pick EMS devices from coredata - for (const element of emsesp_coredata.devices) { - const id = element.id; + if (!fake) { + // pick EMS devices from coredata + for (const element of emsesp_coredata.devices) { + const id = element.id; + dashboard_object = { + id: id, + n: element.n, + nodes: getDashboardEntityData(id) + }; + + // only add to dashboard if we have values + if (dashboard_object.nodes.length > 0) { + dashboard_data.push(dashboard_object); + } + } + + // add the custom entity data dashboard_object = { - id: id, - n: element.n, - nodes: getDashboardEntityData(id) + id: 99, + n: 'Custom Entities', + nodes: getDashboardEntityData(99) }; + // only add to dashboard if we have values + if (dashboard_object.nodes.length > 0) { + dashboard_data.push(dashboard_object); + } + // add temperature sensor data + let sensor_data = {}; + sensor_data = emsesp_sensordata.ts.map((item, index) => ({ + id: 980 + index, + n: item.n ? item.n : item.id, // name may not be set + v: item.t ? item.t : undefined, // can have no value + u: item.u + })); + dashboard_object = { + id: 98, + n: 'Temperature Sensors', + nodes: sensor_data + }; // only add to dashboard if we have values if (dashboard_object.nodes.length > 0) { dashboard_data.push(dashboard_object); } - } - // add the custom entity data - dashboard_object = { - id: 99, - n: 'Custom Entities', - nodes: getDashboardEntityData(99) - }; - // only add to dashboard if we have values - if (dashboard_object.nodes.length > 0) { - dashboard_data.push(dashboard_object); - } + // add analog sensor data + // remove disabled sensors (t = 0) + sensor_data = emsesp_sensordata.as.filter((item) => item.t !== 0); - // add temperature sensor data - let sensor_data = {}; - sensor_data = emsesp_sensordata.ts.map((item, index) => ({ - id: 980 + index, - n: item.n ? item.n : item.id, // name may not be set - v: item.t ? item.t : undefined, // can have no value - u: item.u - })); - dashboard_object = { - id: 98, - n: 'Temperature Sensors', - nodes: sensor_data - }; - // only add to dashboard if we have values - if (dashboard_object.nodes.length > 0) { - dashboard_data.push(dashboard_object); - } + sensor_data = sensor_data.map((item, index) => ({ + id: 970 + index, + n: item.n, + v: item.v, + u: item.u + })); - // add analog sensor data - sensor_data = emsesp_sensordata.as.map((item, index) => ({ - id: 970 + index, - n: item.n, - v: item.v, - u: item.u - })); - dashboard_object = { - id: 97, - n: 'Analog Sensors', - nodes: sensor_data - }; - // only add to dashboard if we have values - if (dashboard_object.nodes.length > 0) { - dashboard_data.push(dashboard_object); + dashboard_object = { + id: 97, + n: 'Analog Sensors', + nodes: sensor_data + }; + // only add to dashboard if we have values + if (dashboard_object.nodes.length > 0) { + dashboard_data.push(dashboard_object); + } } // console.log('dashboard_data: ', dashboard_data); - return new Response(encoder.encode(dashboard_data), { headers }); // msgpack it }) From fd7d8ca532fe996fbbd6910c0208296ba1e1d2d7 Mon Sep 17 00:00:00 2001 From: proddy Date: Fri, 4 Oct 2024 13:32:42 +0200 Subject: [PATCH 06/66] enable write in dashboard --- interface/package.json | 2 +- interface/src/app/main/Dashboard.tsx | 176 +++++++++++++----- interface/src/app/main/DeviceIcon.tsx | 71 +++---- interface/src/app/main/Devices.tsx | 104 +++++------ interface/src/app/main/types.ts | 8 +- .../src/components/layout/LayoutMenu.tsx | 4 +- interface/yarn.lock | 10 +- mock-api/rest_server.ts | 76 ++++++-- 8 files changed, 286 insertions(+), 165 deletions(-) diff --git a/interface/package.json b/interface/package.json index 2b2351c04..f6a92f559 100644 --- a/interface/package.json +++ b/interface/package.json @@ -31,7 +31,7 @@ "async-validator": "^4.2.5", "jwt-decode": "^4.0.0", "mime-types": "^2.1.35", - "preact": "^10.24.1", + "preact": "^10.24.2", "react": "^18.3.1", "react-dom": "^18.3.1", "react-icons": "^5.3.0", diff --git a/interface/src/app/main/Dashboard.tsx b/interface/src/app/main/Dashboard.tsx index a66acced7..0c6b61b84 100644 --- a/interface/src/app/main/Dashboard.tsx +++ b/interface/src/app/main/Dashboard.tsx @@ -1,24 +1,38 @@ -import { useEffect, useState } from 'react'; +import { useContext, useEffect, useState } from 'react'; +import { IconContext } from 'react-icons/lib'; +import { toast } from 'react-toastify'; import ChevronRightIcon from '@mui/icons-material/ChevronRight'; +import EditIcon from '@mui/icons-material/Edit'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import UnfoldLessIcon from '@mui/icons-material/UnfoldLess'; import UnfoldMoreIcon from '@mui/icons-material/UnfoldMore'; -import { Box, ToggleButton, ToggleButtonGroup, Typography } from '@mui/material'; +import { + Box, + IconButton, + ToggleButton, + ToggleButtonGroup, + Typography +} from '@mui/material'; import { Body, Cell, Row, Table } from '@table-library/react-table-library/table'; import { useTheme } from '@table-library/react-table-library/theme'; import { CellTree, useTree } from '@table-library/react-table-library/tree'; -import { useAutoRequest } from 'alova/client'; +import { useAutoRequest, useRequest } from 'alova/client'; import { FormLoader, SectionContent, useLayoutTitle } from 'components'; +import { AuthenticatedContext } from 'contexts/authentication'; import { useI18nContext } from 'i18n/i18n-react'; -import { readDashboard } from '../../api/app'; +import { readDashboard, writeDeviceValue } from '../../api/app'; +import DeviceIcon from './DeviceIcon'; +import DashboardDevicesDialog from './DevicesDialog'; import { formatValue } from './deviceValue'; -import type { DashboardItem } from './types'; +import { type DashboardItem, type DeviceValue } from './types'; +import { deviceValueItemValidation } from './validators'; const Dashboard = () => { const { LL } = useI18nContext(); + const { me } = useContext(AuthenticatedContext); useLayoutTitle('Dashboard'); // TODO translate @@ -34,9 +48,39 @@ const Dashboard = () => { pollingTime: 1500 }); + const { loading: submitting, send: sendDeviceValue } = useRequest( + (data: { id: number; c: string; v: unknown }) => writeDeviceValue(data), + { + immediate: false + } + ); + + const [deviceValueDialogOpen, setDeviceValueDialogOpen] = useState(false); + const [selectedDeviceValue, setSelectedDeviceValue] = useState(); + + const deviceValueDialogClose = () => { + setDeviceValueDialogOpen(false); + void sendDeviceData(selectedDevice); + }; + const deviceValueDialogSave = async (devicevalue: DeviceValue) => { + const id = Number(device_select.state.id); + await sendDeviceValue({ id, c: devicevalue.c ?? '', v: devicevalue.v }) + .then(() => { + toast.success(LL.WRITE_CMD_SENT()); + }) + .catch((error: Error) => { + toast.error(error.message); + }) + .finally(async () => { + setDeviceValueDialogOpen(false); + await sendDeviceData(id); + setSelectedDeviceValue(undefined); + }); + }; + const dashboard_theme = useTheme({ Table: ` - --data-table-library_grid-template-columns: minmax(80px, auto) 120px; + --data-table-library_grid-template-columns: minmax(80px, auto) 120px 40px; `, BaseRow: ` font-size: 14px; @@ -46,10 +90,8 @@ const Dashboard = () => { `, Row: ` background-color: #1e1e1e; - // position: relative; - // cursor: pointer; .td { - height: 24px; + height: 22px; } &:hover .td { border-top: 1px solid #177ac9; @@ -58,15 +100,8 @@ const Dashboard = () => { ` }); - function onTreeChange(action, state) { - // do nothing for now - } - const tree = useTree( { nodes: data }, - { - onChange: onTreeChange - }, { treeIcon: { margin: '4px', @@ -84,22 +119,38 @@ const Dashboard = () => { tree.fns.onToggleAll({}); setFirstLoad(false); } - }, [data]); + }); const showName = (di: DashboardItem) => { if (di.id < 100) { + // if its a device row if (di.nodes?.length) { return ( - - {di.n} + <> + + +   {di.n} +  ({di.nodes?.length}) - + ); } - return
{di.n}
; } + return
{di.n}
; + }; - return
{di.n}
; + const showDeviceValue = (di: DashboardItem) => { + // convert di to dv + // TODO should we not just use dv? + const dv: DeviceValue = { + id: ' ' + di.n, + v: di.v, + u: di.u, + c: di.c, + l: di.l + }; + setSelectedDeviceValue(dv); + setDeviceValueDialogOpen(true); }; const handleShowAll = ( @@ -129,6 +180,7 @@ const Dashboard = () => { return ( <> + {/* TODO translate */} The dashboard shows all EMS entities that are marked as favorite, and the sensors. @@ -158,33 +210,71 @@ const Dashboard = () => { border: '1px solid grey' }} > - - {(tableList: DashboardItem[]) => ( - - {tableList.map((di: DashboardItem) => ( - - {di.nodes?.length === 0 ? ( - {showName(di)} - ) : ( - {showName(di)} - )} - {formatValue(LL, di.v, di.u)} - - ))} - - )} -
+ + {(tableList: DashboardItem[]) => ( + + {tableList.map((di: DashboardItem) => ( + + {di.nodes?.length === 0 ? ( + {showName(di)} + ) : ( + {showName(di)} + )} + +
+ {formatValue(LL, di.v, di.u)} +
+
+ + + {me.admin && di.c && ( + showDeviceValue(di)} + > + + + )} + +
+ ))} + + )} +
+ ); }; - return {renderContent()}; + return ( + + {renderContent()} + {selectedDeviceValue && ( + + )} + + ); }; export default Dashboard; diff --git a/interface/src/app/main/DeviceIcon.tsx b/interface/src/app/main/DeviceIcon.tsx index a9429b67c..b498f6316 100644 --- a/interface/src/app/main/DeviceIcon.tsx +++ b/interface/src/app/main/DeviceIcon.tsx @@ -2,6 +2,7 @@ import { AiOutlineAlert, AiOutlineControl, AiOutlineGateway } from 'react-icons/ import { CgSmartHomeBoiler } from 'react-icons/cg'; import { FaSolarPanel } from 'react-icons/fa'; import { GiHeatHaze, GiTap } from 'react-icons/gi'; +import { MdPlaylistAdd } from 'react-icons/md'; import { MdOutlineDevices, MdOutlinePool, @@ -11,50 +12,40 @@ import { import { TiFlowSwitch } from 'react-icons/ti'; import { VscVmConnect } from 'react-icons/vsc'; -import PlaylistAddIcon from '@mui/icons-material/PlaylistAdd'; +import type { SvgIconProps } from '@mui/material'; import { DeviceType } from './types'; +const deviceIconLookup: { + [key in DeviceType]: React.ComponentType | undefined; +} = { + [DeviceType.TEMPERATURESENSOR]: MdOutlineSensors, + [DeviceType.ANALOGSENSOR]: MdOutlineSensors, + [DeviceType.BOILER]: CgSmartHomeBoiler, + [DeviceType.HEATSOURCE]: CgSmartHomeBoiler, + [DeviceType.THERMOSTAT]: MdThermostatAuto, + [DeviceType.MIXER]: AiOutlineControl, + [DeviceType.SOLAR]: FaSolarPanel, + [DeviceType.HEATPUMP]: GiHeatHaze, + [DeviceType.GATEWAY]: AiOutlineGateway, + [DeviceType.SWITCH]: TiFlowSwitch, + [DeviceType.CONTROLLER]: VscVmConnect, + [DeviceType.CONNECT]: VscVmConnect, + [DeviceType.ALERT]: AiOutlineAlert, + [DeviceType.EXTENSION]: MdOutlineDevices, + [DeviceType.WATER]: GiTap, + [DeviceType.POOL]: MdOutlinePool, + [DeviceType.CUSTOM]: MdPlaylistAdd, + [DeviceType.UNKNOWN]: undefined, + [DeviceType.SYSTEM]: undefined, + [DeviceType.SCHEDULER]: undefined, + [DeviceType.GENERIC]: undefined, + [DeviceType.VENTILATION]: undefined +}; + const DeviceIcon = ({ type_id }: { type_id: DeviceType }) => { - switch (type_id) { - case DeviceType.TEMPERATURESENSOR: - case DeviceType.ANALOGSENSOR: - return ; - case DeviceType.BOILER: - case DeviceType.HEATSOURCE: - return ; - case DeviceType.THERMOSTAT: - return ; - case DeviceType.MIXER: - return ; - case DeviceType.SOLAR: - return ; - case DeviceType.HEATPUMP: - return ; - case DeviceType.GATEWAY: - return ; - case DeviceType.SWITCH: - return ; - case DeviceType.CONTROLLER: - case DeviceType.CONNECT: - return ; - case DeviceType.ALERT: - return ; - case DeviceType.EXTENSION: - return ; - case DeviceType.WATER: - return ; - case DeviceType.POOL: - return ; - case DeviceType.CUSTOM: - return ( - - ); - default: - return null; - } + const Icon = deviceIconLookup[type_id]; + return Icon ? : null; }; export default DeviceIcon; diff --git a/interface/src/app/main/Devices.tsx b/interface/src/app/main/Devices.tsx index fe306dbda..457286824 100644 --- a/interface/src/app/main/Devices.tsx +++ b/interface/src/app/main/Devices.tsx @@ -170,7 +170,7 @@ const Devices = () => { common_theme, { Table: ` - --data-table-library_grid-template-columns: 40px repeat(1, minmax(0, 1fr)) 130px; + --data-table-library_grid-template-columns: repeat(1, minmax(0, 1fr)) 130px; `, BaseRow: ` .td { @@ -527,57 +527,57 @@ const Devices = () => { }; const renderCoreData = () => ( - - {!coreData.connected && ( - - )} + <> + + {!coreData.connected && ( + + )} - {coreData.connected && ( - - {(tableList: Device[]) => ( - <> -
- - - {LL.DESCRIPTION()} - {LL.TYPE(0)} - -
- - {tableList.length === 0 && ( - - )} - {tableList.map((device: Device) => ( - - - - - - {device.n} - -   ({device.e}) - - - {device.tn} - - ))} - - - )} -
- )} -
+ {coreData.connected && ( + + {(tableList: Device[]) => ( + <> +
+ + {LL.DESCRIPTION()} + {LL.TYPE(0)} + +
+ + {tableList.length === 0 && ( + + )} + {tableList.map((device: Device) => ( + + + +    + {device.n} + +   ({device.e}) + + + {device.tn} + + ))} + + + )} +
+ )} +
+ ); const deviceValueDialogClose = () => { @@ -733,7 +733,7 @@ const Devices = () => { size="small" onClick={() => showDeviceValue(dv)} > - {dv.v === '' && dv.c ? ( + {dv.v === '' ? ( ) : ( diff --git a/interface/src/app/main/types.ts b/interface/src/app/main/types.ts index 30efb7e59..8b88b6392 100644 --- a/interface/src/app/main/types.ts +++ b/interface/src/app/main/types.ts @@ -117,9 +117,13 @@ export interface CoreData { export interface DashboardItem { id: number; // unique index n: string; // name - v?: unknown; // value + v?: unknown; // value, optional u: number; // uom - nodes?: DashboardItem[]; // nodes + t: number; // type from DeviceType + c?: string; // command, optional + l?: string[]; // list, optional + h?: string; // help text, optional + nodes?: DashboardItem[]; // nodes, optional } export interface DashboardData { diff --git a/interface/src/components/layout/LayoutMenu.tsx b/interface/src/components/layout/LayoutMenu.tsx index 1817abc2e..d0e016491 100644 --- a/interface/src/components/layout/LayoutMenu.tsx +++ b/interface/src/components/layout/LayoutMenu.tsx @@ -144,8 +144,8 @@ const LayoutMenu = () => {
- - + + diff --git a/interface/yarn.lock b/interface/yarn.lock index a2062e721..643fa47d6 100644 --- a/interface/yarn.lock +++ b/interface/yarn.lock @@ -1864,7 +1864,7 @@ __metadata: formidable: "npm:^3.5.1" jwt-decode: "npm:^4.0.0" mime-types: "npm:^2.1.35" - preact: "npm:^10.24.1" + preact: "npm:^10.24.2" prettier: "npm:^3.3.3" react: "npm:^18.3.1" react-dom: "npm:^18.3.1" @@ -5793,10 +5793,10 @@ __metadata: languageName: node linkType: hard -"preact@npm:^10.24.1": - version: 10.24.1 - resolution: "preact@npm:10.24.1" - checksum: 10c0/f9bc8b2f88d340f1b8f854208889244059c46916449b8f8f2174fcacbc0904c445c5870896fb0cfeaf442eeade975857e8e03f0785135c41d63cd32d9414c9c6 +"preact@npm:^10.24.2": + version: 10.24.2 + resolution: "preact@npm:10.24.2" + checksum: 10c0/d1d22c5e1abc10eb8f83501857ef22c54a3fda2d20449d06f5b3c7d5ae812bd702c16c05b672138b8906504f9c893e072e9cebcbcada8cac320edf36265788fb languageName: node linkType: hard diff --git a/mock-api/rest_server.ts b/mock-api/rest_server.ts index 9837ed0b8..ccd725973 100644 --- a/mock-api/rest_server.ts +++ b/mock-api/rest_server.ts @@ -32,6 +32,32 @@ const headers = { let countWifiScanPoll = 0; // wifi network scan let countHardwarePoll = 0; // for during an upload +// DeviceTypes +const enum DeviceType { + SYSTEM = 0, + TEMPERATURESENSOR, + ANALOGSENSOR, + SCHEDULER, + CUSTOM, + BOILER, + THERMOSTAT, + MIXER, + SOLAR, + HEATPUMP, + GATEWAY, + SWITCH, + CONTROLLER, + CONNECT, + ALERT, + EXTENSION, + GENERIC, + HEATSOURCE, + VENTILATION, + WATER, + POOL, + UNKNOWN +} + function updateMask(entity: any, de: any, dd: any) { const current_mask = parseInt(entity.slice(0, 2), 16); @@ -1708,7 +1734,7 @@ const emsesp_devicedata_3 = { { v: 'hot', u: 0, - id: '00dhw comfort', + id: '08dhw comfort', c: 'dhw/comfort', l: ['hot', 'eco', 'intelligent'] }, @@ -4268,11 +4294,11 @@ function getDashboardEntityData(id: number) { else if (id == 10) device_data = emsesp_devicedata_10; else if (id == 99) device_data = emsesp_devicedata_99; - // filter device_data, just want id, v, u + // filter device_data // and only favorite items (bit 8 set), only for non-Custom Entities // and replace id by striping off the 2-char mask let new_data = (device_data as any).data - .map(({ id, c, m, x, s, h, l, ...rest }) => ({ + .map(({ id, m, x, s, ...rest }) => ({ ...rest, id2: id })) @@ -4281,9 +4307,14 @@ function getDashboardEntityData(id: number) { id: id * 100 + index, // unique id n: item.id2.slice(2), // name v: item.v, // value - u: item.u // uom + u: item.u, // uom + c: item.c, // command + l: item.l, // list + h: item.h // help })); + // TODO only and command if not marked as READONLY + return new_data; } @@ -4325,10 +4356,12 @@ router params.id ? deviceEntities(Number(params.id)) : status(404) ) .get(EMSESP_DASHBOARD_DATA_ENDPOINT, () => { - let dashboard_data = []; - let dashboard_object = {}; + let dashboard_data: { id?: number; n?: string; t?: number; nodes?: any[] }[] = + []; + let dashboard_object: { id?: number; n?: string; t?: number; nodes?: any[] } = + {}; let fake = false; - // let fake = true; + // let fake = true; // fakes no data if (!fake) { // pick EMS devices from coredata @@ -4338,11 +4371,12 @@ router dashboard_object = { id: id, n: element.n, + t: element.t, nodes: getDashboardEntityData(id) }; - // only add to dashboard if we have values - if (dashboard_object.nodes.length > 0) { + // only add to dashboard if we have values + if ((dashboard_object.nodes ?? []).length > 0) { dashboard_data.push(dashboard_object); } } @@ -4351,15 +4385,16 @@ router dashboard_object = { id: 99, n: 'Custom Entities', + t: 4, // DeviceType::CUSTOM nodes: getDashboardEntityData(99) }; - // only add to dashboard if we have values - if (dashboard_object.nodes.length > 0) { + // only add to dashboard if we have values + if ((dashboard_object.nodes ?? []).length > 0) { dashboard_data.push(dashboard_object); } - // add temperature sensor data - let sensor_data = {}; + // add temperature sensor data. no command c + let sensor_data: any[] = []; sensor_data = emsesp_sensordata.ts.map((item, index) => ({ id: 980 + index, n: item.n ? item.n : item.id, // name may not be set @@ -4369,17 +4404,17 @@ router dashboard_object = { id: 98, n: 'Temperature Sensors', + t: 1, // DeviceType::TEMPERATURESENSOR nodes: sensor_data }; - // only add to dashboard if we have values - if (dashboard_object.nodes.length > 0) { + // only add to dashboard if we have values + if ((dashboard_object.nodes ?? []).length > 0) { dashboard_data.push(dashboard_object); } - // add analog sensor data - // remove disabled sensors (t = 0) + // add analog sensor data. no command c + // remove disabled sensors first (t = 0) sensor_data = emsesp_sensordata.as.filter((item) => item.t !== 0); - sensor_data = sensor_data.map((item, index) => ({ id: 970 + index, n: item.n, @@ -4390,10 +4425,11 @@ router dashboard_object = { id: 97, n: 'Analog Sensors', + t: 2, // DeviceType::ANALOGSENSOR nodes: sensor_data }; - // only add to dashboard if we have values - if (dashboard_object.nodes.length > 0) { + // only add to dashboard if we have values + if ((dashboard_object.nodes ?? []).length > 0) { dashboard_data.push(dashboard_object); } } From 4fde9e7c546d7bc88f31b662fb994db8314cebfe Mon Sep 17 00:00:00 2001 From: proddy Date: Fri, 4 Oct 2024 15:41:58 +0200 Subject: [PATCH 07/66] fix slowness --- interface/src/app/main/CustomEntities.tsx | 2 +- interface/src/app/main/Dashboard.tsx | 132 +++++++++++++--------- interface/src/app/main/Devices.tsx | 2 +- 3 files changed, 80 insertions(+), 56 deletions(-) diff --git a/interface/src/app/main/CustomEntities.tsx b/interface/src/app/main/CustomEntities.tsx index 31a0811d7..09a2d51c9 100644 --- a/interface/src/app/main/CustomEntities.tsx +++ b/interface/src/app/main/CustomEntities.tsx @@ -62,7 +62,7 @@ const CustomEntities = () => { return () => { clearInterval(timer); }; - }); + }, []); const { send: writeEntities } = useRequest( (data: Entities) => writeCustomEntities(data), diff --git a/interface/src/app/main/Dashboard.tsx b/interface/src/app/main/Dashboard.tsx index 0c6b61b84..cab09a708 100644 --- a/interface/src/app/main/Dashboard.tsx +++ b/interface/src/app/main/Dashboard.tsx @@ -18,7 +18,7 @@ import { import { Body, Cell, Row, Table } from '@table-library/react-table-library/table'; import { useTheme } from '@table-library/react-table-library/theme'; import { CellTree, useTree } from '@table-library/react-table-library/tree'; -import { useAutoRequest, useRequest } from 'alova/client'; +import { useRequest } from 'alova/client'; import { FormLoader, SectionContent, useLayoutTitle } from 'components'; import { AuthenticatedContext } from 'contexts/authentication'; import { useI18nContext } from 'i18n/i18n-react'; @@ -42,10 +42,10 @@ const Dashboard = () => { const { data, send: fetchDashboard, - error - } = useAutoRequest(readDashboard, { - initialData: [], - pollingTime: 1500 + error, + loading + } = useRequest(readDashboard, { + initialData: [] }); const { loading: submitting, send: sendDeviceValue } = useRequest( @@ -62,6 +62,8 @@ const Dashboard = () => { setDeviceValueDialogOpen(false); void sendDeviceData(selectedDevice); }; + + // TODO get this working next const deviceValueDialogSave = async (devicevalue: DeviceValue) => { const id = Number(device_select.state.id); await sendDeviceValue({ id, c: devicevalue.c ?? '', v: devicevalue.v }) @@ -102,6 +104,9 @@ const Dashboard = () => { const tree = useTree( { nodes: data }, + { + onChange: null + }, { treeIcon: { margin: '4px', @@ -109,25 +114,37 @@ const Dashboard = () => { iconRight: , iconDown: }, - indentation: 28 + indentation: 50 } ); + useEffect(() => { + const timer = setInterval(() => { + if (deviceValueDialogOpen) { + return; + } + fetchDashboard(); + }, 2000); + return () => { + clearInterval(timer); + }; + }, []); + // auto expand on first load useEffect(() => { - if (data.length && firstLoad && !tree.state.ids.length) { + if (firstLoad && data.length && !tree.state.ids.length) { tree.fns.onToggleAll({}); setFirstLoad(false); } - }); + }, [data]); const showName = (di: DashboardItem) => { if (di.id < 100) { - // if its a device row + // if its a device and has entities if (di.nodes?.length) { return ( <> - +   {di.n} @@ -136,7 +153,7 @@ const Dashboard = () => { ); } } - return
{di.n}
; + return {di.n}; }; const showDeviceValue = (di: DashboardItem) => { @@ -168,14 +185,14 @@ const Dashboard = () => { return ; } - if (data.length === 0) { - return ( - - {/* TODO translate */} - No entities found. - - ); - } + // if (data.length === 0) { + // return ( + // + // {/* TODO translate */} + // No entities found. + // + // ); + // } return ( <> @@ -217,42 +234,49 @@ const Dashboard = () => { style: { verticalAlign: 'middle' } }} > - - {(tableList: DashboardItem[]) => ( - - {tableList.map((di: DashboardItem) => ( - - {di.nodes?.length === 0 ? ( - {showName(di)} - ) : ( - {showName(di)} - )} - -
- {formatValue(LL, di.v, di.u)} -
-
- - - {me.admin && di.c && ( - showDeviceValue(di)} - > - - + {!loading && data.length === 0 ? ( + + {/* TODO translate */} + No entities found. + + ) : ( +
+ {(tableList: DashboardItem[]) => ( + + {tableList.map((di: DashboardItem) => ( + + {di.id > 99 ? ( + {showName(di)} + ) : ( + {showName(di)} )} - - - ))} - - )} -
+ + + {formatValue(LL, di.v, di.u)} + + + + + {me.admin && di.c && ( + showDeviceValue(di)} + > + + + )} + + + ))} + + )} + + )} diff --git a/interface/src/app/main/Devices.tsx b/interface/src/app/main/Devices.tsx index 457286824..c57b56a6f 100644 --- a/interface/src/app/main/Devices.tsx +++ b/interface/src/app/main/Devices.tsx @@ -428,7 +428,7 @@ const Devices = () => { return () => { clearInterval(timer); }; - }); + }, []); const deviceValueDialogSave = async (devicevalue: DeviceValue) => { const id = Number(device_select.state.id); From e501ac31f57e8b7d7f86b2b05430bab45e98d80f Mon Sep 17 00:00:00 2001 From: proddy Date: Sat, 5 Oct 2024 16:25:53 +0200 Subject: [PATCH 08/66] translations and finish writeDevice --- interface/package.json | 4 +- interface/src/app/main/CustomEntities.tsx | 21 ++-- interface/src/app/main/Customizations.tsx | 2 +- interface/src/app/main/Dashboard.tsx | 119 +++++++----------- interface/src/app/main/Devices.tsx | 17 +-- interface/src/app/main/Help.tsx | 4 +- interface/src/app/main/Modules.tsx | 2 +- interface/src/app/main/Scheduler.tsx | 2 +- interface/src/app/main/Sensors.tsx | 2 +- interface/src/app/main/types.ts | 12 +- interface/src/app/settings/DownloadUpload.tsx | 6 +- interface/src/app/status/APStatus.tsx | 2 +- interface/src/app/status/Activity.tsx | 2 +- interface/src/app/status/HardwareStatus.tsx | 2 +- interface/src/app/status/MqttStatus.tsx | 2 +- interface/src/app/status/NTPStatus.tsx | 2 +- interface/src/app/status/NetworkStatus.tsx | 2 +- interface/src/app/status/RestartMonitor.tsx | 2 +- interface/src/app/status/Status.tsx | 2 +- interface/src/i18n/de/index.ts | 5 +- interface/src/i18n/en/index.ts | 5 +- interface/src/i18n/fr/index.ts | 5 +- interface/src/i18n/it/index.ts | 5 +- interface/src/i18n/nl/index.ts | 5 +- interface/src/i18n/no/index.ts | 5 +- interface/src/i18n/pl/index.ts | 5 +- interface/src/i18n/sk/index.ts | 5 +- interface/src/i18n/sv/index.ts | 5 +- interface/src/i18n/tr/index.ts | 5 +- interface/src/utils/index.ts | 2 + interface/src/utils/useInterval.ts | 22 ++++ interface/yarn.lock | 73 ++++++----- mock-api/rest_server.ts | 63 ++++++---- 33 files changed, 230 insertions(+), 187 deletions(-) create mode 100644 interface/src/utils/useInterval.ts diff --git a/interface/package.json b/interface/package.json index f6a92f559..01082ee23 100644 --- a/interface/package.json +++ b/interface/package.json @@ -42,7 +42,7 @@ }, "devDependencies": { "@babel/core": "^7.25.7", - "@eslint/js": "^9.11.1", + "@eslint/js": "^9.12.0", "@preact/compat": "^18.3.1", "@preact/preset-vite": "^2.9.1", "@trivago/prettier-plugin-sort-imports": "^4.3.0", @@ -52,7 +52,7 @@ "@types/react-dom": "^18.3.0", "@types/react-router-dom": "^5.3.3", "concurrently": "^9.0.1", - "eslint": "^9.11.1", + "eslint": "^9.12.0", "eslint-config-prettier": "^9.1.0", "formidable": "^3.5.1", "prettier": "^3.3.3", diff --git a/interface/src/app/main/CustomEntities.tsx b/interface/src/app/main/CustomEntities.tsx index 09a2d51c9..68fac89d2 100644 --- a/interface/src/app/main/CustomEntities.tsx +++ b/interface/src/app/main/CustomEntities.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useState } from 'react'; import { useBlocker } from 'react-router-dom'; import { toast } from 'react-toastify'; @@ -27,6 +27,7 @@ import { useLayoutTitle } from 'components'; import { useI18nContext } from 'i18n/i18n-react'; +import { useInterval } from 'utils'; import { readCustomEntities, writeCustomEntities } from '../../api/app'; import SettingsCustomEntitiesDialog from './CustomEntitiesDialog'; @@ -52,17 +53,11 @@ const CustomEntities = () => { initialData: [] }); - useEffect(() => { - const timer = setInterval(async () => { - if (dialogOpen || numChanges > 0) { - return; - } - await fetchEntities(); - }, 2000); - return () => { - clearInterval(timer); - }; - }, []); + useInterval(() => { + if (!dialogOpen && !numChanges) { + void fetchEntities(); + } + }, 3000); const { send: writeEntities } = useRequest( (data: Entities) => writeCustomEntities(data), @@ -295,7 +290,7 @@ const CustomEntities = () => { {blocker ? : null} - {LL.ENTITIES_HELP_1()} + {LL.ENTITIES_HELP_1()} {renderEntity()} diff --git a/interface/src/app/main/Customizations.tsx b/interface/src/app/main/Customizations.tsx index 653c28e42..72babe728 100644 --- a/interface/src/app/main/Customizations.tsx +++ b/interface/src/app/main/Customizations.tsx @@ -427,7 +427,7 @@ const Customizations = () => { const renderDeviceList = () => ( <> - {LL.CUSTOMIZATIONS_HELP_1()}. + {LL.CUSTOMIZATIONS_HELP_1()}. {rename ? ( diff --git a/interface/src/app/main/Dashboard.tsx b/interface/src/app/main/Dashboard.tsx index cab09a708..190a2ef93 100644 --- a/interface/src/app/main/Dashboard.tsx +++ b/interface/src/app/main/Dashboard.tsx @@ -22,23 +22,29 @@ import { useRequest } from 'alova/client'; import { FormLoader, SectionContent, useLayoutTitle } from 'components'; import { AuthenticatedContext } from 'contexts/authentication'; import { useI18nContext } from 'i18n/i18n-react'; +import { useInterval } from 'utils'; import { readDashboard, writeDeviceValue } from '../../api/app'; import DeviceIcon from './DeviceIcon'; import DashboardDevicesDialog from './DevicesDialog'; import { formatValue } from './deviceValue'; -import { type DashboardItem, type DeviceValue } from './types'; +import { type DashboardItem, DeviceEntityMask, type DeviceValue } from './types'; import { deviceValueItemValidation } from './validators'; const Dashboard = () => { const { LL } = useI18nContext(); const { me } = useContext(AuthenticatedContext); - useLayoutTitle('Dashboard'); // TODO translate + useLayoutTitle(LL.DASHBOARD()); const [firstLoad, setFirstLoad] = useState(true); const [showAll, setShowAll] = useState(true); + const [deviceValueDialogOpen, setDeviceValueDialogOpen] = useState(false); + + const [selectedDashboardItem, setSelectedDashboardItem] = + useState(); + const { data, send: fetchDashboard, @@ -55,17 +61,11 @@ const Dashboard = () => { } ); - const [deviceValueDialogOpen, setDeviceValueDialogOpen] = useState(false); - const [selectedDeviceValue, setSelectedDeviceValue] = useState(); - - const deviceValueDialogClose = () => { - setDeviceValueDialogOpen(false); - void sendDeviceData(selectedDevice); - }; - - // TODO get this working next const deviceValueDialogSave = async (devicevalue: DeviceValue) => { - const id = Number(device_select.state.id); + if (!selectedDashboardItem) { + return; + } + const id = selectedDashboardItem.parentNode.id; // this is the parent ID await sendDeviceValue({ id, c: devicevalue.c ?? '', v: devicevalue.v }) .then(() => { toast.success(LL.WRITE_CMD_SENT()); @@ -73,10 +73,9 @@ const Dashboard = () => { .catch((error: Error) => { toast.error(error.message); }) - .finally(async () => { + .finally(() => { setDeviceValueDialogOpen(false); - await sendDeviceData(id); - setSelectedDeviceValue(undefined); + setSelectedDashboardItem(undefined); }); }; @@ -105,7 +104,7 @@ const Dashboard = () => { const tree = useTree( { nodes: data }, { - onChange: null + onChange: null // not used but needed }, { treeIcon: { @@ -118,25 +117,19 @@ const Dashboard = () => { } ); - useEffect(() => { - const timer = setInterval(() => { - if (deviceValueDialogOpen) { - return; - } - fetchDashboard(); - }, 2000); - return () => { - clearInterval(timer); - }; - }, []); + useInterval(() => { + if (!deviceValueDialogOpen) { + void fetchDashboard(); + } + }, 3000); // auto expand on first load useEffect(() => { - if (firstLoad && data.length && !tree.state.ids.length) { + if (firstLoad && Array.isArray(data) && data.length && !tree.state.ids.length) { tree.fns.onToggleAll({}); setFirstLoad(false); } - }, [data]); + }, [loading]); const showName = (di: DashboardItem) => { if (di.id < 100) { @@ -145,7 +138,7 @@ const Dashboard = () => { return ( <> - +   {di.n}  ({di.nodes?.length}) @@ -153,20 +146,14 @@ const Dashboard = () => { ); } } - return {di.n}; + return {di.dv ? di.dv.id.slice(2) : ''}; }; - const showDeviceValue = (di: DashboardItem) => { - // convert di to dv - // TODO should we not just use dv? - const dv: DeviceValue = { - id: ' ' + di.n, - v: di.v, - u: di.u, - c: di.c, - l: di.l - }; - setSelectedDeviceValue(dv); + const hasMask = (id: string, mask: number) => + (parseInt(id.slice(0, 2), 16) & mask) === mask; + + const editDashboardValue = (di: DashboardItem) => { + setSelectedDashboardItem(di); setDeviceValueDialogOpen(true); }; @@ -185,21 +172,10 @@ const Dashboard = () => { return ; } - // if (data.length === 0) { - // return ( - // - // {/* TODO translate */} - // No entities found. - // - // ); - // } - return ( <> - - {/* TODO translate */} - The dashboard shows all EMS entities that are marked as favorite, and the - sensors. + + {LL.DASHBOARD_1()} { }} > {!loading && data.length === 0 ? ( - - {/* TODO translate */} - No entities found. + + {LL.NO_DATA()} ) : ( { )} - {formatValue(LL, di.v, di.u)} + {di.dv && formatValue(LL, di.dv.v, di.dv.u)} - {me.admin && di.c && ( - showDeviceValue(di)} - > - - - )} + {me.admin && + di.dv?.c && + !hasMask(di.dv.id, DeviceEntityMask.DV_READONLY) && ( + editDashboardValue(di)} + > + + + )} ))} @@ -286,14 +263,14 @@ const Dashboard = () => { return ( {renderContent()} - {selectedDeviceValue && ( + {selectedDashboardItem && selectedDashboardItem.dv && ( setDeviceValueDialogOpen(false)} onSave={deviceValueDialogSave} - selectedItem={selectedDeviceValue} + selectedItem={selectedDashboardItem.dv} writeable={true} - validator={deviceValueItemValidation(selectedDeviceValue)} + validator={deviceValueItemValidation(selectedDashboardItem.dv)} progress={submitting} /> )} diff --git a/interface/src/app/main/Devices.tsx b/interface/src/app/main/Devices.tsx index c57b56a6f..a075279df 100644 --- a/interface/src/app/main/Devices.tsx +++ b/interface/src/app/main/Devices.tsx @@ -60,6 +60,7 @@ import { useRequest } from 'alova/client'; import { MessageBox, SectionContent, useLayoutTitle } from 'components'; import { AuthenticatedContext } from 'contexts/authentication'; import { useI18nContext } from 'i18n/i18n-react'; +import { useInterval } from 'utils'; import { readCoreData, readDeviceData, writeDeviceValue } from '../../api/app'; import DeviceIcon from './DeviceIcon'; @@ -77,7 +78,7 @@ const Devices = () => { const [selectedDeviceValue, setSelectedDeviceValue] = useState(); const [onlyFav, setOnlyFav] = useState(false); const [deviceValueDialogOpen, setDeviceValueDialogOpen] = useState(false); - const [showDeviceInfo, setShowDeviceInfo] = useState(false); + const [showDeviceInfo, setShowDeviceInfo] = useState(false); const [selectedDevice, setSelectedDevice] = useState(); const navigate = useNavigate(); @@ -418,17 +419,11 @@ const Devices = () => { downloadBlob(new Blob([csvData], { type: 'text/csv;charset:utf-8' })); }; - useEffect(() => { - const timer = setInterval(() => { - if (deviceValueDialogOpen) { - return; - } + useInterval(() => { + if (!deviceValueDialogOpen) { selectedDevice ? void sendDeviceData(selectedDevice) : void sendCoreData(); - }, 2000); - return () => { - clearInterval(timer); - }; - }, []); + } + }, 3000); const deviceValueDialogSave = async (devicevalue: DeviceValue) => { const id = Number(device_select.state.id); diff --git a/interface/src/app/main/Help.tsx b/interface/src/app/main/Help.tsx index da45a0f1b..159a1ace6 100644 --- a/interface/src/app/main/Help.tsx +++ b/interface/src/app/main/Help.tsx @@ -24,7 +24,7 @@ import { useRequest } from 'alova/client'; import { SectionContent, useLayoutTitle } from 'components'; import { AuthenticatedContext } from 'contexts/authentication'; import { useI18nContext } from 'i18n/i18n-react'; -import { saveFile } from 'utils/file'; +import { saveFile } from 'utils'; import { API, callAction } from '../../api/app'; import type { APIcall } from './types'; @@ -147,7 +147,7 @@ const Help = () => { )} - + {LL.HELP_INFORMATION_4()}
{ {blocker ? : null} - {LL.SCHEDULER_HELP_1()} + {LL.SCHEDULER_HELP_1()} {renderSchedule()} diff --git a/interface/src/app/main/Sensors.tsx b/interface/src/app/main/Sensors.tsx index bfaf10d19..121c72d95 100644 --- a/interface/src/app/main/Sensors.tsx +++ b/interface/src/app/main/Sensors.tsx @@ -68,7 +68,7 @@ const Sensors = () => { analog_enabled: false, platform: 'ESP32' }, - pollingTime: 2000 + pollingTime: 3000 } ); diff --git a/interface/src/app/main/types.ts b/interface/src/app/main/types.ts index 8b88b6392..c44325825 100644 --- a/interface/src/app/main/types.ts +++ b/interface/src/app/main/types.ts @@ -116,14 +116,10 @@ export interface CoreData { export interface DashboardItem { id: number; // unique index - n: string; // name - v?: unknown; // value, optional - u: number; // uom - t: number; // type from DeviceType - c?: string; // command, optional - l?: string[]; // list, optional - h?: string; // help text, optional - nodes?: DashboardItem[]; // nodes, optional + t?: number; // type from DeviceType + n?: string; // name + dv?: DeviceValue; + nodes?: DashboardItem[]; // children nodes, optional } export interface DashboardData { diff --git a/interface/src/app/settings/DownloadUpload.tsx b/interface/src/app/settings/DownloadUpload.tsx index c3cc12e1a..07753a40a 100644 --- a/interface/src/app/settings/DownloadUpload.tsx +++ b/interface/src/app/settings/DownloadUpload.tsx @@ -32,7 +32,7 @@ import { useLayoutTitle } from 'components'; import { useI18nContext } from 'i18n/i18n-react'; -import { saveFile } from 'utils/file'; +import { saveFile } from 'utils'; const DownloadUpload = () => { const { LL } = useI18nContext(); @@ -221,7 +221,7 @@ const DownloadUpload = () => { {LL.DOWNLOAD(0)} - + {LL.DOWNLOAD_SETTINGS_TEXT()} @@ -269,7 +269,7 @@ const DownloadUpload = () => { - {LL.UPLOAD_TEXT()} + {LL.UPLOAD_TEXT()} diff --git a/interface/src/app/status/APStatus.tsx b/interface/src/app/status/APStatus.tsx index ee6bc22ab..2f4903976 100644 --- a/interface/src/app/status/APStatus.tsx +++ b/interface/src/app/status/APStatus.tsx @@ -38,7 +38,7 @@ const APStatus = () => { data, send: loadData, error - } = useAutoRequest(APApi.readAPStatus, { pollingTime: 5000 }); + } = useAutoRequest(APApi.readAPStatus, { pollingTime: 3000 }); const { LL } = useI18nContext(); useLayoutTitle(LL.STATUS_OF(LL.ACCESS_POINT(0))); diff --git a/interface/src/app/status/Activity.tsx b/interface/src/app/status/Activity.tsx index b91073fc6..6e1a78c50 100644 --- a/interface/src/app/status/Activity.tsx +++ b/interface/src/app/status/Activity.tsx @@ -21,7 +21,7 @@ const SystemActivity = () => { data, send: loadData, error - } = useAutoRequest(readActivity, { pollingTime: 2000 }); + } = useAutoRequest(readActivity, { pollingTime: 3000 }); const { LL } = useI18nContext(); diff --git a/interface/src/app/status/HardwareStatus.tsx b/interface/src/app/status/HardwareStatus.tsx index 50b21dc3e..93f8f3d1a 100644 --- a/interface/src/app/status/HardwareStatus.tsx +++ b/interface/src/app/status/HardwareStatus.tsx @@ -36,7 +36,7 @@ const HardwareStatus = () => { data, send: loadData, error - } = useAutoRequest(SystemApi.readSystemStatus, { pollingTime: 2000 }); + } = useAutoRequest(SystemApi.readSystemStatus, { pollingTime: 3000 }); const content = () => { if (!data) { diff --git a/interface/src/app/status/MqttStatus.tsx b/interface/src/app/status/MqttStatus.tsx index a1096f724..8f7207c1b 100644 --- a/interface/src/app/status/MqttStatus.tsx +++ b/interface/src/app/status/MqttStatus.tsx @@ -58,7 +58,7 @@ const MqttStatus = () => { data, send: loadData, error - } = useAutoRequest(MqttApi.readMqttStatus, { pollingTime: 5000 }); + } = useAutoRequest(MqttApi.readMqttStatus, { pollingTime: 3000 }); const { LL } = useI18nContext(); useLayoutTitle(LL.STATUS_OF('MQTT')); diff --git a/interface/src/app/status/NTPStatus.tsx b/interface/src/app/status/NTPStatus.tsx index 757d65225..a65ee8620 100644 --- a/interface/src/app/status/NTPStatus.tsx +++ b/interface/src/app/status/NTPStatus.tsx @@ -40,7 +40,7 @@ const NTPStatus = () => { data, send: loadData, error - } = useAutoRequest(NTPApi.readNTPStatus, { pollingTime: 5000 }); + } = useAutoRequest(NTPApi.readNTPStatus, { pollingTime: 3000 }); const [localTime, setLocalTime] = useState(''); const [settingTime, setSettingTime] = useState(false); diff --git a/interface/src/app/status/NetworkStatus.tsx b/interface/src/app/status/NetworkStatus.tsx index 92d9dff18..55112b295 100644 --- a/interface/src/app/status/NetworkStatus.tsx +++ b/interface/src/app/status/NetworkStatus.tsx @@ -85,7 +85,7 @@ const NetworkStatus = () => { data, send: loadData, error - } = useAutoRequest(NetworkApi.readNetworkStatus, { pollingTime: 5000 }); + } = useAutoRequest(NetworkApi.readNetworkStatus, { pollingTime: 3000 }); const { LL } = useI18nContext(); useLayoutTitle(LL.STATUS_OF(LL.NETWORK(1))); diff --git a/interface/src/app/status/RestartMonitor.tsx b/interface/src/app/status/RestartMonitor.tsx index d95487d59..d3a7cede8 100644 --- a/interface/src/app/status/RestartMonitor.tsx +++ b/interface/src/app/status/RestartMonitor.tsx @@ -28,7 +28,7 @@ const RestartMonitor = () => { initialData: { status: 'Getting ready...' }, async middleware(_, next) { if (count++ >= 1) { - // skip first request (1 seconds) to allow AsyncWS to send its response + // skip first request (1 second) to allow AsyncWS to send its response await next(); } } diff --git a/interface/src/app/status/Status.tsx b/interface/src/app/status/Status.tsx index 74a6f6747..f3a53dbdd 100644 --- a/interface/src/app/status/Status.tsx +++ b/interface/src/app/status/Status.tsx @@ -65,7 +65,7 @@ const SystemStatus = () => { error } = useAutoRequest(readSystemStatus, { initialData: [], - pollingTime: 5000, + pollingTime: 3000, async middleware(_, next) { if (!restarting) { await next(); diff --git a/interface/src/i18n/de/index.ts b/interface/src/i18n/de/index.ts index 03ad2f1fd..3faa417ac 100644 --- a/interface/src/i18n/de/index.ts +++ b/interface/src/i18n/de/index.ts @@ -340,7 +340,10 @@ const de: Translation = { PLEASE_WAIT: 'Bitte warten', RESTARTING_PRE: 'Initialisierung', RESTARTING_POST: 'Vorbereitung', - AUTO_SCROLL: 'Automatisches Scrollen' + AUTO_SCROLL: 'Automatisches Scrollen', + DASHBOARD: 'Dashboard', // TODO translate + NO_DATA: 'No data available', // TODO translate + DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorites using the Customizations module.' // TODO translate }; export default de; diff --git a/interface/src/i18n/en/index.ts b/interface/src/i18n/en/index.ts index b9f48abd2..dc4097adc 100644 --- a/interface/src/i18n/en/index.ts +++ b/interface/src/i18n/en/index.ts @@ -340,7 +340,10 @@ const en: Translation = { PLEASE_WAIT: 'Please wait', RESTARTING_PRE: 'Initializing', RESTARTING_POST: 'Preparing', - AUTO_SCROLL: 'Auto Scroll' + AUTO_SCROLL: 'Auto Scroll', + DASHBOARD: 'Dashboard', + NO_DATA: 'No data available', + DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorites using the Customizations module.', }; export default en; diff --git a/interface/src/i18n/fr/index.ts b/interface/src/i18n/fr/index.ts index d9ceff889..65bc641f0 100644 --- a/interface/src/i18n/fr/index.ts +++ b/interface/src/i18n/fr/index.ts @@ -340,7 +340,10 @@ const fr: Translation = { PLEASE_WAIT: 'Please wait', // TODO translate RESTARTING_PRE: 'Initializing', // TODO translate RESTARTING_POST: 'Preparing', // TODO translate - AUTO_SCROLL: 'Auto Scroll' // TODO translate + AUTO_SCROLL: 'Auto Scroll', // TODO translate + DASHBOARD: 'Dashboard', // TODO translate + NO_DATA: 'No data available', // TODO translate + DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorites using the Customizations module.' // TODO translate }; export default fr; diff --git a/interface/src/i18n/it/index.ts b/interface/src/i18n/it/index.ts index 4dbc4b5a1..ce9f1c48b 100644 --- a/interface/src/i18n/it/index.ts +++ b/interface/src/i18n/it/index.ts @@ -340,7 +340,10 @@ const it: Translation = { PLEASE_WAIT: 'Please wait', // TODO translate RESTARTING_PRE: 'Initializing', // TODO translate RESTARTING_POST: 'Preparing', // TODO translate - AUTO_SCROLL: 'Auto Scroll' // TODO translate + AUTO_SCROLL: 'Auto Scroll', // TODO translate + DASHBOARD: 'Dashboard', // TODO translate + NO_DATA: 'No data available', // TODO translate + DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorites using the Customizations module.' // TODO translate }; export default it; diff --git a/interface/src/i18n/nl/index.ts b/interface/src/i18n/nl/index.ts index 688b83c43..bbc44ad59 100644 --- a/interface/src/i18n/nl/index.ts +++ b/interface/src/i18n/nl/index.ts @@ -340,7 +340,10 @@ const nl: Translation = { PLEASE_WAIT: 'Please wait', // TODO translate RESTARTING_PRE: 'Initializing', // TODO translate RESTARTING_POST: 'Preparing', // TODO translate - AUTO_SCROLL: 'Auto Scroll' // TODO translate + AUTO_SCROLL: 'Auto Scroll', // TODO translate + DASHBOARD: 'Dashboard', // TODO translate + NO_DATA: 'No data available', // TODO translate + DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorites using the Customizations module.' // TODO translate }; export default nl; diff --git a/interface/src/i18n/no/index.ts b/interface/src/i18n/no/index.ts index a019130d5..830bbb2b2 100644 --- a/interface/src/i18n/no/index.ts +++ b/interface/src/i18n/no/index.ts @@ -340,7 +340,10 @@ const no: Translation = { PLEASE_WAIT: 'Please wait', // TODO translate RESTARTING_PRE: 'Initializing', // TODO translate RESTARTING_POST: 'Preparing', // TODO translate - AUTO_SCROLL: 'Auto Scroll' // TODO translate + AUTO_SCROLL: 'Auto Scroll', // TODO translate + DASHBOARD: 'Dashboard', // TODO translate + NO_DATA: 'No data available', // TODO translate + DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorites using the Customizations module.' // TODO translate }; export default no; diff --git a/interface/src/i18n/pl/index.ts b/interface/src/i18n/pl/index.ts index 09d12cff7..adeed62b1 100644 --- a/interface/src/i18n/pl/index.ts +++ b/interface/src/i18n/pl/index.ts @@ -340,7 +340,10 @@ const pl: BaseTranslation = { PLEASE_WAIT: 'Please wait', // TODO translate RESTARTING_PRE: 'Initializing', // TODO translate RESTARTING_POST: 'Preparing', // TODO translate - AUTO_SCROLL: 'Auto Scroll' // TODO translate + AUTO_SCROLL: 'Auto Scroll', // TODO translate + DASHBOARD: 'Dashboard', // TODO translate + NO_DATA: 'No data available', // TODO translate + DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorites using the Customizations module.' // TODO translate }; export default pl; diff --git a/interface/src/i18n/sk/index.ts b/interface/src/i18n/sk/index.ts index e4e68da93..c29a95fe7 100644 --- a/interface/src/i18n/sk/index.ts +++ b/interface/src/i18n/sk/index.ts @@ -340,7 +340,10 @@ const sk: Translation = { PLEASE_WAIT: 'Čakajte prosím', RESTARTING_PRE: 'Prebieha inicializácia', RESTARTING_POST: 'Príprava', - AUTO_SCROLL: 'Auto Scroll' // TODO translate + AUTO_SCROLL: 'Auto Scroll', // TODO translate + DASHBOARD: 'Dashboard', // TODO translate + NO_DATA: 'No data available', // TODO translate + DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorites using the Customizations module.' // TODO translate }; export default sk; diff --git a/interface/src/i18n/sv/index.ts b/interface/src/i18n/sv/index.ts index 0a7be5378..2b0900510 100644 --- a/interface/src/i18n/sv/index.ts +++ b/interface/src/i18n/sv/index.ts @@ -340,7 +340,10 @@ const sv: Translation = { PLEASE_WAIT: 'Please wait', // TODO translate RESTARTING_PRE: 'Initializing', // TODO translate RESTARTING_POST: 'Preparing', // TODO translate - AUTO_SCROLL: 'Auto Scroll' // TODO translate + AUTO_SCROLL: 'Auto Scroll', // TODO translate + DASHBOARD: 'Dashboard', // TODO translate + NO_DATA: 'No data available', // TODO translate + DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorites using the Customizations module.' // TODO translate }; export default sv; diff --git a/interface/src/i18n/tr/index.ts b/interface/src/i18n/tr/index.ts index a94ef0031..02add6145 100644 --- a/interface/src/i18n/tr/index.ts +++ b/interface/src/i18n/tr/index.ts @@ -340,7 +340,10 @@ const tr: Translation = { PLEASE_WAIT: 'Please wait', // TODO translate RESTARTING_PRE: 'Initializing', // TODO translate RESTARTING_POST: 'Preparing', // TODO translate - AUTO_SCROLL: 'Auto Scroll' // TODO translate + AUTO_SCROLL: 'Auto Scroll', // TODO translate + DASHBOARD: 'Dashboard', // TODO translate + NO_DATA: 'No data available', // TODO translate + DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorites using the Customizations module.' // TODO translate }; export default tr; diff --git a/interface/src/utils/index.ts b/interface/src/utils/index.ts index 7ee4414df..e704f66e5 100644 --- a/interface/src/utils/index.ts +++ b/interface/src/utils/index.ts @@ -3,4 +3,6 @@ export * from './route'; export * from './submit'; export * from './time'; export * from './useRest'; +export * from './useInterval'; export * from './props'; +export * from './file'; diff --git a/interface/src/utils/useInterval.ts b/interface/src/utils/useInterval.ts new file mode 100644 index 000000000..61ed2c9e6 --- /dev/null +++ b/interface/src/utils/useInterval.ts @@ -0,0 +1,22 @@ +import { useEffect, useRef } from 'react'; + +// adapted from https://www.joshwcomeau.com/snippets/react-hooks/use-interval/ +export const useInterval = (callback: () => void, delay: number) => { + const intervalRef = useRef(null); + const savedCallback = useRef<() => void>(callback); + useEffect(() => { + savedCallback.current = callback; + }, [callback]); + useEffect(() => { + const tick = () => savedCallback.current(); + if (typeof delay === 'number') { + intervalRef.current = window.setInterval(tick, delay); + return () => { + if (intervalRef.current !== null) { + window.clearInterval(intervalRef.current); + } + }; + } + }, [delay]); + return intervalRef; +}; diff --git a/interface/yarn.lock b/interface/yarn.lock index 643fa47d6..fc0860f80 100644 --- a/interface/yarn.lock +++ b/interface/yarn.lock @@ -918,10 +918,10 @@ __metadata: languageName: node linkType: hard -"@eslint/js@npm:9.11.1, @eslint/js@npm:^9.11.1": - version: 9.11.1 - resolution: "@eslint/js@npm:9.11.1" - checksum: 10c0/22916ef7b09c6f60c62635d897c66e1e3e38d90b5a5cf5e62769033472ecbcfb6ec7c886090a4b32fe65d6ce371da54384e46c26a899e38184dfc152c6152f7b +"@eslint/js@npm:9.12.0, @eslint/js@npm:^9.12.0": + version: 9.12.0 + resolution: "@eslint/js@npm:9.12.0" + checksum: 10c0/325650a59a1ce3d97c69441501ebaf415607248bacbe8c8ca35adc7cb73b524f592f266a75772f496b06f3239e3ee1996722a242148085f0ee5fb3dd7065897c languageName: node linkType: hard @@ -941,6 +941,23 @@ __metadata: languageName: node linkType: hard +"@humanfs/core@npm:^0.19.0": + version: 0.19.0 + resolution: "@humanfs/core@npm:0.19.0" + checksum: 10c0/f87952d5caba6ae427a620eff783c5d0b6cef0cfc256dec359cdaa636c5f161edb8d8dad576742b3de7f0b2f222b34aad6870248e4b7d2177f013426cbcda232 + languageName: node + linkType: hard + +"@humanfs/node@npm:^0.16.5": + version: 0.16.5 + resolution: "@humanfs/node@npm:0.16.5" + dependencies: + "@humanfs/core": "npm:^0.19.0" + "@humanwhocodes/retry": "npm:^0.3.0" + checksum: 10c0/41c365ab09e7c9eaeed373d09243195aef616d6745608a36fc3e44506148c28843872f85e69e2bf5f1e992e194286155a1c1cecfcece6a2f43875e37cd243935 + languageName: node + linkType: hard + "@humanwhocodes/module-importer@npm:^1.0.1": version: 1.0.1 resolution: "@humanwhocodes/module-importer@npm:1.0.1" @@ -955,6 +972,13 @@ __metadata: languageName: node linkType: hard +"@humanwhocodes/retry@npm:^0.3.1": + version: 0.3.1 + resolution: "@humanwhocodes/retry@npm:0.3.1" + checksum: 10c0/f0da1282dfb45e8120480b9e2e275e2ac9bbe1cf016d046fdad8e27cc1285c45bb9e711681237944445157b430093412b4446c1ab3fc4bb037861b5904101d3b + languageName: node + linkType: hard + "@isaacs/cliui@npm:^8.0.2": version: 8.0.2 resolution: "@isaacs/cliui@npm:8.0.2" @@ -1196,7 +1220,7 @@ __metadata: languageName: node linkType: hard -"@nodelib/fs.walk@npm:^1.2.3, @nodelib/fs.walk@npm:^1.2.8": +"@nodelib/fs.walk@npm:^1.2.3": version: 1.2.8 resolution: "@nodelib/fs.walk@npm:1.2.8" dependencies: @@ -1844,7 +1868,7 @@ __metadata: "@babel/core": "npm:^7.25.7" "@emotion/react": "npm:^11.13.3" "@emotion/styled": "npm:^11.13.0" - "@eslint/js": "npm:^9.11.1" + "@eslint/js": "npm:^9.12.0" "@mui/icons-material": "npm:^6.1.2" "@mui/material": "npm:^6.1.2" "@preact/compat": "npm:^18.3.1" @@ -1859,7 +1883,7 @@ __metadata: alova: "npm:3.0.17" async-validator: "npm:^4.2.5" concurrently: "npm:^9.0.1" - eslint: "npm:^9.11.1" + eslint: "npm:^9.12.0" eslint-config-prettier: "npm:^9.1.0" formidable: "npm:^3.5.1" jwt-decode: "npm:^4.0.0" @@ -3322,7 +3346,7 @@ __metadata: languageName: node linkType: hard -"eslint-scope@npm:^8.0.2": +"eslint-scope@npm:^8.1.0": version: 8.1.0 resolution: "eslint-scope@npm:8.1.0" dependencies: @@ -3339,27 +3363,27 @@ __metadata: languageName: node linkType: hard -"eslint-visitor-keys@npm:^4.0.0, eslint-visitor-keys@npm:^4.1.0": +"eslint-visitor-keys@npm:^4.1.0": version: 4.1.0 resolution: "eslint-visitor-keys@npm:4.1.0" checksum: 10c0/5483ef114c93a136aa234140d7aa3bd259488dae866d35cb0d0b52e6a158f614760a57256ac8d549acc590a87042cb40f6951815caa821e55dc4fd6ef4c722eb languageName: node linkType: hard -"eslint@npm:^9.11.1": - version: 9.11.1 - resolution: "eslint@npm:9.11.1" +"eslint@npm:^9.12.0": + version: 9.12.0 + resolution: "eslint@npm:9.12.0" dependencies: "@eslint-community/eslint-utils": "npm:^4.2.0" "@eslint-community/regexpp": "npm:^4.11.0" "@eslint/config-array": "npm:^0.18.0" "@eslint/core": "npm:^0.6.0" "@eslint/eslintrc": "npm:^3.1.0" - "@eslint/js": "npm:9.11.1" + "@eslint/js": "npm:9.12.0" "@eslint/plugin-kit": "npm:^0.2.0" + "@humanfs/node": "npm:^0.16.5" "@humanwhocodes/module-importer": "npm:^1.0.1" - "@humanwhocodes/retry": "npm:^0.3.0" - "@nodelib/fs.walk": "npm:^1.2.8" + "@humanwhocodes/retry": "npm:^0.3.1" "@types/estree": "npm:^1.0.6" "@types/json-schema": "npm:^7.0.15" ajv: "npm:^6.12.4" @@ -3367,9 +3391,9 @@ __metadata: cross-spawn: "npm:^7.0.2" debug: "npm:^4.3.2" escape-string-regexp: "npm:^4.0.0" - eslint-scope: "npm:^8.0.2" - eslint-visitor-keys: "npm:^4.0.0" - espree: "npm:^10.1.0" + eslint-scope: "npm:^8.1.0" + eslint-visitor-keys: "npm:^4.1.0" + espree: "npm:^10.2.0" esquery: "npm:^1.5.0" esutils: "npm:^2.0.2" fast-deep-equal: "npm:^3.1.3" @@ -3379,13 +3403,11 @@ __metadata: ignore: "npm:^5.2.0" imurmurhash: "npm:^0.1.4" is-glob: "npm:^4.0.0" - is-path-inside: "npm:^3.0.3" json-stable-stringify-without-jsonify: "npm:^1.0.1" lodash.merge: "npm:^4.6.2" minimatch: "npm:^3.1.2" natural-compare: "npm:^1.4.0" optionator: "npm:^0.9.3" - strip-ansi: "npm:^6.0.1" text-table: "npm:^0.2.0" peerDependencies: jiti: "*" @@ -3394,11 +3416,11 @@ __metadata: optional: true bin: eslint: bin/eslint.js - checksum: 10c0/fc9afc31155fef8c27fc4fd00669aeafa4b89ce5abfbf6f60e05482c03d7ff1d5e7546e416aa47bf0f28c9a56597a94663fd0264c2c42a1890f53cac49189f24 + checksum: 10c0/67cf6ea3ea28dcda7dd54aac33e2d4028eb36991d13defb0d2339c3eaa877d5dddd12cd4416ddc701a68bcde9e0bb9e65524c2e4e9914992c724f5b51e949dda languageName: node linkType: hard -"espree@npm:^10.0.1, espree@npm:^10.1.0": +"espree@npm:^10.0.1, espree@npm:^10.2.0": version: 10.2.0 resolution: "espree@npm:10.2.0" dependencies: @@ -4552,13 +4574,6 @@ __metadata: languageName: node linkType: hard -"is-path-inside@npm:^3.0.3": - version: 3.0.3 - resolution: "is-path-inside@npm:3.0.3" - checksum: 10c0/cf7d4ac35fb96bab6a1d2c3598fe5ebb29aafb52c0aaa482b5a3ed9d8ba3edc11631e3ec2637660c44b3ce0e61a08d54946e8af30dec0b60a7c27296c68ffd05 - languageName: node - linkType: hard - "is-plain-obj@npm:^1.0.0, is-plain-obj@npm:^1.1.0": version: 1.1.0 resolution: "is-plain-obj@npm:1.1.0" diff --git a/mock-api/rest_server.ts b/mock-api/rest_server.ts index ccd725973..52b720f84 100644 --- a/mock-api/rest_server.ts +++ b/mock-api/rest_server.ts @@ -4294,31 +4294,19 @@ function getDashboardEntityData(id: number) { else if (id == 10) device_data = emsesp_devicedata_10; else if (id == 99) device_data = emsesp_devicedata_99; - // filter device_data - // and only favorite items (bit 8 set), only for non-Custom Entities - // and replace id by striping off the 2-char mask + // filter device_data on + // - only add favorite (mask has bit 8 set) except for Custom Entities (type 99) let new_data = (device_data as any).data - .map(({ id, m, x, s, ...rest }) => ({ - ...rest, - id2: id - })) - .filter((item) => id === 99 || parseInt(item.id2.slice(0, 2), 16) & 0x08) + .filter((item) => id === 99 || parseInt(item.id.slice(0, 2), 16) & 0x08) .map((item, index) => ({ - id: id * 100 + index, // unique id - n: item.id2.slice(2), // name - v: item.v, // value - u: item.u, // uom - c: item.c, // command - l: item.l, // list - h: item.h // help + id: id * 100 + index, // mandatory unique id for table + dv: item // devicevalue })); - // TODO only and command if not marked as READONLY - return new_data; } -// Router starts here... +// Router routing starts here... router // EMS-ESP Settings .get(EMSESP_SETTINGS_ENDPOINT, () => settings) @@ -4360,8 +4348,10 @@ router []; let dashboard_object: { id?: number; n?: string; t?: number; nodes?: any[] } = {}; + let fake = false; - // let fake = true; // fakes no data + + fake = true; // for testing if (!fake) { // pick EMS devices from coredata @@ -4397,9 +4387,11 @@ router let sensor_data: any[] = []; sensor_data = emsesp_sensordata.ts.map((item, index) => ({ id: 980 + index, - n: item.n ? item.n : item.id, // name may not be set - v: item.t ? item.t : undefined, // can have no value - u: item.u + dv: { + id: ' ' + item.n, + v: item.t, // value is called t in ts (temperature) + u: item.u + } })); dashboard_object = { id: 98, @@ -4417,9 +4409,11 @@ router sensor_data = emsesp_sensordata.as.filter((item) => item.t !== 0); sensor_data = sensor_data.map((item, index) => ({ id: 970 + index, - n: item.n, - v: item.v, - u: item.u + dv: { + id: ' ' + item.n, + v: item.v, + u: item.u + } })); dashboard_object = { @@ -4432,9 +4426,24 @@ router if ((dashboard_object.nodes ?? []).length > 0) { dashboard_data.push(dashboard_object); } + } else { + // for testing + // single object + // const element = emsesp_coredata.devices[3]; // pick the 4th device + // const id = element.id; + // dashboard_object = { + // id: id, + // n: element.n, + // t: element.t, + // nodes: getDashboardEntityData(id) + // }; + // if ((dashboard_object.nodes ?? []).length > 0) { + // dashboard_data.push(dashboard_object); + // } } // console.log('dashboard_data: ', dashboard_data); + // return dashboard_data; // if not using msgpack return new Response(encoder.encode(dashboard_data), { headers }); // msgpack it }) @@ -4570,7 +4579,9 @@ router } // await delay(1000); // wait to show spinner - console.log('device value saved', content); + console.log( + 'Device Value updated. command:' + command + ' value:' + value + ' id:' + id + ); return status(200); }) From 812df3640b6c3ee746c9819ee531462213e7c60a Mon Sep 17 00:00:00 2001 From: proddy Date: Sat, 5 Oct 2024 16:28:28 +0200 Subject: [PATCH 09/66] turn on test data --- mock-api/rest_server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mock-api/rest_server.ts b/mock-api/rest_server.ts index 52b720f84..76baedc80 100644 --- a/mock-api/rest_server.ts +++ b/mock-api/rest_server.ts @@ -4351,7 +4351,7 @@ router let fake = false; - fake = true; // for testing + // fake = true; // for testing if (!fake) { // pick EMS devices from coredata From 87cea5865ae5db64570f29b0dc8a8324101085a0 Mon Sep 17 00:00:00 2001 From: proddy Date: Tue, 8 Oct 2024 22:49:29 +0200 Subject: [PATCH 10/66] optimize with useMemo --- interface/src/components/layout/Layout.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/interface/src/components/layout/Layout.tsx b/interface/src/components/layout/Layout.tsx index a11d194d4..c02b26f46 100644 --- a/interface/src/components/layout/Layout.tsx +++ b/interface/src/components/layout/Layout.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import type { FC } from 'react'; import { useLocation } from 'react-router-dom'; @@ -24,9 +24,11 @@ const Layout: FC = ({ children }) => { useEffect(() => setMobileOpen(false), [pathname]); + // cache the object to prevent unnecessary re-renders + const obj = useMemo(() => ({ title, setTitle }), [title]); + return ( - // TODO wrap title/setTitle in a useMemo() - + From 5d69ce18a2bf6fd578e10a904039fa6deaa97f69 Mon Sep 17 00:00:00 2001 From: proddy Date: Tue, 8 Oct 2024 22:49:44 +0200 Subject: [PATCH 11/66] optimize with useMemo --- .../contexts/authentication/Authentication.tsx | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/interface/src/contexts/authentication/Authentication.tsx b/interface/src/contexts/authentication/Authentication.tsx index ff2c77395..5691ab76e 100644 --- a/interface/src/contexts/authentication/Authentication.tsx +++ b/interface/src/contexts/authentication/Authentication.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import type { FC } from 'react'; import { redirect } from 'react-router-dom'; import { toast } from 'react-toastify'; @@ -67,17 +67,15 @@ const Authentication: FC = ({ children }) => { void refresh(); }, [refresh]); + // cache object to prevent re-renders + const obj = useMemo( + () => ({ signIn, signOut, me, refresh }), + [signIn, signOut, me, refresh] + ); + if (initialized) { return ( - // TODO useMemo? - + {children} ); From 1681d992384e7f0933f0f60b909f41ce33dfc989 Mon Sep 17 00:00:00 2001 From: proddy Date: Tue, 8 Oct 2024 22:50:06 +0200 Subject: [PATCH 12/66] replace data with nodes --- interface/src/app/main/Devices.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/interface/src/app/main/Devices.tsx b/interface/src/app/main/Devices.tsx index a075279df..2a6aef8ee 100644 --- a/interface/src/app/main/Devices.tsx +++ b/interface/src/app/main/Devices.tsx @@ -96,7 +96,7 @@ const Devices = () => { (id: number) => readDeviceData(id), { initialData: { - data: [] + nodes: [] }, immediate: false } @@ -252,7 +252,7 @@ const Devices = () => { }; const dv_sort = useSort( - { nodes: deviceData.data }, + { nodes: deviceData.nodes }, {}, { sortIcon: { @@ -384,8 +384,8 @@ const Devices = () => { ]; const data = onlyFav - ? deviceData.data.filter((dv) => hasMask(dv.id, DeviceEntityMask.DV_FAVORITE)) - : deviceData.data; + ? deviceData.nodes.filter((dv) => hasMask(dv.id, DeviceEntityMask.DV_FAVORITE)) + : deviceData.nodes; const csvData = data.reduce( (csvString: string, rowItem: DeviceValue) => @@ -606,8 +606,8 @@ const Devices = () => { ); const shown_data = onlyFav - ? deviceData.data.filter((dv) => hasMask(dv.id, DeviceEntityMask.DV_FAVORITE)) - : deviceData.data; + ? deviceData.nodes.filter((dv) => hasMask(dv.id, DeviceEntityMask.DV_FAVORITE)) + : deviceData.nodes; const deviceIndex = coreData.devices.findIndex( (d) => d.id === device_select.state.id From 59c48665302299c5fc4a91766272dd54cd7a741b Mon Sep 17 00:00:00 2001 From: proddy Date: Tue, 8 Oct 2024 22:50:19 +0200 Subject: [PATCH 13/66] updated for Dashboard --- mock-api/rest_server.ts | 158 ++++++++++++++++++++-------------------- 1 file changed, 79 insertions(+), 79 deletions(-) diff --git a/mock-api/rest_server.ts b/mock-api/rest_server.ts index 76baedc80..09080f018 100644 --- a/mock-api/rest_server.ts +++ b/mock-api/rest_server.ts @@ -80,22 +80,22 @@ function updateMask(entity: any, de: any, dd: any) { } // find in dd, either looking for fullname or custom name - // console.log('looking for ' + fullname + ' in ' + dd.data); - const dd_objIndex = dd.data.findIndex( + // console.log('looking for ' + fullname + ' in ' + dd.nodes); + const dd_objIndex = dd.nodes.findIndex( (obj: any) => obj.id.slice(2) === fullname ); if (dd_objIndex !== -1) { let changed = new Boolean(false); // see if the mask has changed - const old_mask = parseInt(dd.data[dd_objIndex].id.slice(0, 2), 16); + const old_mask = parseInt(dd.nodes[dd_objIndex].id.slice(0, 2), 16); if (old_mask !== current_mask) { changed = true; console.log('mask has changed to ' + current_mask.toString(16)); } // see if the custom name has changed - const old_custom_name = dd.data[dd_objIndex].cn; + const old_custom_name = dd.nodes[dd_objIndex].cn; console.log( 'comparing names, old (' + old_custom_name + @@ -113,8 +113,8 @@ function updateMask(entity: any, de: any, dd: any) { // see if min or max has changed // get current min/max values if they exist - const current_min = dd.data[dd_objIndex].min; - const current_max = dd.data[dd_objIndex].max; + const current_min = dd.nodes[dd_objIndex].min; + const current_max = dd.nodes[dd_objIndex].max; let new_min = current_min; let new_max = current_max; if (has_min_max) { @@ -135,9 +135,9 @@ function updateMask(entity: any, de: any, dd: any) { if (new_max) { de[de_objIndex].ma = new_max; } - dd.data[dd_objIndex].id = + dd.nodes[dd_objIndex].id = current_mask.toString(16).padStart(2, '0') + new_fullname; - dd.data[dd_objIndex].cn = new_fullname; + dd.nodes[dd_objIndex].cn = new_fullname; } } } @@ -906,7 +906,7 @@ const activity = { // 99 - Custom const emsesp_devicedata_1 = { - data: [ + nodes: [ { v: '22(816) 01.05.2023 13:07 (1 min)', u: 0, @@ -1350,7 +1350,7 @@ const emsesp_devicedata_1 = { }; const emsesp_devicedata_2 = { - data: [ + nodes: [ { v: '(0)', u: 0, @@ -1364,7 +1364,7 @@ const emsesp_devicedata_2 = { { v: 18.2, u: 1, - id: '00Chosen Room Temperature', + id: '08Chosen Room Temperature', c: 'hc1/seltemp', m: 5, x: 52, @@ -1373,7 +1373,7 @@ const emsesp_devicedata_2 = { { v: 22.6, u: 1, - id: '00hc1 current room temperature' + id: '08hc1 current room temperature' }, { v: 'auto', @@ -1393,7 +1393,7 @@ const emsesp_devicedata_2 = { }; const emsesp_devicedata_3 = { - data: [ + nodes: [ { v: '', u: 0, @@ -1889,7 +1889,7 @@ const emsesp_devicedata_3 = { }; const emsesp_devicedata_4 = { - data: [ + nodes: [ { v: 16, u: 1, @@ -1905,7 +1905,7 @@ const emsesp_devicedata_4 = { { v: 'off', u: 0, - id: '02hc2 mode', + id: '03hc2 mode', c: 'hc2/mode', l: ['off', 'on', 'auto'] } @@ -1913,7 +1913,7 @@ const emsesp_devicedata_4 = { }; const emsesp_devicedata_5 = { - data: [ + nodes: [ { v: 30, u: 1, @@ -1960,7 +1960,7 @@ const emsesp_devicedata_5 = { }; const emsesp_devicedata_6 = { - data: [ + nodes: [ { v: 43.9, u: 1, @@ -2109,7 +2109,7 @@ const emsesp_devicedata_6 = { }; const emsesp_devicedata_7 = { - data: [ + nodes: [ { v: '', u: 0, id: '08reset', c: 'reset', l: ['-', 'maintenance', 'error'] }, { v: 'off', u: 0, id: '08heating active' }, { v: 'off', u: 0, id: '04tapwater active' }, @@ -2252,7 +2252,7 @@ const emsesp_devicedata_7 = { }; const emsesp_devicedata_8 = { - data: [ + nodes: [ { v: '', u: 0, @@ -2292,22 +2292,22 @@ const emsesp_devicedata_8 = { id: '00heating pump modulation' }, { - v: 30.299999237060547, + v: 30.29, u: 1, id: '00outside temperature' }, { - v: 18.700000762939453, + v: 18.7, u: 1, id: '00current flow temperature' }, { - v: 21.399999618530273, + v: 21.39, u: 1, id: '00return temperature' }, { - v: 18.700000762939453, + v: 18.7, u: 1, id: '00low loss header' }, @@ -2592,7 +2592,7 @@ const emsesp_devicedata_8 = { id: '00brine out/condenser' }, { - v: 21.399999618530273, + v: 21.39, u: 1, id: '00heat carrier return (TC0)' }, @@ -2607,12 +2607,12 @@ const emsesp_devicedata_8 = { id: '00condenser temperature (TC3)' }, { - v: 51.599998474121094, + v: 51.59, u: 1, id: '00compressor temperature (TR1)' }, { - v: 14.600000381469727, + v: 14.6, u: 1, id: '00refrigerant temperature liquid side (condenser output) (TR3)' }, @@ -2622,32 +2622,32 @@ const emsesp_devicedata_8 = { id: '00evaporator inlet temperature (TR4)' }, { - v: 20.200000762939453, + v: 20.2, u: 1, id: '00compressor inlet temperature (TR5)' }, { - v: 54.599998474121094, + v: 54.59, u: 1, id: '00compressor outlet temperature (TR6)' }, { - v: 29.600000381469727, + v: 29.6, u: 1, id: '00air inlet temperature (TL2)' }, { - v: 13.899999618530273, + v: 13.89, u: 1, id: '00low pressure side temperature (PL1)' }, { - v: 37.79999923706055, + v: 37.79, u: 1, id: '00high pressure side temperature (PH1)' }, { - v: 25.600000381469727, + v: 25.6, u: 1, id: '00drain pan temp (TA4)' }, @@ -2791,7 +2791,7 @@ const emsesp_devicedata_8 = { s: '0.1' }, { - v: 0.10000000149011612, + v: 0.1, u: 22, id: '00aux heater limit start', c: 'auxlimitstart', @@ -3157,7 +3157,7 @@ const emsesp_devicedata_8 = { l: ['off', 'on'] }, { - v: 58.70000076293945, + v: 58.7, u: 1, id: '00dhw current intern temperature' }, @@ -3226,7 +3226,7 @@ const emsesp_devicedata_8 = { }; const emsesp_devicedata_9 = { - data: [ + nodes: [ { v: 24, u: 1, @@ -3255,7 +3255,7 @@ const emsesp_devicedata_9 = { }; const emsesp_devicedata_10 = { - data: [ + nodes: [ { v: '26.06.2024 14:49', u: 0, @@ -3744,7 +3744,7 @@ const emsesp_devicedata_10 = { }; const emsesp_devicedata_99 = { - data: [ + nodes: [ { v: 5, u: 1, @@ -3944,7 +3944,7 @@ const emsesp_deviceentities_2 = [ v: 18.2, n: 'Chosen Room Temperature', id: 'hc1/seltemp', - m: 0, + m: 8, mi: 5, ma: 52, w: true @@ -3953,7 +3953,7 @@ const emsesp_deviceentities_2 = [ v: 22.6, n: 'hc1 current room temperature', id: 'hc1/curtemp', - m: 0, + m: 8, w: false }, { @@ -3984,7 +3984,7 @@ const emsesp_deviceentities_4 = [ v: 'off', n: 'hc2 mode', id: 'hc2/mode', - m: 2, + m: 3, w: true } ]; @@ -4230,7 +4230,7 @@ function deviceData(id: number) { } if (id == 8) { // test changing the selected flow temp on a Bosch Compress 7000i AW Heat Pump (Boiler/HP) - emsesp_devicedata_8.data[4].v = Math.floor(Math.random() * 100); + emsesp_devicedata_8.nodes[4].v = Math.floor(Math.random() * 100); return new Response(encoder.encode(emsesp_devicedata_8), { headers }); } if (id == 9) { @@ -4296,7 +4296,7 @@ function getDashboardEntityData(id: number) { // filter device_data on // - only add favorite (mask has bit 8 set) except for Custom Entities (type 99) - let new_data = (device_data as any).data + let new_data = (device_data as any).nodes .filter((item) => id === 99 || parseInt(item.id.slice(0, 2), 16) & 0x08) .map((item, index) => ({ id: id * 100 + index, // mandatory unique id for table @@ -4373,8 +4373,8 @@ router // add the custom entity data dashboard_object = { - id: 99, - n: 'Custom Entities', + id: 99, // unique ID for custom entities + n: 'Custom Entities', // this is translated in the C++ code t: 4, // DeviceType::CUSTOM nodes: getDashboardEntityData(99) }; @@ -4429,20 +4429,20 @@ router } else { // for testing // single object - // const element = emsesp_coredata.devices[3]; // pick the 4th device - // const id = element.id; - // dashboard_object = { - // id: id, - // n: element.n, - // t: element.t, - // nodes: getDashboardEntityData(id) - // }; - // if ((dashboard_object.nodes ?? []).length > 0) { - // dashboard_data.push(dashboard_object); - // } + const element = emsesp_coredata.devices[3]; // pick the 4th device + const id = element.id; + dashboard_object = { + id: id, + n: element.n, + t: element.t, + nodes: getDashboardEntityData(id) + }; + if ((dashboard_object.nodes ?? []).length > 0) { + dashboard_data.push(dashboard_object); + } + console.log('dashboard_data: ', dashboard_data); } - // console.log('dashboard_data: ', dashboard_data); // return dashboard_data; // if not using msgpack return new Response(encoder.encode(dashboard_data), { headers }); // msgpack it }) @@ -4524,7 +4524,7 @@ router }) .get(EMSESP_CUSTOMENTITIES_ENDPOINT, () => emsesp_customentities) - // Device Dashboard + // Devices page .post(EMSESP_WRITE_DEVICEVALUE_ENDPOINT, async (request: any) => { const content = await request.json(); const command = content.c; @@ -4533,49 +4533,49 @@ router var objIndex; if (id === 1) { - objIndex = emsesp_devicedata_1.data.findIndex((obj) => obj.c == command); - emsesp_devicedata_1.data[objIndex].v = value; + objIndex = emsesp_devicedata_1.nodes.findIndex((obj) => obj.c == command); + emsesp_devicedata_1.nodes[objIndex].v = value; } if (id === 2) { - objIndex = emsesp_devicedata_2.data.findIndex((obj) => obj.c == command); - emsesp_devicedata_2.data[objIndex].v = value; + objIndex = emsesp_devicedata_2.nodes.findIndex((obj) => obj.c == command); + emsesp_devicedata_2.nodes[objIndex].v = value; } if (id === 3) { - objIndex = emsesp_devicedata_3.data.findIndex((obj) => obj.c == command); - emsesp_devicedata_3.data[objIndex].v = value; + objIndex = emsesp_devicedata_3.nodes.findIndex((obj) => obj.c == command); + emsesp_devicedata_3.nodes[objIndex].v = value; } if (id === 4) { - objIndex = emsesp_devicedata_4.data.findIndex((obj) => obj.c == command); - emsesp_devicedata_4.data[objIndex].v = value; + objIndex = emsesp_devicedata_4.nodes.findIndex((obj) => obj.c == command); + emsesp_devicedata_4.nodes[objIndex].v = value; } if (id === 5) { - objIndex = emsesp_devicedata_5.data.findIndex((obj) => obj.c == command); - emsesp_devicedata_5.data[objIndex].v = value; + objIndex = emsesp_devicedata_5.nodes.findIndex((obj) => obj.c == command); + emsesp_devicedata_5.nodes[objIndex].v = value; } if (id === 6) { - objIndex = emsesp_devicedata_6.data.findIndex((obj) => obj.c == command); - emsesp_devicedata_6.data[objIndex].v = value; + objIndex = emsesp_devicedata_6.nodes.findIndex((obj) => obj.c == command); + emsesp_devicedata_6.nodes[objIndex].v = value; } if (id === 7) { - objIndex = emsesp_devicedata_7.data.findIndex((obj) => obj.c == command); - emsesp_devicedata_7.data[objIndex].v = value; + objIndex = emsesp_devicedata_7.nodes.findIndex((obj) => obj.c == command); + emsesp_devicedata_7.nodes[objIndex].v = value; } if (id === 8) { - objIndex = emsesp_devicedata_8.data.findIndex((obj) => obj.c == command); - emsesp_devicedata_8.data[objIndex].v = value; + objIndex = emsesp_devicedata_8.nodes.findIndex((obj) => obj.c == command); + emsesp_devicedata_8.nodes[objIndex].v = value; } if (id === 9) { - objIndex = emsesp_devicedata_9.data.findIndex((obj) => obj.c == command); - emsesp_devicedata_9.data[objIndex].v = value; + objIndex = emsesp_devicedata_9.nodes.findIndex((obj) => obj.c == command); + emsesp_devicedata_9.nodes[objIndex].v = value; } if (id === 10) { - objIndex = emsesp_devicedata_10.data.findIndex((obj) => obj.c == command); - emsesp_devicedata_10.data[objIndex].v = value; + objIndex = emsesp_devicedata_10.nodes.findIndex((obj) => obj.c == command); + emsesp_devicedata_10.nodes[objIndex].v = value; } if (id === 99) { // custom entities - objIndex = emsesp_devicedata_99.data.findIndex((obj) => obj.c == command); - emsesp_devicedata_99.data[objIndex].v = value; + objIndex = emsesp_devicedata_99.nodes.findIndex((obj) => obj.c == command); + emsesp_devicedata_99.nodes[objIndex].v = value; } // await delay(1000); // wait to show spinner From 0c3a83d3b302e763391c1e7e5282af28988638f3 Mon Sep 17 00:00:00 2001 From: proddy Date: Tue, 8 Oct 2024 22:50:28 +0200 Subject: [PATCH 14/66] ignore mjs files --- interface/eslint.config.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/interface/eslint.config.js b/interface/eslint.config.js index 281c28372..c53ece12c 100644 --- a/interface/eslint.config.js +++ b/interface/eslint.config.js @@ -16,7 +16,15 @@ export default tseslint.config( } }, { - ignores: ['dist/*', 'build/*', '*.js', '**/*.cjs', '**/unpack.ts', 'i18n*.*'] + ignores: [ + 'dist/*', + '*.mjs', + 'build/*', + '*.js', + '**/*.cjs', + '**/unpack.ts', + 'i18n*.*' + ] }, { rules: { From e7d9978e02cde9bbd96658990910518f3f83824e Mon Sep 17 00:00:00 2001 From: proddy Date: Tue, 8 Oct 2024 22:50:37 +0200 Subject: [PATCH 15/66] package update --- interface/package.json | 6 +- interface/yarn.lock | 139 ++++++++++++++++++++++------------------- 2 files changed, 77 insertions(+), 68 deletions(-) diff --git a/interface/package.json b/interface/package.json index 01082ee23..6d9080f73 100644 --- a/interface/package.json +++ b/interface/package.json @@ -27,7 +27,7 @@ "@mui/icons-material": "^6.1.2", "@mui/material": "^6.1.2", "@table-library/react-table-library": "4.1.7", - "alova": "3.0.17", + "alova": "3.0.20", "async-validator": "^4.2.5", "jwt-decode": "^4.0.0", "mime-types": "^2.1.35", @@ -47,7 +47,7 @@ "@preact/preset-vite": "^2.9.1", "@trivago/prettier-plugin-sort-imports": "^4.3.0", "@types/formidable": "^3", - "@types/node": "^22.7.4", + "@types/node": "^22.7.5", "@types/react": "^18.3.11", "@types/react-dom": "^18.3.0", "@types/react-router-dom": "^5.3.3", @@ -58,7 +58,7 @@ "prettier": "^3.3.3", "rollup-plugin-visualizer": "^5.12.0", "terser": "^5.34.1", - "typescript-eslint": "8.8.0", + "typescript-eslint": "8.8.1", "vite": "^5.4.8", "vite-plugin-imagemin": "^0.6.1", "vite-tsconfig-paths": "^5.0.1" diff --git a/interface/yarn.lock b/interface/yarn.lock index fc0860f80..4f6d26e75 100644 --- a/interface/yarn.lock +++ b/interface/yarn.lock @@ -1644,7 +1644,7 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:*, @types/node@npm:^22.7.4": +"@types/node@npm:*": version: 22.7.4 resolution: "@types/node@npm:22.7.4" dependencies: @@ -1653,6 +1653,15 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:^22.7.5": + version: 22.7.5 + resolution: "@types/node@npm:22.7.5" + dependencies: + undici-types: "npm:~6.19.2" + checksum: 10c0/cf11f74f1a26053ec58066616e3a8685b6bcd7259bc569738b8f752009f9f0f7f85a1b2d24908e5b0f752482d1e8b6babdf1fbb25758711ec7bb9500bfcd6e60 + languageName: node + linkType: hard + "@types/parse-json@npm:^4.0.0": version: 4.0.2 resolution: "@types/parse-json@npm:4.0.2" @@ -1744,15 +1753,15 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/eslint-plugin@npm:8.8.0": - version: 8.8.0 - resolution: "@typescript-eslint/eslint-plugin@npm:8.8.0" +"@typescript-eslint/eslint-plugin@npm:8.8.1": + version: 8.8.1 + resolution: "@typescript-eslint/eslint-plugin@npm:8.8.1" dependencies: "@eslint-community/regexpp": "npm:^4.10.0" - "@typescript-eslint/scope-manager": "npm:8.8.0" - "@typescript-eslint/type-utils": "npm:8.8.0" - "@typescript-eslint/utils": "npm:8.8.0" - "@typescript-eslint/visitor-keys": "npm:8.8.0" + "@typescript-eslint/scope-manager": "npm:8.8.1" + "@typescript-eslint/type-utils": "npm:8.8.1" + "@typescript-eslint/utils": "npm:8.8.1" + "@typescript-eslint/visitor-keys": "npm:8.8.1" graphemer: "npm:^1.4.0" ignore: "npm:^5.3.1" natural-compare: "npm:^1.4.0" @@ -1763,66 +1772,66 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 10c0/98ac37587eda02a713710f0a62ca979833482024968f1d1735881718abe102a6b49707db4f1dac0d7c731d1cbf8111d829c5125348d4829ab6fad7a7b3b344e4 + checksum: 10c0/020a0a482202b34c6665a56ec5902e38ae1870b2600ec1b2092de352b23099dde553781ee8323974f63962ebe164a6304f0019e937afb5cf7854b0e0163ad1ca languageName: node linkType: hard -"@typescript-eslint/parser@npm:8.8.0": - version: 8.8.0 - resolution: "@typescript-eslint/parser@npm:8.8.0" +"@typescript-eslint/parser@npm:8.8.1": + version: 8.8.1 + resolution: "@typescript-eslint/parser@npm:8.8.1" dependencies: - "@typescript-eslint/scope-manager": "npm:8.8.0" - "@typescript-eslint/types": "npm:8.8.0" - "@typescript-eslint/typescript-estree": "npm:8.8.0" - "@typescript-eslint/visitor-keys": "npm:8.8.0" + "@typescript-eslint/scope-manager": "npm:8.8.1" + "@typescript-eslint/types": "npm:8.8.1" + "@typescript-eslint/typescript-estree": "npm:8.8.1" + "@typescript-eslint/visitor-keys": "npm:8.8.1" debug: "npm:^4.3.4" peerDependencies: eslint: ^8.57.0 || ^9.0.0 peerDependenciesMeta: typescript: optional: true - checksum: 10c0/cf72a644b89c62cd55b09fa1d22b51a2c726714aac344a797f0c2ad80bfbabcb7567000fadd4ea8188aa1d923675bebdca06acc1d28ac1b8360bf28a36b46f3a + checksum: 10c0/2afd147ccec6754316d6837d6108a5d822eb6071e1a7355073288c232530bc3e49901d3f08755ce02d497110c531f3b3658eb46d0ff875a69d4f360b5f938cb4 languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:8.8.0": - version: 8.8.0 - resolution: "@typescript-eslint/scope-manager@npm:8.8.0" +"@typescript-eslint/scope-manager@npm:8.8.1": + version: 8.8.1 + resolution: "@typescript-eslint/scope-manager@npm:8.8.1" dependencies: - "@typescript-eslint/types": "npm:8.8.0" - "@typescript-eslint/visitor-keys": "npm:8.8.0" - checksum: 10c0/29ddf589ff0e465dbbf3eb87b79a29face4ec5a6cb617bbaafbac6ae8340d376b5b405bca762ee1c7a40cbdf7912a32734f9119f6864df048c7a0b2de21bdd3d + "@typescript-eslint/types": "npm:8.8.1" + "@typescript-eslint/visitor-keys": "npm:8.8.1" + checksum: 10c0/6f697baf087aedc3f0f228ff964fd108a9dd33fe4e5cc6c914be6367c324cee55629e099832668042bedfec8cdc72c6ef2ca960ee26966dbcc75753059a1352f languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:8.8.0": - version: 8.8.0 - resolution: "@typescript-eslint/type-utils@npm:8.8.0" +"@typescript-eslint/type-utils@npm:8.8.1": + version: 8.8.1 + resolution: "@typescript-eslint/type-utils@npm:8.8.1" dependencies: - "@typescript-eslint/typescript-estree": "npm:8.8.0" - "@typescript-eslint/utils": "npm:8.8.0" + "@typescript-eslint/typescript-estree": "npm:8.8.1" + "@typescript-eslint/utils": "npm:8.8.1" debug: "npm:^4.3.4" ts-api-utils: "npm:^1.3.0" peerDependenciesMeta: typescript: optional: true - checksum: 10c0/d6ee11f4686fb54daea1f436f73b96eb31a95f6e535abc0534abf5794e7597669a92d12300969c8afee0fc1912dbc1591664f7e37f0da5935016cc981b2921a8 + checksum: 10c0/6edfc2b9fca5233dd922141f080377b677db1093ec3e702a3ab52d58f77b91c0fb69479d4d42f125536b8fc0ffa85c07c7de2f17cc4c6fa1df1226ec01e5608c languageName: node linkType: hard -"@typescript-eslint/types@npm:8.8.0": - version: 8.8.0 - resolution: "@typescript-eslint/types@npm:8.8.0" - checksum: 10c0/cd168fafcaf77641b023c4405ea3a8c30fbad1737abb5aec9fce67fe2ae20224b624b5a2e3e84900ba81dc7dd33343add3653763703a225326cc81356b182d09 +"@typescript-eslint/types@npm:8.8.1": + version: 8.8.1 + resolution: "@typescript-eslint/types@npm:8.8.1" + checksum: 10c0/4b44857332a0b1bfafbeccb8be157f8266d9e226ac723f6af1272b9b670b49444423ddac733655163eb3b90e8c88393a68ab2d7f326f5775371eaf4b9ca31d7b languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:8.8.0": - version: 8.8.0 - resolution: "@typescript-eslint/typescript-estree@npm:8.8.0" +"@typescript-eslint/typescript-estree@npm:8.8.1": + version: 8.8.1 + resolution: "@typescript-eslint/typescript-estree@npm:8.8.1" dependencies: - "@typescript-eslint/types": "npm:8.8.0" - "@typescript-eslint/visitor-keys": "npm:8.8.0" + "@typescript-eslint/types": "npm:8.8.1" + "@typescript-eslint/visitor-keys": "npm:8.8.1" debug: "npm:^4.3.4" fast-glob: "npm:^3.3.2" is-glob: "npm:^4.0.3" @@ -1832,31 +1841,31 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 10c0/9b9e849f6b2d4e250840ef8e05f55a97d6598adaf48c1e6df83084b94c30feca6a3e7916ee1c235178188d0db6364a877cbf8fe218c36d5f8d5acb50767f3273 + checksum: 10c0/e3b9bc1e925c07833237044271cdc9bd8bdba3e2143dcfc5bf3bf481c89731b666a6fad25333a4b1980ac2f4c6f5e6e42c71206f73f3704e319f6b3b67463a6a languageName: node linkType: hard -"@typescript-eslint/utils@npm:8.8.0": - version: 8.8.0 - resolution: "@typescript-eslint/utils@npm:8.8.0" +"@typescript-eslint/utils@npm:8.8.1": + version: 8.8.1 + resolution: "@typescript-eslint/utils@npm:8.8.1" dependencies: "@eslint-community/eslint-utils": "npm:^4.4.0" - "@typescript-eslint/scope-manager": "npm:8.8.0" - "@typescript-eslint/types": "npm:8.8.0" - "@typescript-eslint/typescript-estree": "npm:8.8.0" + "@typescript-eslint/scope-manager": "npm:8.8.1" + "@typescript-eslint/types": "npm:8.8.1" + "@typescript-eslint/typescript-estree": "npm:8.8.1" peerDependencies: eslint: ^8.57.0 || ^9.0.0 - checksum: 10c0/fcf2dfd4a2d9491aa096a29c2c1fdd891ca3c13933d20cfea44e51b3d10a397e7ed9a9cd71ac9a29e8c4706264ae00c25a29394e2a6bda3291be298062901f2c + checksum: 10c0/954a2e85ae56a3ebefb6e41fb33c59ffa886963860536e9729a35ecea55eefdc58858c7aa126048c4a61f4fd9997b4f7601e7884ed2b3e4e7a46c9e4617a9f29 languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:8.8.0": - version: 8.8.0 - resolution: "@typescript-eslint/visitor-keys@npm:8.8.0" +"@typescript-eslint/visitor-keys@npm:8.8.1": + version: 8.8.1 + resolution: "@typescript-eslint/visitor-keys@npm:8.8.1" dependencies: - "@typescript-eslint/types": "npm:8.8.0" + "@typescript-eslint/types": "npm:8.8.1" eslint-visitor-keys: "npm:^3.4.3" - checksum: 10c0/580ce74c9b09b9e6a6f3f0ac2d2f0c6a6b983a78ce3b2544822ee08107c57142858d674897f61ff32a9a5e8fca00c916545c159bb75d134f4380884642542d38 + checksum: 10c0/6f917090b61277bd443aa851c532c4a9cc91ad57aedf185c5dff0c530f158cce84ef815833bd8deffa87f0bbf7a9f1abd1e02e30af2463c4e7f27c0c08f59080 languageName: node linkType: hard @@ -1876,11 +1885,11 @@ __metadata: "@table-library/react-table-library": "npm:4.1.7" "@trivago/prettier-plugin-sort-imports": "npm:^4.3.0" "@types/formidable": "npm:^3" - "@types/node": "npm:^22.7.4" + "@types/node": "npm:^22.7.5" "@types/react": "npm:^18.3.11" "@types/react-dom": "npm:^18.3.0" "@types/react-router-dom": "npm:^5.3.3" - alova: "npm:3.0.17" + alova: "npm:3.0.20" async-validator: "npm:^4.2.5" concurrently: "npm:^9.0.1" eslint: "npm:^9.12.0" @@ -1899,7 +1908,7 @@ __metadata: terser: "npm:^5.34.1" typesafe-i18n: "npm:^5.26.2" typescript: "npm:^5.6.2" - typescript-eslint: "npm:8.8.0" + typescript-eslint: "npm:8.8.1" vite: "npm:^5.4.8" vite-plugin-imagemin: "npm:^0.6.1" vite-tsconfig-paths: "npm:^5.0.1" @@ -1962,13 +1971,13 @@ __metadata: languageName: node linkType: hard -"alova@npm:3.0.17": - version: 3.0.17 - resolution: "alova@npm:3.0.17" +"alova@npm:3.0.20": + version: 3.0.20 + resolution: "alova@npm:3.0.20" dependencies: "@alova/shared": "npm:^1.0.5" rate-limiter-flexible: "npm:^5.0.3" - checksum: 10c0/e8a2ae885a3ff44dafec230d9388dc22b6445bb0cf8511fc9855b5a98ad9961941b0d33a7da874df23db4af0dba75872a470e3edebbdcc5ead8aecbc7fcc3d6b + checksum: 10c0/6c2544183dafe09b3381a5eda9eac255dd16ced64a3db8801ececf7795aa9e26b0a17e41675e7d46a3dfcd6542c45911e2c57d2efb45e4633438a0bbb9f13b7e languageName: node linkType: hard @@ -7001,17 +7010,17 @@ __metadata: languageName: node linkType: hard -"typescript-eslint@npm:8.8.0": - version: 8.8.0 - resolution: "typescript-eslint@npm:8.8.0" +"typescript-eslint@npm:8.8.1": + version: 8.8.1 + resolution: "typescript-eslint@npm:8.8.1" dependencies: - "@typescript-eslint/eslint-plugin": "npm:8.8.0" - "@typescript-eslint/parser": "npm:8.8.0" - "@typescript-eslint/utils": "npm:8.8.0" + "@typescript-eslint/eslint-plugin": "npm:8.8.1" + "@typescript-eslint/parser": "npm:8.8.1" + "@typescript-eslint/utils": "npm:8.8.1" peerDependenciesMeta: typescript: optional: true - checksum: 10c0/545f0ce051282921aff56288baf288cffe6f7bafee5149f1b87af2c67f81f8c2088924a2e0fc0f0dcd12692b6a97eca10149a619c8c85d4aaef2fe763938da8d + checksum: 10c0/d6793697fce239ef8838ced6e1e59940c30579c8f62c49bc605fdeda9f3f7a5c24bfddd997b142f8c411859dc0b9985ecdae569814dd4f8e6775e1899d55e9cc languageName: node linkType: hard From 621f35b1d5880d376a36093d68b12cc97d28529f Mon Sep 17 00:00:00 2001 From: proddy Date: Tue, 8 Oct 2024 22:50:51 +0200 Subject: [PATCH 16/66] add Dashboard test in api3 --- src/test/test.cpp | 60 +++++++++++++++++++++++++++-------------------- src/test/test.h | 2 +- 2 files changed, 35 insertions(+), 27 deletions(-) diff --git a/src/test/test.cpp b/src/test/test.cpp index 63f9241bf..14eef754b 100644 --- a/src/test/test.cpp +++ b/src/test/test.cpp @@ -973,47 +973,55 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const if (single) { // run dedicated tests only - // EMSESP::webCustomEntityService.test(); // custom entities - // EMSESP::webCustomizationService.test(); // set customizations - this will overwrite any settings in the FS - // EMSESP::temperaturesensor_.test(); // add temperature sensors + EMSESP::webCustomEntityService.test(); // custom entities + EMSESP::webCustomizationService.test(); // set customizations - this will overwrite any settings in the FS + EMSESP::temperaturesensor_.test(); // add temperature sensors + // EMSESP::webSchedulerService.test(); // run scheduler tests, and conditions + // request.url("/rest/deviceEntities"); + // EMSESP::webCustomizationService.device_entities(&request); + + request.url("/rest/dashboardData"); + EMSESP::webDataService.dashboard_data(&request); + + // COMMANDS // shell.invoke_command("call system fetch"); // request.url("/api/system/fetch"); // EMSESP::webAPIService.webAPIService(&request); - // request.url("/api/system/restart"); // EMSESP::webAPIService.webAPIService(&request); - // request.url("/api/system/format"); // EMSESP::webAPIService.webAPIService(&request); - request.method(HTTP_POST); + // request.url("/api/thermostat"); + // EMSESP::webAPIService.webAPIService(&request); + // request.url("/api/thermostat/hc1"); + // EMSESP::webAPIService.webAPIService(&request); - char data1[] = "{\"device\":\"system\", \"cmd\":\"restart\",\"id\":-1}"; - deserializeJson(doc, data1); - request.url("/api"); - EMSESP::webAPIService.webAPIService(&request, doc.as()); + // POST COMMANDS + // request.method(HTTP_POST); - char data2[] = "{\"action\":\"customSupport\", \"param\":\"hello\"}"; - deserializeJson(doc, data2); - request.url("/rest/action"); - EMSESP::webStatusService.action(&request, doc.as()); + // char data1[] = "{\"device\":\"system\", \"cmd\":\"restart\",\"id\":-1}"; + // deserializeJson(doc, data1); + // request.url("/api"); + // EMSESP::webAPIService.webAPIService(&request, doc.as()); - char data3[] = "{\"action\":\"export\", \"param\":\"schedule\"}"; - deserializeJson(doc, data3); - request.url("/rest/action"); - EMSESP::webStatusService.action(&request, doc.as()); + // char data2[] = "{\"action\":\"customSupport\", \"param\":\"hello\"}"; + // deserializeJson(doc, data2); + // request.url("/rest/action"); + // EMSESP::webStatusService.action(&request, doc.as()); - char data4[] = "{\"action\":\"export\", \"param\":\"allvalues\"}"; - deserializeJson(doc, data4); - request.url("/rest/action"); - EMSESP::webStatusService.action(&request, doc.as()); + // char data3[] = "{\"action\":\"export\", \"param\":\"schedule\"}"; + // deserializeJson(doc, data3); + // request.url("/rest/action"); + // EMSESP::webStatusService.action(&request, doc.as()); + + // char data4[] = "{\"action\":\"export\", \"param\":\"allvalues\"}"; + // deserializeJson(doc, data4); + // request.url("/rest/action"); + // EMSESP::webStatusService.action(&request, doc.as()); - // request.url("/api/thermostat"); - // EMSESP::webAPIService.webAPIService(&request); - // request.url("/api/thermostat/hc1"); - // EMSESP::webAPIService.webAPIService(&request); } else { EMSESP::webCustomEntityService.test(); // custom entities diff --git a/src/test/test.h b/src/test/test.h index e2c6fd94e..e979985b7 100644 --- a/src/test/test.h +++ b/src/test/test.h @@ -41,7 +41,7 @@ namespace emsesp { // #define EMSESP_DEBUG_DEFAULT "310" // #define EMSESP_DEBUG_DEFAULT "render" // #define EMSESP_DEBUG_DEFAULT "api" -// #define EMSESP_DEBUG_DEFAULT "api3" +#define EMSESP_DEBUG_DEFAULT "api3" // #define EMSESP_DEBUG_DEFAULT "crash" // #define EMSESP_DEBUG_DEFAULT "dv" // #define EMSESP_DEBUG_DEFAULT "lastcode" From 8b1015b706119ac777f55b750df20a12cb7b691b Mon Sep 17 00:00:00 2001 From: proddy Date: Tue, 8 Oct 2024 22:51:14 +0200 Subject: [PATCH 17/66] add note about upload speeds on MacBooks --- platformio.ini | 3 +++ 1 file changed, 3 insertions(+) diff --git a/platformio.ini b/platformio.ini index 45f4bc6ab..92dd7f86a 100644 --- a/platformio.ini +++ b/platformio.ini @@ -69,6 +69,9 @@ extra_scripts = [env] monitor_speed = 115200 monitor_filters = direct +; on MacBook default upload speed is 921600, which is too fast for USB. Lower for Mac. +; see https://docs.platformio.org/en/latest/projectconf/sections/env/options/upload/upload_speed.html +; upload_speed = 460800 upload_speed = 921600 build_type = release check_tool = cppcheck, clangtidy From 8cbac95b993391be219c7ae6bb3142e503afb66f Mon Sep 17 00:00:00 2001 From: proddy Date: Tue, 8 Oct 2024 22:51:26 +0200 Subject: [PATCH 18/66] 3.7.0-dev.44 --- src/version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/version.h b/src/version.h index de8b8bc67..42b0ceacd 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define EMSESP_APP_VERSION "3.7.0-dev.43" +#define EMSESP_APP_VERSION "3.7.0-dev.44" From aa88c0793bfa5565a472e6d26253e5ac6db93aab Mon Sep 17 00:00:00 2001 From: proddy Date: Tue, 8 Oct 2024 22:51:40 +0200 Subject: [PATCH 19/66] more cleanup --- src/device_library.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/device_library.h b/src/device_library.h index 6a915c201..9d56b57b5 100644 --- a/src/device_library.h +++ b/src/device_library.h @@ -30,7 +30,7 @@ { 72, DeviceType::BOILER, "Logano GB1*5, Logamatic MC10", DeviceFlags::EMS_DEVICE_FLAG_EMS}, { 81, DeviceType::BOILER, "Cascade CM10", DeviceFlags::EMS_DEVICE_FLAG_NONE}, { 84, DeviceType::BOILER, "Logamax Plus GB022", DeviceFlags::EMS_DEVICE_FLAG_NONE}, -{ 95, DeviceType::BOILER, "Condens 2500/5000W, Logamax/Logomatic, Cerapur Top, Greenstar, Generic HT3", DeviceFlags::EMS_DEVICE_FLAG_HT3}, +{ 95, DeviceType::BOILER, "Condens, Logamax/Logomatic, Cerapur Top, Greenstar, Generic HT3", DeviceFlags::EMS_DEVICE_FLAG_HT3}, {115, DeviceType::BOILER, "Topline, GB162", DeviceFlags::EMS_DEVICE_FLAG_NONE}, {121, DeviceType::BOILER, "Cascade MCM10", DeviceFlags::EMS_DEVICE_FLAG_NONE}, {122, DeviceType::BOILER, "Proline", DeviceFlags::EMS_DEVICE_FLAG_NONE}, From fc0fd625d3f98bf4c832f8e938e9ab6436a92b3a Mon Sep 17 00:00:00 2001 From: proddy Date: Tue, 8 Oct 2024 22:52:05 +0200 Subject: [PATCH 20/66] show error when using an invalid gpio in webUI --- src/analogsensor.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/analogsensor.cpp b/src/analogsensor.cpp index 7b1703f54..1a3d48681 100644 --- a/src/analogsensor.cpp +++ b/src/analogsensor.cpp @@ -134,8 +134,8 @@ void AnalogSensor::reload(bool get_nvs) { // first check if the GPIO is valid. If not, force set it to disabled if (!System::is_valid_gpio(sensor.gpio())) { LOG_WARNING("Bad GPIO %d for Sensor %s. Disabling.", sensor.gpio(), sensor.name().c_str()); - sensor.set_type(AnalogType::NOTUSED); - continue; // skip this loop pass + sensor.set_type(AnalogType::NOTUSED); // set disabled + continue; // skip this loop pass } if (sensor.type() == AnalogType::ADC) { @@ -402,7 +402,9 @@ bool AnalogSensor::update(uint8_t gpio, std::string & name, double offset, doubl // reloads the sensors in the customizations file into the sensors list reload(); - return true; + // return false if it's an invalid GPIO, an error will show in WebUI + // and reported as an error in the log + return System::is_valid_gpio(gpio); } // check to see if values have been updated From 4611ed49b0d419b2c48413e26d0ea9d5c386430a Mon Sep 17 00:00:00 2001 From: proddy Date: Tue, 8 Oct 2024 22:53:36 +0200 Subject: [PATCH 21/66] Dashboard implementation --- CHANGELOG_LATEST.md | 7 +-- interface/src/app/main/Dashboard.tsx | 1 + interface/src/app/main/types.ts | 8 ++-- src/emsdevice.cpp | 72 +++++++++++++++++++++------- src/emsdevice.h | 10 ++-- src/web/WebCustomEntityService.cpp | 24 +++++++--- src/web/WebCustomEntityService.h | 2 +- src/web/WebDataService.cpp | 56 +++++++++++++++++++++- src/web/WebDataService.h | 2 + 9 files changed, 145 insertions(+), 37 deletions(-) diff --git a/CHANGELOG_LATEST.md b/CHANGELOG_LATEST.md index 02dc06d9b..04a924280 100644 --- a/CHANGELOG_LATEST.md +++ b/CHANGELOG_LATEST.md @@ -36,13 +36,14 @@ For more details go to [www.emsesp.org](https://www.emsesp.org/). - RT800 remote emulation [#1867](https://github.com/emsesp/EMS-ESP32/issues/1867) - RC310 cooling parameters [#1857](https://github.com/emsesp/EMS-ESP32/issues/1857) - command `api/device/entities` [#1897](https://github.com/emsesp/EMS-ESP32/issues/1897) -- switchprogmode [#1903] +- switchprogmode [#1903](https://github.com/emsesp/EMS-ESP32/discussions/1903) - autodetect and download firmware upgrades via the WebUI - command 'show log' that lists out the current weblog buffer, showing last messages. - default web log buffer to 25 lines for ESP32s with no PSRAM - try and determine correct board profile if none is set during boot - auto Scroll in WebLog UI - reduced delay so incoming logs are faster -- uploading custom support info for Guest users [#2054] +- uploading custom support info, shown to Guest users in Help page [#2054](https://github.com/emsesp/EMS-ESP32/issues/2054) +- feature: Dashboard showing all data (favorites, sensors, custom) [#1958](https://github.com/emsesp/EMS-ESP32/issues/1958) ## Fixed @@ -69,7 +70,7 @@ For more details go to [www.emsesp.org](https://www.emsesp.org/). - Change key-names in JSON to be compliant and consistent [#1860](https://github.com/emsesp/EMS-ESP32/issues/1860) - Updates to webUI [#1920](https://github.com/emsesp/EMS-ESP32/issues/1920) - Correct firmware naming #1933 [#1933](https://github.com/emsesp/EMS-ESP32/issues/1933) -- Don't start Serial console if not connected to a Serial port. Will initiate manually after a CTRL-C +- Don't start Serial console if not connected to a Serial port. Will initiate manually after a CTRL-C/CTRL-S - WebLog UI matches color schema of the terminal console correctly - Updated Web libraries, ArduinoJson - Help page doesn't show detailed tech info if the user is not 'admin' role [#2054](https://github.com/emsesp/EMS-ESP32/issues/2054) diff --git a/interface/src/app/main/Dashboard.tsx b/interface/src/app/main/Dashboard.tsx index 190a2ef93..f27a58201 100644 --- a/interface/src/app/main/Dashboard.tsx +++ b/interface/src/app/main/Dashboard.tsx @@ -225,6 +225,7 @@ const Dashboard = () => { {tableList.map((di: DashboardItem) => ( + {/* TODO add a comment about the number 99 */} {di.id > 99 ? ( {showName(di)} ) : ( diff --git a/interface/src/app/main/types.ts b/interface/src/app/main/types.ts index c44325825..9389f65f9 100644 --- a/interface/src/app/main/types.ts +++ b/interface/src/app/main/types.ts @@ -117,13 +117,13 @@ export interface CoreData { export interface DashboardItem { id: number; // unique index t?: number; // type from DeviceType - n?: string; // name - dv?: DeviceValue; + n?: string; // name, optional + dv?: DeviceValue; // device value, optional nodes?: DashboardItem[]; // children nodes, optional } export interface DashboardData { - data: DashboardItem[]; + nodes: DashboardItem[]; } export interface DeviceValue { @@ -139,7 +139,7 @@ export interface DeviceValue { } export interface DeviceData { - data: DeviceValue[]; + nodes: DeviceValue[]; } export interface DeviceEntity { diff --git a/src/emsdevice.cpp b/src/emsdevice.cpp index 545f2b470..c87fd1f3d 100644 --- a/src/emsdevice.cpp +++ b/src/emsdevice.cpp @@ -33,7 +33,16 @@ uint8_t EMSdevice::count_entities() { return count; } -// see if there are entities, excluding any commands +// count favorites, used in Dashboard +uint8_t EMSdevice::count_entities_fav() { + uint8_t count = 0; + for (const auto & dv : devicevalues_) { + count += dv.has_state(DeviceValueState::DV_FAVORITE); + } + return count; +} + +// see if there are customized entities, excluding any commands bool EMSdevice::has_entities() const { for (const auto & dv : devicevalues_) { if (dv.type != DeviceValueType::CMD) { @@ -513,6 +522,7 @@ void EMSdevice::register_telegram_type(const uint16_t telegram_type_id, const ch } // add to device value library, also know now as a "device entity" +// this function will also apply any customizations to the entity void EMSdevice::add_device_value(int8_t tag, // to be used to group mqtt together, either as separate topics as a nested object void * value_p, // pointer to the value from the .h file uint8_t type, // one of DeviceValueType @@ -900,24 +910,39 @@ bool EMSdevice::export_values(uint8_t device_type, JsonObject output, const int8 } // prepare array of device values used for the WebUI +// this is used for the Dashboard and also the Devices page // this is loosely based of the function generate_values used for the MQTT and Console // except additional data is stored in the JSON document needed for the Web UI like the UOM and command // v=value, u=uom, n=name, c=cmd, h=help string, s=step, m=min, x=max -void EMSdevice::generate_values_web(JsonObject output) { +// see types.ts::DeviceValue for the structure +void EMSdevice::generate_values_web(JsonObject output, const bool is_dashboard) { // output["label"] = to_string_short(); // output["label"] = name_; - JsonArray data = output["data"].to(); + + JsonArray nodes = output["nodes"].to(); + uint8_t count = 0; for (auto & dv : devicevalues_) { auto fullname = dv.get_fullname(); // check conditions: // 1. fullname cannot be empty - // 2. it must have a valid value, if it is not a command like 'reset' - // 3. show favorites first - if (!dv.has_state(DeviceValueState::DV_WEB_EXCLUDE) && !fullname.empty() && (dv.hasValue() || (dv.type == DeviceValueType::CMD))) { - JsonObject obj = data.add(); // create the object, we know there is a value - uint8_t fahrenheit = 0; + // 2. it must have a valid value, unless its a command like 'reset' + // 3. if is_dashboard then only show favs + bool matching_states = (is_dashboard) ? dv.has_state(DeviceValueState::DV_FAVORITE) : !dv.has_state(DeviceValueState::DV_WEB_EXCLUDE); + + if (matching_states && !fullname.empty() && (dv.hasValue() || (dv.type == DeviceValueType::CMD))) { + JsonObject root_obj = nodes.add(); // create the object, we know there is a value + + JsonObject obj; + if (is_dashboard) { + root_obj["id"] = (unique_id() * 100) + count++; // make unique + obj = root_obj["dv"].to(); + } else { + obj = root_obj; + } + + uint8_t fahrenheit = 0; // handle Booleans (true, false), output as strings according to the user settings if (dv.type == DeviceValueType::BOOL) { @@ -1021,11 +1046,11 @@ void EMSdevice::generate_values_web(JsonObject output) { } } -// as generate_values_web() but stripped down to only show all entities and their state -// this is used only for WebCustomizationService::device_entities() (rest/deviceEntities?id=n) +// as generate_values_web() but with extra data for WebCustomizationService::device_entities() (rest/deviceEntities?id=n) +// also show commands and entities that have an empty fullname +// see types.ts::DeviceEntity for the structure void EMSdevice::generate_values_web_customization(JsonArray output) { for (auto & dv : devicevalues_) { - // also show commands and entities that have an empty fullname JsonObject obj = output.add(); uint8_t fahrenheit = !EMSESP::system_.fahrenheit() ? 0 : (dv.uom == DeviceValueUOM::DEGREES) ? 2 : (dv.uom == DeviceValueUOM::DEGREES_R) ? 1 : 0; @@ -1066,7 +1091,8 @@ void EMSdevice::generate_values_web_customization(JsonArray output) { } } - // id holds the shortname and must always have a value for the WebUI table to work + // create the id + // it holds the shortname and must always have a unique value for the WebUI table to work if (dv.tag >= DeviceValueTAG::TAG_HC1) { char id_s[50]; snprintf(id_s, sizeof(id_s), "%s/%s", tag_to_mqtt(dv.tag), dv.short_name); @@ -1082,7 +1108,6 @@ void EMSdevice::generate_values_web_customization(JsonArray output) { if (fullname) { // obj["n"] = dv.has_tag() ? std::string(tag_to_string(dv.tag)) + " " + fullname : fullname; // prefix tag obj["n"] = fullname; - // TAG https://github.com/emsesp/EMS-ESP32/issues/1338 // obj["n"] = (dv.has_tag()) ? fullname + " " + tag_to_string(dv.tag) : fullname; // suffix tag } @@ -1098,8 +1123,15 @@ void EMSdevice::generate_values_web_customization(JsonArray output) { if (dv.has_tag()) { obj["t"] = tag_to_string(dv.tag); } - obj["m"] = dv.state >> 4; // send back the mask state. We're only interested in the high nibble - obj["w"] = dv.has_cmd; // if writable + + // the mask state. We're only interested in the high nibble which contains the flags, so shift right + // 0x80 = 128 = DV_FAVORITE + // 0x40 = 64 = DV_READONLY + // 0x20 = 32 = DV_API_MQTT_EXCLUDE + // 0x10 = 16 = DV_WEB_EXCLUDE + obj["m"] = dv.state >> 4; + + obj["w"] = dv.has_cmd; // if writable if (dv.has_cmd && (obj["v"].is() || obj["v"].is())) { // set the min and max values if there are any and if entity has a value @@ -1112,16 +1144,20 @@ void EMSdevice::generate_values_web_customization(JsonArray output) { } } + // apply and blacklisted/removed entities + // this is when the mask has it's high bit (0x80) set + // https://github.com/emsesp/EMS-ESP32/issues/891 EMSESP::webCustomizationService.read([&](WebCustomization & settings) { for (EntityCustomization entityCustomization : settings.entityCustomizations) { if (entityCustomization.device_id == device_id()) { + // entity_ids is a list of all entities with the mask prefixed in the string for (const std::string & entity_id : entityCustomization.entity_ids) { uint8_t mask = Helpers::hextoint(entity_id.substr(0, 2).c_str()); if (mask & 0x80) { JsonObject obj = output.add(); - obj["id"] = DeviceValue::get_name(entity_id); - obj["m"] = mask; - obj["w"] = false; + obj["id"] = DeviceValue::get_name(entity_id); // set the name, it could be custom following a '|' + obj["m"] = mask; // update the mask + obj["w"] = false; // not writeable as it won't be shown } } break; diff --git a/src/emsdevice.h b/src/emsdevice.h index cb7716ed9..b217f1e23 100644 --- a/src/emsdevice.h +++ b/src/emsdevice.h @@ -230,7 +230,7 @@ class EMSdevice { enum OUTPUT_TARGET : uint8_t { API_VERBOSE, API_SHORTNAMES, MQTT, CONSOLE }; bool generate_values(JsonObject output, const int8_t tag_filter, const bool nested, const uint8_t output_target); - void generate_values_web(JsonObject output); + void generate_values_web(JsonObject output, const bool is_dashboard = false); void generate_values_web_customization(JsonArray output); void add_device_value(int8_t tag, @@ -452,6 +452,7 @@ class EMSdevice { static constexpr uint8_t EMS_DEVICE_FLAG_CR120 = 16; // mostly like RC300, but some changes uint8_t count_entities(); + uint8_t count_entities_fav(); bool has_entities() const; // void reserve_device_values(uint8_t elements) { @@ -509,9 +510,12 @@ class EMSdevice { std::vector telegram_functions_; // each EMS device has its own set of registered telegram types - std::vector devicevalues_; // all the device values - std::vector handlers_ignored_; + +#if defined(EMSESP_STANDALONE) || defined(EMSESP_TEST) + public: // so we can call it from WebCustomizationService::test() +#endif + std::vector devicevalues_; // all the device values }; } // namespace emsesp diff --git a/src/web/WebCustomEntityService.cpp b/src/web/WebCustomEntityService.cpp index bb71fc6cf..268c9b4ff 100644 --- a/src/web/WebCustomEntityService.cpp +++ b/src/web/WebCustomEntityService.cpp @@ -473,15 +473,25 @@ uint8_t WebCustomEntityService::count_entities() { } // send to dashboard, msgpack don't like serialized, use number -void WebCustomEntityService::generate_value_web(JsonObject output) { - JsonArray data = output["data"].to(); +void WebCustomEntityService::generate_value_web(JsonObject output, const bool is_dashboard) { + JsonArray nodes = output["nodes"].to(); uint8_t index = 0; for (const CustomEntityItem & entity : *customEntityItems_) { - bool include = false; - JsonObject obj = data.add(); // create the object, we know there is a value - obj["id"] = "00" + entity.name; - obj["u"] = entity.uom; + bool include = false; + JsonObject root_obj = nodes.add(); // create the object, we know there is a value + + JsonObject obj; + if (is_dashboard) { + // TODO make #define for 99 + root_obj["id"] = (99 * 100) + index; // make unique + obj = root_obj["dv"].to(); + } else { + obj = root_obj; + } + + obj["id"] = "00" + entity.name; + obj["u"] = entity.uom; if (entity.writeable) { obj["c"] = entity.name; @@ -549,7 +559,7 @@ void WebCustomEntityService::generate_value_web(JsonObject output) { if (include) { index++; } else { - data.remove(index); + nodes.remove(index); } } } diff --git a/src/web/WebCustomEntityService.h b/src/web/WebCustomEntityService.h index cacefd3b8..ae037c314 100644 --- a/src/web/WebCustomEntityService.h +++ b/src/web/WebCustomEntityService.h @@ -64,7 +64,7 @@ class WebCustomEntityService : public StatefulService { void fetch(); void render_value(JsonObject output, CustomEntityItem & entity, const bool useVal = false, const bool web = false, const bool add_uom = false); void show_values(JsonObject output); - void generate_value_web(JsonObject output); + void generate_value_web(JsonObject output, const bool is_dashboard = false); uint8_t count_entities(); void ha_reset() { diff --git a/src/web/WebDataService.cpp b/src/web/WebDataService.cpp index f1470df8f..f8d8e7979 100644 --- a/src/web/WebDataService.cpp +++ b/src/web/WebDataService.cpp @@ -43,9 +43,13 @@ WebDataService::WebDataService(AsyncWebServer * server, SecurityManager * securi server->on(EMSESP_SENSOR_DATA_SERVICE_PATH, HTTP_GET, securityManager->wrapRequest([this](AsyncWebServerRequest * request) { sensor_data(request); }, AuthenticationPredicates::IS_AUTHENTICATED)); + + server->on(EMSESP_DASHBOARD_DATA_SERVICE_PATH, + HTTP_GET, + securityManager->wrapRequest([this](AsyncWebServerRequest * request) { dashboard_data(request); }, AuthenticationPredicates::IS_AUTHENTICATED)); } -// this is used in the dashboard and contains all ems device information +// this is used in the Devices page and contains all EMS device information // /coreData endpoint void WebDataService::core_data(AsyncWebServerRequest * request) { auto * response = new AsyncJsonResponse(false); @@ -150,6 +154,7 @@ void WebDataService::sensor_data(AsyncWebServerRequest * request) { } // The unique_id is the unique record ID from the Web table to identify which device to load +// endpoint /rest/deviceData?id=n // Compresses the JSON using MsgPack https://msgpack.org/index.html void WebDataService::device_data(AsyncWebServerRequest * request) { uint8_t id; @@ -346,4 +351,53 @@ void WebDataService::write_analog_sensor(AsyncWebServerRequest * request, JsonVa request->send(response); } +// this is used in the dashboard and contains all ems device information +// /dashboardData endpoint +void WebDataService::dashboard_data(AsyncWebServerRequest * request) { + auto * response = new AsyncJsonResponse(true, true); // its an Array and also msgpack'd + +#if defined(EMSESP_STANDALONE) + JsonDocument doc; + JsonArray root = doc.to(); +#else + JsonArray root = response->getRoot(); +#endif + + for (const auto & emsdevice : EMSESP::emsdevices) { + if (emsdevice->count_entities_fav()) { + JsonObject obj = root.add(); + obj["id"] = emsdevice->unique_id(); // it's unique id + obj["n"] = emsdevice->name(); // custom name + obj["t"] = emsdevice->device_type(); // device type number + emsdevice->generate_values_web(obj, true); // is_dashboard = true + } + } + + // add custom entities, if we have any + if (EMSESP::webCustomEntityService.count_entities()) { + JsonObject obj = root.add(); + obj["id"] = 99; // it's unique id + obj["n"] = Helpers::translated_word(FL_(custom_device_name)); // custom name + obj["t"] = EMSdevice::DeviceType::CUSTOM; // device type number + EMSESP::webCustomEntityService.generate_value_web(obj, true); + } + + // add temperature sensors + + // add analog sensors + + // show scheduler, with name, on/off - and pencil edit + +#if defined(EMSESP_TEST) && defined(EMSESP_STANDALONE) + Serial.println(); + Serial.print("ALL dashboard_data: "); + serializeJsonPretty(root, Serial); + Serial.println(); +#endif + + response->setLength(); + request->send(response); +} + + } // namespace emsesp \ No newline at end of file diff --git a/src/web/WebDataService.h b/src/web/WebDataService.h index bf8008893..53bf2be88 100644 --- a/src/web/WebDataService.h +++ b/src/web/WebDataService.h @@ -23,6 +23,7 @@ #define EMSESP_CORE_DATA_SERVICE_PATH "/rest/coreData" #define EMSESP_DEVICE_DATA_SERVICE_PATH "/rest/deviceData" #define EMSESP_SENSOR_DATA_SERVICE_PATH "/rest/sensorData" +#define EMSESP_DASHBOARD_DATA_SERVICE_PATH "/rest/dashboardData" // POST #define EMSESP_WRITE_DEVICE_VALUE_SERVICE_PATH "/rest/writeDeviceValue" @@ -44,6 +45,7 @@ class WebDataService { void core_data(AsyncWebServerRequest * request); void sensor_data(AsyncWebServerRequest * request); void device_data(AsyncWebServerRequest * request); + void dashboard_data(AsyncWebServerRequest * request); // POST void write_device_value(AsyncWebServerRequest * request, JsonVariant json); From f94ac6b067ecc725dca3326970d5b0812c61c87d Mon Sep 17 00:00:00 2001 From: proddy Date: Tue, 8 Oct 2024 22:54:01 +0200 Subject: [PATCH 22/66] change CTRL-C to CTRL-S as pio monitor uses ctrl-c to exit --- src/emsesp.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/emsesp.cpp b/src/emsesp.cpp index 12a515590..31718654a 100644 --- a/src/emsesp.cpp +++ b/src/emsesp.cpp @@ -1691,7 +1691,7 @@ void EMSESP::loop() { static bool show_prompt = true; - // user has to ctrl-c to create a serial console stream, exit command will close it + // user has to CTRL-S to create a serial console stream, exit command will close it // this saves around 2kb of heap memory if (shell_) { if (!shell_->running()) { @@ -1710,7 +1710,7 @@ void EMSESP::loop() { show_prompt = true; } // https://daleswanson.org/ascii.htm#:~:text=0 - if (c == '\x03') { + if (c == '\x13') { start_serial_console(); } } @@ -1731,7 +1731,7 @@ void EMSESP::start_serial_console() { void EMSESP::shell_prompt() { #ifndef EMSESP_STANDALONE serial_console_.println(); - serial_console_.printf("EMS-ESP %s: press CTRL-C to activate this serial console", EMSESP_APP_VERSION); + serial_console_.printf("EMS-ESP %s: press CTRL-S to activate this serial console", EMSESP_APP_VERSION); serial_console_.println(); #endif } From dbaefe4d5070b3f312f3d5b9c504a3b308ab0928 Mon Sep 17 00:00:00 2001 From: proddy Date: Tue, 8 Oct 2024 22:54:24 +0200 Subject: [PATCH 23/66] added some comments --- src/emsdevicevalue.cpp | 1 + src/emsdevicevalue.h | 34 ++++++++++++++++++---------------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/emsdevicevalue.cpp b/src/emsdevicevalue.cpp index d17b364de..fb4be3152 100644 --- a/src/emsdevicevalue.cpp +++ b/src/emsdevicevalue.cpp @@ -369,6 +369,7 @@ std::string DeviceValue::get_fullname() const { return customname; } +// returns any custom name defined in the entity_id std::string DeviceValue::get_name(const std::string & entity) { auto pos = entity.find('|'); if (pos != std::string::npos) { diff --git a/src/emsdevicevalue.h b/src/emsdevicevalue.h index 46810dacf..2780e19a0 100644 --- a/src/emsdevicevalue.h +++ b/src/emsdevicevalue.h @@ -130,7 +130,7 @@ class DeviceValue { DV_WEB_EXCLUDE = (1 << 4), // 16 - not shown on web DV_API_MQTT_EXCLUDE = (1 << 5), // 32 - not shown on mqtt, API DV_READONLY = (1 << 6), // 64 - read only - DV_FAVORITE = (1 << 7) // 128 - sort to front + DV_FAVORITE = (1 << 7) // 128 - marked as a favorite }; // numeric operators @@ -154,7 +154,6 @@ class DeviceValue { const char * const ** options; // options as a flash char array const char * const * options_single; // options are not translated int8_t numeric_operator; - uint8_t options_size; // number of options in the char array, calculated const char * const short_name; // used in MQTT and API const char * const * fullname; // used in Web and Console, is translated std::string custom_fullname; // optional, from customization @@ -164,21 +163,24 @@ class DeviceValue { uint32_t max; // max range uint8_t state; // DeviceValueState::* - DeviceValue(uint8_t device_type, - int8_t tag, - void * value_p, - uint8_t type, - const char * const ** options, - const char * const * options_single, + uint8_t options_size; // number of options in the char array, calculated at class initialization + + DeviceValue(uint8_t device_type, // EMSdevice::DeviceType + int8_t tag, // DeviceValueTAG::* + void * value_p, // pointer to variable of any type + uint8_t type, // DeviceValueType::* + const char * const ** options, // options as a flash char array + const char * const * options_single, // options are not translated int8_t numeric_operator, - const char * const short_name, - const char * const * fullname, - std::string & custom_fullname, - uint8_t uom, - bool has_cmd, - int16_t min, - uint32_t max, - uint8_t state); + const char * const short_name, // used in MQTT and API + const char * const * fullname, // used in Web and Console, is translated + std::string & custom_fullname, // optional, from customization + uint8_t uom, // DeviceValueUOM::* + bool has_cmd, // true if there is a Console/MQTT command which matches the short_name + int16_t min, // min range + uint32_t max, // max range + uint8_t state // DeviceValueState::* (also known as the mask) + ); bool hasValue() const; bool has_tag() const; From 81541d032359ffbf0999551730b171cff9ab3b6d Mon Sep 17 00:00:00 2001 From: proddy Date: Tue, 8 Oct 2024 22:54:39 +0200 Subject: [PATCH 24/66] added a standalone test --- src/web/WebCustomizationService.cpp | 45 +++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/src/web/WebCustomizationService.cpp b/src/web/WebCustomizationService.cpp index 8e77de4fc..16fc695f1 100644 --- a/src/web/WebCustomizationService.cpp +++ b/src/web/WebCustomizationService.cpp @@ -162,8 +162,15 @@ void WebCustomizationService::reset_customization(AsyncWebServerRequest * reques // send back list of device entities void WebCustomizationService::device_entities(AsyncWebServerRequest * request) { uint8_t id; + + // for testing we hardcode the id to 1 - the boiler +#if defined(EMSESP_STANDALONE) && defined(EMSESP_TEST) + if (1) { + id = 1; +#else if (request->hasParam(F_(id))) { id = Helpers::atoint(request->getParam(F_(id))->value().c_str()); // get id from url +#endif auto * response = new AsyncJsonResponse(true, true); // array and msgpack @@ -178,7 +185,12 @@ void WebCustomizationService::device_entities(AsyncWebServerRequest * request) { #ifndef EMSESP_STANDALONE JsonArray output = response->getRoot(); emsdevice->generate_values_web_customization(output); +#else + JsonDocument doc; + JsonArray output = doc.to(); + emsdevice->generate_values_web_customization(output); #endif + #if defined(EMSESP_DEBUG) size_t length = response->setLength(); EMSESP::logger().debug("Customizations buffer used: %d", length); @@ -379,15 +391,44 @@ void WebCustomizationService::test() { analog.type = 1; webCustomization.analogCustomizations.push_back(analog); - // EMS entities + // EMS entities, mark some as favorites webCustomization.entityCustomizations.clear(); auto emsEntity = EntityCustomization(); emsEntity.product_id = 123; emsEntity.device_id = 8; - emsEntity.custom_name = "Custom Name!!"; + emsEntity.custom_name = "My Custom Boiler"; emsEntity.entity_ids.push_back("08heatingactive|is my heating on?"); + emsEntity.entity_ids.push_back("08tapwateractive"); + emsEntity.entity_ids.push_back("08selflowtemp|<90"); webCustomization.entityCustomizations.push_back(emsEntity); + // since custom device name is loaded at discovery, we need to force it here + for (const auto & emsdevice : EMSESP::emsdevices) { + if (emsdevice->is_device_id(emsEntity.device_id)) { + emsdevice->custom_name(emsEntity.custom_name); + break; + } + } + + // ...and the same with the custom masks and names for entity values. It's done in EMSdevice::add_device_value() + // so we need to force it here + for (const auto & emsdevice : EMSESP::emsdevices) { + if (emsdevice->is_device_id(emsEntity.device_id)) { + // find the device value and set the mask and custom name to match the above fake data + for (auto & dv : emsdevice->devicevalues_) { + if (strcmp(dv.short_name, "heatingactive") == 0) { + dv.state = DeviceValueState::DV_FAVORITE; // set as favorite + dv.custom_fullname = "is my heating on?"; + } else if (strcmp(dv.short_name, "tapwateractive") == 0) { + dv.state = DeviceValueState::DV_FAVORITE; // set as favorite + } else if (strcmp(dv.short_name, "selflowtemp") == 0) { + dv.state = DeviceValueState::DV_FAVORITE; // set as favorite + } + } + break; + } + } + return StateUpdateResult::CHANGED; // persist the changes }); From 8e56cbfa63d5ddf4624d2291260b45efc9c928fc Mon Sep 17 00:00:00 2001 From: proddy Date: Tue, 8 Oct 2024 22:55:06 +0200 Subject: [PATCH 25/66] fix data loading when dialog was open --- interface/src/app/main/Sensors.tsx | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/interface/src/app/main/Sensors.tsx b/interface/src/app/main/Sensors.tsx index 121c72d95..8be8cffad 100644 --- a/interface/src/app/main/Sensors.tsx +++ b/interface/src/app/main/Sensors.tsx @@ -19,10 +19,11 @@ import { } from '@table-library/react-table-library/table'; import { useTheme } from '@table-library/react-table-library/theme'; import type { State } from '@table-library/react-table-library/types/common'; -import { useAutoRequest, useRequest } from 'alova/client'; +import { useRequest } from 'alova/client'; import { SectionContent, useLayoutTitle } from 'components'; import { AuthenticatedContext } from 'contexts/authentication'; import { useI18nContext } from 'i18n/i18n-react'; +import { useInterval } from 'utils'; import { readSensorData, @@ -59,7 +60,7 @@ const Sensors = () => { const [analogDialogOpen, setAnalogDialogOpen] = useState(false); const [creating, setCreating] = useState(false); - const { data: sensorData, send: fetchSensorData } = useAutoRequest( + const { data: sensorData, send: fetchSensorData } = useRequest( () => readSensorData(), { initialData: { @@ -67,8 +68,7 @@ const Sensors = () => { as: [], analog_enabled: false, platform: 'ESP32' - }, - pollingTime: 3000 + } } ); @@ -86,6 +86,12 @@ const Sensors = () => { } ); + useInterval(() => { + if (!temperatureDialogOpen && !analogDialogOpen) { + void fetchSensorData(); + } + }, 3000); + const common_theme = useTheme({ BaseRow: ` font-size: 14px; From 63f35170c6bc26ca4b4ba972d92217230abbd39e Mon Sep 17 00:00:00 2001 From: proddy Date: Tue, 8 Oct 2024 22:55:23 +0200 Subject: [PATCH 26/66] make all REMOVE and UPDATE buttons look the same --- interface/src/app/main/DevicesDialog.tsx | 4 ++-- interface/src/app/main/SensorsAnalogDialog.tsx | 6 +++--- interface/src/app/main/SensorsTemperatureDialog.tsx | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/interface/src/app/main/DevicesDialog.tsx b/interface/src/app/main/DevicesDialog.tsx index e21afaf84..98127c1ec 100644 --- a/interface/src/app/main/DevicesDialog.tsx +++ b/interface/src/app/main/DevicesDialog.tsx @@ -195,9 +195,9 @@ const DevicesDialog = ({ diff --git a/interface/src/app/main/SensorsAnalogDialog.tsx b/interface/src/app/main/SensorsAnalogDialog.tsx index d3782c428..420c790bc 100644 --- a/interface/src/app/main/SensorsAnalogDialog.tsx +++ b/interface/src/app/main/SensorsAnalogDialog.tsx @@ -322,7 +322,7 @@ const SensorsAnalogDialog = ({ diff --git a/interface/src/app/main/SensorsTemperatureDialog.tsx b/interface/src/app/main/SensorsTemperatureDialog.tsx index f1b647cb2..71bfa4fc1 100644 --- a/interface/src/app/main/SensorsTemperatureDialog.tsx +++ b/interface/src/app/main/SensorsTemperatureDialog.tsx @@ -122,9 +122,9 @@ const SensorsTemperatureDialog = ({ From 0881c574b2e64247d468779eca6cf5cf98d352f8 Mon Sep 17 00:00:00 2001 From: proddy Date: Wed, 9 Oct 2024 09:04:32 +0200 Subject: [PATCH 27/66] reverse polarity if offset 0 --- src/analogsensor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/analogsensor.cpp b/src/analogsensor.cpp index 1a3d48681..17a2b57c1 100644 --- a/src/analogsensor.cpp +++ b/src/analogsensor.cpp @@ -210,7 +210,7 @@ void AnalogSensor::reload(bool get_nvs) { sensor.set_offset(EMSESP::nvs_.getChar(sensor.name().c_str())); } } - digitalWrite(sensor.gpio(), sensor.offset() * sensor.factor() > 0 ? 1 : 0); + digitalWrite(sensor.gpio(), (sensor.offset() == 0) ^ (sensor.factor() != 0)); sensor.set_value(sensor.offset()); } publish_sensor(sensor); @@ -756,7 +756,7 @@ bool AnalogSensor::command_setvalue(const char * value, const int8_t gpio) { sensor.set_offset(v); sensor.set_value(v); pinMode(sensor.gpio(), OUTPUT); - digitalWrite(sensor.gpio(), sensor.offset() * sensor.factor() > 0 ? 1 : 0); + digitalWrite(sensor.gpio(), (sensor.offset() == 0) ^ (sensor.factor() != 0)); if (sensor.uom() == 0 && EMSESP::nvs_.getChar(sensor.name().c_str()) != (int8_t)sensor.offset()) { EMSESP::nvs_.putChar(sensor.name().c_str(), (int8_t)sensor.offset()); } From 1e7835787b5711dc4b33b33ebbe7742eaf8395ea Mon Sep 17 00:00:00 2001 From: proddy Date: Wed, 9 Oct 2024 09:04:40 +0200 Subject: [PATCH 28/66] package update --- interface/package.json | 2 +- interface/yarn.lock | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/interface/package.json b/interface/package.json index 6d9080f73..d5abc2297 100644 --- a/interface/package.json +++ b/interface/package.json @@ -38,7 +38,7 @@ "react-router-dom": "^6.26.2", "react-toastify": "^10.0.5", "typesafe-i18n": "^5.26.2", - "typescript": "^5.6.2" + "typescript": "^5.6.3" }, "devDependencies": { "@babel/core": "^7.25.7", diff --git a/interface/yarn.lock b/interface/yarn.lock index 4f6d26e75..e24e360b9 100644 --- a/interface/yarn.lock +++ b/interface/yarn.lock @@ -1907,7 +1907,7 @@ __metadata: rollup-plugin-visualizer: "npm:^5.12.0" terser: "npm:^5.34.1" typesafe-i18n: "npm:^5.26.2" - typescript: "npm:^5.6.2" + typescript: "npm:^5.6.3" typescript-eslint: "npm:8.8.1" vite: "npm:^5.4.8" vite-plugin-imagemin: "npm:^0.6.1" @@ -7024,23 +7024,23 @@ __metadata: languageName: node linkType: hard -"typescript@npm:^5.6.2": - version: 5.6.2 - resolution: "typescript@npm:5.6.2" +"typescript@npm:^5.6.3": + version: 5.6.3 + resolution: "typescript@npm:5.6.3" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10c0/3ed8297a8c7c56b7fec282532503d1ac795239d06e7c4966b42d4330c6cf433a170b53bcf93a130a7f14ccc5235de5560df4f1045eb7f3550b46ebed16d3c5e5 + checksum: 10c0/44f61d3fb15c35359bc60399cb8127c30bae554cd555b8e2b46d68fa79d680354b83320ad419ff1b81a0bdf324197b29affe6cc28988cd6a74d4ac60c94f9799 languageName: node linkType: hard -"typescript@patch:typescript@npm%3A^5.6.2#optional!builtin": - version: 5.6.2 - resolution: "typescript@patch:typescript@npm%3A5.6.2#optional!builtin::version=5.6.2&hash=8c6c40" +"typescript@patch:typescript@npm%3A^5.6.3#optional!builtin": + version: 5.6.3 + resolution: "typescript@patch:typescript@npm%3A5.6.3#optional!builtin::version=5.6.3&hash=8c6c40" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10c0/94eb47e130d3edd964b76da85975601dcb3604b0c848a36f63ac448d0104e93819d94c8bdf6b07c00120f2ce9c05256b8b6092d23cf5cf1c6fa911159e4d572f + checksum: 10c0/7c9d2e07c81226d60435939618c91ec2ff0b75fbfa106eec3430f0fcf93a584bc6c73176676f532d78c3594fe28a54b36eb40b3d75593071a7ec91301533ace7 languageName: node linkType: hard From 6b99fb0404af35671c2db67f2cba0a8c760df302 Mon Sep 17 00:00:00 2001 From: proddy Date: Thu, 10 Oct 2024 21:20:36 +0100 Subject: [PATCH 29/66] add icon for Scheduler --- interface/src/app/main/DeviceIcon.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/interface/src/app/main/DeviceIcon.tsx b/interface/src/app/main/DeviceIcon.tsx index b498f6316..ad921aacf 100644 --- a/interface/src/app/main/DeviceIcon.tsx +++ b/interface/src/app/main/DeviceIcon.tsx @@ -3,6 +3,7 @@ import { CgSmartHomeBoiler } from 'react-icons/cg'; import { FaSolarPanel } from 'react-icons/fa'; import { GiHeatHaze, GiTap } from 'react-icons/gi'; import { MdPlaylistAdd } from 'react-icons/md'; +import { MdMoreTime } from 'react-icons/md'; import { MdOutlineDevices, MdOutlinePool, @@ -38,7 +39,7 @@ const deviceIconLookup: { [DeviceType.CUSTOM]: MdPlaylistAdd, [DeviceType.UNKNOWN]: undefined, [DeviceType.SYSTEM]: undefined, - [DeviceType.SCHEDULER]: undefined, + [DeviceType.SCHEDULER]: MdMoreTime, [DeviceType.GENERIC]: undefined, [DeviceType.VENTILATION]: undefined }; From 3203d0325271573f0c2789f8931eb7272af9412b Mon Sep 17 00:00:00 2001 From: proddy Date: Thu, 10 Oct 2024 21:20:49 +0100 Subject: [PATCH 30/66] move Sensors to Modules --- .../src/components/layout/LayoutMenu.tsx | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/interface/src/components/layout/LayoutMenu.tsx b/interface/src/components/layout/LayoutMenu.tsx index d0e016491..713770f12 100644 --- a/interface/src/components/layout/LayoutMenu.tsx +++ b/interface/src/components/layout/LayoutMenu.tsx @@ -54,7 +54,6 @@ const LayoutMenu = () => { - { mb: '2px', color: 'lightblue' }} - secondary={ - LL.CUSTOMIZATIONS() + - ', ' + - LL.SCHEDULER() + - ', ' + - LL.CUSTOM_ENTITIES(0) + - '...' - } - secondaryTypographyProps={{ - noWrap: true, - fontSize: 12, - color: menuOpen ? 'rgba(0,0,0,0)' : 'rgba(255,255,255,0.5)' - }} + // secondary={ + // LL.CUSTOMIZATIONS() + + // ', ' + + // LL.SCHEDULER() + + // ', ' + + // LL.CUSTOM_ENTITIES(0) + + // '...' + // } + // secondaryTypographyProps={{ + // noWrap: true, + // fontSize: 12, + // color: menuOpen ? 'rgba(0,0,0,0)' : 'rgba(255,255,255,0.5)' + // }} sx={{ my: 0 }} /> { {menuOpen && ( <> + + Date: Thu, 10 Oct 2024 21:21:19 +0100 Subject: [PATCH 31/66] rename DevicesDialog --- interface/src/app/main/Devices.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/app/main/Devices.tsx b/interface/src/app/main/Devices.tsx index 2a6aef8ee..462e4326b 100644 --- a/interface/src/app/main/Devices.tsx +++ b/interface/src/app/main/Devices.tsx @@ -64,7 +64,7 @@ import { useInterval } from 'utils'; import { readCoreData, readDeviceData, writeDeviceValue } from '../../api/app'; import DeviceIcon from './DeviceIcon'; -import DashboardDevicesDialog from './DevicesDialog'; +import DevicesDialog from './DevicesDialog'; import { formatValue } from './deviceValue'; import { DeviceEntityMask, DeviceType, DeviceValueUOM_s } from './types'; import type { Device, DeviceValue } from './types'; @@ -752,7 +752,7 @@ const Devices = () => { {renderDeviceData()} {renderDeviceDetails()} {selectedDeviceValue && ( - Date: Thu, 10 Oct 2024 21:21:35 +0100 Subject: [PATCH 32/66] support for unknown value and uom --- interface/src/app/main/deviceValue.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/interface/src/app/main/deviceValue.ts b/interface/src/app/main/deviceValue.ts index f7a71d7ff..132717800 100644 --- a/interface/src/app/main/deviceValue.ts +++ b/interface/src/app/main/deviceValue.ts @@ -27,12 +27,16 @@ const formatDurationMin = (LL: TranslationFunctions, duration_min: number) => { export function formatValue( LL: TranslationFunctions, - value: unknown, - uom: DeviceValueUOM + value?: unknown, + uom?: DeviceValueUOM ) { - if (typeof value !== 'number') { - return (value === undefined ? '' : value) as string; + if (typeof value !== 'number' || uom === undefined || value === undefined) { + if (value === undefined || typeof value === 'boolean') { + return ''; + } + return value as string; } + switch (uom) { case DeviceValueUOM.HOURS: return value ? formatDurationMin(LL, value * 60) : LL.NUM_HOURS({ num: 0 }); From faa019863be04ddceceee3000bd0995d037cadc6 Mon Sep 17 00:00:00 2001 From: proddy Date: Thu, 10 Oct 2024 21:21:45 +0100 Subject: [PATCH 33/66] DashboardItem[] --- interface/src/app/main/types.ts | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/interface/src/app/main/types.ts b/interface/src/app/main/types.ts index 9389f65f9..38398bccb 100644 --- a/interface/src/app/main/types.ts +++ b/interface/src/app/main/types.ts @@ -122,14 +122,10 @@ export interface DashboardItem { nodes?: DashboardItem[]; // children nodes, optional } -export interface DashboardData { - nodes: DashboardItem[]; -} - export interface DeviceValue { id: string; // index, contains mask+name - v: unknown; // value, Number or String - u: number; // uom + v?: unknown; // value, Number, String or Boolean - can be undefined + u?: number; // uom, optional c?: string; // command, optional l?: string[]; // list, optional h?: string; // help text, optional @@ -312,7 +308,7 @@ export interface ScheduleItem { time: string; // also used for Condition and On Change cmd: string; value: string; - name: string; // is optional + name: string; // can be empty o_id?: number; o_active?: boolean; o_deleted?: boolean; @@ -395,10 +391,10 @@ export interface Entities { // matches emsdevice.h DeviceType export const enum DeviceType { SYSTEM = 0, - TEMPERATURESENSOR, - ANALOGSENSOR, - SCHEDULER, - CUSTOM, + TEMPERATURESENSOR = 1, + ANALOGSENSOR = 2, + SCHEDULER = 3, + CUSTOM = 4, BOILER, THERMOSTAT, MIXER, From 87c9036b87ddfc8245b1e97318f6303a6b82eb76 Mon Sep 17 00:00:00 2001 From: proddy Date: Thu, 10 Oct 2024 21:21:57 +0100 Subject: [PATCH 34/66] text for Dashboard --- interface/src/i18n/de/index.ts | 2 +- interface/src/i18n/en/index.ts | 2 +- interface/src/i18n/fr/index.ts | 2 +- interface/src/i18n/it/index.ts | 2 +- interface/src/i18n/nl/index.ts | 2 +- interface/src/i18n/no/index.ts | 2 +- interface/src/i18n/pl/index.ts | 2 +- interface/src/i18n/sk/index.ts | 2 +- interface/src/i18n/sv/index.ts | 2 +- interface/src/i18n/tr/index.ts | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/interface/src/i18n/de/index.ts b/interface/src/i18n/de/index.ts index 3faa417ac..724530efd 100644 --- a/interface/src/i18n/de/index.ts +++ b/interface/src/i18n/de/index.ts @@ -343,7 +343,7 @@ const de: Translation = { AUTO_SCROLL: 'Automatisches Scrollen', DASHBOARD: 'Dashboard', // TODO translate NO_DATA: 'No data available', // TODO translate - DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorites using the Customizations module.' // TODO translate + DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorite using the Customizations module.' // TODO translate }; export default de; diff --git a/interface/src/i18n/en/index.ts b/interface/src/i18n/en/index.ts index dc4097adc..e6c6e8119 100644 --- a/interface/src/i18n/en/index.ts +++ b/interface/src/i18n/en/index.ts @@ -343,7 +343,7 @@ const en: Translation = { AUTO_SCROLL: 'Auto Scroll', DASHBOARD: 'Dashboard', NO_DATA: 'No data available', - DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorites using the Customizations module.', + DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorite using the Customizations module.', }; export default en; diff --git a/interface/src/i18n/fr/index.ts b/interface/src/i18n/fr/index.ts index 65bc641f0..89cd69bb8 100644 --- a/interface/src/i18n/fr/index.ts +++ b/interface/src/i18n/fr/index.ts @@ -343,7 +343,7 @@ const fr: Translation = { AUTO_SCROLL: 'Auto Scroll', // TODO translate DASHBOARD: 'Dashboard', // TODO translate NO_DATA: 'No data available', // TODO translate - DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorites using the Customizations module.' // TODO translate + DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorite using the Customizations module.' // TODO translate }; export default fr; diff --git a/interface/src/i18n/it/index.ts b/interface/src/i18n/it/index.ts index ce9f1c48b..52809dce2 100644 --- a/interface/src/i18n/it/index.ts +++ b/interface/src/i18n/it/index.ts @@ -343,7 +343,7 @@ const it: Translation = { AUTO_SCROLL: 'Auto Scroll', // TODO translate DASHBOARD: 'Dashboard', // TODO translate NO_DATA: 'No data available', // TODO translate - DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorites using the Customizations module.' // TODO translate + DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorite using the Customizations module.' // TODO translate }; export default it; diff --git a/interface/src/i18n/nl/index.ts b/interface/src/i18n/nl/index.ts index bbc44ad59..08063b706 100644 --- a/interface/src/i18n/nl/index.ts +++ b/interface/src/i18n/nl/index.ts @@ -343,7 +343,7 @@ const nl: Translation = { AUTO_SCROLL: 'Auto Scroll', // TODO translate DASHBOARD: 'Dashboard', // TODO translate NO_DATA: 'No data available', // TODO translate - DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorites using the Customizations module.' // TODO translate + DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorite using the Customizations module.' // TODO translate }; export default nl; diff --git a/interface/src/i18n/no/index.ts b/interface/src/i18n/no/index.ts index 830bbb2b2..f2aed8a44 100644 --- a/interface/src/i18n/no/index.ts +++ b/interface/src/i18n/no/index.ts @@ -343,7 +343,7 @@ const no: Translation = { AUTO_SCROLL: 'Auto Scroll', // TODO translate DASHBOARD: 'Dashboard', // TODO translate NO_DATA: 'No data available', // TODO translate - DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorites using the Customizations module.' // TODO translate + DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorite using the Customizations module.' // TODO translate }; export default no; diff --git a/interface/src/i18n/pl/index.ts b/interface/src/i18n/pl/index.ts index adeed62b1..413e8bf7a 100644 --- a/interface/src/i18n/pl/index.ts +++ b/interface/src/i18n/pl/index.ts @@ -343,7 +343,7 @@ const pl: BaseTranslation = { AUTO_SCROLL: 'Auto Scroll', // TODO translate DASHBOARD: 'Dashboard', // TODO translate NO_DATA: 'No data available', // TODO translate - DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorites using the Customizations module.' // TODO translate + DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorite using the Customizations module.' // TODO translate }; export default pl; diff --git a/interface/src/i18n/sk/index.ts b/interface/src/i18n/sk/index.ts index c29a95fe7..fcf6c1cd9 100644 --- a/interface/src/i18n/sk/index.ts +++ b/interface/src/i18n/sk/index.ts @@ -343,7 +343,7 @@ const sk: Translation = { AUTO_SCROLL: 'Auto Scroll', // TODO translate DASHBOARD: 'Dashboard', // TODO translate NO_DATA: 'No data available', // TODO translate - DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorites using the Customizations module.' // TODO translate + DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorite using the Customizations module.' // TODO translate }; export default sk; diff --git a/interface/src/i18n/sv/index.ts b/interface/src/i18n/sv/index.ts index 2b0900510..be0582b95 100644 --- a/interface/src/i18n/sv/index.ts +++ b/interface/src/i18n/sv/index.ts @@ -343,7 +343,7 @@ const sv: Translation = { AUTO_SCROLL: 'Auto Scroll', // TODO translate DASHBOARD: 'Dashboard', // TODO translate NO_DATA: 'No data available', // TODO translate - DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorites using the Customizations module.' // TODO translate + DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorite using the Customizations module.' // TODO translate }; export default sv; diff --git a/interface/src/i18n/tr/index.ts b/interface/src/i18n/tr/index.ts index 02add6145..17ed3fb0b 100644 --- a/interface/src/i18n/tr/index.ts +++ b/interface/src/i18n/tr/index.ts @@ -343,7 +343,7 @@ const tr: Translation = { AUTO_SCROLL: 'Auto Scroll', // TODO translate DASHBOARD: 'Dashboard', // TODO translate NO_DATA: 'No data available', // TODO translate - DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorites using the Customizations module.' // TODO translate + DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorite using the Customizations module.' // TODO translate }; export default tr; From 03ae9735ef398e7295e04a20e96d1fd655538d6c Mon Sep 17 00:00:00 2001 From: proddy Date: Thu, 10 Oct 2024 21:22:12 +0100 Subject: [PATCH 35/66] add test for Dashboard --- src/test/test.cpp | 17 ++++++++--------- src/test/test.h | 2 +- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/test/test.cpp b/src/test/test.cpp index 14eef754b..50e33aec5 100644 --- a/src/test/test.cpp +++ b/src/test/test.cpp @@ -849,7 +849,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const EMSESP::webAPIService.webAPIService(&request); request.url("/api/analogsensor/info"); EMSESP::webAPIService.webAPIService(&request); - request.url("/api/analogsensor/test_analog1"); + request.url("/api/analogsensor/test_analogsensor1"); request.url("/api/analogsensor/36"); EMSESP::webAPIService.webAPIService(&request); @@ -976,8 +976,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const EMSESP::webCustomEntityService.test(); // custom entities EMSESP::webCustomizationService.test(); // set customizations - this will overwrite any settings in the FS EMSESP::temperaturesensor_.test(); // add temperature sensors - - // EMSESP::webSchedulerService.test(); // run scheduler tests, and conditions + EMSESP::webSchedulerService.test(); // run scheduler tests, and conditions // request.url("/rest/deviceEntities"); // EMSESP::webCustomizationService.device_entities(&request); @@ -1100,11 +1099,11 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const EMSESP::webAPIService.webAPIService(&request); request.url("/api/temperaturesensor/info"); EMSESP::webAPIService.webAPIService(&request); - request.url("/api/temperaturesensor/test_sensor2"); + request.url("/api/temperaturesensor/test_tempsensor2"); EMSESP::webAPIService.webAPIService(&request); request.url("/api/temperaturesensor/0B_0C0D_0E0F_1011"); EMSESP::webAPIService.webAPIService(&request); - request.url("/api/temperaturesensor/test_sensor2/value"); + request.url("/api/temperaturesensor/test_tempsensor2/value"); EMSESP::webAPIService.webAPIService(&request); // analogsensor @@ -1112,9 +1111,9 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const EMSESP::webAPIService.webAPIService(&request); request.url("/api/analogsensor/info"); EMSESP::webAPIService.webAPIService(&request); - request.url("/api/analogsensor/test_analog1"); + request.url("/api/analogsensor/test_analogsensor1"); EMSESP::webAPIService.webAPIService(&request); - request.url("/api/analogsensor/test_analog1/offset"); + request.url("/api/analogsensor/test_analogsensor1/offset"); EMSESP::webAPIService.webAPIService(&request); // system calls with POST @@ -1191,11 +1190,11 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const EMSESP::webAPIService.webAPIService(&request); request.url("/api/temperaturesensor/0B_0C0D_0E0F_XXXX"); EMSESP::webAPIService.webAPIService(&request); - request.url("/api/temperaturesensor/test_sensor2/bad"); + request.url("/api/temperaturesensor/test_tempsensor2/bad"); EMSESP::webAPIService.webAPIService(&request); // analogsensor - request.url("/api/analogsensor/test_analog1/bad"); + request.url("/api/analogsensor/test_analogsensor1/bad"); EMSESP::webAPIService.webAPIService(&request); request.url("/api/analogsensor/test_analog10"); EMSESP::webAPIService.webAPIService(&request); diff --git a/src/test/test.h b/src/test/test.h index e979985b7..e2c6fd94e 100644 --- a/src/test/test.h +++ b/src/test/test.h @@ -41,7 +41,7 @@ namespace emsesp { // #define EMSESP_DEBUG_DEFAULT "310" // #define EMSESP_DEBUG_DEFAULT "render" // #define EMSESP_DEBUG_DEFAULT "api" -#define EMSESP_DEBUG_DEFAULT "api3" +// #define EMSESP_DEBUG_DEFAULT "api3" // #define EMSESP_DEBUG_DEFAULT "crash" // #define EMSESP_DEBUG_DEFAULT "dv" // #define EMSESP_DEBUG_DEFAULT "lastcode" From 594a48afed66325323b42bcdb8c33d3d5346590f Mon Sep 17 00:00:00 2001 From: proddy Date: Thu, 10 Oct 2024 21:22:21 +0100 Subject: [PATCH 36/66] Dashboard data --- mock-api/rest_server.ts | 94 ++++++++++++++++++++++++++--------------- 1 file changed, 60 insertions(+), 34 deletions(-) diff --git a/mock-api/rest_server.ts b/mock-api/rest_server.ts index 09080f018..ea9ff6694 100644 --- a/mock-api/rest_server.ts +++ b/mock-api/rest_server.ts @@ -58,6 +58,13 @@ const enum DeviceType { UNKNOWN } +const enum DeviceTypeUniqueID { + SCHEDULER_UID = 96, + ANALOGSENSOR_UID = 97, + TEMPERATURESENSOR_UID = 98, + CUSTOM_UID = 99 // always 99 +} + function updateMask(entity: any, de: any, dd: any) { const current_mask = parseInt(entity.slice(0, 2), 16); @@ -833,26 +840,6 @@ const emsesp_coredata = { ] }; -const emsesp_coredata2 = { - connected: true, - // connected: false, - // devices: [] - devices: [ - { - id: 4, - t: 6, - tn: 'Thermostat', - b: 'Nefit', - n: 'Moduline 1000', - d: 16, - p: 165, - v: '04.01', - e: 3, - url: 'thermostat' - } - ] -}; - const emsesp_coredata_custom = { id: 99, t: 4, @@ -3869,7 +3856,7 @@ let emsesp_schedule = { time: '', cmd: 'system/message', value: '"hello world"', - name: 'immediate' + name: '' // empty } ] }; @@ -4373,9 +4360,8 @@ router // add the custom entity data dashboard_object = { - id: 99, // unique ID for custom entities - n: 'Custom Entities', // this is translated in the C++ code - t: 4, // DeviceType::CUSTOM + id: DeviceTypeUniqueID.CUSTOM_UID, // unique ID for custom entities + t: DeviceType.CUSTOM, nodes: getDashboardEntityData(99) }; // only add to dashboard if we have values @@ -4386,17 +4372,16 @@ router // add temperature sensor data. no command c let sensor_data: any[] = []; sensor_data = emsesp_sensordata.ts.map((item, index) => ({ - id: 980 + index, + id: DeviceTypeUniqueID.TEMPERATURESENSOR_UID * 100 + index, dv: { - id: ' ' + item.n, + id: item.n, v: item.t, // value is called t in ts (temperature) u: item.u } })); dashboard_object = { - id: 98, - n: 'Temperature Sensors', - t: 1, // DeviceType::TEMPERATURESENSOR + id: DeviceTypeUniqueID.TEMPERATURESENSOR_UID, + t: DeviceType.TEMPERATURESENSOR, nodes: sensor_data }; // only add to dashboard if we have values @@ -4408,24 +4393,45 @@ router // remove disabled sensors first (t = 0) sensor_data = emsesp_sensordata.as.filter((item) => item.t !== 0); sensor_data = sensor_data.map((item, index) => ({ - id: 970 + index, + id: DeviceTypeUniqueID.ANALOGSENSOR_UID * 100 + index, dv: { - id: ' ' + item.n, + id: item.n, v: item.v, u: item.u } })); dashboard_object = { - id: 97, - n: 'Analog Sensors', - t: 2, // DeviceType::ANALOGSENSOR + id: DeviceTypeUniqueID.ANALOGSENSOR_UID, + t: DeviceType.ANALOGSENSOR, nodes: sensor_data }; // only add to dashboard if we have values if ((dashboard_object.nodes ?? []).length > 0) { dashboard_data.push(dashboard_object); } + + // add the scheduler data + // filter emsesp_schedule with only if it has a name + let scheduler_data = emsesp_schedule.schedule.filter((item) => item.name); + let scheduler_data2 = scheduler_data.map((item, index) => ({ + id: DeviceTypeUniqueID.SCHEDULER_UID * 100 + index, + dv: { + id: item.name, + v: item.active + // u: item.u // don't need uom + } + })); + dashboard_object = { + id: DeviceTypeUniqueID.SCHEDULER_UID, + t: DeviceType.SCHEDULER, + nodes: scheduler_data2 + }; + // only add to dashboard if we have values + if ((dashboard_object.nodes ?? []).length > 0) { + dashboard_data.push(dashboard_object); + } + // } else { // for testing // single object @@ -4443,6 +4449,8 @@ router console.log('dashboard_data: ', dashboard_data); } + // console.log('dashboard_data: ', dashboard_data); + // return dashboard_data; // if not using msgpack return new Response(encoder.encode(dashboard_data), { headers }); // msgpack it }) @@ -4490,6 +4498,24 @@ router // Scheduler .post(EMSESP_SCHEDULE_ENDPOINT, async (request: any) => { const content = await request.json(); + // check if we're changing active from the Dashboard + if (content.schedule.id === 0) { + console.log( + "Toggle schedule '" + + content.schedule.name + + "' to " + + content.schedule.active + ); + // find the schedule in emsesp_schedule via the name and toggle the active + const objIndex = emsesp_schedule.schedule.findIndex( + (obj) => obj.name === content.schedule.name + ); + if (objIndex !== -1) { + emsesp_schedule.schedule[objIndex].active = content.schedule.active; + } + + return status(200); + } emsesp_schedule = content; console.log('schedule saved', emsesp_schedule); return status(200); From 5a472cb6eeab2bb5417fd8b0003bf195d522a601 Mon Sep 17 00:00:00 2001 From: proddy Date: Thu, 10 Oct 2024 21:22:32 +0100 Subject: [PATCH 37/66] package update --- interface/package.json | 6 +- interface/yarn.lock | 131 ++++++++++++++++++++++++----------------- 2 files changed, 80 insertions(+), 57 deletions(-) diff --git a/interface/package.json b/interface/package.json index d5abc2297..802a1d620 100644 --- a/interface/package.json +++ b/interface/package.json @@ -24,8 +24,8 @@ "@alova/adapter-xhr": "2.0.7", "@emotion/react": "^11.13.3", "@emotion/styled": "^11.13.0", - "@mui/icons-material": "^6.1.2", - "@mui/material": "^6.1.2", + "@mui/icons-material": "^6.1.3", + "@mui/material": "^6.1.3", "@table-library/react-table-library": "4.1.7", "alova": "3.0.20", "async-validator": "^4.2.5", @@ -41,7 +41,7 @@ "typescript": "^5.6.3" }, "devDependencies": { - "@babel/core": "^7.25.7", + "@babel/core": "^7.25.8", "@eslint/js": "^9.12.0", "@preact/compat": "^18.3.1", "@preact/preset-vite": "^2.9.1", diff --git a/interface/yarn.lock b/interface/yarn.lock index e24e360b9..bf670a2eb 100644 --- a/interface/yarn.lock +++ b/interface/yarn.lock @@ -90,9 +90,9 @@ __metadata: languageName: node linkType: hard -"@babel/core@npm:^7.25.7": - version: 7.25.7 - resolution: "@babel/core@npm:7.25.7" +"@babel/core@npm:^7.25.8": + version: 7.25.8 + resolution: "@babel/core@npm:7.25.8" dependencies: "@ampproject/remapping": "npm:^2.2.0" "@babel/code-frame": "npm:^7.25.7" @@ -100,16 +100,16 @@ __metadata: "@babel/helper-compilation-targets": "npm:^7.25.7" "@babel/helper-module-transforms": "npm:^7.25.7" "@babel/helpers": "npm:^7.25.7" - "@babel/parser": "npm:^7.25.7" + "@babel/parser": "npm:^7.25.8" "@babel/template": "npm:^7.25.7" "@babel/traverse": "npm:^7.25.7" - "@babel/types": "npm:^7.25.7" + "@babel/types": "npm:^7.25.8" convert-source-map: "npm:^2.0.0" debug: "npm:^4.1.0" gensync: "npm:^1.0.0-beta.2" json5: "npm:^2.2.3" semver: "npm:^6.3.1" - checksum: 10c0/dad20af39624086afc3a0910bd97ae712c9ad0e9dda09fc5da93876e8ea1802b63ddd81c44f4aa8a9834db46de801eaab1ce9b81ab54b4fe907ae052c24de136 + checksum: 10c0/8411ea506e6f7c8a39ab5c1524b00589fa3b087edb47389708f7fe07170929192171734666e3ea10b95a951643a531a6d09eedfe071572c9ea28516646265086 languageName: node linkType: hard @@ -403,6 +403,17 @@ __metadata: languageName: node linkType: hard +"@babel/parser@npm:^7.25.8": + version: 7.25.8 + resolution: "@babel/parser@npm:7.25.8" + dependencies: + "@babel/types": "npm:^7.25.8" + bin: + parser: ./bin/babel-parser.js + checksum: 10c0/a1a13845b7e8dda4c970791814a4bbf60004969882f18f470e260ad822d2e1f8941948f851e9335895563610f240fa6c98481ce8019865e469502bbf21daafa4 + languageName: node + linkType: hard + "@babel/plugin-syntax-jsx@npm:^7.24.7": version: 7.24.7 resolution: "@babel/plugin-syntax-jsx@npm:7.24.7" @@ -551,6 +562,17 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.25.8": + version: 7.25.8 + resolution: "@babel/types@npm:7.25.8" + dependencies: + "@babel/helper-string-parser": "npm:^7.25.7" + "@babel/helper-validator-identifier": "npm:^7.25.7" + to-fast-properties: "npm:^2.0.0" + checksum: 10c0/55ca2d6df6426c98db2769ce884ce5e9de83a512ea2dd7bcf56c811984dc14351cacf42932a723630c5afcff2455809323decd645820762182f10b7b5252b59f + languageName: node + linkType: hard + "@emotion/babel-plugin@npm:^11.12.0": version: 11.12.0 resolution: "@emotion/babel-plugin@npm:11.12.0" @@ -627,7 +649,7 @@ __metadata: languageName: node linkType: hard -"@emotion/serialize@npm:^1.2.0, @emotion/serialize@npm:^1.3.0, @emotion/serialize@npm:^1.3.1": +"@emotion/serialize@npm:^1.2.0, @emotion/serialize@npm:^1.3.0, @emotion/serialize@npm:^1.3.1, @emotion/serialize@npm:^1.3.2": version: 1.3.2 resolution: "@emotion/serialize@npm:1.3.2" dependencies: @@ -1045,38 +1067,38 @@ __metadata: languageName: node linkType: hard -"@mui/core-downloads-tracker@npm:^6.1.2": - version: 6.1.2 - resolution: "@mui/core-downloads-tracker@npm:6.1.2" - checksum: 10c0/a685ac90a614be07c07bba752a6772be1caf06329e278c731f2a60b7712381c467adb3f5e0cfe7f2bc51b744ba76138f451853ff767c4739fa6379c8bc49d407 +"@mui/core-downloads-tracker@npm:^6.1.3": + version: 6.1.3 + resolution: "@mui/core-downloads-tracker@npm:6.1.3" + checksum: 10c0/250df05f7cb497fb950d20680c67ce5c0e33d32fb7c56dfd2edfe28c298542c35fc79039123f64ae51a379cd205d182f310bde613d3a36e0da40079d24998515 languageName: node linkType: hard -"@mui/icons-material@npm:^6.1.2": - version: 6.1.2 - resolution: "@mui/icons-material@npm:6.1.2" +"@mui/icons-material@npm:^6.1.3": + version: 6.1.3 + resolution: "@mui/icons-material@npm:6.1.3" dependencies: "@babel/runtime": "npm:^7.25.6" peerDependencies: - "@mui/material": ^6.1.2 + "@mui/material": ^6.1.3 "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 react: ^17.0.0 || ^18.0.0 || ^19.0.0 peerDependenciesMeta: "@types/react": optional: true - checksum: 10c0/ae5f1134e3ac45914c2bfef76a31848a8dc655237a9e478959580f778c31d77e6e54a588ec0ec552d8c6a904298f02d7cf482a5d6df294389d9ea9da8109ef94 + checksum: 10c0/38a47fcb22c80f2beeb1897aad66ac8641aad236169e9b324bae63596c1fab69a55e39f233d0c4976f2cf492cd135e4203a24e8b047baa2918d7ca8382358400 languageName: node linkType: hard -"@mui/material@npm:^6.1.2": - version: 6.1.2 - resolution: "@mui/material@npm:6.1.2" +"@mui/material@npm:^6.1.3": + version: 6.1.3 + resolution: "@mui/material@npm:6.1.3" dependencies: "@babel/runtime": "npm:^7.25.6" - "@mui/core-downloads-tracker": "npm:^6.1.2" - "@mui/system": "npm:^6.1.2" - "@mui/types": "npm:^7.2.17" - "@mui/utils": "npm:^6.1.2" + "@mui/core-downloads-tracker": "npm:^6.1.3" + "@mui/system": "npm:^6.1.3" + "@mui/types": "npm:^7.2.18" + "@mui/utils": "npm:^6.1.3" "@popperjs/core": "npm:^2.11.8" "@types/react-transition-group": "npm:^4.4.11" clsx: "npm:^2.1.1" @@ -1087,7 +1109,7 @@ __metadata: peerDependencies: "@emotion/react": ^11.5.0 "@emotion/styled": ^11.3.0 - "@mui/material-pigment-css": ^6.1.2 + "@mui/material-pigment-css": ^6.1.3 "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 react: ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -1100,16 +1122,16 @@ __metadata: optional: true "@types/react": optional: true - checksum: 10c0/7825be6ec47f82b3b3469fd0724b818bcc488f9b42de275fed38de314c216dfcefeec1cd23a0e8f8e6355ca51fbb2554ee4011834d6188a9245a8d60e7be2398 + checksum: 10c0/657f8d501b368208f69f02c4058f7d93d68d9b50c01812ea5c16117929448fa0e9ead6f49b9828f927b379075af72a991565632de926976a266b1162b3ea0f89 languageName: node linkType: hard -"@mui/private-theming@npm:^6.1.2": - version: 6.1.2 - resolution: "@mui/private-theming@npm:6.1.2" +"@mui/private-theming@npm:^6.1.3": + version: 6.1.3 + resolution: "@mui/private-theming@npm:6.1.3" dependencies: "@babel/runtime": "npm:^7.25.6" - "@mui/utils": "npm:^6.1.2" + "@mui/utils": "npm:^6.1.3" prop-types: "npm:^15.8.1" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -1117,16 +1139,17 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: 10c0/b8f8ee447dde7ecd34b9b96c19d68cff6771a7d5a133b85c3ff2dcb19fc8f2375375de6695c439dcfb6cb94e28d0a7c0889e132bfa467700a883e0dd834e8080 + checksum: 10c0/b84ca829a9f1fbcc15a8b43410108cc03bb65fff80b8ec908352dde5666c3722df60af8130ded9b6eabf02d202c536393adccb6ba13cad08da7edce76200a271 languageName: node linkType: hard -"@mui/styled-engine@npm:^6.1.2": - version: 6.1.2 - resolution: "@mui/styled-engine@npm:6.1.2" +"@mui/styled-engine@npm:^6.1.3": + version: 6.1.3 + resolution: "@mui/styled-engine@npm:6.1.3" dependencies: "@babel/runtime": "npm:^7.25.6" "@emotion/cache": "npm:^11.13.1" + "@emotion/serialize": "npm:^1.3.2" "@emotion/sheet": "npm:^1.4.0" csstype: "npm:^3.1.3" prop-types: "npm:^15.8.1" @@ -1139,19 +1162,19 @@ __metadata: optional: true "@emotion/styled": optional: true - checksum: 10c0/cad5a768d039a8e6652c5f33371ff68e82afedec75f340a0c4154ca4bf29877f23e89c041b0cbdad7d81c4fe4e356882d0de3d24157a05323a88e9703ed85d30 + checksum: 10c0/3788657bfae25dc78bfc82f0077919a29ee18c5659e82f9b73de29afd57447397e0dee1416c3f25360b584b26d8116287512bf3a5563a16f5d8eabb03f990a95 languageName: node linkType: hard -"@mui/system@npm:^6.1.2": - version: 6.1.2 - resolution: "@mui/system@npm:6.1.2" +"@mui/system@npm:^6.1.3": + version: 6.1.3 + resolution: "@mui/system@npm:6.1.3" dependencies: "@babel/runtime": "npm:^7.25.6" - "@mui/private-theming": "npm:^6.1.2" - "@mui/styled-engine": "npm:^6.1.2" - "@mui/types": "npm:^7.2.17" - "@mui/utils": "npm:^6.1.2" + "@mui/private-theming": "npm:^6.1.3" + "@mui/styled-engine": "npm:^6.1.3" + "@mui/types": "npm:^7.2.18" + "@mui/utils": "npm:^6.1.3" clsx: "npm:^2.1.1" csstype: "npm:^3.1.3" prop-types: "npm:^15.8.1" @@ -1167,28 +1190,28 @@ __metadata: optional: true "@types/react": optional: true - checksum: 10c0/b9240574f5db5a79d1ad1fc5dead2fc2b0dcaee2bc5515e6b8ea88cddd374f58adb18611fee65ae38ef165434712de425a6a5fc4d8d717d9b4f5a3716e2c3c46 + checksum: 10c0/53934c4abcae93e1474cf3457e7fc2a43c6a57b51c0b5aba7561746dbab71192dd2716dde86230be7fd56bbef130260e4b8af61462a0453d5ba9da240dafefed languageName: node linkType: hard -"@mui/types@npm:^7.2.17": - version: 7.2.17 - resolution: "@mui/types@npm:7.2.17" +"@mui/types@npm:^7.2.18": + version: 7.2.18 + resolution: "@mui/types@npm:7.2.18" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 peerDependenciesMeta: "@types/react": optional: true - checksum: 10c0/2dd77e88490d364f1f8408a5270583d323919461812768b804cae3874143d79714a862f14c7167f5f65109f7f1e90252ffee14473e3470dd1ae12d7b7308c6c4 + checksum: 10c0/338404bdef7c7f9ebcd389ebbf429c44d2cc9c25c65d8669dc900a24b2c8718240482273bf6cd953578965e3838ad40a8e7376c71d3d9146be3afb88bff1b67a languageName: node linkType: hard -"@mui/utils@npm:^6.1.2": - version: 6.1.2 - resolution: "@mui/utils@npm:6.1.2" +"@mui/utils@npm:^6.1.3": + version: 6.1.3 + resolution: "@mui/utils@npm:6.1.3" dependencies: "@babel/runtime": "npm:^7.25.6" - "@mui/types": "npm:^7.2.17" + "@mui/types": "npm:^7.2.18" "@types/prop-types": "npm:^15.7.13" clsx: "npm:^2.1.1" prop-types: "npm:^15.8.1" @@ -1199,7 +1222,7 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: 10c0/ed56688610f3c04ad219066b5bd94b16c2c1318e2fc4d49e6d6d7b3800d10b4751185cbcc88431a3a1070a236f61e41238c06ef2998a3d65724ac6245a8b64e6 + checksum: 10c0/2879a47b309565a3aa83177c94a9dddb67fcad5286ec1e5cec498543e07d7ee3c8930586a5ec36dc54a33c12c451ea8eb913d83a85127074c6cf8289b75d1ea9 languageName: node linkType: hard @@ -1874,12 +1897,12 @@ __metadata: resolution: "EMS-ESP@workspace:." dependencies: "@alova/adapter-xhr": "npm:2.0.7" - "@babel/core": "npm:^7.25.7" + "@babel/core": "npm:^7.25.8" "@emotion/react": "npm:^11.13.3" "@emotion/styled": "npm:^11.13.0" "@eslint/js": "npm:^9.12.0" - "@mui/icons-material": "npm:^6.1.2" - "@mui/material": "npm:^6.1.2" + "@mui/icons-material": "npm:^6.1.3" + "@mui/material": "npm:^6.1.3" "@preact/compat": "npm:^18.3.1" "@preact/preset-vite": "npm:^2.9.1" "@table-library/react-table-library": "npm:4.1.7" From 0e451f0a8202305b21a9bbdf78f8d859d91426e0 Mon Sep 17 00:00:00 2001 From: proddy Date: Thu, 10 Oct 2024 21:22:49 +0100 Subject: [PATCH 38/66] rename test sensors --- test/test_api/test_api.cpp | 12 ++++++------ test/test_api/test_api.h | 30 ++++++++++++++++-------------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/test/test_api/test_api.cpp b/test/test_api/test_api.cpp index ef8c33933..1b0270a0b 100644 --- a/test/test_api/test_api.cpp +++ b/test/test_api/test_api.cpp @@ -359,15 +359,15 @@ void create_tests() { // temperaturesensor capture("/api/temperaturesensor"); capture("/api/temperaturesensor/info"); - capture("/api/temperaturesensor/test_sensor2"); + capture("/api/temperaturesensor/test_tempsensor2"); capture("/api/temperaturesensor/0B_0C0D_0E0F_1011"); - capture("/api/temperaturesensor/test_sensor2/value"); + capture("/api/temperaturesensor/test_tempsensor2/value"); // analogsensor capture("/api/analogsensor"); capture("/api/analogsensor/info"); - capture("/api/analogsensor/test_analog1"); - capture("/api/analogsensor/test_analog1/offset"); + capture("/api/analogsensor/test_analogsensor1"); + capture("/api/analogsensor/test_analogsensor1/offset"); // these tests should all fail... capture("/api/boiler2"); @@ -391,10 +391,10 @@ void create_tests() { // temperaturesensor capture("/api/temperaturesensor/test_sensor20"); capture("/api/temperaturesensor/0B_0C0D_0E0F_XXXX"); - capture("/api/temperaturesensor/test_sensor2/bad"); + capture("/api/temperaturesensor/test_tempsensor2/bad"); // analogsensor - capture("/api/analogsensor/test_analog1/bad"); + capture("/api/analogsensor/test_analogsensor1/bad"); capture("/api/analogsensor/test_analog10"); capture("/api/analogsensor/test_analog10/bad2"); diff --git a/test/test_api/test_api.h b/test/test_api/test_api.h index fcfdc134a..f08df7ab9 100644 --- a/test/test_api/test_api.h +++ b/test/test_api/test_api.h @@ -234,49 +234,51 @@ void test_26() { } void test_27() { - auto expected_response = "[{\"test_sensor1\":12.3,\"test_sensor2\":45.6}]"; + auto expected_response = "[{\"test_tempsensor1\":12.3,\"test_tempsensor2\":45.6}]"; TEST_ASSERT_EQUAL_STRING(expected_response, call_url("/api/temperaturesensor")); } void test_28() { - auto expected_response = "[{\"test_sensor1\":12.3,\"test_sensor2\":45.6}]"; + auto expected_response = "[{\"test_tempsensor1\":12.3,\"test_tempsensor2\":45.6}]"; TEST_ASSERT_EQUAL_STRING(expected_response, call_url("/api/temperaturesensor/info")); } void test_29() { - auto expected_response = "[{\"id\":\"0B_0C0D_0E0F_1011\",\"name\":\"test_sensor2\",\"value\":45.6,\"type\":\"number\",\"uom\":\"°C\",\"writeable\":false}]"; - TEST_ASSERT_EQUAL_STRING(expected_response, call_url("/api/temperaturesensor/test_sensor2")); + auto expected_response = + "[{\"id\":\"0B_0C0D_0E0F_1011\",\"name\":\"test_tempsensor2\",\"value\":45.6,\"type\":\"number\",\"uom\":\"°C\",\"writeable\":false}]"; + TEST_ASSERT_EQUAL_STRING(expected_response, call_url("/api/temperaturesensor/test_tempsensor2")); } void test_30() { - auto expected_response = "[{\"id\":\"0B_0C0D_0E0F_1011\",\"name\":\"test_sensor2\",\"value\":45.6,\"type\":\"number\",\"uom\":\"°C\",\"writeable\":false}]"; + auto expected_response = + "[{\"id\":\"0B_0C0D_0E0F_1011\",\"name\":\"test_tempsensor2\",\"value\":45.6,\"type\":\"number\",\"uom\":\"°C\",\"writeable\":false}]"; TEST_ASSERT_EQUAL_STRING(expected_response, call_url("/api/temperaturesensor/0B_0C0D_0E0F_1011")); } void test_31() { auto expected_response = "[{\"api_data\":\"45.6\"}]"; - TEST_ASSERT_EQUAL_STRING(expected_response, call_url("/api/temperaturesensor/test_sensor2/value")); + TEST_ASSERT_EQUAL_STRING(expected_response, call_url("/api/temperaturesensor/test_tempsensor2/value")); } void test_32() { - auto expected_response = "[{\"test_analog1\":0,\"test_analog2\":1}]"; + auto expected_response = "[{\"test_analogsensor1\":0,\"test_analogsensor2\":1}]"; TEST_ASSERT_EQUAL_STRING(expected_response, call_url("/api/analogsensor")); } void test_33() { - auto expected_response = "[{\"test_analog1\":0,\"test_analog2\":1}]"; + auto expected_response = "[{\"test_analogsensor1\":0,\"test_analogsensor2\":1}]"; TEST_ASSERT_EQUAL_STRING(expected_response, call_url("/api/analogsensor/info")); } void test_34() { auto expected_response = "[{\"gpio\":36,\"type\":\"number\",\"analog\":\"adc\",\"value\":0,\"writeable\":false,\"offset\":0,\"factor\":0.1,\"uom\":\"mV\"}]"; - TEST_ASSERT_EQUAL_STRING(expected_response, call_url("/api/analogsensor/test_analog1")); + TEST_ASSERT_EQUAL_STRING(expected_response, call_url("/api/analogsensor/test_analogsensor1")); } void test_35() { auto expected_response = "[{\"api_data\":\"0\"}]"; - TEST_ASSERT_EQUAL_STRING(expected_response, call_url("/api/analogsensor/test_analog1/offset")); + TEST_ASSERT_EQUAL_STRING(expected_response, call_url("/api/analogsensor/test_analogsensor1/offset")); } void test_36() { @@ -345,13 +347,13 @@ void test_48() { } void test_49() { - auto expected_response = "[{\"message\":\"no bad in test_sensor2\"}]"; - TEST_ASSERT_EQUAL_STRING(expected_response, call_url("/api/temperaturesensor/test_sensor2/bad")); + auto expected_response = "[{\"message\":\"no bad in test_tempsensor2\"}]"; + TEST_ASSERT_EQUAL_STRING(expected_response, call_url("/api/temperaturesensor/test_tempsensor2/bad")); } void test_50() { - auto expected_response = "[{\"message\":\"no bad in test_analog1\"}]"; - TEST_ASSERT_EQUAL_STRING(expected_response, call_url("/api/analogsensor/test_analog1/bad")); + auto expected_response = "[{\"message\":\"no bad in test_analogsensor1\"}]"; + TEST_ASSERT_EQUAL_STRING(expected_response, call_url("/api/analogsensor/test_analogsensor1/bad")); } void test_51() { From c5257d7ccf326630f9696f916c81c42047f4abd6 Mon Sep 17 00:00:00 2001 From: proddy Date: Thu, 10 Oct 2024 21:23:15 +0100 Subject: [PATCH 39/66] ignore local package --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index c6cb358e8..e3283c440 100644 --- a/.gitignore +++ b/.gitignore @@ -71,3 +71,4 @@ words-found-verbose.txt # sonarlint compile_commands.json +package.json From 5234a4477ff53948ea22bda3fab99c6d5e0f6384 Mon Sep 17 00:00:00 2001 From: proddy Date: Thu, 10 Oct 2024 21:23:34 +0100 Subject: [PATCH 40/66] 3.7.0-dev.44 --- src/version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/version.h b/src/version.h index 42b0ceacd..ed70fa27e 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define EMSESP_APP_VERSION "3.7.0-dev.44" +#define EMSESP_APP_VERSION "3.7.0-dev.44" \ No newline at end of file From a9b01e05c9f356f494316e5ae1cf5f6d01e60770 Mon Sep 17 00:00:00 2001 From: proddy Date: Thu, 10 Oct 2024 21:23:51 +0100 Subject: [PATCH 41/66] CTRL-D for console --- src/emsesp.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/emsesp.cpp b/src/emsesp.cpp index 31718654a..b2b23c566 100644 --- a/src/emsesp.cpp +++ b/src/emsesp.cpp @@ -1691,7 +1691,7 @@ void EMSESP::loop() { static bool show_prompt = true; - // user has to CTRL-S to create a serial console stream, exit command will close it + // user has to CTRL-D to create a serial console stream, exit command will close it // this saves around 2kb of heap memory if (shell_) { if (!shell_->running()) { @@ -1708,9 +1708,11 @@ void EMSESP::loop() { int c = serial_console_.read(); if (c != -1) { show_prompt = true; + Serial.println(c); } // https://daleswanson.org/ascii.htm#:~:text=0 - if (c == '\x13') { + // CTRL-D to open + if (c == '\x04') { start_serial_console(); } } @@ -1731,7 +1733,7 @@ void EMSESP::start_serial_console() { void EMSESP::shell_prompt() { #ifndef EMSESP_STANDALONE serial_console_.println(); - serial_console_.printf("EMS-ESP %s: press CTRL-S to activate this serial console", EMSESP_APP_VERSION); + serial_console_.printf("EMS-ESP %s: press CTRL-D to activate this serial console", EMSESP_APP_VERSION); serial_console_.println(); #endif } From ca8ed2d1a52545e4034354c4bb2680440f0d7d00 Mon Sep 17 00:00:00 2001 From: proddy Date: Thu, 10 Oct 2024 21:24:04 +0100 Subject: [PATCH 42/66] added DeviceTypeUniqueID for Dashboard --- src/emsdevice.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/emsdevice.h b/src/emsdevice.h index b217f1e23..0c801d443 100644 --- a/src/emsdevice.h +++ b/src/emsdevice.h @@ -346,6 +346,15 @@ class EMSdevice { IVT // 13 }; + // Unique Identifiers for each Device type, used in Dashboard table + // 100 and above is reserved for DeviceType + enum DeviceTypeUniqueID : uint8_t { + SCHEDULER_UID = 96, + ANALOGSENSOR_UID = 97, + TEMPERATURESENSOR_UID = 98, + CUSTOM_UID = 99 // always 99 + }; + enum DeviceType : uint8_t { SYSTEM = 0, // this is us (EMS-ESP) TEMPERATURESENSOR, // for internal temperature sensors From b7e65525579fab17ae8e1449c382c0d457cb0d13 Mon Sep 17 00:00:00 2001 From: proddy Date: Thu, 10 Oct 2024 21:24:32 +0100 Subject: [PATCH 43/66] Dashboard - #1958 --- interface/src/app/main/Dashboard.tsx | 217 +++++++++++++++++++++------ 1 file changed, 167 insertions(+), 50 deletions(-) diff --git a/interface/src/app/main/Dashboard.tsx b/interface/src/app/main/Dashboard.tsx index f27a58201..061fa8165 100644 --- a/interface/src/app/main/Dashboard.tsx +++ b/interface/src/app/main/Dashboard.tsx @@ -3,6 +3,7 @@ import { IconContext } from 'react-icons/lib'; import { toast } from 'react-toastify'; import ChevronRightIcon from '@mui/icons-material/ChevronRight'; +import CircleIcon from '@mui/icons-material/Circle'; import EditIcon from '@mui/icons-material/Edit'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import UnfoldLessIcon from '@mui/icons-material/UnfoldLess'; @@ -12,8 +13,10 @@ import { IconButton, ToggleButton, ToggleButtonGroup, + Tooltip, Typography } from '@mui/material'; +import Grid from '@mui/material/Grid2'; import { Body, Cell, Row, Table } from '@table-library/react-table-library/table'; import { useTheme } from '@table-library/react-table-library/theme'; @@ -24,11 +27,17 @@ import { AuthenticatedContext } from 'contexts/authentication'; import { useI18nContext } from 'i18n/i18n-react'; import { useInterval } from 'utils'; -import { readDashboard, writeDeviceValue } from '../../api/app'; +import { readDashboard, writeDeviceValue, writeSchedule } from '../../api/app'; import DeviceIcon from './DeviceIcon'; -import DashboardDevicesDialog from './DevicesDialog'; +import DevicesDialog from './DevicesDialog'; import { formatValue } from './deviceValue'; -import { type DashboardItem, DeviceEntityMask, type DeviceValue } from './types'; +import { + type DashboardItem, + DeviceEntityMask, + DeviceType, + type DeviceValue, + type Schedule +} from './types'; import { deviceValueItemValidation } from './validators'; const Dashboard = () => { @@ -54,6 +63,13 @@ const Dashboard = () => { initialData: [] }); + const { send: updateSchedule } = useRequest( + (data: Schedule) => writeSchedule(data), + { + immediate: false + } + ); + const { loading: submitting, send: sendDeviceValue } = useRequest( (data: { id: number; c: string; v: unknown }) => writeDeviceValue(data), { @@ -104,14 +120,24 @@ const Dashboard = () => { const tree = useTree( { nodes: data }, { - onChange: null // not used but needed + onChange: undefined // not used but needed }, { treeIcon: { margin: '4px', iconDefault: null, - iconRight: , - iconDown: + iconRight: ( + + ), + iconDown: ( + + ) }, indentation: 50 } @@ -131,27 +157,78 @@ const Dashboard = () => { } }, [loading]); + const showType = (n?: string, t?: number) => { + // if we have a name show it + if (n) { + return n; + } + if (t) { + // otherwise pick translation based on type + switch (t) { + case DeviceType.CUSTOM: + return LL.CUSTOM_ENTITIES(0); + case DeviceType.ANALOGSENSOR: + return LL.ANALOG_SENSOR(0); + case DeviceType.TEMPERATURESENSOR: + return LL.TEMP_SENSOR(); + case DeviceType.SCHEDULER: + return LL.SCHEDULER(); + default: + break; + } + } + return ''; + }; + const showName = (di: DashboardItem) => { if (di.id < 100) { - // if its a device and has entities + // if its a device (parent node) and has entities if (di.nodes?.length) { return ( <> -   {di.n} +   {showType(di.n, di.t)}  ({di.nodes?.length}) ); } } - return {di.dv ? di.dv.id.slice(2) : ''}; + if (di.dv) { + return ( + // ids for scheduler, and sensors are between 9600 and 9900 + + {di.id >= 9600 && di.id < 9900 ? di.dv.id : di.dv.id.slice(2)} + + ); + } }; const hasMask = (id: string, mask: number) => (parseInt(id.slice(0, 2), 16) & mask) === mask; + const toggleSchedule = async (di: DashboardItem) => { + // create a dummy record, the id=0 picks it up + await updateSchedule({ + schedule: { + id: 0, // special number for only changing the active flag + active: !di.dv?.v, + flags: 0, // unused + time: '', // unused + cmd: '', // unused + value: '', // unused + name: di.dv?.id ?? '' + } + }) + .catch((error: Error) => { + toast.error(error.message); + }) + .finally(async () => { + await fetchDashboard(); + }); + }; + const editDashboardValue = (di: DashboardItem) => { setSelectedDashboardItem(di); setDeviceValueDialogOpen(true); @@ -174,32 +251,37 @@ const Dashboard = () => { return ( <> - - {LL.DASHBOARD_1()} - + + + + {LL.DASHBOARD_1()} + + - - - - - - - - + + + + + + + + + + + @@ -225,30 +307,65 @@ const Dashboard = () => { {tableList.map((di: DashboardItem) => ( - {/* TODO add a comment about the number 99 */} {di.id > 99 ? ( - {showName(di)} + <> + {showName(di)} + + + + {di.dv ? formatValue(LL, di.dv.v, di.dv.u) : ''} + + + + + + {me.admin && di.id < 9700 && di.id >= 9600 ? ( + toggleSchedule(di)} + > + {di.dv?.v ? ( + + ) : ( + + )} + + ) : ( + me.admin && + di.dv?.c && + !hasMask(di.dv.id, DeviceEntityMask.DV_READONLY) && ( + editDashboardValue(di)} + > + + + ) + )} + + ) : ( - {showName(di)} + <> + {showName(di)} + + + )} - - - {di.dv && formatValue(LL, di.dv.v, di.dv.u)} - - - - - {me.admin && - di.dv?.c && - !hasMask(di.dv.id, DeviceEntityMask.DV_READONLY) && ( - editDashboardValue(di)} - > - - - )} - ))} @@ -265,7 +382,7 @@ const Dashboard = () => { {renderContent()} {selectedDashboardItem && selectedDashboardItem.dv && ( - setDeviceValueDialogOpen(false)} onSave={deviceValueDialogSave} From 9a6e84c68af2553d112f770eee60c836aa31a211 Mon Sep 17 00:00:00 2001 From: proddy Date: Thu, 10 Oct 2024 21:24:57 +0100 Subject: [PATCH 44/66] replace DashboardData with array --- interface/src/api/app.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/api/app.ts b/interface/src/api/app.ts index a56f4db53..dae9038f1 100644 --- a/interface/src/api/app.ts +++ b/interface/src/api/app.ts @@ -5,7 +5,7 @@ import type { Action, Activity, CoreData, - DashboardData, + DashboardItem, DeviceData, DeviceEntity, Entities, @@ -22,7 +22,7 @@ import type { // Dashboard export const readDashboard = () => - alovaInstance.Get('/rest/dashboardData', { + alovaInstance.Get('/rest/dashboardData', { responseType: 'arraybuffer' // uses msgpack }); From e52753e83c9c772f12f79d28f3764e1b351a42ae Mon Sep 17 00:00:00 2001 From: proddy Date: Thu, 10 Oct 2024 21:25:11 +0100 Subject: [PATCH 45/66] rename DialogProps --- interface/src/app/main/DevicesDialog.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/interface/src/app/main/DevicesDialog.tsx b/interface/src/app/main/DevicesDialog.tsx index 98127c1ec..7e0389426 100644 --- a/interface/src/app/main/DevicesDialog.tsx +++ b/interface/src/app/main/DevicesDialog.tsx @@ -29,7 +29,7 @@ import { validate } from 'validators'; import { DeviceValueUOM, DeviceValueUOM_s } from './types'; import type { DeviceValue } from './types'; -interface DashboardDevicesDialogProps { +interface DevicesDialogProps { open: boolean; onClose: () => void; onSave: (as: DeviceValue) => void; @@ -47,7 +47,7 @@ const DevicesDialog = ({ writeable, validator, progress -}: DashboardDevicesDialogProps) => { +}: DevicesDialogProps) => { const { LL } = useI18nContext(); const [editItem, setEditItem] = useState(selectedItem); const [fieldErrors, setFieldErrors] = useState(); @@ -75,7 +75,10 @@ const DevicesDialog = ({ } }; - const setUom = (uom: DeviceValueUOM) => { + const setUom = (uom?: DeviceValueUOM) => { + if (uom === undefined) { + return; + } switch (uom) { case DeviceValueUOM.HOURS: return LL.HOURS(); From 37ac684d24a19180cf2ba23f04187397e94d4e22 Mon Sep 17 00:00:00 2001 From: proddy Date: Thu, 10 Oct 2024 21:25:48 +0100 Subject: [PATCH 46/66] rename test temp sensors --- src/web/WebCustomizationService.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/web/WebCustomizationService.cpp b/src/web/WebCustomizationService.cpp index 16fc695f1..40e6a9533 100644 --- a/src/web/WebCustomizationService.cpp +++ b/src/web/WebCustomizationService.cpp @@ -360,13 +360,13 @@ void WebCustomizationService::test() { webCustomization.sensorCustomizations.clear(); auto sensor = SensorCustomization(); sensor.id = "01_0203_0405_0607"; - sensor.name = "test_sensor1"; + sensor.name = "test_tempsensor1"; sensor.offset = 0; webCustomization.sensorCustomizations.push_back(sensor); auto sensor2 = SensorCustomization(); sensor2.id = "0B_0C0D_0E0F_1011"; - sensor2.name = "test_sensor2"; + sensor2.name = "test_tempsensor2"; sensor2.offset = 4; webCustomization.sensorCustomizations.push_back(sensor2); @@ -375,7 +375,7 @@ void WebCustomizationService::test() { webCustomization.analogCustomizations.clear(); auto analog = AnalogCustomization(); analog.gpio = 36; - analog.name = "test_analog1"; + analog.name = "test_analogsensor1"; analog.offset = 0; analog.factor = 0.1; analog.uom = 17; @@ -384,7 +384,7 @@ void WebCustomizationService::test() { analog = AnalogCustomization(); analog.gpio = 37; - analog.name = "test_analog2"; + analog.name = "test_analogsensor2"; analog.offset = 0; analog.factor = 1; analog.uom = 0; From 31cfdc6604f61b2206a886fd9dd42aa00c3948f4 Mon Sep 17 00:00:00 2001 From: proddy Date: Thu, 10 Oct 2024 21:26:22 +0100 Subject: [PATCH 47/66] support single scheduler item updates for Dashboard --- src/web/WebSchedulerService.cpp | 92 ++++++++++++++++++++++----------- 1 file changed, 63 insertions(+), 29 deletions(-) diff --git a/src/web/WebSchedulerService.cpp b/src/web/WebSchedulerService.cpp index b1b0229c4..dae491fa9 100644 --- a/src/web/WebSchedulerService.cpp +++ b/src/web/WebSchedulerService.cpp @@ -49,10 +49,10 @@ void WebSchedulerService::begin() { // and also calls when the Scheduler web page is refreshed void WebScheduler::read(WebScheduler & webScheduler, JsonObject root) { JsonArray schedule = root["schedule"].to(); - uint8_t counter = 0; + uint8_t counter = 1; for (const ScheduleItem & scheduleItem : webScheduler.scheduleItems) { JsonObject si = schedule.add(); - si["id"] = counter++; // id is only used to render the table and must be unique + si["id"] = counter++; // id is only used to render the table and must be unique. 0 is for Dashboard si["flags"] = scheduleItem.flags; si["active"] = scheduleItem.flags != SCHEDULEFLAG_SCHEDULE_IMMEDIATE ? scheduleItem.active : false; si["time"] = scheduleItem.flags != SCHEDULEFLAG_SCHEDULE_IMMEDIATE ? scheduleItem.time : ""; @@ -65,39 +65,60 @@ void WebScheduler::read(WebScheduler & webScheduler, JsonObject root) { // call on initialization and also when the Schedule web page is saved // this loads the data into the internal class StateUpdateResult WebScheduler::update(JsonObject root, WebScheduler & webScheduler) { + // check if we're only toggling it on/off via the Dashboard + // if it is a single schedule object and id is 0 + if (root["schedule"].is()) { + JsonObject si = root["schedule"]; + if (si["id"] == 0) { + // find the item, matching the name + for (ScheduleItem & scheduleItem : webScheduler.scheduleItems) { + std::string name = si["name"].as(); + if (scheduleItem.name == name) { + scheduleItem.active = si["active"]; + EMSESP::webSchedulerService.publish(true); + return StateUpdateResult::CHANGED; + } + } + return StateUpdateResult::UNCHANGED; + } + } + + // otherwise we're updating and saving the whole list again + if (!root["schedule"].is()) { + return StateUpdateResult::ERROR; // invalid format + } + // reset the list Command::erase_device_commands(EMSdevice::DeviceType::SCHEDULER); webScheduler.scheduleItems.clear(); EMSESP::webSchedulerService.ha_reset(); // build up the list of schedule items - if (root["schedule"].is()) { - for (const JsonObject schedule : root["schedule"].as()) { - // create each schedule item, overwriting any previous settings - // ignore the id (as this is only used in the web for table rendering) - auto si = ScheduleItem(); - si.active = schedule["active"]; - si.flags = schedule["flags"]; - si.time = si.flags == SCHEDULEFLAG_SCHEDULE_IMMEDIATE ? "" : schedule["time"].as(); - si.cmd = schedule["cmd"].as(); - si.value = schedule["value"].as(); - si.name = schedule["name"].as(); - - // calculated elapsed minutes - si.elapsed_min = Helpers::string2minutes(si.time); - si.retry_cnt = 0xFF; // no startup retries - - webScheduler.scheduleItems.push_back(si); // add to list - if (!webScheduler.scheduleItems.back().name.empty()) { - Command::add( - EMSdevice::DeviceType::SCHEDULER, - webScheduler.scheduleItems.back().name.c_str(), - [webScheduler](const char * value, const int8_t id) { - return EMSESP::webSchedulerService.command_setvalue(value, id, webScheduler.scheduleItems.back().name.c_str()); - }, - FL_(schedule_cmd), - CommandFlag::ADMIN_ONLY); - } + for (const JsonObject schedule : root["schedule"].as()) { + // create each schedule item, overwriting any previous settings + // ignore the id (as this is only used in the web for table rendering) + auto si = ScheduleItem(); + si.active = schedule["active"]; + si.flags = schedule["flags"]; + si.time = si.flags == SCHEDULEFLAG_SCHEDULE_IMMEDIATE ? "" : schedule["time"].as(); + si.cmd = schedule["cmd"].as(); + si.value = schedule["value"].as(); + si.name = schedule["name"].as(); + + // calculated elapsed minutes + si.elapsed_min = Helpers::string2minutes(si.time); + si.retry_cnt = 0xFF; // no startup retries + + webScheduler.scheduleItems.push_back(si); // add to list + if (!webScheduler.scheduleItems.back().name.empty()) { + Command::add( + EMSdevice::DeviceType::SCHEDULER, + webScheduler.scheduleItems.back().name.c_str(), + [webScheduler](const char * value, const int8_t id) { + return EMSESP::webSchedulerService.command_setvalue(value, id, webScheduler.scheduleItems.back().name.c_str()); + }, + FL_(schedule_cmd), + CommandFlag::ADMIN_ONLY); } } @@ -551,6 +572,19 @@ void WebSchedulerService::test() { si.elapsed_min = 0; si.retry_cnt = 0xFF; // no startup retries + webScheduler.scheduleItems.push_back(si); + + // test 2 + si = ScheduleItem(); + si.active = false; + si.flags = 1; + si.time = "13:00"; + si.cmd = "system/message"; + si.value = "20"; + si.name = ""; // to make sure its excluded from Dashboard + si.elapsed_min = 0; + si.retry_cnt = 0xFF; // no startup retries + webScheduler.scheduleItems.push_back(si); already_added = true; From a001a314018570841f334c04447271282bd39b58 Mon Sep 17 00:00:00 2001 From: proddy Date: Thu, 10 Oct 2024 21:26:39 +0100 Subject: [PATCH 48/66] use DeviceTypeUniqueID --- src/web/WebCustomEntityService.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/web/WebCustomEntityService.cpp b/src/web/WebCustomEntityService.cpp index 268c9b4ff..cca572bf5 100644 --- a/src/web/WebCustomEntityService.cpp +++ b/src/web/WebCustomEntityService.cpp @@ -483,8 +483,7 @@ void WebCustomEntityService::generate_value_web(JsonObject output, const bool is JsonObject obj; if (is_dashboard) { - // TODO make #define for 99 - root_obj["id"] = (99 * 100) + index; // make unique + root_obj["id"] = (EMSdevice::DeviceTypeUniqueID::CUSTOM_UID * 100) + index; // make unique obj = root_obj["dv"].to(); } else { obj = root_obj; From d373309feab3df584181627ddb95ef82c5031b08 Mon Sep 17 00:00:00 2001 From: proddy Date: Thu, 10 Oct 2024 21:27:00 +0100 Subject: [PATCH 49/66] Implements back end for Dashboard - Feature: Dashboard showing all data (favourites, sensors, custom) #1958 --- src/web/WebDataService.cpp | 75 +++++++++++++++++++++++++++++++++++--- 1 file changed, 69 insertions(+), 6 deletions(-) diff --git a/src/web/WebDataService.cpp b/src/web/WebDataService.cpp index f8d8e7979..1aa88ac36 100644 --- a/src/web/WebDataService.cpp +++ b/src/web/WebDataService.cpp @@ -376,22 +376,85 @@ void WebDataService::dashboard_data(AsyncWebServerRequest * request) { // add custom entities, if we have any if (EMSESP::webCustomEntityService.count_entities()) { JsonObject obj = root.add(); - obj["id"] = 99; // it's unique id - obj["n"] = Helpers::translated_word(FL_(custom_device_name)); // custom name - obj["t"] = EMSdevice::DeviceType::CUSTOM; // device type number + obj["id"] = EMSdevice::DeviceTypeUniqueID::CUSTOM_UID; // it's unique id + obj["t"] = EMSdevice::DeviceType::CUSTOM; // device type number EMSESP::webCustomEntityService.generate_value_web(obj, true); } // add temperature sensors + if (EMSESP::temperaturesensor_.have_sensors()) { + JsonObject obj = root.add(); + obj["id"] = EMSdevice::DeviceTypeUniqueID::TEMPERATURESENSOR_UID; // it's unique id + obj["t"] = EMSdevice::DeviceType::TEMPERATURESENSOR; // device type number + JsonArray nodes = obj["nodes"].to(); + uint8_t count = 0; + for (const auto & sensor : EMSESP::temperaturesensor_.sensors()) { + JsonObject node = nodes.add(); + node["id"] = (EMSdevice::DeviceTypeUniqueID::TEMPERATURESENSOR_UID * 100) + count++; + + JsonObject dv = node["dv"].to(); + dv["id"] = sensor.name(); + if (EMSESP::system_.fahrenheit()) { + if (Helpers::hasValue(sensor.temperature_c)) { + dv["v"] = (float)sensor.temperature_c * 0.18 + 32; + } + dv["u"] = DeviceValueUOM::FAHRENHEIT; + } else { + if (Helpers::hasValue(sensor.temperature_c)) { + dv["v"] = (float)sensor.temperature_c / 10; + } + dv["u"] = DeviceValueUOM::DEGREES; + } + } + } // add analog sensors + if (EMSESP::analog_enabled() && EMSESP::analogsensor_.have_sensors()) { + JsonObject obj = root.add(); + obj["id"] = EMSdevice::DeviceTypeUniqueID::ANALOGSENSOR_UID; // it's unique id + obj["t"] = EMSdevice::DeviceType::ANALOGSENSOR; // device type number + JsonArray nodes = obj["nodes"].to(); + uint8_t count = 0; + for (const auto & sensor : EMSESP::analogsensor_.sensors()) { + if (sensor.type() != AnalogSensor::AnalogType::NOTUSED) { + JsonObject node = nodes.add(); + node["id"] = (EMSdevice::DeviceTypeUniqueID::ANALOGSENSOR_UID * 100) + count++; - // show scheduler, with name, on/off - and pencil edit + JsonObject dv = node["dv"].to(); + dv["id"] = sensor.name(); + dv["v"] = Helpers::transformNumFloat(sensor.value(), 0); // is optional and is a float + dv["u"] = sensor.uom(); + } + } + } + + // show scheduler, with name, on/off + if (EMSESP::webSchedulerService.count_entities()) { + JsonObject obj = root.add(); + obj["id"] = EMSdevice::DeviceTypeUniqueID::SCHEDULER_UID; // it's unique id + obj["t"] = EMSdevice::DeviceType::SCHEDULER; // device type number + JsonArray nodes = obj["nodes"].to(); + uint8_t count = 0; + + EMSESP::webSchedulerService.read([&](const WebScheduler & webScheduler) { + for (const ScheduleItem & scheduleItem : webScheduler.scheduleItems) { + // only add if we have a name - we don't need a u (UOM) for this + if (!scheduleItem.name.empty()) { + JsonObject node = nodes.add(); + node["id"] = (EMSdevice::DeviceTypeUniqueID::SCHEDULER_UID * 100) + count++; + + JsonObject dv = node["dv"].to(); + dv["id"] = scheduleItem.name; + dv["v"] = scheduleItem.flags != SCHEDULEFLAG_SCHEDULE_IMMEDIATE ? scheduleItem.active : false; // value is active + } + } + }); + } #if defined(EMSESP_TEST) && defined(EMSESP_STANDALONE) Serial.println(); - Serial.print("ALL dashboard_data: "); - serializeJsonPretty(root, Serial); + Serial.print("All dashboard_data: "); + serializeJson(root, Serial); Serial.println(); #endif From 6d45dfb9254292bca34ec7eef64b27c19db5db22 Mon Sep 17 00:00:00 2001 From: proddy Date: Fri, 11 Oct 2024 10:49:02 +0100 Subject: [PATCH 50/66] DE translations for Dashboard --- interface/src/i18n/de/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/src/i18n/de/index.ts b/interface/src/i18n/de/index.ts index 724530efd..7e52d6d2f 100644 --- a/interface/src/i18n/de/index.ts +++ b/interface/src/i18n/de/index.ts @@ -341,9 +341,9 @@ const de: Translation = { RESTARTING_PRE: 'Initialisierung', RESTARTING_POST: 'Vorbereitung', AUTO_SCROLL: 'Automatisches Scrollen', - DASHBOARD: 'Dashboard', // TODO translate - NO_DATA: 'No data available', // TODO translate - DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorite using the Customizations module.' // TODO translate + DASHBOARD: 'Dashboard', + NO_DATA: 'Keine Daten verfügbar', + DASHBOARD_1: 'Passen Sie Ihr Dashboard an, indem Sie EMS-Entitäten mithilfe des Moduls „Anpassungen“ als Favorit markieren.' }; export default de; From ef3d42b61905365c46e609d5d86ad8747b09766a Mon Sep 17 00:00:00 2001 From: MichaelDvP Date: Fri, 11 Oct 2024 19:51:39 +0200 Subject: [PATCH 51/66] fix s3 board_build.extra_flags for USB serial --- platformio.ini | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/platformio.ini b/platformio.ini index 92dd7f86a..4572f19d2 100644 --- a/platformio.ini +++ b/platformio.ini @@ -122,7 +122,6 @@ board = lolin_s3 board_build.f_cpu = 240000000L board_upload.flash_size = 16MB board_build.partitions = esp32_partition_16M.csv -board_build.extra_flags = -DBOARD_HAS_PSRAM ; ; Direct builds @@ -154,7 +153,6 @@ extends = espressif32_base_tasmota board = lolin_c3_mini board_upload.flash_size = 4MB board_build.partitions = esp32_partition_4M.csv -board_build.extra_flags = '-DEMSESP_DEFAULT_BOARD_PROFILE="C3MINI"' ; lolin C3 mini v1 needs special wifi init. ; https://www.wemos.cc/en/latest/c3/c3_mini_1_0_0.html#about-wifi @@ -163,14 +161,16 @@ extends = espressif32_base_tasmota board = lolin_c3_mini board_upload.flash_size = 4MB board_build.partitions = esp32_partition_4M.csv -board_build.extra_flags = -DBOARD_C3_MINI_V1 '-DEMSESP_DEFAULT_BOARD_PROFILE="C3MINI"' +build_flags = + ${common.build_flags} + -DTASMOTA_SDK + -DBOARD_C3_MINI_V1 [env:s2_4M] extends = espressif32_base_tasmota board = lolin_s2_mini board_upload.flash_size = 4MB board_build.partitions = esp32_partition_4M.csv -board_build.extra_flags = '-DEMSESP_DEFAULT_BOARD_PROFILE="S2MINI"' ; https://github.com/platformio/platform-espressif32/blob/master/boards/lolin_s3.json [env:s3_16M_P] @@ -180,7 +180,6 @@ board_upload.use_1200bps_touch = false board_upload.wait_for_upload_port = false board_upload.flash_size = 16MB board_build.partitions = esp32_partition_16M.csv -board_build.extra_flags = -DBOARD_HAS_PSRAM [env:s3_32M_P] extends = espressif32_base @@ -189,7 +188,6 @@ board_build.arduino.memory_type: opi_opi board_build.flash_mode = opi board_upload.flash_size = 32MB board_build.partitions = esp32_partition_32M.csv -board_build.extra_flags = -DBOARD_HAS_PSRAM ; ; Building and testing natively, standalone without an ESP32. From 33efb1a440634cfa8b77452bc0bd214bdacc69cf Mon Sep 17 00:00:00 2001 From: MichaelDvP Date: Fri, 11 Oct 2024 20:09:31 +0200 Subject: [PATCH 52/66] all dashboard changes uses commands --- interface/src/app/main/Dashboard.tsx | 69 ++--------- interface/src/app/main/DeviceIcon.tsx | 13 +- src/web/WebDataService.cpp | 163 ++++++++++++++------------ src/web/WebSchedulerService.cpp | 23 ---- 4 files changed, 102 insertions(+), 166 deletions(-) diff --git a/interface/src/app/main/Dashboard.tsx b/interface/src/app/main/Dashboard.tsx index 061fa8165..002727f52 100644 --- a/interface/src/app/main/Dashboard.tsx +++ b/interface/src/app/main/Dashboard.tsx @@ -3,7 +3,6 @@ import { IconContext } from 'react-icons/lib'; import { toast } from 'react-toastify'; import ChevronRightIcon from '@mui/icons-material/ChevronRight'; -import CircleIcon from '@mui/icons-material/Circle'; import EditIcon from '@mui/icons-material/Edit'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import UnfoldLessIcon from '@mui/icons-material/UnfoldLess'; @@ -27,7 +26,7 @@ import { AuthenticatedContext } from 'contexts/authentication'; import { useI18nContext } from 'i18n/i18n-react'; import { useInterval } from 'utils'; -import { readDashboard, writeDeviceValue, writeSchedule } from '../../api/app'; +import { readDashboard, writeDeviceValue } from '../../api/app'; import DeviceIcon from './DeviceIcon'; import DevicesDialog from './DevicesDialog'; import { formatValue } from './deviceValue'; @@ -35,8 +34,7 @@ import { type DashboardItem, DeviceEntityMask, DeviceType, - type DeviceValue, - type Schedule + type DeviceValue } from './types'; import { deviceValueItemValidation } from './validators'; @@ -63,13 +61,6 @@ const Dashboard = () => { initialData: [] }); - const { send: updateSchedule } = useRequest( - (data: Schedule) => writeSchedule(data), - { - immediate: false - } - ); - const { loading: submitting, send: sendDeviceValue } = useRequest( (data: { id: number; c: string; v: unknown }) => writeDeviceValue(data), { @@ -114,6 +105,11 @@ const Dashboard = () => { border-top: 1px solid #177ac9; border-bottom: 1px solid #177ac9; } + `, + BaseCell: ` + &:nth-of-type(2) { + text-align: right; + } ` }); @@ -196,39 +192,13 @@ const Dashboard = () => { } } if (di.dv) { - return ( - // ids for scheduler, and sensors are between 9600 and 9900 - - {di.id >= 9600 && di.id < 9900 ? di.dv.id : di.dv.id.slice(2)} - - ); + return {di.dv.id.slice(2)}; } }; const hasMask = (id: string, mask: number) => (parseInt(id.slice(0, 2), 16) & mask) === mask; - const toggleSchedule = async (di: DashboardItem) => { - // create a dummy record, the id=0 picks it up - await updateSchedule({ - schedule: { - id: 0, // special number for only changing the active flag - active: !di.dv?.v, - flags: 0, // unused - time: '', // unused - cmd: '', // unused - value: '', // unused - name: di.dv?.id ?? '' - } - }) - .catch((error: Error) => { - toast.error(error.message); - }) - .finally(async () => { - await fetchDashboard(); - }); - }; - const editDashboardValue = (di: DashboardItem) => { setSelectedDashboardItem(di); setDeviceValueDialogOpen(true); @@ -325,25 +295,7 @@ const Dashboard = () => { - {me.admin && di.id < 9700 && di.id >= 9600 ? ( - toggleSchedule(di)} - > - {di.dv?.v ? ( - - ) : ( - - )} - - ) : ( - me.admin && + {me.admin && di.dv?.c && !hasMask(di.dv.id, DeviceEntityMask.DV_READONLY) && ( { sx={{ fontSize: 16 }} /> - ) - )} + )} ) : ( diff --git a/interface/src/app/main/DeviceIcon.tsx b/interface/src/app/main/DeviceIcon.tsx index ad921aacf..b216cbacb 100644 --- a/interface/src/app/main/DeviceIcon.tsx +++ b/interface/src/app/main/DeviceIcon.tsx @@ -10,7 +10,8 @@ import { MdOutlineSensors, MdThermostatAuto } from 'react-icons/md'; -import { TiFlowSwitch } from 'react-icons/ti'; +import { PiFan, PiGauge } from 'react-icons/pi'; +import { TiFlowSwitch, TiThermometer } from 'react-icons/ti'; import { VscVmConnect } from 'react-icons/vsc'; import type { SvgIconProps } from '@mui/material'; @@ -20,8 +21,8 @@ import { DeviceType } from './types'; const deviceIconLookup: { [key in DeviceType]: React.ComponentType | undefined; } = { - [DeviceType.TEMPERATURESENSOR]: MdOutlineSensors, - [DeviceType.ANALOGSENSOR]: MdOutlineSensors, + [DeviceType.TEMPERATURESENSOR]: TiThermometer, + [DeviceType.ANALOGSENSOR]: PiGauge, [DeviceType.BOILER]: CgSmartHomeBoiler, [DeviceType.HEATSOURCE]: CgSmartHomeBoiler, [DeviceType.THERMOSTAT]: MdThermostatAuto, @@ -37,11 +38,11 @@ const deviceIconLookup: { [DeviceType.WATER]: GiTap, [DeviceType.POOL]: MdOutlinePool, [DeviceType.CUSTOM]: MdPlaylistAdd, - [DeviceType.UNKNOWN]: undefined, + [DeviceType.UNKNOWN]: MdOutlineSensors, [DeviceType.SYSTEM]: undefined, [DeviceType.SCHEDULER]: MdMoreTime, - [DeviceType.GENERIC]: undefined, - [DeviceType.VENTILATION]: undefined + [DeviceType.GENERIC]: MdOutlineSensors, + [DeviceType.VENTILATION]: PiFan }; const DeviceIcon = ({ type_id }: { type_id: DeviceType }) => { diff --git a/src/web/WebDataService.cpp b/src/web/WebDataService.cpp index 1aa88ac36..e06eaf556 100644 --- a/src/web/WebDataService.cpp +++ b/src/web/WebDataService.cpp @@ -78,7 +78,7 @@ void WebDataService::core_data(AsyncWebServerRequest * request) { uint8_t customEntities = EMSESP::webCustomEntityService.count_entities(); if (customEntities) { JsonObject obj = devices.add(); - obj["id"] = 99; // the last unique id + obj["id"] = EMSdevice::DeviceTypeUniqueID::CUSTOM_UID; obj["tn"] = Helpers::translated_word(FL_(custom_device)); // translated device type name obj["t"] = EMSdevice::DeviceType::CUSTOM; // device type number obj["b"] = Helpers::translated_word(FL_(na)); // brand @@ -194,7 +194,7 @@ void WebDataService::device_data(AsyncWebServerRequest * request) { } #ifndef EMSESP_STANDALONE - if (id == 99) { + if (id == EMSdevice::DeviceTypeUniqueID::CUSTOM_UID) { JsonObject output = response->getRoot(); EMSESP::webCustomEntityService.generate_value_web(output); response->setLength(); @@ -224,84 +224,76 @@ void WebDataService::write_device_value(AsyncWebServerRequest * request, JsonVar } // using the unique ID from the web find the real device type - for (const auto & emsdevice : EMSESP::emsdevices) { - if (emsdevice->unique_id() == unique_id) { - // create JSON for output - auto * response = new AsyncJsonResponse(false); - JsonObject output = response->getRoot(); - - // the data could be in any format, but we need string - // authenticated is always true - uint8_t return_code = CommandRet::NOT_FOUND; - uint8_t device_type = emsdevice->device_type(); - // parse the command as it could have a hc or dhw prefixed, e.g. hc2/seltemp - int8_t id = -1; // default - if (device_type >= EMSdevice::DeviceType::BOILER) { - cmd = Command::parse_command_string(cmd, id); // extract hc or dhw - } - if (data.is()) { - return_code = Command::call(device_type, cmd, data.as(), true, id, output); - } else if (data.is()) { - char s[10]; - return_code = Command::call(device_type, cmd, Helpers::render_value(s, data.as(), 0), true, id, output); - } else if (data.is()) { - char s[10]; - return_code = Command::call(device_type, cmd, Helpers::render_value(s, data.as(), 1), true, id, output); - } else if (data.is()) { - return_code = Command::call(device_type, cmd, data.as() ? "true" : "false", true, id, output); - } - - // write log - if (return_code != CommandRet::OK) { - EMSESP::logger().err("Write command failed %s (%s)", (const char *)output["message"], Command::return_code_string(return_code)); - } else { -#if defined(EMSESP_DEBUG) - EMSESP::logger().debug("Write command successful"); -#endif + uint8_t device_type = EMSdevice::DeviceType::UNKNOWN; + switch (unique_id) { + case EMSdevice::DeviceTypeUniqueID::CUSTOM_UID: + device_type = EMSdevice::DeviceType::CUSTOM; + break; + case EMSdevice::DeviceTypeUniqueID::SCHEDULER_UID: + device_type = EMSdevice::DeviceType::SCHEDULER; + break; + case EMSdevice::DeviceTypeUniqueID::TEMPERATURESENSOR_UID: + device_type = EMSdevice::DeviceType::TEMPERATURESENSOR; + break; + case EMSdevice::DeviceTypeUniqueID::ANALOGSENSOR_UID: + device_type = EMSdevice::DeviceType::ANALOGSENSOR; + break; + default: + for (const auto & emsdevice : EMSESP::emsdevices) { + if (emsdevice->unique_id() == unique_id) { + device_type = emsdevice->device_type(); + break; } - - response->setCode((return_code == CommandRet::OK) ? 200 : 400); // bad request - response->setLength(); - request->send(response); - return; } + break; + } + if (device_type == EMSdevice::DeviceType::UNKNOWN) { + EMSESP::logger().warning("Write command failed, bad device id: %d", unique_id); + AsyncWebServerResponse * response = request->beginResponse(400); // bad request + request->send(response); + return; + } + // create JSON for output + auto * response = new AsyncJsonResponse(false); + JsonObject output = response->getRoot(); + // the data could be in any format, but we need string + // authenticated is always true + uint8_t return_code = CommandRet::NOT_FOUND; + // parse the command as it could have a hc or dhw prefixed, e.g. hc2/seltemp + int8_t id = -1; // default + if (device_type >= EMSdevice::DeviceType::BOILER) { + cmd = Command::parse_command_string(cmd, id); // extract hc or dhw + } + if (data.is()) { + return_code = Command::call(device_type, cmd, data.as(), true, id, output); + } else if (data.is()) { + char s[10]; + return_code = Command::call(device_type, cmd, Helpers::render_value(s, data.as(), 0), true, id, output); + } else if (data.is()) { + char s[10]; + return_code = Command::call(device_type, cmd, Helpers::render_value(s, data.as(), 1), true, id, output); + } else if (data.is()) { + return_code = Command::call(device_type, cmd, data.as() ? "true" : "false", true, id, output); } - // special check for custom entities (which have a unique id of 99) - if (unique_id == 99) { - auto * response = new AsyncJsonResponse(false); - JsonObject output = response->getRoot(); - uint8_t return_code = CommandRet::NOT_FOUND; - uint8_t device_type = EMSdevice::DeviceType::CUSTOM; - // parse the command as it could have a hc or dhw prefixed, e.g. hc2/seltemp - int8_t id = -1; - if (device_type >= EMSdevice::DeviceType::BOILER) { - cmd = Command::parse_command_string(cmd, id); // extract hc or dhw - } - if (data.is()) { - return_code = Command::call(device_type, cmd, data.as(), true, id, output); - } else if (data.is()) { - char s[10]; - return_code = Command::call(device_type, cmd, Helpers::render_value(s, data.as(), 0), true, id, output); - } else if (data.is()) { - char s[10]; - return_code = Command::call(device_type, cmd, Helpers::render_value(s, data.as(), 1), true, id, output); - } - if (return_code != CommandRet::OK) { - EMSESP::logger().err("Write command failed %s (%s)", (const char *)output["message"], Command::return_code_string(return_code)); - } else { + // write log + if (return_code != CommandRet::OK) { + // is already logged by command and message contains code + // EMSESP::logger().err("Write command failed %s (%s)", (const char *)output["message"], Command::return_code_string(return_code)); + } else { #if defined(EMSESP_DEBUG) - EMSESP::logger().debug("Write command successful"); + EMSESP::logger().debug("Write command successful"); #endif - } - - response->setCode((return_code == CommandRet::OK) ? 200 : 400); // bad request - response->setLength(); - request->send(response); - return; } + + response->setCode((return_code == CommandRet::OK) ? 200 : 400); // bad request + response->setLength(); + request->send(response); + return; } + EMSESP::logger().warning("Write command failed, bad json"); + // if we reach here, fail AsyncWebServerResponse * response = request->beginResponse(400); // bad request request->send(response); @@ -393,7 +385,7 @@ void WebDataService::dashboard_data(AsyncWebServerRequest * request) { node["id"] = (EMSdevice::DeviceTypeUniqueID::TEMPERATURESENSOR_UID * 100) + count++; JsonObject dv = node["dv"].to(); - dv["id"] = sensor.name(); + dv["id"] = "00" + sensor.name(); if (EMSESP::system_.fahrenheit()) { if (Helpers::hasValue(sensor.temperature_c)) { dv["v"] = (float)sensor.temperature_c * 0.18 + 32; @@ -421,9 +413,20 @@ void WebDataService::dashboard_data(AsyncWebServerRequest * request) { node["id"] = (EMSdevice::DeviceTypeUniqueID::ANALOGSENSOR_UID * 100) + count++; JsonObject dv = node["dv"].to(); - dv["id"] = sensor.name(); - dv["v"] = Helpers::transformNumFloat(sensor.value(), 0); // is optional and is a float - dv["u"] = sensor.uom(); + dv["id"] = "00" + sensor.name(); + if (sensor.type() == AnalogSensor::AnalogType::DIGITAL_OUT || sensor.type() == AnalogSensor::AnalogType::DIGITAL_IN) { + char s[12]; + dv["v"] = Helpers::render_boolean(s, sensor.value() != 0, true); + JsonArray l = dv["l"].to(); + l.add(Helpers::render_boolean(s, false, true)); + l.add(Helpers::render_boolean(s, true, true)); + } else { + dv["v"] = Helpers::transformNumFloat(sensor.value(), 0); + } + if (sensor.type() == AnalogSensor::AnalogType::COUNTER || sensor.type() >= AnalogSensor::AnalogType::DIGITAL_OUT) { + dv["c"] = sensor.name(); + } + dv["u"] = sensor.uom(); } } } @@ -444,8 +447,13 @@ void WebDataService::dashboard_data(AsyncWebServerRequest * request) { node["id"] = (EMSdevice::DeviceTypeUniqueID::SCHEDULER_UID * 100) + count++; JsonObject dv = node["dv"].to(); - dv["id"] = scheduleItem.name; - dv["v"] = scheduleItem.flags != SCHEDULEFLAG_SCHEDULE_IMMEDIATE ? scheduleItem.active : false; // value is active + dv["id"] = "00" + scheduleItem.name; + dv["c"] = scheduleItem.name; + char s[12]; + dv["v"] = Helpers::render_boolean(s, scheduleItem.active, true); + JsonArray l = dv["l"].to(); + l.add(Helpers::render_boolean(s, false, true)); + l.add(Helpers::render_boolean(s, true, true)); } } }); @@ -462,5 +470,4 @@ void WebDataService::dashboard_data(AsyncWebServerRequest * request) { request->send(response); } - } // namespace emsesp \ No newline at end of file diff --git a/src/web/WebSchedulerService.cpp b/src/web/WebSchedulerService.cpp index dae491fa9..525367b18 100644 --- a/src/web/WebSchedulerService.cpp +++ b/src/web/WebSchedulerService.cpp @@ -65,29 +65,6 @@ void WebScheduler::read(WebScheduler & webScheduler, JsonObject root) { // call on initialization and also when the Schedule web page is saved // this loads the data into the internal class StateUpdateResult WebScheduler::update(JsonObject root, WebScheduler & webScheduler) { - // check if we're only toggling it on/off via the Dashboard - // if it is a single schedule object and id is 0 - if (root["schedule"].is()) { - JsonObject si = root["schedule"]; - if (si["id"] == 0) { - // find the item, matching the name - for (ScheduleItem & scheduleItem : webScheduler.scheduleItems) { - std::string name = si["name"].as(); - if (scheduleItem.name == name) { - scheduleItem.active = si["active"]; - EMSESP::webSchedulerService.publish(true); - return StateUpdateResult::CHANGED; - } - } - return StateUpdateResult::UNCHANGED; - } - } - - // otherwise we're updating and saving the whole list again - if (!root["schedule"].is()) { - return StateUpdateResult::ERROR; // invalid format - } - // reset the list Command::erase_device_commands(EMSdevice::DeviceType::SCHEDULER); webScheduler.scheduleItems.clear(); From 662df4c902e97c6fe04f07f6c0352008a1199519 Mon Sep 17 00:00:00 2001 From: proddy Date: Fri, 11 Oct 2024 20:38:41 +0100 Subject: [PATCH 53/66] package update --- interface/package.json | 4 ++-- interface/yarn.lock | 42 +++++++++++++++++++++--------------------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/interface/package.json b/interface/package.json index 802a1d620..22f227f34 100644 --- a/interface/package.json +++ b/interface/package.json @@ -35,7 +35,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-icons": "^5.3.0", - "react-router-dom": "^6.26.2", + "react-router-dom": "^6.27.0", "react-toastify": "^10.0.5", "typesafe-i18n": "^5.26.2", "typescript": "^5.6.3" @@ -49,7 +49,7 @@ "@types/formidable": "^3", "@types/node": "^22.7.5", "@types/react": "^18.3.11", - "@types/react-dom": "^18.3.0", + "@types/react-dom": "^18.3.1", "@types/react-router-dom": "^5.3.3", "concurrently": "^9.0.1", "eslint": "^9.12.0", diff --git a/interface/yarn.lock b/interface/yarn.lock index bf670a2eb..b1ac8d749 100644 --- a/interface/yarn.lock +++ b/interface/yarn.lock @@ -1360,10 +1360,10 @@ __metadata: languageName: node linkType: hard -"@remix-run/router@npm:1.19.2": - version: 1.19.2 - resolution: "@remix-run/router@npm:1.19.2" - checksum: 10c0/ac7fc813350686705f2c29219e70e1e299d9a8e3b301e9e81f7e84f578c40c6462b590cf0d78863bac40dbc325b68c71ae070f4a1465793d1d1971b619618295 +"@remix-run/router@npm:1.20.0": + version: 1.20.0 + resolution: "@remix-run/router@npm:1.20.0" + checksum: 10c0/2e017dea530717a6e93a16d478714c4c9165313a1c48e39172ec609bc20324ca6362e8ee2243602df6343644c9268d82a3f50f154d3bb8a17dddde6c37be6e83 languageName: node linkType: hard @@ -1699,12 +1699,12 @@ __metadata: languageName: node linkType: hard -"@types/react-dom@npm:^18.3.0": - version: 18.3.0 - resolution: "@types/react-dom@npm:18.3.0" +"@types/react-dom@npm:^18.3.1": + version: 18.3.1 + resolution: "@types/react-dom@npm:18.3.1" dependencies: "@types/react": "npm:*" - checksum: 10c0/6c90d2ed72c5a0e440d2c75d99287e4b5df3e7b011838cdc03ae5cd518ab52164d86990e73246b9d812eaf02ec351d74e3b4f5bd325bf341e13bf980392fd53b + checksum: 10c0/8b416551c60bb6bd8ec10e198c957910cfb271bc3922463040b0d57cf4739cdcd24b13224f8d68f10318926e1ec3cd69af0af79f0291b599a992f8c80d47f1eb languageName: node linkType: hard @@ -1910,7 +1910,7 @@ __metadata: "@types/formidable": "npm:^3" "@types/node": "npm:^22.7.5" "@types/react": "npm:^18.3.11" - "@types/react-dom": "npm:^18.3.0" + "@types/react-dom": "npm:^18.3.1" "@types/react-router-dom": "npm:^5.3.3" alova: "npm:3.0.20" async-validator: "npm:^4.2.5" @@ -1925,7 +1925,7 @@ __metadata: react: "npm:^18.3.1" react-dom: "npm:^18.3.1" react-icons: "npm:^5.3.0" - react-router-dom: "npm:^6.26.2" + react-router-dom: "npm:^6.27.0" react-toastify: "npm:^10.0.5" rollup-plugin-visualizer: "npm:^5.12.0" terser: "npm:^5.34.1" @@ -6003,27 +6003,27 @@ __metadata: languageName: node linkType: hard -"react-router-dom@npm:^6.26.2": - version: 6.26.2 - resolution: "react-router-dom@npm:6.26.2" +"react-router-dom@npm:^6.27.0": + version: 6.27.0 + resolution: "react-router-dom@npm:6.27.0" dependencies: - "@remix-run/router": "npm:1.19.2" - react-router: "npm:6.26.2" + "@remix-run/router": "npm:1.20.0" + react-router: "npm:6.27.0" peerDependencies: react: ">=16.8" react-dom: ">=16.8" - checksum: 10c0/7515128a98eef0a6b2bf354ef9dfefad03556a06be00fa9220eda6526aaada8a42f294911083473d7ced6d7128c3088bd193218bbb3d62593f9f4f7053781c23 + checksum: 10c0/7db48ffd0b387af0eed060ceaf42075d074e63fbd30f4cf60993526b3610883a9ff82615965001165ed69d2bf2f1bce05c594a21c8d0d845e7b9bf203201116e languageName: node linkType: hard -"react-router@npm:6.26.2": - version: 6.26.2 - resolution: "react-router@npm:6.26.2" +"react-router@npm:6.27.0": + version: 6.27.0 + resolution: "react-router@npm:6.27.0" dependencies: - "@remix-run/router": "npm:1.19.2" + "@remix-run/router": "npm:1.20.0" peerDependencies: react: ">=16.8" - checksum: 10c0/0d15a39b419c99fb5ccad76388bfc4ee2b01323b3b1b694595a9f9ea28e1fbeea25486b5398f5d3d93922f5c6a9aa751b6bb27419488d85279f6ca5ff9e0a6bb + checksum: 10c0/440d6ee00890cec92a0c2183164149fbb96363efccf52bb132a964f44e51aec2f4b5a0520c67f6f17faddaa4097090fd76f7efe58263947532fceeb11dd4cdf3 languageName: node linkType: hard From 38f42445aa832a45a95e3a4dd3de3a364080b37e Mon Sep 17 00:00:00 2001 From: proddy Date: Fri, 11 Oct 2024 22:00:39 +0100 Subject: [PATCH 54/66] update test data for dashboard --- mock-api/rest_server.ts | 91 +++++++++++++++++++++++------------------ 1 file changed, 52 insertions(+), 39 deletions(-) diff --git a/mock-api/rest_server.ts b/mock-api/rest_server.ts index ea9ff6694..2c6e2dd31 100644 --- a/mock-api/rest_server.ts +++ b/mock-api/rest_server.ts @@ -4351,7 +4351,6 @@ router t: element.t, nodes: getDashboardEntityData(id) }; - // only add to dashboard if we have values if ((dashboard_object.nodes ?? []).length > 0) { dashboard_data.push(dashboard_object); @@ -4374,7 +4373,7 @@ router sensor_data = emsesp_sensordata.ts.map((item, index) => ({ id: DeviceTypeUniqueID.TEMPERATURESENSOR_UID * 100 + index, dv: { - id: item.n, + id: '00' + item.n, v: item.t, // value is called t in ts (temperature) u: item.u } @@ -4395,12 +4394,11 @@ router sensor_data = sensor_data.map((item, index) => ({ id: DeviceTypeUniqueID.ANALOGSENSOR_UID * 100 + index, dv: { - id: item.n, + id: '00' + item.n, v: item.v, u: item.u } })); - dashboard_object = { id: DeviceTypeUniqueID.ANALOGSENSOR_UID, t: DeviceType.ANALOGSENSOR, @@ -4417,9 +4415,10 @@ router let scheduler_data2 = scheduler_data.map((item, index) => ({ id: DeviceTypeUniqueID.SCHEDULER_UID * 100 + index, dv: { - id: item.name, - v: item.active - // u: item.u // don't need uom + id: '00' + item.name, + v: item.active ? 'on' : 'off', + c: item.name, + l: ['off', 'on'] } })); dashboard_object = { @@ -4431,22 +4430,39 @@ router if ((dashboard_object.nodes ?? []).length > 0) { dashboard_data.push(dashboard_object); } - // } else { - // for testing - // single object - const element = emsesp_coredata.devices[3]; // pick the 4th device - const id = element.id; + // for testing only + + // add the custom entity data dashboard_object = { - id: id, - n: element.n, - t: element.t, - nodes: getDashboardEntityData(id) + id: DeviceTypeUniqueID.CUSTOM_UID, // unique ID for custom entities + t: DeviceType.CUSTOM, + nodes: getDashboardEntityData(99) }; + // only add to dashboard if we have values + if ((dashboard_object.nodes ?? []).length > 0) { + dashboard_data.push(dashboard_object); + } + + let scheduler_data = emsesp_schedule.schedule.filter((item) => item.name); + let scheduler_data2 = scheduler_data.map((item, index) => ({ + id: DeviceTypeUniqueID.SCHEDULER_UID * 100 + index, + dv: { + id: '00' + item.name, + v: item.active ? 'on' : 'off', + c: item.name, + l: ['off', 'on'] + } + })); + dashboard_object = { + id: DeviceTypeUniqueID.SCHEDULER_UID, + t: DeviceType.SCHEDULER, + nodes: scheduler_data2 + }; + // only add to dashboard if we have values if ((dashboard_object.nodes ?? []).length > 0) { dashboard_data.push(dashboard_object); } - console.log('dashboard_data: ', dashboard_data); } // console.log('dashboard_data: ', dashboard_data); @@ -4498,24 +4514,6 @@ router // Scheduler .post(EMSESP_SCHEDULE_ENDPOINT, async (request: any) => { const content = await request.json(); - // check if we're changing active from the Dashboard - if (content.schedule.id === 0) { - console.log( - "Toggle schedule '" + - content.schedule.name + - "' to " + - content.schedule.active - ); - // find the schedule in emsesp_schedule via the name and toggle the active - const objIndex = emsesp_schedule.schedule.findIndex( - (obj) => obj.name === content.schedule.name - ); - if (objIndex !== -1) { - emsesp_schedule.schedule[objIndex].active = content.schedule.active; - } - - return status(200); - } emsesp_schedule = content; console.log('schedule saved', emsesp_schedule); return status(200); @@ -4557,6 +4555,10 @@ router const value = content.v; const id = content.id; + console.log( + 'write device value, id: ' + id + ' command: ' + command + ' value: ' + value + ); + var objIndex; if (id === 1) { objIndex = emsesp_devicedata_1.nodes.findIndex((obj) => obj.c == command); @@ -4598,16 +4600,27 @@ router objIndex = emsesp_devicedata_10.nodes.findIndex((obj) => obj.c == command); emsesp_devicedata_10.nodes[objIndex].v = value; } - if (id === 99) { + if (id === DeviceTypeUniqueID.CUSTOM_UID) { // custom entities objIndex = emsesp_devicedata_99.nodes.findIndex((obj) => obj.c == command); emsesp_devicedata_99.nodes[objIndex].v = value; } + if (id === DeviceTypeUniqueID.SCHEDULER_UID) { + // toggle scheduler + // find the schedule in emsesp_schedule via the name and toggle the active + const objIndex = emsesp_schedule.schedule.findIndex( + (obj) => obj.name === command + ); + if (objIndex !== -1) { + emsesp_schedule.schedule[objIndex].active = value; + console.log("Toggle schedule '" + command + "' to " + value); + } + } // await delay(1000); // wait to show spinner - console.log( - 'Device Value updated. command:' + command + ' value:' + value + ' id:' + id - ); + // console.log( + // 'Device Value updated. command:' + command + ' value:' + value + ' id:' + id + // ); return status(200); }) From c79d5f28905900938ca2f29ff2add8e078e4864f Mon Sep 17 00:00:00 2001 From: proddy Date: Fri, 11 Oct 2024 22:00:57 +0100 Subject: [PATCH 55/66] toast slide in from left so it doesnt obscure data --- interface/src/App.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/App.tsx b/interface/src/App.tsx index f6c9d49be..f9f3a9a91 100644 --- a/interface/src/App.tsx +++ b/interface/src/App.tsx @@ -25,7 +25,7 @@ const App = () => { Date: Fri, 11 Oct 2024 22:01:13 +0100 Subject: [PATCH 56/66] table formatting, remove inner scrollbar --- interface/src/app/main/Dashboard.tsx | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/interface/src/app/main/Dashboard.tsx b/interface/src/app/main/Dashboard.tsx index 002727f52..235d2c70f 100644 --- a/interface/src/app/main/Dashboard.tsx +++ b/interface/src/app/main/Dashboard.tsx @@ -88,28 +88,34 @@ const Dashboard = () => { const dashboard_theme = useTheme({ Table: ` - --data-table-library_grid-template-columns: minmax(80px, auto) 120px 40px; + --data-table-library_grid-template-columns: minmax(80px, auto) 120px 32px; `, BaseRow: ` font-size: 14px; .td { - height: 32px; + height: 28px; } `, Row: ` + cursor: pointer; background-color: #1e1e1e; .td { - height: 22px; + // border-top: 1px solid #0000; + // border-bottom: 1px solid #0000; } &:hover .td { - border-top: 1px solid #177ac9; - border-bottom: 1px solid #177ac9; + background-color: #177ac9; + // border-top: 1px solid #177ac9; + // border-bottom: 1px solid #177ac9; } `, BaseCell: ` &:nth-of-type(2) { text-align: right; } + &:nth-of-type(3) { + text-align: right; + } ` }); @@ -164,9 +170,9 @@ const Dashboard = () => { case DeviceType.CUSTOM: return LL.CUSTOM_ENTITIES(0); case DeviceType.ANALOGSENSOR: - return LL.ANALOG_SENSOR(0); + return LL.ANALOG_SENSORS(); case DeviceType.TEMPERATURESENSOR: - return LL.TEMP_SENSOR(); + return LL.TEMP_SENSORS(); case DeviceType.SCHEDULER: return LL.SCHEDULER(); default: @@ -294,7 +300,7 @@ const Dashboard = () => { - + {me.admin && di.dv?.c && !hasMask(di.dv.id, DeviceEntityMask.DV_READONLY) && ( From ebd7d86502bbfd1b3a4e166b3b441cf3794bdb5b Mon Sep 17 00:00:00 2001 From: proddy Date: Sat, 12 Oct 2024 01:06:09 +0100 Subject: [PATCH 57/66] table formatting --- interface/src/app/main/Dashboard.tsx | 16 ++++++++++--- interface/src/app/main/Devices.tsx | 36 ++++++++++++++++++---------- 2 files changed, 36 insertions(+), 16 deletions(-) diff --git a/interface/src/app/main/Dashboard.tsx b/interface/src/app/main/Dashboard.tsx index 235d2c70f..558c7ac02 100644 --- a/interface/src/app/main/Dashboard.tsx +++ b/interface/src/app/main/Dashboard.tsx @@ -103,6 +103,10 @@ const Dashboard = () => { // border-top: 1px solid #0000; // border-bottom: 1px solid #0000; } + &.tr.tr-body.row-select.row-select-single-selected { + background-color: #177ac9; + font-weight: normal; + } &:hover .td { background-color: #177ac9; // border-top: 1px solid #177ac9; @@ -206,8 +210,10 @@ const Dashboard = () => { (parseInt(id.slice(0, 2), 16) & mask) === mask; const editDashboardValue = (di: DashboardItem) => { - setSelectedDashboardItem(di); - setDeviceValueDialogOpen(true); + if (me.admin && di.dv?.c) { + setSelectedDashboardItem(di); + setDeviceValueDialogOpen(true); + } }; const handleShowAll = ( @@ -282,7 +288,11 @@ const Dashboard = () => { {(tableList: DashboardItem[]) => ( {tableList.map((di: DashboardItem) => ( - + editDashboardValue(di)} + > {di.id > 99 ? ( <> {showName(di)} diff --git a/interface/src/app/main/Devices.tsx b/interface/src/app/main/Devices.tsx index 462e4326b..5e0146a56 100644 --- a/interface/src/app/main/Devices.tsx +++ b/interface/src/app/main/Devices.tsx @@ -148,22 +148,23 @@ const Devices = () => { } `, Row: ` - background-color: #1E1E1E; - position: relative; cursor: pointer; + background-color: #1E1E1E; + // position: relative; .td { padding: 8px; - border-top: 1px solid #565656; - border-bottom: 1px solid #565656; + // border-top: 1px solid #565656; + // border-bottom: 1px solid #565656; } &.tr.tr-body.row-select.row-select-single-selected { - background-color: #3d4752; + background-color: #177ac9; font-weight: normal; } - &:hover .td { - border-top: 1px solid #177ac9; - border-bottom: 1px solid #177ac9; - } + // &:hover .td { + // background-color: #177ac9; + // border-top: 1px solid #177ac9; + // border-bottom: 1px solid #177ac9; + // } ` }); @@ -174,14 +175,18 @@ const Devices = () => { --data-table-library_grid-template-columns: repeat(1, minmax(0, 1fr)) 130px; `, BaseRow: ` - .td { - height: 42px; - } + // .td { + // height: 42px; + // } `, HeaderRow: ` .th { padding: 8px; height: 36px; + `, + Row: ` + &:hover .td { + background-color: #177ac9; ` } ]); @@ -222,7 +227,12 @@ const Devices = () => { Row: ` &:nth-of-type(odd) .td { background-color: #303030; - } + }, + &:hover .td { + background-color: #177ac9; + // border-top: 1px solid #177ac9; + // border-bottom: 1px solid #177ac9; + } ` } ]); From b251fa4f267348144d5c0bdc12069fa248420659 Mon Sep 17 00:00:00 2001 From: proddy Date: Sat, 12 Oct 2024 10:43:52 +0100 Subject: [PATCH 58/66] alova update --- interface/package.json | 4 ++-- interface/yarn.lock | 34 +++++++++++++++++----------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/interface/package.json b/interface/package.json index 22f227f34..ec076a48c 100644 --- a/interface/package.json +++ b/interface/package.json @@ -21,13 +21,13 @@ "lint": "eslint . --fix" }, "dependencies": { - "@alova/adapter-xhr": "2.0.7", + "@alova/adapter-xhr": "2.0.8", "@emotion/react": "^11.13.3", "@emotion/styled": "^11.13.0", "@mui/icons-material": "^6.1.3", "@mui/material": "^6.1.3", "@table-library/react-table-library": "4.1.7", - "alova": "3.0.20", + "alova": "3.1.0", "async-validator": "^4.2.5", "jwt-decode": "^4.0.0", "mime-types": "^2.1.35", diff --git a/interface/yarn.lock b/interface/yarn.lock index b1ac8d749..270ae0232 100644 --- a/interface/yarn.lock +++ b/interface/yarn.lock @@ -5,21 +5,21 @@ __metadata: version: 8 cacheKey: 10c0 -"@alova/adapter-xhr@npm:2.0.7": - version: 2.0.7 - resolution: "@alova/adapter-xhr@npm:2.0.7" +"@alova/adapter-xhr@npm:2.0.8": + version: 2.0.8 + resolution: "@alova/adapter-xhr@npm:2.0.8" dependencies: - "@alova/shared": "npm:^1.0.5" + "@alova/shared": "npm:^1.0.6" peerDependencies: - alova: ^3.0.16 - checksum: 10c0/2c21313ef963df22af08ae652c310381f681948e2edca2f863cc92becd7c6e815771649c9a0b77e4193eb507a4bb5bb9e77f332fe194f86f00a4144845b7757e + alova: ^3.0.20 + checksum: 10c0/84b2f8f33733af3ed9418e7eb4d6195115b318717afc588147514d474632eaa703f72f753f18198d1a90da0a688c60a29345aabe0f93941c4ef089b7ac636204 languageName: node linkType: hard -"@alova/shared@npm:^1.0.5": - version: 1.0.5 - resolution: "@alova/shared@npm:1.0.5" - checksum: 10c0/58d99d8b6b026e60c7184c55bdccd43d3b39bec598722135b272fd3c355942facc40f9433dceb6fcabb6631b87766c7227432c2a2bf3ae281d9a26b2eba2a69d +"@alova/shared@npm:^1.0.6": + version: 1.0.6 + resolution: "@alova/shared@npm:1.0.6" + checksum: 10c0/7779bef946a6c9cca9a6369a9fa664be9fe929b0132a94fe60836b787844f884aef1eb69a1f8850003b92169d7495b772b59e40d8de138019386f32cdc689aad languageName: node linkType: hard @@ -1896,7 +1896,7 @@ __metadata: version: 0.0.0-use.local resolution: "EMS-ESP@workspace:." dependencies: - "@alova/adapter-xhr": "npm:2.0.7" + "@alova/adapter-xhr": "npm:2.0.8" "@babel/core": "npm:^7.25.8" "@emotion/react": "npm:^11.13.3" "@emotion/styled": "npm:^11.13.0" @@ -1912,7 +1912,7 @@ __metadata: "@types/react": "npm:^18.3.11" "@types/react-dom": "npm:^18.3.1" "@types/react-router-dom": "npm:^5.3.3" - alova: "npm:3.0.20" + alova: "npm:3.1.0" async-validator: "npm:^4.2.5" concurrently: "npm:^9.0.1" eslint: "npm:^9.12.0" @@ -1994,13 +1994,13 @@ __metadata: languageName: node linkType: hard -"alova@npm:3.0.20": - version: 3.0.20 - resolution: "alova@npm:3.0.20" +"alova@npm:3.1.0": + version: 3.1.0 + resolution: "alova@npm:3.1.0" dependencies: - "@alova/shared": "npm:^1.0.5" + "@alova/shared": "npm:^1.0.6" rate-limiter-flexible: "npm:^5.0.3" - checksum: 10c0/6c2544183dafe09b3381a5eda9eac255dd16ced64a3db8801ececf7795aa9e26b0a17e41675e7d46a3dfcd6542c45911e2c57d2efb45e4633438a0bbb9f13b7e + checksum: 10c0/4f073e37fae112dff2c8b168d37220fa4c9475305d7689869c1338a91d1368f2b941c1131bc46214ddebfa72489b6c449b246e2b4ff484d1ff1486dd7172f694 languageName: node linkType: hard From b78f6da35b0df9e49087563fe1e56a02724b5396 Mon Sep 17 00:00:00 2001 From: proddy Date: Sat, 12 Oct 2024 16:15:08 +0100 Subject: [PATCH 59/66] add comment reminders --- interface/src/app/main/Devices.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/interface/src/app/main/Devices.tsx b/interface/src/app/main/Devices.tsx index 5e0146a56..3cf94054c 100644 --- a/interface/src/app/main/Devices.tsx +++ b/interface/src/app/main/Devices.tsx @@ -153,6 +153,7 @@ const Devices = () => { // position: relative; .td { padding: 8px; + // TODO remove // border-top: 1px solid #565656; // border-bottom: 1px solid #565656; } @@ -160,6 +161,7 @@ const Devices = () => { background-color: #177ac9; font-weight: normal; } + // TODO remove // &:hover .td { // background-color: #177ac9; // border-top: 1px solid #177ac9; @@ -230,6 +232,7 @@ const Devices = () => { }, &:hover .td { background-color: #177ac9; + // TODO remove // border-top: 1px solid #177ac9; // border-bottom: 1px solid #177ac9; } From 67f7cc53b3e422edf51370dffe763955ee2b9873 Mon Sep 17 00:00:00 2001 From: proddy Date: Sat, 12 Oct 2024 16:17:12 +0100 Subject: [PATCH 60/66] update sample test data --- mock-api/rest_server.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/mock-api/rest_server.ts b/mock-api/rest_server.ts index 2c6e2dd31..f8231c602 100644 --- a/mock-api/rest_server.ts +++ b/mock-api/rest_server.ts @@ -4434,15 +4434,15 @@ router // for testing only // add the custom entity data - dashboard_object = { - id: DeviceTypeUniqueID.CUSTOM_UID, // unique ID for custom entities - t: DeviceType.CUSTOM, - nodes: getDashboardEntityData(99) - }; - // only add to dashboard if we have values - if ((dashboard_object.nodes ?? []).length > 0) { - dashboard_data.push(dashboard_object); - } + // dashboard_object = { + // id: DeviceTypeUniqueID.CUSTOM_UID, // unique ID for custom entities + // t: DeviceType.CUSTOM, + // nodes: getDashboardEntityData(99) + // }; + // // only add to dashboard if we have values + // if ((dashboard_object.nodes ?? []).length > 0) { + // dashboard_data.push(dashboard_object); + // } let scheduler_data = emsesp_schedule.schedule.filter((item) => item.name); let scheduler_data2 = scheduler_data.map((item, index) => ({ From 801c1aedaa2b8734413e0da00edc11463c7861eb Mon Sep 17 00:00:00 2001 From: proddy Date: Sat, 12 Oct 2024 16:17:40 +0100 Subject: [PATCH 61/66] add TODOs for formatting reminders --- interface/src/app/main/Dashboard.tsx | 31 ++++++++++++++-------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/interface/src/app/main/Dashboard.tsx b/interface/src/app/main/Dashboard.tsx index 558c7ac02..24eee643a 100644 --- a/interface/src/app/main/Dashboard.tsx +++ b/interface/src/app/main/Dashboard.tsx @@ -100,13 +100,16 @@ const Dashboard = () => { cursor: pointer; background-color: #1e1e1e; .td { + // TODO remove // border-top: 1px solid #0000; // border-bottom: 1px solid #0000; } - &.tr.tr-body.row-select.row-select-single-selected { - background-color: #177ac9; - font-weight: normal; - } + // TODO remove + // &.tr.tr-body.row-select.row-select-single-selected { + // background-color: #177ac9; + // font-weight: normal; + // color: red; + // } &:hover .td { background-color: #177ac9; // border-top: 1px solid #177ac9; @@ -114,12 +117,12 @@ const Dashboard = () => { } `, BaseCell: ` - &:nth-of-type(2) { - text-align: right; - } - &:nth-of-type(3) { - text-align: right; - } + &:nth-of-type(2) { + text-align: right; + } + &:nth-of-type(3) { + text-align: right; + } ` }); @@ -145,7 +148,7 @@ const Dashboard = () => { /> ) }, - indentation: 50 + indentation: 45 } ); @@ -299,13 +302,11 @@ const Dashboard = () => { - {di.dv ? formatValue(LL, di.dv.v, di.dv.u) : ''} + {formatValue(LL, di.dv?.v, di.dv?.u)} From 4ca7da684ef277af87f06d52b8342989c548f07a Mon Sep 17 00:00:00 2001 From: proddy Date: Sun, 13 Oct 2024 00:09:43 +0100 Subject: [PATCH 62/66] add a test_analogsensor3 that is disabled --- src/web/WebCustomizationService.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/web/WebCustomizationService.cpp b/src/web/WebCustomizationService.cpp index 40e6a9533..d06fce320 100644 --- a/src/web/WebCustomizationService.cpp +++ b/src/web/WebCustomizationService.cpp @@ -391,6 +391,15 @@ void WebCustomizationService::test() { analog.type = 1; webCustomization.analogCustomizations.push_back(analog); + analog = AnalogCustomization(); + analog.gpio = 38; + analog.name = "test_analogsensor3"; + analog.offset = 0; + analog.factor = 1; + analog.uom = 0; + analog.type = 0; // disabled, not-used + webCustomization.analogCustomizations.push_back(analog); + // EMS entities, mark some as favorites webCustomization.entityCustomizations.clear(); auto emsEntity = EntityCustomization(); From ed11260ffa246118c3e4630a248146380207773a Mon Sep 17 00:00:00 2001 From: proddy Date: Sun, 13 Oct 2024 00:09:54 +0100 Subject: [PATCH 63/66] don't include disabled analog sensors in dashboard --- src/analogsensor.h | 6 +++++- src/web/WebDataService.cpp | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/analogsensor.h b/src/analogsensor.h index 23cb72668..04e5439ff 100644 --- a/src/analogsensor.h +++ b/src/analogsensor.h @@ -153,7 +153,11 @@ class AnalogSensor { return (!sensors_.empty()); } - size_t count_entities() const { + size_t count_entities(bool count_disabled = false) const { + if (count_disabled) { + // count number of items in sensors_ where type is not set to disabled + return std::count_if(sensors_.begin(), sensors_.end(), [](const Sensor & sensor) { return sensor.type() != AnalogSensor::AnalogType::NOTUSED; }); + } return sensors_.size(); } diff --git a/src/web/WebDataService.cpp b/src/web/WebDataService.cpp index e06eaf556..c2b80a708 100644 --- a/src/web/WebDataService.cpp +++ b/src/web/WebDataService.cpp @@ -408,7 +408,7 @@ void WebDataService::dashboard_data(AsyncWebServerRequest * request) { JsonArray nodes = obj["nodes"].to(); uint8_t count = 0; for (const auto & sensor : EMSESP::analogsensor_.sensors()) { - if (sensor.type() != AnalogSensor::AnalogType::NOTUSED) { + if (sensor.type() != AnalogSensor::AnalogType::NOTUSED) { // ignore disabled JsonObject node = nodes.add(); node["id"] = (EMSdevice::DeviceTypeUniqueID::ANALOGSENSOR_UID * 100) + count++; From e40d01ff872a25e059f9f1ab33d642f9ebd1bfc6 Mon Sep 17 00:00:00 2001 From: proddy Date: Sun, 13 Oct 2024 09:21:39 +0100 Subject: [PATCH 64/66] package update --- interface/package.json | 2 +- interface/yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/interface/package.json b/interface/package.json index ec076a48c..d2cfa7ac4 100644 --- a/interface/package.json +++ b/interface/package.json @@ -36,7 +36,7 @@ "react-dom": "^18.3.1", "react-icons": "^5.3.0", "react-router-dom": "^6.27.0", - "react-toastify": "^10.0.5", + "react-toastify": "^10.0.6", "typesafe-i18n": "^5.26.2", "typescript": "^5.6.3" }, diff --git a/interface/yarn.lock b/interface/yarn.lock index 270ae0232..237d4479d 100644 --- a/interface/yarn.lock +++ b/interface/yarn.lock @@ -1926,7 +1926,7 @@ __metadata: react-dom: "npm:^18.3.1" react-icons: "npm:^5.3.0" react-router-dom: "npm:^6.27.0" - react-toastify: "npm:^10.0.5" + react-toastify: "npm:^10.0.6" rollup-plugin-visualizer: "npm:^5.12.0" terser: "npm:^5.34.1" typesafe-i18n: "npm:^5.26.2" @@ -6027,15 +6027,15 @@ __metadata: languageName: node linkType: hard -"react-toastify@npm:^10.0.5": - version: 10.0.5 - resolution: "react-toastify@npm:10.0.5" +"react-toastify@npm:^10.0.6": + version: 10.0.6 + resolution: "react-toastify@npm:10.0.6" dependencies: clsx: "npm:^2.1.0" peerDependencies: react: ">=18" react-dom: ">=18" - checksum: 10c0/66c68ec3d6c017d9f32652d73bb925224921c6a80b629b9d481430d5b4fd504abb7a99995a64b9aef0fc31326c74f3cbe088b3287b978dd0c355079c4bbf4158 + checksum: 10c0/4042b716d008295d0feab32488d1e88ec655a1b7a9176fa7d253c70387578a8a0c04aca0ff86d20e1722f3b4baadae8970f50f462940d67a90453c307dd350a9 languageName: node linkType: hard From 55bcc4410b0c4fb2b69f9bf6482009b19ce38182 Mon Sep 17 00:00:00 2001 From: proddy Date: Sun, 13 Oct 2024 09:21:56 +0100 Subject: [PATCH 65/66] fix dashboard/disabled analog sensors --- src/analogsensor.h | 4 ++-- src/web/WebDataService.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/analogsensor.h b/src/analogsensor.h index 04e5439ff..96f239e0a 100644 --- a/src/analogsensor.h +++ b/src/analogsensor.h @@ -153,8 +153,8 @@ class AnalogSensor { return (!sensors_.empty()); } - size_t count_entities(bool count_disabled = false) const { - if (count_disabled) { + size_t count_entities(bool include_disabled = true) const { + if (!include_disabled) { // count number of items in sensors_ where type is not set to disabled return std::count_if(sensors_.begin(), sensors_.end(), [](const Sensor & sensor) { return sensor.type() != AnalogSensor::AnalogType::NOTUSED; }); } diff --git a/src/web/WebDataService.cpp b/src/web/WebDataService.cpp index c2b80a708..f30109b5e 100644 --- a/src/web/WebDataService.cpp +++ b/src/web/WebDataService.cpp @@ -400,8 +400,8 @@ void WebDataService::dashboard_data(AsyncWebServerRequest * request) { } } - // add analog sensors - if (EMSESP::analog_enabled() && EMSESP::analogsensor_.have_sensors()) { + // add analog sensors, count excludes disabled entries + if (EMSESP::analog_enabled() && EMSESP::analogsensor_.count_entities(false)) { JsonObject obj = root.add(); obj["id"] = EMSdevice::DeviceTypeUniqueID::ANALOGSENSOR_UID; // it's unique id obj["t"] = EMSdevice::DeviceType::ANALOGSENSOR; // device type number From 3c1bfa0f3e098a6fd02972c03d4fb0f7d7aa53f4 Mon Sep 17 00:00:00 2001 From: proddy Date: Sun, 13 Oct 2024 18:40:57 +0100 Subject: [PATCH 66/66] formatting table --- interface/src/app/main/Dashboard.tsx | 65 +++++++++++++--------------- interface/src/app/main/Devices.tsx | 13 ------ 2 files changed, 30 insertions(+), 48 deletions(-) diff --git a/interface/src/app/main/Dashboard.tsx b/interface/src/app/main/Dashboard.tsx index 24eee643a..dda4a907f 100644 --- a/interface/src/app/main/Dashboard.tsx +++ b/interface/src/app/main/Dashboard.tsx @@ -99,21 +99,8 @@ const Dashboard = () => { Row: ` cursor: pointer; background-color: #1e1e1e; - .td { - // TODO remove - // border-top: 1px solid #0000; - // border-bottom: 1px solid #0000; - } - // TODO remove - // &.tr.tr-body.row-select.row-select-single-selected { - // background-color: #177ac9; - // font-weight: normal; - // color: red; - // } &:hover .td { background-color: #177ac9; - // border-top: 1px solid #177ac9; - // border-bottom: 1px solid #177ac9; } `, BaseCell: ` @@ -236,30 +223,38 @@ const Dashboard = () => { return ( <> - - - - {LL.DASHBOARD_1()} - - + + + + + {LL.DASHBOARD_1()} + + - - - - - - - - - + + + + + + + + + + - + { Row: ` cursor: pointer; background-color: #1E1E1E; - // position: relative; .td { padding: 8px; - // TODO remove - // border-top: 1px solid #565656; - // border-bottom: 1px solid #565656; } &.tr.tr-body.row-select.row-select-single-selected { background-color: #177ac9; font-weight: normal; } - // TODO remove - // &:hover .td { - // background-color: #177ac9; - // border-top: 1px solid #177ac9; - // border-bottom: 1px solid #177ac9; - // } ` }); @@ -232,9 +222,6 @@ const Devices = () => { }, &:hover .td { background-color: #177ac9; - // TODO remove - // border-top: 1px solid #177ac9; - // border-bottom: 1px solid #177ac9; } ` }