From e12b27747220a8ad2e04a88c5e7016977061af5b Mon Sep 17 00:00:00 2001 From: proddy Date: Sat, 25 Oct 2025 11:11:38 +0200 Subject: [PATCH 01/21] typo --- src/core/helpers.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/helpers.cpp b/src/core/helpers.cpp index 5dd08c1c2..9fba3184e 100644 --- a/src/core/helpers.cpp +++ b/src/core/helpers.cpp @@ -425,7 +425,7 @@ char * Helpers::render_string(char * result, const char * c, const uint8_t len) c++; p++; } - *p = '\0'; // terminat result + *p = '\0'; // terminate result return result; } @@ -467,7 +467,7 @@ char * Helpers::utf8tolatin1(char * result, const char * c, const uint8_t len) { c++; p++; } - *p = '\0'; // terminat result + *p = '\0'; // terminate result return result; } // creates string of hex values from an array of bytes From 0e133840c99a61adbfa69b94a1e68148594d4c8a Mon Sep 17 00:00:00 2001 From: proddy Date: Sat, 25 Oct 2025 11:11:51 +0200 Subject: [PATCH 02/21] package update --- interface/pnpm-lock.yaml | 8 ++++---- mock-api/pnpm-lock.yaml | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/interface/pnpm-lock.yaml b/interface/pnpm-lock.yaml index 8a510778f..bc79ff892 100644 --- a/interface/pnpm-lock.yaml +++ b/interface/pnpm-lock.yaml @@ -635,8 +635,8 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@paralleldrive/cuid2@2.2.2': - resolution: {integrity: sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==} + '@paralleldrive/cuid2@2.3.1': + resolution: {integrity: sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==} '@popperjs/core@2.11.8': resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} @@ -3565,7 +3565,7 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.19.1 - '@paralleldrive/cuid2@2.2.2': + '@paralleldrive/cuid2@2.3.1': dependencies: '@noble/hashes': 1.8.0 @@ -4714,7 +4714,7 @@ snapshots: formidable@3.5.4: dependencies: - '@paralleldrive/cuid2': 2.2.2 + '@paralleldrive/cuid2': 2.3.1 dezalgo: 1.0.4 once: 1.4.0 diff --git a/mock-api/pnpm-lock.yaml b/mock-api/pnpm-lock.yaml index 2aa54d4f8..14781c83e 100644 --- a/mock-api/pnpm-lock.yaml +++ b/mock-api/pnpm-lock.yaml @@ -84,8 +84,8 @@ packages: resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} engines: {node: ^14.21.3 || >=16} - '@paralleldrive/cuid2@2.2.2': - resolution: {integrity: sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==} + '@paralleldrive/cuid2@2.3.1': + resolution: {integrity: sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==} '@trivago/prettier-plugin-sort-imports@5.2.2': resolution: {integrity: sha512-fYDQA9e6yTNmA13TLVSA+WMQRc5Bn/c0EUBditUHNfMMxN7M82c38b1kEggVE3pLpZ0FwkwJkUEKMiOi52JXFA==} @@ -223,7 +223,7 @@ snapshots: '@noble/hashes@1.8.0': {} - '@paralleldrive/cuid2@2.2.2': + '@paralleldrive/cuid2@2.3.1': dependencies: '@noble/hashes': 1.8.0 @@ -252,7 +252,7 @@ snapshots: formidable@3.5.4: dependencies: - '@paralleldrive/cuid2': 2.2.2 + '@paralleldrive/cuid2': 2.3.1 dezalgo: 1.0.4 once: 1.4.0 From dd69e02f6bb3e183d156b5b4405ba4d08b3a2ca3 Mon Sep 17 00:00:00 2001 From: proddy Date: Sat, 25 Oct 2025 11:44:08 +0200 Subject: [PATCH 03/21] add memo cache, smaller toaster windows --- interface/src/App.tsx | 65 ++++++++++++++++++++++++++----------------- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/interface/src/App.tsx b/interface/src/App.tsx index 7b4115c2c..496f2e3eb 100644 --- a/interface/src/App.tsx +++ b/interface/src/App.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { memo, useCallback, useEffect, useMemo, useState } from 'react'; import { ToastContainer, Zoom } from 'react-toastify'; import AppRouting from 'AppRouting'; @@ -8,7 +8,8 @@ import type { Locales } from 'i18n/i18n-types'; import { loadLocaleAsync } from 'i18n/i18n-util.async'; import { detectLocale, navigatorDetector } from 'typesafe-i18n/detectors'; -const availableLocales = [ +// Memoize available locales to prevent recreation on every render +const AVAILABLE_LOCALES = [ 'de', 'en', 'it', @@ -20,47 +21,59 @@ const availableLocales = [ 'sv', 'tr', 'cz' -]; +] as Locales[]; -const App = () => { +const App = memo(() => { const [wasLoaded, setWasLoaded] = useState(false); const [locale, setLocale] = useState('en'); - useEffect(() => { - // determine locale, take from session if set other default to browser language - const browserLocale = detectLocale('en', availableLocales, navigatorDetector); + // Memoize locale initialization to prevent unnecessary re-runs + const initializeLocale = useCallback(async () => { + const browserLocale = detectLocale('en', AVAILABLE_LOCALES, navigatorDetector); const newLocale = (localStorage.getItem('lang') || browserLocale) as Locales; localStorage.setItem('lang', newLocale); setLocale(newLocale); - void loadLocaleAsync(newLocale).then(() => setWasLoaded(true)); + await loadLocaleAsync(newLocale); + setWasLoaded(true); }, []); + useEffect(() => { + void initializeLocale(); + }, [initializeLocale]); + + // Memoize toast container props to prevent recreation + const toastContainerProps = useMemo( + () => ({ + position: 'bottom-left' as const, + autoClose: 3000, + hideProgressBar: false, + newestOnTop: false, + closeOnClick: true, + rtl: false, + pauseOnFocusLoss: true, + draggable: false, + pauseOnHover: false, + transition: Zoom, + closeButton: false, + theme: 'dark' as const, + toastStyle: { + border: '1px solid #177ac9', + width: 'fit-content' + } + }), + [] + ); + if (!wasLoaded) return null; return ( - + ); -}; +}); export default App; From 4c30e930cf2286cea468618afbd1d0814d44753a Mon Sep 17 00:00:00 2001 From: proddy Date: Sat, 25 Oct 2025 14:14:25 +0200 Subject: [PATCH 04/21] add building section --- README.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8fafc5364..d91264b37 100644 --- a/README.md +++ b/README.md @@ -82,13 +82,19 @@ EMS-ESP is a project created by [proddy](https://github.com/proddy) and owned an If you like **EMS-ESP**, please give it a ✨ on GitHub, or even better fork it and contribute. You can also offer a small donation. This is an open-source project maintained by volunteers, and your support is greatly appreciated. +## 📦  **Building** + +To build the web interface only, run `platformio run -e build_webUI`. This will install the necessary dependencies and build the web interface and also create the embedded code used need to build the firmware. You can run the web interface locally by going to the `interface` directory and running `pnpm standalone`. + +To build the firmware, run `platformio run`. This will build the firmware for all ESP32 modules and place the binaries in the `build/firmware` folder. If you want to configure the build for a single platform create a local `pio_local.ni` file in the root directory (see example in `pio_local.ini_example`). + ## 📢  **Libraries used** -- [esp8266-react](https://github.com/rjwats/esp8266-react) by @rjwats for the core framework that provides the Web UI, which has been heavily modified +- [esp8266-react](https://github.com/rjwats/esp8266-react) originally by @rjwats for the core framework that provides the Web UI, which has been heavily modified - [uuid-\*](https://github.com/nomis/mcu-uuid-console) from @nomis. The console, syslog, telnet and logging are based off these awesome open source libraries - [ArduinoJson](https://github.com/bblanchon/ArduinoJson) for all the JSON processing - [espMqttClient](https://github.com/bertmelis/espMqttClient) for the MQTT client -- [ESPAsyncWebServer](https://github.com/ESP32Async/ESPAsyncWebServer) and [AsyncTCP](https://github.com/ESP32Async/AsyncTCP) for the Web server and TCP backends +- [ESPAsyncWebServer](https://github.com/ESP32Async/ESPAsyncWebServer) and [AsyncTCP](https://github.com/ESP32Async/AsyncTCP) for the Web server ## 📜  **License** From 90038e08dc37dfdb69d3c1861d4446db5f01b544 Mon Sep 17 00:00:00 2001 From: proddy Date: Sat, 25 Oct 2025 14:14:42 +0200 Subject: [PATCH 05/21] force a build for WebUI --- scripts/build_interface.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/build_interface.py b/scripts/build_interface.py index c38776914..e72afd68d 100755 --- a/scripts/build_interface.py +++ b/scripts/build_interface.py @@ -109,12 +109,14 @@ def build_webUI(*args, **kwargs): print("Web interface build failed!") env.Exit(1) env.Exit(0) - + # Create custom target that only runs the script and then exits, without continuing with the pio workflow env.AddCustomTarget( name="build", dependencies=None, actions=[build_webUI], title="build web interface", - description="installs pnpm packages, updates libraries and builds web UI" + description="installs pnpm packages, updates libraries and builds web UI", + always_build=True ) + From e4f129db048fc11f7194f40f2bb38e573b09ec8e Mon Sep 17 00:00:00 2001 From: proddy Date: Sat, 25 Oct 2025 14:15:03 +0200 Subject: [PATCH 06/21] don't compile code when target is build_webUI --- platformio.ini | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index a4f93b4d0..9d3cc153e 100644 --- a/platformio.ini +++ b/platformio.ini @@ -114,7 +114,9 @@ lib_deps = [env:build_webUI] platform = native targets = build -extra_scripts = post:scripts/build_interface.py +extra_scripts = pre:scripts/build_interface.py +; Exclude all source files so the C code doesn't build +build_src_filter = -<*> ; ; Builds for different board types From 4bb876031ebff56bdebb9763b3057ad451c46153 Mon Sep 17 00:00:00 2001 From: proddy Date: Sat, 25 Oct 2025 14:16:12 +0200 Subject: [PATCH 07/21] text change --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d91264b37..77d981e27 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ Visit [emsesp.org](https://docs.emsesp.org) for more details on how to install a To chat with the community reach out on our [Discord Server](https://discord.gg/3J3GgnzpyT). -If you find an issue or have a request, see [here](https://docs.emsesp.org/Support/) on how to submit a bug report or feature request. +If you find an issue or have a request, see [how to request support](https://docs.emsesp.org/Support/) on how to submit a bug report or feature request. ## 🎥  **Live Demo** From 7ece395d1bc97e91e7241870f525f900f24160ee Mon Sep 17 00:00:00 2001 From: proddy Date: Sat, 25 Oct 2025 14:49:14 +0200 Subject: [PATCH 08/21] smaller table for dialog --- interface/src/app/status/Version.tsx | 97 ++++++++++++++++++++-------- interface/src/i18n/cz/index.ts | 3 +- interface/src/i18n/de/index.ts | 3 +- interface/src/i18n/en/index.ts | 3 +- interface/src/i18n/fr/index.ts | 3 +- interface/src/i18n/it/index.ts | 3 +- interface/src/i18n/nl/index.ts | 3 +- interface/src/i18n/no/index.ts | 3 +- interface/src/i18n/pl/index.ts | 3 +- interface/src/i18n/sk/index.ts | 3 +- interface/src/i18n/sv/index.ts | 3 +- interface/src/i18n/tr/index.ts | 3 +- 12 files changed, 91 insertions(+), 39 deletions(-) diff --git a/interface/src/app/status/Version.tsx b/interface/src/app/status/Version.tsx index 87dd1c2f4..584ac975b 100644 --- a/interface/src/app/status/Version.tsx +++ b/interface/src/app/status/Version.tsx @@ -19,9 +19,10 @@ import { Grid, IconButton, Link, - List, - ListItem, - ListItemText, + Table, + TableBody, + TableCell, + TableRow, Typography } from '@mui/material'; @@ -41,6 +42,7 @@ import { } from 'components'; import { AuthenticatedContext } from 'contexts/authentication'; import { useI18nContext } from 'i18n/i18n-react'; +import type { TranslationFunctions } from 'i18n/i18n-types'; import { prettyDateTime } from 'utils/time'; // Constants moved outside component to avoid recreation @@ -86,7 +88,7 @@ const VersionInfoDialog = memo( latestVersion?: VersionInfo; latestDevVersion?: VersionInfo; locale: string; - LL: any; + LL: TranslationFunctions; onClose: () => void; }) => { if (showVersionInfo === 0) return null; @@ -97,30 +99,69 @@ const VersionInfoDialog = memo( return ( - Version Information + {LL.FIRMWARE_VERSION_INFO()} - - - {LL.TYPE(0)}} - secondary={isStable ? LL.STABLE() : LL.DEVELOPMENT()} - /> - - - {LL.VERSION()}} - secondary={version?.name} - /> - - {version?.published_at && ( - - Release Date} - secondary={prettyDateTime(locale, new Date(version.published_at))} - /> - - )} - + + + + + {LL.TYPE(0)} + + + {isStable ? LL.STABLE() : LL.DEVELOPMENT()} + + + + + {LL.VERSION()} + + + {version?.name} + + + {version?.published_at && ( + + + Build Date + + + {prettyDateTime(locale, new Date(version.published_at))} + + + )} + +
); - const content = () => ( - <> - + return ( + + { {renderFactoryResetDialog()} - + + + - + ); - - return {content()}; }; export default Settings; diff --git a/interface/src/app/status/Status.tsx b/interface/src/app/status/Status.tsx index 286901424..bb1f0b76c 100644 --- a/interface/src/app/status/Status.tsx +++ b/interface/src/app/status/Status.tsx @@ -253,7 +253,7 @@ const SystemStatus = () => { return ( <> - + ( - -))(({ theme }) => ({ - [`& .${tooltipClasses.arrow}`]: { - color: theme.palette.success.main - }, - [`& .${tooltipClasses.tooltip}`]: { - backgroundColor: theme.palette.success.main, - color: 'rgba(0, 0, 0, 0.87)', - boxShadow: theme.shadows[1], - fontSize: 10 - } -})); +export const ButtonTooltip = ({ children, ...props }: TooltipProps) => ( + {children} +); export default ButtonTooltip; diff --git a/interface/src/components/SectionContent.tsx b/interface/src/components/SectionContent.tsx index 3dcc56c27..97615bbc4 100644 --- a/interface/src/components/SectionContent.tsx +++ b/interface/src/components/SectionContent.tsx @@ -1,30 +1,20 @@ import type { FC } from 'react'; -import { Divider, Paper } from '@mui/material'; +import { Paper } from '@mui/material'; import type { RequiredChildrenProps } from 'utils'; interface SectionContentProps extends RequiredChildrenProps { - title?: string; id?: string; } const SectionContent: FC = (props) => { - const { children, title, id } = props; + const { children, id } = props; return ( - - {title && ( - - {title} - - )} + {children} ); From a074ac732d252162f960630f73886de90c1a0367 Mon Sep 17 00:00:00 2001 From: proddy Date: Sun, 26 Oct 2025 12:12:36 +0100 Subject: [PATCH 21/21] dont run if no C++ src files changed --- .github/workflows/pr_check.yml | 11 ++--------- .github/workflows/sonar_check.yml | 8 +++++--- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/.github/workflows/pr_check.yml b/.github/workflows/pr_check.yml index 78fc64b81..1b35c830b 100644 --- a/.github/workflows/pr_check.yml +++ b/.github/workflows/pr_check.yml @@ -7,15 +7,8 @@ on: pull_request: branches: dev paths: - - '**.c' - - '**.cpp' - - '**.h' - - '**.hpp' - - '**.json' - - '**.py' - - '**.md' - - '.github/workflows/pr_check.yml' - + - 'src/**' + jobs: pre-release: name: 'Automatic pre-release build' diff --git a/.github/workflows/sonar_check.yml b/.github/workflows/sonar_check.yml index 7dd9fcc07..2b3af4291 100644 --- a/.github/workflows/sonar_check.yml +++ b/.github/workflows/sonar_check.yml @@ -1,12 +1,14 @@ # see https://github.com/marketplace/actions/sonarcloud-scan-for-c-and-c#usage name: Sonar Check - +permissions: + contents: read + on: push: branches: - dev - # pull_request: - # types: [opened, synchronize, reopened] + paths: + - 'src/**' jobs: build: