diff --git a/.gitignore b/.gitignore index 69de4cb6e..966b345ba 100644 --- a/.gitignore +++ b/.gitignore @@ -61,3 +61,4 @@ bw-output/ # dump_entities.csv # dump_entities.xls* +benchmark/*.log diff --git a/CHANGELOG_LATEST.md b/CHANGELOG_LATEST.md index 775da9bb4..97513fc8f 100644 --- a/CHANGELOG_LATEST.md +++ b/CHANGELOG_LATEST.md @@ -10,6 +10,8 @@ - heatpump energy meters [#1463](https://github.com/emsesp/EMS-ESP32/issues/1463) - heatpump max power [#1475](https://github.com/emsesp/EMS-ESP32/issues/1475) - checkbox for MQTT-TLS enable [#1474](https://github.com/emsesp/EMS-ESP32/issues/1474) +- added SK (Slovencina) language. Thanks @misa1515 +- CPU info [#1497](https://github.com/emsesp/EMS-ESP32/pull/1497) ## Fixed diff --git a/README.md b/README.md index 6ce06bbeb..3f87ea6f0 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,6 @@ EMS-ESP is a project owned and maintained by [proddy](https://github.com/proddy) - [uuid-\*](https://github.com/nomis/mcu-uuid-console) from @nomis. The console, syslog, telnet and logging are based off these open source libraries - [ArduinoJson](https://github.com/bblanchon/ArduinoJson) for all the JSON - [espMqttClient](https://github.com/bertmelis/espMqttClient) for the MQTT client, with custom modifications from @MichaelDvP and @proddy -- ESPAsyncWebServer and AsyncTCP for the Web server and TCP backends, with custom modifications for performance ## **License** diff --git a/benchmark/http-client-test.js b/benchmark/http-client-test.js new file mode 100755 index 000000000..b493ae1ab --- /dev/null +++ b/benchmark/http-client-test.js @@ -0,0 +1,39 @@ +#!/usr/bin/env node + +const axios = require('axios'); + +const url = 'http://10.10.10.135/api/system/commands'; +const queryParams = { + entity: 'commands', + id: 0 +}; + +const totalRequests = 1000000; +const requestsPerCount = 100; + +let requestCount = 0; + +function fetchData() { + axios + .get(url, { params: queryParams }) + .then((response) => { + requestCount++; + + if (requestCount % requestsPerCount === 0) { + console.log(`Requests completed: ${requestCount}`); + } + + if (requestCount < totalRequests) { + fetchData(); + } else { + console.log('All requests completed.'); + } + }) + .catch((error) => { + console.error('Error making request:', error.message); + }); +} + +// Start making requests +console.log(`Starting test`); +fetchData(); diff --git a/benchmark/loadtest-http.sh b/benchmark/loadtest-http.sh new file mode 100755 index 000000000..dd5133e4a --- /dev/null +++ b/benchmark/loadtest-http.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# Install: +# npm install -g autocannon +# yarn global add autocannon +# +# or run https://github.com/nearform/autocannon-ui + +TEST_IP="10.10.10.135" +TEST_TIME=60 +LOG_FILE=http-loadtest.log +TIMEOUT=10000 +PROTOCOL=http +#PROTOCOL=https + +if test -f "$LOG_FILE"; then + rm $LOG_FILE +fi + +# for CONCURRENCY in 1 2 3 4 5 6 7 8 9 10 15 20 +for CONCURRENCY in 1 +#for CONCURRENCY in 20 +do + printf "\n\nCLIENTS: *** $CONCURRENCY ***\n\n" >> $LOG_FILE + echo "Testing $CONCURRENCY clients on $PROTOCOL://$TEST_IP/" + autocannon -c $CONCURRENCY -w 1 -d $TEST_TIME --renderStatusCodes "$PROTOCOL://$TEST_IP/" >> $LOG_FILE 2>&1 + printf "\n\n----------------\n\n" >> $LOG_FILE + sleep 1 + + echo "Testing $CONCURRENCY clients on $PROTOCOL://$TEST_IP/api/system/commands" + autocannon -c $CONCURRENCY -w 1 -d $TEST_TIME --renderStatusCodes "$PROTOCOL://$TEST_IP/api/system/commands" >> $LOG_FILE 2>&1 + printf "\n\n----------------\n\n" >> $LOG_FILE + sleep 1 + + echo "Testing $CONCURRENCY clients on $PROTOCOL://$TEST_IP/app/icon.png" + autocannon -c $CONCURRENCY -w 1 -d $TEST_TIME --renderStatusCodes "$PROTOCOL://$TEST_IP/app/icon.png" >> $LOG_FILE 2>&1 + printf "\n\n----------------\n\n" >> $LOG_FILE + sleep 1 +done \ No newline at end of file diff --git a/benchmark/package.json b/benchmark/package.json new file mode 100644 index 000000000..ca404ee2c --- /dev/null +++ b/benchmark/package.json @@ -0,0 +1,8 @@ +{ + "dependencies": { + "autocannon": "^7.14.0", + "axios": "^1.6.2", + "eventsource": "^2.0.2", + "ws": "^8.14.2" + } +} diff --git a/interface/package.json b/interface/package.json index 781ed6cc1..756f32dbb 100644 --- a/interface/package.json +++ b/interface/package.json @@ -20,20 +20,20 @@ "lint": "eslint . --cache --fix" }, "dependencies": { - "@alova/adapter-xhr": "^1.0.1", - "@babel/core": "^7.23.6", - "@emotion/react": "^11.11.1", + "@alova/adapter-xhr": "^1.0.2", + "@babel/core": "^7.23.7", + "@emotion/react": "^11.11.3", "@emotion/styled": "^11.11.0", - "@mui/icons-material": "^5.15.0", - "@mui/material": "^5.15.0", + "@mui/icons-material": "^5.15.2", + "@mui/material": "^5.15.2", "@table-library/react-table-library": "4.1.7", "@types/imagemin": "^8.0.5", "@types/lodash-es": "^4.17.12", - "@types/node": "^20.10.4", - "@types/react": "^18.2.43", - "@types/react-dom": "^18.2.17", + "@types/node": "^20.10.6", + "@types/react": "^18.2.46", + "@types/react-dom": "^18.2.18", "@types/react-router-dom": "^5.3.3", - "alova": "^2.16.0", + "alova": "^2.16.2", "async-validator": "^4.2.5", "history": "^5.3.0", "jwt-decode": "^4.0.0", @@ -43,7 +43,7 @@ "react-dom": "latest", "react-dropzone": "^14.2.3", "react-icons": "^4.12.0", - "react-router-dom": "^6.20.1", + "react-router-dom": "^6.21.1", "react-toastify": "^9.1.3", "sockette": "^2.0.6", "typesafe-i18n": "^5.26.2", @@ -52,27 +52,27 @@ "devDependencies": { "@preact/compat": "^17.1.2", "@preact/preset-vite": "^2.7.0", - "@typescript-eslint/eslint-plugin": "^6.14.0", - "@typescript-eslint/parser": "^6.14.0", + "@typescript-eslint/eslint-plugin": "^6.16.0", + "@typescript-eslint/parser": "^6.16.0", "concurrently": "^8.2.2", - "eslint": "^8.55.0", + "eslint": "^8.56.0", "eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb-typescript": "^17.1.0", "eslint-config-prettier": "^9.1.0", "eslint-import-resolver-typescript": "^3.6.1", "eslint-plugin-autofix": "^1.1.0", - "eslint-plugin-import": "^2.29.0", + "eslint-plugin-import": "^2.29.1", "eslint-plugin-jsx-a11y": "^6.8.0", "eslint-plugin-prettier": "alpha", "eslint-plugin-react": "^7.33.2", "eslint-plugin-react-hooks": "^4.6.0", "preact": "^10.19.3", "prettier": "^3.1.1", - "rollup-plugin-visualizer": "^5.11.0", + "rollup-plugin-visualizer": "^5.12.0", "terser": "^5.26.0", - "vite": "^5.0.8", + "vite": "^5.0.10", "vite-plugin-imagemin": "^0.6.1", - "vite-tsconfig-paths": "^4.2.2" + "vite-tsconfig-paths": "^4.2.3" }, "packageManager": "yarn@4.0.2" } diff --git a/interface/progmem-generator.js b/interface/progmem-generator.js index 98d74f5f4..f95fee287 100644 --- a/interface/progmem-generator.js +++ b/interface/progmem-generator.js @@ -11,7 +11,7 @@ const bytesPerLine = 20; var totalSize = 0; const generateWWWClass = () => - `typedef std::function RouteRegistrationHandler; + `typedef std::function RouteRegistrationHandler; class WWWData { ${indent}public: diff --git a/interface/public/fonts/re.woff2 b/interface/public/fonts/re.woff2 index 020729ef8..716979a34 100644 Binary files a/interface/public/fonts/re.woff2 and b/interface/public/fonts/re.woff2 differ diff --git a/interface/src/SignIn.tsx b/interface/src/SignIn.tsx index 116c1b1eb..0cb842da7 100644 --- a/interface/src/SignIn.tsx +++ b/interface/src/SignIn.tsx @@ -22,6 +22,7 @@ import ITflag from 'i18n/IT.svg'; import NLflag from 'i18n/NL.svg'; import NOflag from 'i18n/NO.svg'; import PLflag from 'i18n/PL.svg'; +import SKflag from 'i18n/SK.svg'; import SVflag from 'i18n/SV.svg'; import TRflag from 'i18n/TR.svg'; import { I18nContext } from 'i18n/i18n-react'; @@ -142,6 +143,10 @@ const SignIn: FC = () => {  PL + + +  SK +  SV diff --git a/interface/src/components/layout/LayoutAuthMenu.tsx b/interface/src/components/layout/LayoutAuthMenu.tsx index e9bbdaf6f..4710c3540 100644 --- a/interface/src/components/layout/LayoutAuthMenu.tsx +++ b/interface/src/components/layout/LayoutAuthMenu.tsx @@ -14,11 +14,9 @@ import { } from '@mui/material'; import { useState, useContext } from 'react'; import type { TypographyProps } from '@mui/material'; - import type { Locales } from 'i18n/i18n-types'; import type { FC, ChangeEventHandler } from 'react'; import { AuthenticatedContext } from 'contexts/authentication'; - import DEflag from 'i18n/DE.svg'; import FRflag from 'i18n/FR.svg'; import GBflag from 'i18n/GB.svg'; @@ -26,8 +24,10 @@ import ITflag from 'i18n/IT.svg'; import NLflag from 'i18n/NL.svg'; import NOflag from 'i18n/NO.svg'; import PLflag from 'i18n/PL.svg'; +import SKflag from 'i18n/SK.svg'; import SVflag from 'i18n/SV.svg'; import TRflag from 'i18n/TR.svg'; + import { I18nContext } from 'i18n/i18n-react'; import { loadLocaleAsync } from 'i18n/i18n-util.async'; @@ -102,6 +102,10 @@ const LayoutAuthMenu: FC = () => {  PL + + +  SK +  SV diff --git a/interface/src/framework/network/NetworkSettingsForm.tsx b/interface/src/framework/network/NetworkSettingsForm.tsx index ffd2fd81e..fe4c574a5 100644 --- a/interface/src/framework/network/NetworkSettingsForm.tsx +++ b/interface/src/framework/network/NetworkSettingsForm.tsx @@ -313,7 +313,7 @@ const WiFiSettingsForm: FC = () => { )} {restartNeeded && ( - + diff --git a/interface/src/framework/system/SystemStatusForm.tsx b/interface/src/framework/system/SystemStatusForm.tsx index a6c24c993..3da021afe 100644 --- a/interface/src/framework/system/SystemStatusForm.tsx +++ b/interface/src/framework/system/SystemStatusForm.tsx @@ -1,6 +1,7 @@ import AppsIcon from '@mui/icons-material/Apps'; import BuildIcon from '@mui/icons-material/Build'; import CancelIcon from '@mui/icons-material/Cancel'; +import DeveloperBoardIcon from '@mui/icons-material/DeveloperBoard'; import DevicesIcon from '@mui/icons-material/Devices'; import FolderIcon from '@mui/icons-material/Folder'; import MemoryIcon from '@mui/icons-material/Memory'; @@ -9,7 +10,6 @@ import RefreshIcon from '@mui/icons-material/Refresh'; import SdCardAlertIcon from '@mui/icons-material/SdCardAlert'; import SdStorageIcon from '@mui/icons-material/SdStorage'; import SettingsBackupRestoreIcon from '@mui/icons-material/SettingsBackupRestore'; -import ShowChartIcon from '@mui/icons-material/ShowChart'; import TimerIcon from '@mui/icons-material/Timer'; import { Avatar, @@ -200,15 +200,6 @@ const SystemStatusForm: FC = () => { - - - - - - - - - @@ -221,10 +212,31 @@ const SystemStatusForm: FC = () => { - + - + + + + + + + + + + @@ -277,7 +289,9 @@ const SystemStatusForm: FC = () => { diff --git a/interface/src/framework/system/UploadFileForm.tsx b/interface/src/framework/system/UploadFileForm.tsx index 9be4325e8..3b72c12dd 100644 --- a/interface/src/framework/system/UploadFileForm.tsx +++ b/interface/src/framework/system/UploadFileForm.tsx @@ -28,7 +28,7 @@ const UploadFileForm: FC = () => { const { send: getSchedule, onSuccess: onSuccessGetSchedule } = useRequest(EMSESP.getSchedule(), { immediate: false }); - const { send: getAPI, onSuccess: onGetAPI } = useRequest((data) => EMSESP.API(data), { + const { send: getSystemAPI, onSuccess: onSystemAPI } = useRequest((data) => EMSESP.APIcall('system', data), { immediate: false }); @@ -89,8 +89,8 @@ const UploadFileForm: FC = () => { onSuccessGetSchedule((event) => { saveFile(event.data, 'schedule.json'); }); - onGetAPI((event) => { - saveFile(event.data, event.sendArgs[0].device + '_' + event.sendArgs[0].entity + '.txt'); + onSystemAPI((event) => { + saveFile(event.data, event.sendArgs[0].entity + '.txt'); }); const downloadSettings = async () => { @@ -121,8 +121,8 @@ const UploadFileForm: FC = () => { }); }; - const callAPI = async (device: string, entity: string) => { - await getAPI({ device, entity, id: 0 }).catch((error) => { + const callSystemAPI = async (entity: string) => { + await getSystemAPI({ entity, id: 0 }).catch((error) => { toast.error(error.message); }); }; @@ -134,9 +134,10 @@ const UploadFileForm: FC = () => { - {LL.UPLOAD_TEXT()} + {LL.UPLOAD_TEXT()}.
- {LL.RESTART_TEXT()}. +
+ {LL.RESTART_TEXT(1)}.
{md5 && ( @@ -148,33 +149,28 @@ const UploadFileForm: FC = () => { {!isUploading && ( <> - {LL.DOWNLOAD(0)} {LL.SUPPORT_INFORMATION()} + {LL.DOWNLOAD(0)} {LL.SUPPORT_INFORMATION(1)} {LL.HELP_INFORMATION_4()} - - {LL.DOWNLOAD(0)} {LL.SETTINGS()} + {LL.DOWNLOAD(0)} {LL.SETTINGS(1)} diff --git a/interface/src/i18n/SK.svg b/interface/src/i18n/SK.svg new file mode 100644 index 000000000..938840791 --- /dev/null +++ b/interface/src/i18n/SK.svg @@ -0,0 +1 @@ + diff --git a/interface/src/i18n/de/index.ts b/interface/src/i18n/de/index.ts index 7574a09f7..8405362e1 100644 --- a/interface/src/i18n/de/index.ts +++ b/interface/src/i18n/de/index.ts @@ -194,13 +194,11 @@ const de: Translation = { RELEASE_IS: 'release ist', // TODO translate RELEASE_NOTES: 'Versionshinweise', EMS_ESP_VER: 'EMS-ESP Version', - PLATFORM: 'Platform (Platform / SDK)', UPTIME: 'System Betriebszeit', - CPU_FREQ: 'CPU Frequenz', HEAP: 'freier RAM Speicher (Gesamt / max. Block)', PSRAM: 'PSRAM (Größe / Frei)', FLASH: 'Flash Speicher (Größe / Geschwindigkeit)', - APPSIZE: 'Programm (Genutzt / Frei)', + APPSIZE: 'Programm (Partition: Genutzt / Frei)', FILESYSTEM: 'Dateisystem (Genutzt / Frei)', BUFFER_SIZE: 'max. Puffergröße', COMPACT: 'Kompakte Darstellung', diff --git a/interface/src/i18n/en/index.ts b/interface/src/i18n/en/index.ts index c91c4d56d..c8cd47f91 100644 --- a/interface/src/i18n/en/index.ts +++ b/interface/src/i18n/en/index.ts @@ -196,11 +196,10 @@ const en: Translation = { EMS_ESP_VER: 'EMS-ESP Version', PLATFORM: 'Device (Platform / SDK)', UPTIME: 'System Uptime', - CPU_FREQ: 'CPU Frequency', HEAP: 'Heap (Free / Max Alloc)', PSRAM: 'PSRAM (Size / Free)', FLASH: 'Flash Chip (Size / Speed)', - APPSIZE: 'Application (Used / Free)', + APPSIZE: 'Application (Partition: Used / Free)', FILESYSTEM: 'File System (Used / Free)', BUFFER_SIZE: 'Max Buffer Size', COMPACT: 'Compact', diff --git a/interface/src/i18n/fr/index.ts b/interface/src/i18n/fr/index.ts index a0b9972b9..32a2247fb 100644 --- a/interface/src/i18n/fr/index.ts +++ b/interface/src/i18n/fr/index.ts @@ -194,13 +194,11 @@ const fr: Translation = { RELEASE_IS: 'release est', // TODO translate RELEASE_NOTES: 'notes de version', EMS_ESP_VER: 'Version EMS-ESP', - PLATFORM: 'Appareil (Plateforme / SDK)', UPTIME: 'Durée de fonctionnement du système', - CPU_FREQ: 'Fréquence du CPU', HEAP: 'Heap (Libre / Max Allouée)', PSRAM: 'PSRAM (Taille / Libre)', FLASH: 'Flash Chip (Taille / Vitesse)', - APPSIZE: 'Application (Utilisée / Libre)', + APPSIZE: 'Application (Partition: Utilisée / Libre)', FILESYSTEM: 'File System (Utilisée / Libre)', BUFFER_SIZE: 'Max taille du buffer', COMPACT: 'Compact', diff --git a/interface/src/i18n/it/index.ts b/interface/src/i18n/it/index.ts index fc5d14294..89f07a790 100644 --- a/interface/src/i18n/it/index.ts +++ b/interface/src/i18n/it/index.ts @@ -196,13 +196,11 @@ const it: Translation = { RELEASE_IS: 'rilascio é', RELEASE_NOTES: 'note rilascio', EMS_ESP_VER: 'Versione EMS-ESP', - PLATFORM: 'Dispositivo (Piattaforma / SDK)', UPTIME: 'Tempo di attività del sistema', - CPU_FREQ: 'Frequenza CPU ', HEAP: 'Heap (Free / Max Alloc)', PSRAM: 'PSRAM (Size / Free)', FLASH: 'Flash Chip (Size / Speed)', - APPSIZE: 'Applicazione (Usata / Libera)', + APPSIZE: 'Applicazione (Partizione: Usata / Libera)', FILESYSTEM: 'Memoria Sistema (Usata / Libera)', BUFFER_SIZE: 'Max Buffer Size', COMPACT: 'Compact', diff --git a/interface/src/i18n/nl/index.ts b/interface/src/i18n/nl/index.ts index d54750d80..9cdb1e6ab 100644 --- a/interface/src/i18n/nl/index.ts +++ b/interface/src/i18n/nl/index.ts @@ -194,13 +194,11 @@ const nl: Translation = { RELEASE_IS: 'release is', RELEASE_NOTES: 'release notes', EMS_ESP_VER: 'EMS-ESP Versie', - PLATFORM: 'Apparaat (Platform / SDK)', UPTIME: 'Systeem Uptime', - CPU_FREQ: 'CPU Frequency', HEAP: 'Heap (Free / Max Alloc)', PSRAM: 'PSRAM (Size / Free)', FLASH: 'Flash Chip (Size / Speed)', - APPSIZE: 'Application (Used / Free)', + APPSIZE: 'Application (Partition: Used / Free)', FILESYSTEM: 'File System (Used / Free)', BUFFER_SIZE: 'Max Buffer Size', COMPACT: 'Compact', diff --git a/interface/src/i18n/no/index.ts b/interface/src/i18n/no/index.ts index c3f6e7e2c..bf5a873ca 100644 --- a/interface/src/i18n/no/index.ts +++ b/interface/src/i18n/no/index.ts @@ -194,13 +194,11 @@ const no: Translation = { RELEASE_IS: 'release er', RELEASE_NOTES: 'release notes', EMS_ESP_VER: 'EMS-ESP Version', - PLATFORM: 'Enhet (Platform / SDK)', UPTIME: 'System Oppetid', - CPU_FREQ: 'CPU Frekvens', HEAP: 'Heap (Ledig / Max Allokert)', PSRAM: 'PSRAM (Størrelse / Ledig)', FLASH: 'Flash Chip (Størrelse / Hastighet)', - APPSIZE: 'Applikasjon (Brukt / Ledig)', + APPSIZE: 'Applikasjon (Partition: Brukt / Ledig)', FILESYSTEM: 'File System (Brukt / Ledig)', BUFFER_SIZE: 'Max Buffer Størrelse', COMPACT: 'Komprimere', diff --git a/interface/src/i18n/pl/index.ts b/interface/src/i18n/pl/index.ts index 0cc5a5630..0b68a3728 100644 --- a/interface/src/i18n/pl/index.ts +++ b/interface/src/i18n/pl/index.ts @@ -53,7 +53,7 @@ const pl: BaseTranslation = { PROBLEM_LOADING: 'Problem z załadowaniem!', ANALOG_SENSOR: '{{u|u||ustawienia u|ustawień u}}rządzeni{{a podłączonego do EMS-ESP|e||a podłączonego do EMS-ESP|a podłączonego do EMS-ESP}}', ANALOG_SENSORS: 'Urządzenia podłączone do EMS-ESP', - SETTINGS: 'ustawienia', + SETTINGS: 'ustawie{{nia|ń|}}', UPDATED_OF: 'Zaktualizowano {0}.', UPDATE_OF: 'Aktualizacja {0}', REMOVED_OF: 'Usunięto ustawienia {0}.', @@ -126,7 +126,7 @@ const pl: BaseTranslation = { BYPASS_TOKEN: 'Pomiń autoryzację tokenem w wywołaniach API', READONLY: 'Tryb pracy "tylko do odczytu" (blokuje wszystkie komendy zapisu na magistralę EMS)', UNDERCLOCK_CPU: 'Obniż taktowanie CPU', - HEATINGOFF: 'Uruchom boiler z wymuszonym wyłączonym grzaniem', + HEATINGOFF: 'Uruchom kocioł z wymuszonym wyłączonym grzaniem', ENABLE_SHOWER_TIMER: 'Aktywuj minutnik prysznica', ENABLE_SHOWER_ALERT: 'Aktywuj alarm prysznica', TRIGGER_TIME: 'Wyzwalaj po czasie', @@ -146,13 +146,13 @@ const pl: BaseTranslation = { MINUTES: 'minut', HOURS: 'godzin', RESTART: 'Restart', - RESTART_TEXT: 'Aby zastosować wprowadzone zmiany interfejs EMS-ESP musi zostać zrestartowany.', + RESTART_TEXT: 'Aby zastosować wprowadzone zmiany, interfejs EMS-ESP {{musi zostać|zostanie|}} uruchomiony ponowni{{e.|e|}}', RESTART_CONFIRM: 'Na pewno chcesz zrestartować interfejs EMS-ESP?', COMMAND: '{{Komenda|KOMENDA|}}', CUSTOMIZATIONS_RESTART: 'Wszystkie personalizacje zostały usunięte. Restartuję...', CUSTOMIZATIONS_FULL: 'Wybrano za dużo obiektów. Wprowadź zmiany w mniejszych partiach.', CUSTOMIZATIONS_SAVED: 'Personalizacje zostały zapisane.', - CUSTOMIZATIONS_HELP_1: 'Wybierz urządzenie EMS, dostosuj opcje lub kliknij by zmienić nazwę encji.', + CUSTOMIZATIONS_HELP_1: 'Wybierz urządzenie EMS, a następnie dostosuj opcje lub kliknij na nazwie encji by tę nazwę zmienić', CUSTOMIZATIONS_HELP_2: 'oznacz jako ulubioną', CUSTOMIZATIONS_HELP_3: 'zablokuj akcje zapisu', CUSTOMIZATIONS_HELP_4: 'wyklucz z MQTT i API', @@ -164,12 +164,12 @@ const pl: BaseTranslation = { NAME: '{{Nazwa|nazwa|}}', CUSTOMIZATIONS_RESET: 'Na pewno chcesz usunąć wszystkie personalizacje łącznie z ustawieniami dla czujników temperatury 1-Wire® i urządzeń podłączonych do EMS-ESP?', DEVICE_ENTITIES: 'Encje urządzenia', - SUPPORT_INFORMATION: 'Informacje dotyczące wsparcia', + SUPPORT_INFORMATION: '{{I|i|}}nformacj{{e|i|}} o systemie', CLICK_HERE: 'Kliknij tu', - HELP_INFORMATION_1: 'Aby uzyskać instrukcje dotyczące konfiguracji EMS-ESP skorzystaj z wiki w internecie', + HELP_INFORMATION_1: 'Aby uzyskać instrukcje dotyczące konfiguracji EMS-ESP, skorzystaj z wiki w internecie', HELP_INFORMATION_2: 'Aby dołączyć do naszego serwera Discord i komunikować się na żywo ze społecznością', HELP_INFORMATION_3: 'Aby zaproponować nową funkcjonalność lub zgłosić problem', - HELP_INFORMATION_4: 'Zgłaszając problem, nie zapomnij dołączyć informacji o swoim systemie!', + HELP_INFORMATION_4: 'Zgłaszając problem, nie zapomnij pobrać i dołączyć informacji o swoim systemie!', HELP_INFORMATION_5: 'EMS-ESP jest darmowym projektem typu open-source. Aby go wesprzeć, rozważ przyznanie nam gwiazdki na Github!', UPLOAD: 'Wysyłanie', DOWNLOAD: '{{P|p||P}}obier{{anie|z||z}}', @@ -194,21 +194,19 @@ const pl: BaseTranslation = { RELEASE_IS: 'wydanie to', RELEASE_NOTES: 'lista zmian', EMS_ESP_VER: 'Wersja EMS-ESP', - PLATFORM: 'Urządzenie (platforma / SDK)', UPTIME: 'Czas działania systemu', - CPU_FREQ: 'Taktowanie CPU', HEAP: 'HEAP (wolne / maksymalny przydział)', PSRAM: 'PSRAM (rozmiar / wolne)', FLASH: 'FLASH (rozmiar / taktowanie)', - APPSIZE: 'Aplikacja (wykorzystane / wolne)', + APPSIZE: 'Aplikacja (partycja: wykorzystane / wolne)', FILESYSTEM: 'System plików (wykorzystane / wolne)', BUFFER_SIZE: 'Maksymalna pojemność bufora (ilość wpisów)', COMPACT: 'Kompaktowy', ENABLE_OTA: 'Aktywuj aktualizację OTA', DOWNLOAD_CUSTOMIZATION_TEXT: 'Pobierz personalizacje.', DOWNLOAD_SCHEDULE_TEXT: 'Pobierz harmonogram zdarzeń.', - DOWNLOAD_SETTINGS_TEXT: 'Pobierz ustawienia aplikacji. Uważaj jeśli udostępniasz plik z ustawieniami, ponieważ zawiera on hasła oraz inne wrażliwe informacje!', - UPLOAD_TEXT: 'Wyślij firmware (.bin), ustawienia lub personalizacje (.json). Opcjonalnie, wyślij wcześniej plik walidacji (.md5).', + DOWNLOAD_SETTINGS_TEXT: 'Pobierz ustawienia aplikacji. Uwaga! Plik z ustawieniami zawiera hasła oraz inne wrażliwe informacje systemowe! Nie udostepniaj go pochopnie!', + UPLOAD_TEXT: 'Wyślij firmware (.bin), ustawienia lub personalizacje (.json). Opcjonalnie, wyślij wcześniej plik walidacji z sumą kontrolną (.md5).', UPLOADING: 'Wysłano', UPLOAD_DROP_TEXT: 'Przeciągnij tutaj plik lub kliknij', ERROR: 'Nieoczekiwany błąd, spróbuj ponownie!', @@ -320,10 +318,10 @@ const pl: BaseTranslation = { CUSTOM_ENTITIES: '{{N|n|}}iestandardowe{{|j|}} encj{{e|i|}}', ENTITIES_HELP_1: 'Zdefiniuj niestandardowe encje dla magistrali EMS.', ENTITIES_UPDATED: 'Niestandardowe encje zostały uaktualnione.', - WRITEABLE: 'zapisywalna', + WRITEABLE: 'Zapisywalna', SHOWING: 'Wyświetlane', SEARCH: 'Szukaj', - CERT: 'Certyfikat główny TLS (pozostaw puste zby wyłączyć TLS-insecure)', + CERT: 'Certyfikat główny TLS (pozostaw puste dla TLS-insecure)', ENABLE_TLS: 'Włącz wsparcie dla TLS', ON: 'włączony', OFF: 'wyłączony', diff --git a/interface/src/i18n/sk/index.ts b/interface/src/i18n/sk/index.ts new file mode 100644 index 000000000..a7339e3c7 --- /dev/null +++ b/interface/src/i18n/sk/index.ts @@ -0,0 +1,335 @@ +import type { Translation } from '../i18n-types'; +/* prettier-ignore */ +/* eslint-disable */ + +const sk: Translation = { + LANGUAGE: 'Jazyk', + RETRY: 'Opakovať', + LOADING: 'Načítanie', + IS_REQUIRED: '{0} je požadovaných', + SIGN_IN: 'Prihlásiť sa', + SIGN_OUT: 'Odhlásiť sa', + USERNAME: 'Užívateľské meno', + PASSWORD: 'Heslo', + SU_PASSWORD: 'su heslo', + DASHBOARD: 'Panel', + SETTINGS_OF: '{0} Nastavenia', + HELP_OF: '{0} Pomoc', + LOGGED_IN: 'Prihlásený ako {name}', + PLEASE_SIGNIN: 'Ak chcete pokračovať, prihláste sa', + UPLOAD_SUCCESSFUL: 'Nahratie úspešné', + DOWNLOAD_SUCCESSFUL: 'Stiahnutie úspešné', + INVALID_LOGIN: 'Nesprávne prihlasovacie údaje', + NETWORK: 'Sieť', + SECURITY: 'Zabezpečenie', + ONOFF_CAP: 'ZAP/VYP', + ONOFF: 'zap/vyp', + TYPE: 'Typ', + DESCRIPTION: 'Popis', + ENTITIES: 'Entity', + REFRESH: 'Obnoviť', + EXPORT: 'Export', + DEVICE_DETAILS: 'Detaily zariadenia', + ID_OF: '{0} ID', + DEVICE: 'Zariadenie', + PRODUCT: 'Produkt', + VERSION: 'Verzia', + BRAND: 'Značka', + ENTITY_NAME: 'Názov entity', + VALUE: '{{Value|value}}', + DEVICE_DATA: 'Dáta zariadenia', + SENSOR_DATA: 'Dáta snímača', + DEVICES: 'Zariadenia', + SENSORS: 'Snímače', + RUN_COMMAND: 'Volať príkaz', + CHANGE_VALUE: 'Zmena hodnoty', + CANCEL: 'Zrušiť', + RESET: 'Reset', + APPLY_CHANGES: 'Aplikovať zmeny ({0})', + UPDATE: 'Aktualizovať', + EXECUTE: 'Spustiť', + REMOVE: 'Odstrániť', + PROBLEM_UPDATING: 'Problém s aktualizáciou', + PROBLEM_LOADING: 'Problém s načítaním', + ANALOG_SENSOR: 'Analógový snímač', + ANALOG_SENSORS: 'Analógové snímače', + SETTINGS: 'Nastavenia', + UPDATED_OF: '{0} aktualizovaných', + UPDATE_OF: '{0} aktualizované', + REMOVED_OF: '{0} odstránených', + DELETION_OF: '{0} zmazaných', + OFFSET: 'Ofset', + FACTOR: 'Faktor', + FREQ: 'Frekvencia', + DUTY_CYCLE: 'Duty Cycle', + UNIT: 'UoM', + STARTVALUE: 'Počiatočná hodnota', + WARN_GPIO: 'Upozornenie: Buďte opatrní pri priraďovaní GPIO!', + EDIT: 'Editovať', + SENSOR: 'Snímač', + TEMP_SENSOR: 'Snímač teploty', + TEMP_SENSORS: 'Snímače teploty', + WRITE_CMD_SENT: 'Príkaz zápisu bol odoslaný', + EMS_BUS_WARNING: 'Zbernica EMS odpojená. Ak toto upozornenie pretrváva aj po niekoľkých sekundách, skontrolujte nastavenia a profil dosky', + EMS_BUS_SCANNING: 'Zisťovanie EMS zariadení...', + CONNECTED: 'Pripojené', + TX_ISSUES: 'Problémy s Tx – skontrolujte Tx režim', + DISCONNECTED: 'Odpojené', + EMS_SCAN: 'Naozaj chcete spustiť úplnú kontrolu zariadenia zbernice EMS?', + EMS_BUS_STATUS: 'Stav zbernice EMS', + ACTIVE_DEVICES: 'Aktívne zariadenia a snímače', + EMS_DEVICE: 'EMS zariadenie', + SUCCESS: 'ÚSPEŠNÉ', + FAIL: 'ZLYHANIE', + QUALITY: 'KVALITA', + SCAN_DEVICES: 'Scan pre nové zariadenia', + EMS_BUS_STATUS_TITLE: 'EMS zbernica & stav aktivity', + SCAN: 'Scan', + STATUS_NAMES: [ + 'EMS Telegramy prijaté (Rx)', + 'EMS Čítania (Tx)', + 'EMS Zápisy (Tx)', + 'Čítanie snímača teploty', + 'Analógové snímanie', + 'MQTT Publikovanie', + 'API volania', + 'Syslog správy' + ], + NUM_DEVICES: '{num} Zariadenia{{s}}', + NUM_TEMP_SENSORS: '{num} Teplotné snímače{{s}}', + NUM_ANALOG_SENSORS: '{num} Analógové snímače{{s}}', + NUM_DAYS: '{num} dní{{s}}', + NUM_SECONDS: '{num} sekúnd{{s}}', + NUM_HOURS: '{num} hodín{{s}}', + NUM_MINUTES: '{num} minút{{s}}', + APPLICATION_SETTINGS: 'Nastavenia aplikácie', + CUSTOMIZATIONS: 'Prispôsobenia', + APPLICATION_RESTARTING: 'EMS-ESP sa reštartuje', + INTERFACE_BOARD_PROFILE: 'Profil boardu rozhrania', + BOARD_PROFILE_TEXT: 'Vyberte vopred nakonfigurovaný profil dosky rozhrania zo zoznamu nižšie alebo vyberte možnosť Vlastné a nakonfigurujte svoje vlastné hardvérové nastavenia', + BOARD_PROFILE: 'Board profil', + CUSTOM: 'Vlastné', + GPIO_OF: '{0} GPIO', + BUTTON: 'Tlačidlo', + TEMPERATURE: 'Teplota', + PHY_TYPE: 'Eth PHY Typ', + DISABLED: 'zakázané', + TX_MODE: 'Tx režim', + HARDWARE: 'Hardware', + EMS_BUS: '{{BUS|EMS BUS}}', + GENERAL_OPTIONS: 'Všeobecné možnosti', + LANGUAGE_ENTITIES: 'Jazyk (pre entity zariadenia)', + HIDE_LED: 'Skryť LED', + ENABLE_TELNET: 'Povoliť Telnet konzolu', + ENABLE_ANALOG: 'Povoliť analógové snímače', + CONVERT_FAHRENHEIT: 'Previesť hodnoty teploty na fahrenheity', + BYPASS_TOKEN: 'Vynechajte autorizáciu prístupového tokenu pri volaniach API', + READONLY: 'Povoliť režim len na čítanie (blokuje všetky odchádzajúce príkazy EMS Tx Write)', + UNDERCLOCK_CPU: 'Podtaktovanie rýchlosti procesora', + HEATINGOFF: 'Spustite kotol s núteným vykurovaním', + ENABLE_SHOWER_TIMER: 'Povoliť časovač sprchovania', + ENABLE_SHOWER_ALERT: 'Povoliť upozornenie na sprchu', + TRIGGER_TIME: 'Čas spustenia', + COLD_SHOT_DURATION: 'Trvanie studeného záberu', + FORMATTING_OPTIONS: 'Možnosti formátovania', + BOOLEAN_FORMAT_DASHBOARD: 'Panel Boolean formát', + BOOLEAN_FORMAT_API: 'Boolean formát API/MQTT', + ENUM_FORMAT: 'Enum formát API/MQTT', + INDEX: 'Index', + ENABLE_PARASITE: 'Povolenie parazitného napájania', + LOGGING: 'Logovanie', + LOG_HEX: 'Záznam telegramov EMS v hexadecimálnej sústave', + ENABLE_SYSLOG: 'Povoliť Syslog', + LOG_LEVEL: 'Log úroveň', + MARK_INTERVAL: 'Označenie intervalu', + SECONDS: 'sekundy', + MINUTES: 'minúty', + HOURS: 'hodiny', + RESTART: 'Reštart', + RESTART_TEXT: 'EMS-ESP sa musí reštartovať, aby sa použili zmenené systémové nastavenia', + RESTART_CONFIRM: 'Ste si istí, že chcete reštartovať EMS-ESP?', + COMMAND: 'Príkaz', + CUSTOMIZATIONS_RESTART: 'Ste si istí, že chcete reštartovať EMS-ESP?', + CUSTOMIZATIONS_FULL: 'Vybrané subjekty prekročili limit. Prosím, ukladajte v dávkach', + CUSTOMIZATIONS_SAVED: 'Uložené prispôsobenia', + CUSTOMIZATIONS_HELP_1: 'Vyberte zariadenie a prispôsobte možnosti entít alebo kliknutím premenujte', + CUSTOMIZATIONS_HELP_2: 'označiť ako obľúbené', + CUSTOMIZATIONS_HELP_3: 'zakázať akciu zápisu', + CUSTOMIZATIONS_HELP_4: 'vylúčiť z MQTT a API', + CUSTOMIZATIONS_HELP_5: 'skryť z panela', + CUSTOMIZATIONS_HELP_6: 'odstrániť z pamäte', + SELECT_DEVICE: 'Zvoliť zariadenie', + SET_ALL: 'nastaviť všetko', + OPTIONS: 'Možnosti', + NAME: 'Názov', + CUSTOMIZATIONS_RESET: 'Naozaj chcete odstrániť všetky prispôsobenia vrátane vlastných nastavení snímačov teploty a analógových snímačov?', + DEVICE_ENTITIES: 'Entity zariadenia', + SUPPORT_INFORMATION: 'Informácie o podpore', + CLICK_HERE: 'Kliknite tu', + HELP_INFORMATION_1: 'Navštívte online wiki, kde nájdete pokyny na konfiguráciu EMS-ESP', + HELP_INFORMATION_2: 'Pre živý komunitný chat sa pripojte na náš Discord server', + HELP_INFORMATION_3: 'Ak chcete požiadať o funkciu alebo nahlásiť chybu', + HELP_INFORMATION_4: 'nezabudnite si stiahnuť a pripojiť informácie o vašom systéme, aby ste mohli rýchlejšie reagovať pri nahlasovaní problému', + HELP_INFORMATION_5: 'EMS-ESP je bezplatný a open source projekt. Podporte jeho budúci vývoj tým, že mu dáte hviezdičku na Github!', + UPLOAD: 'Nahrať', + DOWNLOAD: '{{S|s|s}}tiahnuť', + ABORTED: 'zrušené', + FAILED: 'chybné', + SUCCESSFUL: 'úspešné', + SYSTEM: 'Systém', + LOG_OF: '{0} Log', + STATUS_OF: '{0} Stav', + UPLOAD_DOWNLOAD: 'Nahrať/Stiahnuť', + VERSION_ON: 'Momentálne ste vo verzii', + SYSTEM_APPLY_FIRMWARE: 'na použitie nového firmvéru', + CLOSE: 'Zatvoriť', + USE: 'Použiť', + FACTORY_RESET: 'Továrenské nastavenia', + SYSTEM_FACTORY_TEXT: 'Zariadenie bolo obnovené z výroby a teraz sa reštartuje', + SYSTEM_FACTORY_TEXT_DIALOG: 'Naozaj chcete resetovať EMS-ESP na predvolené výrobné nastavenia?', + VERSION_CHECK: 'Kontrola verzie', + THE_LATEST: 'Posledná', + OFFICIAL: 'officiálna', + DEVELOPMENT: 'vývojárska', + RELEASE_IS: 'vydanie je', + RELEASE_NOTES: 'poznámky k vydaniu', + EMS_ESP_VER: 'EMS-ESP verzia', + UPTIME: 'Beh systému', + HEAP: 'Zásobník (voľné / max pridelenie)', + PSRAM: 'PSRAM (Veľkosť / Voľné)', + FLASH: 'Flash chip (Veľkosť / Rýchlosť)', + APPSIZE: 'Applikácia (Priečka: Použité / Voľné)', + FILESYSTEM: 'Súborový systém (Použité / Voľné)', + BUFFER_SIZE: 'Maximálna veľkosť vyrovnávacej pamäte', + COMPACT: 'Kompaktné', + ENABLE_OTA: 'Povoliť OTA aktualizácie', + DOWNLOAD_CUSTOMIZATION_TEXT: 'Stiahnutie prispôsobení entity', + DOWNLOAD_SCHEDULE_TEXT: 'Stiahnutie plánovača udalostí', + DOWNLOAD_SETTINGS_TEXT: 'Stiahnite si nastavenia aplikácie. Pri zdieľaní nastavení buďte opatrní, pretože tento súbor obsahuje heslá a iné citlivé systémové informácie.', + UPLOAD_TEXT: 'Najskôr nahrajte nový súbor firmvéru (.bin), nastavenia alebo prispôsobenia (.json), pre voliteľné overenie nahrajte súbor (.md5)', + UPLOADING: 'Nahrávanie', + UPLOAD_DROP_TEXT: 'Zahodiť súbor alebo kliknúť sem', + ERROR: 'Neočakávaná chyba, prosím skúste to znova', + TIME_SET: 'Nastavený čas', + MANAGE_USERS: 'Správa používateľov', + IS_ADMIN: 'je Admin', + USER_WARNING: 'Musíte mať nakonfigurovaného aspoň jedného používateľa administrátora', + ADD: 'Pridať', + ACCESS_TOKEN_FOR: 'Prístupový token pre', + ACCESS_TOKEN_TEXT: 'Nižšie uvedený token sa používa pri volaniach REST API, ktoré vyžadujú autorizáciu. Môže byť odovzdaný buď ako token Bearer v hlavičke Authorization (Autorizácia), alebo v parametri dotazu URL access_token.', + GENERATING_TOKEN: 'Generovanie tokenu', + USER: 'Užívateľ', + MODIFY: 'Upraviť', + SU_TEXT: 'Heslo su (superužívateľ) sa používa na podpisovanie autentifikačných tokenov a tiež na povolenie oprávnení správcu v rámci konzoly.', + NOT_ENABLED: 'Nie je povolené', + ERRORS_OF: '{0} errory', + DISCONNECT_REASON: 'Dôvod odpojenia', + ENABLE_MQTT: 'Povoliť MQTT', + BROKER: 'Broker', + CLIENT: 'Klient', + BASE_TOPIC: 'Base', + OPTIONAL: 'voliteľné', + FORMATTING: 'Formátovanie', + MQTT_FORMAT: 'Formát témy/záťaže', + MQTT_NEST_1: 'Vnorené do jednej témy', + MQTT_NEST_2: 'Ako jednotlivé témy', + MQTT_RESPONSE: 'Publikovanie výstupu príkazu do témy `response`', + MQTT_PUBLISH_TEXT_1: 'Zverejňovanie tém jednotlivých hodnôt pri zmene', + MQTT_PUBLISH_TEXT_2: 'Publikovanie do tém príkazov (ioBroker)', + MQTT_PUBLISH_TEXT_3: 'Povolenie zisťovania MQTT', + MQTT_PUBLISH_TEXT_4: 'Predpona tém Discovery', + MQTT_PUBLISH_TEXT_5: 'Typ zistenia', + MQTT_PUBLISH_INTERVALS: 'Intervaly zverejňovania', + MQTT_INT_BOILER: 'Kotly a tepelné čerpadlá', + MQTT_INT_THERMOSTATS: 'Termostaty', + MQTT_INT_SOLAR: 'Solárne moduly', + MQTT_INT_MIXER: 'Zmiešavacie moduley', + MQTT_QUEUE: 'Fronta MQTT', + DEFAULT: 'Predvolené', + MQTT_ENTITY_FORMAT: 'ID formát entity', + MQTT_ENTITY_FORMAT_0: 'Jedna inštancia, dlhý názov (v3.4)', + MQTT_ENTITY_FORMAT_1: 'Jedna inštancia, krátky názov', + MQTT_ENTITY_FORMAT_2: 'Viacero inštancií, krátky názov', + MQTT_CLEAN_SESSION: 'Nastavenie čistej relácie', + MQTT_RETAIN_FLAG: 'Vždy nastaviť príznak Retain', + INACTIVE: 'Neaktívne', + ACTIVE: 'Aktívne', + UNKNOWN: 'Neznáme', + SET_TIME: 'Nastavený čas', + SET_TIME_TEXT: 'Na nastavenie času zadajte miestny dátum a čas nižšie', + LOCAL_TIME: 'Lokálny čas', + UTC_TIME: 'UTC čas', + ENABLE_NTP: 'Povoliť NTP', + NTP_SERVER: 'NTP Server', + TIME_ZONE: 'Časová zóna', + ACCESS_POINT: 'Prístupový bod', + AP_PROVIDE: 'Povoliť prístupový bod', + AP_PROVIDE_TEXT_1: 'vždy', + AP_PROVIDE_TEXT_2: 'keď WiFi je odpojená', + AP_PROVIDE_TEXT_3: 'nikdy', + AP_PREFERRED_CHANNEL: 'Preferovaný kanál', + AP_HIDE_SSID: 'Skryť SSID', + AP_CLIENTS: 'AP klienti', + AP_MAX_CLIENTS: 'Max klientov', + AP_LOCAL_IP: 'Lokálna IP', + NETWORK_SCAN: 'Scan WiFi siete', + IDLE: 'Nečinné', + LOST: 'Stratené', + SCANNING: 'Scanovanie', + SCAN_AGAIN: 'Scanovať znova', + NETWORK_SCANNER: 'Sieťový scanner', + NETWORK_NO_WIFI: 'WiFi siete nenájdené', + NETWORK_BLANK_SSID: 'nechajte prázdne, ak chcete zakázať WiFi a povoliť ETH', + NETWORK_BLANK_BSSID: 'ponechajte prázdne, ak chcete používať iba SSID', + TX_POWER: 'Tx výkon', + HOSTNAME: 'Hostname', + NETWORK_DISABLE_SLEEP: 'Zakázanie režimu spánku WiFi', + NETWORK_LOW_BAND: 'Používanie menšej šírky pásma WiFi', + NETWORK_USE_DNS: 'Povoliť mDNS službu', + NETWORK_ENABLE_CORS: 'Povoliť CORS', + NETWORK_CORS_ORIGIN: 'CORS origin', + NETWORK_ENABLE_IPV6: 'Povoliť podporu IPv6', + NETWORK_FIXED_IP: 'Použiť fixnú IP adresu', + NETWORK_GATEWAY: 'Brána', + NETWORK_SUBNET: 'Maska podsiete', + NETWORK_DNS: 'DNS servery', + ADDRESS_OF: '{0} adries', + ADMIN: 'Admin', + GUEST: 'Hosť', + NEW: 'Nová', + NEW_NAME_OF: 'Nových {0} názvov', + ENTITY: 'entita', + MIN: 'min', + MAX: 'max', + BLOCK_NAVIGATE_1: 'Máte neuložené zmeny', + BLOCK_NAVIGATE_2: 'Ak prejdete na inú stránku, neuložené zmeny sa stratia. Ste si istí, že chcete opustiť túto stránku?', + STAY: 'Zostať', + LEAVE: 'Opustiť', + SCHEDULER: 'Plánovač', + SCHEDULER_HELP_1: 'Automatizujte príkazy pridaním naplánovaných udalostí nižšie. Nastavte jedinečné meno na aktiváciu/deaktiváciu cez API/MQTT.', + SCHEDULER_HELP_2: 'Použite 00:00 na jednorazové spustenie pri štarte', + SCHEDULE: 'Plánovať', + TIME: 'Čas', + TIMER: 'Časovač', + SCHEDULE_UPDATED: 'Plánovanie aktualizované', + SCHEDULE_TIMER_1: 'pri spustení', + SCHEDULE_TIMER_2: 'každú minútu', + SCHEDULE_TIMER_3: 'každú hodinu', + CUSTOM_ENTITIES: 'Vlastné entity', + ENTITIES_HELP_1: 'Získavanie vlastných entít zo zbernice EMS', + ENTITIES_UPDATED: 'Aktualizované entity', + WRITEABLE: 'Zapísateľný', + SHOWING: 'Zobrazenie', + SEARCH: 'Vyhľadať', + CERT: 'Koreňový certifikát TLS (ak chcete vypnúť TLS, nechajte prázdne)', + ENABLE_TLS: 'Povoliť TLS', + ON: 'Zap', + OFF: 'Vyp', + POLARITY: 'Polarita', + ACTIVEHIGH: 'Aktívny Vysoký', + ACTIVELOW: 'Aktívny Nízky', + UNCHANGED: 'Nezmenené', + ALWAYS: 'Vždy' +}; + +export default sk; diff --git a/interface/src/i18n/sv/index.ts b/interface/src/i18n/sv/index.ts index 3dea8a225..d0da71165 100644 --- a/interface/src/i18n/sv/index.ts +++ b/interface/src/i18n/sv/index.ts @@ -194,13 +194,11 @@ const sv: Translation = { RELEASE_IS: 'release är', // TODO translate RELEASE_NOTES: 'release-logg', EMS_ESP_VER: 'EMS-ESP Version', - PLATFORM: 'Enhet (Plattform / SDK)', UPTIME: 'Systemets Upptid', - CPU_FREQ: 'CPU-frekvens', HEAP: 'Heap (Ledigt / Max allokerat)', PSRAM: 'PSRAM (Storlek / Ledigt)', FLASH: 'Flashminne (Storlek / Hastighet)', - APPSIZE: 'Applikationer (Använt / Ledigt)', + APPSIZE: 'Applikationer (Partition: Använt / Ledigt)', FILESYSTEM: 'Filsystem (Använt / Ledigt)', BUFFER_SIZE: 'Max Bufferstorlek', COMPACT: 'Komprimera', diff --git a/interface/src/i18n/tr/index.ts b/interface/src/i18n/tr/index.ts index a5d7e4505..67831c170 100644 --- a/interface/src/i18n/tr/index.ts +++ b/interface/src/i18n/tr/index.ts @@ -194,13 +194,11 @@ const tr: Translation = { RELEASE_IS: 'release is', // TODO translate RELEASE_NOTES: 'yayınlanma notları', EMS_ESP_VER: 'EMS-ESP Sürümü', - PLATFORM: 'Cihaz (Platform / SDK)', UPTIME: 'Sistem Çalışma Süresi', - CPU_FREQ: 'İşlemci frekansı', HEAP: 'Yığın (Boş / Maksimum Tahsis)', PSRAM: 'PSRAM (Boyut / Boş)', FLASH: 'Flash Çipi (Boyut / Hız)', - APPSIZE: 'Uygulama (Kullanılmış / Boş)', + APPSIZE: 'Uygulama (Bölme: Kullanılmış / Boş)', FILESYSTEM: 'Dosya Sistemi (Kullanılmış / Boş)', BUFFER_SIZE: 'En fazla bellek boyutu', COMPACT: 'Sıkışık', diff --git a/interface/src/project/DashboardDevices.tsx b/interface/src/project/DashboardDevices.tsx index 693ca740e..abbb07b18 100644 --- a/interface/src/project/DashboardDevices.tsx +++ b/interface/src/project/DashboardDevices.tsx @@ -420,33 +420,35 @@ const DashboardDevices: FC = () => { )} - - {(tableList: any) => ( - <> -
- - - {LL.DESCRIPTION()} - {LL.TYPE(0)} - -
- - {tableList.map((device: Device) => ( - - - - - - {device.n} -   ({device.e}) - - {device.tn} - - ))} - - - )} -
+ {coreData.connected && ( + + {(tableList: any) => ( + <> +
+ + + {LL.DESCRIPTION()} + {LL.TYPE(0)} + +
+ + {tableList.map((device: Device) => ( + + + + + + {device.n} +   ({device.e}) + + {device.tn} + + ))} + + + )} +
+ )} ); diff --git a/interface/src/project/DashboardDevicesDialog.tsx b/interface/src/project/DashboardDevicesDialog.tsx index aab6fa30d..7fe962371 100644 --- a/interface/src/project/DashboardDevicesDialog.tsx +++ b/interface/src/project/DashboardDevicesDialog.tsx @@ -103,7 +103,7 @@ const DashboardDevicesDialog = ({ return ( - {selectedItem.v === '' && selectedItem.c ? LL.RUN_COMMAND() : writeable ? LL.CHANGE_VALUE() : LL.VALUE(1)} + {selectedItem.v === '' && selectedItem.c ? LL.RUN_COMMAND() : writeable ? LL.CHANGE_VALUE() : LL.VALUE(0)} diff --git a/interface/src/project/Help.tsx b/interface/src/project/Help.tsx index 611695e74..ffea7e633 100644 --- a/interface/src/project/Help.tsx +++ b/interface/src/project/Help.tsx @@ -15,7 +15,7 @@ const Help: FC = () => { const { LL } = useI18nContext(); useLayoutTitle(LL.HELP_OF('')); - const { send: getAPI, onSuccess: onGetAPI } = useRequest((data) => EMSESP.API(data), { + const { send: getSystemAPI, onSuccess: onGetAPI } = useRequest((data) => EMSESP.APIcall('system', data), { immediate: false }); @@ -26,20 +26,20 @@ const Help: FC = () => { type: 'text/plain' }) ); - anchor.download = 'emsesp_' + event.sendArgs[0].device + '_' + event.sendArgs[0].entity + '.txt'; + anchor.download = 'emsesp_' + event.sendArgs[0].entity + '.txt'; anchor.click(); URL.revokeObjectURL(anchor.href); toast.info(LL.DOWNLOAD_SUCCESSFUL()); }); - const callAPI = async (device: string, entity: string) => { - await getAPI({ device, entity, id: 0 }).catch((error) => { + const callSystemAPI = async (entity: string) => { + await getSystemAPI({ entity, id: 0 }).catch((error) => { toast.error(error.message); }); }; return ( - + @@ -89,15 +89,15 @@ const Help: FC = () => { {LL.HELP_INFORMATION_4()}
- @@ -111,7 +111,7 @@ const Help: FC = () => { {'github.com/emsesp/EMS-ESP32'} - + @proddy @MichaelDvP diff --git a/interface/src/project/SettingsApplication.tsx b/interface/src/project/SettingsApplication.tsx index 1f145af17..70a9df199 100644 --- a/interface/src/project/SettingsApplication.tsx +++ b/interface/src/project/SettingsApplication.tsx @@ -384,6 +384,7 @@ const SettingsApplication: FC = () => { Nederlands (NL) Norsk (NO) Polski (PL) + Slovenčina (SK) Svenska (SV) Türk (TR) @@ -644,7 +645,7 @@ const SettingsApplication: FC = () => { )} {restartNeeded && ( - + diff --git a/interface/src/project/SettingsCustomization.tsx b/interface/src/project/SettingsCustomization.tsx index 463ec3518..e352d4c1f 100644 --- a/interface/src/project/SettingsCustomization.tsx +++ b/interface/src/project/SettingsCustomization.tsx @@ -514,7 +514,7 @@ const SettingsCustomization: FC = () => { {devices && renderDeviceList()} {deviceEntities && renderDeviceData()} {restartNeeded && ( - + diff --git a/interface/src/project/SettingsCustomizationDialog.tsx b/interface/src/project/SettingsCustomizationDialog.tsx index 1e616218c..e1df49ed7 100644 --- a/interface/src/project/SettingsCustomizationDialog.tsx +++ b/interface/src/project/SettingsCustomizationDialog.tsx @@ -70,7 +70,7 @@ const SettingsCustomizationDialog = ({ open, onClose, onSave, selectedItem }: Se - {LL.ENTITY() + ' ID'}:  + {LL.ID_OF(LL.ENTITY())}:  {editItem.id} diff --git a/interface/src/project/SettingsScheduler.tsx b/interface/src/project/SettingsScheduler.tsx index f8d2d8f72..6dcb83b06 100644 --- a/interface/src/project/SettingsScheduler.tsx +++ b/interface/src/project/SettingsScheduler.tsx @@ -208,7 +208,7 @@ const SettingsScheduler: FC = () => { {LL.SCHEDULE(0)} {LL.TIME(0)} {LL.COMMAND(0)} - {LL.VALUE(0)} + {LL.VALUE(1)} {LL.NAME(0)} diff --git a/interface/src/project/SettingsSchedulerDialog.tsx b/interface/src/project/SettingsSchedulerDialog.tsx index 111625da1..d038f8c40 100644 --- a/interface/src/project/SettingsSchedulerDialog.tsx +++ b/interface/src/project/SettingsSchedulerDialog.tsx @@ -210,7 +210,7 @@ const SettingsSchedulerDialog = ({ /> alovaInstance.Get(`/rest/coreData`); export const readDeviceData = (id: number) => alovaInstance.Get('/rest/deviceData', { // alovaInstance.Get(`/rest/deviceData/${id}`, { - params: { id }, // TODO remove later + params: { id }, // TODO replace params later responseType: 'arraybuffer' // uses msgpack }); export const writeDeviceValue = (data: any) => alovaInstance.Post('/rest/writeDeviceValue', data); @@ -44,7 +44,7 @@ export const readStatus = () => alovaInstance.Get('/rest/status'); export const scanDevices = () => alovaInstance.Post('/rest/scanDevices'); // HelpInformation -export const API = (apiCall: APIcall) => alovaInstance.Post('/api', apiCall); +export const APIcall = (device: string, apiData: APIdata) => alovaInstance.Post(`/api/${device}`, apiData); // UploadFileForm export const getSettings = () => alovaInstance.Get('/rest/getSettings'); @@ -56,7 +56,7 @@ export const getSchedule = () => alovaInstance.Get('/rest/getSchedule'); export const readDeviceEntities = (id: number) => // alovaInstance.Get(`/rest/deviceEntities/${id}`, { alovaInstance.Get(`/rest/deviceEntities`, { - params: { id }, // TODO remove later + params: { id }, // TODO replace params later responseType: 'arraybuffer', transformData(data: any) { return data.map((de: DeviceEntity) => ({ ...de, o_m: de.m, o_cn: de.cn, o_mi: de.mi, o_ma: de.ma })); diff --git a/interface/src/project/types.ts b/interface/src/project/types.ts index 9fe1bde37..fb044ac9f 100644 --- a/interface/src/project/types.ts +++ b/interface/src/project/types.ts @@ -266,8 +266,7 @@ export interface BoardProfile { eth_clock_mode: number; } -export interface APIcall { - device: string; +export interface APIdata { entity: string; id: any; } diff --git a/interface/src/types/system.ts b/interface/src/types/system.ts index 783c3e496..e78f0b1cf 100644 --- a/interface/src/types/system.ts +++ b/interface/src/types/system.ts @@ -2,9 +2,14 @@ export interface SystemStatus { emsesp_version: string; esp_platform: string; max_alloc_heap: number; + cpu_type: string; + cpu_rev: number; + cpu_cores: number; cpu_freq_mhz: number; free_heap: number; + arduino_version: string; sdk_version: string; + partition: string; flash_chip_size: number; flash_chip_speed: number; app_used: number; diff --git a/interface/src/utils/useRest.ts b/interface/src/utils/useRest.ts index bce61546e..990465d3e 100644 --- a/interface/src/utils/useRest.ts +++ b/interface/src/utils/useRest.ts @@ -33,7 +33,7 @@ export const useRest = ({ read, update }: RestRequestOptions2) => { }; onWriteSuccess(() => { - toast.success(LL.UPDATED_OF(LL.SETTINGS())); + toast.success(LL.UPDATED_OF(LL.SETTINGS(0))); setDirtyFlags([]); }); diff --git a/interface/yarn.lock b/interface/yarn.lock index 398cbaeda..c91ac531c 100644 --- a/interface/yarn.lock +++ b/interface/yarn.lock @@ -12,10 +12,10 @@ __metadata: languageName: node linkType: hard -"@alova/adapter-xhr@npm:^1.0.1": - version: 1.0.1 - resolution: "@alova/adapter-xhr@npm:1.0.1" - checksum: 10fb023fd30408bf47433491679057458c599194040077d30c8427531b6ee4a6549bf468a7132d8ae91e99c8bdcea27dc4706b5b982033ac820f779a5016be7d +"@alova/adapter-xhr@npm:^1.0.2": + version: 1.0.2 + resolution: "@alova/adapter-xhr@npm:1.0.2" + checksum: a57d178e89e3b655191bebccbc34d22760813b97b430e16f77b6ad561e3bb4ad8a34948aa2d724f5833d675f21a337ab769a3e5f73878430c3139374c6afb6ea languageName: node linkType: hard @@ -86,26 +86,26 @@ __metadata: languageName: node linkType: hard -"@babel/core@npm:^7.23.6": - version: 7.23.6 - resolution: "@babel/core@npm:7.23.6" +"@babel/core@npm:^7.23.7": + version: 7.23.7 + resolution: "@babel/core@npm:7.23.7" dependencies: "@ampproject/remapping": "npm:^2.2.0" "@babel/code-frame": "npm:^7.23.5" "@babel/generator": "npm:^7.23.6" "@babel/helper-compilation-targets": "npm:^7.23.6" "@babel/helper-module-transforms": "npm:^7.23.3" - "@babel/helpers": "npm:^7.23.6" + "@babel/helpers": "npm:^7.23.7" "@babel/parser": "npm:^7.23.6" "@babel/template": "npm:^7.22.15" - "@babel/traverse": "npm:^7.23.6" + "@babel/traverse": "npm:^7.23.7" "@babel/types": "npm:^7.23.6" 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: a72ba71d2f557d09ff58a5f0846344b9cea9dfcbd7418729a3a74d5b0f37a5ca024942fef4d19f248de751928a1be3d5cb0488746dd8896009dd55b974bb552e + checksum: 956841695ea801c8b4196d01072e6c1062335960715a6fcfd4009831003b526b00627c78b373ed49b1658c3622c71142f7ff04235fe839cac4a1a25ed51b90aa languageName: node linkType: hard @@ -304,14 +304,14 @@ __metadata: languageName: node linkType: hard -"@babel/helpers@npm:^7.23.6": - version: 7.23.6 - resolution: "@babel/helpers@npm:7.23.6" +"@babel/helpers@npm:^7.23.7": + version: 7.23.7 + resolution: "@babel/helpers@npm:7.23.7" dependencies: "@babel/template": "npm:^7.22.15" - "@babel/traverse": "npm:^7.23.6" + "@babel/traverse": "npm:^7.23.7" "@babel/types": "npm:^7.23.6" - checksum: 2a85fd2bcbc15a6c94dbe7b9e94d8920f9de76d164179d6895fee89c4339079d9e3e56f572bf19b5e7d1e6f1997d7fbaeaa686b47d35136852631dfd09e85c2f + checksum: ec07061dc871d406ed82c8757c4d7a510aaf15145799fb0a2c3bd3c72ca101fe82a02dd5f83ca604fbbba5de5408dd731bb1452150562bed4f3b0a2846f81f61 languageName: node linkType: hard @@ -401,7 +401,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.23.5": +"@babel/runtime@npm:^7.23.6": version: 7.23.6 resolution: "@babel/runtime@npm:7.23.6" dependencies: @@ -439,9 +439,9 @@ __metadata: languageName: node linkType: hard -"@babel/traverse@npm:^7.23.6": - version: 7.23.6 - resolution: "@babel/traverse@npm:7.23.6" +"@babel/traverse@npm:^7.23.7": + version: 7.23.7 + resolution: "@babel/traverse@npm:7.23.7" dependencies: "@babel/code-frame": "npm:^7.23.5" "@babel/generator": "npm:^7.23.6" @@ -453,7 +453,7 @@ __metadata: "@babel/types": "npm:^7.23.6" debug: "npm:^4.3.1" globals: "npm:^11.1.0" - checksum: ee4434a3ce792ee8956b64d76843caa1dda4779bb621ed9f951dd3551965bf1f292f097011c9730ecbc0b57f02434b1fa5a771610a2ef570726b0df0fc3332d9 + checksum: 3215e59429963c8dac85c26933372cdd322952aa9930e4bc5ef2d0e4bd7a1510d1ecf8f8fd860ace5d4d9fe496d23805a1ea019a86410aee4111de5f63ee84f9 languageName: node linkType: hard @@ -534,14 +534,14 @@ __metadata: languageName: node linkType: hard -"@emotion/react@npm:^11.11.1": - version: 11.11.1 - resolution: "@emotion/react@npm:11.11.1" +"@emotion/react@npm:^11.11.3": + version: 11.11.3 + resolution: "@emotion/react@npm:11.11.3" dependencies: "@babel/runtime": "npm:^7.18.3" "@emotion/babel-plugin": "npm:^11.11.0" "@emotion/cache": "npm:^11.11.0" - "@emotion/serialize": "npm:^1.1.2" + "@emotion/serialize": "npm:^1.1.3" "@emotion/use-insertion-effect-with-fallbacks": "npm:^1.0.1" "@emotion/utils": "npm:^1.2.1" "@emotion/weak-memoize": "npm:^0.3.1" @@ -551,7 +551,7 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: dfc140718d0a8051a74e51c379226d9de6b19f6a5dd595fb282ef72f4413695a2d012ba919f1e9eeff761c6659e6f7398da8e0e36eb7997a4fdf54cef88644ae + checksum: f7b98557b7d5236296dda48c2fc8a6cde4af7399758496e9f710f85a80c7d66fee1830966caabd7b237601bfdaca4e1add8c681d1ae4cc3d497fe88958d541c4 languageName: node linkType: hard @@ -568,6 +568,19 @@ __metadata: languageName: node linkType: hard +"@emotion/serialize@npm:^1.1.3": + version: 1.1.3 + resolution: "@emotion/serialize@npm:1.1.3" + dependencies: + "@emotion/hash": "npm:^0.9.1" + "@emotion/memoize": "npm:^0.8.1" + "@emotion/unitless": "npm:^0.8.1" + "@emotion/utils": "npm:^1.2.1" + csstype: "npm:^3.0.2" + checksum: 48d88923663273ae70359bc1a1f30454136716cbe0ddd9664be08e257ce56acedab911f125b627627358e37c9f450bbac3ea09b534ef42f9f67325d47b1e2a7b + languageName: node + linkType: hard + "@emotion/sheet@npm:^1.2.2": version: 1.2.2 resolution: "@emotion/sheet@npm:1.2.2" @@ -821,10 +834,10 @@ __metadata: languageName: node linkType: hard -"@eslint/js@npm:8.55.0": - version: 8.55.0 - resolution: "@eslint/js@npm:8.55.0" - checksum: 34b001a95b16501fd64f525b1de3ab0e4c252e5820b74069004934cb13977fc04ba4522a3e8f8074bd6af49da10d3444cd49fa711819f425ad73d6bf46eea82d +"@eslint/js@npm:8.56.0": + version: 8.56.0 + resolution: "@eslint/js@npm:8.56.0" + checksum: 97a4b5ccf7e24f4d205a1fb0f21cdcd610348ecf685f6798a48dd41ba443f2c1eedd3050ff5a0b8f30b8cf6501ab512aa9b76e531db15e59c9ebaa41f3162e37 languageName: node linkType: hard @@ -957,14 +970,14 @@ __metadata: languageName: node linkType: hard -"@mui/base@npm:5.0.0-beta.27": - version: 5.0.0-beta.27 - resolution: "@mui/base@npm:5.0.0-beta.27" +"@mui/base@npm:5.0.0-beta.29": + version: 5.0.0-beta.29 + resolution: "@mui/base@npm:5.0.0-beta.29" dependencies: - "@babel/runtime": "npm:^7.23.5" + "@babel/runtime": "npm:^7.23.6" "@floating-ui/react-dom": "npm:^2.0.4" "@mui/types": "npm:^7.2.11" - "@mui/utils": "npm:^5.15.0" + "@mui/utils": "npm:^5.15.2" "@popperjs/core": "npm:^2.11.8" clsx: "npm:^2.0.0" prop-types: "npm:^15.8.1" @@ -975,22 +988,22 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: 944f2a020cb6b58f5dccde55cdc25ec486b26a1f89ee18d108b555e9e8855834890664e4b67eb6e3d1961c3847cd14ec725dd6a28d7e462a26f933c5ef8472b3 + checksum: a651464968af6ebb775c24d2b9badc735b1d595e526ff7f8181186e6eed0735b14af8324db22a8744039ad79ce6dbb7c62920bb92a57959a66cf8e72d68af9aa languageName: node linkType: hard -"@mui/core-downloads-tracker@npm:^5.15.0": - version: 5.15.0 - resolution: "@mui/core-downloads-tracker@npm:5.15.0" - checksum: a7aadd4071ff715e618b8db647137579ca63cab4bf6e3acfe86a7d461f71605fc7ce44eeea5b1e789faae8546617b1f7d14c72a0c00fa3d59951b6eee42a6c5d +"@mui/core-downloads-tracker@npm:^5.15.2": + version: 5.15.2 + resolution: "@mui/core-downloads-tracker@npm:5.15.2" + checksum: 8c88ac73a1d87c8ce565f6295dcd084c643580848e8f59159402e9db89975263da06305a0e605d3744479e917c2d297319496534bca9df8338e203162f1e7c33 languageName: node linkType: hard -"@mui/icons-material@npm:^5.15.0": - version: 5.15.0 - resolution: "@mui/icons-material@npm:5.15.0" +"@mui/icons-material@npm:^5.15.2": + version: 5.15.2 + resolution: "@mui/icons-material@npm:5.15.2" dependencies: - "@babel/runtime": "npm:^7.23.5" + "@babel/runtime": "npm:^7.23.6" peerDependencies: "@mui/material": ^5.0.0 "@types/react": ^17.0.0 || ^18.0.0 @@ -998,21 +1011,21 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: 48a1c32a334bafe53723445ce29f73f920150b9421118909fbca37857f635ab802e3f49a247b7bbaf06f0d2b879deed9a9462df4dbd8d3c06cec29638c6a2d12 + checksum: 6dad9fa436889ab89217d428f38b1f7868eb5db0b8aa2b16086f6e81666763767a29db8897e76d078919df7349a149c6e16da1aea1b3ae48ca0b7ee1e0d9d458 languageName: node linkType: hard -"@mui/material@npm:^5.15.0": - version: 5.15.0 - resolution: "@mui/material@npm:5.15.0" +"@mui/material@npm:^5.15.2": + version: 5.15.2 + resolution: "@mui/material@npm:5.15.2" dependencies: - "@babel/runtime": "npm:^7.23.5" - "@mui/base": "npm:5.0.0-beta.27" - "@mui/core-downloads-tracker": "npm:^5.15.0" - "@mui/system": "npm:^5.15.0" + "@babel/runtime": "npm:^7.23.6" + "@mui/base": "npm:5.0.0-beta.29" + "@mui/core-downloads-tracker": "npm:^5.15.2" + "@mui/system": "npm:^5.15.2" "@mui/types": "npm:^7.2.11" - "@mui/utils": "npm:^5.15.0" - "@types/react-transition-group": "npm:^4.4.9" + "@mui/utils": "npm:^5.15.2" + "@types/react-transition-group": "npm:^4.4.10" clsx: "npm:^2.0.0" csstype: "npm:^3.1.2" prop-types: "npm:^15.8.1" @@ -1031,16 +1044,16 @@ __metadata: optional: true "@types/react": optional: true - checksum: 2ecffcebaa854de521dad6eef7b6d19db15f4cfd17462f6db548db2485bfe2ece8c4347c16a4ba40c621cd1c5c6a823d5936c3af1baf6550883d156d41bbc027 + checksum: 1ce902070022c40009e01208e95d0d61205ffdbcf4fadd16e6337acdfccfb1c66004525ffe277691c7f3fbdfcebb998f1544c054a31164d580cb040e8a7d2d80 languageName: node linkType: hard -"@mui/private-theming@npm:^5.15.0": - version: 5.15.0 - resolution: "@mui/private-theming@npm:5.15.0" +"@mui/private-theming@npm:^5.15.2": + version: 5.15.2 + resolution: "@mui/private-theming@npm:5.15.2" dependencies: - "@babel/runtime": "npm:^7.23.5" - "@mui/utils": "npm:^5.15.0" + "@babel/runtime": "npm:^7.23.6" + "@mui/utils": "npm:^5.15.2" prop-types: "npm:^15.8.1" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 @@ -1048,15 +1061,15 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: f02eee2c460a3d9ea288743dcd2fcb3d1e254c9428b9aae1d7cc37295d6ea530f6641071636922bbf8f36b8ee150330a71d643ca88420b18431d769c3a3cf413 + checksum: 2b1665044fd77286068100bd5c67ba3a31320084b442788e1c0224359b6e8e3213505676fa1db451c970b2e432811b12cbcf2f882c9063d37497dbfcfcd8811e languageName: node linkType: hard -"@mui/styled-engine@npm:^5.15.0": - version: 5.15.0 - resolution: "@mui/styled-engine@npm:5.15.0" +"@mui/styled-engine@npm:^5.15.2": + version: 5.15.2 + resolution: "@mui/styled-engine@npm:5.15.2" dependencies: - "@babel/runtime": "npm:^7.23.5" + "@babel/runtime": "npm:^7.23.6" "@emotion/cache": "npm:^11.11.0" csstype: "npm:^3.1.2" prop-types: "npm:^15.8.1" @@ -1069,19 +1082,19 @@ __metadata: optional: true "@emotion/styled": optional: true - checksum: 02de548366cf52461ba253fad81af00e1eeca828537e9647410583316a7585900467daa62258ec7e4f49143f6c7a114efc987a44b574b83d3e158243606eeaa1 + checksum: c004a37f4343139896059a706e96175a0f8975cc8807bcea96c099a68a94cf24d7869e685b06511389c9a6e4412acac5ef07614659983a7782f203012b78315b languageName: node linkType: hard -"@mui/system@npm:^5.15.0": - version: 5.15.0 - resolution: "@mui/system@npm:5.15.0" +"@mui/system@npm:^5.15.2": + version: 5.15.2 + resolution: "@mui/system@npm:5.15.2" dependencies: - "@babel/runtime": "npm:^7.23.5" - "@mui/private-theming": "npm:^5.15.0" - "@mui/styled-engine": "npm:^5.15.0" + "@babel/runtime": "npm:^7.23.6" + "@mui/private-theming": "npm:^5.15.2" + "@mui/styled-engine": "npm:^5.15.2" "@mui/types": "npm:^7.2.11" - "@mui/utils": "npm:^5.15.0" + "@mui/utils": "npm:^5.15.2" clsx: "npm:^2.0.0" csstype: "npm:^3.1.2" prop-types: "npm:^15.8.1" @@ -1097,7 +1110,7 @@ __metadata: optional: true "@types/react": optional: true - checksum: eeaaf11f5b63e53bec8e8bdc4eff187cea13d2ec0dbb0b2b3d1a392b264cc31f3b5e6cba69835aa8b02da29ec7e2c3522946b2c738321a14cd3e995f04c45312 + checksum: 05335cc7856750a930e5eef4eaf3e935c1d6dd78add48e86d1d976736adea71c5f37f3c329fd0a8f5fd9d11e40775ab5a62192dc056d240cb365416ad4db5568 languageName: node linkType: hard @@ -1113,11 +1126,11 @@ __metadata: languageName: node linkType: hard -"@mui/utils@npm:^5.15.0": - version: 5.15.0 - resolution: "@mui/utils@npm:5.15.0" +"@mui/utils@npm:^5.15.2": + version: 5.15.2 + resolution: "@mui/utils@npm:5.15.2" dependencies: - "@babel/runtime": "npm:^7.23.5" + "@babel/runtime": "npm:^7.23.6" "@types/prop-types": "npm:^15.7.11" prop-types: "npm:^15.8.1" react-is: "npm:^18.2.0" @@ -1127,7 +1140,7 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: 241ce42bc2f18df46f2adbbac640f16a64afd55a7829b10ef944d6648d45223ecad825ee56a44db29f6c387384e3977e30a966ea4425bda667e92989dd218b0a + checksum: 9ede26d8e2b456a5ecf088d4e2d6903613be57eae97fcd30a9f31ff2c35a0e4329c728bd20c94c6f3468038935c3101a040c2cfb7dd6ff7a490811af0675d90a languageName: node linkType: hard @@ -1262,10 +1275,10 @@ __metadata: languageName: node linkType: hard -"@remix-run/router@npm:1.13.1": - version: 1.13.1 - resolution: "@remix-run/router@npm:1.13.1" - checksum: bf1ff266744352e71fc414f983a9f7772c10ec55cf4b978d851026e6c12b39c0084f99e4e45de706b800a71889ef09f652b8e7c43e21800351cc14c5ada8c834 +"@remix-run/router@npm:1.14.1": + version: 1.14.1 + resolution: "@remix-run/router@npm:1.14.1" + checksum: caed61639006444a66ca832f1e500bac2fcf02695183e967ff1452d3172f888f2bb40591b239c85f9003b9628383cfd4c8ef55cde800d14276905c7031c9f0b9 languageName: node linkType: hard @@ -1553,12 +1566,12 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^20.10.4": - version: 20.10.4 - resolution: "@types/node@npm:20.10.4" +"@types/node@npm:^20.10.6": + version: 20.10.6 + resolution: "@types/node@npm:20.10.6" dependencies: undici-types: "npm:~5.26.4" - checksum: c10c1dd13f5c2341ad866777dc32946538a99e1ebd203ae127730814b8e5fa4aedfbcb01cb3e24a5466f1af64bcdfa16e7de6e745ff098fff0942aa779b7fe03 + checksum: 08471220d3cbbb6669835c4b78541edf5eface8f2c2e36c550cfa4ff73da73071c90e200a06359fac25d6564127597c23e178128058fb676824ec23d5178a017 languageName: node linkType: hard @@ -1583,12 +1596,12 @@ __metadata: languageName: node linkType: hard -"@types/react-dom@npm:^18.2.17": - version: 18.2.17 - resolution: "@types/react-dom@npm:18.2.17" +"@types/react-dom@npm:^18.2.18": + version: 18.2.18 + resolution: "@types/react-dom@npm:18.2.18" dependencies: "@types/react": "npm:*" - checksum: fe0dbb3224b48515da8fe25559e3777d756a27c3f22903f0b1b020de8d68bd57eb1f0af62b52ee65d9632637950afed8cbad24d158c4f3d910d083d49bd73fba + checksum: 4ef7725b4cebd4a32e049097ddfdfd855a178e63ead97ab6d3084872e7d6c1acd71aa923488123cd1015f0e0b11489d2b44f674a1df8fe82d7827eabbec6dbf1 languageName: node linkType: hard @@ -1613,12 +1626,12 @@ __metadata: languageName: node linkType: hard -"@types/react-transition-group@npm:^4.4.9": - version: 4.4.9 - resolution: "@types/react-transition-group@npm:4.4.9" +"@types/react-transition-group@npm:^4.4.10": + version: 4.4.10 + resolution: "@types/react-transition-group@npm:4.4.10" dependencies: "@types/react": "npm:*" - checksum: 74ed0985380544bd1d63d8865a452a859ed7122b35dd2cf919fa7d1f31936345671995d36c89263456f27dbb5940eac8d4607be969e27187102eecff1cc64ba3 + checksum: b429f3bd54d9aea6c0395943ce2dda6b76fb458e902365bd91fd99bf72064fb5d59e2b74e78d10f2871908501d350da63e230d81bda2b616c967cab8dc51bd16 languageName: node linkType: hard @@ -1633,14 +1646,14 @@ __metadata: languageName: node linkType: hard -"@types/react@npm:^18.2.43": - version: 18.2.43 - resolution: "@types/react@npm:18.2.43" +"@types/react@npm:^18.2.46": + version: 18.2.46 + resolution: "@types/react@npm:18.2.46" dependencies: "@types/prop-types": "npm:*" "@types/scheduler": "npm:*" csstype: "npm:^3.0.2" - checksum: a9d90a93380bb67623f27eba83e2d05b548109f7eb6fd591f5c4a3716bc257cc7cb078455db7ea4308d5f2ff6b4fe48d9a4a560145d9384069a2b5121bc93937 + checksum: 10fb28a5b8504106512ce3b154c45d1ac045c31633786773a29f003b3079b434060368bb56f95ef6c39510835ceec4fb8fdc271d6ca2b9cdd979379cf53f126b languageName: node linkType: hard @@ -1676,15 +1689,15 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/eslint-plugin@npm:^6.14.0": - version: 6.14.0 - resolution: "@typescript-eslint/eslint-plugin@npm:6.14.0" +"@typescript-eslint/eslint-plugin@npm:^6.16.0": + version: 6.16.0 + resolution: "@typescript-eslint/eslint-plugin@npm:6.16.0" dependencies: "@eslint-community/regexpp": "npm:^4.5.1" - "@typescript-eslint/scope-manager": "npm:6.14.0" - "@typescript-eslint/type-utils": "npm:6.14.0" - "@typescript-eslint/utils": "npm:6.14.0" - "@typescript-eslint/visitor-keys": "npm:6.14.0" + "@typescript-eslint/scope-manager": "npm:6.16.0" + "@typescript-eslint/type-utils": "npm:6.16.0" + "@typescript-eslint/utils": "npm:6.16.0" + "@typescript-eslint/visitor-keys": "npm:6.16.0" debug: "npm:^4.3.4" graphemer: "npm:^1.4.0" ignore: "npm:^5.2.4" @@ -1697,44 +1710,44 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: d420277bed0104713fb4a3c2e0fed32b300919708db3f2e3d13bc83e80a9aec181bfc4e1e6012c65408c318f3ac113926fc77e6667d7657e34fa0d5a2c21ee32 + checksum: 4bedce948ac3c20492a59813ee5d4f1f2306310857864dfaac2736f6c38e18785002c36844fd64c9fbdf3059fc390b29412be105fd7a118177f1eeeb1eb533f7 languageName: node linkType: hard -"@typescript-eslint/parser@npm:^6.14.0": - version: 6.14.0 - resolution: "@typescript-eslint/parser@npm:6.14.0" +"@typescript-eslint/parser@npm:^6.16.0": + version: 6.16.0 + resolution: "@typescript-eslint/parser@npm:6.16.0" dependencies: - "@typescript-eslint/scope-manager": "npm:6.14.0" - "@typescript-eslint/types": "npm:6.14.0" - "@typescript-eslint/typescript-estree": "npm:6.14.0" - "@typescript-eslint/visitor-keys": "npm:6.14.0" + "@typescript-eslint/scope-manager": "npm:6.16.0" + "@typescript-eslint/types": "npm:6.16.0" + "@typescript-eslint/typescript-estree": "npm:6.16.0" + "@typescript-eslint/visitor-keys": "npm:6.16.0" debug: "npm:^4.3.4" peerDependencies: eslint: ^7.0.0 || ^8.0.0 peerDependenciesMeta: typescript: optional: true - checksum: 34f46aa8aaadb0d0ecb7d791a8436fcf44ec04af33ee9d198bcf6f7ca3927d8caa79d4756e0c4ef0d50979d895df0b8f1a2473fc83104423c96856e9d56047f3 + checksum: 3d941ce345dc2ce29957e2110957662873d514b094b8939923c3281d858c11cd1f9058db862644afe14f68d087770f39a0a1f9e523a2013ed5d2fdf3421b34d0 languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:6.14.0": - version: 6.14.0 - resolution: "@typescript-eslint/scope-manager@npm:6.14.0" +"@typescript-eslint/scope-manager@npm:6.16.0": + version: 6.16.0 + resolution: "@typescript-eslint/scope-manager@npm:6.16.0" dependencies: - "@typescript-eslint/types": "npm:6.14.0" - "@typescript-eslint/visitor-keys": "npm:6.14.0" - checksum: fbe945169fe092df5953a54a552a9e8d9dc3dc158a39cd99de7f1843a169c82d3ba59e314b7d0f5b8110dbbe8c37c9e62dc2dda91a31536fe054221d5d8972c3 + "@typescript-eslint/types": "npm:6.16.0" + "@typescript-eslint/visitor-keys": "npm:6.16.0" + checksum: 3360aae4b85f5c31d20ad48d771cc09a6f8f6b1811b00d94f06e55b5a09c610ac75631b1c4edecb3bec682d41351b87e7d14d42bee84aa032064d0e13463035b languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:6.14.0": - version: 6.14.0 - resolution: "@typescript-eslint/type-utils@npm:6.14.0" +"@typescript-eslint/type-utils@npm:6.16.0": + version: 6.16.0 + resolution: "@typescript-eslint/type-utils@npm:6.16.0" dependencies: - "@typescript-eslint/typescript-estree": "npm:6.14.0" - "@typescript-eslint/utils": "npm:6.14.0" + "@typescript-eslint/typescript-estree": "npm:6.16.0" + "@typescript-eslint/utils": "npm:6.16.0" debug: "npm:^4.3.4" ts-api-utils: "npm:^1.0.1" peerDependencies: @@ -1742,59 +1755,60 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 52c2a380d694f629ed2d37ce9decc5d8f6d276b030dcb8ee2d0a21b667d789e0d50c8a4d06fa60a053cbcc162b50c3708260f569ccd765609f17499d5294c19d + checksum: 5964b87a87252bed278a248eb568902babd7c34defd3af8c3df371926d96aec716f33f1dc14bde170e93f73ed1b0af6e591e647853d0f33f378e2c7b3b73fc5b languageName: node linkType: hard -"@typescript-eslint/types@npm:6.14.0": - version: 6.14.0 - resolution: "@typescript-eslint/types@npm:6.14.0" - checksum: bcb32d69ac4a570634e37a3f149b7653a85334ac7b1d736961b627647ceff74797c4ac30b1405c508ede9462fad53b0b4442dbdf21877bf91263390c6e426e95 +"@typescript-eslint/types@npm:6.16.0": + version: 6.16.0 + resolution: "@typescript-eslint/types@npm:6.16.0" + checksum: 236ca318c2440c95068e5d4d147e2bfed62447775e18695e21c8ca04a341a74d01c37ed2b417629b7bf2fb91ad4fd5e2a6570215d16fc24dd1507ce6973b4e22 languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:6.14.0": - version: 6.14.0 - resolution: "@typescript-eslint/typescript-estree@npm:6.14.0" +"@typescript-eslint/typescript-estree@npm:6.16.0": + version: 6.16.0 + resolution: "@typescript-eslint/typescript-estree@npm:6.16.0" dependencies: - "@typescript-eslint/types": "npm:6.14.0" - "@typescript-eslint/visitor-keys": "npm:6.14.0" + "@typescript-eslint/types": "npm:6.16.0" + "@typescript-eslint/visitor-keys": "npm:6.16.0" debug: "npm:^4.3.4" globby: "npm:^11.1.0" is-glob: "npm:^4.0.3" + minimatch: "npm:9.0.3" semver: "npm:^7.5.4" ts-api-utils: "npm:^1.0.1" peerDependenciesMeta: typescript: optional: true - checksum: 870f00e81de428c0afae3f753c04229170aeec76d62dcded0e22cff1c733fe60a350cf68571c889f87ea7a6008b73f7c62a079e91ab056d79aa2b9803a5b7150 + checksum: 8e1ef03ecabaf3791b11240a51217836dbb74850e458258db77ac5eab5508cd9c63fb671924993d1e7654718c0c857c3550d51ecba0845fe489d143bb858e1b1 languageName: node linkType: hard -"@typescript-eslint/utils@npm:6.14.0": - version: 6.14.0 - resolution: "@typescript-eslint/utils@npm:6.14.0" +"@typescript-eslint/utils@npm:6.16.0": + version: 6.16.0 + resolution: "@typescript-eslint/utils@npm:6.16.0" dependencies: "@eslint-community/eslint-utils": "npm:^4.4.0" "@types/json-schema": "npm:^7.0.12" "@types/semver": "npm:^7.5.0" - "@typescript-eslint/scope-manager": "npm:6.14.0" - "@typescript-eslint/types": "npm:6.14.0" - "@typescript-eslint/typescript-estree": "npm:6.14.0" + "@typescript-eslint/scope-manager": "npm:6.16.0" + "@typescript-eslint/types": "npm:6.16.0" + "@typescript-eslint/typescript-estree": "npm:6.16.0" semver: "npm:^7.5.4" peerDependencies: eslint: ^7.0.0 || ^8.0.0 - checksum: fec7338edc31d89d5413ec49ce690e05741511ba1ba2a8c59ce14321f5026e73e0584dc9f35645ab4100561bcf8ecef8a08c042388743db53fe73f047132a150 + checksum: 84dd02f7c8e47fae699cc222da5cbea08b28c6e1cc7827860430bc86c2a17ee3f86e198a4356902b95930f85785aa662266ea9c476f69bf80c6a5f648e55f9f4 languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:6.14.0": - version: 6.14.0 - resolution: "@typescript-eslint/visitor-keys@npm:6.14.0" +"@typescript-eslint/visitor-keys@npm:6.16.0": + version: 6.16.0 + resolution: "@typescript-eslint/visitor-keys@npm:6.16.0" dependencies: - "@typescript-eslint/types": "npm:6.14.0" + "@typescript-eslint/types": "npm:6.16.0" eslint-visitor-keys: "npm:^3.4.1" - checksum: 404f87a121b4375b13e59ffc11ac2fe3c8e40025d0ef5cd6738ab7b3648ce1d41378414b1130ee68e0b454d7e6ec1937540799cdaa4ea572e2447b04d737ee44 + checksum: 19e559f14ea0092585a374b8c5f1aca9b6b271fc23909d9857de9cf71a1e1d3abc0afd237e9c02d7a5fbdfe8e3be7853cf9fedf40a6f16bac3495cb7f4e67982 languageName: node linkType: hard @@ -1809,33 +1823,33 @@ __metadata: version: 0.0.0-use.local resolution: "EMS-ESP@workspace:." dependencies: - "@alova/adapter-xhr": "npm:^1.0.1" - "@babel/core": "npm:^7.23.6" - "@emotion/react": "npm:^11.11.1" + "@alova/adapter-xhr": "npm:^1.0.2" + "@babel/core": "npm:^7.23.7" + "@emotion/react": "npm:^11.11.3" "@emotion/styled": "npm:^11.11.0" - "@mui/icons-material": "npm:^5.15.0" - "@mui/material": "npm:^5.15.0" + "@mui/icons-material": "npm:^5.15.2" + "@mui/material": "npm:^5.15.2" "@preact/compat": "npm:^17.1.2" "@preact/preset-vite": "npm:^2.7.0" "@table-library/react-table-library": "npm:4.1.7" "@types/imagemin": "npm:^8.0.5" "@types/lodash-es": "npm:^4.17.12" - "@types/node": "npm:^20.10.4" - "@types/react": "npm:^18.2.43" - "@types/react-dom": "npm:^18.2.17" + "@types/node": "npm:^20.10.6" + "@types/react": "npm:^18.2.46" + "@types/react-dom": "npm:^18.2.18" "@types/react-router-dom": "npm:^5.3.3" - "@typescript-eslint/eslint-plugin": "npm:^6.14.0" - "@typescript-eslint/parser": "npm:^6.14.0" - alova: "npm:^2.16.0" + "@typescript-eslint/eslint-plugin": "npm:^6.16.0" + "@typescript-eslint/parser": "npm:^6.16.0" + alova: "npm:^2.16.2" async-validator: "npm:^4.2.5" concurrently: "npm:^8.2.2" - eslint: "npm:^8.55.0" + eslint: "npm:^8.56.0" eslint-config-airbnb: "npm:^19.0.4" eslint-config-airbnb-typescript: "npm:^17.1.0" eslint-config-prettier: "npm:^9.1.0" eslint-import-resolver-typescript: "npm:^3.6.1" eslint-plugin-autofix: "npm:^1.1.0" - eslint-plugin-import: "npm:^2.29.0" + eslint-plugin-import: "npm:^2.29.1" eslint-plugin-jsx-a11y: "npm:^6.8.0" eslint-plugin-prettier: "npm:alpha" eslint-plugin-react: "npm:^7.33.2" @@ -1850,16 +1864,16 @@ __metadata: react-dom: "npm:latest" react-dropzone: "npm:^14.2.3" react-icons: "npm:^4.12.0" - react-router-dom: "npm:^6.20.1" + react-router-dom: "npm:^6.21.1" react-toastify: "npm:^9.1.3" - rollup-plugin-visualizer: "npm:^5.11.0" + rollup-plugin-visualizer: "npm:^5.12.0" sockette: "npm:^2.0.6" terser: "npm:^5.26.0" typesafe-i18n: "npm:^5.26.2" typescript: "npm:^5.3.3" - vite: "npm:^5.0.8" + vite: "npm:^5.0.10" vite-plugin-imagemin: "npm:^0.6.1" - vite-tsconfig-paths: "npm:^4.2.2" + vite-tsconfig-paths: "npm:^4.2.3" languageName: unknown linkType: soft @@ -1928,10 +1942,10 @@ __metadata: languageName: node linkType: hard -"alova@npm:^2.16.0": - version: 2.16.0 - resolution: "alova@npm:2.16.0" - checksum: 476aaf451c6760f46822f0e8bf834c86700ccd6e8f28b28f0381afa43a4d2bc361fa1414fe30e8494e6a5a5a49a6f744b9a59ec7f0ad179f4cf5b985a0a638ed +"alova@npm:^2.16.2": + version: 2.16.2 + resolution: "alova@npm:2.16.2" + checksum: 06fafddf380d4d8e8e5dd172ebcaa0bc229c76c11b2675cfb2c0ab884a36d4818159267adb14ec7a3cbe681464793085b0386d7741e6a6a732c764b14c8783a8 languageName: node linkType: hard @@ -3814,9 +3828,9 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-import@npm:^2.29.0": - version: 2.29.0 - resolution: "eslint-plugin-import@npm:2.29.0" +"eslint-plugin-import@npm:^2.29.1": + version: 2.29.1 + resolution: "eslint-plugin-import@npm:2.29.1" dependencies: array-includes: "npm:^3.1.7" array.prototype.findlastindex: "npm:^1.2.3" @@ -3834,10 +3848,10 @@ __metadata: object.groupby: "npm:^1.0.1" object.values: "npm:^1.1.7" semver: "npm:^6.3.1" - tsconfig-paths: "npm:^3.14.2" + tsconfig-paths: "npm:^3.15.0" peerDependencies: eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 - checksum: d6e8d016f38369892c85b866f762c03dee2b337d4f12031756e30d7490879261d1192a3c2f682fd7c4d2b923465f7a1e3d22cfdad5da1b1391c3bd39ea87af1a + checksum: 5865f05c38552145423c535326ec9a7113ab2305c7614c8b896ff905cfabc859c8805cac21e979c9f6f742afa333e6f62f812eabf891a7e8f5f0b853a32593c1 languageName: node linkType: hard @@ -3945,14 +3959,14 @@ __metadata: languageName: node linkType: hard -"eslint@npm:^8.55.0": - version: 8.55.0 - resolution: "eslint@npm:8.55.0" +"eslint@npm:^8.56.0": + version: 8.56.0 + resolution: "eslint@npm:8.56.0" dependencies: "@eslint-community/eslint-utils": "npm:^4.2.0" "@eslint-community/regexpp": "npm:^4.6.1" "@eslint/eslintrc": "npm:^2.1.4" - "@eslint/js": "npm:8.55.0" + "@eslint/js": "npm:8.56.0" "@humanwhocodes/config-array": "npm:^0.11.13" "@humanwhocodes/module-importer": "npm:^1.0.1" "@nodelib/fs.walk": "npm:^1.2.8" @@ -3989,7 +4003,7 @@ __metadata: text-table: "npm:^0.2.0" bin: eslint: bin/eslint.js - checksum: afd016cfbe9e9d667b3f98c14c681a7e518808f6c30856e56cbb02248900eac5bf6dc5e577a7eaec259539486db48ef7d16ef58fb14b1585ba7c84b35490c53c + checksum: ef6193c6e4cef20774b985a5cc2fd4bf6d3c4decd423117cbc4a0196617861745db291217ad3c537bc3a160650cca965bc818f55e1f3e446af1fcb293f9940a5 languageName: node linkType: hard @@ -6164,6 +6178,15 @@ __metadata: languageName: node linkType: hard +"minimatch@npm:9.0.3, minimatch@npm:^9.0.1": + version: 9.0.3 + resolution: "minimatch@npm:9.0.3" + dependencies: + brace-expansion: "npm:^2.0.1" + checksum: c81b47d28153e77521877649f4bab48348d10938df9e8147a58111fe00ef89559a2938de9f6632910c4f7bf7bb5cd81191a546167e58d357f0cfb1e18cecc1c5 + languageName: node + linkType: hard + "minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": version: 3.1.2 resolution: "minimatch@npm:3.1.2" @@ -6173,15 +6196,6 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^9.0.1": - version: 9.0.3 - resolution: "minimatch@npm:9.0.3" - dependencies: - brace-expansion: "npm:^2.0.1" - checksum: c81b47d28153e77521877649f4bab48348d10938df9e8147a58111fe00ef89559a2938de9f6632910c4f7bf7bb5cd81191a546167e58d357f0cfb1e18cecc1c5 - languageName: node - linkType: hard - "minimist@npm:^1.1.3, minimist@npm:^1.2.0, minimist@npm:^1.2.6": version: 1.2.8 resolution: "minimist@npm:1.2.8" @@ -7130,27 +7144,27 @@ __metadata: languageName: node linkType: hard -"react-router-dom@npm:^6.20.1": - version: 6.20.1 - resolution: "react-router-dom@npm:6.20.1" +"react-router-dom@npm:^6.21.1": + version: 6.21.1 + resolution: "react-router-dom@npm:6.21.1" dependencies: - "@remix-run/router": "npm:1.13.1" - react-router: "npm:6.20.1" + "@remix-run/router": "npm:1.14.1" + react-router: "npm:6.21.1" peerDependencies: react: ">=16.8" react-dom: ">=16.8" - checksum: 27efb05af0025bdcd7ecc85d2df2f53ca90bbf4db1dd4319002714b5be4e23c9434f95932d79f14a42d7c347ca882e9a0bba74a4d6331de8f7fb527c21f3f069 + checksum: 2d75bd889828fa5516ad076b44506656d826c365645e7079138cd0ef899db28a1b212f708a6c6e3b543ae11b96b2031f01201cc2fe1733dd4d9c5cbdd4d734ef languageName: node linkType: hard -"react-router@npm:6.20.1": - version: 6.20.1 - resolution: "react-router@npm:6.20.1" +"react-router@npm:6.21.1": + version: 6.21.1 + resolution: "react-router@npm:6.21.1" dependencies: - "@remix-run/router": "npm:1.13.1" + "@remix-run/router": "npm:1.14.1" peerDependencies: react: ">=16.8" - checksum: 96c25c8ca782dfa5b501540b9a491d8dca67c829a90fda237238a22881c695226fd5bbe14fcb2793bd5877aec2514d932c3293bf1f2463606fb3f2326628d766 + checksum: 1220cc75e0c915a26dde9dbb6509a8f0b0163d96e5ad591af91d9bb5a92a18401718f8d872a03d1cb366e7a6216c165a5cadd12375adf97943f37d7f5c487a90 languageName: node linkType: hard @@ -7436,9 +7450,9 @@ __metadata: languageName: node linkType: hard -"rollup-plugin-visualizer@npm:^5.11.0": - version: 5.11.0 - resolution: "rollup-plugin-visualizer@npm:5.11.0" +"rollup-plugin-visualizer@npm:^5.12.0": + version: 5.12.0 + resolution: "rollup-plugin-visualizer@npm:5.12.0" dependencies: open: "npm:^8.4.0" picomatch: "npm:^2.3.1" @@ -7451,7 +7465,7 @@ __metadata: optional: true bin: rollup-plugin-visualizer: dist/bin/cli.js - checksum: 947238aa22706a47a4d3e8ce616855f0e5cb969ed9f61b9a268eaede0a86f461ecb38e27b4e6bf00f4b5e3f63677667f65e0d4af89a659a5160f74add1f192bb + checksum: 47358feb672291d6edcfd94197577c192a84c24cb644119425dae8241fb6f5a52556efd0c501f38b276c07534642a80c0885ef681babb474e83c7b5a3b475b84 languageName: node linkType: hard @@ -8363,15 +8377,15 @@ __metadata: languageName: node linkType: hard -"tsconfig-paths@npm:^3.14.2": - version: 3.14.2 - resolution: "tsconfig-paths@npm:3.14.2" +"tsconfig-paths@npm:^3.15.0": + version: 3.15.0 + resolution: "tsconfig-paths@npm:3.15.0" dependencies: "@types/json5": "npm:^0.0.29" json5: "npm:^1.0.2" minimist: "npm:^1.2.6" strip-bom: "npm:^3.0.0" - checksum: 17f23e98612a60cf23b80dc1d3b7b840879e41fcf603868fc3618a30f061ac7b463ef98cad8c28b68733b9bfe0cc40ffa2bcf29e94cf0d26e4f6addf7ac8527d + checksum: 2041beaedc6c271fc3bedd12e0da0cc553e65d030d4ff26044b771fac5752d0460944c0b5e680f670c2868c95c664a256cec960ae528888db6ded83524e33a14 languageName: node linkType: hard @@ -8660,9 +8674,9 @@ __metadata: languageName: node linkType: hard -"vite-tsconfig-paths@npm:^4.2.2": - version: 4.2.2 - resolution: "vite-tsconfig-paths@npm:4.2.2" +"vite-tsconfig-paths@npm:^4.2.3": + version: 4.2.3 + resolution: "vite-tsconfig-paths@npm:4.2.3" dependencies: debug: "npm:^4.1.1" globrex: "npm:^0.1.2" @@ -8672,13 +8686,13 @@ __metadata: peerDependenciesMeta: vite: optional: true - checksum: 790b9a48dd69b6e93bc41455ef0cc63fc8149b40a6d344784067fc2487b0a02f2d6a6d71396214dab7537a52c5e1ddfc88c363232fa707377db161d05e8f68cd + checksum: ba6abe5d18fc1c1e494e1f1d8a7db56445c2a40e15aadb5d47a9c66cc5372d6f69b94ff0b1e47b67659d6ecaeddebab0a9d11e40b1c3c36c0115800736a6c760 languageName: node linkType: hard -"vite@npm:^5.0.8": - version: 5.0.8 - resolution: "vite@npm:5.0.8" +"vite@npm:^5.0.10": + version: 5.0.10 + resolution: "vite@npm:5.0.10" dependencies: esbuild: "npm:^0.19.3" fsevents: "npm:~2.3.3" @@ -8712,7 +8726,7 @@ __metadata: optional: true bin: vite: bin/vite.js - checksum: ea36e34fa45401d8e29317c3355f4d7081df09b412578bd7b6a26d44bccace9d130625f7f317a3cbc20ad2aadc5881d01d1508e8d9e36060ae44d974f505dd7e + checksum: 5421e9c7f8cf3152eace9a8b528269141635f367e5dc63c5f1fe2712a766d9757f8197733cf3f28be590afdd520130d38de90c955e6dba6edfa6f9056c1e5ea7 languageName: node linkType: hard diff --git a/lib/AsyncTCP/README.md b/lib/AsyncTCP/README.md deleted file mode 100644 index 983aabd97..000000000 --- a/lib/AsyncTCP/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# AsyncTCP -[![Build Status](https://travis-ci.org/me-no-dev/AsyncTCP.svg?branch=master)](https://travis-ci.org/me-no-dev/AsyncTCP) ![](https://github.com/me-no-dev/AsyncTCP/workflows/Async%20TCP%20CI/badge.svg) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/2f7e4d1df8b446d192cbfec6dc174d2d)](https://www.codacy.com/manual/me-no-dev/AsyncTCP?utm_source=github.com&utm_medium=referral&utm_content=me-no-dev/AsyncTCP&utm_campaign=Badge_Grade) - -### Async TCP Library for ESP32 Arduino - -[![Join the chat at https://gitter.im/me-no-dev/ESPAsyncWebServer](https://badges.gitter.im/me-no-dev/ESPAsyncWebServer.svg)](https://gitter.im/me-no-dev/ESPAsyncWebServer?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) - -This is a fully asynchronous TCP library, aimed at enabling trouble-free, multi-connection network environment for Espressif's ESP32 MCUs. - -This library is the base for [ESPAsyncWebServer](https://github.com/me-no-dev/ESPAsyncWebServer) - -## AsyncClient and AsyncServer -The base classes on which everything else is built. They expose all possible scenarios, but are really raw and require more skills to use. diff --git a/lib/AsyncTCP/library.properties b/lib/AsyncTCP/library.properties deleted file mode 100644 index eb4e26e90..000000000 --- a/lib/AsyncTCP/library.properties +++ /dev/null @@ -1,9 +0,0 @@ -name=AsyncTCP -version=1.1.1 -author=Me-No-Dev -maintainer=Me-No-Dev -sentence=Async TCP Library for ESP32 -paragraph=Async TCP Library for ESP32 -category=Other -url=https://github.com/me-no-dev/AsyncTCP -architectures=* diff --git a/lib/AsyncTCP/src/AsyncTCP.cpp b/lib/AsyncTCP/src/AsyncTCP.cpp deleted file mode 100644 index 2a2a99d94..000000000 --- a/lib/AsyncTCP/src/AsyncTCP.cpp +++ /dev/null @@ -1,1476 +0,0 @@ -/* - Asynchronous TCP library for Espressif MCUs - - Copyright (c) 2016 Hristo Gochkov. All rights reserved. - This file is part of the esp8266 core for Arduino environment. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#include "Arduino.h" - -#include "AsyncTCP.h" -extern "C" { -#include "lwip/opt.h" -#include "lwip/tcp.h" -#include "lwip/inet.h" -#include "lwip/dns.h" -#include "lwip/err.h" -} -#include "esp_task_wdt.h" - -/* - * TCP/IP Event Task - * */ - -typedef enum { - LWIP_TCP_SENT, - LWIP_TCP_RECV, - LWIP_TCP_FIN, - LWIP_TCP_ERROR, - LWIP_TCP_POLL, - LWIP_TCP_CLEAR, - LWIP_TCP_ACCEPT, - LWIP_TCP_CONNECTED, - LWIP_TCP_DNS -} lwip_event_t; - -typedef struct { - lwip_event_t event; - void * arg; - union { - struct { - void * pcb; - int8_t err; - } connected; - struct { - int8_t err; - } error; - struct { - tcp_pcb * pcb; - uint16_t len; - } sent; - struct { - tcp_pcb * pcb; - pbuf * pb; - int8_t err; - } recv; - struct { - tcp_pcb * pcb; - int8_t err; - } fin; - struct { - tcp_pcb * pcb; - } poll; - struct { - AsyncClient * client; - } accept; - struct { - const char * name; - ip_addr_t addr; - } dns; - }; -} lwip_event_packet_t; - -static QueueHandle_t _async_queue; -static TaskHandle_t _async_service_task_handle = NULL; - - -SemaphoreHandle_t _slots_lock; -const int _number_of_closed_slots = CONFIG_LWIP_MAX_ACTIVE_TCP; -static uint32_t _closed_slots[_number_of_closed_slots]; -static uint32_t _closed_index = []() { - _slots_lock = xSemaphoreCreateBinary(); - xSemaphoreGive(_slots_lock); - for (int i = 0; i < _number_of_closed_slots; ++i) { - _closed_slots[i] = 1; - } - return 1; -}(); - - -static inline bool _init_async_event_queue() { - if (!_async_queue) { - _async_queue = xQueueCreate(CONFIG_ASYNC_TCP_QUEUE, sizeof(lwip_event_packet_t *)); // double queue to 128 see https://github.com/emsesp/EMS-ESP32/issues/177 - if (!_async_queue) { - return false; - } - } - return true; -} - -static inline bool _send_async_event(lwip_event_packet_t ** e) { - return _async_queue && xQueueSend(_async_queue, e, portMAX_DELAY) == pdPASS; -} - -static inline bool _prepend_async_event(lwip_event_packet_t ** e) { - return _async_queue && xQueueSendToFront(_async_queue, e, portMAX_DELAY) == pdPASS; -} - -static inline bool _get_async_event(lwip_event_packet_t ** e) { - return _async_queue && xQueueReceive(_async_queue, e, portMAX_DELAY) == pdPASS; -} - -static bool _remove_events_with_arg(void * arg) { - lwip_event_packet_t * first_packet = NULL; - lwip_event_packet_t * packet = NULL; - - if (!_async_queue) { - return false; - } - //figure out which is the first packet so we can keep the order - while (!first_packet) { - if (xQueueReceive(_async_queue, &first_packet, 0) != pdPASS) { - return false; - } - //discard packet if matching - if ((int)first_packet->arg == (int)arg) { - free(first_packet); - first_packet = NULL; - //return first packet to the back of the queue - } else if (xQueueSend(_async_queue, &first_packet, portMAX_DELAY) != pdPASS) { - return false; - } - } - - while (xQueuePeek(_async_queue, &packet, 0) == pdPASS && packet != first_packet) { - if (xQueueReceive(_async_queue, &packet, 0) != pdPASS) { - return false; - } - if ((int)packet->arg == (int)arg) { - free(packet); - packet = NULL; - } else if (xQueueSend(_async_queue, &packet, portMAX_DELAY) != pdPASS) { - return false; - } - } - return true; -} - -static void _handle_async_event(lwip_event_packet_t * e) { - if (e->arg == NULL) { - // do nothing when arg is NULL - //ets_printf("event arg == NULL: 0x%08x\n", e->recv.pcb); - } else if (e->event == LWIP_TCP_CLEAR) { - _remove_events_with_arg(e->arg); - } else if (e->event == LWIP_TCP_RECV) { - //ets_printf("-R: 0x%08x\n", e->recv.pcb); - AsyncClient::_s_recv(e->arg, e->recv.pcb, e->recv.pb, e->recv.err); - } else if (e->event == LWIP_TCP_FIN) { - //ets_printf("-F: 0x%08x\n", e->fin.pcb); - AsyncClient::_s_fin(e->arg, e->fin.pcb, e->fin.err); - } else if (e->event == LWIP_TCP_SENT) { - //ets_printf("-S: 0x%08x\n", e->sent.pcb); - AsyncClient::_s_sent(e->arg, e->sent.pcb, e->sent.len); - } else if (e->event == LWIP_TCP_POLL) { - //ets_printf("-P: 0x%08x\n", e->poll.pcb); - AsyncClient::_s_poll(e->arg, e->poll.pcb); - } else if (e->event == LWIP_TCP_ERROR) { - //ets_printf("-E: 0x%08x %d\n", e->arg, e->error.err); - AsyncClient::_s_error(e->arg, e->error.err); - } else if (e->event == LWIP_TCP_CONNECTED) { - //ets_printf("C: 0x%08x 0x%08x %d\n", e->arg, e->connected.pcb, e->connected.err); - AsyncClient::_s_connected(e->arg, e->connected.pcb, e->connected.err); - } else if (e->event == LWIP_TCP_ACCEPT) { - //ets_printf("A: 0x%08x 0x%08x\n", e->arg, e->accept.client); - AsyncServer::_s_accepted(e->arg, e->accept.client); - } else if (e->event == LWIP_TCP_DNS) { - //ets_printf("D: 0x%08x %s = %s\n", e->arg, e->dns.name, ipaddr_ntoa(&e->dns.addr)); - AsyncClient::_s_dns_found(e->dns.name, &e->dns.addr, e->arg); - } - free((void *)(e)); -} - -static void _async_service_task(void * pvParameters) { - lwip_event_packet_t * packet = NULL; - for (;;) { - if (_get_async_event(&packet)) { -#if CONFIG_ASYNC_TCP_USE_WDT - if (esp_task_wdt_add(NULL) != ESP_OK) { - log_e("Failed to add async task to WDT"); - } -#endif - _handle_async_event(packet); -#if CONFIG_ASYNC_TCP_USE_WDT - if (esp_task_wdt_delete(NULL) != ESP_OK) { - log_e("Failed to remove loop task from WDT"); - } -#endif - } - } - vTaskDelete(NULL); - _async_service_task_handle = NULL; -} -/* -static void _stop_async_task(){ - if(_async_service_task_handle){ - vTaskDelete(_async_service_task_handle); - _async_service_task_handle = NULL; - } -} -*/ -static bool _start_async_task() { - if (!_init_async_event_queue()) { - return false; - } - if (!_async_service_task_handle) { - // xTaskCreateUniversal(_async_service_task, "async_tcp", 8192 * 2, NULL, 3, &_async_service_task_handle, CONFIG_ASYNC_TCP_RUNNING_CORE); - xTaskCreate(_async_service_task, "async_tcp", CONFIG_ASYNC_TCP_STACK, NULL, CONFIG_ASYNC_TCP_TASK_PRIORITY, &_async_service_task_handle); - if (!_async_service_task_handle) { - return false; - } - } - return true; -} - -/* - * LwIP Callbacks - * */ - -static int8_t _tcp_clear_events(void * arg) { - lwip_event_packet_t * e = (lwip_event_packet_t *)malloc(sizeof(lwip_event_packet_t)); - e->event = LWIP_TCP_CLEAR; - e->arg = arg; - if (!_prepend_async_event(&e)) { - free((void *)(e)); - } - return ERR_OK; -} - -static int8_t _tcp_connected(void * arg, tcp_pcb * pcb, int8_t err) { - //ets_printf("+C: 0x%08x\n", pcb); - lwip_event_packet_t * e = (lwip_event_packet_t *)malloc(sizeof(lwip_event_packet_t)); - e->event = LWIP_TCP_CONNECTED; - e->arg = arg; - e->connected.pcb = pcb; - e->connected.err = err; - if (!_prepend_async_event(&e)) { - free((void *)(e)); - } - return ERR_OK; -} - -static int8_t _tcp_poll(void * arg, struct tcp_pcb * pcb) { - //ets_printf("+P: 0x%08x\n", pcb); - lwip_event_packet_t * e = (lwip_event_packet_t *)malloc(sizeof(lwip_event_packet_t)); - e->event = LWIP_TCP_POLL; - e->arg = arg; - e->poll.pcb = pcb; - if (!_send_async_event(&e)) { - free((void *)(e)); - } - return ERR_OK; -} - -static int8_t _tcp_recv(void * arg, struct tcp_pcb * pcb, struct pbuf * pb, int8_t err) { - lwip_event_packet_t * e = (lwip_event_packet_t *)malloc(sizeof(lwip_event_packet_t)); - e->arg = arg; - if (pb) { - //ets_printf("+R: 0x%08x\n", pcb); - e->event = LWIP_TCP_RECV; - e->recv.pcb = pcb; - e->recv.pb = pb; - e->recv.err = err; - } else { - //ets_printf("+F: 0x%08x\n", pcb); - e->event = LWIP_TCP_FIN; - e->fin.pcb = pcb; - e->fin.err = err; - //close the PCB in LwIP thread - AsyncClient::_s_lwip_fin(e->arg, e->fin.pcb, e->fin.err); - } - if (!_send_async_event(&e)) { - free((void *)(e)); - } - return ERR_OK; -} - -static int8_t _tcp_sent(void * arg, struct tcp_pcb * pcb, uint16_t len) { - //ets_printf("+S: 0x%08x\n", pcb); - lwip_event_packet_t * e = (lwip_event_packet_t *)malloc(sizeof(lwip_event_packet_t)); - e->event = LWIP_TCP_SENT; - e->arg = arg; - e->sent.pcb = pcb; - e->sent.len = len; - if (!_send_async_event(&e)) { - free((void *)(e)); - } - return ERR_OK; -} - -static void _tcp_error(void * arg, int8_t err) { - //ets_printf("+E: 0x%08x\n", arg); - lwip_event_packet_t * e = (lwip_event_packet_t *)malloc(sizeof(lwip_event_packet_t)); - e->event = LWIP_TCP_ERROR; - e->arg = arg; - e->error.err = err; - if (!_send_async_event(&e)) { - free((void *)(e)); - } -} - -static void _tcp_dns_found(const char * name, struct ip_addr * ipaddr, void * arg) { - lwip_event_packet_t * e = (lwip_event_packet_t *)malloc(sizeof(lwip_event_packet_t)); - //ets_printf("+DNS: name=%s ipaddr=0x%08x arg=%x\n", name, ipaddr, arg); - e->event = LWIP_TCP_DNS; - e->arg = arg; - e->dns.name = name; - if (ipaddr) { - memcpy(&e->dns.addr, ipaddr, sizeof(struct ip_addr)); - } else { - memset(&e->dns.addr, 0, sizeof(e->dns.addr)); - } - if (!_send_async_event(&e)) { - free((void *)(e)); - } -} - -//Used to switch out from LwIP thread -static int8_t _tcp_accept(void * arg, AsyncClient * client) { - lwip_event_packet_t * e = (lwip_event_packet_t *)malloc(sizeof(lwip_event_packet_t)); - e->event = LWIP_TCP_ACCEPT; - e->arg = arg; - e->accept.client = client; - if (!_prepend_async_event(&e)) { - free((void *)(e)); - } - return ERR_OK; -} - -/* - * TCP/IP API Calls - * */ - -#include "lwip/priv/tcpip_priv.h" - -typedef struct { - struct tcpip_api_call_data call; - tcp_pcb * pcb; - int8_t closed_slot; - int8_t err; - union { - struct { - const char * data; - size_t size; - uint8_t apiflags; - } write; - size_t received; - struct { - ip_addr_t * addr; - uint16_t port; - tcp_connected_fn cb; - } connect; - struct { - ip_addr_t * addr; - uint16_t port; - } bind; - uint8_t backlog; - }; -} tcp_api_call_t; - -static err_t _tcp_output_api(struct tcpip_api_call_data * api_call_msg) { - tcp_api_call_t * msg = (tcp_api_call_t *)api_call_msg; - msg->err = ERR_CONN; - if (msg->closed_slot == -1 || !_closed_slots[msg->closed_slot]) { - msg->err = tcp_output(msg->pcb); - } - return msg->err; -} - -static esp_err_t _tcp_output(tcp_pcb * pcb, int8_t closed_slot) { - if (!pcb) { - return ERR_CONN; - } - tcp_api_call_t msg; - msg.pcb = pcb; - msg.closed_slot = closed_slot; - tcpip_api_call(_tcp_output_api, (struct tcpip_api_call_data *)&msg); - return msg.err; -} - -static err_t _tcp_write_api(struct tcpip_api_call_data * api_call_msg) { - tcp_api_call_t * msg = (tcp_api_call_t *)api_call_msg; - msg->err = ERR_CONN; - if (msg->closed_slot == -1 || !_closed_slots[msg->closed_slot]) { - msg->err = tcp_write(msg->pcb, msg->write.data, msg->write.size, msg->write.apiflags); - } - return msg->err; -} - -static esp_err_t _tcp_write(tcp_pcb * pcb, int8_t closed_slot, const char * data, size_t size, uint8_t apiflags) { - if (!pcb) { - return ERR_CONN; - } - tcp_api_call_t msg; - msg.pcb = pcb; - msg.closed_slot = closed_slot; - msg.write.data = data; - msg.write.size = size; - msg.write.apiflags = apiflags; - tcpip_api_call(_tcp_write_api, (struct tcpip_api_call_data *)&msg); - return msg.err; -} - -static err_t _tcp_recved_api(struct tcpip_api_call_data * api_call_msg) { - tcp_api_call_t * msg = (tcp_api_call_t *)api_call_msg; - msg->err = ERR_CONN; - if (msg->closed_slot == -1 || !_closed_slots[msg->closed_slot]) { - msg->err = 0; - tcp_recved(msg->pcb, msg->received); - } - return msg->err; -} - -static esp_err_t _tcp_recved(tcp_pcb * pcb, int8_t closed_slot, size_t len) { - if (!pcb) { - return ERR_CONN; - } - tcp_api_call_t msg; - msg.pcb = pcb; - msg.closed_slot = closed_slot; - msg.received = len; - tcpip_api_call(_tcp_recved_api, (struct tcpip_api_call_data *)&msg); - return msg.err; -} - -static err_t _tcp_close_api(struct tcpip_api_call_data * api_call_msg) { - tcp_api_call_t * msg = (tcp_api_call_t *)api_call_msg; - msg->err = ERR_CONN; - if (msg->closed_slot == -1 || !_closed_slots[msg->closed_slot]) { - msg->err = tcp_close(msg->pcb); - } - return msg->err; -} - -static esp_err_t _tcp_close(tcp_pcb * pcb, int8_t closed_slot) { - if (!pcb) { - return ERR_CONN; - } - tcp_api_call_t msg; - msg.pcb = pcb; - msg.closed_slot = closed_slot; - tcpip_api_call(_tcp_close_api, (struct tcpip_api_call_data *)&msg); - return msg.err; -} - -static err_t _tcp_abort_api(struct tcpip_api_call_data * api_call_msg) { - tcp_api_call_t * msg = (tcp_api_call_t *)api_call_msg; - msg->err = ERR_CONN; - if (msg->closed_slot == -1 || !_closed_slots[msg->closed_slot]) { - tcp_abort(msg->pcb); - } - return msg->err; -} - -static esp_err_t _tcp_abort(tcp_pcb * pcb, int8_t closed_slot) { - if (!pcb) { - return ERR_CONN; - } - tcp_api_call_t msg; - msg.pcb = pcb; - msg.closed_slot = closed_slot; - tcpip_api_call(_tcp_abort_api, (struct tcpip_api_call_data *)&msg); - return msg.err; -} - -static err_t _tcp_connect_api(struct tcpip_api_call_data * api_call_msg) { - tcp_api_call_t * msg = (tcp_api_call_t *)api_call_msg; - msg->err = tcp_connect(msg->pcb, msg->connect.addr, msg->connect.port, msg->connect.cb); - return msg->err; -} - -static esp_err_t _tcp_connect(tcp_pcb * pcb, int8_t closed_slot, ip_addr_t * addr, uint16_t port, tcp_connected_fn cb) { - if (!pcb) { - return ESP_FAIL; - } - tcp_api_call_t msg; - msg.pcb = pcb; - msg.closed_slot = closed_slot; - msg.connect.addr = addr; - msg.connect.port = port; - msg.connect.cb = cb; - tcpip_api_call(_tcp_connect_api, (struct tcpip_api_call_data *)&msg); - return msg.err; -} - -static err_t _tcp_bind_api(struct tcpip_api_call_data * api_call_msg) { - tcp_api_call_t * msg = (tcp_api_call_t *)api_call_msg; - msg->err = tcp_bind(msg->pcb, msg->bind.addr, msg->bind.port); - return msg->err; -} - -static esp_err_t _tcp_bind(tcp_pcb * pcb, ip_addr_t * addr, uint16_t port) { - if (!pcb) { - return ESP_FAIL; - } - tcp_api_call_t msg; - msg.pcb = pcb; - msg.closed_slot = -1; - msg.bind.addr = addr; - msg.bind.port = port; - tcpip_api_call(_tcp_bind_api, (struct tcpip_api_call_data *)&msg); - return msg.err; -} - -static err_t _tcp_listen_api(struct tcpip_api_call_data * api_call_msg) { - tcp_api_call_t * msg = (tcp_api_call_t *)api_call_msg; - msg->err = 0; - msg->pcb = tcp_listen_with_backlog(msg->pcb, msg->backlog); - return msg->err; -} - -static tcp_pcb * _tcp_listen_with_backlog(tcp_pcb * pcb, uint8_t backlog) { - if (!pcb) { - return NULL; - } - tcp_api_call_t msg; - msg.pcb = pcb; - msg.closed_slot = -1; - msg.backlog = backlog ? backlog : 0xFF; - tcpip_api_call(_tcp_listen_api, (struct tcpip_api_call_data *)&msg); - return msg.pcb; -} - - - -/* - Async TCP Client - */ - -AsyncClient::AsyncClient(tcp_pcb * pcb) - : _connect_cb(0) - , _connect_cb_arg(0) - , _discard_cb(0) - , _discard_cb_arg(0) - , _sent_cb(0) - , _sent_cb_arg(0) - , _error_cb(0) - , _error_cb_arg(0) - , _recv_cb(0) - , _recv_cb_arg(0) - , _pb_cb(0) - , _pb_cb_arg(0) - , _timeout_cb(0) - , _timeout_cb_arg(0) - , _pcb_busy(false) - , _pcb_sent_at(0) - , _ack_pcb(true) - , _rx_last_packet(0) - , _rx_since_timeout(0) - , _ack_timeout(ASYNC_MAX_ACK_TIME) - , _connect_port(0) - , prev(NULL) - , next(NULL) { - _pcb = pcb; - _closed_slot = -1; - if (_pcb) { - _allocate_closed_slot(); - _rx_last_packet = millis(); - tcp_arg(_pcb, this); - tcp_recv(_pcb, &_tcp_recv); - tcp_sent(_pcb, &_tcp_sent); - tcp_err(_pcb, &_tcp_error); - tcp_poll(_pcb, &_tcp_poll, 1); - } -} - -AsyncClient::~AsyncClient() { - if (_pcb) { - _close(); - } - _free_closed_slot(); -} - -/* - * Operators - * */ - -AsyncClient & AsyncClient::operator=(const AsyncClient & other) { - if (_pcb) { - _close(); - } - - _pcb = other._pcb; - _closed_slot = other._closed_slot; - if (_pcb) { - _rx_last_packet = millis(); - tcp_arg(_pcb, this); - tcp_recv(_pcb, &_tcp_recv); - tcp_sent(_pcb, &_tcp_sent); - tcp_err(_pcb, &_tcp_error); - tcp_poll(_pcb, &_tcp_poll, 1); - } - return *this; -} - -bool AsyncClient::operator==(const AsyncClient & other) { - return _pcb == other._pcb; -} - -AsyncClient & AsyncClient::operator+=(const AsyncClient & other) { - if (next == NULL) { - next = (AsyncClient *)(&other); - next->prev = this; - } else { - AsyncClient * c = next; - while (c->next != NULL) { - c = c->next; - } - c->next = (AsyncClient *)(&other); - c->next->prev = c; - } - return *this; -} - -/* - * Callback Setters - * */ - -void AsyncClient::onConnect(AcConnectHandler cb, void * arg) { - _connect_cb = cb; - _connect_cb_arg = arg; -} - -void AsyncClient::onDisconnect(AcConnectHandler cb, void * arg) { - _discard_cb = cb; - _discard_cb_arg = arg; -} - -void AsyncClient::onAck(AcAckHandler cb, void * arg) { - _sent_cb = cb; - _sent_cb_arg = arg; -} - -void AsyncClient::onError(AcErrorHandler cb, void * arg) { - _error_cb = cb; - _error_cb_arg = arg; -} - -void AsyncClient::onData(AcDataHandler cb, void * arg) { - _recv_cb = cb; - _recv_cb_arg = arg; -} - -void AsyncClient::onPacket(AcPacketHandler cb, void * arg) { - _pb_cb = cb; - _pb_cb_arg = arg; -} - -void AsyncClient::onTimeout(AcTimeoutHandler cb, void * arg) { - _timeout_cb = cb; - _timeout_cb_arg = arg; -} - -void AsyncClient::onPoll(AcConnectHandler cb, void * arg) { - _poll_cb = cb; - _poll_cb_arg = arg; -} - -/* - * Main Public Methods - * */ - -bool AsyncClient::_connect(ip_addr_t addr, uint16_t port) { - if (_pcb) { - log_w("already connected, state %d", _pcb->state); - return false; - } - if (!_start_async_task()) { - log_e("failed to start task"); - return false; - } - - tcp_pcb * pcb = tcp_new_ip_type(addr.type); - if (!pcb) { - log_e("pcb == NULL"); - return false; - } - - tcp_arg(pcb, this); - tcp_err(pcb, &_tcp_error); - tcp_recv(pcb, &_tcp_recv); - tcp_sent(pcb, &_tcp_sent); - tcp_poll(pcb, &_tcp_poll, 1); - _tcp_connect(pcb, _closed_slot, &addr, port, (tcp_connected_fn)&_tcp_connected); - return true; -} - -bool AsyncClient::connect(IPAddress ip, uint16_t port) { - ip_addr_t addr; - addr.type = IPADDR_TYPE_V4; - addr.u_addr.ip4.addr = ip; - - return _connect(addr, port); -} - -bool AsyncClient::connect(IPv6Address ip, uint16_t port) { - ip_addr_t addr; - addr.type = IPADDR_TYPE_V6; - memcpy(addr.u_addr.ip6.addr, static_cast(ip), sizeof(uint32_t) * 4); - - return _connect(addr, port); -} - -bool AsyncClient::connect(const char * host, uint16_t port) { - ip_addr_t addr; - - if (!_start_async_task()) { - log_e("failed to start task"); - return false; - } - - err_t err = dns_gethostbyname(host, &addr, (dns_found_callback)&_tcp_dns_found, this); - if (err == ERR_OK) { - if (addr.type == IPADDR_TYPE_V6) { - return connect(IPv6Address(addr.u_addr.ip6.addr), port); - } - return connect(IPAddress(addr.u_addr.ip4.addr), port); - } else if (err == ERR_INPROGRESS) { - _connect_port = port; - return true; - } - log_e("error: %d", err); - return false; -} - -void AsyncClient::close(bool now) { - if (_pcb) { - _tcp_recved(_pcb, _closed_slot, _rx_ack_len); - } - _close(); -} - -int8_t AsyncClient::abort() { - if (_pcb) { - _tcp_abort(_pcb, _closed_slot); - _pcb = NULL; - } - return ERR_ABRT; -} - -size_t AsyncClient::space() { - if ((_pcb != NULL) && (_pcb->state == 4)) { - return tcp_sndbuf(_pcb); - } - return 0; -} - -size_t AsyncClient::add(const char * data, size_t size, uint8_t apiflags) { - if (!_pcb || size == 0 || data == NULL) { - return 0; - } - size_t room = space(); - if (!room) { - return 0; - } - size_t will_send = (room < size) ? room : size; - int8_t err = ERR_OK; - err = _tcp_write(_pcb, _closed_slot, data, will_send, apiflags); - if (err != ERR_OK) { - return 0; - } - return will_send; -} - -bool AsyncClient::send() { - int8_t err = ERR_OK; - err = _tcp_output(_pcb, _closed_slot); - if (err == ERR_OK) { - _pcb_busy = true; - _pcb_sent_at = millis(); - return true; - } - return false; -} - -size_t AsyncClient::ack(size_t len) { - if (len > _rx_ack_len) - len = _rx_ack_len; - if (len) { - _tcp_recved(_pcb, _closed_slot, len); - } - _rx_ack_len -= len; - return len; -} - -void AsyncClient::ackPacket(struct pbuf * pb) { - if (!pb) { - return; - } - _tcp_recved(_pcb, _closed_slot, pb->len); - pbuf_free(pb); -} - -/* - * Main Private Methods - * */ - -int8_t AsyncClient::_close() { - //ets_printf("X: 0x%08x\n", (uint32_t)this); - int8_t err = ERR_OK; - if (_pcb) { - //log_i(""); - tcp_arg(_pcb, NULL); - tcp_sent(_pcb, NULL); - tcp_recv(_pcb, NULL); - tcp_err(_pcb, NULL); - tcp_poll(_pcb, NULL, 0); - _tcp_clear_events(this); - err = _tcp_close(_pcb, _closed_slot); - if (err != ERR_OK) { - err = abort(); - } - _pcb = NULL; - if (_discard_cb) { - _discard_cb(_discard_cb_arg, this); - } - } - return err; -} - -void AsyncClient::_allocate_closed_slot() { - xSemaphoreTake(_slots_lock, portMAX_DELAY); - uint32_t closed_slot_min_index = 0; - for (int i = 0; i < _number_of_closed_slots; ++i) { - if ((_closed_slot == -1 || _closed_slots[i] <= closed_slot_min_index) && _closed_slots[i] != 0) { - closed_slot_min_index = _closed_slots[i]; - _closed_slot = i; - } - } - if (_closed_slot != -1) { - _closed_slots[_closed_slot] = 0; - } - xSemaphoreGive(_slots_lock); -} - -void AsyncClient::_free_closed_slot() { - if (_closed_slot != -1) { - _closed_slots[_closed_slot] = _closed_index; - _closed_slot = -1; - ++_closed_index; - } -} - -/* - * Private Callbacks - * */ - -int8_t AsyncClient::_connected(void * pcb, int8_t err) { - _pcb = reinterpret_cast(pcb); - if (_pcb) { - _rx_last_packet = millis(); - _pcb_busy = false; - // tcp_recv(_pcb, &_tcp_recv); - // tcp_sent(_pcb, &_tcp_sent); - // tcp_poll(_pcb, &_tcp_poll, 1); - } - if (_connect_cb) { - _connect_cb(_connect_cb_arg, this); - } - return ERR_OK; -} - -void AsyncClient::_error(int8_t err) { - if (_pcb) { - tcp_arg(_pcb, NULL); - if (_pcb->state == LISTEN) { - tcp_sent(_pcb, NULL); - tcp_recv(_pcb, NULL); - tcp_err(_pcb, NULL); - tcp_poll(_pcb, NULL, 0); - } - _pcb = NULL; - } - if (_error_cb) { - _error_cb(_error_cb_arg, this, err); - } - if (_discard_cb) { - _discard_cb(_discard_cb_arg, this); - } -} - -//In LwIP Thread -int8_t AsyncClient::_lwip_fin(tcp_pcb * pcb, int8_t err) { - if (!_pcb || pcb != _pcb) { - log_e("0x%08x != 0x%08x", (uint32_t)pcb, (uint32_t)_pcb); - return ERR_OK; - } - tcp_arg(_pcb, NULL); - if (_pcb->state == LISTEN) { - tcp_sent(_pcb, NULL); - tcp_recv(_pcb, NULL); - tcp_err(_pcb, NULL); - tcp_poll(_pcb, NULL, 0); - } - if (tcp_close(_pcb) != ERR_OK) { - tcp_abort(_pcb); - } - _free_closed_slot(); - _pcb = NULL; - return ERR_OK; -} - -//In Async Thread -int8_t AsyncClient::_fin(tcp_pcb * pcb, int8_t err) { - _tcp_clear_events(this); - if (_discard_cb) { - _discard_cb(_discard_cb_arg, this); - } - return ERR_OK; -} - -int8_t AsyncClient::_sent(tcp_pcb * pcb, uint16_t len) { - _rx_last_packet = millis(); - //log_i("%u", len); - _pcb_busy = false; - if (_sent_cb) { - _sent_cb(_sent_cb_arg, this, len, (millis() - _pcb_sent_at)); - } - return ERR_OK; -} - -int8_t AsyncClient::_recv(tcp_pcb * pcb, pbuf * pb, int8_t err) { - while (pb != NULL) { - _rx_last_packet = millis(); - //we should not ack before we assimilate the data - _ack_pcb = true; - pbuf * b = pb; - pb = b->next; - b->next = NULL; - if (_pb_cb) { - _pb_cb(_pb_cb_arg, this, b); - } else { - if (_recv_cb) { - _recv_cb(_recv_cb_arg, this, b->payload, b->len); - } - if (!_ack_pcb) { - _rx_ack_len += b->len; - } else if (_pcb) { - _tcp_recved(_pcb, _closed_slot, b->len); - } - pbuf_free(b); - } - } - return ERR_OK; -} - -int8_t AsyncClient::_poll(tcp_pcb * pcb) { - if (!_pcb) { - log_w("pcb is NULL"); - return ERR_OK; - } - if (pcb != _pcb) { - log_e("0x%08x != 0x%08x", (uint32_t)pcb, (uint32_t)_pcb); - return ERR_OK; - } - - uint32_t now = millis(); - - // ACK Timeout - if (_pcb_busy && _ack_timeout && (now - _pcb_sent_at) >= _ack_timeout) { - _pcb_busy = false; - log_w("ack timeout %d", pcb->state); - if (_timeout_cb) - _timeout_cb(_timeout_cb_arg, this, (now - _pcb_sent_at)); - return ERR_OK; - } - // RX Timeout - if (_rx_since_timeout && (now - _rx_last_packet) >= (_rx_since_timeout * 1000)) { - log_w("rx timeout %d", pcb->state); - _close(); - return ERR_OK; - } - // Everything is fine - if (_poll_cb) { - _poll_cb(_poll_cb_arg, this); - } - return ERR_OK; -} - -void AsyncClient::_dns_found(struct ip_addr * ipaddr) { - if (ipaddr && ipaddr->u_addr.ip4.addr) { - connect(IPAddress(ipaddr->u_addr.ip4.addr), _connect_port); - } else if (ipaddr && ipaddr->u_addr.ip6.addr) { - connect(IPv6Address(ipaddr->u_addr.ip6.addr), _connect_port); - } else { - if (_error_cb) { - _error_cb(_error_cb_arg, this, -55); - } - if (_discard_cb) { - _discard_cb(_discard_cb_arg, this); - } - } -} - -/* - * Public Helper Methods - * */ - -void AsyncClient::stop() { - close(false); -} - -bool AsyncClient::free() { - if (!_pcb) { - return true; - } - if (_pcb->state == 0 || _pcb->state > 4) { - return true; - } - return false; -} - -size_t AsyncClient::write(const char * data) { - if (data == NULL) { - return 0; - } - return write(data, strlen(data)); -} - -size_t AsyncClient::write(const char * data, size_t size, uint8_t apiflags) { - size_t will_send = add(data, size, apiflags); - if (!will_send) { - return 0; - } - while (connected() && !send()) { - taskYIELD(); - } - return will_send; -} - -void AsyncClient::setRxTimeout(uint32_t timeout) { - _rx_since_timeout = timeout; -} - -uint32_t AsyncClient::getRxTimeout() { - return _rx_since_timeout; -} - -uint32_t AsyncClient::getAckTimeout() { - return _ack_timeout; -} - -void AsyncClient::setAckTimeout(uint32_t timeout) { - _ack_timeout = timeout; -} - -void AsyncClient::setNoDelay(bool nodelay) { - if (!_pcb) { - return; - } - if (nodelay) { - tcp_nagle_disable(_pcb); - } else { - tcp_nagle_enable(_pcb); - } -} - -bool AsyncClient::getNoDelay() { - if (!_pcb) { - return false; - } - return tcp_nagle_disabled(_pcb); -} - -void AsyncClient::setKeepAlive(uint32_t ms, uint8_t cnt) { - if (ms != 0) { - _pcb->so_options |= SOF_KEEPALIVE; //Turn on TCP Keepalive for the given pcb - // Set the time between keepalive messages in milli-seconds - _pcb->keep_idle = ms; - _pcb->keep_intvl = ms; - _pcb->keep_cnt = cnt; //The number of unanswered probes required to force closure of the socket - } else { - _pcb->so_options &= ~SOF_KEEPALIVE; //Turn off TCP Keepalive for the given pcb - } -} - -uint16_t AsyncClient::getMss() { - if (!_pcb) { - return 0; - } - return tcp_mss(_pcb); -} - -uint32_t AsyncClient::getRemoteAddress() { - if (!_pcb) { - return 0; - } - return _pcb->remote_ip.u_addr.ip4.addr; -} - -ip6_addr_t AsyncClient::getRemoteAddress6() { - if (!_pcb) { - ip6_addr_t nulladdr; - ip6_addr_set_zero(&nulladdr); - return nulladdr; - } - return _pcb->remote_ip.u_addr.ip6; -} - -uint16_t AsyncClient::getRemotePort() { - if (!_pcb) { - return 0; - } - return _pcb->remote_port; -} - -uint32_t AsyncClient::getLocalAddress() { - if (!_pcb) { - return 0; - } - return _pcb->local_ip.u_addr.ip4.addr; -} - -ip6_addr_t AsyncClient::getLocalAddress6() { - if (!_pcb) { - ip6_addr_t nulladdr; - ip6_addr_set_zero(&nulladdr); - return nulladdr; - } - return _pcb->local_ip.u_addr.ip6; -} - -uint16_t AsyncClient::getLocalPort() { - if (!_pcb) { - return 0; - } - return _pcb->local_port; -} - -IPAddress AsyncClient::remoteIP() { - return IPAddress(getRemoteAddress()); -} - -IPv6Address AsyncClient::remoteIP6() { - return IPv6Address(getRemoteAddress6().addr); -} - -uint16_t AsyncClient::remotePort() { - return getRemotePort(); -} - -IPAddress AsyncClient::localIP() { - return IPAddress(getLocalAddress()); -} - -IPv6Address AsyncClient::localIP6() { - return IPv6Address(getLocalAddress6().addr); -} - -uint16_t AsyncClient::localPort() { - return getLocalPort(); -} - -uint8_t AsyncClient::state() { - if (!_pcb) { - return 0; - } - return _pcb->state; -} - -bool AsyncClient::connected() { - if (!_pcb) { - return false; - } - return _pcb->state == 4; -} - -bool AsyncClient::connecting() { - if (!_pcb) { - return false; - } - return _pcb->state > 0 && _pcb->state < 4; -} - -bool AsyncClient::disconnecting() { - if (!_pcb) { - return false; - } - return _pcb->state > 4 && _pcb->state < 10; -} - -bool AsyncClient::disconnected() { - if (!_pcb) { - return true; - } - return _pcb->state == 0 || _pcb->state == 10; -} - -bool AsyncClient::freeable() { - if (!_pcb) { - return true; - } - return _pcb->state == 0 || _pcb->state > 4; -} - -bool AsyncClient::canSend() { - return space() > 0; -} - -const char * AsyncClient::errorToString(int8_t error) { - switch (error) { - case ERR_OK: - return "OK"; - case ERR_MEM: - return "Out of memory error"; - case ERR_BUF: - return "Buffer error"; - case ERR_TIMEOUT: - return "Timeout"; - case ERR_RTE: - return "Routing problem"; - case ERR_INPROGRESS: - return "Operation in progress"; - case ERR_VAL: - return "Illegal value"; - case ERR_WOULDBLOCK: - return "Operation would block"; - case ERR_USE: - return "Address in use"; - case ERR_ALREADY: - return "Already connected"; - case ERR_CONN: - return "Not connected"; - case ERR_IF: - return "Low-level netif error"; - case ERR_ABRT: - return "Connection aborted"; - case ERR_RST: - return "Connection reset"; - case ERR_CLSD: - return "Connection closed"; - case ERR_ARG: - return "Illegal argument"; - case -55: - return "DNS failed"; - default: - return "UNKNOWN"; - } -} - -const char * AsyncClient::stateToString() { - switch (state()) { - case 0: - return "Closed"; - case 1: - return "Listen"; - case 2: - return "SYN Sent"; - case 3: - return "SYN Received"; - case 4: - return "Established"; - case 5: - return "FIN Wait 1"; - case 6: - return "FIN Wait 2"; - case 7: - return "Close Wait"; - case 8: - return "Closing"; - case 9: - return "Last ACK"; - case 10: - return "Time Wait"; - default: - return "UNKNOWN"; - } -} - -/* - * Static Callbacks (LwIP C2C++ interconnect) - * */ - -void AsyncClient::_s_dns_found(const char * name, struct ip_addr * ipaddr, void * arg) { - reinterpret_cast(arg)->_dns_found(ipaddr); -} - -int8_t AsyncClient::_s_poll(void * arg, struct tcp_pcb * pcb) { - return reinterpret_cast(arg)->_poll(pcb); -} - -int8_t AsyncClient::_s_recv(void * arg, struct tcp_pcb * pcb, struct pbuf * pb, int8_t err) { - return reinterpret_cast(arg)->_recv(pcb, pb, err); -} - -int8_t AsyncClient::_s_fin(void * arg, struct tcp_pcb * pcb, int8_t err) { - return reinterpret_cast(arg)->_fin(pcb, err); -} - -int8_t AsyncClient::_s_lwip_fin(void * arg, struct tcp_pcb * pcb, int8_t err) { - return reinterpret_cast(arg)->_lwip_fin(pcb, err); -} - -int8_t AsyncClient::_s_sent(void * arg, struct tcp_pcb * pcb, uint16_t len) { - return reinterpret_cast(arg)->_sent(pcb, len); -} - -void AsyncClient::_s_error(void * arg, int8_t err) { - reinterpret_cast(arg)->_error(err); -} - -int8_t AsyncClient::_s_connected(void * arg, void * pcb, int8_t err) { - return reinterpret_cast(arg)->_connected(pcb, err); -} - -/* - Async TCP Server - */ - -AsyncServer::AsyncServer(IPAddress addr, uint16_t port) - : _port(port) - , _bind4(true) - , _addr(addr) - , _noDelay(false) - , _pcb(0) - , _connect_cb(0) - , _connect_cb_arg(0) { -} - -AsyncServer::AsyncServer(IPv6Address addr, uint16_t port) - : _port(port) - , _bind6(true) - , _addr6(addr) - , _noDelay(false) - , _pcb(0) - , _connect_cb(0) - , _connect_cb_arg(0) { -} - -AsyncServer::AsyncServer(uint16_t port) - : _port(port) - , _bind4(true) - , _bind6(true) - , _addr((uint32_t)IPADDR_ANY) - , _addr6() - , _noDelay(false) - , _pcb(0) - , _connect_cb(0) - , _connect_cb_arg(0) { -} - -AsyncServer::~AsyncServer() { - end(); -} - -void AsyncServer::onClient(AcConnectHandler cb, void * arg) { - _connect_cb = cb; - _connect_cb_arg = arg; -} - -void AsyncServer::begin() { - if (_pcb) { - return; - } - - if (!_start_async_task()) { - log_e("failed to start task"); - return; - } - int8_t err, bind_type; - - if (_bind4 && _bind6) { - bind_type = IPADDR_TYPE_ANY; - } else if (_bind6) { - bind_type = IPADDR_TYPE_V6; - } else { - bind_type = IPADDR_TYPE_V4; - } - - _pcb = tcp_new_ip_type(bind_type); - if (!_pcb) { - log_e("_pcb == NULL"); - return; - } - - ip_addr_t local_addr; - local_addr.type = bind_type; - local_addr.u_addr.ip4.addr = (uint32_t)_addr; - memcpy(local_addr.u_addr.ip6.addr, static_cast(_addr6), sizeof(uint32_t) * 4); - err = _tcp_bind(_pcb, &local_addr, _port); - - if (err != ERR_OK) { - _tcp_close(_pcb, -1); - log_e("bind error: %d", err); - return; - } - - static uint8_t backlog = 5; - _pcb = _tcp_listen_with_backlog(_pcb, backlog); - if (!_pcb) { - log_e("listen_pcb == NULL"); - return; - } - tcp_arg(_pcb, (void *)this); - tcp_accept(_pcb, &_s_accept); -} - -void AsyncServer::end() { - if (_pcb) { - tcp_arg(_pcb, NULL); - tcp_accept(_pcb, NULL); - if (tcp_close(_pcb) != ERR_OK) { - _tcp_abort(_pcb, -1); - } - _pcb = NULL; - } -} - -//runs on LwIP thread -int8_t AsyncServer::_accept(tcp_pcb * pcb, int8_t err) { - //ets_printf("+A: 0x%08x\n", pcb); - if (_connect_cb) { - AsyncClient * c = new AsyncClient(pcb); - if (c) { - c->setNoDelay(_noDelay); - return _tcp_accept(this, c); - } - } - if (tcp_close(pcb) != ERR_OK) { - tcp_abort(pcb); - } - log_e("FAIL"); - return ERR_OK; -} - -int8_t AsyncServer::_accepted(AsyncClient * client) { - if (_connect_cb) { - _connect_cb(_connect_cb_arg, client); - } - return ERR_OK; -} - -void AsyncServer::setNoDelay(bool nodelay) { - _noDelay = nodelay; -} - -bool AsyncServer::getNoDelay() { - return _noDelay; -} - -uint8_t AsyncServer::status() { - if (!_pcb) { - return 0; - } - return _pcb->state; -} - -int8_t AsyncServer::_s_accept(void * arg, tcp_pcb * pcb, int8_t err) { - return reinterpret_cast(arg)->_accept(pcb, err); -} - -int8_t AsyncServer::_s_accepted(void * arg, AsyncClient * client) { - return reinterpret_cast(arg)->_accepted(client); -} \ No newline at end of file diff --git a/lib/AsyncTCP/src/AsyncTCP.h b/lib/AsyncTCP/src/AsyncTCP.h deleted file mode 100644 index 412c62943..000000000 --- a/lib/AsyncTCP/src/AsyncTCP.h +++ /dev/null @@ -1,250 +0,0 @@ -/* - Asynchronous TCP library for Espressif MCUs - - Copyright (c) 2016 Hristo Gochkov. All rights reserved. - This file is part of the esp8266 core for Arduino environment. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#ifndef ASYNCTCP_H_ -#define ASYNCTCP_H_ - -#include "IPAddress.h" -#include "IPv6Address.h" -#include "sdkconfig.h" -#include -extern "C" { -#include "freertos/FreeRTOS.h" -#include "freertos/semphr.h" -#include "lwip/pbuf.h" -#include "lwip/ip_addr.h" -#include "lwip/ip6_addr.h" -} - -//If core is not defined, then we are running in Arduino or PIO -#ifndef CONFIG_ASYNC_TCP_RUNNING_CORE -#define CONFIG_ASYNC_TCP_RUNNING_CORE -1 //any available core -#define CONFIG_ASYNC_TCP_USE_WDT 0 //if enabled, adds between 33us and 200us per event -#endif - -#ifndef CONFIG_ASYNC_TCP_TASK_PRIORITY -#define CONFIG_ASYNC_TCP_TASK_PRIORITY 5 -#endif - -#ifndef CONFIG_ASYNC_TCP_STACK -#define CONFIG_ASYNC_TCP_STACK 8192 -#endif - -#ifndef CONFIG_ASYNC_TCP_QUEUE -#define CONFIG_ASYNC_TCP_QUEUE 128 -#endif - -class AsyncClient; - -#define ASYNC_MAX_ACK_TIME 5000 -#define ASYNC_WRITE_FLAG_COPY 0x01 //will allocate new buffer to hold the data while sending (else will hold reference to the data given) -#define ASYNC_WRITE_FLAG_MORE 0x02 //will not send PSH flag, meaning that there should be more data to be sent before the application should react. - -typedef std::function AcConnectHandler; -typedef std::function AcAckHandler; -typedef std::function AcErrorHandler; -typedef std::function AcDataHandler; -typedef std::function AcPacketHandler; -typedef std::function AcTimeoutHandler; - -struct tcp_pcb; -struct ip_addr; - -class AsyncClient { - public: - AsyncClient(tcp_pcb * pcb = 0); - ~AsyncClient(); - - AsyncClient & operator=(const AsyncClient & other); - AsyncClient & operator+=(const AsyncClient & other); - - bool operator==(const AsyncClient & other); - - bool operator!=(const AsyncClient & other) { - return !(*this == other); - } - bool connect(IPAddress ip, uint16_t port); - bool connect(IPv6Address ip, uint16_t port); - bool connect(const char * host, uint16_t port); - void close(bool now = false); - void stop(); - int8_t abort(); - bool free(); - - bool canSend(); //ack is not pending - size_t space(); //space available in the TCP window - size_t add(const char * data, size_t size, uint8_t apiflags = ASYNC_WRITE_FLAG_COPY); //add for sending - bool send(); //send all data added with the method above - - //write equals add()+send() - size_t write(const char * data); - size_t write(const char * data, size_t size, uint8_t apiflags = ASYNC_WRITE_FLAG_COPY); //only when canSend() == true - - uint8_t state(); - bool connecting(); - bool connected(); - bool disconnecting(); - bool disconnected(); - bool freeable(); //disconnected or disconnecting - - uint16_t getMss(); - - uint32_t getRxTimeout(); - void setRxTimeout(uint32_t timeout); //no RX data timeout for the connection in seconds - - uint32_t getAckTimeout(); - void setAckTimeout(uint32_t timeout); //no ACK timeout for the last sent packet in milliseconds - - void setNoDelay(bool nodelay); - bool getNoDelay(); - - void setKeepAlive(uint32_t ms, uint8_t cnt); - - uint32_t getRemoteAddress(); - ip6_addr_t getRemoteAddress6(); - uint16_t getRemotePort(); - uint32_t getLocalAddress(); - ip6_addr_t getLocalAddress6(); - uint16_t getLocalPort(); - - //compatibility - IPAddress remoteIP(); - IPv6Address remoteIP6(); - uint16_t remotePort(); - IPAddress localIP(); - IPv6Address localIP6(); - uint16_t localPort(); - - void onConnect(AcConnectHandler cb, void * arg = 0); //on successful connect - void onDisconnect(AcConnectHandler cb, void * arg = 0); //disconnected - void onAck(AcAckHandler cb, void * arg = 0); //ack received - void onError(AcErrorHandler cb, void * arg = 0); //unsuccessful connect or error - void onData(AcDataHandler cb, void * arg = 0); //data received (called if onPacket is not used) - void onPacket(AcPacketHandler cb, void * arg = 0); //data received - void onTimeout(AcTimeoutHandler cb, void * arg = 0); //ack timeout - void onPoll(AcConnectHandler cb, void * arg = 0); //every 125ms when connected - - void ackPacket(struct pbuf * pb); //ack pbuf from onPacket - size_t ack(size_t len); //ack data that you have not acked using the method below - void ackLater() { - _ack_pcb = false; - } //will not ack the current packet. Call from onData - - const char * errorToString(int8_t error); - const char * stateToString(); - - //Do not use any of the functions below! - static int8_t _s_poll(void * arg, struct tcp_pcb * tpcb); - static int8_t _s_recv(void * arg, struct tcp_pcb * tpcb, struct pbuf * pb, int8_t err); - static int8_t _s_fin(void * arg, struct tcp_pcb * tpcb, int8_t err); - static int8_t _s_lwip_fin(void * arg, struct tcp_pcb * tpcb, int8_t err); - static void _s_error(void * arg, int8_t err); - static int8_t _s_sent(void * arg, struct tcp_pcb * tpcb, uint16_t len); - static int8_t _s_connected(void * arg, void * tpcb, int8_t err); - static void _s_dns_found(const char * name, struct ip_addr * ipaddr, void * arg); - - int8_t _recv(tcp_pcb * pcb, pbuf * pb, int8_t err); - tcp_pcb * pcb() { - return _pcb; - } - - protected: - bool _connect(ip_addr_t addr, uint16_t port); - - tcp_pcb * _pcb; - int8_t _closed_slot; - - AcConnectHandler _connect_cb; - void * _connect_cb_arg; - AcConnectHandler _discard_cb; - void * _discard_cb_arg; - AcAckHandler _sent_cb; - void * _sent_cb_arg; - AcErrorHandler _error_cb; - void * _error_cb_arg; - AcDataHandler _recv_cb; - void * _recv_cb_arg; - AcPacketHandler _pb_cb; - void * _pb_cb_arg; - AcTimeoutHandler _timeout_cb; - void * _timeout_cb_arg; - AcConnectHandler _poll_cb; - void * _poll_cb_arg; - - bool _pcb_busy; - uint32_t _pcb_sent_at; - bool _ack_pcb; - uint32_t _rx_ack_len; - uint32_t _rx_last_packet; - uint32_t _rx_since_timeout; - uint32_t _ack_timeout; - uint16_t _connect_port; - - int8_t _close(); - void _free_closed_slot(); - void _allocate_closed_slot(); - int8_t _connected(void * pcb, int8_t err); - void _error(int8_t err); - int8_t _poll(tcp_pcb * pcb); - int8_t _sent(tcp_pcb * pcb, uint16_t len); - int8_t _fin(tcp_pcb * pcb, int8_t err); - int8_t _lwip_fin(tcp_pcb * pcb, int8_t err); - void _dns_found(struct ip_addr * ipaddr); - - public: - AsyncClient * prev; - AsyncClient * next; -}; - -class AsyncServer { - public: - AsyncServer(IPAddress addr, uint16_t port); - AsyncServer(IPv6Address addr, uint16_t port); - AsyncServer(uint16_t port); - ~AsyncServer(); - void onClient(AcConnectHandler cb, void * arg); - void begin(); - void end(); - void setNoDelay(bool nodelay); - bool getNoDelay(); - uint8_t status(); - - //Do not use any of the functions below! - static int8_t _s_accept(void * arg, tcp_pcb * newpcb, int8_t err); - static int8_t _s_accepted(void * arg, AsyncClient * client); - - protected: - uint16_t _port; - bool _bind4 = false; - bool _bind6 = false; - IPAddress _addr; - IPv6Address _addr6; - bool _noDelay; - tcp_pcb * _pcb; - AcConnectHandler _connect_cb; - void * _connect_cb_arg; - - int8_t _accept(tcp_pcb * newpcb, int8_t err); - int8_t _accepted(AsyncClient * client); -}; - - -#endif /* ASYNCTCP_H_ */ \ No newline at end of file diff --git a/lib/ESPAsyncWebServer/AsyncEventSource.cpp b/lib/ESPAsyncWebServer/AsyncEventSource.cpp deleted file mode 100644 index e6ae38d69..000000000 --- a/lib/ESPAsyncWebServer/AsyncEventSource.cpp +++ /dev/null @@ -1,369 +0,0 @@ -/* - Asynchronous WebServer library for Espressif MCUs - - Copyright (c) 2016 Hristo Gochkov. All rights reserved. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ -#include "Arduino.h" -#include "AsyncEventSource.h" - -static String generateEventMessage(const char *message, const char *event, uint32_t id, uint32_t reconnect){ - String ev; - - if(reconnect){ - ev += F("retry: "); - ev += reconnect; - ev += F("\r\n"); - } - - if(id){ - ev += F("id: "); - ev += String(id); - ev += F("\r\n"); - } - - if(event != NULL){ - ev += F("event: "); - ev += String(event); - ev += F("\r\n"); - } - - if(message != NULL){ - size_t messageLen = strlen(message); - char * lineStart = (char *)message; - char * lineEnd; - do { - char * nextN = strchr(lineStart, '\n'); - char * nextR = strchr(lineStart, '\r'); - if(nextN == NULL && nextR == NULL){ - size_t llen = ((char *)message + messageLen) - lineStart; - char * ldata = (char *)malloc(llen+1); - if(ldata != NULL){ - memcpy(ldata, lineStart, llen); - ldata[llen] = 0; - ev += F("data: "); - ev += ldata; - ev += F("\r\n\r\n"); - free(ldata); - } - lineStart = (char *)message + messageLen; - } else { - char * nextLine = NULL; - if(nextN != NULL && nextR != NULL){ - if(nextR < nextN){ - lineEnd = nextR; - if(nextN == (nextR + 1)) - nextLine = nextN + 1; - else - nextLine = nextR + 1; - } else { - lineEnd = nextN; - if(nextR == (nextN + 1)) - nextLine = nextR + 1; - else - nextLine = nextN + 1; - } - } else if(nextN != NULL){ - lineEnd = nextN; - nextLine = nextN + 1; - } else { - lineEnd = nextR; - nextLine = nextR + 1; - } - - size_t llen = lineEnd - lineStart; - char * ldata = (char *)malloc(llen+1); - if(ldata != NULL){ - memcpy(ldata, lineStart, llen); - ldata[llen] = 0; - ev += F("data: "); - ev += ldata; - ev += F("\r\n"); - free(ldata); - } - lineStart = nextLine; - if(lineStart == ((char *)message + messageLen)) - ev += F("\r\n"); - } - } while(lineStart < ((char *)message + messageLen)); - } - - return ev; -} - -// Message - -AsyncEventSourceMessage::AsyncEventSourceMessage(const char * data, size_t len) -: _data(nullptr), _len(len), _sent(0), _acked(0) -{ - _data = (uint8_t*)malloc(_len+1); - if(_data == nullptr){ - _len = 0; - } else { - memcpy(_data, data, len); - _data[_len] = 0; - } -} - -AsyncEventSourceMessage::~AsyncEventSourceMessage() { - if(_data != NULL) - free(_data); -} - -size_t AsyncEventSourceMessage::ack(size_t len, uint32_t time) { - (void)time; - // If the whole message is now acked... - if(_acked + len > _len){ - // Return the number of extra bytes acked (they will be carried on to the next message) - const size_t extra = _acked + len - _len; - _acked = _len; - return extra; - } - // Return that no extra bytes left. - _acked += len; - return 0; -} - -size_t AsyncEventSourceMessage::send(AsyncClient *client) { - const size_t len = _len - _sent; - if(client->space() < len){ - return 0; - } - size_t sent = client->add((const char *)_data, len); - if(client->canSend()) - client->send(); - _sent += sent; - return sent; -} - -// Client - -AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server) -: _messageQueue(LinkedList([](AsyncEventSourceMessage *m){ delete m; })) -{ - _client = request->client(); - _server = server; - _lastId = 0; - if(request->hasHeader(F("Last-Event-ID"))) - _lastId = atoi(request->getHeader(F("Last-Event-ID"))->value().c_str()); - - _client->setRxTimeout(0); - _client->onError(NULL, NULL); - _client->onAck([](void *r, AsyncClient* c, size_t len, uint32_t time){ (void)c; ((AsyncEventSourceClient*)(r))->_onAck(len, time); }, this); - _client->onPoll([](void *r, AsyncClient* c){ (void)c; ((AsyncEventSourceClient*)(r))->_onPoll(); }, this); - _client->onData(NULL, NULL); - _client->onTimeout([this](void *r, AsyncClient* c __attribute__((unused)), uint32_t time){ ((AsyncEventSourceClient*)(r))->_onTimeout(time); }, this); - _client->onDisconnect([this](void *r, AsyncClient* c){ ((AsyncEventSourceClient*)(r))->_onDisconnect(); delete c; }, this); - - _server->_addClient(this); - delete request; -} - -AsyncEventSourceClient::~AsyncEventSourceClient(){ - _messageQueue.free(); - close(); -} - -void AsyncEventSourceClient::_queueMessage(AsyncEventSourceMessage *dataMessage){ - if(dataMessage == NULL) - return; - if(!connected()){ - delete dataMessage; - return; - } - if(_messageQueue.length() >= SSE_MAX_QUEUED_MESSAGES){ - // ets_printf(String(F("ERROR: Too many messages queued\n")).c_str()); - delete dataMessage; - } else { - _messageQueue.add(dataMessage); - } - if(_client->canSend()) - _runQueue(); -} - -void AsyncEventSourceClient::_onAck(size_t len, uint32_t time){ - while(len && !_messageQueue.isEmpty()){ - len = _messageQueue.front()->ack(len, time); - if(_messageQueue.front()->finished()) - _messageQueue.remove(_messageQueue.front()); - } - - _runQueue(); -} - -void AsyncEventSourceClient::_onPoll(){ - if(!_messageQueue.isEmpty()){ - _runQueue(); - } -} - - -void AsyncEventSourceClient::_onTimeout(uint32_t time __attribute__((unused))){ - _client->close(true); -} - -void AsyncEventSourceClient::_onDisconnect(){ - _client = NULL; - _server->_handleDisconnect(this); -} - -void AsyncEventSourceClient::close(){ - if(_client != NULL) - _client->close(); -} - -void AsyncEventSourceClient::write(const char * message, size_t len){ - _queueMessage(new AsyncEventSourceMessage(message, len)); -} - -void AsyncEventSourceClient::send(const char *message, const char *event, uint32_t id, uint32_t reconnect){ - String ev = generateEventMessage(message, event, id, reconnect); - _queueMessage(new AsyncEventSourceMessage(ev.c_str(), ev.length())); -} - -void AsyncEventSourceClient::_runQueue(){ - while(!_messageQueue.isEmpty() && _messageQueue.front()->finished()){ - _messageQueue.remove(_messageQueue.front()); - } - - for(auto i = _messageQueue.begin(); i != _messageQueue.end(); ++i) - { - if(!(*i)->sent()) - (*i)->send(_client); - } -} - - -// Handler - -AsyncEventSource::AsyncEventSource(const String& url) - : _url(url) - , _clients(LinkedList([](AsyncEventSourceClient *c){ delete c; })) - , _connectcb(NULL) -{} - -AsyncEventSource::~AsyncEventSource(){ - close(); -} - -void AsyncEventSource::onConnect(ArEventHandlerFunction cb){ - _connectcb = cb; -} - -void AsyncEventSource::_addClient(AsyncEventSourceClient * client){ - /*char * temp = (char *)malloc(2054); - if(temp != NULL){ - memset(temp+1,' ',2048); - temp[0] = ':'; - temp[2049] = '\r'; - temp[2050] = '\n'; - temp[2051] = '\r'; - temp[2052] = '\n'; - temp[2053] = 0; - client->write((const char *)temp, 2053); - free(temp); - }*/ - - _clients.add(client); - if(_connectcb) - _connectcb(client); -} - -void AsyncEventSource::_handleDisconnect(AsyncEventSourceClient * client){ - _clients.remove(client); -} - -void AsyncEventSource::close(){ - for(const auto &c: _clients){ - if(c->connected()) - c->close(); - } -} - -// pmb fix -size_t AsyncEventSource::avgPacketsWaiting() const { - if(_clients.isEmpty()) - return 0; - - size_t aql=0; - uint32_t nConnectedClients=0; - - for(const auto &c: _clients){ - if(c->connected()) { - aql+=c->packetsWaiting(); - ++nConnectedClients; - } - } -// return aql / nConnectedClients; - return ((aql) + (nConnectedClients/2))/(nConnectedClients); // round up -} - -void AsyncEventSource::send(const char *message, const char *event, uint32_t id, uint32_t reconnect){ - - - String ev = generateEventMessage(message, event, id, reconnect); - for(const auto &c: _clients){ - if(c->connected()) { - c->write(ev.c_str(), ev.length()); - } - } -} - -size_t AsyncEventSource::count() const { - return _clients.count_if([](AsyncEventSourceClient *c){ - return c->connected(); - }); -} - -bool AsyncEventSource::canHandle(AsyncWebServerRequest *request){ - if(request->method() != HTTP_GET || !request->url().equals(_url)) { - return false; - } - request->addInterestingHeader(F("Last-Event-ID")); - return true; -} - -void AsyncEventSource::handleRequest(AsyncWebServerRequest *request){ - if((_username.length() && _password.length()) && !request->authenticate(_username.c_str(), _password.c_str())) { - return request->requestAuthentication(); - } - request->send(new AsyncEventSourceResponse(this)); -} - -// Response - -AsyncEventSourceResponse::AsyncEventSourceResponse(AsyncEventSource *server){ - _server = server; - _code = 200; - _contentType = F("text/event-stream"); - _sendContentLength = false; - addHeader(F("Cache-Control"), F("no-cache")); - addHeader(F("Connection"), F("keep-alive")); -} - -void AsyncEventSourceResponse::_respond(AsyncWebServerRequest *request){ - String out = _assembleHead(request->version()); - request->client()->write(out.c_str(), _headLength); - _state = RESPONSE_WAIT_ACK; -} - -size_t AsyncEventSourceResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time __attribute__((unused))){ - if(len){ - new AsyncEventSourceClient(request, _server); - } - return 0; -} - diff --git a/lib/ESPAsyncWebServer/AsyncEventSource.h b/lib/ESPAsyncWebServer/AsyncEventSource.h deleted file mode 100644 index b097fa623..000000000 --- a/lib/ESPAsyncWebServer/AsyncEventSource.h +++ /dev/null @@ -1,133 +0,0 @@ -/* - Asynchronous WebServer library for Espressif MCUs - - Copyright (c) 2016 Hristo Gochkov. All rights reserved. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ -#ifndef ASYNCEVENTSOURCE_H_ -#define ASYNCEVENTSOURCE_H_ - -#include -#ifdef ESP32 -#include -#define SSE_MAX_QUEUED_MESSAGES 32 -#else -#include -#define SSE_MAX_QUEUED_MESSAGES 8 -#endif -#include - -#include "AsyncWebSynchronization.h" - -#ifdef ESP8266 -#include -#ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library -#include <../src/Hash.h> -#endif -#endif - -#ifdef ESP32 -#define DEFAULT_MAX_SSE_CLIENTS 8 -#else -#define DEFAULT_MAX_SSE_CLIENTS 4 -#endif - -class AsyncEventSource; -class AsyncEventSourceResponse; -class AsyncEventSourceClient; -typedef std::function ArEventHandlerFunction; - -class AsyncEventSourceMessage { - private: - uint8_t * _data; - size_t _len; - size_t _sent; - //size_t _ack; - size_t _acked; - public: - AsyncEventSourceMessage(const char * data, size_t len); - ~AsyncEventSourceMessage(); - size_t ack(size_t len, uint32_t time __attribute__((unused))); - size_t send(AsyncClient *client); - bool finished(){ return _acked == _len; } - bool sent() { return _sent == _len; } -}; - -class AsyncEventSourceClient { - private: - AsyncClient *_client; - AsyncEventSource *_server; - uint32_t _lastId; - LinkedList _messageQueue; - void _queueMessage(AsyncEventSourceMessage *dataMessage); - void _runQueue(); - - public: - - AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server); - ~AsyncEventSourceClient(); - - AsyncClient* client(){ return _client; } - void close(); - void write(const char * message, size_t len); - void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0); - bool connected() const { return (_client != NULL) && _client->connected(); } - uint32_t lastId() const { return _lastId; } - size_t packetsWaiting() const { return _messageQueue.length(); } - - //system callbacks (do not call) - void _onAck(size_t len, uint32_t time); - void _onPoll(); - void _onTimeout(uint32_t time); - void _onDisconnect(); -}; - -class AsyncEventSource: public AsyncWebHandler { - private: - String _url; - LinkedList _clients; - ArEventHandlerFunction _connectcb; - public: - AsyncEventSource(const String& url); - ~AsyncEventSource(); - - const char * url() const { return _url.c_str(); } - void close(); - void onConnect(ArEventHandlerFunction cb); - void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0); - size_t count() const; //number clinets connected - size_t avgPacketsWaiting() const; - - //system callbacks (do not call) - void _addClient(AsyncEventSourceClient * client); - void _handleDisconnect(AsyncEventSourceClient * client); - virtual bool canHandle(AsyncWebServerRequest *request) override final; - virtual void handleRequest(AsyncWebServerRequest *request) override final; -}; - -class AsyncEventSourceResponse: public AsyncWebServerResponse { - private: - String _content; - AsyncEventSource *_server; - public: - AsyncEventSourceResponse(AsyncEventSource *server); - void _respond(AsyncWebServerRequest *request); - size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); - bool _sourceValid() const { return true; } -}; - - -#endif /* ASYNCEVENTSOURCE_H_ */ diff --git a/lib/ESPAsyncWebServer/AsyncJson.h b/lib/ESPAsyncWebServer/AsyncJson.h deleted file mode 100644 index 1f7af7356..000000000 --- a/lib/ESPAsyncWebServer/AsyncJson.h +++ /dev/null @@ -1,254 +0,0 @@ -// AsyncJson.h -/* - Async Response to use with ArduinoJson and AsyncWebServer - Written by Andrew Melvin (SticilFace) with help from me-no-dev and BBlanchon. -*/ - -#ifndef ASYNC_JSON_H_ -#define ASYNC_JSON_H_ -#include -#include -#include - -#define DYNAMIC_JSON_DOCUMENT_SIZE 1024 - -constexpr const char * JSON_MIMETYPE = "application/json"; - -/* - * Json Response - */ - -class ChunkPrint : public Print { - private: - uint8_t * _destination; - size_t _to_skip; - size_t _to_write; - size_t _pos; - - public: - ChunkPrint(uint8_t * destination, size_t from, size_t len) - : _destination(destination) - , _to_skip(from) - , _to_write(len) - , _pos{0} { - } - virtual ~ChunkPrint() { - } - size_t write(uint8_t c) { - if (_to_skip > 0) { - _to_skip--; - return 1; - } else if (_to_write > 0) { - _to_write--; - _destination[_pos++] = c; - return 1; - } - return 0; - } - size_t write(const uint8_t * buffer, size_t size) { - return this->Print::write(buffer, size); - } -}; - -// added by Proddy -class MsgpackAsyncJsonResponse : public AsyncAbstractResponse { - protected: - DynamicJsonDocument _jsonBuffer; - JsonVariant _root; - bool _isValid; - - public: - MsgpackAsyncJsonResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE) - : _jsonBuffer(maxJsonBufferSize) - , _isValid{false} { - _code = 200; - _contentType = JSON_MIMETYPE; - if (isArray) - _root = _jsonBuffer.createNestedArray(); - else - _root = _jsonBuffer.createNestedObject(); - } - - ~MsgpackAsyncJsonResponse() { - } - JsonVariant & getRoot() { - return _root; - } - bool _sourceValid() const { - return _isValid; - } - size_t setLength() { - _contentLength = measureMsgPack(_root); - // EMS-ESP - //_headers.add(new AsyncWebHeader("Json-Length", String(_jsonBuffer.memoryUsage()))); // For determining size of EMSESP_JSON_SIZE_XXLARGE (Sunbuzz) - // Json-Length: 10635 - if (_contentLength) { - _isValid = true; - } - return _contentLength; - } - - size_t getSize() { - return _jsonBuffer.size(); - } - - size_t _fillBuffer(uint8_t * data, size_t len) { - ChunkPrint dest(data, _sentLength, len); - serializeMsgPack(_root, dest); - return len; - } -}; - -class AsyncJsonResponse : public AsyncAbstractResponse { - protected: - DynamicJsonDocument _jsonBuffer; - - JsonVariant _root; - bool _isValid; - - public: - AsyncJsonResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE) - : _jsonBuffer(maxJsonBufferSize) - , _isValid{false} { - _code = 200; - _contentType = JSON_MIMETYPE; - if (isArray) - _root = _jsonBuffer.createNestedArray(); - else - _root = _jsonBuffer.createNestedObject(); - } - - ~AsyncJsonResponse() { - } - JsonVariant & getRoot() { - return _root; - } - bool _sourceValid() const { - return _isValid; - } - size_t setLength() { - _contentLength = measureJson(_root); - if (_contentLength) { - _isValid = true; - } - return _contentLength; - } - - size_t getSize() { - return _jsonBuffer.size(); - } - - size_t _fillBuffer(uint8_t * data, size_t len) { - ChunkPrint dest(data, _sentLength, len); - serializeJson(_root, dest); - return len; - } -}; - -class PrettyAsyncJsonResponse : public AsyncJsonResponse { - public: - PrettyAsyncJsonResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE) - : AsyncJsonResponse{isArray, maxJsonBufferSize} { - } - size_t setLength() { - _contentLength = measureJsonPretty(_root); - if (_contentLength) { - _isValid = true; - } - return _contentLength; - } - size_t _fillBuffer(uint8_t * data, size_t len) { - ChunkPrint dest(data, _sentLength, len); - serializeJsonPretty(_root, dest); - return len; - } -}; - -typedef std::function ArJsonRequestHandlerFunction; - -class AsyncCallbackJsonWebHandler : public AsyncWebHandler { - private: - protected: - const String _uri; - WebRequestMethodComposite _method; - ArJsonRequestHandlerFunction _onRequest; - size_t _contentLength; -#ifndef ARDUINOJSON_5_COMPATIBILITY - size_t _maxJsonBufferSize; -#endif - size_t _maxContentLength; - - public: - AsyncCallbackJsonWebHandler(const String & uri, ArJsonRequestHandlerFunction onRequest, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE) - : _uri(uri) - , _method(HTTP_POST | HTTP_PUT | HTTP_PATCH) - , _onRequest(onRequest) - , _maxJsonBufferSize(maxJsonBufferSize) - , _maxContentLength(16384) { - } - - void setMethod(WebRequestMethodComposite method) { - _method = method; - } - void setMaxContentLength(int maxContentLength) { - _maxContentLength = maxContentLength; - } - void setMaxJsonBufferSize(size_t maxJsonBufferSize) { - _maxJsonBufferSize = maxJsonBufferSize; - } - void onRequest(ArJsonRequestHandlerFunction fn) { - _onRequest = fn; - } - - virtual bool canHandle(AsyncWebServerRequest * request) override final { - if (!_onRequest) - return false; - - if (!(_method & request->method())) - return false; - - if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/"))) - return false; - - if (!request->contentType().equalsIgnoreCase(JSON_MIMETYPE)) - return false; - - request->addInterestingHeader("ANY"); - return true; - } - - virtual void handleRequest(AsyncWebServerRequest * request) override final { - if (_onRequest) { - if (request->_tempObject != NULL) { - DynamicJsonDocument jsonBuffer(this->_maxJsonBufferSize); - DeserializationError error = deserializeJson(jsonBuffer, (uint8_t *)(request->_tempObject)); - if (!error) { - JsonVariant json = jsonBuffer.as(); - _onRequest(request, json); - return; - } - } - request->send(_contentLength > _maxContentLength ? 413 : 400); - } else { - request->send(500); - } - } - virtual void handleUpload(AsyncWebServerRequest * request, const String & filename, size_t index, uint8_t * data, size_t len, bool final) override final { - } - virtual void handleBody(AsyncWebServerRequest * request, uint8_t * data, size_t len, size_t index, size_t total) override final { - if (_onRequest) { - _contentLength = total; - if (total > 0 && request->_tempObject == NULL && total < _maxContentLength) { - request->_tempObject = malloc(total); - } - if (request->_tempObject != NULL) { - memcpy((uint8_t *)(request->_tempObject) + index, data, len); - } - } - } - virtual bool isRequestHandlerTrivial() override final { - return _onRequest ? false : true; - } -}; - -#endif diff --git a/lib/ESPAsyncWebServer/AsyncWebSocket.cpp b/lib/ESPAsyncWebServer/AsyncWebSocket.cpp deleted file mode 100644 index 3b810ac60..000000000 --- a/lib/ESPAsyncWebServer/AsyncWebSocket.cpp +++ /dev/null @@ -1,1317 +0,0 @@ -/* - Asynchronous WebServer library for Espressif MCUs - - Copyright (c) 2016 Hristo Gochkov. All rights reserved. - This file is part of the esp8266 core for Arduino environment. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ -#include "Arduino.h" -#include "AsyncWebSocket.h" - -#include - -#ifndef ESP8266 -extern "C" { -typedef struct { - uint32_t state[5]; - uint32_t count[2]; - unsigned char buffer[64]; -} SHA1_CTX; - -void SHA1Transform(uint32_t state[5], const unsigned char buffer[64]); -void SHA1Init(SHA1_CTX* context); -void SHA1Update(SHA1_CTX* context, const unsigned char* data, uint32_t len); -void SHA1Final(unsigned char digest[20], SHA1_CTX* context); -} -#else -#include -#endif - -#define MAX_PRINTF_LEN 64 - -size_t AsyncWebSocketMessageBufferLinkedList::_totalCount = 0; -size_t AsyncWebSocketMessageBufferLinkedList::_totalSize = 0; - -size_t webSocketSendFrameWindow(AsyncClient *client){ - if(!client->canSend()) - return 0; - size_t space = client->space(); - if(space < 9) - return 0; - return space - 8; -} - -size_t webSocketSendFrame(AsyncClient *client, bool final, uint8_t opcode, bool mask, uint8_t *data, size_t len){ - if(!client->canSend()) - return 0; - size_t space = client->space(); - if(space < 2) - return 0; - uint8_t mbuf[4] = {0,0,0,0}; - uint8_t headLen = 2; - if(len && mask){ - headLen += 4; - mbuf[0] = rand() % 0xFF; - mbuf[1] = rand() % 0xFF; - mbuf[2] = rand() % 0xFF; - mbuf[3] = rand() % 0xFF; - } - if(len > 125) - headLen += 2; - if(space < headLen) - return 0; - space -= headLen; - - if(len > space) len = space; - - uint8_t *buf = (uint8_t*)malloc(headLen); - if(buf == NULL){ - //os_printf("could not malloc %u bytes for frame header\n", headLen); - return 0; - } - - buf[0] = opcode & 0x0F; - if(final) - buf[0] |= 0x80; - if(len < 126) - buf[1] = len & 0x7F; - else { - buf[1] = 126; - buf[2] = (uint8_t)((len >> 8) & 0xFF); - buf[3] = (uint8_t)(len & 0xFF); - } - if(len && mask){ - buf[1] |= 0x80; - memcpy(buf + (headLen - 4), mbuf, 4); - } - if(client->add((const char *)buf, headLen) != headLen){ - //os_printf("error adding %lu header bytes\n", headLen); - free(buf); - return 0; - } - free(buf); - - if(len){ - if(len && mask){ - size_t i; - for(i=0;iadd((const char *)data, len) != len){ - //os_printf("error adding %lu data bytes\n", len); - return 0; - } - } - if(!client->send()){ - //os_printf("error sending frame: %lu\n", headLen+len); - return 0; - } - return len; -} - - -/* - * AsyncWebSocketMessageBuffer - */ - - - -AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer() - :_data(nullptr) - ,_len(0) - ,_lock(false) - ,_count(0) -{ - -} - -AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(uint8_t * data, size_t size) - :_data(nullptr) - ,_len(size) - ,_lock(false) - ,_count(0) -{ - - if (!data) { - return; - } - - _data = new uint8_t[_len + 1]; - - if (_data) { - memcpy(_data, data, _len); - _data[_len] = 0; - } -} - - -AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(size_t size) - :_data(nullptr) - ,_len(size) - ,_lock(false) - ,_count(0) -{ - _data = new uint8_t[_len + 1]; - - if (_data) { - _data[_len] = 0; - } - -} - -AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(const AsyncWebSocketMessageBuffer & copy) - :_data(nullptr) - ,_len(0) - ,_lock(false) - ,_count(0) -{ - _len = copy._len; - _lock = copy._lock; - _count = 0; - - if (_len) { - _data = new uint8_t[_len + 1]; - _data[_len] = 0; - } - - if (_data) { - memcpy(_data, copy._data, _len); - _data[_len] = 0; - } - -} - -AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(AsyncWebSocketMessageBuffer && copy) - :_data(nullptr) - ,_len(0) - ,_lock(false) - ,_count(0) -{ - _len = copy._len; - _lock = copy._lock; - _count = 0; - - if (copy._data) { - _data = copy._data; - copy._data = nullptr; - } - -} - -AsyncWebSocketMessageBuffer::~AsyncWebSocketMessageBuffer() -{ - if (_data) { - delete[] _data; - } -} - -bool AsyncWebSocketMessageBuffer::reserve(size_t size) -{ - _len = size; - - if (_data) { - delete[] _data; - _data = nullptr; - } - - _data = new uint8_t[_len + 1]; - - if (_data) { - _data[_len] = 0; - return true; - } else { - return false; - } - -} - - - -/* - * Control Frame - */ - -class AsyncWebSocketControl { - private: - uint8_t _opcode; - uint8_t *_data; - size_t _len; - bool _mask; - bool _finished; - public: - AsyncWebSocketControl(uint8_t opcode, uint8_t *data=NULL, size_t len=0, bool mask=false) - :_opcode(opcode) - ,_len(len) - ,_mask(len && mask) - ,_finished(false) - { - if(data == NULL) - _len = 0; - if(_len){ - if(_len > 125) - _len = 125; - _data = (uint8_t*)malloc(_len); - if(_data == NULL) - _len = 0; - else memcpy_P(_data, data, len); - } else _data = NULL; - } - virtual ~AsyncWebSocketControl(){ - if(_data != NULL) - free(_data); - } - virtual bool finished() const { return _finished; } - uint8_t opcode(){ return _opcode; } - uint8_t len(){ return _len + 2; } - size_t send(AsyncClient *client){ - _finished = true; - return webSocketSendFrame(client, true, _opcode & 0x0F, _mask, _data, _len); - } -}; - -/* - * Basic Buffered Message - */ - - -AsyncWebSocketBasicMessage::AsyncWebSocketBasicMessage(const char * data, size_t len, uint8_t opcode, bool mask) - :_len(len) - ,_sent(0) - ,_ack(0) - ,_acked(0) -{ - _opcode = opcode & 0x07; - _mask = mask; - _data = (uint8_t*)malloc(_len+1); - if(_data == NULL){ - _len = 0; - _status = WS_MSG_ERROR; - } else { - _status = WS_MSG_SENDING; - memcpy(_data, data, _len); - _data[_len] = 0; - } -} -AsyncWebSocketBasicMessage::AsyncWebSocketBasicMessage(uint8_t opcode, bool mask) - :_len(0) - ,_sent(0) - ,_ack(0) - ,_acked(0) - ,_data(NULL) -{ - _opcode = opcode & 0x07; - _mask = mask; - -} - - -AsyncWebSocketBasicMessage::~AsyncWebSocketBasicMessage() { - if(_data != NULL) - free(_data); -} - - void AsyncWebSocketBasicMessage::ack(size_t len, uint32_t time) { - (void)time; - _acked += len; - if(_sent == _len && _acked == _ack){ - _status = WS_MSG_SENT; - } -} - size_t AsyncWebSocketBasicMessage::send(AsyncClient *client) { - if(_status != WS_MSG_SENDING) - return 0; - if(_acked < _ack){ - return 0; - } - if(_sent == _len){ - if(_acked == _ack) - _status = WS_MSG_SENT; - return 0; - } - if(_sent > _len){ - _status = WS_MSG_ERROR; - return 0; - } - - size_t toSend = _len - _sent; - size_t window = webSocketSendFrameWindow(client); - - if(window < toSend) { - toSend = window; - } - - _sent += toSend; - _ack += toSend + ((toSend < 126)?2:4) + (_mask * 4); - - bool final = (_sent == _len); - uint8_t* dPtr = (uint8_t*)(_data + (_sent - toSend)); - uint8_t opCode = (toSend && _sent == toSend)?_opcode:(uint8_t)WS_CONTINUATION; - - size_t sent = webSocketSendFrame(client, final, opCode, _mask, dPtr, toSend); - _status = WS_MSG_SENDING; - if(toSend && sent != toSend){ - _sent -= (toSend - sent); - _ack -= (toSend - sent); - } - return sent; -} - -// bool AsyncWebSocketBasicMessage::reserve(size_t size) { -// if (size) { -// _data = (uint8_t*)malloc(size +1); -// if (_data) { -// memset(_data, 0, size); -// _len = size; -// _status = WS_MSG_SENDING; -// return true; -// } -// } -// return false; -// } - - -/* - * AsyncWebSocketMultiMessage Message - */ - - -AsyncWebSocketMultiMessage::AsyncWebSocketMultiMessage(AsyncWebSocketMessageBuffer * buffer, uint8_t opcode, bool mask) - :_len(0) - ,_sent(0) - ,_ack(0) - ,_acked(0) - ,_WSbuffer(nullptr) -{ - - _opcode = opcode & 0x07; - _mask = mask; - - if (buffer) { - _WSbuffer = buffer; - (*_WSbuffer)++; - _data = buffer->get(); - _len = buffer->length(); - _status = WS_MSG_SENDING; - //ets_printf("M: %u\n", _len); - } else { - _status = WS_MSG_ERROR; - } - -} - - -AsyncWebSocketMultiMessage::~AsyncWebSocketMultiMessage() { - if (_WSbuffer) { - (*_WSbuffer)--; // decreases the counter. - } -} - - void AsyncWebSocketMultiMessage::ack(size_t len, uint32_t time) { - (void)time; - _acked += len; - if(_sent >= _len && _acked >= _ack){ - _status = WS_MSG_SENT; - } - //ets_printf("A: %u\n", len); -} - size_t AsyncWebSocketMultiMessage::send(AsyncClient *client) { - if(_status != WS_MSG_SENDING) - return 0; - if(_acked < _ack){ - return 0; - } - if(_sent == _len){ - _status = WS_MSG_SENT; - return 0; - } - if(_sent > _len){ - _status = WS_MSG_ERROR; - //ets_printf("E: %u > %u\n", _sent, _len); - return 0; - } - - size_t toSend = _len - _sent; - size_t window = webSocketSendFrameWindow(client); - - if(window < toSend) { - toSend = window; - } - - _sent += toSend; - _ack += toSend + ((toSend < 126)?2:4) + (_mask * 4); - - //ets_printf("W: %u %u\n", _sent - toSend, toSend); - - bool final = (_sent == _len); - uint8_t* dPtr = (uint8_t*)(_data + (_sent - toSend)); - uint8_t opCode = (toSend && _sent == toSend)?_opcode:(uint8_t)WS_CONTINUATION; - - size_t sent = webSocketSendFrame(client, final, opCode, _mask, dPtr, toSend); - _status = WS_MSG_SENDING; - if(toSend && sent != toSend){ - //ets_printf("E: %u != %u\n", toSend, sent); - _sent -= (toSend - sent); - _ack -= (toSend - sent); - } - //ets_printf("S: %u %u\n", _sent, sent); - return sent; -} - - -/* - * Async WebSocket Client - */ - const char *AWSC_PING_PAYLOAD[] PROGMEM = { "ESPAsyncWebServer-PING" }; - const size_t AWSC_PING_PAYLOAD_LEN = 22; - -AsyncWebSocketClient::AsyncWebSocketClient(AsyncWebServerRequest *request, AsyncWebSocket *server) - : _controlQueue(LinkedList([](AsyncWebSocketControl *c){ delete c; })) - , _messageQueue(LinkedList([](AsyncWebSocketMessage *m){ delete m; })) - , _tempObject(NULL) -{ - _client = request->client(); - _server = server; - _clientId = _server->_getNextId(); - _status = WS_CONNECTED; - _pstate = 0; - _lastMessageTime = millis(); - _keepAlivePeriod = 0; - _client->setRxTimeout(0); - _client->onError([](void *r, AsyncClient* c, int8_t error){ (void)c; ((AsyncWebSocketClient*)(r))->_onError(error); }, this); - _client->onAck([](void *r, AsyncClient* c, size_t len, uint32_t time){ (void)c; ((AsyncWebSocketClient*)(r))->_onAck(len, time); }, this); - _client->onDisconnect([](void *r, AsyncClient* c){ ((AsyncWebSocketClient*)(r))->_onDisconnect(); delete c; }, this); - _client->onTimeout([](void *r, AsyncClient* c, uint32_t time){ (void)c; ((AsyncWebSocketClient*)(r))->_onTimeout(time); }, this); - _client->onData([](void *r, AsyncClient* c, void *buf, size_t len){ (void)c; ((AsyncWebSocketClient*)(r))->_onData(buf, len); }, this); - _client->onPoll([](void *r, AsyncClient* c){ (void)c; ((AsyncWebSocketClient*)(r))->_onPoll(); }, this); - _server->_addClient(this); - _server->_handleEvent(this, WS_EVT_CONNECT, request, NULL, 0); - delete request; -} - -AsyncWebSocketClient::~AsyncWebSocketClient(){ - _messageQueue.free(); - _controlQueue.free(); - _server->_handleEvent(this, WS_EVT_DISCONNECT, NULL, NULL, 0); -} - -void AsyncWebSocketClient::_onAck(size_t len, uint32_t time){ - _lastMessageTime = millis(); - if(!_controlQueue.isEmpty()){ - auto head = _controlQueue.front(); - if(head->finished()){ - len -= head->len(); - if(_status == WS_DISCONNECTING && head->opcode() == WS_DISCONNECT){ - _controlQueue.remove(head); - _status = WS_DISCONNECTED; - _client->close(true); - return; - } - _controlQueue.remove(head); - } - } - if(len && !_messageQueue.isEmpty()){ - _messageQueue.front()->ack(len, time); - } - _server->_cleanBuffers(); - _runQueue(); -} - -void AsyncWebSocketClient::_onPoll(){ - if(_client->canSend() && (!_controlQueue.isEmpty() || !_messageQueue.isEmpty())){ - _runQueue(); - } else if(_keepAlivePeriod > 0 && _controlQueue.isEmpty() && _messageQueue.isEmpty() && (millis() - _lastMessageTime) >= _keepAlivePeriod){ - ping((uint8_t *)AWSC_PING_PAYLOAD, AWSC_PING_PAYLOAD_LEN); - } -} - -void AsyncWebSocketClient::_runQueue(){ - while(!_messageQueue.isEmpty() && _messageQueue.front()->finished()){ - _messageQueue.remove(_messageQueue.front()); - } - - if(!_controlQueue.isEmpty() && (_messageQueue.isEmpty() || _messageQueue.front()->betweenFrames()) && webSocketSendFrameWindow(_client) > (size_t)(_controlQueue.front()->len() - 1)){ - _controlQueue.front()->send(_client); - } else if(!_messageQueue.isEmpty() && _messageQueue.front()->betweenFrames() && webSocketSendFrameWindow(_client)){ - _messageQueue.front()->send(_client); - } -} - -bool AsyncWebSocketClient::queueIsFull(){ - if(_queueIsFull() || (_status != WS_CONNECTED) ) return true; - return false; -} - -bool AsyncWebSocketClient::_queueIsFull() const { - return - (_server->getQueuedMessageCount() >= WS_MAX_QUEUED_MESSAGES) || - (_server->getQueuedMessageSize() >= WS_MAX_QUEUED_MESSAGES_SIZE) -#if WS_MAX_QUEUED_MESSAGES_MIN_HEAP - || (ESP.getFreeHeap() < WS_MAX_QUEUED_MESSAGES_MIN_HEAP) -#endif - ; -} - -void AsyncWebSocketClient::_queueMessage(AsyncWebSocketMessage *dataMessage){ - if(dataMessage == NULL) - return; - if(_status != WS_CONNECTED){ - delete dataMessage; - return; - } - if(_queueIsFull()){ -#if DEBUG - ::printf(PSTR("AsyncWebSocketClient: Too many messages queued\n")); -#endif - delete dataMessage; - } else { - _messageQueue.add(dataMessage); - } - if(_client->canSend()) - _runQueue(); -} - -void AsyncWebSocketClient::_queueControl(AsyncWebSocketControl *controlMessage){ - if(controlMessage == NULL) - return; - _controlQueue.add(controlMessage); - if(_client->canSend()) - _runQueue(); -} - -void AsyncWebSocketClient::close(uint16_t code, const char * message){ - if(_status != WS_CONNECTED) - return; - if(code){ - uint8_t packetLen = 2; - if(message != NULL){ - size_t mlen = strlen(message); - if(mlen > 123) mlen = 123; - packetLen += mlen; - } - char * buf = (char*)malloc(packetLen); - if(buf != NULL){ - buf[0] = (uint8_t)(code >> 8); - buf[1] = (uint8_t)(code & 0xFF); - if(message != NULL){ - memcpy(buf+2, message, packetLen -2); - } - _queueControl(new AsyncWebSocketControl(WS_DISCONNECT,(uint8_t*)buf,packetLen)); - free(buf); - return; - } - } - _queueControl(new AsyncWebSocketControl(WS_DISCONNECT)); -} - -void AsyncWebSocketClient::ping(uint8_t *data, size_t len){ - if(_status == WS_CONNECTED) - _queueControl(new AsyncWebSocketControl(WS_PING, data, len)); -} - -void AsyncWebSocketClient::_onError(int8_t){} - -void AsyncWebSocketClient::_onTimeout(uint32_t time){ - (void)time; - _client->close(true); -} - -void AsyncWebSocketClient::_onDisconnect(){ - _client = NULL; - _server->_handleDisconnect(this); -} - -void AsyncWebSocketClient::_onData(void *pbuf, size_t plen){ - _lastMessageTime = millis(); - uint8_t *data = (uint8_t*)pbuf; - while(plen > 0){ - if(!_pstate){ - const uint8_t *fdata = data; - _pinfo.index = 0; - _pinfo.final = (fdata[0] & 0x80) != 0; - _pinfo.opcode = fdata[0] & 0x0F; - _pinfo.masked = (fdata[1] & 0x80) != 0; - _pinfo.len = fdata[1] & 0x7F; - data += 2; - plen -= 2; - if(_pinfo.len == 126){ - _pinfo.len = fdata[3] | (uint16_t)(fdata[2]) << 8; - data += 2; - plen -= 2; - } else if(_pinfo.len == 127){ - _pinfo.len = fdata[9] | (uint16_t)(fdata[8]) << 8 | (uint32_t)(fdata[7]) << 16 | (uint32_t)(fdata[6]) << 24 | (uint64_t)(fdata[5]) << 32 | (uint64_t)(fdata[4]) << 40 | (uint64_t)(fdata[3]) << 48 | (uint64_t)(fdata[2]) << 56; - data += 8; - plen -= 8; - } - - if(_pinfo.masked){ - memcpy(_pinfo.mask, data, 4); - data += 4; - plen -= 4; - } - } - - const size_t datalen = std::min((size_t)(_pinfo.len - _pinfo.index), plen); - const auto datalast = data[datalen]; - - if(_pinfo.masked){ - for(size_t i=0;i_handleEvent(this, WS_EVT_DATA, (void *)&_pinfo, (uint8_t*)data, datalen); - - _pinfo.index += datalen; - } else if((datalen + _pinfo.index) == _pinfo.len){ - _pstate = 0; - if(_pinfo.opcode == WS_DISCONNECT){ - if(datalen){ - uint16_t reasonCode = (uint16_t)(data[0] << 8) + data[1]; - char * reasonString = (char*)(data+2); - if(reasonCode > 1001){ - _server->_handleEvent(this, WS_EVT_ERROR, (void *)&reasonCode, (uint8_t*)reasonString, strlen(reasonString)); - } - } - if(_status == WS_DISCONNECTING){ - _status = WS_DISCONNECTED; - _client->close(true); - } else { - _status = WS_DISCONNECTING; - _client->ackLater(); - _queueControl(new AsyncWebSocketControl(WS_DISCONNECT, data, datalen)); - } - } else if(_pinfo.opcode == WS_PING){ - _queueControl(new AsyncWebSocketControl(WS_PONG, data, datalen)); - } else if(_pinfo.opcode == WS_PONG){ - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wstringop-overflow" - if(datalen != AWSC_PING_PAYLOAD_LEN || memcmp_P(data, AWSC_PING_PAYLOAD, AWSC_PING_PAYLOAD_LEN) != 0) - _server->_handleEvent(this, WS_EVT_PONG, NULL, data, datalen); -#pragma GCC diagnostic pop - - } else if(_pinfo.opcode < 8){//continuation or text/binary frame - _server->_handleEvent(this, WS_EVT_DATA, (void *)&_pinfo, data, datalen); - } - } else { - //os_printf("frame error: len: %u, index: %llu, total: %llu\n", datalen, _pinfo.index, _pinfo.len); - //what should we do? - break; - } - - // restore byte as _handleEvent may have added a null terminator i.e., data[len] = 0; - if (datalen > 0) - data[datalen] = datalast; - - data += datalen; - plen -= datalen; - } -} - -size_t AsyncWebSocketClient::printf(const char *format, ...) { - va_list arg; - va_start(arg, format); - char* temp = new char[MAX_PRINTF_LEN]; - if(!temp){ - va_end(arg); - return 0; - } - char* buffer = temp; - size_t len = vsnprintf(temp, MAX_PRINTF_LEN, format, arg); - va_end(arg); - - if (len > (MAX_PRINTF_LEN - 1)) { - buffer = new char[len + 1]; - if (!buffer) { - delete[] temp; - return 0; - } - va_start(arg, format); - vsnprintf(buffer, len + 1, format, arg); - va_end(arg); - } - text(buffer, len); - if (buffer != temp) { - delete[] buffer; - } - delete[] temp; - return len; -} - -#ifndef ESP32 -size_t AsyncWebSocketClient::printf_P(PGM_P formatP, ...) { - va_list arg; - va_start(arg, formatP); - char* temp = new char[MAX_PRINTF_LEN]; - if(!temp){ - va_end(arg); - return 0; - } - char* buffer = temp; - size_t len = vsnprintf_P(temp, MAX_PRINTF_LEN, formatP, arg); - va_end(arg); - - if (len > (MAX_PRINTF_LEN - 1)) { - buffer = new char[len + 1]; - if (!buffer) { - delete[] temp; - return 0; - } - va_start(arg, formatP); - vsnprintf_P(buffer, len + 1, formatP, arg); - va_end(arg); - } - text(buffer, len); - if (buffer != temp) { - delete[] buffer; - } - delete[] temp; - return len; -} -#endif - -void AsyncWebSocketClient::text(const char * message, size_t len){ - _queueMessage(new AsyncWebSocketBasicMessage(message, len)); -} -void AsyncWebSocketClient::text(const char * message){ - text(message, strlen(message)); -} -void AsyncWebSocketClient::text(uint8_t * message, size_t len){ - text((const char *)message, len); -} -void AsyncWebSocketClient::text(char * message){ - text(message, strlen(message)); -} -void AsyncWebSocketClient::text(const String &message){ - text(message.c_str(), message.length()); -} -void AsyncWebSocketClient::text(const __FlashStringHelper *data){ - text(String(data)); -} -void AsyncWebSocketClient::text(AsyncWebSocketMessageBuffer * buffer) -{ - _queueMessage(new AsyncWebSocketMultiMessage(buffer)); -} - -void AsyncWebSocketClient::binary(const char * message, size_t len){ - _queueMessage(new AsyncWebSocketBasicMessage(message, len, WS_BINARY)); -} -void AsyncWebSocketClient::binary(const char * message){ - binary(message, strlen(message)); -} -void AsyncWebSocketClient::binary(uint8_t * message, size_t len){ - binary((const char *)message, len); -} -void AsyncWebSocketClient::binary(char * message){ - binary(message, strlen(message)); -} -void AsyncWebSocketClient::binary(const String &message){ - binary(message.c_str(), message.length()); -} -void AsyncWebSocketClient::binary(const __FlashStringHelper *data, size_t len){ - PGM_P p = reinterpret_cast(data); - char * message = (char*) malloc(len); - if(message){ - memcpy_P(message, p, len); - binary(message, len); - free(message); - } - -} -void AsyncWebSocketClient::binary(AsyncWebSocketMessageBuffer * buffer) -{ - _queueMessage(new AsyncWebSocketMultiMessage(buffer, WS_BINARY)); -} - -IPAddress AsyncWebSocketClient::remoteIP() { - if(!_client) { - return IPAddress((uint32_t)0); - } - return _client->remoteIP(); -} - -uint16_t AsyncWebSocketClient::remotePort() { - if(!_client) { - return 0; - } - return _client->remotePort(); -} - - - -/* - * Async Web Socket - Each separate socket location - */ - -AsyncWebSocket::AsyncWebSocket(const String& url) - :_url(url) - ,_clients(LinkedList([](AsyncWebSocketClient *c){ delete c; })) - ,_cNextId(1) - ,_enabled(true) -{ - _eventHandler = NULL; -} - -AsyncWebSocket::~AsyncWebSocket(){} - -void AsyncWebSocket::_handleEvent(AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){ - if(_eventHandler != NULL){ - _eventHandler(this, client, type, arg, data, len); - } -} - -void AsyncWebSocket::_addClient(AsyncWebSocketClient * client){ - _clients.add(client); -} - -void AsyncWebSocket::_handleDisconnect(AsyncWebSocketClient * client){ - - _clients.remove_first([=](AsyncWebSocketClient * c){ - return c->id() == client->id(); - }); -} - -bool AsyncWebSocket::availableForWriteAll(){ - for(const auto& c: _clients){ - if(c->queueIsFull()) return false; - } - return true; -} - -bool AsyncWebSocket::availableForWrite(uint32_t id){ - for(const auto& c: _clients){ - if(c->queueIsFull() && (c->id() == id )) return false; - } - return true; -} - -size_t AsyncWebSocket::count() const { - return _clients.count_if([](AsyncWebSocketClient * c){ - return c->status() == WS_CONNECTED; - }); -} - -AsyncWebSocketClient * AsyncWebSocket::client(uint32_t id){ - for(const auto &c: _clients){ - if(c->id() == id && c->status() == WS_CONNECTED){ - return c; - } - } - return nullptr; -} - - -void AsyncWebSocket::close(uint32_t id, uint16_t code, const char * message){ - AsyncWebSocketClient * c = client(id); - if(c) - c->close(code, message); -} - -void AsyncWebSocket::closeAll(uint16_t code, const char * message){ - for(const auto& c: _clients){ - if(c->status() == WS_CONNECTED) - c->close(code, message); - } -} - -void AsyncWebSocket::cleanupClients(uint16_t maxClients) -{ - if (count() > maxClients){ - _clients.front()->close(); - } -} - -void AsyncWebSocket::ping(uint32_t id, uint8_t *data, size_t len){ - AsyncWebSocketClient * c = client(id); - if(c) - c->ping(data, len); -} - -void AsyncWebSocket::pingAll(uint8_t *data, size_t len){ - for(const auto& c: _clients){ - if(c->status() == WS_CONNECTED) - c->ping(data, len); - } -} - -void AsyncWebSocket::text(uint32_t id, const char * message, size_t len){ - AsyncWebSocketClient * c = client(id); - if(c) - c->text(message, len); -} - -void AsyncWebSocket::textAll(AsyncWebSocketMessageBuffer * buffer){ - if (!buffer) return; - buffer->lock(); - for(const auto& c: _clients){ - if(c->status() == WS_CONNECTED){ - c->text(buffer); - } - } - buffer->unlock(); - _cleanBuffers(); -} - - -void AsyncWebSocket::textAll(const char * message, size_t len){ - AsyncWebSocketMessageBuffer * WSBuffer = makeBuffer((uint8_t *)message, len); - textAll(WSBuffer); -} - -void AsyncWebSocket::binary(uint32_t id, const char * message, size_t len){ - AsyncWebSocketClient * c = client(id); - if(c) - c->binary(message, len); -} - -void AsyncWebSocket::binaryAll(const char * message, size_t len){ - AsyncWebSocketMessageBuffer * buffer = makeBuffer((uint8_t *)message, len); - binaryAll(buffer); -} - -void AsyncWebSocket::binaryAll(AsyncWebSocketMessageBuffer * buffer) -{ - if (!buffer) return; - buffer->lock(); - for(const auto& c: _clients){ - if(c->status() == WS_CONNECTED) - c->binary(buffer); - } - buffer->unlock(); - _cleanBuffers(); -} - -void AsyncWebSocket::message(uint32_t id, AsyncWebSocketMessage *message){ - AsyncWebSocketClient * c = client(id); - if(c) - c->message(message); -} - -void AsyncWebSocket::messageAll(AsyncWebSocketMultiMessage *message){ - for(const auto& c: _clients){ - if(c->status() == WS_CONNECTED) - c->message(message); - } - _cleanBuffers(); -} - -size_t AsyncWebSocket::printf(uint32_t id, const char *format, ...){ - AsyncWebSocketClient * c = client(id); - if(c){ - va_list arg; - va_start(arg, format); - size_t len = c->printf(format, arg); - va_end(arg); - return len; - } - return 0; -} - -size_t AsyncWebSocket::printfAll(const char *format, ...) { - va_list arg; - char* temp = new char[MAX_PRINTF_LEN]; - if(!temp){ - return 0; - } - va_start(arg, format); - size_t len = vsnprintf(temp, MAX_PRINTF_LEN, format, arg); - va_end(arg); - delete[] temp; - - AsyncWebSocketMessageBuffer * buffer = makeBuffer(len); - if (!buffer) { - return 0; - } - - va_start(arg, format); - vsnprintf( (char *)buffer->get(), len + 1, format, arg); - va_end(arg); - - textAll(buffer); - return len; -} - -#ifndef ESP32 -size_t AsyncWebSocket::printf_P(uint32_t id, PGM_P formatP, ...){ - AsyncWebSocketClient * c = client(id); - if(c != NULL){ - va_list arg; - va_start(arg, formatP); - size_t len = c->printf_P(formatP, arg); - va_end(arg); - return len; - } - return 0; -} -#endif - -size_t AsyncWebSocket::printfAll_P(PGM_P formatP, ...) { - va_list arg; - char* temp = new char[MAX_PRINTF_LEN]; - if(!temp){ - return 0; - } - va_start(arg, formatP); - size_t len = vsnprintf_P(temp, MAX_PRINTF_LEN, formatP, arg); - va_end(arg); - delete[] temp; - - AsyncWebSocketMessageBuffer * buffer = makeBuffer(len + 1); - if (!buffer) { - return 0; - } - - va_start(arg, formatP); - vsnprintf_P((char *)buffer->get(), len + 1, formatP, arg); - va_end(arg); - - textAll(buffer); - return len; -} - -void AsyncWebSocket::text(uint32_t id, const char * message){ - text(id, message, strlen(message)); -} -void AsyncWebSocket::text(uint32_t id, uint8_t * message, size_t len){ - text(id, (const char *)message, len); -} -void AsyncWebSocket::text(uint32_t id, char * message){ - text(id, message, strlen(message)); -} -void AsyncWebSocket::text(uint32_t id, const String &message){ - text(id, message.c_str(), message.length()); -} -void AsyncWebSocket::text(uint32_t id, const __FlashStringHelper *message){ - AsyncWebSocketClient * c = client(id); - if(c != NULL) - c->text(message); -} -void AsyncWebSocket::textAll(const char * message){ - textAll(message, strlen(message)); -} -void AsyncWebSocket::textAll(uint8_t * message, size_t len){ - textAll((const char *)message, len); -} -void AsyncWebSocket::textAll(char * message){ - textAll(message, strlen(message)); -} -void AsyncWebSocket::textAll(const String &message){ - textAll(message.c_str(), message.length()); -} -void AsyncWebSocket::textAll(const __FlashStringHelper *message){ - for(const auto& c: _clients){ - if(c->status() == WS_CONNECTED) - c->text(message); - } -} -void AsyncWebSocket::binary(uint32_t id, const char * message){ - binary(id, message, strlen(message)); -} -void AsyncWebSocket::binary(uint32_t id, uint8_t * message, size_t len){ - binary(id, (const char *)message, len); -} -void AsyncWebSocket::binary(uint32_t id, char * message){ - binary(id, message, strlen(message)); -} -void AsyncWebSocket::binary(uint32_t id, const String &message){ - binary(id, message.c_str(), message.length()); -} -void AsyncWebSocket::binary(uint32_t id, const __FlashStringHelper *message, size_t len){ - AsyncWebSocketClient * c = client(id); - if(c != NULL) - c-> binary(message, len); -} -void AsyncWebSocket::binaryAll(const char * message){ - binaryAll(message, strlen(message)); -} -void AsyncWebSocket::binaryAll(uint8_t * message, size_t len){ - binaryAll((const char *)message, len); -} -void AsyncWebSocket::binaryAll(char * message){ - binaryAll(message, strlen(message)); -} -void AsyncWebSocket::binaryAll(const String &message){ - binaryAll(message.c_str(), message.length()); -} -void AsyncWebSocket::binaryAll(const __FlashStringHelper *message, size_t len){ - for(const auto& c: _clients){ - if(c->status() == WS_CONNECTED) - c-> binary(message, len); - } - } - -const char __WS_STR_CONNECTION[] PROGMEM = { "Connection" }; -const char __WS_STR_UPGRADE[] PROGMEM = { "Upgrade" }; -const char __WS_STR_ORIGIN[] PROGMEM = { "Origin" }; -const char __WS_STR_VERSION[] PROGMEM = { "Sec-WebSocket-Version" }; -const char __WS_STR_KEY[] PROGMEM = { "Sec-WebSocket-Key" }; -const char __WS_STR_PROTOCOL[] PROGMEM = { "Sec-WebSocket-Protocol" }; -const char __WS_STR_ACCEPT[] PROGMEM = { "Sec-WebSocket-Accept" }; -const char __WS_STR_UUID[] PROGMEM = { "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" }; - -#define WS_STR_CONNECTION FPSTR(__WS_STR_CONNECTION) -#define WS_STR_UPGRADE FPSTR(__WS_STR_UPGRADE) -#define WS_STR_ORIGIN FPSTR(__WS_STR_ORIGIN) -#define WS_STR_VERSION FPSTR(__WS_STR_VERSION) -#define WS_STR_KEY FPSTR(__WS_STR_KEY) -#define WS_STR_PROTOCOL FPSTR(__WS_STR_PROTOCOL) -#define WS_STR_ACCEPT FPSTR(__WS_STR_ACCEPT) -#define WS_STR_UUID FPSTR(__WS_STR_UUID) - -bool AsyncWebSocket::canHandle(AsyncWebServerRequest *request){ - if(!_enabled) - return false; - - if(request->method() != HTTP_GET || !request->url().equals(_url) || !request->isExpectedRequestedConnType(RCT_WS)) - return false; - - request->addInterestingHeader(WS_STR_CONNECTION); - request->addInterestingHeader(WS_STR_UPGRADE); - request->addInterestingHeader(WS_STR_ORIGIN); - request->addInterestingHeader(WS_STR_VERSION); - request->addInterestingHeader(WS_STR_KEY); - request->addInterestingHeader(WS_STR_PROTOCOL); - return true; -} - -void AsyncWebSocket::handleRequest(AsyncWebServerRequest *request){ - if(!request->hasHeader(WS_STR_VERSION) || !request->hasHeader(WS_STR_KEY)){ - request->send(400); - return; - } - if((_username.length() && _password.length()) && !request->authenticate(_username.c_str(), _password.c_str())){ - return request->requestAuthentication(); - } - AsyncWebHeader* version = request->getHeader(WS_STR_VERSION); - if(version->value().toInt() != 13){ - AsyncWebServerResponse *response = request->beginResponse(400); - response->addHeader(WS_STR_VERSION, F("13")); - request->send(response); - return; - } - AsyncWebHeader* key = request->getHeader(WS_STR_KEY); - AsyncWebServerResponse *response = new AsyncWebSocketResponse(key->value(), this); - if(request->hasHeader(WS_STR_PROTOCOL)){ - AsyncWebHeader* protocol = request->getHeader(WS_STR_PROTOCOL); - //ToDo: check protocol - response->addHeader(WS_STR_PROTOCOL, protocol->value()); - } - request->send(response); -} - -AsyncWebSocketMessageBuffer * AsyncWebSocket::makeBuffer(size_t size) -{ - AsyncWebSocketMessageBuffer * buffer = new AsyncWebSocketMessageBuffer(size); - if (buffer) { - AsyncWebLockGuard l(_lock); - _buffers.add(buffer); - } - return buffer; -} - -AsyncWebSocketMessageBuffer * AsyncWebSocket::makeBuffer(uint8_t * data, size_t size) -{ - AsyncWebSocketMessageBuffer * buffer = new AsyncWebSocketMessageBuffer(data, size); - - if (buffer) { - AsyncWebLockGuard l(_lock); - _buffers.add(buffer); - } - - return buffer; -} - -void AsyncWebSocket::_cleanBuffers() -{ - AsyncWebLockGuard l(_lock); - - for(AsyncWebSocketMessageBuffer * c: _buffers){ - if(c && c->canDelete()){ - _buffers.remove(c); - } - } -} - -AsyncWebSocket::AsyncWebSocketClientLinkedList AsyncWebSocket::getClients() const { - return _clients; -} - -/* - * Response to Web Socket request - sends the authorization and detaches the TCP Client from the web server - * Authentication code from https://github.com/Links2004/arduinoWebSockets/blob/master/src/WebSockets.cpp#L480 - */ - -AsyncWebSocketResponse::AsyncWebSocketResponse(const String& key, AsyncWebSocket *server){ - _server = server; - _code = 101; - _sendContentLength = false; - - uint8_t * hash = (uint8_t*)malloc(20); - if(hash == NULL){ - _state = RESPONSE_FAILED; - return; - } - char * buffer = (char *) malloc(33); - if(buffer == NULL){ - free(hash); - _state = RESPONSE_FAILED; - return; - } -#ifdef ESP8266 - sha1(key + WS_STR_UUID, hash); -#else - (String&)key += WS_STR_UUID; - SHA1_CTX ctx; - SHA1Init(&ctx); - SHA1Update(&ctx, (const unsigned char*)key.c_str(), key.length()); - SHA1Final(hash, &ctx); -#endif - base64_encodestate _state; - base64_init_encodestate(&_state); - int len = base64_encode_block((const char *) hash, 20, buffer, &_state); - len = base64_encode_blockend((buffer + len), &_state); - addHeader(WS_STR_CONNECTION, WS_STR_UPGRADE); - addHeader(WS_STR_UPGRADE, F("websocket")); - addHeader(WS_STR_ACCEPT,buffer); - free(buffer); - free(hash); -} - -void AsyncWebSocketResponse::_respond(AsyncWebServerRequest *request){ - if(_state == RESPONSE_FAILED){ - request->client()->close(true); - return; - } - String out = _assembleHead(request->version()); - request->client()->write(out.c_str(), _headLength); - _state = RESPONSE_WAIT_ACK; -} - -size_t AsyncWebSocketResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){ - (void)time; - if(len){ - new AsyncWebSocketClient(request, _server); - } - return 0; -} diff --git a/lib/ESPAsyncWebServer/AsyncWebSocket.h b/lib/ESPAsyncWebServer/AsyncWebSocket.h deleted file mode 100644 index 69dfaa9d0..000000000 --- a/lib/ESPAsyncWebServer/AsyncWebSocket.h +++ /dev/null @@ -1,448 +0,0 @@ -/* - Asynchronous WebServer library for Espressif MCUs - - Copyright (c) 2016 Hristo Gochkov. All rights reserved. - This file is part of the esp8266 core for Arduino environment. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ -#ifndef ASYNCWEBSOCKET_H_ -#define ASYNCWEBSOCKET_H_ - -#include -#ifdef ESP32 -#include -#ifndef WS_MAX_QUEUED_MESSAGES -#define WS_MAX_QUEUED_MESSAGES 32 -#endif -#ifndef WS_MAX_QUEUED_MESSAGES_SIZE -#define WS_MAX_QUEUED_MESSAGES_SIZE 65535 -#endif -#else -#include -#ifndef WS_MAX_QUEUED_MESSAGES -#define WS_MAX_QUEUED_MESSAGES 8 -#endif -#ifndef WS_MAX_QUEUED_MESSAGES_SIZE -#define WS_MAX_QUEUED_MESSAGES_SIZE 8192 -#endif -#endif -#include - -// 0 = disable -#ifndef WS_MAX_QUEUED_MESSAGES_MIN_HEAP -#define WS_MAX_QUEUED_MESSAGES_MIN_HEAP 0 -#endif - -#include "AsyncWebSynchronization.h" - -#ifdef ESP8266 -#include -#ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library -#include <../src/Hash.h> -#endif -#endif - -#ifdef ESP32 -#define DEFAULT_MAX_WS_CLIENTS 8 -#else -#define DEFAULT_MAX_WS_CLIENTS 4 -#endif - -class AsyncWebSocket; -class AsyncWebSocketResponse; -class AsyncWebSocketClient; -class AsyncWebSocketControl; - -typedef struct { - /** Message type as defined by enum AwsFrameType. - * Note: Applications will only see WS_TEXT and WS_BINARY. - * All other types are handled by the library. */ - uint8_t message_opcode; - /** Frame number of a fragmented message. */ - uint32_t num; - /** Is this the last frame in a fragmented message ?*/ - uint8_t final; - /** Is this frame masked? */ - uint8_t masked; - /** Message type as defined by enum AwsFrameType. - * This value is the same as message_opcode for non-fragmented - * messages, but may also be WS_CONTINUATION in a fragmented message. */ - uint8_t opcode; - /** Length of the current frame. - * This equals the total length of the message if num == 0 && final == true */ - uint64_t len; - /** Mask key */ - uint8_t mask[4]; - /** Offset of the data inside the current frame. */ - uint64_t index; -} AwsFrameInfo; - -typedef enum { WS_DISCONNECTED, WS_CONNECTED, WS_DISCONNECTING } AwsClientStatus; -typedef enum { WS_CONTINUATION, WS_TEXT, WS_BINARY, WS_DISCONNECT = 0x08, WS_PING, WS_PONG } AwsFrameType; -typedef enum { WS_MSG_SENDING, WS_MSG_SENT, WS_MSG_ERROR } AwsMessageStatus; -typedef enum { WS_EVT_CONNECT, WS_EVT_DISCONNECT, WS_EVT_PONG, WS_EVT_ERROR, WS_EVT_DATA } AwsEventType; - -class AsyncWebSocketMessageBuffer { - private: - uint8_t * _data; - size_t _len; - bool _lock; - uint32_t _count; - - public: - AsyncWebSocketMessageBuffer(); - AsyncWebSocketMessageBuffer(size_t size); - AsyncWebSocketMessageBuffer(uint8_t * data, size_t size); - AsyncWebSocketMessageBuffer(const AsyncWebSocketMessageBuffer &); - AsyncWebSocketMessageBuffer(AsyncWebSocketMessageBuffer &&); - ~AsyncWebSocketMessageBuffer(); - void operator ++(int i) { (void)i; _count++; } - void operator --(int i) { (void)i; if (_count > 0) { _count--; } ; } - bool reserve(size_t size); - void lock() { _lock = true; } - void unlock() { _lock = false; } - uint8_t * get() { return _data; } - size_t length() { return _len; } - uint32_t count() { return _count; } - bool canDelete() { return (!_count && !_lock); } - - friend AsyncWebSocket; - -}; - -class AsyncWebSocketMessage { - protected: - uint8_t _opcode; - bool _mask; - AwsMessageStatus _status; - public: - AsyncWebSocketMessage():_opcode(WS_TEXT),_mask(false),_status(WS_MSG_ERROR){} - virtual ~AsyncWebSocketMessage(){} - virtual void ack(size_t len __attribute__((unused)), uint32_t time __attribute__((unused))){} - virtual size_t send(AsyncClient *client __attribute__((unused))){ return 0; } - virtual bool finished(){ return _status != WS_MSG_SENDING; } - virtual bool betweenFrames() const { return false; } -}; - -class AsyncWebSocketBasicMessage: public AsyncWebSocketMessage { - private: - size_t _len; - size_t _sent; - size_t _ack; - size_t _acked; - uint8_t * _data; -public: - AsyncWebSocketBasicMessage(const char * data, size_t len, uint8_t opcode=WS_TEXT, bool mask=false); - AsyncWebSocketBasicMessage(uint8_t opcode=WS_TEXT, bool mask=false); - virtual ~AsyncWebSocketBasicMessage() override; - virtual bool betweenFrames() const override { return _acked == _ack; } - virtual void ack(size_t len, uint32_t time) override ; - virtual size_t send(AsyncClient *client) override ; -}; - -class AsyncWebSocketMultiMessage: public AsyncWebSocketMessage { - private: - uint8_t * _data; - size_t _len; - size_t _sent; - size_t _ack; - size_t _acked; - AsyncWebSocketMessageBuffer * _WSbuffer; -public: - AsyncWebSocketMultiMessage(AsyncWebSocketMessageBuffer * buffer, uint8_t opcode=WS_TEXT, bool mask=false); - virtual ~AsyncWebSocketMultiMessage() override; - virtual bool betweenFrames() const override { return _acked == _ack; } - virtual void ack(size_t len, uint32_t time) override ; - virtual size_t send(AsyncClient *client) override ; -}; - -class AsyncWebSocketMessageBufferLinkedList : public LinkedList { -public: - using T = AsyncWebSocketMessageBuffer *; - using LT = LinkedList; - -private: - void onRemove(AsyncWebSocketMessageBuffer *t) { - _totalSize -= t->length(); - _totalCount--; - delete t; - } - -public: - AsyncWebSocketMessageBufferLinkedList() : LT([this](AsyncWebSocketMessageBuffer *b){ this->onRemove(b); }) { - } - - void add(const T& t){ - _totalSize += t->length(); - _totalCount++; - LT::add(t); - } - -private: - friend class AsyncWebSocket; - - // counters for all web sockets - static size_t _totalCount; - static size_t _totalSize; -}; - -class AsyncWebSocketClient { - private: - AsyncClient *_client; - AsyncWebSocket *_server; - uint32_t _clientId; - AwsClientStatus _status; - - LinkedList _controlQueue; - LinkedList _messageQueue; - - uint8_t _pstate; - AwsFrameInfo _pinfo; - - uint32_t _lastMessageTime; - uint32_t _keepAlivePeriod; - - void _queueMessage(AsyncWebSocketMessage *dataMessage); - void _queueControl(AsyncWebSocketControl *controlMessage); - void _runQueue(); - - public: - void *_tempObject; - - AsyncWebSocketClient(AsyncWebServerRequest *request, AsyncWebSocket *server); - ~AsyncWebSocketClient(); - - //client id increments for the given server - uint32_t id(){ return _clientId; } - AwsClientStatus status(){ return _status; } - AsyncClient* client(){ return _client; } - AsyncWebSocket *server(){ return _server; } - AwsFrameInfo const &pinfo() const { return _pinfo; } - - IPAddress remoteIP(); - uint16_t remotePort(); - - //control frames - void close(uint16_t code=0, const char * message=NULL); - void ping(uint8_t *data=NULL, size_t len=0); - - //set auto-ping period in seconds. disabled if zero (default) - void keepAlivePeriod(uint16_t seconds){ - _keepAlivePeriod = seconds * 1000; - } - uint16_t keepAlivePeriod(){ - return (uint16_t)(_keepAlivePeriod / 1000); - } - - //data packets - void message(AsyncWebSocketMessage *message){ _queueMessage(message); } - bool queueIsFull(); - - size_t printf(const char *format, ...) __attribute__ ((format (printf, 2, 3))); -#ifndef ESP32 - size_t printf_P(PGM_P formatP, ...) __attribute__ ((format (printf, 2, 3))); -#endif - void text(const char * message, size_t len); - void text(const char * message); - void text(uint8_t * message, size_t len); - void text(char * message); - void text(const String &message); - void text(const __FlashStringHelper *data); - void text(AsyncWebSocketMessageBuffer *buffer); - - void binary(const char * message, size_t len); - void binary(const char * message); - void binary(uint8_t * message, size_t len); - void binary(char * message); - void binary(const String &message); - void binary(const __FlashStringHelper *data, size_t len); - void binary(AsyncWebSocketMessageBuffer *buffer); - - bool canSend() { return !_queueIsFull(); } - - bool _queueIsFull() const; - - //system callbacks (do not call) - void _onAck(size_t len, uint32_t time); - void _onError(int8_t); - void _onPoll(); - void _onTimeout(uint32_t time); - void _onDisconnect(); - void _onData(void *pbuf, size_t plen); -}; - -typedef std::function AwsEventHandler; - -//WebServer Handler implementation that plays the role of a socket server -class AsyncWebSocket: public AsyncWebHandler { - public: - typedef LinkedList AsyncWebSocketClientLinkedList; - private: - String _url; - AsyncWebSocketClientLinkedList _clients; - uint32_t _cNextId; - AwsEventHandler _eventHandler; - bool _enabled; - AsyncWebLock _lock; - - public: - AsyncWebSocket(const String& url); - ~AsyncWebSocket(); - const char * url() const { return _url.c_str(); } - void enable(bool e){ _enabled = e; } - bool enabled() const { return _enabled; } - bool availableForWriteAll(); - bool availableForWrite(uint32_t id); - - size_t count() const; - AsyncWebSocketClient * client(uint32_t id); - bool hasClient(uint32_t id){ return client(id) != NULL; } - - void close(uint32_t id, uint16_t code=0, const char * message=NULL); - void closeAll(uint16_t code=0, const char * message=NULL); - void cleanupClients(uint16_t maxClients = DEFAULT_MAX_WS_CLIENTS); - - void ping(uint32_t id, uint8_t *data=NULL, size_t len=0); - void pingAll(uint8_t *data=NULL, size_t len=0); // done - - void text(uint32_t id, const char * message, size_t len); - void text(uint32_t id, const char * message); - void text(uint32_t id, uint8_t * message, size_t len); - void text(uint32_t id, char * message); - void text(uint32_t id, const String &message); - void text(uint32_t id, const __FlashStringHelper *message); - - void textAll(const char * message, size_t len); - void textAll(const char * message); - void textAll(uint8_t * message, size_t len); - void textAll(char * message); - void textAll(const String &message); - void textAll(const __FlashStringHelper *message); // need to convert - void textAll(AsyncWebSocketMessageBuffer * buffer); - - void binary(uint32_t id, const char * message, size_t len); - void binary(uint32_t id, const char * message); - void binary(uint32_t id, uint8_t * message, size_t len); - void binary(uint32_t id, char * message); - void binary(uint32_t id, const String &message); - void binary(uint32_t id, const __FlashStringHelper *message, size_t len); - - void binaryAll(const char * message, size_t len); - void binaryAll(const char * message); - void binaryAll(uint8_t * message, size_t len); - void binaryAll(char * message); - void binaryAll(const String &message); - void binaryAll(const __FlashStringHelper *message, size_t len); - void binaryAll(AsyncWebSocketMessageBuffer * buffer); - - void message(uint32_t id, AsyncWebSocketMessage *message); - void messageAll(AsyncWebSocketMultiMessage *message); - - size_t printf(uint32_t id, const char *format, ...) __attribute__ ((format (printf, 3, 4))); - size_t printfAll(const char *format, ...) __attribute__ ((format (printf, 2, 3))); -#ifndef ESP32 - size_t printf_P(uint32_t id, PGM_P formatP, ...) __attribute__ ((format (printf, 3, 4))); -#endif - size_t printfAll_P(PGM_P formatP, ...) __attribute__ ((format (printf, 2, 3))); - - //event listener - void onEvent(AwsEventHandler handler){ - _eventHandler = handler; - } - - //system callbacks (do not call) - uint32_t _getNextId(){ return _cNextId++; } - void _addClient(AsyncWebSocketClient * client); - void _handleDisconnect(AsyncWebSocketClient * client); - void _handleEvent(AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len); - virtual bool canHandle(AsyncWebServerRequest *request) override final; - virtual void handleRequest(AsyncWebServerRequest *request) override final; - - - // messagebuffer functions/objects. - AsyncWebSocketMessageBuffer * makeBuffer(size_t size = 0); - AsyncWebSocketMessageBuffer * makeBuffer(uint8_t * data, size_t size); - AsyncWebSocketMessageBufferLinkedList _buffers; - void _cleanBuffers(); - - AsyncWebSocketClientLinkedList getClients() const; - -public: -#ifndef DEBUG_AWS_QUEUE_COUNTERS -#define DEBUG_AWS_QUEUE_COUNTERS 0 -#endif - -#if DEBUG_AWS_QUEUE_COUNTERS - // total for all sockets - size_t getQueuedMessageCount() const { - _verifyCounters(); - return AsyncWebSocketMessageBufferLinkedList::_totalCount; - } - size_t getQueuedMessageSize() const { - _verifyCounters(); - return AsyncWebSocketMessageBufferLinkedList::_totalSize; - } - - static size_t _getQueuedMessageCount() { - return AsyncWebSocketMessageBufferLinkedList::_totalCount; - } - static size_t _getQueuedMessageSize() { - return AsyncWebSocketMessageBufferLinkedList::_totalSize; - } -private: - void _verifyCounters() const { - size_t t=0,c=0; - for(const auto b: _buffers) { - t+=b->length(); - c++; - } - if (AsyncWebSocketMessageBufferLinkedList::_totalSize!=t || AsyncWebSocketMessageBufferLinkedList::_totalCount!=c) { - ::printf(PSTR("AsyncWebSocketMessageBufferLinkedList size %u=%u cnt %u=%u\n"), AsyncWebSocketMessageBufferLinkedList::_totalSize, t,AsyncWebSocketMessageBufferLinkedList::_totalCount, c); - panic(); - } - } -#else - size_t getQueuedMessageCount() const { - return _getQueuedMessageCount(); - } - size_t getQueuedMessageSize() const { - return _getQueuedMessageSize(); - } - - static size_t _getQueuedMessageCount() { - return AsyncWebSocketMessageBufferLinkedList::_totalCount; - } - static size_t _getQueuedMessageSize() { - return AsyncWebSocketMessageBufferLinkedList::_totalSize; - } -#endif - -}; - -//WebServer response to authenticate the socket and detach the tcp client from the web server request -class AsyncWebSocketResponse: public AsyncWebServerResponse { - private: - String _content; - AsyncWebSocket *_server; - public: - AsyncWebSocketResponse(const String& key, AsyncWebSocket *server); - void _respond(AsyncWebServerRequest *request); - size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); - bool _sourceValid() const { return true; } -}; - - -#endif /* ASYNCWEBSOCKET_H_ */ diff --git a/lib/ESPAsyncWebServer/AsyncWebSynchronization.h b/lib/ESPAsyncWebServer/AsyncWebSynchronization.h deleted file mode 100644 index f36c52dcf..000000000 --- a/lib/ESPAsyncWebServer/AsyncWebSynchronization.h +++ /dev/null @@ -1,87 +0,0 @@ -#ifndef ASYNCWEBSYNCHRONIZATION_H_ -#define ASYNCWEBSYNCHRONIZATION_H_ - -// Synchronisation is only available on ESP32, as the ESP8266 isn't using FreeRTOS by default - -#include - -#ifdef ESP32 - -// This is the ESP32 version of the Sync Lock, using the FreeRTOS Semaphore -class AsyncWebLock -{ -private: - SemaphoreHandle_t _lock; - mutable void *_lockedBy; - -public: - AsyncWebLock() { - _lock = xSemaphoreCreateBinary(); - _lockedBy = NULL; - xSemaphoreGive(_lock); - } - - ~AsyncWebLock() { - vSemaphoreDelete(_lock); - } - - bool lock() const { - extern void *pxCurrentTCB; - if (_lockedBy != pxCurrentTCB) { - xSemaphoreTake(_lock, portMAX_DELAY); - _lockedBy = pxCurrentTCB; - return true; - } - return false; - } - - void unlock() const { - _lockedBy = NULL; - xSemaphoreGive(_lock); - } -}; - -#else - -// This is the 8266 version of the Sync Lock which is currently unimplemented -class AsyncWebLock -{ - -public: - AsyncWebLock() { - } - - ~AsyncWebLock() { - } - - bool lock() const { - return false; - } - - void unlock() const { - } -}; -#endif - -class AsyncWebLockGuard -{ -private: - const AsyncWebLock *_lock; - -public: - AsyncWebLockGuard(const AsyncWebLock &l) { - if (l.lock()) { - _lock = &l; - } else { - _lock = NULL; - } - } - - ~AsyncWebLockGuard() { - if (_lock) { - _lock->unlock(); - } - } -}; - -#endif // ASYNCWEBSYNCHRONIZATION_H_ \ No newline at end of file diff --git a/lib/ESPAsyncWebServer/ESPAsyncWebServer.h b/lib/ESPAsyncWebServer/ESPAsyncWebServer.h deleted file mode 100644 index 02a96cb2f..000000000 --- a/lib/ESPAsyncWebServer/ESPAsyncWebServer.h +++ /dev/null @@ -1,487 +0,0 @@ -/* - Asynchronous WebServer library for Espressif MCUs - - Copyright (c) 2016 Hristo Gochkov. All rights reserved. - This file is part of the esp8266 core for Arduino environment. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ -#ifndef _ESPAsyncWebServer_H_ -#define _ESPAsyncWebServer_H_ - -#include "Arduino.h" - -#include -#include "FS.h" - -#include "StringArray.h" - -#ifdef ESP32 -#include -#include -#elif defined(ESP8266) -#include -#include -#else -#error Platform not supported -#endif - -#ifdef ASYNCWEBSERVER_REGEX -#define ASYNCWEBSERVER_REGEX_ATTRIBUTE -#else -#define ASYNCWEBSERVER_REGEX_ATTRIBUTE __attribute__((warning("ASYNCWEBSERVER_REGEX not defined"))) -#endif - -#define DEBUGF(...) //Serial.printf(__VA_ARGS__) - -class AsyncWebServer; -class AsyncWebServerRequest; -class AsyncWebServerResponse; -class AsyncWebHeader; -class AsyncWebParameter; -class AsyncWebRewrite; -class AsyncWebHandler; -class AsyncStaticWebHandler; -class AsyncCallbackWebHandler; -class AsyncResponseStream; - -#ifndef WEBSERVER_H -typedef enum { - HTTP_GET = 0b00000001, - HTTP_POST = 0b00000010, - HTTP_DELETE = 0b00000100, - HTTP_PUT = 0b00001000, - HTTP_PATCH = 0b00010000, - HTTP_HEAD = 0b00100000, - HTTP_OPTIONS = 0b01000000, - HTTP_ANY = 0b01111111, -} WebRequestMethod; -#endif - -#ifndef HAVE_FS_FILE_OPEN_MODE -namespace fs { - class FileOpenMode { - public: - static const char *read; - static const char *write; - static const char *append; - }; -}; -#else -#include "FileOpenMode.h" -#endif - -//if this value is returned when asked for data, packet will not be sent and you will be asked for data again -#define RESPONSE_TRY_AGAIN 0xFFFFFFFF - -typedef uint8_t WebRequestMethodComposite; -typedef std::function ArDisconnectHandler; - -/* - * PARAMETER :: Chainable object to hold GET/POST and FILE parameters - * */ - -class AsyncWebParameter { - private: - String _name; - String _value; - size_t _size; - bool _isForm; - bool _isFile; - - public: - - AsyncWebParameter(const String& name, const String& value, bool form=false, bool file=false, size_t size=0): _name(name), _value(value), _size(size), _isForm(form), _isFile(file){} - const String& name() const { return _name; } - const String& value() const { return _value; } - size_t size() const { return _size; } - bool isPost() const { return _isForm; } - bool isFile() const { return _isFile; } -}; - -/* - * HEADER :: Chainable object to hold the headers - * */ - -class AsyncWebHeader { - private: - String _name; - String _value; - - public: - AsyncWebHeader(const String& name, const String& value): _name(name), _value(value){} - AsyncWebHeader(const String& data): _name(), _value(){ - if(!data) return; - int index = data.indexOf(':'); - if (index < 0) return; - _name = data.substring(0, index); - _value = data.substring(index + 2); - } - ~AsyncWebHeader(){} - const String& name() const { return _name; } - const String& value() const { return _value; } - String toString() const { return String(_name + F(": ") + _value + F("\r\n")); } -}; - -/* - * REQUEST :: Each incoming Client is wrapped inside a Request and both live together until disconnect - * */ - -typedef enum { RCT_NOT_USED = -1, RCT_DEFAULT = 0, RCT_HTTP, RCT_WS, RCT_EVENT, RCT_MAX } RequestedConnectionType; - -typedef std::function AwsResponseFiller; -typedef std::function AwsTemplateProcessor; - -class AsyncWebServerRequest { - using File = fs::File; - using FS = fs::FS; - friend class AsyncWebServer; - friend class AsyncCallbackWebHandler; - friend class HttpCookieHeader; - private: - AsyncClient* _client; - AsyncWebServer* _server; - AsyncWebHandler* _handler; - AsyncWebServerResponse* _response; - StringArray _interestingHeaders; - ArDisconnectHandler _onDisconnectfn; - - String _temp; - uint8_t _parseState; - - uint8_t _version; - WebRequestMethodComposite _method; - String _url; - String _host; - String _contentType; - String _boundary; - String _authorization; - RequestedConnectionType _reqconntype; - void _removeNotInterestingHeaders(); - bool _isDigest; - bool _isMultipart; - bool _isPlainPost; - bool _expectingContinue; - size_t _contentLength; - size_t _parsedLength; - - LinkedList _headers; - LinkedList _params; - LinkedList _pathParams; - - uint8_t _multiParseState; - uint8_t _boundaryPosition; - size_t _itemStartIndex; - size_t _itemSize; - String _itemName; - String _itemFilename; - String _itemType; - String _itemValue; - uint8_t *_itemBuffer; - size_t _itemBufferIndex; - bool _itemIsFile; - - void _onPoll(); - void _onAck(size_t len, uint32_t time); - void _onError(int8_t error); - void _onTimeout(uint32_t time); - void _onDisconnect(); - void _onData(void *buf, size_t len); - - void _addParam(AsyncWebParameter*); - void _addPathParam(const char *param); - - bool _parseReqHead(); - bool _parseReqHeader(); - void _parseLine(); - void _parsePlainPostChar(uint8_t data); - void _parseMultipartPostByte(uint8_t data, bool last); - void _addGetParams(const String& params); - - void _handleUploadStart(); - void _handleUploadByte(uint8_t data, bool last); - void _handleUploadEnd(); - - public: - File _tempFile; - void *_tempObject; - - AsyncWebServerRequest(AsyncWebServer*, AsyncClient*); - ~AsyncWebServerRequest(); - - AsyncClient* client(){ return _client; } - uint8_t version() const { return _version; } - WebRequestMethodComposite method() const { return _method; } - const String& url() const { return _url; } - const String& host() const { return _host; } - const String& contentType() const { return _contentType; } - size_t contentLength() const { return _contentLength; } - bool multipart() const { return _isMultipart; } - const char *methodToString() const; - const char *requestedConnTypeToString() const; - RequestedConnectionType requestedConnType() const { return _reqconntype; } - bool isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2 = RCT_NOT_USED, RequestedConnectionType erct3 = RCT_NOT_USED); - void onDisconnect (ArDisconnectHandler fn); - - //hash is the string representation of: - // base64(user:pass) for basic or - // user:realm:md5(user:realm:pass) for digest - bool authenticate(const char * hash); - bool authenticate(const char * username, const char * password, const char * realm = NULL, bool passwordIsHash = false); - void requestAuthentication(const char * realm = NULL, bool isDigest = true); - - void setHandler(AsyncWebHandler *handler){ _handler = handler; } - void addInterestingHeader(const String& name); - - void redirect(const String& url); - - void send(AsyncWebServerResponse *response); - void send(int code, const String& contentType=String(), const String& content=String()); - void send(FS &fs, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); - void send(File content, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); - void send(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback=nullptr); - void send(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); - void sendChunked(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); - void send_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr); - void send_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback=nullptr); - - AsyncWebServerResponse *beginResponse(int code, const String& contentType=String(), const String& content=String()); - AsyncWebServerResponse *beginResponse(FS &fs, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); - AsyncWebServerResponse *beginResponse(File content, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); - AsyncWebServerResponse *beginResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback=nullptr); - AsyncWebServerResponse *beginResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); - AsyncWebServerResponse *beginChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); - AsyncResponseStream *beginResponseStream(const String& contentType, size_t bufferSize=1460); - AsyncWebServerResponse *beginResponse_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr); - AsyncWebServerResponse *beginResponse_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback=nullptr); - - size_t headers() const; // get header count - bool hasHeader(const String& name) const; // check if header exists - bool hasHeader(const __FlashStringHelper * data) const; // check if header exists - - AsyncWebHeader* getHeader(const String& name) const; - AsyncWebHeader* getHeader(const __FlashStringHelper * data) const; - AsyncWebHeader* getHeader(size_t num) const; - - size_t params() const; // get arguments count - bool hasParam(const String& name, bool post=false, bool file=false) const; - bool hasParam(const __FlashStringHelper * data, bool post=false, bool file=false) const; - - AsyncWebParameter* getParam(const String& name, bool post=false, bool file=false) const; - AsyncWebParameter* getParam(const __FlashStringHelper * data, bool post, bool file) const; - AsyncWebParameter* getParam(size_t num) const; - - size_t args() const { return params(); } // get arguments count - const String& arg(const String& name) const; // get request argument value by name - const String& arg(const __FlashStringHelper * data) const; // get request argument value by F(name) - const String& arg(size_t i) const; // get request argument value by number - const String& argName(size_t i) const; // get request argument name by number - bool hasArg(const char* name) const; // check if argument exists - bool hasArg(const __FlashStringHelper * data) const; // check if F(argument) exists - - const String& ASYNCWEBSERVER_REGEX_ATTRIBUTE pathArg(size_t i) const; - - const String& header(const char* name) const;// get request header value by name - const String& header(const __FlashStringHelper * data) const;// get request header value by F(name) - const String& header(size_t i) const; // get request header value by number - const String& headerName(size_t i) const; // get request header name by number - String urlDecode(const String& text) const; -}; - -/* - * FILTER :: Callback to filter AsyncWebRewrite and AsyncWebHandler (done by the Server) - * */ - -typedef std::function ArRequestFilterFunction; - -bool ON_STA_FILTER(AsyncWebServerRequest *request); - -bool ON_AP_FILTER(AsyncWebServerRequest *request); - -/* - * REWRITE :: One instance can be handle any Request (done by the Server) - * */ - -class AsyncWebRewrite { - protected: - String _from; - String _toUrl; - String _params; - ArRequestFilterFunction _filter; - public: - AsyncWebRewrite(const char* from, const char* to): _from(from), _toUrl(to), _params(String()), _filter(NULL){ - int index = _toUrl.indexOf('?'); - if (index > 0) { - _params = _toUrl.substring(index +1); - _toUrl = _toUrl.substring(0, index); - } - } - virtual ~AsyncWebRewrite(){} - AsyncWebRewrite& setFilter(ArRequestFilterFunction fn) { _filter = fn; return *this; } - bool filter(AsyncWebServerRequest *request) const { return _filter == NULL || _filter(request); } - const String& from(void) const { return _from; } - const String& toUrl(void) const { return _toUrl; } - const String& params(void) const { return _params; } - virtual bool match(AsyncWebServerRequest *request) { return from() == request->url() && filter(request); } -}; - -/* - * HANDLER :: One instance can be attached to any Request (done by the Server) - * */ - -class AsyncWebHandler { - protected: - ArRequestFilterFunction _filter; - String _username; - String _password; - public: - AsyncWebHandler():_username(""), _password(""){} - AsyncWebHandler& setFilter(ArRequestFilterFunction fn) { _filter = fn; return *this; } - AsyncWebHandler& setAuthentication(const char *username, const char *password){ _username = String(username);_password = String(password); return *this; }; - bool filter(AsyncWebServerRequest *request){ return _filter == NULL || _filter(request); } - virtual ~AsyncWebHandler(){} - virtual bool canHandle(AsyncWebServerRequest *request __attribute__((unused))){ - return false; - } - virtual void handleRequest(AsyncWebServerRequest *request __attribute__((unused))){} - virtual void handleUpload(AsyncWebServerRequest *request __attribute__((unused)), const String& filename __attribute__((unused)), size_t index __attribute__((unused)), uint8_t *data __attribute__((unused)), size_t len __attribute__((unused)), bool final __attribute__((unused))){} - virtual void handleBody(AsyncWebServerRequest *request __attribute__((unused)), uint8_t *data __attribute__((unused)), size_t len __attribute__((unused)), size_t index __attribute__((unused)), size_t total __attribute__((unused))){} - virtual bool isRequestHandlerTrivial(){return true;} -}; - -/* - * RESPONSE :: One instance is created for each Request (attached by the Handler) - * */ - -typedef enum { - RESPONSE_SETUP, RESPONSE_HEADERS, RESPONSE_CONTENT, RESPONSE_WAIT_ACK, RESPONSE_END, RESPONSE_FAILED -} WebResponseState; - -class AsyncWebServerResponse { - protected: - int _code; - LinkedList _headers; - String _contentType; - size_t _contentLength; - bool _sendContentLength; - bool _chunked; - size_t _headLength; - size_t _sentLength; - size_t _ackedLength; - size_t _writtenLength; - WebResponseState _state; - const char* _responseCodeToString(int code); -public: - static const __FlashStringHelper *responseCodeToString(int code); - - public: - AsyncWebServerResponse(); - virtual ~AsyncWebServerResponse(); - virtual void setCode(int code); - virtual void setContentLength(size_t len); - virtual void setContentType(const String& type); - virtual void addHeader(const String& name, const String& value); - virtual String _assembleHead(uint8_t version); - virtual bool _started() const; - virtual bool _finished() const; - virtual bool _failed() const; - virtual bool _sourceValid() const; - virtual void _respond(AsyncWebServerRequest *request); - virtual size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); -}; - -/* - * SERVER :: One instance - * */ - -typedef std::function ArRequestHandlerFunction; -typedef std::function ArUploadHandlerFunction; -typedef std::function ArBodyHandlerFunction; - -class AsyncWebServer { - protected: - AsyncServer _server; - LinkedList _rewrites; - LinkedList _handlers; - AsyncCallbackWebHandler* _catchAllHandler; - - public: - AsyncWebServer(uint16_t port); - ~AsyncWebServer(); - - void begin(); - void end(); - -#if ASYNC_TCP_SSL_ENABLED - void onSslFileRequest(AcSSlFileHandler cb, void* arg); - void beginSecure(const char *cert, const char *private_key_file, const char *password); -#endif - - AsyncWebRewrite& addRewrite(AsyncWebRewrite* rewrite); - bool removeRewrite(AsyncWebRewrite* rewrite); - AsyncWebRewrite& rewrite(const char* from, const char* to); - - AsyncWebHandler& addHandler(AsyncWebHandler* handler); - bool removeHandler(AsyncWebHandler* handler); - - AsyncCallbackWebHandler& on(const char* uri, ArRequestHandlerFunction onRequest); - AsyncCallbackWebHandler& on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest); - AsyncCallbackWebHandler& on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload); - AsyncCallbackWebHandler& on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody); - - AsyncStaticWebHandler& serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_control = NULL); - - void onNotFound(ArRequestHandlerFunction fn); //called when handler is not assigned - void onFileUpload(ArUploadHandlerFunction fn); //handle file uploads - void onRequestBody(ArBodyHandlerFunction fn); //handle posts with plain body content (JSON often transmitted this way as a request) - - void reset(); //remove all writers and handlers, with onNotFound/onFileUpload/onRequestBody - - void _handleDisconnect(AsyncWebServerRequest *request); - void _attachHandler(AsyncWebServerRequest *request); - void _rewriteRequest(AsyncWebServerRequest *request); -}; - -class DefaultHeaders { - using headers_t = LinkedList; - headers_t _headers; - - DefaultHeaders() - :_headers(headers_t([](AsyncWebHeader *h){ delete h; })) - {} -public: - using ConstIterator = headers_t::ConstIterator; - - void addHeader(const String& name, const String& value){ - _headers.add(new AsyncWebHeader(name, value)); - } - - ConstIterator begin() const { return _headers.begin(); } - ConstIterator end() const { return _headers.end(); } - - DefaultHeaders(DefaultHeaders const &) = delete; - DefaultHeaders &operator=(DefaultHeaders const &) = delete; - static DefaultHeaders &Instance() { - static DefaultHeaders instance; - return instance; - } -}; - -#include "WebResponseImpl.h" -#include "WebHandlerImpl.h" -#include "AsyncWebSocket.h" -#include "AsyncEventSource.h" - -#endif /* _AsyncWebServer_H_ */ diff --git a/lib/ESPAsyncWebServer/README.md b/lib/ESPAsyncWebServer/README.md deleted file mode 100644 index e8cbbb85d..000000000 --- a/lib/ESPAsyncWebServer/README.md +++ /dev/null @@ -1,1526 +0,0 @@ -# Changes in this fork - -- Optimized memory usage by moving all strings to PROGMEM, saving 1.5-2KB of RAM -- AsyncWebSockets are not limit to the number of messages, but the total size of messages in the queue or free heap (see AsyncWebSocket.h) - -# ESPAsyncWebServer -[![Build Status](https://travis-ci.org/me-no-dev/ESPAsyncWebServer.svg?branch=master)](https://travis-ci.org/me-no-dev/ESPAsyncWebServer) ![](https://github.com/me-no-dev/ESPAsyncWebServer/workflows/ESP%20Async%20Web%20Server%20CI/badge.svg) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/395dd42cfc674e6ca2e326af3af80ffc)](https://www.codacy.com/manual/me-no-dev/ESPAsyncWebServer?utm_source=github.com&utm_medium=referral&utm_content=me-no-dev/ESPAsyncWebServer&utm_campaign=Badge_Grade) - -For help and support [![Join the chat at https://gitter.im/me-no-dev/ESPAsyncWebServer](https://badges.gitter.im/me-no-dev/ESPAsyncWebServer.svg)](https://gitter.im/me-no-dev/ESPAsyncWebServer?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) - -Async HTTP and WebSocket Server for ESP8266 Arduino - -For ESP8266 it requires [ESPAsyncTCP](https://github.com/me-no-dev/ESPAsyncTCP) -To use this library you might need to have the latest git versions of [ESP8266](https://github.com/esp8266/Arduino) Arduino Core - -For ESP32 it requires [AsyncTCP](https://github.com/me-no-dev/AsyncTCP) to work -To use this library you might need to have the latest git versions of [ESP32](https://github.com/espressif/arduino-esp32) Arduino Core - -## Table of contents -- [ESPAsyncWebServer](#espasyncwebserver) - - [Table of contents](#table-of-contents) - - [Installation](#installation) - - [Using PlatformIO](#using-platformio) - - [Why should you care](#why-should-you-care) - - [Important things to remember](#important-things-to-remember) - - [Principles of operation](#principles-of-operation) - - [The Async Web server](#the-async-web-server) - - [Request Life Cycle](#request-life-cycle) - - [Rewrites and how do they work](#rewrites-and-how-do-they-work) - - [Handlers and how do they work](#handlers-and-how-do-they-work) - - [Responses and how do they work](#responses-and-how-do-they-work) - - [Template processing](#template-processing) - - [Libraries and projects that use AsyncWebServer](#libraries-and-projects-that-use-asyncwebserver) - - [Request Variables](#request-variables) - - [Common Variables](#common-variables) - - [Headers](#headers) - - [GET, POST and FILE parameters](#get-post-and-file-parameters) - - [FILE Upload handling](#file-upload-handling) - - [Body data handling](#body-data-handling) - - [JSON body handling with ArduinoJson](#json-body-handling-with-arduinojson) - - [Responses](#responses) - - [Redirect to another URL](#redirect-to-another-url) - - [Basic response with HTTP Code](#basic-response-with-http-code) - - [Basic response with HTTP Code and extra headers](#basic-response-with-http-code-and-extra-headers) - - [Basic response with string content](#basic-response-with-string-content) - - [Basic response with string content and extra headers](#basic-response-with-string-content-and-extra-headers) - - [Send large webpage from PROGMEM](#send-large-webpage-from-progmem) - - [Send large webpage from PROGMEM and extra headers](#send-large-webpage-from-progmem-and-extra-headers) - - [Send large webpage from PROGMEM containing templates](#send-large-webpage-from-progmem-containing-templates) - - [Send large webpage from PROGMEM containing templates and extra headers](#send-large-webpage-from-progmem-containing-templates-and-extra-headers) - - [Send binary content from PROGMEM](#send-binary-content-from-progmem) - - [Respond with content coming from a Stream](#respond-with-content-coming-from-a-stream) - - [Respond with content coming from a Stream and extra headers](#respond-with-content-coming-from-a-stream-and-extra-headers) - - [Respond with content coming from a Stream containing templates](#respond-with-content-coming-from-a-stream-containing-templates) - - [Respond with content coming from a Stream containing templates and extra headers](#respond-with-content-coming-from-a-stream-containing-templates-and-extra-headers) - - [Respond with content coming from a File](#respond-with-content-coming-from-a-file) - - [Respond with content coming from a File and extra headers](#respond-with-content-coming-from-a-file-and-extra-headers) - - [Respond with content coming from a File containing templates](#respond-with-content-coming-from-a-file-containing-templates) - - [Respond with content using a callback](#respond-with-content-using-a-callback) - - [Respond with content using a callback and extra headers](#respond-with-content-using-a-callback-and-extra-headers) - - [Respond with content using a callback containing templates](#respond-with-content-using-a-callback-containing-templates) - - [Respond with content using a callback containing templates and extra headers](#respond-with-content-using-a-callback-containing-templates-and-extra-headers) - - [Chunked Response](#chunked-response) - - [Chunked Response containing templates](#chunked-response-containing-templates) - - [Print to response](#print-to-response) - - [ArduinoJson Basic Response](#arduinojson-basic-response) - - [ArduinoJson Advanced Response](#arduinojson-advanced-response) - - [Serving static files](#serving-static-files) - - [Serving specific file by name](#serving-specific-file-by-name) - - [Serving files in directory](#serving-files-in-directory) - - [Serving static files with authentication](#serving-static-files-with-authentication) - - [Specifying Cache-Control header](#specifying-cache-control-header) - - [Specifying Date-Modified header](#specifying-date-modified-header) - - [Specifying Template Processor callback](#specifying-template-processor-callback) - - [Param Rewrite With Matching](#param-rewrite-with-matching) - - [Using filters](#using-filters) - - [Serve different site files in AP mode](#serve-different-site-files-in-ap-mode) - - [Rewrite to different index on AP](#rewrite-to-different-index-on-ap) - - [Serving different hosts](#serving-different-hosts) - - [Determine interface inside callbacks](#determine-interface-inside-callbacks) - - [Bad Responses](#bad-responses) - - [Respond with content using a callback without content length to HTTP/1.0 clients](#respond-with-content-using-a-callback-without-content-length-to-http10-clients) - - [Async WebSocket Plugin](#async-websocket-plugin) - - [Async WebSocket Event](#async-websocket-event) - - [Methods for sending data to a socket client](#methods-for-sending-data-to-a-socket-client) - - [Direct access to web socket message buffer](#direct-access-to-web-socket-message-buffer) - - [Limiting the number of web socket clients](#limiting-the-number-of-web-socket-clients) - - [Async Event Source Plugin](#async-event-source-plugin) - - [Setup Event Source on the server](#setup-event-source-on-the-server) - - [Setup Event Source in the browser](#setup-event-source-in-the-browser) - - [Scanning for available WiFi Networks](#scanning-for-available-wifi-networks) - - [Remove handlers and rewrites](#remove-handlers-and-rewrites) - - [Setting up the server](#setting-up-the-server) - - [Setup global and class functions as request handlers](#setup-global-and-class-functions-as-request-handlers) - - [Methods for controlling websocket connections](#methods-for-controlling-websocket-connections) - - [Adding Default Headers](#adding-default-headers) - - [Path variable](#path-variable) - -## Installation - -### Using PlatformIO - -[PlatformIO](http://platformio.org) is an open source ecosystem for IoT development with cross platform build system, library manager and full support for Espressif ESP8266/ESP32 development. It works on the popular host OS: Mac OS X, Windows, Linux 32/64, Linux ARM (like Raspberry Pi, BeagleBone, CubieBoard). - -1. Install [PlatformIO IDE](http://platformio.org/platformio-ide) -2. Create new project using "PlatformIO Home > New Project" -3. Update dev/platform to staging version: - - [Instruction for Espressif 8266](http://docs.platformio.org/en/latest/platforms/espressif8266.html#using-arduino-framework-with-staging-version) - - [Instruction for Espressif 32](http://docs.platformio.org/en/latest/platforms/espressif32.html#using-arduino-framework-with-staging-version) - 4. Add "ESP Async WebServer" to project using [Project Configuration File `platformio.ini`](http://docs.platformio.org/page/projectconf.html) and [lib_deps](http://docs.platformio.org/page/projectconf/section_env_library.html#lib-deps) option: - -```ini -[env:myboard] -platform = espressif... -board = ... -framework = arduino - -# using the latest stable version -lib_deps = ESP Async WebServer - -# or using GIT Url (the latest development version) -lib_deps = https://github.com/me-no-dev/ESPAsyncWebServer.git -``` - 5. Happy coding with PlatformIO! - -## Why should you care -- Using asynchronous network means that you can handle more than one connection at the same time -- You are called once the request is ready and parsed -- When you send the response, you are immediately ready to handle other connections - while the server is taking care of sending the response in the background -- Speed is OMG -- Easy to use API, HTTP Basic and Digest MD5 Authentication (default), ChunkedResponse -- Easily extendible to handle any type of content -- Supports Continue 100 -- Async WebSocket plugin offering different locations without extra servers or ports -- Async EventSource (Server-Sent Events) plugin to send events to the browser -- URL Rewrite plugin for conditional and permanent url rewrites -- ServeStatic plugin that supports cache, Last-Modified, default index and more -- Simple template processing engine to handle templates - -## Important things to remember -- This is fully asynchronous server and as such does not run on the loop thread. -- You can not use yield or delay or any function that uses them inside the callbacks -- The server is smart enough to know when to close the connection and free resources -- You can not send more than one response to a single request - -## Principles of operation - -### The Async Web server -- Listens for connections -- Wraps the new clients into ```Request``` -- Keeps track of clients and cleans memory -- Manages ```Rewrites``` and apply them on the request url -- Manages ```Handlers``` and attaches them to Requests - -### Request Life Cycle -- TCP connection is received by the server -- The connection is wrapped inside ```Request``` object -- When the request head is received (type, url, get params, http version and host), - the server goes through all ```Rewrites``` (in the order they were added) to rewrite the url and inject query parameters, - next, it goes through all attached ```Handlers```(in the order they were added) trying to find one - that ```canHandle``` the given request. If none are found, the default(catch-all) handler is attached. -- The rest of the request is received, calling the ```handleUpload``` or ```handleBody``` methods of the ```Handler``` if they are needed (POST+File/Body) -- When the whole request is parsed, the result is given to the ```handleRequest``` method of the ```Handler``` and is ready to be responded to -- In the ```handleRequest``` method, to the ```Request``` is attached a ```Response``` object (see below) that will serve the response data back to the client -- When the ```Response``` is sent, the client is closed and freed from the memory - -### Rewrites and how do they work -- The ```Rewrites``` are used to rewrite the request url and/or inject get parameters for a specific request url path. -- All ```Rewrites``` are evaluated on the request in the order they have been added to the server. -- The ```Rewrite``` will change the request url only if the request url (excluding get parameters) is fully match - the rewrite url, and when the optional ```Filter``` callback return true. -- Setting a ```Filter``` to the ```Rewrite``` enables to control when to apply the rewrite, decision can be based on - request url, http version, request host/port/target host, get parameters or the request client's localIP or remoteIP. -- Two filter callbacks are provided: ```ON_AP_FILTER``` to execute the rewrite when request is made to the AP interface, - ```ON_STA_FILTER``` to execute the rewrite when request is made to the STA interface. -- The ```Rewrite``` can specify a target url with optional get parameters, e.g. ```/to-url?with=params``` - -### Handlers and how do they work -- The ```Handlers``` are used for executing specific actions to particular requests -- One ```Handler``` instance can be attached to any request and lives together with the server -- Setting a ```Filter``` to the ```Handler``` enables to control when to apply the handler, decision can be based on - request url, http version, request host/port/target host, get parameters or the request client's localIP or remoteIP. -- Two filter callbacks are provided: ```ON_AP_FILTER``` to execute the rewrite when request is made to the AP interface, - ```ON_STA_FILTER``` to execute the rewrite when request is made to the STA interface. -- The ```canHandle``` method is used for handler specific control on whether the requests can be handled - and for declaring any interesting headers that the ```Request``` should parse. Decision can be based on request - method, request url, http version, request host/port/target host and get parameters -- Once a ```Handler``` is attached to given ```Request``` (```canHandle``` returned true) - that ```Handler``` takes care to receive any file/data upload and attach a ```Response``` - once the ```Request``` has been fully parsed -- ```Handlers``` are evaluated in the order they are attached to the server. The ```canHandle``` is called only - if the ```Filter``` that was set to the ```Handler``` return true. -- The first ```Handler``` that can handle the request is selected, not further ```Filter``` and ```canHandle``` are called. - -### Responses and how do they work -- The ```Response``` objects are used to send the response data back to the client -- The ```Response``` object lives with the ```Request``` and is freed on end or disconnect -- Different techniques are used depending on the response type to send the data in packets - returning back almost immediately and sending the next packet when this one is received. - Any time in between is spent to run the user loop and handle other network packets -- Responding asynchronously is probably the most difficult thing for most to understand -- Many different options exist for the user to make responding a background task - -### Template processing -- ESPAsyncWebserver contains simple template processing engine. -- Template processing can be added to most response types. -- Currently it supports only replacing template placeholders with actual values. No conditional processing, cycles, etc. -- Placeholders are delimited with ```%``` symbols. Like this: ```%TEMPLATE_PLACEHOLDER%```. -- It works by extracting placeholder name from response text and passing it to user provided function which should return actual value to be used instead of placeholder. -- Since it's user provided function, it is possible for library users to implement conditional processing and cycles themselves. -- Since it's impossible to know the actual response size after template processing step in advance (and, therefore, to include it in response headers), the response becomes [chunked](#chunked-response). - -## Libraries and projects that use AsyncWebServer -- [WebSocketToSerial](https://github.com/hallard/WebSocketToSerial) - Debug serial devices through the web browser -- [Sattrack](https://github.com/Hopperpop/Sattrack) - Track the ISS with ESP8266 -- [ESP Radio](https://github.com/Edzelf/Esp-radio) - Icecast radio based on ESP8266 and VS1053 -- [VZero](https://github.com/andig/vzero) - the Wireless zero-config controller for volkszaehler.org -- [ESPurna](https://bitbucket.org/xoseperez/espurna) - ESPurna ("spark" in Catalan) is a custom C firmware for ESP8266 based smart switches. It was originally developed with the ITead Sonoff in mind. -- [fauxmoESP](https://bitbucket.org/xoseperez/fauxmoesp) - Belkin WeMo emulator library for ESP8266. -- [ESP-RFID](https://github.com/omersiar/esp-rfid) - MFRC522 RFID Access Control Management project for ESP8266. - -## Request Variables - -### Common Variables -```cpp -request->version(); // uint8_t: 0 = HTTP/1.0, 1 = HTTP/1.1 -request->method(); // enum: HTTP_GET, HTTP_POST, HTTP_DELETE, HTTP_PUT, HTTP_PATCH, HTTP_HEAD, HTTP_OPTIONS -request->url(); // String: URL of the request (not including host, port or GET parameters) -request->host(); // String: The requested host (can be used for virtual hosting) -request->contentType(); // String: ContentType of the request (not avaiable in Handler::canHandle) -request->contentLength(); // size_t: ContentLength of the request (not avaiable in Handler::canHandle) -request->multipart(); // bool: True if the request has content type "multipart" -``` - -### Headers -```cpp -//List all collected headers -int headers = request->headers(); -int i; -for(i=0;igetHeader(i); - Serial.printf("HEADER[%s]: %s\n", h->name().c_str(), h->value().c_str()); -} - -//get specific header by name -if(request->hasHeader("MyHeader")){ - AsyncWebHeader* h = request->getHeader("MyHeader"); - Serial.printf("MyHeader: %s\n", h->value().c_str()); -} - -//List all collected headers (Compatibility) -int headers = request->headers(); -int i; -for(i=0;iheaderName(i).c_str(), request->header(i).c_str()); -} - -//get specific header by name (Compatibility) -if(request->hasHeader("MyHeader")){ - Serial.printf("MyHeader: %s\n", request->header("MyHeader").c_str()); -} -``` - -### GET, POST and FILE parameters -```cpp -//List all parameters -int params = request->params(); -for(int i=0;igetParam(i); - if(p->isFile()){ //p->isPost() is also true - Serial.printf("FILE[%s]: %s, size: %u\n", p->name().c_str(), p->value().c_str(), p->size()); - } else if(p->isPost()){ - Serial.printf("POST[%s]: %s\n", p->name().c_str(), p->value().c_str()); - } else { - Serial.printf("GET[%s]: %s\n", p->name().c_str(), p->value().c_str()); - } -} - -//Check if GET parameter exists -if(request->hasParam("download")) - AsyncWebParameter* p = request->getParam("download"); - -//Check if POST (but not File) parameter exists -if(request->hasParam("download", true)) - AsyncWebParameter* p = request->getParam("download", true); - -//Check if FILE was uploaded -if(request->hasParam("download", true, true)) - AsyncWebParameter* p = request->getParam("download", true, true); - -//List all parameters (Compatibility) -int args = request->args(); -for(int i=0;iargName(i).c_str(), request->arg(i).c_str()); -} - -//Check if parameter exists (Compatibility) -if(request->hasArg("download")) - String arg = request->arg("download"); -``` - -### FILE Upload handling -```cpp -void handleUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final){ - if(!index){ - Serial.printf("UploadStart: %s\n", filename.c_str()); - } - for(size_t i=0; i(); - // ... -}); -server.addHandler(handler); -``` - -## Responses -### Redirect to another URL -```cpp -//to local url -request->redirect("/login"); - -//to external url -request->redirect("http://esp8266.com"); -``` - -### Basic response with HTTP Code -```cpp -request->send(404); //Sends 404 File Not Found -``` - -### Basic response with HTTP Code and extra headers -```cpp -AsyncWebServerResponse *response = request->beginResponse(404); //Sends 404 File Not Found -response->addHeader("Server","ESP Async Web Server"); -request->send(response); -``` - -### Basic response with string content -```cpp -request->send(200, "text/plain", "Hello World!"); -``` - -### Basic response with string content and extra headers -```cpp -AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", "Hello World!"); -response->addHeader("Server","ESP Async Web Server"); -request->send(response); -``` - -### Send large webpage from PROGMEM -```cpp -const char index_html[] PROGMEM = "..."; // large char array, tested with 14k -request->send_P(200, "text/html", index_html); -``` - -### Send large webpage from PROGMEM and extra headers -```cpp -const char index_html[] PROGMEM = "..."; // large char array, tested with 14k -AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", index_html); -response->addHeader("Server","ESP Async Web Server"); -request->send(response); -``` - -### Send large webpage from PROGMEM containing templates -```cpp -String processor(const String& var) -{ - if(var == "HELLO_FROM_TEMPLATE") - return F("Hello world!"); - return String(); -} - -// ... - -const char index_html[] PROGMEM = "..."; // large char array, tested with 14k -request->send_P(200, "text/html", index_html, processor); -``` - -### Send large webpage from PROGMEM containing templates and extra headers -```cpp -String processor(const String& var) -{ - if(var == "HELLO_FROM_TEMPLATE") - return F("Hello world!"); - return String(); -} - -// ... - -const char index_html[] PROGMEM = "..."; // large char array, tested with 14k -AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", index_html, processor); -response->addHeader("Server","ESP Async Web Server"); -request->send(response); -``` - -### Send binary content from PROGMEM -```cpp - -//File: favicon.ico.gz, Size: 726 -#define favicon_ico_gz_len 726 -const uint8_t favicon_ico_gz[] PROGMEM = { - 0x1F, 0x8B, 0x08, 0x08, 0x0B, 0x87, 0x90, 0x57, 0x00, 0x03, 0x66, 0x61, 0x76, 0x69, 0x63, 0x6F, - 0x6E, 0x2E, 0x69, 0x63, 0x6F, 0x00, 0xCD, 0x53, 0x5F, 0x48, 0x9A, 0x51, 0x14, 0xBF, 0x62, 0x6D, - 0x86, 0x96, 0xA9, 0x64, 0xD3, 0xFE, 0xA8, 0x99, 0x65, 0x1A, 0xB4, 0x8A, 0xA8, 0x51, 0x54, 0x23, - 0xA8, 0x11, 0x49, 0x51, 0x8A, 0x34, 0x62, 0x93, 0x85, 0x31, 0x58, 0x44, 0x12, 0x45, 0x2D, 0x58, - 0xF5, 0x52, 0x41, 0x10, 0x23, 0x82, 0xA0, 0x20, 0x98, 0x2F, 0xC1, 0x26, 0xED, 0xA1, 0x20, 0x89, - 0x04, 0xD7, 0x83, 0x58, 0x20, 0x28, 0x04, 0xAB, 0xD1, 0x9B, 0x8C, 0xE5, 0xC3, 0x60, 0x32, 0x64, - 0x0E, 0x56, 0xBF, 0x9D, 0xEF, 0xF6, 0x30, 0x82, 0xED, 0xAD, 0x87, 0xDD, 0x8F, 0xF3, 0xDD, 0x8F, - 0x73, 0xCF, 0xEF, 0x9C, 0xDF, 0x39, 0xBF, 0xFB, 0x31, 0x26, 0xA2, 0x27, 0x37, 0x97, 0xD1, 0x5B, - 0xCF, 0x9E, 0x67, 0x30, 0xA6, 0x66, 0x8C, 0x99, 0xC9, 0xC8, 0x45, 0x9E, 0x6B, 0x3F, 0x5F, 0x74, - 0xA6, 0x94, 0x5E, 0xDB, 0xFF, 0xB2, 0xE6, 0xE7, 0xE7, 0xF9, 0xDE, 0xD6, 0xD6, 0x96, 0xDB, 0xD8, - 0xD8, 0x78, 0xBF, 0xA1, 0xA1, 0xC1, 0xDA, 0xDC, 0xDC, 0x2C, 0xEB, 0xED, 0xED, 0x15, 0x9B, 0xCD, - 0xE6, 0x4A, 0x83, 0xC1, 0xE0, 0x2E, 0x29, 0x29, 0x99, 0xD6, 0x6A, 0xB5, 0x4F, 0x75, 0x3A, 0x9D, - 0x61, 0x75, 0x75, 0x95, 0xB5, 0xB7, 0xB7, 0xDF, 0xC8, 0xD1, 0xD4, 0xD4, 0xF4, 0xB0, 0xBA, 0xBA, - 0xFA, 0x83, 0xD5, 0x6A, 0xFD, 0x5A, 0x5E, 0x5E, 0x9E, 0x28, 0x2D, 0x2D, 0x0D, 0x10, 0xC6, 0x4B, - 0x98, 0x78, 0x5E, 0x5E, 0xDE, 0x95, 0x42, 0xA1, 0x40, 0x4E, 0x4E, 0xCE, 0x65, 0x76, 0x76, 0xF6, - 0x47, 0xB5, 0x5A, 0x6D, 0x4F, 0x26, 0x93, 0xA2, 0xD6, 0xD6, 0x56, 0x8E, 0x6D, 0x69, 0x69, 0xD1, - 0x11, 0x36, 0x62, 0xB1, 0x58, 0x60, 0x32, 0x99, 0xA0, 0xD7, 0xEB, 0x51, 0x58, 0x58, 0x88, 0xFC, - 0xFC, 0x7C, 0x10, 0x16, 0x02, 0x56, 0x2E, 0x97, 0x43, 0x2A, 0x95, 0x42, 0x2C, 0x16, 0x23, 0x33, - 0x33, 0x33, 0xAE, 0x52, 0xA9, 0x1E, 0x64, 0x65, 0x65, 0x71, 0x7C, 0x7D, 0x7D, 0xBD, 0x93, 0xEA, - 0xFE, 0x30, 0x1A, 0x8D, 0xE8, 0xEC, 0xEC, 0xC4, 0xE2, 0xE2, 0x22, 0x6A, 0x6A, 0x6A, 0x40, 0x39, - 0x41, 0xB5, 0x38, 0x4E, 0xC8, 0x33, 0x3C, 0x3C, 0x0C, 0x87, 0xC3, 0xC1, 0x6B, 0x54, 0x54, 0x54, - 0xBC, 0xE9, 0xEB, 0xEB, 0x93, 0x5F, 0x5C, 0x5C, 0x30, 0x8A, 0x9D, 0x2E, 0x2B, 0x2B, 0xBB, 0xA2, - 0x3E, 0x41, 0xBD, 0x21, 0x1E, 0x8F, 0x63, 0x6A, 0x6A, 0x0A, 0x81, 0x40, 0x00, 0x94, 0x1B, 0x3D, - 0x3D, 0x3D, 0x42, 0x3C, 0x96, 0x96, 0x96, 0x70, 0x7E, 0x7E, 0x8E, 0xE3, 0xE3, 0x63, 0xF8, 0xFD, - 0xFE, 0xB4, 0xD7, 0xEB, 0xF5, 0x8F, 0x8F, 0x8F, 0x5B, 0x68, 0x5E, 0x6F, 0x05, 0xCE, 0xB4, 0xE3, - 0xE8, 0xE8, 0x08, 0x27, 0x27, 0x27, 0xD8, 0xDF, 0xDF, 0xC7, 0xD9, 0xD9, 0x19, 0x6C, 0x36, 0x1B, - 0x36, 0x36, 0x36, 0x38, 0x9F, 0x85, 0x85, 0x05, 0xAC, 0xAF, 0xAF, 0x23, 0x1A, 0x8D, 0x22, 0x91, - 0x48, 0x20, 0x16, 0x8B, 0xFD, 0xDA, 0xDA, 0xDA, 0x7A, 0x41, 0x33, 0x7E, 0x57, 0x50, 0x50, 0x80, - 0x89, 0x89, 0x09, 0x84, 0xC3, 0x61, 0x6C, 0x6F, 0x6F, 0x23, 0x12, 0x89, 0xE0, 0xE0, 0xE0, 0x00, - 0x43, 0x43, 0x43, 0x58, 0x5E, 0x5E, 0xE6, 0x9C, 0x7D, 0x3E, 0x1F, 0x46, 0x47, 0x47, 0x79, 0xBE, - 0xBD, 0xBD, 0x3D, 0xE1, 0x3C, 0x1D, 0x0C, 0x06, 0x9F, 0x10, 0xB7, 0xC7, 0x84, 0x4F, 0xF6, 0xF7, - 0xF7, 0x63, 0x60, 0x60, 0x00, 0x83, 0x83, 0x83, 0x18, 0x19, 0x19, 0xC1, 0xDC, 0xDC, 0x1C, 0x8F, - 0x17, 0x7C, 0xA4, 0x27, 0xE7, 0x34, 0x39, 0x39, 0x89, 0x9D, 0x9D, 0x1D, 0x6E, 0x54, 0xE3, 0x13, - 0xE5, 0x34, 0x11, 0x37, 0x49, 0x51, 0x51, 0xD1, 0x4B, 0xA5, 0x52, 0xF9, 0x45, 0x26, 0x93, 0x5D, - 0x0A, 0xF3, 0x92, 0x48, 0x24, 0xA0, 0x6F, 0x14, 0x17, 0x17, 0xA3, 0xB6, 0xB6, 0x16, 0x5D, 0x5D, - 0x5D, 0x7C, 0x1E, 0xBB, 0xBB, 0xBB, 0x9C, 0xD7, 0xE1, 0xE1, 0x21, 0x42, 0xA1, 0xD0, 0x6B, 0xD2, - 0x45, 0x4C, 0x33, 0x12, 0x34, 0xCC, 0xA0, 0x19, 0x54, 0x92, 0x56, 0x0E, 0xD2, 0xD9, 0x43, 0xF8, - 0xCF, 0x82, 0x56, 0xC2, 0xDC, 0xEB, 0xEA, 0xEA, 0x38, 0x7E, 0x6C, 0x6C, 0x4C, 0xE0, 0xFE, 0x9D, - 0xB8, 0xBF, 0xA7, 0xFA, 0xAF, 0x56, 0x56, 0x56, 0xEE, 0x6D, 0x6E, 0x6E, 0xDE, 0xB8, 0x47, 0x55, - 0x55, 0x55, 0x6C, 0x66, 0x66, 0x46, 0x44, 0xDA, 0x3B, 0x34, 0x1A, 0x4D, 0x94, 0xB0, 0x3F, 0x09, - 0x7B, 0x45, 0xBD, 0xA5, 0x5D, 0x2E, 0x57, 0x8C, 0x7A, 0x73, 0xD9, 0xED, 0xF6, 0x3B, 0x84, 0xFF, - 0xE7, 0x7D, 0xA6, 0x3A, 0x2C, 0x95, 0x4A, 0xB1, 0x8E, 0x8E, 0x0E, 0x6D, 0x77, 0x77, 0xB7, 0xCD, - 0xE9, 0x74, 0x3E, 0x73, 0xBB, 0xDD, 0x8F, 0x3C, 0x1E, 0x8F, 0xE6, 0xF4, 0xF4, 0x94, 0xAD, 0xAD, - 0xAD, 0xDD, 0xDE, 0xCF, 0x73, 0x0B, 0x0B, 0xB8, 0xB6, 0xE0, 0x5D, 0xC6, 0x66, 0xC5, 0xE4, 0x10, - 0x4C, 0xF4, 0xF7, 0xD8, 0x59, 0xF2, 0x7F, 0xA3, 0xB8, 0xB4, 0xFC, 0x0F, 0xEE, 0x37, 0x70, 0xEC, - 0x16, 0x4A, 0x7E, 0x04, 0x00, 0x00 -}; - -AsyncWebServerResponse *response = request->beginResponse_P(200, "image/x-icon", favicon_ico_gz, favicon_ico_gz_len); -response->addHeader("Content-Encoding", "gzip"); -request->send(response); -``` - -### Respond with content coming from a Stream -```cpp -//read 12 bytes from Serial and send them as Content Type text/plain -request->send(Serial, "text/plain", 12); -``` - -### Respond with content coming from a Stream and extra headers -```cpp -//read 12 bytes from Serial and send them as Content Type text/plain -AsyncWebServerResponse *response = request->beginResponse(Serial, "text/plain", 12); -response->addHeader("Server","ESP Async Web Server"); -request->send(response); -``` - -### Respond with content coming from a Stream containing templates -```cpp -String processor(const String& var) -{ - if(var == "HELLO_FROM_TEMPLATE") - return F("Hello world!"); - return String(); -} - -// ... - -//read 12 bytes from Serial and send them as Content Type text/plain -request->send(Serial, "text/plain", 12, processor); -``` - -### Respond with content coming from a Stream containing templates and extra headers -```cpp -String processor(const String& var) -{ - if(var == "HELLO_FROM_TEMPLATE") - return F("Hello world!"); - return String(); -} - -// ... - -//read 12 bytes from Serial and send them as Content Type text/plain -AsyncWebServerResponse *response = request->beginResponse(Serial, "text/plain", 12, processor); -response->addHeader("Server","ESP Async Web Server"); -request->send(response); -``` - -### Respond with content coming from a File -```cpp -//Send index.htm with default content type -request->send(SPIFFS, "/index.htm"); - -//Send index.htm as text -request->send(SPIFFS, "/index.htm", "text/plain"); - -//Download index.htm -request->send(SPIFFS, "/index.htm", String(), true); -``` - -### Respond with content coming from a File and extra headers -```cpp -//Send index.htm with default content type -AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/index.htm"); - -//Send index.htm as text -AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/index.htm", "text/plain"); - -//Download index.htm -AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/index.htm", String(), true); - -response->addHeader("Server","ESP Async Web Server"); -request->send(response); -``` - -### Respond with content coming from a File containing templates -Internally uses [Chunked Response](#chunked-response). - -Index.htm contents: -``` -%HELLO_FROM_TEMPLATE% -``` - -Somewhere in source files: -```cpp -String processor(const String& var) -{ - if(var == "HELLO_FROM_TEMPLATE") - return F("Hello world!"); - return String(); -} - -// ... - -//Send index.htm with template processor function -request->send(SPIFFS, "/index.htm", String(), false, processor); -``` - -### Respond with content using a callback -```cpp -//send 128 bytes as plain text -request->send("text/plain", 128, [](uint8_t *buffer, size_t maxLen, size_t index) -> size_t { - //Write up to "maxLen" bytes into "buffer" and return the amount written. - //index equals the amount of bytes that have been already sent - //You will not be asked for more bytes once the content length has been reached. - //Keep in mind that you can not delay or yield waiting for more data! - //Send what you currently have and you will be asked for more again - return mySource.read(buffer, maxLen); -}); -``` - -### Respond with content using a callback and extra headers -```cpp -//send 128 bytes as plain text -AsyncWebServerResponse *response = request->beginResponse("text/plain", 128, [](uint8_t *buffer, size_t maxLen, size_t index) -> size_t { - //Write up to "maxLen" bytes into "buffer" and return the amount written. - //index equals the amount of bytes that have been already sent - //You will not be asked for more bytes once the content length has been reached. - //Keep in mind that you can not delay or yield waiting for more data! - //Send what you currently have and you will be asked for more again - return mySource.read(buffer, maxLen); -}); -response->addHeader("Server","ESP Async Web Server"); -request->send(response); -``` - -### Respond with content using a callback containing templates -```cpp -String processor(const String& var) -{ - if(var == "HELLO_FROM_TEMPLATE") - return F("Hello world!"); - return String(); -} - -// ... - -//send 128 bytes as plain text -request->send("text/plain", 128, [](uint8_t *buffer, size_t maxLen, size_t index) -> size_t { - //Write up to "maxLen" bytes into "buffer" and return the amount written. - //index equals the amount of bytes that have been already sent - //You will not be asked for more bytes once the content length has been reached. - //Keep in mind that you can not delay or yield waiting for more data! - //Send what you currently have and you will be asked for more again - return mySource.read(buffer, maxLen); -}, processor); -``` - -### Respond with content using a callback containing templates and extra headers -```cpp -String processor(const String& var) -{ - if(var == "HELLO_FROM_TEMPLATE") - return F("Hello world!"); - return String(); -} - -// ... - -//send 128 bytes as plain text -AsyncWebServerResponse *response = request->beginResponse("text/plain", 128, [](uint8_t *buffer, size_t maxLen, size_t index) -> size_t { - //Write up to "maxLen" bytes into "buffer" and return the amount written. - //index equals the amount of bytes that have been already sent - //You will not be asked for more bytes once the content length has been reached. - //Keep in mind that you can not delay or yield waiting for more data! - //Send what you currently have and you will be asked for more again - return mySource.read(buffer, maxLen); -}, processor); -response->addHeader("Server","ESP Async Web Server"); -request->send(response); -``` - -### Chunked Response -Used when content length is unknown. Works best if the client supports HTTP/1.1 -```cpp -AsyncWebServerResponse *response = request->beginChunkedResponse("text/plain", [](uint8_t *buffer, size_t maxLen, size_t index) -> size_t { - //Write up to "maxLen" bytes into "buffer" and return the amount written. - //index equals the amount of bytes that have been already sent - //You will be asked for more data until 0 is returned - //Keep in mind that you can not delay or yield waiting for more data! - return mySource.read(buffer, maxLen); -}); -response->addHeader("Server","ESP Async Web Server"); -request->send(response); -``` - -### Chunked Response containing templates -Used when content length is unknown. Works best if the client supports HTTP/1.1 -```cpp -String processor(const String& var) -{ - if(var == "HELLO_FROM_TEMPLATE") - return F("Hello world!"); - return String(); -} - -// ... - -AsyncWebServerResponse *response = request->beginChunkedResponse("text/plain", [](uint8_t *buffer, size_t maxLen, size_t index) -> size_t { - //Write up to "maxLen" bytes into "buffer" and return the amount written. - //index equals the amount of bytes that have been already sent - //You will be asked for more data until 0 is returned - //Keep in mind that you can not delay or yield waiting for more data! - return mySource.read(buffer, maxLen); -}, processor); -response->addHeader("Server","ESP Async Web Server"); -request->send(response); -``` - -### Print to response -```cpp -AsyncResponseStream *response = request->beginResponseStream("text/html"); -response->addHeader("Server","ESP Async Web Server"); -response->printf("Webpage at %s", request->url().c_str()); - -response->print("

Hello "); -response->print(request->client()->remoteIP()); -response->print("

"); - -response->print("

General

"); -response->print("
    "); -response->printf("
  • Version: HTTP/1.%u
  • ", request->version()); -response->printf("
  • Method: %s
  • ", request->methodToString()); -response->printf("
  • URL: %s
  • ", request->url().c_str()); -response->printf("
  • Host: %s
  • ", request->host().c_str()); -response->printf("
  • ContentType: %s
  • ", request->contentType().c_str()); -response->printf("
  • ContentLength: %u
  • ", request->contentLength()); -response->printf("
  • Multipart: %s
  • ", request->multipart()?"true":"false"); -response->print("
"); - -response->print("

Headers

"); -response->print("
    "); -int headers = request->headers(); -for(int i=0;igetHeader(i); - response->printf("
  • %s: %s
  • ", h->name().c_str(), h->value().c_str()); -} -response->print("
"); - -response->print("

Parameters

"); -response->print("
    "); -int params = request->params(); -for(int i=0;igetParam(i); - if(p->isFile()){ - response->printf("
  • FILE[%s]: %s, size: %u
  • ", p->name().c_str(), p->value().c_str(), p->size()); - } else if(p->isPost()){ - response->printf("
  • POST[%s]: %s
  • ", p->name().c_str(), p->value().c_str()); - } else { - response->printf("
  • GET[%s]: %s
  • ", p->name().c_str(), p->value().c_str()); - } -} -response->print("
"); - -response->print(""); -//send the response last -request->send(response); -``` - -### ArduinoJson Basic Response -This way of sending Json is great for when the result is below 4KB -```cpp -#include "AsyncJson.h" -#include "ArduinoJson.h" - - -AsyncResponseStream *response = request->beginResponseStream("application/json"); -DynamicJsonBuffer jsonBuffer; -JsonObject &root = jsonBuffer.createObject(); -root["heap"] = ESP.getFreeHeap(); -root["ssid"] = WiFi.SSID(); -root.printTo(*response); -request->send(response); -``` - -### ArduinoJson Advanced Response -This response can handle really large Json objects (tested to 40KB) -There isn't any noticeable speed decrease for small results with the method above -Since ArduinoJson does not allow reading parts of the string, the whole Json has to -be passed every time a chunks needs to be sent, which shows speed decrease proportional -to the resulting json packets -```cpp -#include "AsyncJson.h" -#include "ArduinoJson.h" - - -AsyncJsonResponse * response = new AsyncJsonResponse(); -response->addHeader("Server","ESP Async Web Server"); -JsonObject& root = response->getRoot(); -root["heap"] = ESP.getFreeHeap(); -root["ssid"] = WiFi.SSID(); -response->setLength(); -request->send(response); -``` - -## Serving static files -In addition to serving files from SPIFFS as described above, the server provide a dedicated handler that optimize the -performance of serving files from SPIFFS - ```AsyncStaticWebHandler```. Use ```server.serveStatic()``` function to -initialize and add a new instance of ```AsyncStaticWebHandler``` to the server. -The Handler will not handle the request if the file does not exists, e.g. the server will continue to look for another -handler that can handle the request. -Notice that you can chain setter functions to setup the handler, or keep a pointer to change it at a later time. - -### Serving specific file by name -```cpp -// Serve the file "/www/page.htm" when request url is "/page.htm" -server.serveStatic("/page.htm", SPIFFS, "/www/page.htm"); -``` - -### Serving files in directory -To serve files in a directory, the path to the files should specify a directory in SPIFFS and ends with "/". -```cpp -// Serve files in directory "/www/" when request url starts with "/" -// Request to the root or none existing files will try to server the defualt -// file name "index.htm" if exists -server.serveStatic("/", SPIFFS, "/www/"); - -// Server with different default file -server.serveStatic("/", SPIFFS, "/www/").setDefaultFile("default.html"); -``` - -### Serving static files with authentication - -```cpp -server - .serveStatic("/", SPIFFS, "/www/") - .setDefaultFile("default.html") - .setAuthentication("user", "pass"); -``` - -### Specifying Cache-Control header -It is possible to specify Cache-Control header value to reduce the number of calls to the server once the client loaded -the files. For more information on Cache-Control values see [Cache-Control](https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9) -```cpp -// Cache responses for 10 minutes (600 seconds) -server.serveStatic("/", SPIFFS, "/www/").setCacheControl("max-age=600"); - -//*** Change Cache-Control after server setup *** - -// During setup - keep a pointer to the handler -AsyncStaticWebHandler* handler = &server.serveStatic("/", SPIFFS, "/www/").setCacheControl("max-age=600"); - -// At a later event - change Cache-Control -handler->setCacheControl("max-age=30"); -``` - -### Specifying Date-Modified header -It is possible to specify Date-Modified header to enable the server to return Not-Modified (304) response for requests -with "If-Modified-Since" header with the same value, instead of responding with the actual file content. -```cpp -// Update the date modified string every time files are updated -server.serveStatic("/", SPIFFS, "/www/").setLastModified("Mon, 20 Jun 2016 14:00:00 GMT"); - -//*** Chage last modified value at a later stage *** - -// During setup - read last modified value from config or EEPROM -String date_modified = loadDateModified(); -AsyncStaticWebHandler* handler = &server.serveStatic("/", SPIFFS, "/www/"); -handler->setLastModified(date_modified); - -// At a later event when files are updated -String date_modified = getNewDateModfied(); -saveDateModified(date_modified); // Save for next reset -handler->setLastModified(date_modified); -``` - -### Specifying Template Processor callback -It is possible to specify template processor for static files. For information on template processor see -[Respond with content coming from a File containing templates](#respond-with-content-coming-from-a-file-containing-templates). -```cpp -String processor(const String& var) -{ - if(var == "HELLO_FROM_TEMPLATE") - return F("Hello world!"); - return String(); -} - -// ... - -server.serveStatic("/", SPIFFS, "/www/").setTemplateProcessor(processor); -``` - -## Param Rewrite With Matching -It is possible to rewrite the request url with parameter matchg. Here is an example with one parameter: -Rewrite for example "/radio/{frequence}" -> "/radio?f={frequence}" - -```cpp -class OneParamRewrite : public AsyncWebRewrite -{ - protected: - String _urlPrefix; - int _paramIndex; - String _paramsBackup; - - public: - OneParamRewrite(const char* from, const char* to) - : AsyncWebRewrite(from, to) { - - _paramIndex = _from.indexOf('{'); - - if( _paramIndex >=0 && _from.endsWith("}")) { - _urlPrefix = _from.substring(0, _paramIndex); - int index = _params.indexOf('{'); - if(index >= 0) { - _params = _params.substring(0, index); - } - } else { - _urlPrefix = _from; - } - _paramsBackup = _params; - } - - bool match(AsyncWebServerRequest *request) override { - if(request->url().startsWith(_urlPrefix)) { - if(_paramIndex >= 0) { - _params = _paramsBackup + request->url().substring(_paramIndex); - } else { - _params = _paramsBackup; - } - return true; - - } else { - return false; - } - } -}; -``` - -Usage: - -```cpp - server.addRewrite( new OneParamRewrite("/radio/{frequence}", "/radio?f={frequence}") ); -``` - -## Using filters -Filters can be set to `Rewrite` or `Handler` in order to control when to apply the rewrite and consider the handler. -A filter is a callback function that evaluates the request and return a boolean `true` to include the item -or `false` to exclude it. -Two filter callback are provided for convince: -* `ON_STA_FILTER` - return true when requests are made to the STA (station mode) interface. -* `ON_AP_FILTER` - return true when requests are made to the AP (access point) interface. - -### Serve different site files in AP mode -```cpp -server.serveStatic("/", SPIFFS, "/www/").setFilter(ON_STA_FILTER); -server.serveStatic("/", SPIFFS, "/ap/").setFilter(ON_AP_FILTER); -``` - -### Rewrite to different index on AP -```cpp -// Serve the file "/www/index-ap.htm" in AP, and the file "/www/index.htm" on STA -server.rewrite("/", "index.htm"); -server.rewrite("/index.htm", "index-ap.htm").setFilter(ON_AP_FILTER); -server.serveStatic("/", SPIFFS, "/www/"); -``` - -### Serving different hosts -```cpp -// Filter callback using request host -bool filterOnHost1(AsyncWebServerRequest *request) { return request->host() == "host1"; } - -// Server setup: server files in "/host1/" to requests for "host1", and files in "/www/" otherwise. -server.serveStatic("/", SPIFFS, "/host1/").setFilter(filterOnHost1); -server.serveStatic("/", SPIFFS, "/www/"); -``` - -### Determine interface inside callbacks -```cpp - String RedirectUrl = "http://"; - if (ON_STA_FILTER(request)) { - RedirectUrl += WiFi.localIP().toString(); - } else { - RedirectUrl += WiFi.softAPIP().toString(); - } - RedirectUrl += "/index.htm"; - request->redirect(RedirectUrl); -``` - -## Bad Responses -Some responses are implemented, but you should not use them, because they do not conform to HTTP. -The following example will lead to unclean close of the connection and more time wasted -than providing the length of the content - -### Respond with content using a callback without content length to HTTP/1.0 clients -```cpp -//This is used as fallback for chunked responses to HTTP/1.0 Clients -request->send("text/plain", 0, [](uint8_t *buffer, size_t maxLen, size_t index) -> size_t { - //Write up to "maxLen" bytes into "buffer" and return the amount written. - //You will be asked for more data until 0 is returned - //Keep in mind that you can not delay or yield waiting for more data! - return mySource.read(buffer, maxLen); -}); -``` - -## Async WebSocket Plugin -The server includes a web socket plugin which lets you define different WebSocket locations to connect to -without starting another listening service or using different port - -### Async WebSocket Event -```cpp - -void onEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){ - if(type == WS_EVT_CONNECT){ - //client connected - os_printf("ws[%s][%u] connect\n", server->url(), client->id()); - client->printf("Hello Client %u :)", client->id()); - client->ping(); - } else if(type == WS_EVT_DISCONNECT){ - //client disconnected - os_printf("ws[%s][%u] disconnect: %u\n", server->url(), client->id()); - } else if(type == WS_EVT_ERROR){ - //error was received from the other end - os_printf("ws[%s][%u] error(%u): %s\n", server->url(), client->id(), *((uint16_t*)arg), (char*)data); - } else if(type == WS_EVT_PONG){ - //pong message was received (in response to a ping request maybe) - os_printf("ws[%s][%u] pong[%u]: %s\n", server->url(), client->id(), len, (len)?(char*)data:""); - } else if(type == WS_EVT_DATA){ - //data packet - AwsFrameInfo * info = (AwsFrameInfo*)arg; - if(info->final && info->index == 0 && info->len == len){ - //the whole message is in a single frame and we got all of it's data - os_printf("ws[%s][%u] %s-message[%llu]: ", server->url(), client->id(), (info->opcode == WS_TEXT)?"text":"binary", info->len); - if(info->opcode == WS_TEXT){ - data[len] = 0; - os_printf("%s\n", (char*)data); - } else { - for(size_t i=0; i < info->len; i++){ - os_printf("%02x ", data[i]); - } - os_printf("\n"); - } - if(info->opcode == WS_TEXT) - client->text("I got your text message"); - else - client->binary("I got your binary message"); - } else { - //message is comprised of multiple frames or the frame is split into multiple packets - if(info->index == 0){ - if(info->num == 0) - os_printf("ws[%s][%u] %s-message start\n", server->url(), client->id(), (info->message_opcode == WS_TEXT)?"text":"binary"); - os_printf("ws[%s][%u] frame[%u] start[%llu]\n", server->url(), client->id(), info->num, info->len); - } - - os_printf("ws[%s][%u] frame[%u] %s[%llu - %llu]: ", server->url(), client->id(), info->num, (info->message_opcode == WS_TEXT)?"text":"binary", info->index, info->index + len); - if(info->message_opcode == WS_TEXT){ - data[len] = 0; - os_printf("%s\n", (char*)data); - } else { - for(size_t i=0; i < len; i++){ - os_printf("%02x ", data[i]); - } - os_printf("\n"); - } - - if((info->index + len) == info->len){ - os_printf("ws[%s][%u] frame[%u] end[%llu]\n", server->url(), client->id(), info->num, info->len); - if(info->final){ - os_printf("ws[%s][%u] %s-message end\n", server->url(), client->id(), (info->message_opcode == WS_TEXT)?"text":"binary"); - if(info->message_opcode == WS_TEXT) - client->text("I got your text message"); - else - client->binary("I got your binary message"); - } - } - } - } -} -``` - -### Methods for sending data to a socket client -```cpp - - - -//Server methods -AsyncWebSocket ws("/ws"); -//printf to a client -ws.printf((uint32_t)client_id, arguments...); -//printf to all clients -ws.printfAll(arguments...); -//printf_P to a client -ws.printf_P((uint32_t)client_id, PSTR(format), arguments...); -//printfAll_P to all clients -ws.printfAll_P(PSTR(format), arguments...); -//send text to a client -ws.text((uint32_t)client_id, (char*)text); -ws.text((uint32_t)client_id, (uint8_t*)text, (size_t)len); -//send text from PROGMEM to a client -ws.text((uint32_t)client_id, PSTR("text")); -const char flash_text[] PROGMEM = "Text to send" -ws.text((uint32_t)client_id, FPSTR(flash_text)); -//send text to all clients -ws.textAll((char*)text); -ws.textAll((uint8_t*)text, (size_t)len); -//send binary to a client -ws.binary((uint32_t)client_id, (char*)binary); -ws.binary((uint32_t)client_id, (uint8_t*)binary, (size_t)len); -//send binary from PROGMEM to a client -const uint8_t flash_binary[] PROGMEM = { 0x01, 0x02, 0x03, 0x04 }; -ws.binary((uint32_t)client_id, flash_binary, 4); -//send binary to all clients -ws.binaryAll((char*)binary); -ws.binaryAll((uint8_t*)binary, (size_t)len); -//HTTP Authenticate before switch to Websocket protocol -ws.setAuthentication("user", "pass"); - -//client methods -AsyncWebSocketClient * client; -//printf -client->printf(arguments...); -//printf_P -client->printf_P(PSTR(format), arguments...); -//send text -client->text((char*)text); -client->text((uint8_t*)text, (size_t)len); -//send text from PROGMEM -client->text(PSTR("text")); -const char flash_text[] PROGMEM = "Text to send"; -client->text(FPSTR(flash_text)); -//send binary -client->binary((char*)binary); -client->binary((uint8_t*)binary, (size_t)len); -//send binary from PROGMEM -const uint8_t flash_binary[] PROGMEM = { 0x01, 0x02, 0x03, 0x04 }; -client->binary(flash_binary, 4); -``` - -### Direct access to web socket message buffer -When sending a web socket message using the above methods a buffer is created. Under certain circumstances you might want to manipulate or populate this buffer directly from your application, for example to prevent unnecessary duplications of the data. This example below shows how to create a buffer and print data to it from an ArduinoJson object then send it. - -```cpp -void sendDataWs(AsyncWebSocketClient * client) -{ - DynamicJsonBuffer jsonBuffer; - JsonObject& root = jsonBuffer.createObject(); - root["a"] = "abc"; - root["b"] = "abcd"; - root["c"] = "abcde"; - root["d"] = "abcdef"; - root["e"] = "abcdefg"; - size_t len = root.measureLength(); - AsyncWebSocketMessageBuffer * buffer = ws.makeBuffer(len); // creates a buffer (len + 1) for you. - if (buffer) { - root.printTo((char *)buffer->get(), len + 1); - if (client) { - client->text(buffer); - } else { - ws.textAll(buffer); - } - } -} -``` - -### Limiting the number of web socket clients -Browsers sometimes do not correctly close the websocket connection, even when the close() function is called in javascript. This will eventually exhaust the web server's resources and will cause the server to crash. Periodically calling the cleanClients() function from the main loop() function limits the number of clients by closing the oldest client when the maximum number of clients has been exceeded. This can called be every cycle, however, if you wish to use less power, then calling as infrequently as once per second is sufficient. - -```cpp -void loop(){ - ws.cleanupClients(); -} -``` - - -## Async Event Source Plugin -The server includes EventSource (Server-Sent Events) plugin which can be used to send short text events to the browser. -Difference between EventSource and WebSockets is that EventSource is single direction, text-only protocol. - -### Setup Event Source on the server -```cpp -AsyncWebServer server(80); -AsyncEventSource events("/events"); - -void setup(){ - // setup ...... - events.onConnect([](AsyncEventSourceClient *client){ - if(client->lastId()){ - Serial.printf("Client reconnected! Last message ID that it gat is: %u\n", client->lastId()); - } - //send event with message "hello!", id current millis - // and set reconnect delay to 1 second - client->send("hello!",NULL,millis(),1000); - }); - //HTTP Basic authentication - events.setAuthentication("user", "pass"); - server.addHandler(&events); - // setup ...... -} - -void loop(){ - if(eventTriggered){ // your logic here - //send event "myevent" - events.send("my event content","myevent",millis()); - } -} -``` - -### Setup Event Source in the browser -```javascript -if (!!window.EventSource) { - var source = new EventSource('/events'); - - source.addEventListener('open', function(e) { - console.log("Events Connected"); - }, false); - - source.addEventListener('error', function(e) { - if (e.target.readyState != EventSource.OPEN) { - console.log("Events Disconnected"); - } - }, false); - - source.addEventListener('message', function(e) { - console.log("message", e.data); - }, false); - - source.addEventListener('myevent', function(e) { - console.log("myevent", e.data); - }, false); -} -``` - -## Scanning for available WiFi Networks -```cpp -//First request will return 0 results unless you start scan from somewhere else (loop/setup) -//Do not request more often than 3-5 seconds -server.on("/scan", HTTP_GET, [](AsyncWebServerRequest *request){ - String json = "["; - int n = WiFi.scanComplete(); - if(n == -2){ - WiFi.scanNetworks(true); - } else if(n){ - for (int i = 0; i < n; ++i){ - if(i) json += ","; - json += "{"; - json += "\"rssi\":"+String(WiFi.RSSI(i)); - json += ",\"ssid\":\""+WiFi.SSID(i)+"\""; - json += ",\"bssid\":\""+WiFi.BSSIDstr(i)+"\""; - json += ",\"channel\":"+String(WiFi.channel(i)); - json += ",\"secure\":"+String(WiFi.encryptionType(i)); - json += ",\"hidden\":"+String(WiFi.isHidden(i)?"true":"false"); - json += "}"; - } - WiFi.scanDelete(); - if(WiFi.scanComplete() == -2){ - WiFi.scanNetworks(true); - } - } - json += "]"; - request->send(200, "application/json", json); - json = String(); -}); -``` - -## Remove handlers and rewrites - -Server goes through handlers in same order as they were added. You can't simple add handler with same path to override them. -To remove handler: -```arduino -// save callback for particular URL path -auto handler = server.on("/some/path", [](AsyncWebServerRequest *request){ - //do something useful -}); -// when you don't need handler anymore remove it -server.removeHandler(&handler); - -// same with rewrites -server.removeRewrite(&someRewrite); - -server.onNotFound([](AsyncWebServerRequest *request){ - request->send(404); -}); - -// remove server.onNotFound handler -server.onNotFound(NULL); - -// remove all rewrites, handlers and onNotFound/onFileUpload/onRequestBody callbacks -server.reset(); -``` - -## Setting up the server -```cpp -#include "ESPAsyncTCP.h" -#include "ESPAsyncWebServer.h" - -AsyncWebServer server(80); -AsyncWebSocket ws("/ws"); // access at ws://[esp ip]/ws -AsyncEventSource events("/events"); // event source (Server-Sent events) - -const char* ssid = "your-ssid"; -const char* password = "your-pass"; -const char* http_username = "admin"; -const char* http_password = "admin"; - -//flag to use from web update to reboot the ESP -bool shouldReboot = false; - -void onRequest(AsyncWebServerRequest *request){ - //Handle Unknown Request - request->send(404); -} - -void onBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total){ - //Handle body -} - -void onUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final){ - //Handle upload -} - -void onEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){ - //Handle WebSocket event -} - -void setup(){ - Serial.begin(115200); - WiFi.mode(WIFI_STA); - WiFi.begin(ssid, password); - if (WiFi.waitForConnectResult() != WL_CONNECTED) { - Serial.printf("WiFi Failed!\n"); - return; - } - - // attach AsyncWebSocket - ws.onEvent(onEvent); - server.addHandler(&ws); - - // attach AsyncEventSource - server.addHandler(&events); - - // respond to GET requests on URL /heap - server.on("/heap", HTTP_GET, [](AsyncWebServerRequest *request){ - request->send(200, "text/plain", String(ESP.getFreeHeap())); - }); - - // upload a file to /upload - server.on("/upload", HTTP_POST, [](AsyncWebServerRequest *request){ - request->send(200); - }, onUpload); - - // send a file when /index is requested - server.on("/index", HTTP_ANY, [](AsyncWebServerRequest *request){ - request->send(SPIFFS, "/index.htm"); - }); - - // HTTP basic authentication - server.on("/login", HTTP_GET, [](AsyncWebServerRequest *request){ - if(!request->authenticate(http_username, http_password)) - return request->requestAuthentication(); - request->send(200, "text/plain", "Login Success!"); - }); - - // Simple Firmware Update Form - server.on("/update", HTTP_GET, [](AsyncWebServerRequest *request){ - request->send(200, "text/html", "
"); - }); - server.on("/update", HTTP_POST, [](AsyncWebServerRequest *request){ - shouldReboot = !Update.hasError(); - AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", shouldReboot?"OK":"FAIL"); - response->addHeader("Connection", "close"); - request->send(response); - },[](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final){ - if(!index){ - Serial.printf("Update Start: %s\n", filename.c_str()); - Update.runAsync(true); - if(!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000)){ - Update.printError(Serial); - } - } - if(!Update.hasError()){ - if(Update.write(data, len) != len){ - Update.printError(Serial); - } - } - if(final){ - if(Update.end(true)){ - Serial.printf("Update Success: %uB\n", index+len); - } else { - Update.printError(Serial); - } - } - }); - - // attach filesystem root at URL /fs - server.serveStatic("/fs", SPIFFS, "/"); - - // Catch-All Handlers - // Any request that can not find a Handler that canHandle it - // ends in the callbacks below. - server.onNotFound(onRequest); - server.onFileUpload(onUpload); - server.onRequestBody(onBody); - - server.begin(); -} - -void loop(){ - if(shouldReboot){ - Serial.println("Rebooting..."); - delay(100); - ESP.restart(); - } - static char temp[128]; - sprintf(temp, "Seconds since boot: %u", millis()/1000); - events.send(temp, "time"); //send event "time" -} -``` - -### Setup global and class functions as request handlers - -```cpp -#include -#include -#include -#include - -void handleRequest(AsyncWebServerRequest *request){} - -class WebClass { -public : - AsyncWebServer classWebServer = AsyncWebServer(81); - - WebClass(){}; - - void classRequest (AsyncWebServerRequest *request){} - - void begin(){ - // attach global request handler - classWebServer.on("/example", HTTP_ANY, handleRequest); - - // attach class request handler - classWebServer.on("/example", HTTP_ANY, std::bind(&WebClass::classRequest, this, std::placeholders::_1)); - } -}; - -AsyncWebServer globalWebServer(80); -WebClass webClassInstance; - -void setup() { - // attach global request handler - globalWebServer.on("/example", HTTP_ANY, handleRequest); - - // attach class request handler - globalWebServer.on("/example", HTTP_ANY, std::bind(&WebClass::classRequest, webClassInstance, std::placeholders::_1)); -} - -void loop() { - -} -``` - -### Methods for controlling websocket connections - -```cpp - // Disable client connections if it was activated - if ( ws.enabled() ) - ws.enable(false); - - // enable client connections if it was disabled - if ( !ws.enabled() ) - ws.enable(true); -``` - -Example of OTA code - -```cpp - // OTA callbacks - ArduinoOTA.onStart([]() { - // Clean SPIFFS - SPIFFS.end(); - - // Disable client connections - ws.enable(false); - - // Advertise connected clients what's going on - ws.textAll("OTA Update Started"); - - // Close them - ws.closeAll(); - - }); - -``` - -### Adding Default Headers - -In some cases, such as when working with CORS, or with some sort of custom authentication system, -you might need to define a header that should get added to all responses (including static, websocket and EventSource). -The DefaultHeaders singleton allows you to do this. - -Example: - -```cpp -DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*"); -webServer.begin(); -``` - -*NOTE*: You will still need to respond to the OPTIONS method for CORS pre-flight in most cases. (unless you are only using GET) - -This is one option: - -```cpp -webServer.onNotFound([](AsyncWebServerRequest *request) { - if (request->method() == HTTP_OPTIONS) { - request->send(200); - } else { - request->send(404); - } -}); -``` - -### Path variable - -With path variable you can create a custom regex rule for a specific parameter in a route. -For example we want a `sensorId` parameter in a route rule to match only a integer. - -```cpp - server.on("^\\/sensor\\/([0-9]+)$", HTTP_GET, [] (AsyncWebServerRequest *request) { - String sensorId = request->pathArg(0); - }); -``` -*NOTE*: All regex patterns starts with `^` and ends with `$` - -To enable the `Path variable` support, you have to define the buildflag `-DASYNCWEBSERVER_REGEX`. - - -For Arduino IDE create/update `platform.local.txt`: - -`Windows`: C:\Users\(username)\AppData\Local\Arduino15\packages\\`{espxxxx}`\hardware\\`espxxxx`\\`{version}`\platform.local.txt - -`Linux`: ~/.arduino15/packages/`{espxxxx}`/hardware/`{espxxxx}`/`{version}`/platform.local.txt - -Add/Update the following line: -``` - compiler.cpp.extra_flags=-DDASYNCWEBSERVER_REGEX -``` - -For platformio modify `platformio.ini`: -```ini -[env:myboard] -build_flags = - -DASYNCWEBSERVER_REGEX -``` -*NOTE*: By enabling `ASYNCWEBSERVER_REGEX`, `` will be included. This will add an 100k to your binary. diff --git a/lib/ESPAsyncWebServer/SPIFFSEditor.cpp b/lib/ESPAsyncWebServer/SPIFFSEditor.cpp deleted file mode 100644 index a84fa87dd..000000000 --- a/lib/ESPAsyncWebServer/SPIFFSEditor.cpp +++ /dev/null @@ -1,544 +0,0 @@ -#include "SPIFFSEditor.h" -#include - -//File: edit.htm.gz, Size: 4151 -#define edit_htm_gz_len 4151 -const uint8_t edit_htm_gz[] PROGMEM = { - 0x1F, 0x8B, 0x08, 0x08, 0xB8, 0x94, 0xB1, 0x59, 0x00, 0x03, 0x65, 0x64, 0x69, 0x74, 0x2E, 0x68, - 0x74, 0x6D, 0x00, 0xB5, 0x3A, 0x0B, 0x7B, 0xDA, 0xB8, 0xB2, 0x7F, 0xC5, 0x71, 0xCF, 0x66, 0xED, - 0x83, 0x31, 0x90, 0xA4, 0xD9, 0xD6, 0xC4, 0xC9, 0x42, 0x92, 0x36, 0x6D, 0xF3, 0x6A, 0x80, 0xB6, - 0x69, 0x4F, 0xEE, 0x7E, 0xC2, 0x16, 0xA0, 0xC6, 0x96, 0x5D, 0x5B, 0x0E, 0x49, 0x59, 0xFE, 0xFB, - 0x9D, 0x91, 0x6C, 0xB0, 0x09, 0x69, 0x77, 0xCF, 0xBD, 0xBB, 0xDD, 0x2D, 0x92, 0x46, 0x33, 0x9A, - 0x19, 0xCD, 0x53, 0xDE, 0xBD, 0x8D, 0xA3, 0x8B, 0xC3, 0xFE, 0xF5, 0xE5, 0xB1, 0x36, 0x11, 0x61, - 0xB0, 0xBF, 0x87, 0x7F, 0x6B, 0x01, 0xE1, 0x63, 0x97, 0xF2, 0xFD, 0x3D, 0xC1, 0x44, 0x40, 0xF7, - 0x8F, 0x7B, 0x97, 0xDA, 0xB1, 0xCF, 0x44, 0x94, 0xEC, 0x35, 0xD4, 0xCA, 0x5E, 0x2A, 0x1E, 0x02, - 0xAA, 0x85, 0xD4, 0x67, 0xC4, 0x4D, 0xBD, 0x84, 0xC2, 0x66, 0xDB, 0x0B, 0x67, 0xDF, 0xEB, 0x8C, - 0xFB, 0xF4, 0xDE, 0xD9, 0x6E, 0x36, 0xDB, 0x71, 0x94, 0x32, 0xC1, 0x22, 0xEE, 0x90, 0x61, 0x1A, - 0x05, 0x99, 0xA0, 0xED, 0x80, 0x8E, 0x84, 0xF3, 0x3C, 0xBE, 0x6F, 0x0F, 0xA3, 0xC4, 0xA7, 0x89, - 0xD3, 0x8A, 0xEF, 0x35, 0x00, 0x31, 0x5F, 0x7B, 0xB6, 0xB3, 0xB3, 0xD3, 0x1E, 0x12, 0xEF, 0x76, - 0x9C, 0x44, 0x19, 0xF7, 0xEB, 0x5E, 0x14, 0x44, 0x89, 0xF3, 0x6C, 0xF4, 0x1C, 0xFF, 0xB4, 0x7D, - 0x96, 0xC6, 0x01, 0x79, 0x70, 0x78, 0xC4, 0x29, 0xE0, 0xDE, 0xD7, 0xD3, 0x09, 0xF1, 0xA3, 0xA9, - 0xD3, 0xD4, 0x9A, 0x5A, 0xAB, 0x09, 0x44, 0x92, 0xF1, 0x90, 0x18, 0x4D, 0x0B, 0xFF, 0xD8, 0x3B, - 0x66, 0x7B, 0x14, 0x71, 0x51, 0x4F, 0xD9, 0x77, 0xEA, 0xB4, 0xB6, 0xE0, 0x34, 0x39, 0x1D, 0x91, - 0x90, 0x05, 0x0F, 0x4E, 0x4A, 0x78, 0x5A, 0x4F, 0x69, 0xC2, 0x46, 0x6A, 0x79, 0x4A, 0xD9, 0x78, - 0x22, 0x9C, 0xDF, 0x9A, 0xCD, 0x39, 0xF0, 0xAF, 0x65, 0xC1, 0x2C, 0x60, 0x29, 0x20, 0xA3, 0x78, - 0xEA, 0x3C, 0x11, 0xC5, 0x4E, 0x53, 0xB1, 0xDE, 0x6C, 0x87, 0x24, 0x19, 0x33, 0x0E, 0x83, 0x98, - 0xF8, 0x3E, 0xE3, 0x63, 0x47, 0xA1, 0x05, 0x6C, 0xB6, 0x90, 0x36, 0xA1, 0x01, 0x11, 0xEC, 0x8E, - 0xB6, 0x43, 0xC6, 0xEB, 0x53, 0xE6, 0x8B, 0x89, 0xB3, 0x0B, 0x3C, 0xB6, 0xBD, 0x2C, 0x49, 0x41, - 0xA6, 0x38, 0x62, 0x5C, 0xD0, 0x44, 0xA2, 0xA5, 0x31, 0xE1, 0xB3, 0x5C, 0x54, 0x54, 0x40, 0x21, - 0x27, 0xE3, 0x01, 0xE3, 0xB4, 0x3E, 0x0C, 0x22, 0xEF, 0x76, 0x71, 0xD2, 0x6E, 0x7C, 0x9F, 0x9F, - 0xE5, 0x4C, 0xA2, 0x3B, 0x9A, 0xCC, 0x96, 0xEA, 0x92, 0xD8, 0x15, 0x60, 0x85, 0x34, 0xA5, 0x74, - 0x6E, 0x8B, 0xBB, 0x0C, 0xA0, 0x96, 0xFC, 0x05, 0x29, 0x17, 0xFC, 0x2F, 0x45, 0x5A, 0x11, 0x5C, - 0xA1, 0x30, 0x1E, 0x67, 0x62, 0xF6, 0xF8, 0x2A, 0xA3, 0x98, 0x78, 0x4C, 0x3C, 0xA0, 0xFC, 0xB0, - 0x6D, 0x86, 0xBA, 0x04, 0xAC, 0x24, 0x24, 0x81, 0x86, 0x3A, 0xD7, 0x3E, 0xD0, 0xC4, 0x27, 0x9C, - 0x58, 0x9D, 0x84, 0x91, 0xC0, 0xEA, 0x2D, 0xB5, 0x5E, 0x0F, 0xA3, 0xEF, 0xF5, 0x0C, 0xC6, 0x30, - 0x0F, 0xA8, 0x27, 0x94, 0x92, 0xE1, 0x1E, 0x86, 0xB7, 0x4C, 0x3C, 0x06, 0x3C, 0x5A, 0x28, 0xA9, - 0x4B, 0x2A, 0x69, 0xA2, 0x2E, 0xB0, 0x25, 0xD5, 0x83, 0x1C, 0x4B, 0xC9, 0x95, 0x50, 0xF5, 0x61, - 0x24, 0x44, 0x14, 0x4A, 0x93, 0x5B, 0x08, 0xAC, 0x49, 0xAB, 0x79, 0xF1, 0xE8, 0x46, 0xD6, 0x6B, - 0xBF, 0x44, 0xBE, 0x0D, 0x7A, 0x15, 0xCC, 0x23, 0x41, 0x9D, 0x04, 0x6C, 0xCC, 0x9D, 0x90, 0xF9, - 0x7E, 0x40, 0x4B, 0x56, 0xEB, 0x64, 0x49, 0x60, 0xF8, 0x44, 0x10, 0x87, 0x85, 0x64, 0x4C, 0x1B, - 0x31, 0x1F, 0x03, 0x34, 0xA5, 0xBB, 0x3B, 0x16, 0xFB, 0xD0, 0xBD, 0xB8, 0x9A, 0x36, 0xDF, 0xBD, - 0x1E, 0x47, 0x1D, 0xF8, 0xE7, 0xBC, 0x37, 0x98, 0x1C, 0x0F, 0xC6, 0x30, 0xEA, 0xE2, 0xB4, 0xF3, - 0xFE, 0xB0, 0xF3, 0x1E, 0x7E, 0x0E, 0x5B, 0xB5, 0xAF, 0xA3, 0x6F, 0xB8, 0xD0, 0x7D, 0xED, 0x77, - 0xFB, 0x83, 0xE3, 0x4E, 0xE7, 0x5D, 0xE3, 0xCD, 0xF9, 0xF4, 0xE3, 0xBB, 0x5D, 0x04, 0x77, 0x83, - 0xE6, 0xD5, 0x87, 0x49, 0x73, 0xB0, 0xF5, 0x32, 0xF4, 0x4F, 0xFC, 0x89, 0x17, 0x0E, 0x3A, 0xEF, - 0x3F, 0x5E, 0xDD, 0x5D, 0x87, 0x83, 0x71, 0xEF, 0x63, 0x6B, 0xF2, 0x79, 0xEB, 0x43, 0xEF, 0xF3, - 0xC7, 0x57, 0xB7, 0xF4, 0xD3, 0xC9, 0xDB, 0xCF, 0xFD, 0x29, 0x20, 0x1C, 0x45, 0xBD, 0xC1, 0x55, - 0xF7, 0x43, 0x77, 0xFC, 0xB9, 0xEB, 0x1D, 0xDF, 0x0F, 0x83, 0xF3, 0xEE, 0xEB, 0xCE, 0xB0, 0xB3, - 0xE5, 0x51, 0x3A, 0xEE, 0x5F, 0x75, 0xB3, 0x37, 0xEF, 0x2E, 0xC6, 0x8C, 0x4D, 0x7A, 0x9F, 0xCF, - 0xFB, 0xDE, 0xE1, 0xF3, 0xD3, 0xC1, 0x49, 0x87, 0x4D, 0xCE, 0xDF, 0x5E, 0x35, 0x6F, 0x5F, 0xBF, - 0x3B, 0x3C, 0xF2, 0xAE, 0xDF, 0x5E, 0xEF, 0x1E, 0x6D, 0x37, 0x7E, 0xFB, 0xED, 0xCC, 0xBF, 0x60, - 0xBC, 0x7F, 0xF7, 0xBD, 0x33, 0x3E, 0x9C, 0xBE, 0x78, 0x48, 0xFB, 0x93, 0x37, 0x77, 0xBC, 0xF1, - 0x21, 0xFA, 0xFA, 0xE6, 0xE1, 0x0C, 0xFE, 0xBB, 0xBC, 0xAC, 0x0D, 0x7B, 0xAD, 0x74, 0xF0, 0xFE, - 0xCD, 0x87, 0xAD, 0xF4, 0xE5, 0xF3, 0xB8, 0x7B, 0x74, 0x74, 0x17, 0x0E, 0x2F, 0x1B, 0xA1, 0x7F, - 0x3B, 0x12, 0x2F, 0xB6, 0x45, 0x7C, 0x3D, 0xCE, 0x3E, 0x7F, 0x7B, 0xFE, 0x76, 0xD2, 0xB8, 0xA0, - 0xE4, 0x7A, 0x52, 0x7B, 0xF8, 0xFE, 0xF0, 0x62, 0xD2, 0x3F, 0xB9, 0x3B, 0x0F, 0xC8, 0xFD, 0xF9, - 0xB9, 0xF7, 0x3D, 0xAC, 0x05, 0xE4, 0xE5, 0x45, 0x3F, 0x20, 0x49, 0x6B, 0xE0, 0x77, 0x1A, 0xB5, - 0xC3, 0xAD, 0xCE, 0x8E, 0x48, 0xAE, 0x0E, 0xF9, 0xD1, 0xF6, 0xD7, 0xDE, 0x8B, 0x6E, 0xB7, 0x15, - 0x0D, 0xBF, 0x6D, 0xBD, 0xBE, 0xDD, 0x7D, 0x3D, 0xD8, 0x7D, 0x3F, 0x7C, 0xDF, 0xE9, 0xED, 0x74, - 0x07, 0xE4, 0xBA, 0xF7, 0xBE, 0x33, 0xDA, 0x19, 0x4E, 0x26, 0xEF, 0xDE, 0xF5, 0x5F, 0xF9, 0x9D, - 0xEF, 0x49, 0xE7, 0x62, 0xDA, 0xB9, 0x3F, 0x1E, 0x74, 0x4E, 0x6A, 0xEF, 0x8E, 0xCF, 0x9A, 0xAD, - 0xDE, 0xF5, 0xF6, 0xF8, 0x6C, 0x77, 0xDA, 0x4D, 0x8F, 0x3B, 0xEF, 0xBB, 0xCD, 0xF1, 0xDB, 0x5A, - 0x48, 0x3E, 0x47, 0x87, 0xDB, 0xE3, 0x37, 0xBB, 0xEC, 0xF2, 0x9A, 0x74, 0xDE, 0x74, 0xDF, 0xA6, - 0xEC, 0x2A, 0x3C, 0x19, 0x34, 0x3B, 0x9D, 0xD3, 0x0B, 0xFA, 0xEA, 0x70, 0x9B, 0xBC, 0xDB, 0xF2, - 0x3E, 0x82, 0xFE, 0x07, 0x9F, 0xE8, 0x6F, 0xB5, 0xCE, 0xF4, 0xA2, 0x19, 0x78, 0x2F, 0x69, 0xFF, - 0xE4, 0xBA, 0x2F, 0x6F, 0xE7, 0x38, 0x78, 0xD5, 0xBF, 0xED, 0x65, 0xEF, 0xC3, 0xC3, 0x43, 0x53, - 0xE3, 0x51, 0x3D, 0xA1, 0x31, 0x25, 0xA2, 0x1C, 0xAE, 0x16, 0xFE, 0x01, 0xB6, 0xB5, 0xB4, 0xC2, - 0xDC, 0x4F, 0x05, 0xBD, 0x17, 0x75, 0x9F, 0x7A, 0x51, 0x42, 0xE4, 0x1E, 0x40, 0xA0, 0x09, 0x9A, - 0xD8, 0xFC, 0x77, 0x19, 0x3F, 0x35, 0x15, 0x3F, 0x35, 0xC2, 0x7D, 0xCD, 0x28, 0x1C, 0x01, 0x83, - 0x87, 0x4F, 0xEF, 0x98, 0x47, 0xEB, 0x31, 0xBB, 0xA7, 0x41, 0x5D, 0x22, 0x3B, 0x4D, 0x73, 0x26, - 0xFD, 0xAD, 0xD8, 0x46, 0x38, 0x98, 0x9A, 0xA4, 0x5A, 0x2C, 0xF8, 0x5F, 0x89, 0x47, 0x21, 0xB0, - 0x81, 0xCB, 0x84, 0xF8, 0xAB, 0x7C, 0x27, 0x4A, 0xEA, 0xC3, 0x6C, 0x3C, 0x62, 0xF7, 0xE0, 0xD0, - 0x23, 0xC6, 0x99, 0xA0, 0x5A, 0x2B, 0x9D, 0xFF, 0x5E, 0x90, 0xB9, 0xA5, 0x0F, 0xA3, 0x84, 0x84, - 0x34, 0xD5, 0xFE, 0x22, 0x99, 0xD9, 0x28, 0x89, 0xC2, 0x65, 0x10, 0x99, 0x8B, 0xA8, 0x34, 0x99, - 0xCF, 0x9F, 0x65, 0x71, 0x10, 0x11, 0x10, 0x73, 0x4D, 0xE4, 0x50, 0xF1, 0x34, 0x91, 0x6E, 0xB5, - 0x88, 0xAB, 0xB9, 0x9B, 0x6D, 0xA1, 0x5B, 0x96, 0xDD, 0x7A, 0x6B, 0x67, 0xE9, 0xBA, 0x75, 0xB9, - 0x17, 0xE3, 0xFD, 0x9A, 0x4C, 0x81, 0xF1, 0xA0, 0x14, 0xEE, 0x9E, 0x09, 0x50, 0xE9, 0x13, 0x87, - 0xCB, 0x43, 0xF2, 0xC8, 0xB0, 0x60, 0x40, 0x05, 0xEA, 0x96, 0x8C, 0xD4, 0x85, 0x24, 0xB0, 0x6F, - 0xFE, 0x8C, 0xCA, 0xBC, 0x67, 0x3D, 0x8B, 0x13, 0xB8, 0x0D, 0x3A, 0xFD, 0x11, 0xCD, 0x42, 0xA6, - 0x2A, 0x6D, 0x45, 0x53, 0x65, 0xBC, 0x5C, 0x84, 0x65, 0xDA, 0x93, 0xBC, 0x16, 0xA4, 0x1F, 0x4B, - 0x05, 0xE0, 0x05, 0x37, 0xCF, 0x91, 0x9B, 0x1F, 0x6A, 0x75, 0x7B, 0xF7, 0x97, 0x9C, 0x87, 0x9D, - 0xE6, 0x2F, 0x73, 0x3B, 0xDF, 0x5B, 0xA4, 0xE4, 0x56, 0x13, 0xFE, 0x29, 0x32, 0xEF, 0x8B, 0x25, - 0x0B, 0xC3, 0xE7, 0xF8, 0xA7, 0x60, 0x10, 0xE9, 0x94, 0x80, 0xDB, 0x3B, 0x2F, 0x5F, 0xF8, 0xC3, - 0x02, 0x98, 0x0B, 0xF6, 0x24, 0x3C, 0x21, 0x3E, 0xCB, 0x52, 0xE7, 0x79, 0xF3, 0x97, 0x5C, 0x9F, - 0x5B, 0x3B, 0x28, 0xFB, 0xE2, 0x2E, 0x71, 0xB2, 0xB4, 0xD8, 0x34, 0x66, 0x5C, 0xDB, 0x4A, 0x35, - 0xBC, 0x6F, 0x92, 0x2C, 0x0C, 0xB3, 0x92, 0xED, 0xE7, 0xBF, 0x2F, 0x4D, 0x13, 0xF7, 0xCF, 0x9A, - 0xBF, 0xCC, 0x44, 0x02, 0xD9, 0x64, 0x04, 0xB9, 0xC6, 0x49, 0x22, 0x41, 0x04, 0x35, 0x9A, 0xE6, - 0x1C, 0x84, 0x5B, 0x03, 0xD8, 0xDE, 0x6D, 0xFA, 0x74, 0x6C, 0xCE, 0xE7, 0x7B, 0x0D, 0x99, 0xD7, - 0xA0, 0x6C, 0xF1, 0x12, 0x16, 0x8B, 0xFD, 0x51, 0xC6, 0x3D, 0xE4, 0x41, 0x1B, 0x53, 0x83, 0x9A, - 0xB3, 0x84, 0x8A, 0x2C, 0xE1, 0x9A, 0x1F, 0x79, 0x19, 0x1A, 0xBB, 0x3D, 0xA6, 0xE2, 0x58, 0xD9, - 0x7D, 0xF7, 0xE1, 0x8D, 0x0F, 0x3B, 0xE6, 0x0B, 0x04, 0x6F, 0x2D, 0x02, 0x38, 0x30, 0x9C, 0x97, - 0xE3, 0x54, 0xF6, 0x43, 0x82, 0x01, 0x22, 0xEF, 0xE8, 0x83, 0x41, 0x2D, 0xB1, 0x40, 0xA4, 0x36, - 0xAE, 0x1B, 0xC5, 0x2E, 0x80, 0x71, 0x73, 0x76, 0x07, 0x4A, 0x20, 0x2E, 0xFD, 0x22, 0x6E, 0x2C, - 0xE6, 0x72, 0xF8, 0x69, 0xE7, 0xBB, 0xC9, 0x1E, 0x3B, 0xA8, 0xB7, 0x1C, 0xB2, 0xCF, 0x0E, 0x5A, - 0xE0, 0x5E, 0x65, 0x6E, 0xE4, 0xB9, 0xAF, 0x58, 0x40, 0x07, 0xB9, 0xC3, 0xE1, 0x31, 0x48, 0x6C, - 0xB1, 0x85, 0x28, 0xE2, 0x5B, 0xCD, 0xE6, 0x86, 0x4B, 0x0F, 0x48, 0x00, 0x39, 0xCC, 0xD0, 0x8F, - 0xAF, 0xAE, 0x2E, 0xAE, 0xBE, 0xE8, 0x35, 0x5A, 0xD3, 0x6F, 0x1C, 0x4D, 0xAF, 0x71, 0xD3, 0x11, - 0x76, 0x42, 0x47, 0x09, 0x4D, 0x27, 0x97, 0x44, 0x4C, 0x8C, 0xD4, 0xBE, 0x23, 0x41, 0x56, 0x16, - 0x84, 0xA1, 0xDC, 0xC8, 0xA2, 0x70, 0x39, 0x9D, 0x6A, 0xAF, 0x40, 0xCD, 0x47, 0x90, 0xEA, 0xDA, - 0xC2, 0x26, 0x71, 0x4C, 0xB9, 0x6F, 0xE8, 0x31, 0x20, 0xEA, 0x16, 0x35, 0xAD, 0x84, 0x7E, 0xCB, - 0x68, 0x2A, 0x52, 0x1B, 0x2C, 0xD7, 0xD0, 0x2F, 0x07, 0x7D, 0xDD, 0xD2, 0x1B, 0xE8, 0x47, 0x3A, - 0xF0, 0x46, 0xCC, 0x39, 0x52, 0x89, 0x5C, 0xD0, 0xA4, 0x3E, 0xCC, 0xC0, 0xA0, 0xB8, 0x6E, 0xB6, - 0x23, 0x9B, 0x71, 0x4E, 0x93, 0x93, 0xFE, 0xD9, 0xA9, 0xAB, 0x5F, 0x29, 0x46, 0xB4, 0x53, 0x28, - 0x48, 0x74, 0x4B, 0x5E, 0x51, 0x7E, 0xC8, 0xE1, 0x84, 0x05, 0xBE, 0x11, 0x99, 0x6D, 0x24, 0xE1, - 0x49, 0x12, 0xB2, 0x40, 0x01, 0x0A, 0x9E, 0x2D, 0x1E, 0x62, 0xEA, 0xEA, 0x23, 0x50, 0x86, 0x6E, - 0x79, 0x76, 0x98, 0x05, 0x82, 0xC5, 0x01, 0x75, 0x37, 0x5A, 0x30, 0xE3, 0x60, 0x41, 0xAE, 0x8E, - 0xB9, 0x19, 0x61, 0xCC, 0x77, 0x75, 0x15, 0xA1, 0xF2, 0xB8, 0xB6, 0xEE, 0x14, 0x4F, 0x9D, 0x92, - 0x56, 0x4E, 0x49, 0xCB, 0xB8, 0x4A, 0xE0, 0x34, 0x3F, 0x18, 0xC3, 0x3C, 0xCE, 0xD4, 0x51, 0x05, - 0xCC, 0xA7, 0x23, 0x02, 0x9C, 0x7C, 0x40, 0x6D, 0xBA, 0x7A, 0x63, 0xDD, 0x41, 0xA9, 0x3A, 0xC8, - 0xAF, 0x6A, 0xC4, 0x2F, 0x6B, 0x44, 0xDD, 0xEE, 0x3A, 0x64, 0x5F, 0x21, 0x07, 0x55, 0xE4, 0xA0, - 0x8C, 0x7C, 0x28, 0x8D, 0x64, 0x1D, 0x72, 0xA0, 0x90, 0x93, 0x8A, 0x88, 0x89, 0x14, 0x51, 0x85, - 0xBD, 0x3A, 0x6A, 0x13, 0x05, 0xD2, 0xAD, 0xA4, 0x22, 0x66, 0x62, 0x83, 0x97, 0x92, 0x61, 0x40, - 0x7D, 0x77, 0xA3, 0x09, 0x33, 0x2C, 0xB6, 0xDD, 0xAD, 0xE6, 0x9A, 0x33, 0x12, 0x75, 0x46, 0x56, - 0x65, 0x30, 0x2B, 0x33, 0xA8, 0xF5, 0xC8, 0x1D, 0xD5, 0xD6, 0x31, 0x98, 0x99, 0x56, 0x60, 0x47, - 0xDC, 0x0B, 0x98, 0x77, 0xEB, 0x2E, 0xBD, 0xC5, 0x9C, 0xB1, 0x85, 0x85, 0x5A, 0x5C, 0x06, 0xBA, - 0x01, 0x94, 0x5E, 0x8B, 0xA5, 0x7C, 0x80, 0xFA, 0x9E, 0x5B, 0xD9, 0x5A, 0x02, 0xDC, 0xA6, 0xF7, - 0xD4, 0x3B, 0x8C, 0xC2, 0x90, 0xA0, 0xED, 0xA6, 0xC0, 0x41, 0x3E, 0xD1, 0xCD, 0xB9, 0x15, 0xAD, - 0xC5, 0x79, 0xC2, 0x45, 0x2C, 0x7F, 0x3D, 0x8B, 0x23, 0x03, 0x5C, 0xCE, 0xF5, 0x6C, 0xD4, 0x61, - 0x6A, 0x83, 0x1E, 0xC7, 0x62, 0xF2, 0x13, 0x17, 0x2A, 0x0C, 0x54, 0xA2, 0x7C, 0x69, 0xDE, 0x58, - 0x0B, 0x91, 0x56, 0x7C, 0xEA, 0xA2, 0xB7, 0xE2, 0x54, 0xA8, 0xBC, 0x8A, 0x5D, 0x9A, 0x4B, 0x1D, - 0x94, 0x61, 0xB9, 0xBD, 0x2F, 0xA0, 0xFA, 0x7C, 0x0E, 0xE7, 0x01, 0xFF, 0x13, 0x68, 0xF9, 0xE8, - 0x5F, 0x17, 0x60, 0xC9, 0xA3, 0x34, 0x78, 0x8B, 0xBB, 0x0D, 0xE3, 0xC0, 0xF9, 0x8F, 0x6D, 0x7C, - 0xF9, 0x1F, 0xFB, 0xA6, 0x66, 0x9A, 0x07, 0xFF, 0x6A, 0x48, 0x0D, 0x1B, 0xC2, 0xFC, 0xD2, 0xBA, - 0xB1, 0x08, 0x80, 0xED, 0x7F, 0x9B, 0xFF, 0xB1, 0x25, 0xB8, 0x02, 0x6B, 0xDF, 0x45, 0x90, 0x49, - 0xF0, 0x24, 0x34, 0xB0, 0x68, 0xA4, 0x91, 0xCD, 0x4D, 0x43, 0xB8, 0xA4, 0x72, 0x8D, 0x35, 0x51, - 0xD3, 0x6D, 0x88, 0x53, 0x50, 0x5B, 0xAC, 0x04, 0xBF, 0x3E, 0x24, 0x7A, 0x15, 0x5B, 0x17, 0x00, - 0xC9, 0x3D, 0xCA, 0x0C, 0x3D, 0x22, 0x97, 0x52, 0xCB, 0x0C, 0x02, 0x42, 0xA7, 0x89, 0xE7, 0x2A, - 0xAD, 0x1D, 0x14, 0x30, 0x17, 0xA2, 0xE0, 0xBC, 0x1C, 0x2D, 0x15, 0xEA, 0xAA, 0xFD, 0x17, 0x0A, - 0xA3, 0xD6, 0x12, 0x8A, 0x04, 0x31, 0xAD, 0xD8, 0x79, 0xC6, 0x72, 0x75, 0x4C, 0x59, 0xBA, 0x35, - 0x59, 0x5D, 0x96, 0xAD, 0x04, 0xAE, 0x2F, 0x8D, 0xFE, 0xD7, 0x3D, 0x16, 0x8E, 0xB5, 0x12, 0x3F, - 0xF8, 0x97, 0xFB, 0x2B, 0x46, 0xE4, 0xCD, 0x3F, 0xBC, 0x21, 0x70, 0x05, 0xA6, 0x41, 0x6D, 0x1E, - 0x4D, 0x0D, 0xB3, 0xF6, 0xAB, 0xAE, 0x49, 0x8A, 0xAE, 0x1E, 0x92, 0xFB, 0xBC, 0xA7, 0xC4, 0x8C, - 0xD7, 0xD6, 0x70, 0x5E, 0xB4, 0x28, 0xF9, 0x82, 0xEC, 0xE6, 0x48, 0x26, 0xA2, 0xB6, 0x56, 0x64, - 0x52, 0xD5, 0xCA, 0xE8, 0x5A, 0x63, 0xFF, 0xD7, 0x4A, 0x40, 0xB7, 0x98, 0xBA, 0x4E, 0x15, 0x8C, - 0xB3, 0x00, 0x1C, 0x93, 0x3E, 0x1D, 0x69, 0x03, 0x26, 0x03, 0x75, 0x35, 0x46, 0x5A, 0x81, 0xC1, - 0xCC, 0x03, 0xC3, 0x2B, 0xFB, 0xF3, 0x1E, 0x16, 0xBF, 0xFB, 0x97, 0xAA, 0xAA, 0x81, 0xD4, 0x8B, - 0x33, 0x5D, 0x59, 0x59, 0xD5, 0x4B, 0xE0, 0xD2, 0x08, 0xA0, 0x5B, 0x8B, 0x3C, 0x3A, 0x8C, 0xFC, - 0x87, 0x52, 0xF6, 0x4D, 0xBB, 0x0F, 0x87, 0x01, 0x49, 0xD3, 0x73, 0xB8, 0x01, 0x43, 0xF7, 0x42, - 0x50, 0xB8, 0xB2, 0xC2, 0xFD, 0xE6, 0xE6, 0x66, 0x15, 0x29, 0xA1, 0x21, 0x14, 0xDB, 0x8A, 0x2B, - 0xF0, 0x49, 0xD3, 0xF1, 0x81, 0x30, 0x18, 0xD2, 0x1A, 0xC6, 0xF0, 0x25, 0xE3, 0x47, 0x5C, 0x71, - 0xF4, 0xF4, 0x22, 0xA6, 0xFC, 0x33, 0xDC, 0x95, 0x32, 0xCB, 0x1A, 0xAD, 0xA6, 0x68, 0xFA, 0x8F, - 0xD8, 0x3E, 0xCA, 0x0D, 0x76, 0xC1, 0x7A, 0xBA, 0x56, 0xA1, 0xFC, 0x9F, 0x61, 0xB9, 0x94, 0x28, - 0xD6, 0x70, 0x9C, 0x40, 0x80, 0x5A, 0xC3, 0x31, 0xC4, 0x1A, 0x41, 0x17, 0xFC, 0x26, 0x6B, 0xF9, - 0xCD, 0xFE, 0x19, 0x7E, 0x97, 0x76, 0x1E, 0x15, 0x25, 0x91, 0xAA, 0xAF, 0x50, 0x02, 0x9F, 0xDD, - 0xE9, 0xA6, 0x15, 0xB9, 0x55, 0x0A, 0x50, 0x1B, 0x46, 0x41, 0xD0, 0x8F, 0xE2, 0x83, 0x27, 0xD6, - 0x9D, 0xC5, 0x7A, 0x31, 0xC8, 0xD9, 0x5C, 0x6E, 0xB1, 0xBC, 0xB5, 0x44, 0x4F, 0xA1, 0xEC, 0x5F, - 0x4B, 0x15, 0x01, 0x3F, 0x23, 0x8B, 0x7B, 0xAC, 0xD4, 0xA5, 0x36, 0x28, 0x0F, 0x56, 0x3F, 0xD5, - 0x3C, 0xCB, 0x5F, 0xCC, 0xAE, 0x6B, 0x51, 0x9B, 0xC0, 0x38, 0x57, 0x92, 0x8B, 0x4A, 0xB2, 0xC8, - 0x13, 0x01, 0xA8, 0x58, 0xC7, 0x2E, 0xC4, 0x4D, 0x6B, 0x7A, 0x7C, 0xBF, 0x5C, 0x83, 0xC2, 0xDF, - 0xF5, 0xD5, 0x12, 0x33, 0x08, 0xC4, 0xD3, 0x95, 0x4B, 0x29, 0x5F, 0x37, 0x29, 0x8A, 0x0E, 0x62, - 0x47, 0xA3, 0x51, 0x4A, 0xC5, 0x47, 0x0C, 0x49, 0x56, 0xB2, 0x98, 0x9F, 0xC8, 0x90, 0x04, 0x8C, - 0x45, 0x3C, 0x8C, 0xB2, 0x94, 0x46, 0x99, 0xA8, 0xA4, 0x16, 0x63, 0x21, 0xCC, 0x5E, 0xFA, 0xE7, - 0x9F, 0x8B, 0xC9, 0x7E, 0x5A, 0x0B, 0x96, 0xD3, 0xEB, 0x3D, 0xBF, 0x34, 0xD9, 0xF7, 0x6B, 0x89, - 0xB9, 0x7A, 0xE9, 0xFF, 0x67, 0x4B, 0x21, 0x65, 0x4B, 0xF1, 0xB0, 0x54, 0x2E, 0x62, 0x62, 0x29, - 0xE6, 0xC9, 0x82, 0x91, 0x97, 0x7C, 0x16, 0x0D, 0x1A, 0x2B, 0x25, 0x55, 0x9E, 0x97, 0x7D, 0x95, - 0x43, 0x40, 0x59, 0x71, 0xE5, 0x35, 0x11, 0x06, 0x34, 0xE0, 0x63, 0x64, 0xF2, 0x41, 0xEB, 0xA7, - 0xD1, 0x94, 0x26, 0x87, 0x24, 0xA5, 0x06, 0x24, 0xCD, 0x65, 0xDC, 0x41, 0xA8, 0xE9, 0x04, 0xEB, - 0x76, 0x6D, 0x6E, 0x12, 0x05, 0xCE, 0x33, 0x77, 0xC4, 0xB1, 0x26, 0x03, 0xF9, 0xB2, 0xCA, 0x09, - 0xD4, 0xC6, 0xBE, 0x12, 0xA4, 0x3E, 0x52, 0x25, 0xA8, 0x61, 0x5A, 0xD0, 0x76, 0xC0, 0x35, 0x5F, - 0x26, 0x51, 0x4C, 0xC6, 0xB2, 0x07, 0x83, 0x35, 0x74, 0x0F, 0xA4, 0x66, 0x6D, 0x34, 0x91, 0x60, - 0xA9, 0x73, 0x29, 0xFC, 0x66, 0xD9, 0xC2, 0x70, 0x4B, 0x57, 0xC9, 0xB0, 0xBD, 0xF4, 0xA5, 0x35, - 0x59, 0x83, 0xE0, 0x0B, 0x6C, 0x62, 0xE0, 0x1E, 0x68, 0x64, 0xF2, 0x7B, 0x00, 0x77, 0x6B, 0xB6, - 0xA3, 0x3D, 0xD6, 0x8E, 0x6A, 0x35, 0x53, 0x55, 0xE9, 0xAE, 0x0B, 0x6D, 0x4E, 0x74, 0x23, 0x0B, - 0x4B, 0x10, 0xAA, 0x9A, 0x59, 0x0C, 0x38, 0x1B, 0x81, 0xAA, 0xBA, 0xC0, 0x11, 0xD6, 0x98, 0x66, - 0xA9, 0x23, 0xF1, 0x97, 0x1D, 0xC9, 0x13, 0xB5, 0x07, 0x95, 0xF5, 0x05, 0xD4, 0x31, 0xAB, 0x25, - 0x86, 0x30, 0xD3, 0x29, 0x13, 0xDE, 0x04, 0x03, 0x90, 0x07, 0x5A, 0xD5, 0x05, 0x14, 0xB5, 0x8E, - 0x1C, 0x4D, 0x44, 0xB8, 0x1C, 0x05, 0xF9, 0xF0, 0x6B, 0x9A, 0x0F, 0xBC, 0xB4, 0x18, 0xDD, 0x97, - 0x80, 0x50, 0xD2, 0xE6, 0xE0, 0x88, 0x8F, 0xF2, 0x21, 0xF4, 0xB2, 0x05, 0x9D, 0x02, 0x58, 0xFC, - 0xC6, 0x71, 0x3E, 0x8A, 0x27, 0xC5, 0x68, 0x42, 0xEF, 0x17, 0x78, 0x51, 0x01, 0xF5, 0xA9, 0xEE, - 0x28, 0x1B, 0xDB, 0x68, 0xCE, 0xF3, 0x41, 0x6B, 0x29, 0x7F, 0xF0, 0xFF, 0x28, 0x7F, 0xCC, 0xC7, - 0x85, 0x34, 0x71, 0x31, 0x1A, 0xB3, 0x42, 0x96, 0x61, 0x18, 0xFF, 0x90, 0x93, 0xA4, 0xD4, 0x13, - 0x97, 0x7A, 0x5A, 0xF1, 0xB3, 0xB6, 0x53, 0x98, 0x8E, 0x31, 0xAA, 0xF8, 0xE3, 0xC8, 0xF6, 0xF0, - 0xF7, 0x3C, 0xF2, 0x65, 0x6D, 0x69, 0x5A, 0xA1, 0x31, 0x82, 0x3A, 0x57, 0x37, 0xCB, 0x7E, 0x9A, - 0xFD, 0xB7, 0xAD, 0xE8, 0xD1, 0xF1, 0xE9, 0x71, 0xFF, 0xB8, 0x5C, 0x38, 0x23, 0xE7, 0x25, 0x93, - 0x8A, 0x2B, 0x5D, 0xFA, 0xB2, 0x22, 0x80, 0x02, 0x1B, 0x45, 0x01, 0x7B, 0xDD, 0xDC, 0x54, 0x7E, - 0xF1, 0xB6, 0x77, 0x71, 0x6E, 0xC7, 0x24, 0x01, 0x8F, 0x24, 0x15, 0xE6, 0xC2, 0x82, 0x44, 0xF9, - 0xE0, 0xD7, 0xC7, 0xA5, 0x72, 0x5D, 0x7E, 0x61, 0x70, 0xC4, 0xDC, 0x52, 0xA7, 0xA9, 0x7E, 0x78, - 0xE2, 0x62, 0x5D, 0x99, 0xBF, 0x04, 0x41, 0x72, 0x1A, 0x2D, 0x13, 0x55, 0x11, 0x67, 0x46, 0xE5, - 0x30, 0x2F, 0xEE, 0xB2, 0x75, 0x0D, 0xD3, 0xC8, 0xB4, 0xC4, 0x84, 0xA5, 0xE5, 0x46, 0xA5, 0x12, - 0x14, 0xFE, 0xA2, 0xB6, 0xE7, 0x8B, 0x91, 0x24, 0xB7, 0x5A, 0x73, 0xAB, 0x6F, 0x41, 0x2A, 0x3E, - 0x58, 0x04, 0x23, 0x66, 0x39, 0xDB, 0x16, 0x77, 0xA3, 0x43, 0xEE, 0x61, 0x5C, 0x7F, 0xBA, 0x35, - 0x78, 0xD2, 0x3C, 0x79, 0x61, 0x9E, 0xFC, 0xB1, 0x7B, 0x2E, 0x1C, 0x45, 0xF9, 0xDA, 0xE2, 0x98, - 0xF6, 0x10, 0x58, 0xBB, 0x6D, 0x2F, 0x7D, 0x18, 0x20, 0xD2, 0x83, 0xCB, 0x00, 0xF4, 0x63, 0x58, - 0xFF, 0x4A, 0xEE, 0x88, 0x7A, 0x09, 0xAA, 0xA2, 0xAD, 0x73, 0x54, 0xD8, 0xEE, 0xFD, 0x81, 0xA3, - 0xF2, 0xCE, 0x65, 0x18, 0x48, 0x97, 0xC3, 0x92, 0x37, 0x8B, 0x75, 0xC1, 0x61, 0x19, 0x31, 0x64, - 0x6C, 0x00, 0xE3, 0xCD, 0x5D, 0x49, 0x13, 0xD5, 0x1C, 0xB4, 0xF0, 0x1B, 0x08, 0x8A, 0x4F, 0x39, - 0xCE, 0x9A, 0x38, 0xAD, 0x62, 0x72, 0xC5, 0x23, 0xC8, 0x4A, 0x67, 0x89, 0xC0, 0x6E, 0x10, 0x0D, - 0x0D, 0x7C, 0x64, 0x9A, 0xA1, 0xB6, 0x1D, 0x3E, 0x37, 0xD7, 0xBC, 0xD9, 0x54, 0xFA, 0x4B, 0x62, - 0x79, 0xD5, 0xB0, 0x8B, 0x1C, 0x56, 0xCC, 0x75, 0x7D, 0x1F, 0xF4, 0xA3, 0x4E, 0x29, 0xAF, 0x48, - 0xA4, 0x53, 0xD1, 0x83, 0xC4, 0x86, 0xA2, 0x41, 0xBE, 0x91, 0x40, 0x44, 0x72, 0x4A, 0x33, 0x5D, - 0xC7, 0xCA, 0xD2, 0x0B, 0x28, 0x49, 0x7A, 0xB2, 0x73, 0x95, 0x49, 0x6B, 0x25, 0x06, 0xFE, 0xC8, - 0xD7, 0xF0, 0xC7, 0xA1, 0xD0, 0xA3, 0x83, 0x9B, 0x49, 0x2B, 0x83, 0xA4, 0x23, 0x64, 0x83, 0xA9, - 0x37, 0xE4, 0xBB, 0xA8, 0x2D, 0x2F, 0xCB, 0xB4, 0x16, 0x50, 0x70, 0x71, 0x83, 0xBB, 0x11, 0x30, - 0x52, 0x5A, 0xC4, 0x9E, 0x94, 0xA8, 0xC7, 0x8F, 0x10, 0x1F, 0x53, 0x4A, 0x20, 0x06, 0x20, 0xA6, - 0x40, 0xD0, 0xA7, 0x42, 0x8A, 0x54, 0xE6, 0x92, 0x53, 0x2A, 0x20, 0xCA, 0x48, 0xCD, 0xE2, 0xC1, - 0x85, 0x78, 0xD4, 0x46, 0xD6, 0x80, 0xFD, 0xDC, 0xBD, 0x73, 0x33, 0xDE, 0x90, 0x68, 0x09, 0x56, - 0x36, 0x3D, 0x9A, 0xA6, 0x52, 0x5C, 0x54, 0xC7, 0x19, 0xF8, 0xA8, 0xA1, 0x03, 0x5A, 0x23, 0x84, - 0x11, 0x1E, 0x84, 0x8A, 0x01, 0x40, 0x7F, 0x42, 0xC3, 0x1C, 0x22, 0x70, 0x08, 0x20, 0x82, 0xA0, - 0x7F, 0x49, 0x0D, 0xF7, 0x64, 0x05, 0xC9, 0xF8, 0xD8, 0x6D, 0x35, 0xF0, 0x9D, 0x66, 0x95, 0xEC, - 0x20, 0xA5, 0xBD, 0x68, 0x24, 0xFA, 0x64, 0x98, 0x1A, 0x50, 0x00, 0xAC, 0xD9, 0x01, 0xA0, 0x1E, - 0x24, 0x5E, 0x63, 0x2B, 0x3F, 0xEF, 0x04, 0x2A, 0xBB, 0x00, 0xAB, 0xBB, 0x8E, 0x87, 0x5F, 0x39, - 0x4F, 0x19, 0xA7, 0x39, 0x26, 0x00, 0x7B, 0x93, 0x68, 0x7A, 0x99, 0x30, 0x2E, 0xCE, 0x64, 0x1B, - 0x6A, 0x6C, 0xB4, 0xE4, 0xF5, 0xA9, 0x87, 0x15, 0x79, 0x3F, 0xC5, 0x8B, 0xCB, 0x0C, 0xF3, 0xBA, - 0x53, 0x79, 0x77, 0xB1, 0x86, 0x70, 0x21, 0x50, 0x66, 0x38, 0xB3, 0x29, 0x74, 0xB0, 0xFA, 0xA1, - 0x48, 0x82, 0x7A, 0x4F, 0xB7, 0x42, 0xE2, 0xC1, 0x44, 0xED, 0x81, 0xF9, 0xDC, 0xC2, 0xD8, 0xE1, - 0x94, 0x83, 0x5A, 0x0A, 0xB5, 0x02, 0x45, 0xC6, 0x95, 0xCD, 0x98, 0x35, 0x1D, 0x6A, 0x58, 0x88, - 0x61, 0xE0, 0xAF, 0xFE, 0x05, 0x0F, 0x1E, 0x1C, 0xC8, 0x55, 0x3F, 0xE1, 0x23, 0xE3, 0x7E, 0xF4, - 0x23, 0x3E, 0x3E, 0xAF, 0xF0, 0xF1, 0x79, 0x1D, 0x1F, 0xB4, 0xAA, 0x3C, 0x98, 0x0C, 0x80, 0xEC, - 0x19, 0xE1, 0x64, 0x4C, 0x13, 0x58, 0xC0, 0x43, 0x50, 0x25, 0x7F, 0x8B, 0xB3, 0x84, 0xFE, 0x98, - 0xB3, 0xDE, 0x84, 0x8D, 0xC4, 0x23, 0xFE, 0x8A, 0xD5, 0xFF, 0x82, 0x4B, 0x3C, 0x70, 0x3D, 0x97, - 0x79, 0x6D, 0x5A, 0x49, 0x28, 0x3F, 0x7E, 0x2B, 0x91, 0x7E, 0xE4, 0x42, 0x78, 0xA9, 0x38, 0xC8, - 0xDF, 0xB7, 0xF4, 0x00, 0xBC, 0x11, 0xF8, 0x29, 0x35, 0x75, 0xBC, 0x0B, 0xA5, 0xFC, 0x29, 0x30, - 0x64, 0xA8, 0xC0, 0x47, 0xDD, 0xD9, 0xDC, 0x12, 0xAE, 0x01, 0x8A, 0xF1, 0xA3, 0x29, 0xB0, 0xEA, - 0xC9, 0x02, 0xD7, 0x9E, 0x40, 0x26, 0x04, 0x91, 0xE0, 0x48, 0xC8, 0xA7, 0x8D, 0x2F, 0x07, 0x9B, - 0x37, 0x35, 0xC8, 0x43, 0x2E, 0xFC, 0x98, 0x2E, 0x0C, 0x36, 0x6F, 0xFE, 0x6D, 0x36, 0xC6, 0xCC, - 0x5A, 0x76, 0xA4, 0x96, 0x4C, 0xF6, 0xF4, 0x0B, 0xBF, 0x71, 0x09, 0x48, 0x5D, 0x49, 0x78, 0x45, - 0x34, 0x03, 0x6B, 0x43, 0x61, 0xE1, 0x07, 0xFF, 0x47, 0x09, 0xF8, 0x91, 0x9E, 0x07, 0xCE, 0xBD, - 0xE6, 0x3D, 0x5E, 0x2F, 0x3E, 0x85, 0xE9, 0x56, 0xE9, 0xC1, 0x4A, 0xC7, 0xEF, 0x53, 0x3A, 0x76, - 0x59, 0xA2, 0x14, 0x4A, 0x14, 0x59, 0x88, 0x1A, 0x6A, 0x50, 0x0E, 0x51, 0x98, 0x89, 0x17, 0xCD, - 0x81, 0x02, 0x9B, 0x73, 0x34, 0x5B, 0x3A, 0x02, 0x0F, 0xF4, 0xF5, 0x45, 0xEE, 0xFC, 0x74, 0x76, - 0x7A, 0x22, 0x44, 0x7C, 0xA5, 0x62, 0x22, 0xD0, 0xAA, 0x2E, 0x2C, 0x2F, 0xCF, 0x9C, 0x89, 0xE4, - 0xA1, 0x28, 0x75, 0x30, 0x31, 0x28, 0x87, 0xFE, 0x74, 0x31, 0xFC, 0x0A, 0x71, 0xD6, 0xD0, 0xCF, - 0x52, 0x48, 0x58, 0x5B, 0x36, 0xA2, 0xF7, 0xFB, 0x97, 0xF6, 0xAE, 0xDD, 0x84, 0xBA, 0x00, 0xB4, - 0x0A, 0x69, 0x19, 0xEE, 0x7D, 0xFE, 0xB7, 0x90, 0xB7, 0xFF, 0x1E, 0x32, 0x83, 0xA8, 0x95, 0x42, - 0x58, 0x2A, 0xF0, 0xAB, 0xB8, 0x93, 0x24, 0x9A, 0x4A, 0xB4, 0xE3, 0x24, 0xC1, 0x4B, 0xE9, 0x43, - 0x85, 0xA2, 0x0D, 0x61, 0x31, 0xA5, 0x89, 0xE6, 0x47, 0x34, 0xD5, 0x78, 0x24, 0xB4, 0x34, 0x8B, - 0x63, 0x68, 0x5C, 0x56, 0xF4, 0x61, 0xEB, 0xC5, 0xEB, 0xCB, 0xFB, 0x8C, 0x66, 0xD4, 0xCF, 0x97, - 0x69, 0x52, 0xD1, 0x0B, 0x56, 0x50, 0xDF, 0x10, 0xEE, 0x7E, 0xB9, 0xC9, 0xEB, 0xA9, 0x8C, 0x73, - 0x8C, 0xA2, 0x1B, 0x2D, 0x35, 0x07, 0xE9, 0x26, 0x40, 0xD5, 0xE5, 0x59, 0x10, 0xCC, 0xDB, 0x2B, - 0xB4, 0xA0, 0xF1, 0x8A, 0x44, 0x24, 0x9F, 0xCB, 0x67, 0x7F, 0xE4, 0xC9, 0xA9, 0xE2, 0x82, 0x50, - 0xF2, 0x54, 0xA9, 0x36, 0xAD, 0x0D, 0x63, 0x83, 0x6A, 0x8C, 0xA7, 0x82, 0x70, 0x0F, 0xAF, 0x51, - 0xE9, 0xC2, 0x2C, 0x6A, 0x29, 0xDC, 0xDE, 0x46, 0x5F, 0xCB, 0x6D, 0xE9, 0x89, 0x7C, 0x2A, 0x25, - 0xE3, 0xAE, 0xAE, 0x63, 0x55, 0x45, 0xB1, 0x3E, 0x25, 0x61, 0x5A, 0x26, 0x5B, 0x54, 0x06, 0x26, - 0x77, 0x0B, 0x70, 0x9B, 0x06, 0x29, 0x1C, 0xBD, 0x7E, 0x7F, 0xCE, 0x46, 0xD1, 0xCE, 0x11, 0x80, - 0x69, 0xC5, 0x3E, 0x93, 0xD7, 0xE0, 0x24, 0xCC, 0x73, 0x07, 0x32, 0xE9, 0x4A, 0x03, 0x0E, 0xA9, - 0x98, 0x44, 0xFE, 0x81, 0x7E, 0xA0, 0x3B, 0x3A, 0xFC, 0xBB, 0x09, 0x35, 0x47, 0xCD, 0xA5, 0xD0, - 0xA4, 0xFA, 0x74, 0x70, 0xF5, 0x06, 0xC2, 0x53, 0x0C, 0xA5, 0x01, 0x17, 0x50, 0x34, 0xD7, 0x74, - 0x7C, 0x7A, 0x7D, 0x0C, 0x29, 0xC8, 0x7F, 0x21, 0x37, 0x66, 0xBB, 0xAA, 0x6C, 0xB8, 0xF3, 0xEA, - 0x75, 0x56, 0x2E, 0x03, 0x7A, 0x61, 0x8C, 0x58, 0x0F, 0x29, 0x7E, 0xFB, 0x7B, 0xF4, 0x9E, 0x8D, - 0x15, 0xD2, 0x6A, 0x5D, 0x6F, 0xCE, 0x76, 0x90, 0x67, 0x89, 0xD5, 0x43, 0x2C, 0x70, 0x97, 0x1F, - 0x29, 0x59, 0x95, 0x35, 0xDC, 0xF6, 0x48, 0x10, 0xE0, 0xC7, 0x5A, 0x03, 0x1B, 0x6A, 0x22, 0xB2, - 0xD4, 0x42, 0x22, 0x29, 0x08, 0x90, 0xD2, 0x3E, 0x84, 0x39, 0xD3, 0x92, 0x65, 0x86, 0xB2, 0xA1, - 0xBC, 0xFF, 0xC5, 0x9A, 0xA3, 0x64, 0x46, 0xE8, 0xCE, 0xF9, 0x6C, 0x73, 0x53, 0xD8, 0x85, 0x99, - 0x18, 0x05, 0x52, 0x8A, 0x01, 0x1C, 0x9A, 0x7D, 0x68, 0x2D, 0x8C, 0xB2, 0x90, 0x58, 0xAB, 0x3D, - 0xD2, 0xB6, 0x51, 0x55, 0x03, 0x54, 0x7C, 0x46, 0x01, 0x03, 0xCE, 0xB2, 0x24, 0x80, 0xA8, 0x8B, - 0x39, 0xBA, 0xB2, 0x2D, 0xC5, 0xBA, 0xD0, 0x84, 0x0E, 0xEC, 0x67, 0xC8, 0x12, 0x95, 0x97, 0xAD, - 0xA2, 0x27, 0x12, 0xC5, 0x77, 0x95, 0x9E, 0xC8, 0x6F, 0xE5, 0x84, 0xAA, 0xC8, 0x77, 0x88, 0x2F, - 0x13, 0x5C, 0xD4, 0xD1, 0x13, 0xA0, 0x24, 0x83, 0x52, 0x34, 0x60, 0x2A, 0x2C, 0x37, 0xEE, 0xEB, - 0xD3, 0xE9, 0xB4, 0x8E, 0xDF, 0x6A, 0xEB, 0x70, 0x82, 0xB2, 0x02, 0x5F, 0x5F, 0xC7, 0x21, 0x47, - 0x15, 0x58, 0xF8, 0x6E, 0xE1, 0xAC, 0xBA, 0xE8, 0x42, 0x7F, 0x2B, 0xDE, 0xD4, 0xAA, 0xD2, 0x59, - 0xE1, 0x73, 0x79, 0xDB, 0x7B, 0x3B, 0x2B, 0x20, 0x32, 0xC4, 0xAF, 0xB2, 0x90, 0x69, 0x20, 0x0D, - 0x3B, 0xE5, 0x46, 0x56, 0x25, 0x85, 0x65, 0x5C, 0xB0, 0xE3, 0x2C, 0x9D, 0x18, 0x33, 0x60, 0xDD, - 0x11, 0x96, 0xD2, 0x95, 0x43, 0x2D, 0x65, 0xB7, 0x0E, 0xB7, 0x0A, 0xFB, 0x70, 0x30, 0x83, 0x94, - 0x79, 0xFB, 0xF3, 0x4F, 0x39, 0x5B, 0xDE, 0xF6, 0x92, 0x62, 0x71, 0xE1, 0xF3, 0xFC, 0xA9, 0x35, - 0xAF, 0x69, 0xA5, 0xD1, 0xAF, 0xC4, 0x97, 0xBD, 0x46, 0xFE, 0x19, 0x3B, 0xFF, 0x9C, 0xAD, 0x81, - 0xB1, 0x43, 0x23, 0x2A, 0xDC, 0x4C, 0x8C, 0xEA, 0x2F, 0x34, 0xE6, 0x63, 0x79, 0x29, 0xBF, 0x2D, - 0xA0, 0x54, 0xA9, 0xD3, 0x68, 0x78, 0x3E, 0xFF, 0x9A, 0x42, 0x19, 0x1D, 0x65, 0xFE, 0x28, 0x20, - 0x09, 0xC5, 0x82, 0xA3, 0x41, 0xBE, 0x92, 0xFB, 0x46, 0xC0, 0x86, 0x69, 0x03, 0x93, 0x6D, 0xCB, - 0xDE, 0xB2, 0x77, 0x71, 0x64, 0x7F, 0x4D, 0xF7, 0x57, 0x4F, 0xD8, 0x5F, 0x34, 0x69, 0x58, 0x0B, - 0xE7, 0xB5, 0xAB, 0x8A, 0x4D, 0x6A, 0x83, 0xFB, 0xC4, 0xA7, 0x70, 0x3D, 0x6F, 0xB3, 0xCC, 0xB6, - 0x1A, 0xE4, 0x5F, 0x60, 0xD4, 0x31, 0xBA, 0x95, 0x2F, 0x92, 0xF4, 0x81, 0x7B, 0x18, 0x5B, 0x17, - 0x54, 0x26, 0x70, 0x49, 0xD5, 0x87, 0x34, 0xB9, 0xD3, 0x9C, 0x2F, 0x39, 0xC3, 0xB7, 0x3C, 0xA8, - 0x03, 0xE4, 0x37, 0x9C, 0x72, 0x39, 0xB0, 0xBF, 0x07, 0x5D, 0x33, 0x2A, 0x41, 0x79, 0xB1, 0x26, - 0x9B, 0xE6, 0x7C, 0x02, 0x82, 0x01, 0x70, 0xB1, 0xA3, 0x48, 0xCD, 0x2B, 0xCB, 0x98, 0x9B, 0x57, - 0x96, 0x54, 0xE2, 0x5F, 0x59, 0xCC, 0xDB, 0x9F, 0xFC, 0xDB, 0x4C, 0xF9, 0x7F, 0x5B, 0x28, 0x36, - 0x32, 0xF9, 0xE1, 0x09, 0xF7, 0x56, 0x3F, 0x45, 0xAD, 0x47, 0x51, 0xBB, 0xF7, 0xFF, 0x17, 0x53, - 0xE8, 0x9D, 0x36, 0x92, 0x29, 0x00, 0x00 -}; - -#define SPIFFS_MAXLENGTH_FILEPATH 32 -const char *excludeListFile = "/.exclude.files"; - -typedef struct ExcludeListS { - char *item; - ExcludeListS *next; -} ExcludeList; - -static ExcludeList *excludes = NULL; - -static bool matchWild(const char *pattern, const char *testee) { - const char *nxPat = NULL, *nxTst = NULL; - - while (*testee) { - if (( *pattern == '?' ) || (*pattern == *testee)){ - pattern++;testee++; - continue; - } - if (*pattern=='*'){ - nxPat=pattern++; nxTst=testee; - continue; - } - if (nxPat){ - pattern = nxPat+1; testee=++nxTst; - continue; - } - return false; - } - while (*pattern=='*'){pattern++;} - return (*pattern == 0); -} - -static bool addExclude(const char *item){ - size_t len = strlen(item); - if(!len){ - return false; - } - ExcludeList *e = (ExcludeList *)malloc(sizeof(ExcludeList)); - if(!e){ - return false; - } - e->item = (char *)malloc(len+1); - if(!e->item){ - free(e); - return false; - } - memcpy(e->item, item, len+1); - e->next = excludes; - excludes = e; - return true; -} - -static void loadExcludeList(fs::FS &_fs, const char *filename){ - static char linebuf[SPIFFS_MAXLENGTH_FILEPATH]; - fs::File excludeFile=_fs.open(filename, "r"); - if(!excludeFile){ - //addExclude("/*.js.gz"); - return; - } -#ifdef ESP32 - if(excludeFile.isDirectory()){ - excludeFile.close(); - return; - } -#endif - if (excludeFile.size() > 0){ - uint8_t idx; - bool isOverflowed = false; - while (excludeFile.available()){ - linebuf[0] = '\0'; - idx = 0; - int lastChar; - do { - lastChar = excludeFile.read(); - if(lastChar != '\r'){ - linebuf[idx++] = (char) lastChar; - } - } while ((lastChar >= 0) && (lastChar != '\n') && (idx < SPIFFS_MAXLENGTH_FILEPATH)); - - if(isOverflowed){ - isOverflowed = (lastChar != '\n'); - continue; - } - isOverflowed = (idx >= SPIFFS_MAXLENGTH_FILEPATH); - linebuf[idx-1] = '\0'; - if(!addExclude(linebuf)){ - excludeFile.close(); - return; - } - } - } - excludeFile.close(); -} - -static bool isExcluded(fs::FS &_fs, const char *filename) { - if(excludes == NULL){ - loadExcludeList(_fs, excludeListFile); - } - ExcludeList *e = excludes; - while(e){ - if (matchWild(e->item, filename)){ - return true; - } - e = e->next; - } - return false; -} - -// WEB HANDLER IMPLEMENTATION - -#ifdef ESP32 -SPIFFSEditor::SPIFFSEditor(const fs::FS& fs, const String& username, const String& password) -#else -SPIFFSEditor::SPIFFSEditor(const String& username, const String& password, const fs::FS& fs) -#endif -:_fs(fs) -,_username(username) -,_password(password) -,_authenticated(false) -,_startTime(0) -{} - -bool SPIFFSEditor::canHandle(AsyncWebServerRequest *request){ - if(request->url().equalsIgnoreCase("/edit")){ - if(request->method() == HTTP_GET){ - if(request->hasParam("list")) - return true; - if(request->hasParam("edit")){ - request->_tempFile = _fs.open(request->arg("edit"), "r"); - if(!request->_tempFile){ - return false; - } -#ifdef ESP32 - if(request->_tempFile.isDirectory()){ - request->_tempFile.close(); - return false; - } -#endif - } - if(request->hasParam("download")){ - request->_tempFile = _fs.open(request->arg("download"), "r"); - if(!request->_tempFile){ - return false; - } -#ifdef ESP32 - if(request->_tempFile.isDirectory()){ - request->_tempFile.close(); - return false; - } -#endif - } - request->addInterestingHeader("If-Modified-Since"); - return true; - } - else if(request->method() == HTTP_POST) - return true; - else if(request->method() == HTTP_DELETE) - return true; - else if(request->method() == HTTP_PUT) - return true; - - } - return false; -} - - -void SPIFFSEditor::handleRequest(AsyncWebServerRequest *request){ - if(_username.length() && _password.length() && !request->authenticate(_username.c_str(), _password.c_str())) - return request->requestAuthentication(); - - if(request->method() == HTTP_GET){ - if(request->hasParam("list")){ - String path = request->getParam("list")->value(); -#ifdef ESP32 - File dir = _fs.open(path); -#else - Dir dir = _fs.openDir(path); -#endif - path = String(); - String output = "["; -#ifdef ESP32 - File entry = dir.openNextFile(); - while(entry){ -#else - while(dir.next()){ - fs::File entry = dir.openFile("r"); -#endif - if (isExcluded(_fs, entry.name())) { -#ifdef ESP32 - entry = dir.openNextFile(); -#endif - continue; - } - if (output != "[") output += ','; - output += "{\"type\":\""; - output += "file"; - output += "\",\"name\":\""; - output += String(entry.name()); - output += "\",\"size\":"; - output += String(entry.size()); - output += "}"; -#ifdef ESP32 - entry = dir.openNextFile(); -#else - entry.close(); -#endif - } -#ifdef ESP32 - dir.close(); -#endif - output += "]"; - request->send(200, "application/json", output); - output = String(); - } - else if(request->hasParam("edit") || request->hasParam("download")){ - request->send(request->_tempFile, request->_tempFile.name(), String(), request->hasParam("download")); - } - else { - const char * buildTime = __DATE__ " " __TIME__ " GMT"; - if (request->header("If-Modified-Since").equals(buildTime)) { - request->send(304); - } else { - AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", edit_htm_gz, edit_htm_gz_len); - response->addHeader("Content-Encoding", "gzip"); - response->addHeader("Last-Modified", buildTime); - request->send(response); - } - } - } else if(request->method() == HTTP_DELETE){ - if(request->hasParam("path", true)){ - _fs.remove(request->getParam("path", true)->value()); - request->send(200, "", "DELETE: "+request->getParam("path", true)->value()); - } else - request->send(404); - } else if(request->method() == HTTP_POST){ - if(request->hasParam("data", true, true) && _fs.exists(request->getParam("data", true, true)->value())) - request->send(200, "", "UPLOADED: "+request->getParam("data", true, true)->value()); - else - request->send(500); - } else if(request->method() == HTTP_PUT){ - if(request->hasParam("path", true)){ - String filename = request->getParam("path", true)->value(); - if(_fs.exists(filename)){ - request->send(200); - } else { - fs::File f = _fs.open(filename, "w"); - if(f){ - f.write((uint8_t)0x00); - f.close(); - request->send(200, "", "CREATE: "+filename); - } else { - request->send(500); - } - } - } else - request->send(400); - } -} - -void SPIFFSEditor::handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final){ - if(!index){ - if(!_username.length() || request->authenticate(_username.c_str(),_password.c_str())){ - _authenticated = true; - request->_tempFile = _fs.open(filename, "w"); - _startTime = millis(); - } - } - if(_authenticated && request->_tempFile){ - if(len){ - request->_tempFile.write(data,len); - } - if(final){ - request->_tempFile.close(); - } - } -} diff --git a/lib/ESPAsyncWebServer/SPIFFSEditor.h b/lib/ESPAsyncWebServer/SPIFFSEditor.h deleted file mode 100644 index aab1187a3..000000000 --- a/lib/ESPAsyncWebServer/SPIFFSEditor.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef SPIFFSEditor_H_ -#define SPIFFSEditor_H_ -#include - -class SPIFFSEditor : public AsyncWebHandler { - private: - fs::FS _fs; - String _username; - String _password; - bool _authenticated; - uint32_t _startTime; - - public: -#ifdef ESP32 - SPIFFSEditor(const fs::FS & fs, const String & username = String(), const String & password = String()); -#else -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" - SPIFFSEditor(const String & username = String(), const String & password = String(), const fs::FS & fs = SPIFFS); -#pragma GCC diagnostic pop -#endif - virtual bool canHandle(AsyncWebServerRequest * request) override final; - virtual void handleRequest(AsyncWebServerRequest * request) override final; - virtual void handleUpload(AsyncWebServerRequest * request, const String & filename, size_t index, uint8_t * data, size_t len, bool final) override final; - virtual bool isRequestHandlerTrivial() override final { - return false; - } -}; - -#endif diff --git a/lib/ESPAsyncWebServer/StringArray.h b/lib/ESPAsyncWebServer/StringArray.h deleted file mode 100644 index 58b81a3ca..000000000 --- a/lib/ESPAsyncWebServer/StringArray.h +++ /dev/null @@ -1,193 +0,0 @@ -/* - Asynchronous WebServer library for Espressif MCUs - - Copyright (c) 2016 Hristo Gochkov. All rights reserved. - This file is part of the esp8266 core for Arduino environment. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ -#ifndef STRINGARRAY_H_ -#define STRINGARRAY_H_ - -#include "stddef.h" -#include "WString.h" - -template -class LinkedListNode { - T _value; - public: - LinkedListNode* next; - LinkedListNode(const T val): _value(val), next(nullptr) {} - ~LinkedListNode(){} - const T& value() const { return _value; }; - T& value(){ return _value; } -}; - -template class Item = LinkedListNode> -class LinkedList { - public: - typedef Item ItemType; - typedef std::function OnRemove; - typedef std::function Predicate; - private: - ItemType* _root; - OnRemove _onRemove; - - class Iterator { - ItemType* _node; - public: - Iterator(ItemType* current = nullptr) : _node(current) {} - Iterator(const Iterator& i) : _node(i._node) {} - Iterator& operator ++() { _node = _node->next; return *this; } - bool operator != (const Iterator& i) const { return _node != i._node; } - const T& operator * () const { return _node->value(); } - const T* operator -> () const { return &_node->value(); } - }; - - public: - typedef const Iterator ConstIterator; - ConstIterator begin() const { return ConstIterator(_root); } - ConstIterator end() const { return ConstIterator(nullptr); } - - LinkedList(OnRemove onRemove) : _root(nullptr), _onRemove(onRemove) {} - ~LinkedList(){} - void add(const T& t){ - auto it = new ItemType(t); - if(!_root){ - _root = it; - } else { - auto i = _root; - while(i->next) i = i->next; - i->next = it; - } - } - T& front() const { - return _root->value(); - } - - bool isEmpty() const { - return _root == nullptr; - } - size_t length() const { - size_t i = 0; - auto it = _root; - while(it){ - i++; - it = it->next; - } - return i; - } - size_t count_if(Predicate predicate) const { - size_t i = 0; - auto it = _root; - while(it){ - if (!predicate){ - i++; - } - else if (predicate(it->value())) { - i++; - } - it = it->next; - } - return i; - } - const T* nth(size_t N) const { - size_t i = 0; - auto it = _root; - while(it){ - if(i++ == N) - return &(it->value()); - it = it->next; - } - return nullptr; - } - bool remove(const T& t){ - auto it = _root; - auto pit = _root; - while(it){ - if(it->value() == t){ - if(it == _root){ - _root = _root->next; - } else { - pit->next = it->next; - } - - if (_onRemove) { - _onRemove(it->value()); - } - - delete it; - return true; - } - pit = it; - it = it->next; - } - return false; - } - bool remove_first(Predicate predicate){ - auto it = _root; - auto pit = _root; - while(it){ - if(predicate(it->value())){ - if(it == _root){ - _root = _root->next; - } else { - pit->next = it->next; - } - if (_onRemove) { - _onRemove(it->value()); - } - delete it; - return true; - } - pit = it; - it = it->next; - } - return false; - } - - void free(){ - while(_root != nullptr){ - auto it = _root; - _root = _root->next; - if (_onRemove) { - _onRemove(it->value()); - } - delete it; - } - _root = nullptr; - } -}; - - -class StringArray : public LinkedList { -public: - - StringArray() : LinkedList(nullptr) {} - - bool containsIgnoreCase(const String& str){ - for (const auto& s : *this) { - if (str.equalsIgnoreCase(s)) { - return true; - } - } - return false; - } -}; - - - - -#endif /* STRINGARRAY_H_ */ diff --git a/lib/ESPAsyncWebServer/WebAuthentication.cpp b/lib/ESPAsyncWebServer/WebAuthentication.cpp deleted file mode 100644 index 340d4471e..000000000 --- a/lib/ESPAsyncWebServer/WebAuthentication.cpp +++ /dev/null @@ -1,235 +0,0 @@ -/* - Asynchronous WebServer library for Espressif MCUs - - Copyright (c) 2016 Hristo Gochkov. All rights reserved. - This file is part of the esp8266 core for Arduino environment. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ -#include "WebAuthentication.h" -#include -#ifdef ESP32 -#include "mbedtls/md5.h" -#else -#include "md5.h" -#endif - - -// Basic Auth hash = base64("username:password") - -bool checkBasicAuthentication(const char * hash, const char * username, const char * password){ - if(username == NULL || password == NULL || hash == NULL) - return false; - - size_t toencodeLen = strlen(username)+strlen(password)+1; - size_t encodedLen = base64_encode_expected_len(toencodeLen); - if(strlen(hash) != encodedLen) - return false; - - char *toencode = new char[toencodeLen+1]; - if(toencode == NULL){ - return false; - } - char *encoded = new char[base64_encode_expected_len(toencodeLen)+1]; - if(encoded == NULL){ - delete[] toencode; - return false; - } - sprintf_P(toencode, PSTR("%s:%s"), username, password); - if(base64_encode_chars(toencode, toencodeLen, encoded) > 0 && memcmp(hash, encoded, encodedLen) == 0){ - delete[] toencode; - delete[] encoded; - return true; - } - delete[] toencode; - delete[] encoded; - return false; -} - -static bool getMD5(uint8_t * data, uint16_t len, char * output){//33 bytes or more -#ifdef ESP32 - mbedtls_md5_context _ctx; -#else - md5_context_t _ctx; -#endif - uint8_t i; - uint8_t * _buf = (uint8_t*)malloc(16); - if(_buf == NULL) - return false; - memset(_buf, 0x00, 16); -#ifdef ESP32 - mbedtls_md5_init(&_ctx); - mbedtls_md5_update (&_ctx,data,len); - mbedtls_md5_finish(&_ctx,data); - mbedtls_internal_md5_process( &_ctx ,data); -#else - MD5Init(&_ctx); - MD5Update(&_ctx, data, len); - MD5Final(_buf, &_ctx); -#endif - for(i = 0; i < 16; i++) { - sprintf_P(output + (i * 2), PSTR("%02x"), _buf[i]); - } - free(_buf); - return true; -} - -static String genRandomMD5(){ -#ifdef ESP8266 - uint32_t r = RANDOM_REG32; -#else - uint32_t r = rand(); -#endif - char * out = (char*)malloc(33); - if(out == NULL || !getMD5((uint8_t*)(&r), 4, out)) - return emptyString; - String res = String(out); - free(out); - return res; -} - -static String stringMD5(const String& in){ - char * out = (char*)malloc(33); - if(out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out)) - return emptyString; - String res = String(out); - free(out); - return res; -} - -String generateDigestHash(const char * username, const char * password, const char * realm){ - if(username == NULL || password == NULL || realm == NULL){ - return emptyString; - } - char * out = (char*)malloc(33); - String res = String(username); - res += ':'; - res.concat(realm); - res += ':'; - String in = res; - in.concat(password); - if(out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out)) - return emptyString; - res.concat(out); - free(out); - return res; -} - -String requestDigestAuthentication(const char * realm){ - String header = F("realm=\""); - if(realm == NULL) - header.concat(F("asyncesp")); - else - header.concat(realm); - header.concat(F("\", qop=\"auth\", nonce=\"")); - header.concat(genRandomMD5()); - header.concat(F("\", opaque=\"")); - header.concat(genRandomMD5()); - header += '"'; - return header; -} - -bool checkDigestAuthentication(const char * header, const char *method, const char * username, const char * password, const char * realm, bool passwordIsHash, const char * nonce, const char * opaque, const char * uri){ - if(username == NULL || password == NULL || header == NULL || method == NULL){ - //os_printf("AUTH FAIL: missing requred fields\n"); - return false; - } - - String myHeader = String(header); - int nextBreak = myHeader.indexOf(','); - if(nextBreak < 0){ - //os_printf("AUTH FAIL: no variables\n"); - return false; - } - - String myUsername = String(); - String myRealm = String(); - String myNonce = String(); - String myUri = String(); - String myResponse = String(); - String myQop = String(); - String myNc = String(); - String myCnonce = String(); - - myHeader += F(", "); - do { - String avLine = myHeader.substring(0, nextBreak); - avLine.trim(); - myHeader = myHeader.substring(nextBreak+1); - nextBreak = myHeader.indexOf(','); - - int eqSign = avLine.indexOf('='); - if(eqSign < 0){ - //os_printf("AUTH FAIL: no = sign\n"); - return false; - } - String varName = avLine.substring(0, eqSign); - avLine = avLine.substring(eqSign + 1); - if(avLine.startsWith(String('"'))){ - avLine = avLine.substring(1, avLine.length() - 1); - } - - if(varName.equals(F("username"))){ - if(!avLine.equals(username)){ - //os_printf("AUTH FAIL: username\n"); - return false; - } - myUsername = avLine; - } else if(varName.equals(F("realm"))){ - if(realm != NULL && !avLine.equals(realm)){ - //os_printf("AUTH FAIL: realm\n"); - return false; - } - myRealm = avLine; - } else if(varName.equals(F("nonce"))){ - if(nonce != NULL && !avLine.equals(nonce)){ - //os_printf("AUTH FAIL: nonce\n"); - return false; - } - myNonce = avLine; - } else if(varName.equals(F("opaque"))){ - if(opaque != NULL && !avLine.equals(opaque)){ - //os_printf("AUTH FAIL: opaque\n"); - return false; - } - } else if(varName.equals(F("uri"))){ - if(uri != NULL && !avLine.equals(uri)){ - //os_printf("AUTH FAIL: uri\n"); - return false; - } - myUri = avLine; - } else if(varName.equals(F("response"))){ - myResponse = avLine; - } else if(varName.equals(F("qop"))){ - myQop = avLine; - } else if(varName.equals(F("nc"))){ - myNc = avLine; - } else if(varName.equals(F("cnonce"))){ - myCnonce = avLine; - } - } while(nextBreak > 0); - - String ha1 = (passwordIsHash) ? String(password) : stringMD5(myUsername + ':' + myRealm + ':' + String(password)); - String ha2 = String(method) + ':' + myUri; - String response = ha1 + ':' + myNonce + ':' + myNc + ':' + myCnonce + ':' + myQop + ':' + stringMD5(ha2); - - if(myResponse.equals(stringMD5(response))){ - //os_printf("AUTH SUCCESS\n"); - return true; - } - - //os_printf("AUTH FAIL: password\n"); - return false; -} diff --git a/lib/ESPAsyncWebServer/WebAuthentication.h b/lib/ESPAsyncWebServer/WebAuthentication.h deleted file mode 100644 index 951f36a22..000000000 --- a/lib/ESPAsyncWebServer/WebAuthentication.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - Asynchronous WebServer library for Espressif MCUs - - Copyright (c) 2016 Hristo Gochkov. All rights reserved. - This file is part of the esp8266 core for Arduino environment. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#ifndef WEB_AUTHENTICATION_H_ -#define WEB_AUTHENTICATION_H_ - -#include "Arduino.h" - -bool checkBasicAuthentication(const char * header, const char * username, const char * password); -String requestDigestAuthentication(const char * realm); -bool checkDigestAuthentication(const char * header, const char *method, const char * username, const char * password, const char * realm, bool passwordIsHash, const char * nonce, const char * opaque, const char * uri); - -//for storing hashed versions on the device that can be authenticated against -String generateDigestHash(const char * username, const char * password, const char * realm); - -#endif diff --git a/lib/ESPAsyncWebServer/WebHandlerImpl.h b/lib/ESPAsyncWebServer/WebHandlerImpl.h deleted file mode 100644 index d121fa7a3..000000000 --- a/lib/ESPAsyncWebServer/WebHandlerImpl.h +++ /dev/null @@ -1,138 +0,0 @@ -/* - Asynchronous WebServer library for Espressif MCUs - - Copyright (c) 2016 Hristo Gochkov. All rights reserved. - This file is part of the esp8266 core for Arduino environment. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ -#ifndef ASYNCWEBSERVERHANDLERIMPL_H_ -#define ASYNCWEBSERVERHANDLERIMPL_H_ - -#include -#ifdef ASYNCWEBSERVER_REGEX -#include -#endif - -#include "stddef.h" -#include - -class AsyncStaticWebHandler: public AsyncWebHandler { - using File = fs::File; - using FS = fs::FS; - private: - bool _getFile(AsyncWebServerRequest *request); - bool _fileExists(AsyncWebServerRequest *request, const String& path); - uint8_t _countBits(const uint8_t value) const; - protected: - FS _fs; - String _uri; - String _path; - String _default_file; - String _cache_control; - String _last_modified; - AwsTemplateProcessor _callback; - bool _isDir; - bool _gzipFirst; - uint8_t _gzipStats; - public: - AsyncStaticWebHandler(const char* uri, FS& fs, const char* path, const char* cache_control); - virtual bool canHandle(AsyncWebServerRequest *request) override final; - virtual void handleRequest(AsyncWebServerRequest *request) override final; - AsyncStaticWebHandler& setIsDir(bool isDir); - AsyncStaticWebHandler& setDefaultFile(const char* filename); - AsyncStaticWebHandler& setCacheControl(const char* cache_control); - AsyncStaticWebHandler& setLastModified(const char* last_modified); - AsyncStaticWebHandler& setLastModified(struct tm* last_modified); - #ifdef ESP8266 - AsyncStaticWebHandler& setLastModified(time_t last_modified); - AsyncStaticWebHandler& setLastModified(); //sets to current time. Make sure sntp is runing and time is updated - #endif - AsyncStaticWebHandler& setTemplateProcessor(AwsTemplateProcessor newCallback) {_callback = newCallback; return *this;} -}; - -class AsyncCallbackWebHandler: public AsyncWebHandler { - private: - protected: - String _uri; - WebRequestMethodComposite _method; - ArRequestHandlerFunction _onRequest; - ArUploadHandlerFunction _onUpload; - ArBodyHandlerFunction _onBody; - bool _isRegex; - public: - AsyncCallbackWebHandler() : _uri(), _method(HTTP_ANY), _onRequest(NULL), _onUpload(NULL), _onBody(NULL), _isRegex(false) {} - void setUri(const String& uri){ - _uri = uri; - _isRegex = uri.startsWith("^") && uri.endsWith("$"); - } - void setMethod(WebRequestMethodComposite method){ _method = method; } - void onRequest(ArRequestHandlerFunction fn){ _onRequest = fn; } - void onUpload(ArUploadHandlerFunction fn){ _onUpload = fn; } - void onBody(ArBodyHandlerFunction fn){ _onBody = fn; } - - virtual bool canHandle(AsyncWebServerRequest *request) override final{ - - if(!_onRequest) - return false; - - if(!(_method & request->method())) - return false; - -#ifdef ASYNCWEBSERVER_REGEX - if (_isRegex) { - std::regex pattern(_uri.c_str()); - std::smatch matches; - std::string s(request->url().c_str()); - if(std::regex_search(s, matches, pattern)) { - for (size_t i = 1; i < matches.size(); ++i) { // start from 1 - request->_addPathParam(matches[i].str().c_str()); - } - } else { - return false; - } - } else -#endif - if (_uri.length() && _uri.endsWith("*")) { - String uriTemplate = String(_uri); - uriTemplate = uriTemplate.substring(0, uriTemplate.length() - 1); - if (!request->url().startsWith(uriTemplate)) - return false; - } - else if(_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri+"/"))) - return false; - - request->addInterestingHeader("ANY"); - return true; - } - - virtual void handleRequest(AsyncWebServerRequest *request) override final { - if(_onRequest) - _onRequest(request); - else - request->send(500); - } - virtual void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) override final { - if(_onUpload) - _onUpload(request, filename, index, data, len, final); - } - virtual void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override final { - if(_onBody) - _onBody(request, data, len, index, total); - } - virtual bool isRequestHandlerTrivial() override final {return _onRequest ? false : true;} -}; - -#endif /* ASYNCWEBSERVERHANDLERIMPL_H_ */ diff --git a/lib/ESPAsyncWebServer/WebHandlers.cpp b/lib/ESPAsyncWebServer/WebHandlers.cpp deleted file mode 100644 index 952d65eb9..000000000 --- a/lib/ESPAsyncWebServer/WebHandlers.cpp +++ /dev/null @@ -1,224 +0,0 @@ -/* - Asynchronous WebServer library for Espressif MCUs - - Copyright (c) 2016 Hristo Gochkov. All rights reserved. - This file is part of the esp8266 core for Arduino environment. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ -#include "ESPAsyncWebServer.h" -#include "WebHandlerImpl.h" - -AsyncStaticWebHandler::AsyncStaticWebHandler(const char* uri, FS& fs, const char* path, const char* cache_control) - : _fs(fs), _uri(uri), _path(path), _default_file(F("index.htm")), _cache_control(cache_control), _last_modified(), _callback(nullptr) -{ - // Ensure leading '/' - if (_uri.length() == 0 || _uri[0] != '/') _uri = String('/') + _uri; - if (_path.length() == 0 || _path[0] != '/') _path = String('/') + _path; - - // If path ends with '/' we assume a hint that this is a directory to improve performance. - // However - if it does not end with '/' we, can't assume a file, path can still be a directory. - _isDir = _path[_path.length()-1] == '/'; - - // Remove the trailing '/' so we can handle default file - // Notice that root will be "" not "/" - if (_uri[_uri.length()-1] == '/') _uri = _uri.substring(0, _uri.length()-1); - if (_path[_path.length()-1] == '/') _path = _path.substring(0, _path.length()-1); - - // Reset stats - _gzipFirst = false; - _gzipStats = 0xF8; -} - -AsyncStaticWebHandler& AsyncStaticWebHandler::setIsDir(bool isDir){ - _isDir = isDir; - return *this; -} - -AsyncStaticWebHandler& AsyncStaticWebHandler::setDefaultFile(const char* filename){ - _default_file = String(filename); - return *this; -} - -AsyncStaticWebHandler& AsyncStaticWebHandler::setCacheControl(const char* cache_control){ - _cache_control = String(cache_control); - return *this; -} - -AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(const char* last_modified){ - _last_modified = String(last_modified); - return *this; -} - -AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(struct tm* last_modified){ - auto formatP = PSTR("%a, %d %b %Y %H:%M:%S %Z"); - char format[strlen_P(formatP) + 1]; - strcpy_P(format, formatP); - - char result[30]; - strftime(result, sizeof(result), format, last_modified); - return setLastModified((const char *)result); -} - -#ifdef ESP8266 -AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(time_t last_modified){ - return setLastModified((struct tm *)gmtime(&last_modified)); -} - -AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(){ - time_t last_modified; - if(time(&last_modified) == 0) //time is not yet set - return *this; - return setLastModified(last_modified); -} -#endif -bool AsyncStaticWebHandler::canHandle(AsyncWebServerRequest *request){ - if(request->method() != HTTP_GET - || !request->url().startsWith(_uri) - || !request->isExpectedRequestedConnType(RCT_DEFAULT, RCT_HTTP) - ){ - return false; - } - if (_getFile(request)) { - // We interested in "If-Modified-Since" header to check if file was modified - if (_last_modified.length()) - request->addInterestingHeader(F("If-Modified-Since")); - - if(_cache_control.length()) - request->addInterestingHeader(F("If-None-Match")); - - DEBUGF("[AsyncStaticWebHandler::canHandle] TRUE\n"); - return true; - } - - return false; -} - -bool AsyncStaticWebHandler::_getFile(AsyncWebServerRequest *request) -{ - // Remove the found uri - String path = request->url().substring(_uri.length()); - - // We can skip the file check and look for default if request is to the root of a directory or that request path ends with '/' - bool canSkipFileCheck = (_isDir && path.length() == 0) || (path.length() && path[path.length()-1] == '/'); - - path = _path + path; - - // Do we have a file or .gz file - if (!canSkipFileCheck && _fileExists(request, path)) - return true; - - // Can't handle if not default file - if (_default_file.length() == 0) - return false; - - // Try to add default file, ensure there is a trailing '/' ot the path. - if (path.length() == 0 || path[path.length()-1] != '/') - path += String('/'); - path += _default_file; - - return _fileExists(request, path); -} - -#ifdef ESP32 -#define FILE_IS_REAL(f) (f == true && !f.isDirectory()) -#else -#define FILE_IS_REAL(f) (f == true) -#endif - -bool AsyncStaticWebHandler::_fileExists(AsyncWebServerRequest *request, const String& path) -{ - bool fileFound = false; - bool gzipFound = false; - - String gzip = path + F(".gz"); - - if (_gzipFirst) { - request->_tempFile = _fs.open(gzip, fs::FileOpenMode::read); - gzipFound = FILE_IS_REAL(request->_tempFile); - if (!gzipFound){ - request->_tempFile = _fs.open(path, fs::FileOpenMode::read); - fileFound = FILE_IS_REAL(request->_tempFile); - } - } else { - request->_tempFile = _fs.open(path, fs::FileOpenMode::read); - fileFound = FILE_IS_REAL(request->_tempFile); - if (!fileFound){ - request->_tempFile = _fs.open(gzip, fs::FileOpenMode::read); - gzipFound = FILE_IS_REAL(request->_tempFile); - } - } - - bool found = fileFound || gzipFound; - - if (found) { - // Extract the file name from the path and keep it in _tempObject - size_t pathLen = path.length(); - char * _tempPath = (char*)malloc(pathLen+1); - snprintf_P(_tempPath, pathLen+1, PSTR("%s"), path.c_str()); - request->_tempObject = (void*)_tempPath; - - // Calculate gzip statistic - _gzipStats = (_gzipStats << 1) + (gzipFound ? 1 : 0); - if (_gzipStats == 0x00) _gzipFirst = false; // All files are not gzip - else if (_gzipStats == 0xFF) _gzipFirst = true; // All files are gzip - else _gzipFirst = _countBits(_gzipStats) > 4; // IF we have more gzip files - try gzip first - } - - return found; -} - -uint8_t AsyncStaticWebHandler::_countBits(const uint8_t value) const -{ - uint8_t w = value; - uint8_t n; - for (n=0; w!=0; n++) w&=w-1; - return n; -} - -void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest *request) -{ - // Get the filename from request->_tempObject and free it - String filename = String((char*)request->_tempObject); - free(request->_tempObject); - request->_tempObject = NULL; - if((_username.length() && _password.length()) && !request->authenticate(_username.c_str(), _password.c_str())) - return request->requestAuthentication(); - - if (request->_tempFile == true) { - String etag = String(request->_tempFile.size()); - if (_last_modified.length() && _last_modified == request->header(F("If-Modified-Since"))) { - request->_tempFile.close(); - request->send(304); // Not modified - } else if (_cache_control.length() && request->hasHeader(F("If-None-Match")) && request->header(F("If-None-Match")).equals(etag)) { - request->_tempFile.close(); - AsyncWebServerResponse * response = new AsyncBasicResponse(304); // Not modified - response->addHeader(F("Cache-Control"), _cache_control); - response->addHeader(F("ETag"), etag); - request->send(response); - } else { - AsyncWebServerResponse * response = new AsyncFileResponse(request->_tempFile, filename, String(), false, _callback); - if (_last_modified.length()) - response->addHeader(F("Last-Modified"), _last_modified); - if (_cache_control.length()){ - response->addHeader(F("Cache-Control"), _cache_control); - response->addHeader(F("ETag"), etag); - } - request->send(response); - } - } else { - request->send(404); - } -} diff --git a/lib/ESPAsyncWebServer/WebRequest.cpp b/lib/ESPAsyncWebServer/WebRequest.cpp deleted file mode 100644 index ffe2c2623..000000000 --- a/lib/ESPAsyncWebServer/WebRequest.cpp +++ /dev/null @@ -1,930 +0,0 @@ -/* - Asynchronous WebServer library for Espressif MCUs - - Copyright (c) 2016 Hristo Gochkov. All rights reserved. - This file is part of the esp8266 core for Arduino environment. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ -#include "ESPAsyncWebServer.h" -#include "WebResponseImpl.h" -#include "WebAuthentication.h" - -#ifndef ESP8266 -#define os_strlen strlen -#endif - -#define __is_param_char(c) ((c) && ((c) != '{') && ((c) != '[') && ((c) != '&') && ((c) != '=')) - -enum { PARSE_REQ_START, PARSE_REQ_HEADERS, PARSE_REQ_BODY, PARSE_REQ_END, PARSE_REQ_FAIL }; - -AsyncWebServerRequest::AsyncWebServerRequest(AsyncWebServer* s, AsyncClient* c) - : _client(c) - , _server(s) - , _handler(NULL) - , _response(NULL) - , _temp() - , _parseState(0) - , _version(0) - , _method(HTTP_ANY) - , _url() - , _host() - , _contentType() - , _boundary() - , _authorization() - , _reqconntype(RCT_HTTP) - , _isDigest(false) - , _isMultipart(false) - , _isPlainPost(false) - , _expectingContinue(false) - , _contentLength(0) - , _parsedLength(0) - , _headers(LinkedList([](AsyncWebHeader *h){ delete h; })) - , _params(LinkedList([](AsyncWebParameter *p){ delete p; })) - , _pathParams(LinkedList([](String *p){ delete p; })) - , _multiParseState(0) - , _boundaryPosition(0) - , _itemStartIndex(0) - , _itemSize(0) - , _itemName() - , _itemFilename() - , _itemType() - , _itemValue() - , _itemBuffer(0) - , _itemBufferIndex(0) - , _itemIsFile(false) - , _tempObject(NULL) -{ - c->onError([](void *r, AsyncClient* c, int8_t error){ (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onError(error); }, this); - c->onAck([](void *r, AsyncClient* c, size_t len, uint32_t time){ (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onAck(len, time); }, this); - c->onDisconnect([](void *r, AsyncClient* c){ AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onDisconnect(); delete c; }, this); - c->onTimeout([](void *r, AsyncClient* c, uint32_t time){ (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onTimeout(time); }, this); - c->onData([](void *r, AsyncClient* c, void *buf, size_t len){ (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onData(buf, len); }, this); - c->onPoll([](void *r, AsyncClient* c){ (void)c; AsyncWebServerRequest *req = ( AsyncWebServerRequest*)r; req->_onPoll(); }, this); -} - -AsyncWebServerRequest::~AsyncWebServerRequest(){ - _headers.free(); - - _params.free(); - _pathParams.free(); - - _interestingHeaders.free(); - - if(_response != NULL){ - delete _response; - } - - if(_tempObject != NULL){ - free(_tempObject); - } - - if(_tempFile){ - _tempFile.close(); - } -} - -void AsyncWebServerRequest::_onData(void *buf, size_t len){ - size_t i = 0; - while (true) { - - if(_parseState < PARSE_REQ_BODY){ - // Find new line in buf - char *str = (char*)buf; - for (i = 0; i < len; i++) { - if (str[i] == '\n') { - break; - } - } - if (i == len) { // No new line, just add the buffer in _temp - char ch = str[len-1]; - str[len-1] = 0; - _temp.reserve(_temp.length()+len); - _temp.concat(str); - _temp.concat(ch); - } else { // Found new line - extract it and parse - str[i] = 0; // Terminate the string at the end of the line. - _temp.concat(str); - _temp.trim(); - _parseLine(); - if (++i < len) { - // Still have more buffer to process - buf = str+i; - len-= i; - continue; - } - } - } else if(_parseState == PARSE_REQ_BODY){ - // A handler should be already attached at this point in _parseLine function. - // If handler does nothing (_onRequest is NULL), we don't need to really parse the body. - const bool needParse = _handler && !_handler->isRequestHandlerTrivial(); - if(_isMultipart){ - if(needParse){ - size_t i; - for(i=0; i end) equal = end; - String name = params.substring(start, equal); - String value = equal + 1 < end ? params.substring(equal + 1, end) : String(); - _addParam(new AsyncWebParameter(urlDecode(name), urlDecode(value))); - start = end + 1; - } -} - -bool AsyncWebServerRequest::_parseReqHead(){ - // Split the head into method, url and version - int index = _temp.indexOf(' '); - String m = _temp.substring(0, index); - index = _temp.indexOf(' ', index+1); - String u = _temp.substring(m.length()+1, index); - _temp = _temp.substring(index+1); - - if(m == F("GET")){ - _method = HTTP_GET; - } else if(m == F("POST")){ - _method = HTTP_POST; - } else if(m == F("DELETE")){ - _method = HTTP_DELETE; - } else if(m == F("PUT")){ - _method = HTTP_PUT; - } else if(m == F("PATCH")){ - _method = HTTP_PATCH; - } else if(m == F("HEAD")){ - _method = HTTP_HEAD; - } else if(m == F("OPTIONS")){ - _method = HTTP_OPTIONS; - } - - String g; - index = u.indexOf('?'); - if(index > 0){ - g = u.substring(index +1); - u = u.substring(0, index); - } - _url = urlDecode(u); - _addGetParams(g); - - if(!_temp.startsWith(F("HTTP/1.0"))) - _version = 1; - - _temp = String(); - return true; -} - -bool strContains(const String &src, const String &find, bool mindcase = true) { - int pos=0, i=0; - const int slen = src.length(); - const int flen = find.length(); - - if (slen < flen) return false; - while (pos <= (slen - flen)) { - for (i=0; i < flen; i++) { - if (mindcase) { - if (src[pos+i] != find[i]) i = flen + 1; // no match - } - else if (tolower(src[pos+i]) != tolower(find[i])) { - i = flen + 1; // no match - } - } - if (i == flen) return true; - pos++; - } - return false; -} - -bool AsyncWebServerRequest::_parseReqHeader(){ - int index = _temp.indexOf(':'); - if(index){ - String name = _temp.substring(0, index); - String value = _temp.substring(index + 2); - if(name.equalsIgnoreCase("Host")){ - _host = value; - } else if(name.equalsIgnoreCase(F("Content-Type"))){ - _contentType = value.substring(0, value.indexOf(';')); - if (value.startsWith(F("multipart/"))){ - _boundary = value.substring(value.indexOf('=')+1); - _boundary.replace(String('"'), String()); - _isMultipart = true; - } - } else if(name.equalsIgnoreCase(F("Content-Length"))){ - _contentLength = atoi(value.c_str()); - } else if(name.equalsIgnoreCase(F("Expect")) && value == F("100-continue")){ - _expectingContinue = true; - } else if(name.equalsIgnoreCase(F("Authorization"))){ - if(value.length() > 5 && value.substring(0,5).equalsIgnoreCase(F("Basic"))){ - _authorization = value.substring(6); - } else if(value.length() > 6 && value.substring(0,6).equalsIgnoreCase(F("Digest"))){ - _isDigest = true; - _authorization = value.substring(7); - } - } else { - if(name.equalsIgnoreCase(F("Upgrade")) && value.equalsIgnoreCase(F("websocket"))){ - // WebSocket request can be uniquely identified by header: [Upgrade: websocket] - _reqconntype = RCT_WS; - } else { - if(name.equalsIgnoreCase(F("Accept")) && strContains(value, F("text/event-stream"), false)){ - // WebEvent request can be uniquely identified by header: [Accept: text/event-stream] - _reqconntype = RCT_EVENT; - } - } - } - _headers.add(new AsyncWebHeader(name, value)); - } - _temp = String(); - return true; -} - -void AsyncWebServerRequest::_parsePlainPostChar(uint8_t data){ - if(data && (char)data != '&') - _temp += (char)data; - if(!data || (char)data == '&' || _parsedLength == _contentLength){ - String name = F("body"); - String value = _temp; - if(!_temp.startsWith(String('{')) && !_temp.startsWith(String('[')) && _temp.indexOf('=') > 0){ - name = _temp.substring(0, _temp.indexOf('=')); - value = _temp.substring(_temp.indexOf('=') + 1); - } - _addParam(new AsyncWebParameter(urlDecode(name), urlDecode(value), true)); - _temp = String(); - } -} - -void AsyncWebServerRequest::_handleUploadByte(uint8_t data, bool last){ - _itemBuffer[_itemBufferIndex++] = data; - - if(last || _itemBufferIndex == 1460){ - //check if authenticated before calling the upload - if(_handler) - _handler->handleUpload(this, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, false); - _itemBufferIndex = 0; - } -} - -enum { - EXPECT_BOUNDARY, - PARSE_HEADERS, - WAIT_FOR_RETURN1, - EXPECT_FEED1, - EXPECT_DASH1, - EXPECT_DASH2, - BOUNDARY_OR_DATA, - DASH3_OR_RETURN2, - EXPECT_FEED2, - PARSING_FINISHED, - PARSE_ERROR -}; - -void AsyncWebServerRequest::_parseMultipartPostByte(uint8_t data, bool last){ -#define itemWriteByte(b) do { _itemSize++; if(_itemIsFile) _handleUploadByte(b, last); else _itemValue+=(char)(b); } while(0) - - if(!_parsedLength){ - _multiParseState = EXPECT_BOUNDARY; - _temp = String(); - _itemName = String(); - _itemFilename = String(); - _itemType = String(); - } - - if(_multiParseState == WAIT_FOR_RETURN1){ - if(data != '\r'){ - itemWriteByte(data); - } else { - _multiParseState = EXPECT_FEED1; - } - } else if(_multiParseState == EXPECT_BOUNDARY){ - if(_parsedLength < 2 && data != '-'){ - _multiParseState = PARSE_ERROR; - return; - } else if(_parsedLength - 2 < _boundary.length() && _boundary.c_str()[_parsedLength - 2] != data){ - _multiParseState = PARSE_ERROR; - return; - } else if(_parsedLength - 2 == _boundary.length() && data != '\r'){ - _multiParseState = PARSE_ERROR; - return; - } else if(_parsedLength - 3 == _boundary.length()){ - if(data != '\n'){ - _multiParseState = PARSE_ERROR; - return; - } - _multiParseState = PARSE_HEADERS; - _itemIsFile = false; - } - } else if(_multiParseState == PARSE_HEADERS){ - if((char)data != '\r' && (char)data != '\n') - _temp += (char)data; - if((char)data == '\n'){ - if(_temp.length()){ - if(_temp.length() > 12 && _temp.substring(0, 12).equalsIgnoreCase(F("Content-Type"))){ - _itemType = _temp.substring(14); - _itemIsFile = true; - } else if(_temp.length() > 19 && _temp.substring(0, 19).equalsIgnoreCase(F("Content-Disposition"))){ - _temp = _temp.substring(_temp.indexOf(';') + 2); - while(_temp.indexOf(';') > 0){ - String name = _temp.substring(0, _temp.indexOf('=')); - String nameVal = _temp.substring(_temp.indexOf('=') + 2, _temp.indexOf(';') - 1); - if(name == F("name")){ - _itemName = nameVal; - } else if(name == F("filename")){ - _itemFilename = nameVal; - _itemIsFile = true; - } - _temp = _temp.substring(_temp.indexOf(';') + 2); - } - String name = _temp.substring(0, _temp.indexOf('=')); - String nameVal = _temp.substring(_temp.indexOf('=') + 2, _temp.length() - 1); - if(name == F("name")){ - _itemName = nameVal; - } else if(name == F("filename")){ - _itemFilename = nameVal; - _itemIsFile = true; - } - } - _temp = String(); - } else { - _multiParseState = WAIT_FOR_RETURN1; - //value starts from here - _itemSize = 0; - _itemStartIndex = _parsedLength; - _itemValue = String(); - if(_itemIsFile){ - if(_itemBuffer) - free(_itemBuffer); - _itemBuffer = (uint8_t*)malloc(1460); - if(_itemBuffer == NULL){ - _multiParseState = PARSE_ERROR; - return; - } - _itemBufferIndex = 0; - } - } - } - } else if(_multiParseState == EXPECT_FEED1){ - if(data != '\n'){ - _multiParseState = WAIT_FOR_RETURN1; - itemWriteByte('\r'); _parseMultipartPostByte(data, last); - } else { - _multiParseState = EXPECT_DASH1; - } - } else if(_multiParseState == EXPECT_DASH1){ - if(data != '-'){ - _multiParseState = WAIT_FOR_RETURN1; - itemWriteByte('\r'); itemWriteByte('\n'); _parseMultipartPostByte(data, last); - } else { - _multiParseState = EXPECT_DASH2; - } - } else if(_multiParseState == EXPECT_DASH2){ - if(data != '-'){ - _multiParseState = WAIT_FOR_RETURN1; - itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); _parseMultipartPostByte(data, last); - } else { - _multiParseState = BOUNDARY_OR_DATA; - _boundaryPosition = 0; - } - } else if(_multiParseState == BOUNDARY_OR_DATA){ - if(_boundaryPosition < _boundary.length() && _boundary.c_str()[_boundaryPosition] != data){ - _multiParseState = WAIT_FOR_RETURN1; - itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); itemWriteByte('-'); - uint8_t i; - for(i=0; i<_boundaryPosition; i++) - itemWriteByte(_boundary.c_str()[i]); - _parseMultipartPostByte(data, last); - } else if(_boundaryPosition == _boundary.length() - 1){ - _multiParseState = DASH3_OR_RETURN2; - if(!_itemIsFile){ - _addParam(new AsyncWebParameter(_itemName, _itemValue, true)); - } else { - if(_itemSize){ - //check if authenticated before calling the upload - if(_handler) _handler->handleUpload(this, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, true); - _itemBufferIndex = 0; - _addParam(new AsyncWebParameter(_itemName, _itemFilename, true, true, _itemSize)); - } - free(_itemBuffer); - _itemBuffer = NULL; - } - - } else { - _boundaryPosition++; - } - } else if(_multiParseState == DASH3_OR_RETURN2){ - if(data == '-' && (_contentLength - _parsedLength - 4) != 0){ - //os_printf("ERROR: The parser got to the end of the POST but is expecting %u bytes more!\nDrop an issue so we can have more info on the matter!\n", _contentLength - _parsedLength - 4); - _contentLength = _parsedLength + 4;//lets close the request gracefully - } - if(data == '\r'){ - _multiParseState = EXPECT_FEED2; - } else if(data == '-' && _contentLength == (_parsedLength + 4)){ - _multiParseState = PARSING_FINISHED; - } else { - _multiParseState = WAIT_FOR_RETURN1; - itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); itemWriteByte('-'); - uint8_t i; for(i=0; i<_boundary.length(); i++) itemWriteByte(_boundary.c_str()[i]); - _parseMultipartPostByte(data, last); - } - } else if(_multiParseState == EXPECT_FEED2){ - if(data == '\n'){ - _multiParseState = PARSE_HEADERS; - _itemIsFile = false; - } else { - _multiParseState = WAIT_FOR_RETURN1; - itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); itemWriteByte('-'); - uint8_t i; for(i=0; i<_boundary.length(); i++) itemWriteByte(_boundary.c_str()[i]); - itemWriteByte('\r'); _parseMultipartPostByte(data, last); - } - } -} - -void AsyncWebServerRequest::_parseLine(){ - if(_parseState == PARSE_REQ_START){ - if(!_temp.length()){ - _parseState = PARSE_REQ_FAIL; - _client->close(); - } else { - _parseReqHead(); - _parseState = PARSE_REQ_HEADERS; - } - return; - } - - if(_parseState == PARSE_REQ_HEADERS){ - if(!_temp.length()){ - //end of headers - _server->_rewriteRequest(this); - _server->_attachHandler(this); - _removeNotInterestingHeaders(); - if(_expectingContinue){ - String response = F("HTTP/1.1 100 Continue\r\n\r\n"); - _client->write(response.c_str(), response.length()); - } - //check handler for authentication - if(_contentLength){ - _parseState = PARSE_REQ_BODY; - } else { - _parseState = PARSE_REQ_END; - if(_handler) _handler->handleRequest(this); - else send(501); - } - } else _parseReqHeader(); - } -} - -size_t AsyncWebServerRequest::headers() const{ - return _headers.length(); -} - -bool AsyncWebServerRequest::hasHeader(const String& name) const { - for(const auto& h: _headers){ - if(h->name().equalsIgnoreCase(name)){ - return true; - } - } - return false; -} - -bool AsyncWebServerRequest::hasHeader(const __FlashStringHelper * data) const { - return hasHeader(String(data)); -} - -AsyncWebHeader* AsyncWebServerRequest::getHeader(const String& name) const { - for(const auto& h: _headers){ - if(h->name().equalsIgnoreCase(name)){ - return h; - } - } - return nullptr; -} - -AsyncWebHeader* AsyncWebServerRequest::getHeader(const __FlashStringHelper * data) const { - return getHeader(String(data)); -} - -AsyncWebHeader* AsyncWebServerRequest::getHeader(size_t num) const { - auto header = _headers.nth(num); - return header ? *header : nullptr; -} - -size_t AsyncWebServerRequest::params() const { - return _params.length(); -} - -bool AsyncWebServerRequest::hasParam(const String& name, bool post, bool file) const { - for(const auto& p: _params){ - if(p->name() == name && p->isPost() == post && p->isFile() == file){ - return true; - } - } - return false; -} - -bool AsyncWebServerRequest::hasParam(const __FlashStringHelper * data, bool post, bool file) const { - return hasParam(String(data).c_str(), post, file); -} - -AsyncWebParameter* AsyncWebServerRequest::getParam(const String& name, bool post, bool file) const { - for(const auto& p: _params){ - if(p->name() == name && p->isPost() == post && p->isFile() == file){ - return p; - } - } - return nullptr; -} - -AsyncWebParameter* AsyncWebServerRequest::getParam(const __FlashStringHelper * data, bool post, bool file) const { - return getParam(String(data).c_str(), post, file); -} - -AsyncWebParameter* AsyncWebServerRequest::getParam(size_t num) const { - auto param = _params.nth(num); - return param ? *param : nullptr; -} - -void AsyncWebServerRequest::addInterestingHeader(const String& name){ - if(!_interestingHeaders.containsIgnoreCase(name)) - _interestingHeaders.add(name); -} - -void AsyncWebServerRequest::send(AsyncWebServerResponse *response){ - _response = response; - if(_response == NULL){ - _client->close(true); - _onDisconnect(); - return; - } - if(!_response->_sourceValid()){ - delete response; - _response = NULL; - send(500); - } - else { - _client->setRxTimeout(0); - _response->_respond(this); - } -} - -AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(int code, const String& contentType, const String& content){ - return new AsyncBasicResponse(code, contentType, content); -} - -AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(FS &fs, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback){ - if(fs.exists(path) || (!download && fs.exists(path+F(".gz")))) - return new AsyncFileResponse(fs, path, contentType, download, callback); - return NULL; -} - -AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(File content, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback){ - if(content == true) - return new AsyncFileResponse(content, path, contentType, download, callback); - return NULL; -} - -AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback){ - return new AsyncStreamResponse(stream, contentType, len, callback); -} - -AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback){ - return new AsyncCallbackResponse(contentType, len, callback, templateCallback); -} - -AsyncWebServerResponse * AsyncWebServerRequest::beginChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback){ - if(_version) - return new AsyncChunkedResponse(contentType, callback, templateCallback); - return new AsyncCallbackResponse(contentType, 0, callback, templateCallback); -} - -AsyncResponseStream * AsyncWebServerRequest::beginResponseStream(const String& contentType, size_t bufferSize){ - return new AsyncResponseStream(contentType, bufferSize); -} - -AsyncWebServerResponse * AsyncWebServerRequest::beginResponse_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback){ - return new AsyncProgmemResponse(code, contentType, content, len, callback); -} - -AsyncWebServerResponse * AsyncWebServerRequest::beginResponse_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback){ - return beginResponse_P(code, contentType, (const uint8_t *)content, strlen_P(content), callback); -} - -void AsyncWebServerRequest::send(int code, const String& contentType, const String& content){ - send(beginResponse(code, contentType, content)); -} - -void AsyncWebServerRequest::send(FS &fs, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback){ - if(fs.exists(path) || (!download && fs.exists(path+F(".gz")))){ - send(beginResponse(fs, path, contentType, download, callback)); - } else send(404); -} - -void AsyncWebServerRequest::send(File content, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback){ - if(content == true){ - send(beginResponse(content, path, contentType, download, callback)); - } else send(404); -} - -void AsyncWebServerRequest::send(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback){ - send(beginResponse(stream, contentType, len, callback)); -} - -void AsyncWebServerRequest::send(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback){ - send(beginResponse(contentType, len, callback, templateCallback)); -} - -void AsyncWebServerRequest::sendChunked(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback){ - send(beginChunkedResponse(contentType, callback, templateCallback)); -} - -void AsyncWebServerRequest::send_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback){ - send(beginResponse_P(code, contentType, content, len, callback)); -} - -void AsyncWebServerRequest::send_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback){ - send(beginResponse_P(code, contentType, content, callback)); -} - -void AsyncWebServerRequest::redirect(const String& url){ - AsyncWebServerResponse * response = beginResponse(302); - response->addHeader(F("Location"), url); - send(response); -} - -bool AsyncWebServerRequest::authenticate(const char * username, const char * password, const char * realm, bool passwordIsHash){ - if(_authorization.length()){ - if(_isDigest) - return checkDigestAuthentication(_authorization.c_str(), methodToString(), username, password, realm, passwordIsHash, NULL, NULL, NULL); - else if(!passwordIsHash) - return checkBasicAuthentication(_authorization.c_str(), username, password); - else - return _authorization.equals(password); - } - return false; -} - -bool AsyncWebServerRequest::authenticate(const char * hash){ - if(!_authorization.length() || hash == NULL) - return false; - - if(_isDigest){ - String hStr = String(hash); - int separator = hStr.indexOf(':'); - if(separator <= 0) - return false; - String username = hStr.substring(0, separator); - hStr = hStr.substring(separator + 1); - separator = hStr.indexOf(':'); - if(separator <= 0) - return false; - String realm = hStr.substring(0, separator); - hStr = hStr.substring(separator + 1); - return checkDigestAuthentication(_authorization.c_str(), methodToString(), username.c_str(), hStr.c_str(), realm.c_str(), true, NULL, NULL, NULL); - } - - return (_authorization.equals(hash)); -} - -void AsyncWebServerRequest::requestAuthentication(const char * realm, bool isDigest){ - AsyncWebServerResponse * r = beginResponse(401); - if(!isDigest && realm == NULL){ - r->addHeader(F("WWW-Authenticate"), F("Basic realm=\"Login Required\"")); - } else if(!isDigest){ - String header = F("Basic realm=\""); - header.concat(realm); - header += '"'; - r->addHeader(F("WWW-Authenticate"), header); - } else { - String header = F("Digest "); - header.concat(requestDigestAuthentication(realm)); - r->addHeader(F("WWW-Authenticate"), header); - } - send(r); -} - -bool AsyncWebServerRequest::hasArg(const char* name) const { - for(const auto& arg: _params){ - if(arg->name() == name){ - return true; - } - } - return false; -} - -bool AsyncWebServerRequest::hasArg(const __FlashStringHelper * data) const { - return hasArg(String(data).c_str()); -} - - -const String& AsyncWebServerRequest::arg(const String& name) const { - for(const auto& arg: _params){ - if(arg->name() == name){ - return arg->value(); - } - } - return emptyString; -} - -const String& AsyncWebServerRequest::arg(const __FlashStringHelper * data) const { - return arg(String(data).c_str()); -} - -const String& AsyncWebServerRequest::arg(size_t i) const { - return getParam(i)->value(); -} - -const String& AsyncWebServerRequest::argName(size_t i) const { - return getParam(i)->name(); -} - -const String& AsyncWebServerRequest::pathArg(size_t i) const { - auto param = _pathParams.nth(i); - return param ? **param : emptyString; -} - -const String& AsyncWebServerRequest::header(const char* name) const { - AsyncWebHeader* h = getHeader(String(name)); - return h ? h->value() : emptyString; -} - -const String& AsyncWebServerRequest::header(const __FlashStringHelper * data) const { - return header(String(data).c_str()); -}; - - -const String& AsyncWebServerRequest::header(size_t i) const { - AsyncWebHeader* h = getHeader(i); - return h ? h->value() : emptyString; -} - -const String& AsyncWebServerRequest::headerName(size_t i) const { - AsyncWebHeader* h = getHeader(i); - return h ? h->name() : emptyString; -} - -String AsyncWebServerRequest::urlDecode(const String& text) const { - char temp[] = "0x00"; - unsigned int len = text.length(); - unsigned int i = 0; - String decoded = String(); - decoded.reserve(len); // Allocate the string internal buffer - never longer from source text - while (i < len){ - char decodedChar; - char encodedChar = text.charAt(i++); - if ((encodedChar == '%') && (i + 1 < len)){ - temp[2] = text.charAt(i++); - temp[3] = text.charAt(i++); - decodedChar = strtol(temp, NULL, 16); - } else if (encodedChar == '+') { - decodedChar = ' '; - } else { - decodedChar = encodedChar; // normal ascii char - } - decoded.concat(decodedChar); - } - return decoded; -} - - -const char *AsyncWebServerRequest::methodToString() const { - if(_method == HTTP_ANY) return ("ANY"); - else if(_method & HTTP_GET) return ("GET"); - else if(_method & HTTP_POST) return ("POST"); - else if(_method & HTTP_DELETE) return ("DELETE"); - else if(_method & HTTP_PUT) return ("PUT"); - else if(_method & HTTP_PATCH) return ("PATCH"); - else if(_method & HTTP_HEAD) return ("HEAD"); - else if(_method & HTTP_OPTIONS) return ("OPTIONS"); - return ("UNKNOWN"); -} - -const char *AsyncWebServerRequest::requestedConnTypeToString() const { - switch (_reqconntype) { - case RCT_NOT_USED: return ("RCT_NOT_USED"); - case RCT_DEFAULT: return ("RCT_DEFAULT"); - case RCT_HTTP: return ("RCT_HTTP"); - case RCT_WS: return ("RCT_WS"); - case RCT_EVENT: return ("RCT_EVENT"); - default: return ("ERROR"); - } -} - -bool AsyncWebServerRequest::isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2, RequestedConnectionType erct3) { - bool res = false; - if ((erct1 != RCT_NOT_USED) && (erct1 == _reqconntype)) res = true; - if ((erct2 != RCT_NOT_USED) && (erct2 == _reqconntype)) res = true; - if ((erct3 != RCT_NOT_USED) && (erct3 == _reqconntype)) res = true; - return res; -} diff --git a/lib/ESPAsyncWebServer/WebResponseImpl.h b/lib/ESPAsyncWebServer/WebResponseImpl.h deleted file mode 100644 index 9a64e3a52..000000000 --- a/lib/ESPAsyncWebServer/WebResponseImpl.h +++ /dev/null @@ -1,136 +0,0 @@ -/* - Asynchronous WebServer library for Espressif MCUs - - Copyright (c) 2016 Hristo Gochkov. All rights reserved. - This file is part of the esp8266 core for Arduino environment. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ -#ifndef ASYNCWEBSERVERRESPONSEIMPL_H_ -#define ASYNCWEBSERVERRESPONSEIMPL_H_ - -#ifdef Arduino_h -// arduino is not compatible with std::vector -#undef min -#undef max -#endif -#include -// It is possible to restore these defines, but one can use _min and _max instead. Or std::min, std::max. - -class AsyncBasicResponse: public AsyncWebServerResponse { - private: - String _content; - public: - AsyncBasicResponse(int code, const String& contentType=String(), const String& content=String()); - void _respond(AsyncWebServerRequest *request); - size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); - bool _sourceValid() const { return true; } -}; - -class AsyncAbstractResponse: public AsyncWebServerResponse { - private: - String _head; - // Data is inserted into cache at begin(). - // This is inefficient with vector, but if we use some other container, - // we won't be able to access it as contiguous array of bytes when reading from it, - // so by gaining performance in one place, we'll lose it in another. - std::vector _cache; - size_t _readDataFromCacheOrContent(uint8_t* data, const size_t len); - size_t _fillBufferAndProcessTemplates(uint8_t* buf, size_t maxLen); - protected: - AwsTemplateProcessor _callback; - public: - AsyncAbstractResponse(AwsTemplateProcessor callback=nullptr); - void _respond(AsyncWebServerRequest *request); - size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); - bool _sourceValid() const { return false; } - virtual size_t _fillBuffer(uint8_t *buf __attribute__((unused)), size_t maxLen __attribute__((unused))) { return 0; } -}; - -#ifndef TEMPLATE_PLACEHOLDER -#define TEMPLATE_PLACEHOLDER '%' -#endif - -#define TEMPLATE_PARAM_NAME_LENGTH 32 -class AsyncFileResponse: public AsyncAbstractResponse { - using File = fs::File; - using FS = fs::FS; - private: - File _content; - String _path; - void _setContentType(const String& path); - public: - AsyncFileResponse(FS &fs, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); - AsyncFileResponse(File content, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); - ~AsyncFileResponse(); - bool _sourceValid() const { return !!(_content); } - virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; -}; - -class AsyncStreamResponse: public AsyncAbstractResponse { - private: - Stream *_content; - public: - AsyncStreamResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback=nullptr); - bool _sourceValid() const { return !!(_content); } - virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; -}; - -class AsyncCallbackResponse: public AsyncAbstractResponse { - private: - AwsResponseFiller _content; - size_t _filledLength; - public: - AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); - bool _sourceValid() const { return !!(_content); } - virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; -}; - -class AsyncChunkedResponse: public AsyncAbstractResponse { - private: - AwsResponseFiller _content; - size_t _filledLength; - public: - AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); - bool _sourceValid() const { return !!(_content); } - virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; -}; - -class AsyncProgmemResponse: public AsyncAbstractResponse { - private: - const uint8_t * _content; - size_t _readLength; - public: - AsyncProgmemResponse(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr); - bool _sourceValid() const { return true; } - virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; -}; - -class cbuf; - -class AsyncResponseStream: public AsyncAbstractResponse, public Print { - private: - cbuf *_content; - public: - AsyncResponseStream(const String& contentType, size_t bufferSize); - ~AsyncResponseStream(); - bool _sourceValid() const { return (_state < RESPONSE_END); } - virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; - size_t write(const uint8_t *data, size_t len); - size_t write(uint8_t data); - using Print::write; -}; - -#endif /* ASYNCWEBSERVERRESPONSEIMPL_H_ */ diff --git a/lib/ESPAsyncWebServer/WebResponses.cpp b/lib/ESPAsyncWebServer/WebResponses.cpp deleted file mode 100644 index 63bdcd0ff..000000000 --- a/lib/ESPAsyncWebServer/WebResponses.cpp +++ /dev/null @@ -1,789 +0,0 @@ -/* - Asynchronous WebServer library for Espressif MCUs - - Copyright (c) 2016 Hristo Gochkov. All rights reserved. - This file is part of the esp8266 core for Arduino environment. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ -#include "ESPAsyncWebServer.h" -#include "WebResponseImpl.h" -#include "cbuf.h" - -// Since ESP8266 does not link memchr by default, here's its implementation. -void * memchr(void * ptr, int ch, size_t count) { - unsigned char * p = static_cast(ptr); - while (count--) - if (*p++ == static_cast(ch)) - return --p; - return nullptr; -} - - -/* - * Abstract Response - * */ -const char * AsyncWebServerResponse::_responseCodeToString(int code) { - switch (code) { - case 100: - return ("Continue"); - case 101: - return ("Switching Protocols"); - case 200: - return ("OK"); - case 201: - return ("Created"); - case 202: - return ("Accepted"); // proddy: used in wifi - case 203: - return ("Non-Authoritative Information"); - case 204: - return ("No Content"); - case 205: - return ("Reset Content"); // proddy: reboot required - case 206: - return ("Partial Content"); - case 300: - return ("Multiple Choices"); - case 301: - return ("Moved Permanently"); - case 302: - return ("Found"); - case 303: - return ("See Other"); - case 304: - return ("Not Modified"); - case 305: - return ("Use Proxy"); - case 307: - return ("Temporary Redirect"); - case 400: - return ("Bad Request"); - case 401: - return ("Unauthorized"); - case 402: - return ("Payment Required"); - case 403: - return ("Forbidden"); - case 404: - return ("Not Found"); - case 405: - return ("Method Not Allowed"); - case 406: - return ("Not Acceptable"); - case 407: - return ("Proxy Authentication Required"); - case 408: - return ("Request Time-out"); - case 409: - return ("Conflict"); - case 410: - return ("Gone"); - case 411: - return ("Length Required"); - case 412: - return ("Precondition Failed"); - case 413: - return ("Request Entity Too Large"); - case 414: - return ("Request-URI Too Large"); - case 415: - return ("Unsupported Media Type"); - case 416: - return ("Requested range not satisfiable"); - case 417: - return ("Expectation Failed"); - case 500: - return ("Internal Server Error"); - case 501: - return ("Not Implemented"); - case 502: - return ("Bad Gateway"); - case 503: - return ("Service Unavailable"); - case 504: - return ("Gateway Time-out"); - case 505: - return ("HTTP Version not supported"); - case 507: - return ("Insufficient Storage"); - default: - return (""); - } -} - -const __FlashStringHelper * AsyncWebServerResponse::responseCodeToString(int code) { - return reinterpret_cast(responseCodeToString(code)); -} - -AsyncWebServerResponse::AsyncWebServerResponse() - : _code(0) - , _headers(LinkedList([](AsyncWebHeader * h) { delete h; })) - , _contentType() - , _contentLength(0) - , _sendContentLength(true) - , _chunked(false) - , _headLength(0) - , _sentLength(0) - , _ackedLength(0) - , _writtenLength(0) - , _state(RESPONSE_SETUP) { - for (auto header : DefaultHeaders::Instance()) { - _headers.add(new AsyncWebHeader(header->name(), header->value())); - } -} - -AsyncWebServerResponse::~AsyncWebServerResponse() { - _headers.free(); -} - -void AsyncWebServerResponse::setCode(int code) { - if (_state == RESPONSE_SETUP) - _code = code; -} - -void AsyncWebServerResponse::setContentLength(size_t len) { - if (_state == RESPONSE_SETUP) - _contentLength = len; -} - -void AsyncWebServerResponse::setContentType(const String & type) { - if (_state == RESPONSE_SETUP) - _contentType = type; -} - -void AsyncWebServerResponse::addHeader(const String & name, const String & value) { - _headers.add(new AsyncWebHeader(name, value)); -} - -String AsyncWebServerResponse::_assembleHead(uint8_t version) { - if (version) { - addHeader(F("Accept-Ranges"), F("none")); - if (_chunked) - addHeader(F("Transfer-Encoding"), F("chunked")); - } - String out = String(); - int bufSize = 300; - char buf[bufSize]; - - snprintf_P(buf, bufSize, PSTR("HTTP/1.%d %d %s\r\n"), version, _code, _responseCodeToString(_code)); - out.concat(buf); - - if (_sendContentLength) { - snprintf_P(buf, bufSize, PSTR("Content-Length: %d\r\n"), _contentLength); - out.concat(buf); - } - if (_contentType.length()) { - snprintf_P(buf, bufSize, PSTR("Content-Type: %s\r\n"), _contentType.c_str()); - out.concat(buf); - } - - for (const auto & header : _headers) { - snprintf_P(buf, bufSize, PSTR("%s: %s\r\n"), header->name().c_str(), header->value().c_str()); - out.concat(buf); - } - _headers.free(); - - out.concat(F("\r\n")); - _headLength = out.length(); - return out; -} - -bool AsyncWebServerResponse::_started() const { - return _state > RESPONSE_SETUP; -} -bool AsyncWebServerResponse::_finished() const { - return _state > RESPONSE_WAIT_ACK; -} -bool AsyncWebServerResponse::_failed() const { - return _state == RESPONSE_FAILED; -} -bool AsyncWebServerResponse::_sourceValid() const { - return false; -} -void AsyncWebServerResponse::_respond(AsyncWebServerRequest * request) { - _state = RESPONSE_END; - request->client()->close(); -} -size_t AsyncWebServerResponse::_ack(AsyncWebServerRequest * request, size_t len, uint32_t time) { - (void)request; - (void)len; - (void)time; - return 0; -} - -/* - * String/Code Response - * */ -AsyncBasicResponse::AsyncBasicResponse(int code, const String & contentType, const String & content) { - _code = code; - _content = content; - _contentType = contentType; - if (_content.length()) { - _contentLength = _content.length(); - if (!_contentType.length()) - _contentType = F("text/plain"); - } - addHeader(F("Connection"), F("close")); -} - -void AsyncBasicResponse::_respond(AsyncWebServerRequest * request) { - _state = RESPONSE_HEADERS; - String out = _assembleHead(request->version()); - size_t outLen = out.length(); - size_t space = request->client()->space(); - if (!_contentLength && space >= outLen) { - _writtenLength += request->client()->write(out.c_str(), outLen); - _state = RESPONSE_WAIT_ACK; - } else if (_contentLength && space >= outLen + _contentLength) { - out += _content; - outLen += _contentLength; - _writtenLength += request->client()->write(out.c_str(), outLen); - _state = RESPONSE_WAIT_ACK; - } else if (space && space < outLen) { - String partial = out.substring(0, space); - _content = out.substring(space) + _content; - _contentLength += outLen - space; - _writtenLength += request->client()->write(partial.c_str(), partial.length()); - _state = RESPONSE_CONTENT; - } else if (space > outLen && space < (outLen + _contentLength)) { - size_t shift = space - outLen; - outLen += shift; - _sentLength += shift; - out += _content.substring(0, shift); - _content = _content.substring(shift); - _writtenLength += request->client()->write(out.c_str(), outLen); - _state = RESPONSE_CONTENT; - } else { - _content = out + _content; - _contentLength += outLen; - _state = RESPONSE_CONTENT; - } -} - -size_t AsyncBasicResponse::_ack(AsyncWebServerRequest * request, size_t len, uint32_t time) { - (void)time; - _ackedLength += len; - if (_state == RESPONSE_CONTENT) { - size_t available = _contentLength - _sentLength; - size_t space = request->client()->space(); - //we can fit in this packet - if (space > available) { - _writtenLength += request->client()->write(_content.c_str(), available); - _content = String(); - _state = RESPONSE_WAIT_ACK; - return available; - } - //send some data, the rest on ack - String out = _content.substring(0, space); - _content = _content.substring(space); - _sentLength += space; - _writtenLength += request->client()->write(out.c_str(), space); - return space; - } else if (_state == RESPONSE_WAIT_ACK) { - if (_ackedLength >= _writtenLength) { - _state = RESPONSE_END; - } - } - return 0; -} - - -/* - * Abstract Response - * */ - -AsyncAbstractResponse::AsyncAbstractResponse(AwsTemplateProcessor callback) - : _callback(callback) { - // In case of template processing, we're unable to determine real response size - if (callback) { - _contentLength = 0; - _sendContentLength = false; - _chunked = true; - } -} - -void AsyncAbstractResponse::_respond(AsyncWebServerRequest * request) { - addHeader(F("Connection"), F("close")); - _head = _assembleHead(request->version()); - _state = RESPONSE_HEADERS; - _ack(request, 0, 0); -} - -size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest * request, size_t len, uint32_t time) { - (void)time; - if (!_sourceValid()) { - _state = RESPONSE_FAILED; - request->client()->close(); - return 0; - } - _ackedLength += len; - size_t space = request->client()->space(); - - size_t headLen = _head.length(); - if (_state == RESPONSE_HEADERS) { - if (space >= headLen) { - _state = RESPONSE_CONTENT; - space -= headLen; - } else { - String out = _head.substring(0, space); - _head = _head.substring(space); - _writtenLength += request->client()->write(out.c_str(), out.length()); - return out.length(); - } - } - - if (_state == RESPONSE_CONTENT) { - size_t outLen; - if (_chunked) { - if (space <= 8) { - return 0; - } - outLen = space; - } else if (!_sendContentLength) { - outLen = space; - } else { - outLen = ((_contentLength - _sentLength) > space) ? space : (_contentLength - _sentLength); - } - - uint8_t * buf = (uint8_t *)malloc(outLen + headLen); - if (!buf) { - // os_printf("_ack malloc %d failed\n", outLen+headLen); - return 0; - } - - if (headLen) { - memcpy(buf, _head.c_str(), _head.length()); - } - - size_t readLen = 0; - - if (_chunked) { - // HTTP 1.1 allows leading zeros in chunk length. Or spaces may be added. - // See RFC2616 sections 2, 3.6.1. - readLen = _fillBufferAndProcessTemplates(buf + headLen + 6, outLen - 8); - if (readLen == RESPONSE_TRY_AGAIN) { - free(buf); - return 0; - } - outLen = snprintf_P((char *)buf + headLen, sizeof(buf) - headLen - 2, PSTR("%x"), readLen) + headLen; - while (outLen < headLen + 4) - buf[outLen++] = ' '; - buf[outLen++] = '\r'; - buf[outLen++] = '\n'; - outLen += readLen; - buf[outLen++] = '\r'; - buf[outLen++] = '\n'; - } else { - readLen = _fillBufferAndProcessTemplates(buf + headLen, outLen); - if (readLen == RESPONSE_TRY_AGAIN) { - free(buf); - return 0; - } - outLen = readLen + headLen; - } - - if (headLen) { - _head = String(); - } - - if (outLen) { - _writtenLength += request->client()->write((const char *)buf, outLen); - } - - if (_chunked) { - _sentLength += readLen; - } else { - _sentLength += outLen - headLen; - } - - free(buf); - - if ((_chunked && readLen == 0) || (!_sendContentLength && outLen == 0) || (!_chunked && _sentLength == _contentLength)) { - _state = RESPONSE_WAIT_ACK; - } - return outLen; - - } else if (_state == RESPONSE_WAIT_ACK) { - if (!_sendContentLength || _ackedLength >= _writtenLength) { - _state = RESPONSE_END; - if (!_chunked && !_sendContentLength) - request->client()->close(true); - } - } - return 0; -} - -size_t AsyncAbstractResponse::_readDataFromCacheOrContent(uint8_t * data, const size_t len) { - // If we have something in cache, copy it to buffer - const size_t readFromCache = std::min(len, _cache.size()); - if (readFromCache) { - memcpy(data, _cache.data(), readFromCache); - _cache.erase(_cache.begin(), _cache.begin() + readFromCache); - } - // If we need to read more... - const size_t needFromFile = len - readFromCache; - const size_t readFromContent = _fillBuffer(data + readFromCache, needFromFile); - return readFromCache + readFromContent; -} - -size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t * data, size_t len) { - if (!_callback) - return _fillBuffer(data, len); - - const size_t originalLen = len; - len = _readDataFromCacheOrContent(data, len); - // Now we've read 'len' bytes, either from cache or from file - // Search for template placeholders - uint8_t * pTemplateStart = data; - while ((pTemplateStart < &data[len]) - && (pTemplateStart = (uint8_t *)memchr(pTemplateStart, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart + 1))) { // data[0] ... data[len - 1] - uint8_t * pTemplateEnd = - (pTemplateStart < &data[len - 1]) ? (uint8_t *)memchr(pTemplateStart + 1, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart) : nullptr; - // temporary buffer to hold parameter name - uint8_t buf[TEMPLATE_PARAM_NAME_LENGTH + 1]; - String paramName; - // If closing placeholder is found: - if (pTemplateEnd) { - // prepare argument to callback - const size_t paramNameLength = std::min((size_t)sizeof(buf) - 1, (size_t)(pTemplateEnd - pTemplateStart - 1)); - if (paramNameLength) { - memcpy(buf, pTemplateStart + 1, paramNameLength); - buf[paramNameLength] = 0; - paramName = String(reinterpret_cast(buf)); - } else { // double percent sign encountered, this is single percent sign escaped. - // remove the 2nd percent sign - memmove(pTemplateEnd, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1); - len += _readDataFromCacheOrContent(&data[len - 1], 1) - 1; - ++pTemplateStart; - } - } else if (&data[len - 1] - pTemplateStart + 1 < TEMPLATE_PARAM_NAME_LENGTH + 2) { // closing placeholder not found, check if it's in the remaining file data - memcpy(buf, pTemplateStart + 1, &data[len - 1] - pTemplateStart); - const size_t readFromCacheOrContent = - _readDataFromCacheOrContent(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PARAM_NAME_LENGTH + 2 - (&data[len - 1] - pTemplateStart + 1)); - if (readFromCacheOrContent) { - pTemplateEnd = (uint8_t *)memchr(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PLACEHOLDER, readFromCacheOrContent); - if (pTemplateEnd) { - // prepare argument to callback - *pTemplateEnd = 0; - paramName = String(reinterpret_cast(buf)); - // Copy remaining read-ahead data into cache - _cache.insert(_cache.begin(), pTemplateEnd + 1, buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent); - pTemplateEnd = &data[len - 1]; - } else // closing placeholder not found in file data, store found percent symbol as is and advance to the next position - { - // but first, store read file data in cache - _cache.insert(_cache.begin(), buf + (&data[len - 1] - pTemplateStart), buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent); - ++pTemplateStart; - } - } else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position - ++pTemplateStart; - } else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position - ++pTemplateStart; - if (paramName.length()) { - // call callback and replace with result. - // Everything in range [pTemplateStart, pTemplateEnd] can be safely replaced with parameter value. - // Data after pTemplateEnd may need to be moved. - // The first byte of data after placeholder is located at pTemplateEnd + 1. - // It should be located at pTemplateStart + numBytesCopied (to begin right after inserted parameter value). - const String paramValue(_callback(paramName)); - const char * pvstr = paramValue.c_str(); - const unsigned int pvlen = paramValue.length(); - const size_t numBytesCopied = std::min(pvlen, static_cast(&data[originalLen - 1] - pTemplateStart + 1)); - // make room for param value - // 1. move extra data to cache if parameter value is longer than placeholder AND if there is no room to store - if ((pTemplateEnd + 1 < pTemplateStart + numBytesCopied) && (originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1) < len)) { - _cache.insert(_cache.begin(), &data[originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1)], &data[len]); - //2. parameter value is longer than placeholder text, push the data after placeholder which not saved into cache further to the end - memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[originalLen] - pTemplateStart - numBytesCopied); - len = originalLen; // fix issue with truncated data, not sure if it has any side effects - } else if (pTemplateEnd + 1 != pTemplateStart + numBytesCopied) - //2. Either parameter value is shorter than placeholder text OR there is enough free space in buffer to fit. - // Move the entire data after the placeholder - memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1); - // 3. replace placeholder with actual value - memcpy(pTemplateStart, pvstr, numBytesCopied); - // If result is longer than buffer, copy the remainder into cache (this could happen only if placeholder text itself did not fit entirely in buffer) - if (numBytesCopied < pvlen) { - _cache.insert(_cache.begin(), pvstr + numBytesCopied, pvstr + pvlen); - } else if (pTemplateStart + numBytesCopied < pTemplateEnd + 1) { // result is copied fully; if result is shorter than placeholder text... - // there is some free room, fill it from cache - const size_t roomFreed = pTemplateEnd + 1 - pTemplateStart - numBytesCopied; - const size_t totalFreeRoom = originalLen - len + roomFreed; - len += _readDataFromCacheOrContent(&data[len - roomFreed], totalFreeRoom) - roomFreed; - } else { // result is copied fully; it is longer than placeholder text - const size_t roomTaken = pTemplateStart + numBytesCopied - pTemplateEnd - 1; - len = std::min(len + roomTaken, originalLen); - } - } - } // while(pTemplateStart) - return len; -} - - -/* - * File Response - * */ - -AsyncFileResponse::~AsyncFileResponse() { - if (_content) - _content.close(); -} - -void AsyncFileResponse::_setContentType(const String & path) { -#if HAVE_EXTERN_GET_CONTENT_TYPE_FUNCTION - extern const __FlashStringHelper * getContentType(const String & path); - _contentType = getContentType(path); -#else - if (path.endsWith(F(".html"))) - _contentType = F("text/html"); - else if (path.endsWith(F(".htm"))) - _contentType = F("text/html"); - else if (path.endsWith(F(".css"))) - _contentType = F("text/css"); - else if (path.endsWith(F(".json"))) - _contentType = F("application/json"); - else if (path.endsWith(F(".js"))) - _contentType = F("application/javascript"); - else if (path.endsWith(F(".png"))) - _contentType = F("image/png"); - else if (path.endsWith(F(".gif"))) - _contentType = F("image/gif"); - else if (path.endsWith(F(".jpg"))) - _contentType = F("image/jpeg"); - else if (path.endsWith(F(".ico"))) - _contentType = F("image/x-icon"); - else if (path.endsWith(F(".svg"))) - _contentType = F("image/svg+xml"); - else if (path.endsWith(F(".eot"))) - _contentType = F("font/eot"); - else if (path.endsWith(F(".woff"))) - _contentType = F("font/woff"); - else if (path.endsWith(F(".woff2"))) - _contentType = F("font/woff2"); - else if (path.endsWith(F(".ttf"))) - _contentType = F("font/ttf"); - else if (path.endsWith(F(".xml"))) - _contentType = F("text/xml"); - else if (path.endsWith(F(".pdf"))) - _contentType = F("application/pdf"); - else if (path.endsWith(F(".zip"))) - _contentType = F("application/zip"); - else if (path.endsWith(F(".gz"))) - _contentType = F("application/x-gzip"); - else - _contentType = F("text/plain"); -#endif -} - -AsyncFileResponse::AsyncFileResponse(FS & fs, const String & path, const String & contentType, bool download, AwsTemplateProcessor callback) - : AsyncAbstractResponse(callback) { - _code = 200; - _path = path; - - if (!download && !fs.exists(_path) && fs.exists(_path + F(".gz"))) { - _path = _path + F(".gz"); - addHeader(F("Content-Encoding"), F("gzip")); - _callback = nullptr; // Unable to process zipped templates - _sendContentLength = true; - _chunked = false; - } - - _content = fs.open(_path, fs::FileOpenMode::read); - _contentLength = _content.size(); - - if (contentType.length() == 0) - _setContentType(path); - else - _contentType = contentType; - - int filenameStart = path.lastIndexOf('/') + 1; - char buf[26 + path.length() - filenameStart]; - char * filename = (char *)path.c_str() + filenameStart; - - if (download) { - // set filename and force download - snprintf_P(buf, sizeof(buf), PSTR("attachment; filename=\"%s\""), filename); - } else { - // set filename and force rendering - snprintf_P(buf, sizeof(buf), PSTR("inline; filename=\"%s\""), filename); - } - addHeader(F("Content-Disposition"), buf); -} - -AsyncFileResponse::AsyncFileResponse(File content, const String & path, const String & contentType, bool download, AwsTemplateProcessor callback) - : AsyncAbstractResponse(callback) { - _code = 200; - _path = path; - - if (!download && String(content.name()).endsWith(F(".gz")) && !path.endsWith(F(".gz"))) { - addHeader(F("Content-Encoding"), F("gzip")); - _callback = nullptr; // Unable to process gzipped templates - _sendContentLength = true; - _chunked = false; - } - - _content = content; - _contentLength = _content.size(); - - if (contentType.length() == 0) - _setContentType(path); - else - _contentType = contentType; - - int filenameStart = path.lastIndexOf('/') + 1; - char buf[26 + path.length() - filenameStart]; - char * filename = (char *)path.c_str() + filenameStart; - - if (download) { - snprintf_P(buf, sizeof(buf), PSTR("attachment; filename=\"%s\""), filename); - } else { - snprintf_P(buf, sizeof(buf), PSTR("inline; filename=\"%s\""), filename); - } - addHeader(F("Content-Disposition"), buf); -} - -size_t AsyncFileResponse::_fillBuffer(uint8_t * data, size_t len) { - return _content.read(data, len); -} - -/* - * Stream Response - * */ - -AsyncStreamResponse::AsyncStreamResponse(Stream & stream, const String & contentType, size_t len, AwsTemplateProcessor callback) - : AsyncAbstractResponse(callback) { - _code = 200; - _content = &stream; - _contentLength = len; - _contentType = contentType; -} - -size_t AsyncStreamResponse::_fillBuffer(uint8_t * data, size_t len) { - size_t available = _content->available(); - size_t outLen = (available > len) ? len : available; - size_t i; - for (i = 0; i < outLen; i++) - data[i] = _content->read(); - return outLen; -} - -/* - * Callback Response - * */ - -AsyncCallbackResponse::AsyncCallbackResponse(const String & contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback) - : AsyncAbstractResponse(templateCallback) { - _code = 200; - _content = callback; - _contentLength = len; - if (!len) - _sendContentLength = false; - _contentType = contentType; - _filledLength = 0; -} - -size_t AsyncCallbackResponse::_fillBuffer(uint8_t * data, size_t len) { - size_t ret = _content(data, len, _filledLength); - if (ret != RESPONSE_TRY_AGAIN) { - _filledLength += ret; - } - return ret; -} - -/* - * Chunked Response - * */ - -AsyncChunkedResponse::AsyncChunkedResponse(const String & contentType, AwsResponseFiller callback, AwsTemplateProcessor processorCallback) - : AsyncAbstractResponse(processorCallback) { - _code = 200; - _content = callback; - _contentLength = 0; - _contentType = contentType; - _sendContentLength = false; - _chunked = true; - _filledLength = 0; -} - -size_t AsyncChunkedResponse::_fillBuffer(uint8_t * data, size_t len) { - size_t ret = _content(data, len, _filledLength); - if (ret != RESPONSE_TRY_AGAIN) { - _filledLength += ret; - } - return ret; -} - -/* - * Progmem Response - * */ - -AsyncProgmemResponse::AsyncProgmemResponse(int code, const String & contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback) - : AsyncAbstractResponse(callback) { - _code = code; - _content = content; - _contentType = contentType; - _contentLength = len; - _readLength = 0; -} - -size_t AsyncProgmemResponse::_fillBuffer(uint8_t * data, size_t len) { - size_t left = _contentLength - _readLength; - if (left > len) { - memcpy_P(data, _content + _readLength, len); - _readLength += len; - return len; - } - memcpy_P(data, _content + _readLength, left); - _readLength += left; - return left; -} - - -/* - * Response Stream (You can print/write/printf to it, up to the contentLen bytes) - * */ - -AsyncResponseStream::AsyncResponseStream(const String & contentType, size_t bufferSize) { - _code = 200; - _contentLength = 0; - _contentType = contentType; - _content = new cbuf(bufferSize); -} - -AsyncResponseStream::~AsyncResponseStream() { - delete _content; -} - -size_t AsyncResponseStream::_fillBuffer(uint8_t * buf, size_t maxLen) { - return _content->read((char *)buf, maxLen); -} - -size_t AsyncResponseStream::write(const uint8_t * data, size_t len) { - if (_started()) - return 0; - - if (len > _content->room()) { - size_t needed = len - _content->room(); - _content->resizeAdd(needed); - } - size_t written = _content->write((const char *)data, len); - _contentLength += written; - return written; -} - -size_t AsyncResponseStream::write(uint8_t data) { - return write(&data, 1); -} diff --git a/lib/ESPAsyncWebServer/WebServer.cpp b/lib/ESPAsyncWebServer/WebServer.cpp deleted file mode 100644 index 62e85b23a..000000000 --- a/lib/ESPAsyncWebServer/WebServer.cpp +++ /dev/null @@ -1,198 +0,0 @@ -/* - Asynchronous WebServer library for Espressif MCUs - - Copyright (c) 2016 Hristo Gochkov. All rights reserved. - This file is part of the esp8266 core for Arduino environment. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ -#include "ESPAsyncWebServer.h" -#include "WebHandlerImpl.h" - -bool ON_STA_FILTER(AsyncWebServerRequest *request) { - return WiFi.localIP() == request->client()->localIP(); -} - -bool ON_AP_FILTER(AsyncWebServerRequest *request) { - return WiFi.localIP() != request->client()->localIP(); -} - -#ifndef HAVE_FS_FILE_OPEN_MODE -const char *fs::FileOpenMode::read = "r"; -const char *fs::FileOpenMode::write = "w"; -const char *fs::FileOpenMode::append = "a"; -#endif - -AsyncWebServer::AsyncWebServer(uint16_t port) - : _server(port) - , _rewrites(LinkedList([](AsyncWebRewrite* r){ delete r; })) - , _handlers(LinkedList([](AsyncWebHandler* h){ delete h; })) -{ - _catchAllHandler = new AsyncCallbackWebHandler(); - if(_catchAllHandler == NULL) - return; - _server.onClient([](void *s, AsyncClient* c){ - if(c == NULL) - return; - c->setRxTimeout(3); - AsyncWebServerRequest *r = new AsyncWebServerRequest((AsyncWebServer*)s, c); - if(r == NULL){ - c->close(true); - c->free(); - delete c; - } - }, this); -} - -AsyncWebServer::~AsyncWebServer(){ - reset(); - end(); - if(_catchAllHandler) delete _catchAllHandler; -} - -AsyncWebRewrite& AsyncWebServer::addRewrite(AsyncWebRewrite* rewrite){ - _rewrites.add(rewrite); - return *rewrite; -} - -bool AsyncWebServer::removeRewrite(AsyncWebRewrite *rewrite){ - return _rewrites.remove(rewrite); -} - -AsyncWebRewrite& AsyncWebServer::rewrite(const char* from, const char* to){ - return addRewrite(new AsyncWebRewrite(from, to)); -} - -AsyncWebHandler& AsyncWebServer::addHandler(AsyncWebHandler* handler){ - _handlers.add(handler); - return *handler; -} - -bool AsyncWebServer::removeHandler(AsyncWebHandler *handler){ - return _handlers.remove(handler); -} - -void AsyncWebServer::begin(){ - _server.setNoDelay(true); - _server.begin(); -} - -void AsyncWebServer::end(){ - _server.end(); -} - -#if ASYNC_TCP_SSL_ENABLED -void AsyncWebServer::onSslFileRequest(AcSSlFileHandler cb, void* arg){ - _server.onSslFileRequest(cb, arg); -} - -void AsyncWebServer::beginSecure(const char *cert, const char *key, const char *password){ - _server.beginSecure(cert, key, password); -} -#endif - -void AsyncWebServer::_handleDisconnect(AsyncWebServerRequest *request){ - delete request; -} - -void AsyncWebServer::_rewriteRequest(AsyncWebServerRequest *request){ - for(const auto& r: _rewrites){ - if (r->match(request)){ - request->_url = r->toUrl(); - request->_addGetParams(r->params()); - } - } -} - -void AsyncWebServer::_attachHandler(AsyncWebServerRequest *request){ - for(const auto& h: _handlers){ - if (h->filter(request) && h->canHandle(request)){ - request->setHandler(h); - return; - } - } - - request->addInterestingHeader(F("ANY")); - request->setHandler(_catchAllHandler); -} - - -AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody){ - AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); - handler->setUri(uri); - handler->setMethod(method); - handler->onRequest(onRequest); - handler->onUpload(onUpload); - handler->onBody(onBody); - addHandler(handler); - return *handler; -} - -AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload){ - AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); - handler->setUri(uri); - handler->setMethod(method); - handler->onRequest(onRequest); - handler->onUpload(onUpload); - addHandler(handler); - return *handler; -} - -AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest){ - AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); - handler->setUri(uri); - handler->setMethod(method); - handler->onRequest(onRequest); - addHandler(handler); - return *handler; -} - -AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, ArRequestHandlerFunction onRequest){ - AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); - handler->setUri(uri); - handler->onRequest(onRequest); - addHandler(handler); - return *handler; -} - -AsyncStaticWebHandler& AsyncWebServer::serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_control){ - AsyncStaticWebHandler* handler = new AsyncStaticWebHandler(uri, fs, path, cache_control); - addHandler(handler); - return *handler; -} - -void AsyncWebServer::onNotFound(ArRequestHandlerFunction fn){ - _catchAllHandler->onRequest(fn); -} - -void AsyncWebServer::onFileUpload(ArUploadHandlerFunction fn){ - _catchAllHandler->onUpload(fn); -} - -void AsyncWebServer::onRequestBody(ArBodyHandlerFunction fn){ - _catchAllHandler->onBody(fn); -} - -void AsyncWebServer::reset(){ - _rewrites.free(); - _handlers.free(); - - if (_catchAllHandler != NULL){ - _catchAllHandler->onRequest(NULL); - _catchAllHandler->onUpload(NULL); - _catchAllHandler->onBody(NULL); - } -} - diff --git a/lib/OneWire/OneWire.h b/lib/OneWire/OneWire.h index 6777af4df..233ea0993 100644 --- a/lib/OneWire/OneWire.h +++ b/lib/OneWire/OneWire.h @@ -8,10 +8,10 @@ #endif #if ARDUINO >= 100 -#include "Arduino.h" // for delayMicroseconds, digitalPinToBitMask, etc +#include "Arduino.h" // for delayMicroseconds, digitalPinToBitMask, etc #else -#include "WProgram.h" // for delayMicroseconds -#include "pins_arduino.h" // for digitalPinToBitMask, etc +#include "WProgram.h" // for delayMicroseconds +#include "pins_arduino.h" // for digitalPinToBitMask, etc #endif // You can exclude certain features from OneWire. In theory, this @@ -53,62 +53,62 @@ #define FALSE 0 #endif #ifndef TRUE -#define TRUE 1 +#define TRUE 1 #endif // Platform specific I/O definitions #if defined(__AVR__) -#define PIN_TO_BASEREG(pin) (portInputRegister(digitalPinToPort(pin))) -#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) +#define PIN_TO_BASEREG(pin) (portInputRegister(digitalPinToPort(pin))) +#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) #define IO_REG_TYPE uint8_t #define IO_REG_BASE_ATTR asm("r30") #define IO_REG_MASK_ATTR -#define DIRECT_READ(base, mask) (((*(base)) & (mask)) ? 1 : 0) -#define DIRECT_MODE_INPUT(base, mask) ((*((base)+1)) &= ~(mask)) -#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+1)) |= (mask)) -#define DIRECT_WRITE_LOW(base, mask) ((*((base)+2)) &= ~(mask)) -#define DIRECT_WRITE_HIGH(base, mask) ((*((base)+2)) |= (mask)) +#define DIRECT_READ(base, mask) (((*(base)) & (mask)) ? 1 : 0) +#define DIRECT_MODE_INPUT(base, mask) ((*((base) + 1)) &= ~(mask)) +#define DIRECT_MODE_OUTPUT(base, mask) ((*((base) + 1)) |= (mask)) +#define DIRECT_WRITE_LOW(base, mask) ((*((base) + 2)) &= ~(mask)) +#define DIRECT_WRITE_HIGH(base, mask) ((*((base) + 2)) |= (mask)) #elif defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MK66FX1M0__) || defined(__MK64FX512__) -#define PIN_TO_BASEREG(pin) (portOutputRegister(pin)) -#define PIN_TO_BITMASK(pin) (1) +#define PIN_TO_BASEREG(pin) (portOutputRegister(pin)) +#define PIN_TO_BITMASK(pin) (1) #define IO_REG_TYPE uint8_t #define IO_REG_BASE_ATTR -#define IO_REG_MASK_ATTR __attribute__ ((unused)) -#define DIRECT_READ(base, mask) (*((base)+512)) -#define DIRECT_MODE_INPUT(base, mask) (*((base)+640) = 0) -#define DIRECT_MODE_OUTPUT(base, mask) (*((base)+640) = 1) -#define DIRECT_WRITE_LOW(base, mask) (*((base)+256) = 1) -#define DIRECT_WRITE_HIGH(base, mask) (*((base)+128) = 1) +#define IO_REG_MASK_ATTR __attribute__((unused)) +#define DIRECT_READ(base, mask) (*((base) + 512)) +#define DIRECT_MODE_INPUT(base, mask) (*((base) + 640) = 0) +#define DIRECT_MODE_OUTPUT(base, mask) (*((base) + 640) = 1) +#define DIRECT_WRITE_LOW(base, mask) (*((base) + 256) = 1) +#define DIRECT_WRITE_HIGH(base, mask) (*((base) + 128) = 1) #elif defined(__MKL26Z64__) -#define PIN_TO_BASEREG(pin) (portOutputRegister(pin)) -#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) +#define PIN_TO_BASEREG(pin) (portOutputRegister(pin)) +#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) #define IO_REG_TYPE uint8_t #define IO_REG_BASE_ATTR #define IO_REG_MASK_ATTR -#define DIRECT_READ(base, mask) ((*((base)+16) & (mask)) ? 1 : 0) -#define DIRECT_MODE_INPUT(base, mask) (*((base)+20) &= ~(mask)) -#define DIRECT_MODE_OUTPUT(base, mask) (*((base)+20) |= (mask)) -#define DIRECT_WRITE_LOW(base, mask) (*((base)+8) = (mask)) -#define DIRECT_WRITE_HIGH(base, mask) (*((base)+4) = (mask)) +#define DIRECT_READ(base, mask) ((*((base) + 16) & (mask)) ? 1 : 0) +#define DIRECT_MODE_INPUT(base, mask) (*((base) + 20) &= ~(mask)) +#define DIRECT_MODE_OUTPUT(base, mask) (*((base) + 20) |= (mask)) +#define DIRECT_WRITE_LOW(base, mask) (*((base) + 8) = (mask)) +#define DIRECT_WRITE_HIGH(base, mask) (*((base) + 4) = (mask)) #elif defined(__SAM3X8E__) || defined(__SAM3A8C__) || defined(__SAM3A4C__) // Arduino 1.5.1 may have a bug in delayMicroseconds() on Arduino Due. // http://arduino.cc/forum/index.php/topic,141030.msg1076268.html#msg1076268 // If you have trouble with OneWire on Arduino Due, please check the // status of delayMicroseconds() before reporting a bug in OneWire! -#define PIN_TO_BASEREG(pin) (&(digitalPinToPort(pin)->PIO_PER)) -#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) +#define PIN_TO_BASEREG(pin) (&(digitalPinToPort(pin)->PIO_PER)) +#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) #define IO_REG_TYPE uint32_t #define IO_REG_BASE_ATTR #define IO_REG_MASK_ATTR -#define DIRECT_READ(base, mask) (((*((base)+15)) & (mask)) ? 1 : 0) -#define DIRECT_MODE_INPUT(base, mask) ((*((base)+5)) = (mask)) -#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+4)) = (mask)) -#define DIRECT_WRITE_LOW(base, mask) ((*((base)+13)) = (mask)) -#define DIRECT_WRITE_HIGH(base, mask) ((*((base)+12)) = (mask)) +#define DIRECT_READ(base, mask) (((*((base) + 15)) & (mask)) ? 1 : 0) +#define DIRECT_MODE_INPUT(base, mask) ((*((base) + 5)) = (mask)) +#define DIRECT_MODE_OUTPUT(base, mask) ((*((base) + 4)) = (mask)) +#define DIRECT_WRITE_LOW(base, mask) ((*((base) + 13)) = (mask)) +#define DIRECT_WRITE_HIGH(base, mask) ((*((base) + 12)) = (mask)) #ifndef PROGMEM #define PROGMEM #endif @@ -117,16 +117,16 @@ #endif #elif defined(__PIC32MX__) -#define PIN_TO_BASEREG(pin) (portModeRegister(digitalPinToPort(pin))) -#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) +#define PIN_TO_BASEREG(pin) (portModeRegister(digitalPinToPort(pin))) +#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) #define IO_REG_TYPE uint32_t #define IO_REG_BASE_ATTR #define IO_REG_MASK_ATTR -#define DIRECT_READ(base, mask) (((*(base+4)) & (mask)) ? 1 : 0) //PORTX + 0x10 -#define DIRECT_MODE_INPUT(base, mask) ((*(base+2)) = (mask)) //TRISXSET + 0x08 -#define DIRECT_MODE_OUTPUT(base, mask) ((*(base+1)) = (mask)) //TRISXCLR + 0x04 -#define DIRECT_WRITE_LOW(base, mask) ((*(base+8+1)) = (mask)) //LATXCLR + 0x24 -#define DIRECT_WRITE_HIGH(base, mask) ((*(base+8+2)) = (mask)) //LATXSET + 0x28 +#define DIRECT_READ(base, mask) (((*(base + 4)) & (mask)) ? 1 : 0) //PORTX + 0x10 +#define DIRECT_MODE_INPUT(base, mask) ((*(base + 2)) = (mask)) //TRISXSET + 0x08 +#define DIRECT_MODE_OUTPUT(base, mask) ((*(base + 1)) = (mask)) //TRISXCLR + 0x04 +#define DIRECT_WRITE_LOW(base, mask) ((*(base + 8 + 1)) = (mask)) //LATXCLR + 0x24 +#define DIRECT_WRITE_HIGH(base, mask) ((*(base + 8 + 2)) = (mask)) //LATXSET + 0x28 #elif defined(ARDUINO_ARCH_ESP8266) // Special note: I depend on the ESP community to maintain these definitions and @@ -134,155 +134,146 @@ // resolve any problems related to ESP chips. Please do not contact me and please // DO NOT CREATE GITHUB ISSUES for ESP support. All ESP questions must be asked // on ESP community forums. -#define PIN_TO_BASEREG(pin) ((volatile uint32_t*) GPO) -#define PIN_TO_BITMASK(pin) (1 << pin) +#define PIN_TO_BASEREG(pin) ((volatile uint32_t *)GPO) +#define PIN_TO_BITMASK(pin) (1 << pin) #define IO_REG_TYPE uint32_t #define IO_REG_BASE_ATTR #define IO_REG_MASK_ATTR -#define DIRECT_READ(base, mask) ((GPI & (mask)) ? 1 : 0) //GPIO_IN_ADDRESS -#define DIRECT_MODE_INPUT(base, mask) (GPE &= ~(mask)) //GPIO_ENABLE_W1TC_ADDRESS -#define DIRECT_MODE_OUTPUT(base, mask) (GPE |= (mask)) //GPIO_ENABLE_W1TS_ADDRESS -#define DIRECT_WRITE_LOW(base, mask) (GPOC = (mask)) //GPIO_OUT_W1TC_ADDRESS -#define DIRECT_WRITE_HIGH(base, mask) (GPOS = (mask)) //GPIO_OUT_W1TS_ADDRESS +#define DIRECT_READ(base, mask) ((GPI & (mask)) ? 1 : 0) //GPIO_IN_ADDRESS +#define DIRECT_MODE_INPUT(base, mask) (GPE &= ~(mask)) //GPIO_ENABLE_W1TC_ADDRESS +#define DIRECT_MODE_OUTPUT(base, mask) (GPE |= (mask)) //GPIO_ENABLE_W1TS_ADDRESS +#define DIRECT_WRITE_LOW(base, mask) (GPOC = (mask)) //GPIO_OUT_W1TC_ADDRESS +#define DIRECT_WRITE_HIGH(base, mask) (GPOS = (mask)) //GPIO_OUT_W1TS_ADDRESS #elif defined(ARDUINO_ARCH_ESP32) #include #if ESP_IDF_VERSION_MAJOR >= 5 #include "soc/gpio_periph.h" #endif // ESP_IDF_VERSION_MAJOR >= 5 -#define PIN_TO_BASEREG(pin) (0) -#define PIN_TO_BITMASK(pin) (pin) +#define PIN_TO_BASEREG(pin) (0) +#define PIN_TO_BITMASK(pin) (pin) #define IO_REG_TYPE uint32_t #define IO_REG_BASE_ATTR #define IO_REG_MASK_ATTR -static inline __attribute__((always_inline)) -IO_REG_TYPE directRead(IO_REG_TYPE pin) -{ +static inline __attribute__((always_inline)) IO_REG_TYPE directRead(IO_REG_TYPE pin) { // return digitalRead(pin); // Works most of the time // return gpio_ll_get_level(&GPIO, pin); // The hal is not public api, don't use in application code //#if CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6 #if SOC_GPIO_PIN_COUNT <= 32 return (GPIO.in.val >> pin) & 0x1; -#else // ESP32 with over 32 gpios - if ( pin < 32 ) +#else // ESP32 with over 32 gpios + if (pin < 32) return (GPIO.in >> pin) & 0x1; else return (GPIO.in1.val >> (pin - 32)) & 0x1; #endif return 0; - } -static inline __attribute__((always_inline)) -void directWriteLow(IO_REG_TYPE pin) -{ +static inline __attribute__((always_inline)) void directWriteLow(IO_REG_TYPE pin) { // digitalWrite(pin, 0); // Works most of the time // gpio_ll_set_level(&GPIO, pin, 0); // The hal is not public api, don't use in application code //#if CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6 #if SOC_GPIO_PIN_COUNT <= 32 GPIO.out_w1tc.val = ((uint32_t)1 << pin); -#else // ESP32 with over 32 gpios - if ( pin < 32 ) +#else // ESP32 with over 32 gpios + if (pin < 32) GPIO.out_w1tc = ((uint32_t)1 << pin); else GPIO.out1_w1tc.val = ((uint32_t)1 << (pin - 32)); #endif } -static inline __attribute__((always_inline)) -void directWriteHigh(IO_REG_TYPE pin) -{ +static inline __attribute__((always_inline)) void directWriteHigh(IO_REG_TYPE pin) { // digitalWrite(pin, 1); // Works most of the time // gpio_ll_set_level(&GPIO, pin, 1); // The hal is not public api, don't use in application code //#if CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6 #if SOC_GPIO_PIN_COUNT <= 32 GPIO.out_w1ts.val = ((uint32_t)1 << pin); -#else // ESP32 with over 32 gpios - if ( pin < 32 ) +#else // ESP32 with over 32 gpios + if (pin < 32) GPIO.out_w1ts = ((uint32_t)1 << pin); else GPIO.out1_w1ts.val = ((uint32_t)1 << (pin - 32)); #endif - } -static inline __attribute__((always_inline)) -void directModeInput(IO_REG_TYPE pin) -{ -// pinMode(pin, INPUT); // Too slow - doesn't work -// gpio_ll_output_disable(&GPIO, pin); // The hal is not public api, don't use in application code - if ( digitalPinIsValid(pin) ) - { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" + +static inline __attribute__((always_inline)) void directModeInput(IO_REG_TYPE pin) { + // pinMode(pin, INPUT); // Too slow - doesn't work + // gpio_ll_output_disable(&GPIO, pin); // The hal is not public api, don't use in application code + + if (digitalPinIsValid(pin)) { // Input //#if CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6 #if SOC_GPIO_PIN_COUNT <= 32 GPIO.enable_w1tc.val = ((uint32_t)1 << (pin)); -#else // ESP32 with over 32 gpios - if ( pin < 32 ) +#else // ESP32 with over 32 gpios + if (pin < 32) GPIO.enable_w1tc = ((uint32_t)1 << pin); else GPIO.enable1_w1tc.val = ((uint32_t)1 << (pin - 32)); #endif } - } -static inline __attribute__((always_inline)) -void directModeOutput(IO_REG_TYPE pin) -{ -// pinMode(pin, OUTPUT); // Too slow - doesn't work -// gpio_ll_output_enable(&GPIO, pin); // The hal is not public api, don't use in application code +static inline __attribute__((always_inline)) void directModeOutput(IO_REG_TYPE pin) { + // pinMode(pin, OUTPUT); // Too slow - doesn't work + // gpio_ll_output_enable(&GPIO, pin); // The hal is not public api, don't use in application code - if ( digitalPinCanOutput(pin) ) - { + if (digitalPinCanOutput(pin)) { // Output //#if CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6 #if SOC_GPIO_PIN_COUNT <= 32 GPIO.enable_w1ts.val = ((uint32_t)1 << (pin)); -#else // ESP32 with over 32 gpios - if ( pin < 32 ) +#else // ESP32 with over 32 gpios + if (pin < 32) GPIO.enable_w1ts = ((uint32_t)1 << pin); else GPIO.enable1_w1ts.val = ((uint32_t)1 << (pin - 32)); #endif } - } -#define DIRECT_READ(base, pin) directRead(pin) -#define DIRECT_WRITE_LOW(base, pin) directWriteLow(pin) -#define DIRECT_WRITE_HIGH(base, pin) directWriteHigh(pin) -#define DIRECT_MODE_INPUT(base, pin) directModeInput(pin) -#define DIRECT_MODE_OUTPUT(base, pin) directModeOutput(pin) +#pragma GCC diagnostic pop + + +#define DIRECT_READ(base, pin) directRead(pin) +#define DIRECT_WRITE_LOW(base, pin) directWriteLow(pin) +#define DIRECT_WRITE_HIGH(base, pin) directWriteHigh(pin) +#define DIRECT_MODE_INPUT(base, pin) directModeInput(pin) +#define DIRECT_MODE_OUTPUT(base, pin) directModeOutput(pin) #elif defined(__SAMD21G18A__) -#define PIN_TO_BASEREG(pin) portModeRegister(digitalPinToPort(pin)) -#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) +#define PIN_TO_BASEREG(pin) portModeRegister(digitalPinToPort(pin)) +#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) #define IO_REG_TYPE uint32_t #define IO_REG_BASE_ATTR #define IO_REG_MASK_ATTR -#define DIRECT_READ(base, mask) (((*((base)+8)) & (mask)) ? 1 : 0) -#define DIRECT_MODE_INPUT(base, mask) ((*((base)+1)) = (mask)) -#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+2)) = (mask)) -#define DIRECT_WRITE_LOW(base, mask) ((*((base)+5)) = (mask)) -#define DIRECT_WRITE_HIGH(base, mask) ((*((base)+6)) = (mask)) +#define DIRECT_READ(base, mask) (((*((base) + 8)) & (mask)) ? 1 : 0) +#define DIRECT_MODE_INPUT(base, mask) ((*((base) + 1)) = (mask)) +#define DIRECT_MODE_OUTPUT(base, mask) ((*((base) + 2)) = (mask)) +#define DIRECT_WRITE_LOW(base, mask) ((*((base) + 5)) = (mask)) +#define DIRECT_WRITE_HIGH(base, mask) ((*((base) + 6)) = (mask)) #elif defined(RBL_NRF51822) -#define PIN_TO_BASEREG(pin) (0) -#define PIN_TO_BITMASK(pin) (pin) +#define PIN_TO_BASEREG(pin) (0) +#define PIN_TO_BITMASK(pin) (pin) #define IO_REG_TYPE uint32_t #define IO_REG_BASE_ATTR #define IO_REG_MASK_ATTR -#define DIRECT_READ(base, pin) nrf_gpio_pin_read(pin) -#define DIRECT_WRITE_LOW(base, pin) nrf_gpio_pin_clear(pin) -#define DIRECT_WRITE_HIGH(base, pin) nrf_gpio_pin_set(pin) -#define DIRECT_MODE_INPUT(base, pin) nrf_gpio_cfg_input(pin, NRF_GPIO_PIN_NOPULL) -#define DIRECT_MODE_OUTPUT(base, pin) nrf_gpio_cfg_output(pin) +#define DIRECT_READ(base, pin) nrf_gpio_pin_read(pin) +#define DIRECT_WRITE_LOW(base, pin) nrf_gpio_pin_clear(pin) +#define DIRECT_WRITE_HIGH(base, pin) nrf_gpio_pin_set(pin) +#define DIRECT_MODE_INPUT(base, pin) nrf_gpio_cfg_input(pin, NRF_GPIO_PIN_NOPULL) +#define DIRECT_MODE_OUTPUT(base, pin) nrf_gpio_cfg_output(pin) #elif defined(__arc__) /* Arduino101/Genuino101 specifics */ @@ -290,24 +281,22 @@ void directModeOutput(IO_REG_TYPE pin) #include "portable.h" #include "avr/pgmspace.h" -#define GPIO_ID(pin) (g_APinDescription[pin].ulGPIOId) -#define GPIO_TYPE(pin) (g_APinDescription[pin].ulGPIOType) -#define GPIO_BASE(pin) (g_APinDescription[pin].ulGPIOBase) -#define DIR_OFFSET_SS 0x01 -#define DIR_OFFSET_SOC 0x04 -#define EXT_PORT_OFFSET_SS 0x0A -#define EXT_PORT_OFFSET_SOC 0x50 +#define GPIO_ID(pin) (g_APinDescription[pin].ulGPIOId) +#define GPIO_TYPE(pin) (g_APinDescription[pin].ulGPIOType) +#define GPIO_BASE(pin) (g_APinDescription[pin].ulGPIOBase) +#define DIR_OFFSET_SS 0x01 +#define DIR_OFFSET_SOC 0x04 +#define EXT_PORT_OFFSET_SS 0x0A +#define EXT_PORT_OFFSET_SOC 0x50 /* GPIO registers base address */ -#define PIN_TO_BASEREG(pin) ((volatile uint32_t *)g_APinDescription[pin].ulGPIOBase) -#define PIN_TO_BITMASK(pin) pin -#define IO_REG_TYPE uint32_t +#define PIN_TO_BASEREG(pin) ((volatile uint32_t *)g_APinDescription[pin].ulGPIOBase) +#define PIN_TO_BITMASK(pin) pin +#define IO_REG_TYPE uint32_t #define IO_REG_BASE_ATTR #define IO_REG_MASK_ATTR -static inline __attribute__((always_inline)) -IO_REG_TYPE directRead(volatile IO_REG_TYPE *base, IO_REG_TYPE pin) -{ +static inline __attribute__((always_inline)) IO_REG_TYPE directRead(volatile IO_REG_TYPE * base, IO_REG_TYPE pin) { IO_REG_TYPE ret; if (SS_GPIO == GPIO_TYPE(pin)) { ret = READ_ARC_REG(((IO_REG_TYPE)base + EXT_PORT_OFFSET_SS)); @@ -317,31 +306,23 @@ IO_REG_TYPE directRead(volatile IO_REG_TYPE *base, IO_REG_TYPE pin) return ((ret >> GPIO_ID(pin)) & 0x01); } -static inline __attribute__((always_inline)) -void directModeInput(volatile IO_REG_TYPE *base, IO_REG_TYPE pin) -{ +static inline __attribute__((always_inline)) void directModeInput(volatile IO_REG_TYPE * base, IO_REG_TYPE pin) { if (SS_GPIO == GPIO_TYPE(pin)) { - WRITE_ARC_REG(READ_ARC_REG((((IO_REG_TYPE)base) + DIR_OFFSET_SS)) & ~(0x01 << GPIO_ID(pin)), - ((IO_REG_TYPE)(base) + DIR_OFFSET_SS)); + WRITE_ARC_REG(READ_ARC_REG((((IO_REG_TYPE)base) + DIR_OFFSET_SS)) & ~(0x01 << GPIO_ID(pin)), ((IO_REG_TYPE)(base) + DIR_OFFSET_SS)); } else { MMIO_REG_VAL_FROM_BASE((IO_REG_TYPE)base, DIR_OFFSET_SOC) &= ~(0x01 << GPIO_ID(pin)); } } -static inline __attribute__((always_inline)) -void directModeOutput(volatile IO_REG_TYPE *base, IO_REG_TYPE pin) -{ +static inline __attribute__((always_inline)) void directModeOutput(volatile IO_REG_TYPE * base, IO_REG_TYPE pin) { if (SS_GPIO == GPIO_TYPE(pin)) { - WRITE_ARC_REG(READ_ARC_REG(((IO_REG_TYPE)(base) + DIR_OFFSET_SS)) | (0x01 << GPIO_ID(pin)), - ((IO_REG_TYPE)(base) + DIR_OFFSET_SS)); + WRITE_ARC_REG(READ_ARC_REG(((IO_REG_TYPE)(base) + DIR_OFFSET_SS)) | (0x01 << GPIO_ID(pin)), ((IO_REG_TYPE)(base) + DIR_OFFSET_SS)); } else { MMIO_REG_VAL_FROM_BASE((IO_REG_TYPE)base, DIR_OFFSET_SOC) |= (0x01 << GPIO_ID(pin)); } } -static inline __attribute__((always_inline)) -void directWriteLow(volatile IO_REG_TYPE *base, IO_REG_TYPE pin) -{ +static inline __attribute__((always_inline)) void directWriteLow(volatile IO_REG_TYPE * base, IO_REG_TYPE pin) { if (SS_GPIO == GPIO_TYPE(pin)) { WRITE_ARC_REG(READ_ARC_REG(base) & ~(0x01 << GPIO_ID(pin)), base); } else { @@ -349,9 +330,7 @@ void directWriteLow(volatile IO_REG_TYPE *base, IO_REG_TYPE pin) } } -static inline __attribute__((always_inline)) -void directWriteHigh(volatile IO_REG_TYPE *base, IO_REG_TYPE pin) -{ +static inline __attribute__((always_inline)) void directWriteHigh(volatile IO_REG_TYPE * base, IO_REG_TYPE pin) { if (SS_GPIO == GPIO_TYPE(pin)) { WRITE_ARC_REG(READ_ARC_REG(base) | (0x01 << GPIO_ID(pin)), base); } else { @@ -359,11 +338,11 @@ void directWriteHigh(volatile IO_REG_TYPE *base, IO_REG_TYPE pin) } } -#define DIRECT_READ(base, pin) directRead(base, pin) -#define DIRECT_MODE_INPUT(base, pin) directModeInput(base, pin) +#define DIRECT_READ(base, pin) directRead(base, pin) +#define DIRECT_MODE_INPUT(base, pin) directModeInput(base, pin) #define DIRECT_MODE_OUTPUT(base, pin) directModeOutput(base, pin) #define DIRECT_WRITE_LOW(base, pin) directWriteLow(base, pin) -#define DIRECT_WRITE_HIGH(base, pin) directWriteHigh(base, pin) +#define DIRECT_WRITE_HIGH(base, pin) directWriteHigh(base, pin) #elif defined(__riscv) @@ -374,89 +353,81 @@ void directWriteHigh(volatile IO_REG_TYPE *base, IO_REG_TYPE pin) * two high speed modes of the highfive1. It * seems to be less reliable in slow mode. */ -#define PIN_TO_BASEREG(pin) (0) -#define PIN_TO_BITMASK(pin) digitalPinToBitMask(pin) +#define PIN_TO_BASEREG(pin) (0) +#define PIN_TO_BITMASK(pin) digitalPinToBitMask(pin) #define IO_REG_TYPE uint32_t #define IO_REG_BASE_ATTR #define IO_REG_MASK_ATTR -static inline __attribute__((always_inline)) -IO_REG_TYPE directRead(IO_REG_TYPE mask) -{ +static inline __attribute__((always_inline)) IO_REG_TYPE directRead(IO_REG_TYPE mask) { return ((GPIO_REG(GPIO_INPUT_VAL) & mask) != 0) ? 1 : 0; } -static inline __attribute__((always_inline)) -void directModeInput(IO_REG_TYPE mask) -{ - GPIO_REG(GPIO_OUTPUT_XOR) &= ~mask; - GPIO_REG(GPIO_IOF_EN) &= ~mask; +static inline __attribute__((always_inline)) void directModeInput(IO_REG_TYPE mask) { + GPIO_REG(GPIO_OUTPUT_XOR) &= ~mask; + GPIO_REG(GPIO_IOF_EN) &= ~mask; - GPIO_REG(GPIO_INPUT_EN) |= mask; + GPIO_REG(GPIO_INPUT_EN) |= mask; GPIO_REG(GPIO_OUTPUT_EN) &= ~mask; } -static inline __attribute__((always_inline)) -void directModeOutput(IO_REG_TYPE mask) -{ - GPIO_REG(GPIO_OUTPUT_XOR) &= ~mask; - GPIO_REG(GPIO_IOF_EN) &= ~mask; +static inline __attribute__((always_inline)) void directModeOutput(IO_REG_TYPE mask) { + GPIO_REG(GPIO_OUTPUT_XOR) &= ~mask; + GPIO_REG(GPIO_IOF_EN) &= ~mask; - GPIO_REG(GPIO_INPUT_EN) &= ~mask; - GPIO_REG(GPIO_OUTPUT_EN) |= mask; + GPIO_REG(GPIO_INPUT_EN) &= ~mask; + GPIO_REG(GPIO_OUTPUT_EN) |= mask; } -static inline __attribute__((always_inline)) -void directWriteLow(IO_REG_TYPE mask) -{ +static inline __attribute__((always_inline)) void directWriteLow(IO_REG_TYPE mask) { GPIO_REG(GPIO_OUTPUT_VAL) &= ~mask; } -static inline __attribute__((always_inline)) -void directWriteHigh(IO_REG_TYPE mask) -{ +static inline __attribute__((always_inline)) void directWriteHigh(IO_REG_TYPE mask) { GPIO_REG(GPIO_OUTPUT_VAL) |= mask; } -#define DIRECT_READ(base, mask) directRead(mask) -#define DIRECT_WRITE_LOW(base, mask) directWriteLow(mask) -#define DIRECT_WRITE_HIGH(base, mask) directWriteHigh(mask) -#define DIRECT_MODE_INPUT(base, mask) directModeInput(mask) -#define DIRECT_MODE_OUTPUT(base, mask) directModeOutput(mask) +#define DIRECT_READ(base, mask) directRead(mask) +#define DIRECT_WRITE_LOW(base, mask) directWriteLow(mask) +#define DIRECT_WRITE_HIGH(base, mask) directWriteHigh(mask) +#define DIRECT_MODE_INPUT(base, mask) directModeInput(mask) +#define DIRECT_MODE_OUTPUT(base, mask) directModeOutput(mask) #else -#define PIN_TO_BASEREG(pin) (0) -#define PIN_TO_BITMASK(pin) (pin) +#define PIN_TO_BASEREG(pin) (0) +#define PIN_TO_BITMASK(pin) (pin) #define IO_REG_TYPE unsigned int #define IO_REG_BASE_ATTR #define IO_REG_MASK_ATTR -#define DIRECT_READ(base, pin) digitalRead(pin) -#define DIRECT_WRITE_LOW(base, pin) digitalWrite(pin, LOW) -#define DIRECT_WRITE_HIGH(base, pin) digitalWrite(pin, HIGH) -#define DIRECT_MODE_INPUT(base, pin) pinMode(pin,INPUT) -#define DIRECT_MODE_OUTPUT(base, pin) pinMode(pin,OUTPUT) +#define DIRECT_READ(base, pin) digitalRead(pin) +#define DIRECT_WRITE_LOW(base, pin) digitalWrite(pin, LOW) +#define DIRECT_WRITE_HIGH(base, pin) digitalWrite(pin, HIGH) +#define DIRECT_MODE_INPUT(base, pin) pinMode(pin, INPUT) +#define DIRECT_MODE_OUTPUT(base, pin) pinMode(pin, OUTPUT) #warning "OneWire. Fallback mode. Using API calls for pinMode,digitalRead and digitalWrite. Operation of this library is not guaranteed on this architecture." #endif -class OneWire -{ +class OneWire { private: - IO_REG_TYPE bitmask; - volatile IO_REG_TYPE *baseReg; + IO_REG_TYPE bitmask; + volatile IO_REG_TYPE * baseReg; #if ONEWIRE_SEARCH // global search state unsigned char ROM_NO[8]; - uint8_t LastDiscrepancy; - uint8_t LastFamilyDiscrepancy; - uint8_t LastDeviceFlag; + uint8_t LastDiscrepancy; + uint8_t LastFamilyDiscrepancy; + uint8_t LastDeviceFlag; #endif public: - OneWire() { } - OneWire(uint8_t pin) { begin(pin); } + OneWire() { + } + OneWire(uint8_t pin) { + begin(pin); + } void begin(uint8_t pin); // OneWire( uint8_t pin); @@ -477,12 +448,12 @@ class OneWire // another read or write. void write(uint8_t v, uint8_t power = 0); - void write_bytes(const uint8_t *buf, uint16_t count, bool power = 0); + void write_bytes(const uint8_t * buf, uint16_t count, bool power = 0); // Read a byte. uint8_t read(void); - void read_bytes(uint8_t *buf, uint16_t count); + void read_bytes(uint8_t * buf, uint16_t count); // Write a bit. The bus is always left powered at the end, see // note in write() about that. @@ -512,13 +483,13 @@ class OneWire // might be a good idea to check the CRC to make sure you didn't // get garbage. The order is deterministic. You will always get // the same devices in the same order. - uint8_t search(uint8_t *newAddr, bool search_mode = true); + uint8_t search(uint8_t * newAddr, bool search_mode = true); #endif #if ONEWIRE_CRC // Compute a Dallas Semiconductor 8 bit CRC, these are used in the // ROM and scratchpad registers. - static uint8_t crc8(const uint8_t *addr, uint8_t len); + static uint8_t crc8(const uint8_t * addr, uint8_t len); #if ONEWIRE_CRC16 // Compute the 1-Wire CRC16 and compare it against the received CRC. @@ -541,7 +512,7 @@ class OneWire // *not* at a 16-bit integer. // @param crc - The crc starting value (optional) // @return True, iff the CRC matches. - static bool check_crc16(const uint8_t* input, uint16_t len, const uint8_t* inverted_crc, uint16_t crc = 0); + static bool check_crc16(const uint8_t * input, uint16_t len, const uint8_t * inverted_crc, uint16_t crc = 0); // Compute a Dallas Semiconductor 16 bit CRC. This is required to check // the integrity of data received from many 1-Wire devices. Note that the @@ -555,7 +526,7 @@ class OneWire // @param len - How many bytes to use. // @param crc - The crc starting value (optional) // @return The CRC16, as defined by Dallas Semiconductor. - static uint16_t crc16(const uint8_t* input, uint16_t len, uint16_t crc = 0); + static uint16_t crc16(const uint8_t * input, uint16_t len, uint16_t crc = 0); #endif #endif }; diff --git a/lib/PsychicHttp/CHANGELOG.md b/lib/PsychicHttp/CHANGELOG.md new file mode 100644 index 000000000..60c2f81f9 --- /dev/null +++ b/lib/PsychicHttp/CHANGELOG.md @@ -0,0 +1,16 @@ +# v1.1 + +* Changed the internal structure to support request handlers on endpoints and generic requests that do not match an endpoint + * websockets, uploads, etc should now create an appropriate handler and attach to an endpoint with the server.on() syntax +* Added PsychicClient to abstract away some of the internals of ESP-IDF sockets + add convenience + * onOpen and onClose callbacks have changed as a result +* Added support for EventSource / SSE +* Added support for multipart file uploads +* changed getParam() to return a PsychicWebParameter in line with ESPAsyncWebserver +* Renamed various classes / files: + * PsychicHttpFileResponse -> PsychicFileResponse + * PsychicHttpServerEndpoint -> PsychicEndpoint + * PsychicHttpServerRequest -> PsychicRequest + * PsychicHttpServerResponse -> PsychicResponse + * PsychicHttpWebsocket.h -> PsychicWebSocket.h + * Websocket => WebSocket \ No newline at end of file diff --git a/lib/AsyncTCP/LICENSE b/lib/PsychicHttp/LICENSE similarity index 99% rename from lib/AsyncTCP/LICENSE rename to lib/PsychicHttp/LICENSE index 65c5ca88a..0a041280b 100644 --- a/lib/AsyncTCP/LICENSE +++ b/lib/PsychicHttp/LICENSE @@ -1,7 +1,7 @@ GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 - Copyright (C) 2007 Free Software Foundation, Inc. + Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. diff --git a/lib/PsychicHttp/library.json b/lib/PsychicHttp/library.json new file mode 100644 index 000000000..222ed8b5c --- /dev/null +++ b/lib/PsychicHttp/library.json @@ -0,0 +1,44 @@ +{ + "name": "PsychicHttp", + "version": "1.0.1", + "description": "Arduino style wrapper around ESP-IDF HTTP library. HTTP server with SSL + websockets. Works on esp32 and probably esp8266", + "keywords": "network,http,https,tcp,ssl,tls,websocket,espasyncwebserver", + "repository": { + "type": "git", + "url": "https://github.com/hoeken/PsychicHttp" + }, + "authors": [ + { + "name": "Zach Hoeken", + "email": "hoeken@gmail.com", + "maintainer": true + } + ], + "license": "LGPL-3.0-or-later", + "examples": [ + { + "name": "platformio", + "base": "examples/platformio", + "files": ["src/main.cpp"] + } + ], + "frameworks": "arduino", + "platforms": "espressif32", + "dependencies": [ + { + "owner": "bblanchon", + "name": "ArduinoJson", + "version": "^6.21.4" + }, + { + "owner": "bblanchon", + "name": "ArduinoTrace", + "version": "^1.2.0" + }, + { + "owner": "plageoj", + "name": "UrlEncode", + "version": "^1.0.1" + } + ] +} diff --git a/lib/PsychicHttp/library.properties b/lib/PsychicHttp/library.properties new file mode 100644 index 000000000..8557da1b2 --- /dev/null +++ b/lib/PsychicHttp/library.properties @@ -0,0 +1,11 @@ +name=PsychicHttp +version=1.0.1 +author=Zach Hoeken +maintainer=Zach Hoeken +sentence=PsychicHttp is a robust webserver that supports http/https + websockets. +paragraph=This library is based on the ESP-IDF HTTP Server library which is asynchronous, does http / https+ssl and supports websockets. +category=Communication +architectures=esp32 +url=https://github.com/hoeken/PsychicHttp +includes=PsychicHttp.h +depends=ArduinoJson,ArduinoTrace,UrlEncode \ No newline at end of file diff --git a/lib/PsychicHttp/src/ChunkPrinter.h b/lib/PsychicHttp/src/ChunkPrinter.h new file mode 100644 index 000000000..f453e43b9 --- /dev/null +++ b/lib/PsychicHttp/src/ChunkPrinter.h @@ -0,0 +1,52 @@ +#ifndef ChunkPrinter_h +#define ChunkPrinter_h + +#include "PsychicCore.h" +#include "PsychicResponse.h" +#include + +class ChunkPrinter : public Print { + private: + PsychicResponse * _response; + uint8_t * _buffer; + size_t _length; + size_t _pos; + + public: + ChunkPrinter(PsychicResponse * response, uint8_t * buffer, size_t len) + : _response(response) + , _buffer(buffer) + , _length(len) + , _pos(0) { + } + + virtual ~ChunkPrinter() { + } + + size_t write(uint8_t c) { + esp_err_t err; + + _buffer[_pos] = c; + _pos++; + + //if we're full, send a chunk + if (_pos == _length) { + _pos = 0; + + err = _response->sendChunk(_buffer, _length); + if (err != ESP_OK) + return 0; + } + + return 1; + } + + virtual void flush() override { + if (_pos) { + _response->sendChunk(_buffer, _pos); + _pos = 0; + } + } +}; + +#endif \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicClient.cpp b/lib/PsychicHttp/src/PsychicClient.cpp new file mode 100644 index 000000000..bbee4e269 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicClient.cpp @@ -0,0 +1,67 @@ +#include "PsychicClient.h" + +PsychicClient::PsychicClient(httpd_handle_t server, int socket) + : _server(server) + , _socket(socket) + , _friend(NULL) + , isNew(false) { +} + +PsychicClient::~PsychicClient() { +} + +httpd_handle_t PsychicClient::server() { + return _server; +} + +int PsychicClient::socket() { + return _socket; +} + +// I'm not sure this is entirely safe to call. I was having issues with race conditions when highly loaded using this. +esp_err_t PsychicClient::close() { + esp_err_t err = httpd_sess_trigger_close(_server, _socket); + //PsychicHttpServer::closeCallback(_server, _socket); // call this immediately so the client is taken off the list. + + return err; +} + +IPAddress PsychicClient::localIP() { + IPAddress address(0, 0, 0, 0); + + char ipstr[INET6_ADDRSTRLEN]; + struct sockaddr_in6 addr; // esp_http_server uses IPv6 addressing + socklen_t addr_size = sizeof(addr); + + if (getsockname(_socket, (struct sockaddr *)&addr, &addr_size) < 0) { + ESP_LOGE(PH_TAG, "Error getting client IP"); + return address; + } + + // Convert to IPv4 string + inet_ntop(AF_INET, &addr.sin6_addr.un.u32_addr[3], ipstr, sizeof(ipstr)); + ESP_LOGI(PH_TAG, "Client Local IP => %s", ipstr); + address.fromString(ipstr); + + return address; +} + +IPAddress PsychicClient::remoteIP() { + IPAddress address(0, 0, 0, 0); + + char ipstr[INET6_ADDRSTRLEN]; + struct sockaddr_in6 addr; // esp_http_server uses IPv6 addressing + socklen_t addr_size = sizeof(addr); + + if (getpeername(_socket, (struct sockaddr *)&addr, &addr_size) < 0) { + ESP_LOGE(PH_TAG, "Error getting client IP"); + return address; + } + + // Convert to IPv4 string + inet_ntop(AF_INET, &addr.sin6_addr.un.u32_addr[3], ipstr, sizeof(ipstr)); + ESP_LOGI(PH_TAG, "Client Remote IP => %s", ipstr); + address.fromString(ipstr); + + return address; +} \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicClient.h b/lib/PsychicHttp/src/PsychicClient.h new file mode 100644 index 000000000..3b7b79e6d --- /dev/null +++ b/lib/PsychicHttp/src/PsychicClient.h @@ -0,0 +1,39 @@ +#ifndef PsychicClient_h +#define PsychicClient_h + +#include "PsychicCore.h" +#include "PsychicHttpServer.h" +#include + +/* +* PsychicClient :: Generic wrapper around the ESP-IDF socket +*/ + +class PsychicClient { + protected: + httpd_handle_t _server; + int _socket; + + public: + PsychicClient(httpd_handle_t server, int socket); + ~PsychicClient(); + + //no idea if this is the right way to do it or not, but lets see. + //pointer to our derived class (eg. PsychicWebSocketConnection) + void * _friend; + + bool isNew = false; + + bool operator==(PsychicClient & rhs) const { + return _socket == rhs.socket(); + } + + httpd_handle_t server(); + int socket(); + esp_err_t close(); + + IPAddress localIP(); + IPAddress remoteIP(); +}; + +#endif \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicCore.h b/lib/PsychicHttp/src/PsychicCore.h new file mode 100644 index 000000000..5cf8232be --- /dev/null +++ b/lib/PsychicHttp/src/PsychicCore.h @@ -0,0 +1,101 @@ +#ifndef PsychicCore_h +#define PsychicCore_h + +#define PH_TAG "psychic" + +//version numbers +#define PSYCHIC_HTTP_VERSION_MAJOR 1 +#define PSYCHIC_HTTP_VERSION_MINOR 1 +#define PSYCHIC_HTTP_VERSION_PATCH 0 + +#ifndef MAX_COOKIE_SIZE +#define MAX_COOKIE_SIZE 512 +#endif + +#ifndef FILE_CHUNK_SIZE +#define FILE_CHUNK_SIZE 8 * 1024 +#endif + +#ifndef MAX_UPLOAD_SIZE +#define MAX_UPLOAD_SIZE (2048 * 1024) // 2MB +#endif + +#ifndef MAX_REQUEST_BODY_SIZE +#define MAX_REQUEST_BODY_SIZE (16 * 1024) //16K +#endif + +#ifdef ARDUINO +#include +#include +#endif + +#include +#include +#include +#include +#include "esp_random.h" +#include "MD5Builder.h" +#include +#include "FS.h" + +enum HTTPAuthMethod { BASIC_AUTH, DIGEST_AUTH }; + +String urlDecode(const char * encoded); + +class PsychicHttpServer; +class PsychicRequest; +class PsychicWebSocketRequest; +class PsychicClient; + +//filter function definition +typedef std::function PsychicRequestFilterFunction; + +//client connect callback +typedef std::function PsychicClientCallback; + + +struct HTTPHeader { + char * field; + char * value; +}; + +class DefaultHeaders { + std::list _headers; + + public: + DefaultHeaders() { + } + + void addHeader(const String & field, const String & value) { + addHeader(field.c_str(), value.c_str()); + } + + void addHeader(const char * field, const char * value) { + HTTPHeader header; + + //these are just going to stick around forever. + header.field = (char *)malloc(strlen(field) + 1); + header.value = (char *)malloc(strlen(value) + 1); + + strlcpy(header.field, field, strlen(field) + 1); + strlcpy(header.value, value, strlen(value) + 1); + + _headers.push_back(header); + } + + const std::list & getHeaders() { + return _headers; + } + + //delete the copy constructor, singleton class + DefaultHeaders(DefaultHeaders const &) = delete; + DefaultHeaders & operator=(DefaultHeaders const &) = delete; + + //single static class interface + static DefaultHeaders & Instance() { + static DefaultHeaders instance; + return instance; + } +}; + +#endif //PsychicCore_h \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicEndpoint.cpp b/lib/PsychicHttp/src/PsychicEndpoint.cpp new file mode 100644 index 000000000..e17ddef13 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicEndpoint.cpp @@ -0,0 +1,82 @@ +#include "PsychicEndpoint.h" + +PsychicEndpoint::PsychicEndpoint() + : _server(NULL) + , _uri("") + , _method(HTTP_GET) + , _handler(NULL) { +} + +PsychicEndpoint::PsychicEndpoint(PsychicHttpServer * server, http_method method, const char * uri) + : _server(server) + , _uri(uri) + , _method(method) + , _handler(NULL) { +} + +PsychicEndpoint * PsychicEndpoint::setHandler(PsychicHandler * handler) { + //clean up old / default handler + if (_handler != NULL) + delete _handler; + + //get our new pointer + _handler = handler; + + //keep a pointer to the server + _handler->_server = _server; + + return this; +} + +PsychicHandler * PsychicEndpoint::handler() { + return _handler; +} + +String PsychicEndpoint::uri() { + return _uri; +} + +esp_err_t PsychicEndpoint::requestCallback(httpd_req_t * req) { +#ifdef ENABLE_ASYNC + if (is_on_async_worker_thread() == false) { + if (submit_async_req(req, PsychicEndpoint::requestCallback) == ESP_OK) { + return ESP_OK; + } else { + httpd_resp_set_status(req, "503 Busy"); + httpd_resp_sendstr(req, "No workers available. Server busy."); + return ESP_OK; + } + } +#endif + + PsychicEndpoint * self = (PsychicEndpoint *)req->user_ctx; + PsychicHandler * handler = self->handler(); + PsychicRequest request(self->_server, req); + + //make sure we have a handler + if (handler != NULL) { + if (handler->filter(&request) && handler->canHandle(&request)) { + //check our credentials + if (handler->needsAuthentication(&request)) + return handler->authenticate(&request); + + //pass it to our handler + return handler->handleRequest(&request); + } + //pass it to our generic handlers + else + return PsychicHttpServer::notFoundHandler(req, HTTPD_500_INTERNAL_SERVER_ERROR); + } else + return request.reply(500, "text/html", "No handler registered."); +} + +PsychicEndpoint * PsychicEndpoint::setFilter(PsychicRequestFilterFunction fn) { + _handler->setFilter(fn); + return this; +} + +PsychicEndpoint * +PsychicEndpoint::setAuthentication(const char * username, const char * password, HTTPAuthMethod method, const char * realm, const char * authFailMsg) { + _handler->setAuthentication(username, password, method, realm, authFailMsg); + return this; +}; \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicEndpoint.h b/lib/PsychicHttp/src/PsychicEndpoint.h new file mode 100644 index 000000000..3df414794 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicEndpoint.h @@ -0,0 +1,36 @@ +#ifndef PsychicEndpoint_h +#define PsychicEndpoint_h + +#include "PsychicCore.h" +#include "PsychicHttpServer.h" + +#ifdef ENABLE_ASYNC +#include "async_worker.h" +#endif + +class PsychicEndpoint { + friend PsychicHttpServer; + + private: + PsychicHttpServer * _server; + String _uri; + http_method _method; + PsychicHandler * _handler; + + public: + PsychicEndpoint(); + PsychicEndpoint(PsychicHttpServer * server, http_method method, const char * uri); + + PsychicEndpoint * setHandler(PsychicHandler * handler); + PsychicHandler * handler(); + + PsychicEndpoint * setFilter(PsychicRequestFilterFunction fn); + PsychicEndpoint * + setAuthentication(const char * username, const char * password, HTTPAuthMethod method = BASIC_AUTH, const char * realm = "", const char * authFailMsg = ""); + + String uri(); + + static esp_err_t requestCallback(httpd_req_t * req); +}; + +#endif // PsychicEndpoint_h \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicEventSource.cpp b/lib/PsychicHttp/src/PsychicEventSource.cpp new file mode 100644 index 000000000..d457e4754 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicEventSource.cpp @@ -0,0 +1,217 @@ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "PsychicEventSource.h" + +/*****************************************/ +// PsychicEventSource - Handler +/*****************************************/ + +PsychicEventSource::PsychicEventSource() + : PsychicHandler() + , _onOpen(NULL) + , _onClose(NULL) { +} + +PsychicEventSource::~PsychicEventSource() { +} + +PsychicEventSourceClient * PsychicEventSource::getClient(int socket) { + PsychicClient * client = PsychicHandler::getClient(socket); + + if (client == NULL) + return NULL; + + return (PsychicEventSourceClient *)client->_friend; +} + +PsychicEventSourceClient * PsychicEventSource::getClient(PsychicClient * client) { + return getClient(client->socket()); +} + +esp_err_t PsychicEventSource::handleRequest(PsychicRequest * request) { + //start our open ended HTTP response + PsychicEventSourceResponse response(request); + esp_err_t err = response.send(); + + //lookup our client + PsychicClient * client = checkForNewClient(request->client()); + if (client->isNew) { + //did we get our last id? + if (request->hasHeader("Last-Event-ID")) { + PsychicEventSourceClient * buddy = getClient(client); + buddy->_lastId = atoi(request->header("Last-Event-ID").c_str()); + } + + //let our handler know. + openCallback(client); + } + + return err; +} + +PsychicEventSource * PsychicEventSource::onOpen(PsychicEventSourceClientCallback fn) { + _onOpen = fn; + return this; +} + +PsychicEventSource * PsychicEventSource::onClose(PsychicEventSourceClientCallback fn) { + _onClose = fn; + return this; +} + +void PsychicEventSource::addClient(PsychicClient * client) { + client->_friend = new PsychicEventSourceClient(client); + PsychicHandler::addClient(client); +} + +void PsychicEventSource::removeClient(PsychicClient * client) { + PsychicHandler::removeClient(client); + delete (PsychicEventSourceClient *)client->_friend; + client->_friend = NULL; +} + +void PsychicEventSource::openCallback(PsychicClient * client) { + PsychicEventSourceClient * buddy = getClient(client); + if (buddy == NULL) { + TRACE(); + return; + } + + if (_onOpen != NULL) + _onOpen(buddy); +} + +void PsychicEventSource::closeCallback(PsychicClient * client) { + PsychicEventSourceClient * buddy = getClient(client); + if (buddy == NULL) { + TRACE(); + return; + } + + if (_onClose != NULL) + _onClose(getClient(buddy)); +} + +void PsychicEventSource::send(const char * message, const char * event, uint32_t id, uint32_t reconnect) { + String ev = generateEventMessage(message, event, id, reconnect); + for (PsychicClient * c : _clients) { + ((PsychicEventSourceClient *)c->_friend)->sendEvent(ev.c_str()); + } +} + +/*****************************************/ +// PsychicEventSourceClient +/*****************************************/ + +PsychicEventSourceClient::PsychicEventSourceClient(PsychicClient * client) + : PsychicClient(client->server(), client->socket()) + , _lastId(0) { +} + +PsychicEventSourceClient::~PsychicEventSourceClient() { +} + +void PsychicEventSourceClient::send(const char * message, const char * event, uint32_t id, uint32_t reconnect) { + String ev = generateEventMessage(message, event, id, reconnect); + sendEvent(ev.c_str()); +} + +void PsychicEventSourceClient::sendEvent(const char * event) { + int result; + do { + result = httpd_socket_send(this->server(), this->socket(), event, strlen(event), 0); + } while (result == HTTPD_SOCK_ERR_TIMEOUT); + + //if (result < 0) + //error log here +} + +/*****************************************/ +// PsychicEventSourceResponse +/*****************************************/ + +PsychicEventSourceResponse::PsychicEventSourceResponse(PsychicRequest * request) + : PsychicResponse(request) { +} + +esp_err_t PsychicEventSourceResponse::send() { + //build our main header + String out = String(); + out.concat("HTTP/1.1 200 OK\r\n"); + out.concat("Content-Type: text/event-stream\r\n"); + out.concat("Cache-Control: no-cache\r\n"); + out.concat("Connection: keep-alive\r\n"); + + //get our global headers out of the way first + for (HTTPHeader header : DefaultHeaders::Instance().getHeaders()) + out.concat(String(header.field) + ": " + String(header.value) + "\r\n"); + + //separator + out.concat("\r\n"); + + int result; + do { + result = httpd_send(_request->request(), out.c_str(), out.length()); + } while (result == HTTPD_SOCK_ERR_TIMEOUT); + + if (result < 0) + ESP_LOGE(PH_TAG, "EventSource send failed with %s", esp_err_to_name(result)); + + if (result > 0) + return ESP_OK; + else + return ESP_ERR_HTTPD_RESP_SEND; +} + +/*****************************************/ +// Event Message Generator +/*****************************************/ + +String generateEventMessage(const char * message, const char * event, uint32_t id, uint32_t reconnect) { + String ev = ""; + + if (reconnect) { + ev += "retry: "; + ev += String(reconnect); + ev += "\r\n"; + } + + if (id) { + ev += "id: "; + ev += String(id); + ev += "\r\n"; + } + + if (event != NULL) { + ev += "event: "; + ev += String(event); + ev += "\r\n"; + } + + if (message != NULL) { + ev += "data: "; + ev += String(message); + ev += "\r\n"; + } + ev += "\r\n"; + + return ev; +} \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicEventSource.h b/lib/PsychicHttp/src/PsychicEventSource.h new file mode 100644 index 000000000..1406e5c30 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicEventSource.h @@ -0,0 +1,84 @@ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#ifndef PsychicEventSource_H_ +#define PsychicEventSource_H_ + +#include "PsychicCore.h" +#include "PsychicClient.h" +#include "PsychicHandler.h" +#include "PsychicResponse.h" + +class PsychicEventSource; +class PsychicEventSourceResponse; +class PsychicEventSourceClient; +class PsychicResponse; + +typedef std::function PsychicEventSourceClientCallback; + +class PsychicEventSourceClient : public PsychicClient { + friend PsychicEventSource; + + protected: + uint32_t _lastId; + + public: + PsychicEventSourceClient(PsychicClient * client); + ~PsychicEventSourceClient(); + + uint32_t lastId() const { + return _lastId; + } + void send(const char * message, const char * event = NULL, uint32_t id = 0, uint32_t reconnect = 0); + void sendEvent(const char * event); +}; + +class PsychicEventSource : public PsychicHandler { + private: + PsychicEventSourceClientCallback _onOpen; + PsychicEventSourceClientCallback _onClose; + + public: + PsychicEventSource(); + ~PsychicEventSource(); + + PsychicEventSourceClient * getClient(int socket) override; + PsychicEventSourceClient * getClient(PsychicClient * client) override; + void addClient(PsychicClient * client) override; + void removeClient(PsychicClient * client) override; + void openCallback(PsychicClient * client) override; + void closeCallback(PsychicClient * client) override; + + PsychicEventSource * onOpen(PsychicEventSourceClientCallback fn); + PsychicEventSource * onClose(PsychicEventSourceClientCallback fn); + + esp_err_t handleRequest(PsychicRequest * request) override final; + + void send(const char * message, const char * event = NULL, uint32_t id = 0, uint32_t reconnect = 0); +}; + +class PsychicEventSourceResponse : public PsychicResponse { + public: + PsychicEventSourceResponse(PsychicRequest * request); + virtual esp_err_t send() override; +}; + +String generateEventMessage(const char * message, const char * event, uint32_t id, uint32_t reconnect); + +#endif /* PsychicEventSource_H_ */ \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicFileResponse.cpp b/lib/PsychicHttp/src/PsychicFileResponse.cpp new file mode 100644 index 000000000..51827cc49 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicFileResponse.cpp @@ -0,0 +1,169 @@ +#include "PsychicFileResponse.h" +#include "PsychicResponse.h" +#include "PsychicRequest.h" + + +PsychicFileResponse::PsychicFileResponse(PsychicRequest * request, FS & fs, const String & path, const String & contentType, bool download) + : PsychicResponse(request) { + //_code = 200; + _path = path; + + if (!download && !fs.exists(_path) && fs.exists(_path + ".gz")) { + _path = _path + ".gz"; + addHeader("Content-Encoding", "gzip"); + _sendContentLength = true; + _chunked = false; + } + + _content = fs.open(_path, "r"); + _contentLength = _content.size(); + + if (contentType == "") + _setContentType(path); + else + _contentType = contentType; + setContentType(_contentType.c_str()); + + int filenameStart = path.lastIndexOf('/') + 1; + char buf[26 + path.length() - filenameStart]; + char * filename = (char *)path.c_str() + filenameStart; + + if (download) { + // set filename and force download + snprintf(buf, sizeof(buf), "attachment; filename=\"%s\"", filename); + } else { + // set filename and force rendering + snprintf(buf, sizeof(buf), "inline; filename=\"%s\"", filename); + } + addHeader("Content-Disposition", buf); +} + +PsychicFileResponse::PsychicFileResponse(PsychicRequest * request, File content, const String & path, const String & contentType, bool download) + : PsychicResponse(request) { + _path = path; + + if (!download && String(content.name()).endsWith(".gz") && !path.endsWith(".gz")) { + addHeader("Content-Encoding", "gzip"); + _sendContentLength = true; + _chunked = false; + } + + _content = content; + _contentLength = _content.size(); + setContentLength(_contentLength); + + if (contentType == "") + _setContentType(path); + else + _contentType = contentType; + setContentType(_contentType.c_str()); + + int filenameStart = path.lastIndexOf('/') + 1; + char buf[26 + path.length() - filenameStart]; + char * filename = (char *)path.c_str() + filenameStart; + + if (download) { + snprintf(buf, sizeof(buf), "attachment; filename=\"%s\"", filename); + } else { + snprintf(buf, sizeof(buf), "inline; filename=\"%s\"", filename); + } + addHeader("Content-Disposition", buf); +} + +PsychicFileResponse::~PsychicFileResponse() { + if (_content) + _content.close(); +} + +void PsychicFileResponse::_setContentType(const String & path) { + if (path.endsWith(".html")) + _contentType = "text/html"; + else if (path.endsWith(".htm")) + _contentType = "text/html"; + else if (path.endsWith(".css")) + _contentType = "text/css"; + else if (path.endsWith(".json")) + _contentType = "application/json"; + else if (path.endsWith(".js")) + _contentType = "application/javascript"; + else if (path.endsWith(".png")) + _contentType = "image/png"; + else if (path.endsWith(".gif")) + _contentType = "image/gif"; + else if (path.endsWith(".jpg")) + _contentType = "image/jpeg"; + else if (path.endsWith(".ico")) + _contentType = "image/x-icon"; + else if (path.endsWith(".svg")) + _contentType = "image/svg+xml"; + else if (path.endsWith(".eot")) + _contentType = "font/eot"; + else if (path.endsWith(".woff")) + _contentType = "font/woff"; + else if (path.endsWith(".woff2")) + _contentType = "font/woff2"; + else if (path.endsWith(".ttf")) + _contentType = "font/ttf"; + else if (path.endsWith(".xml")) + _contentType = "text/xml"; + else if (path.endsWith(".pdf")) + _contentType = "application/pdf"; + else if (path.endsWith(".zip")) + _contentType = "application/zip"; + else if (path.endsWith(".gz")) + _contentType = "application/x-gzip"; + else + _contentType = "text/plain"; +} + +esp_err_t PsychicFileResponse::send() { + esp_err_t err = ESP_OK; + + //just send small files directly + size_t size = getContentLength(); + if (size < FILE_CHUNK_SIZE) { + uint8_t * buffer = (uint8_t *)malloc(size); + int readSize = _content.readBytes((char *)buffer, size); + + this->setContent(buffer, size); + err = PsychicResponse::send(); + + free(buffer); + } else { + /* Retrieve the pointer to scratch buffer for temporary storage */ + char * chunk = (char *)malloc(FILE_CHUNK_SIZE); + if (chunk == NULL) { + /* Respond with 500 Internal Server Error */ + httpd_resp_send_err(this->_request->request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Unable to allocate memory."); + return ESP_FAIL; + } + + this->sendHeaders(); + + size_t chunksize; + do { + /* Read file in chunks into the scratch buffer */ + chunksize = _content.readBytes(chunk, FILE_CHUNK_SIZE); + if (chunksize > 0) { + err = this->sendChunk((uint8_t *)chunk, chunksize); + if (err != ESP_OK) + break; + } + + /* Keep looping till the whole file is sent */ + } while (chunksize != 0); + + //keep track of our memory + free(chunk); + + if (err == ESP_OK) { + ESP_LOGI(PH_TAG, "File sending complete"); + this->finishChunking(); + } + + /* Close file after sending complete */ + _content.close(); + } + + return err; +} diff --git a/lib/PsychicHttp/src/PsychicFileResponse.h b/lib/PsychicHttp/src/PsychicFileResponse.h new file mode 100644 index 000000000..918e81bb7 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicFileResponse.h @@ -0,0 +1,28 @@ +#ifndef PsychicFileResponse_h +#define PsychicFileResponse_h + +#include "PsychicCore.h" +#include "PsychicResponse.h" + +class PsychicRequest; + +class PsychicFileResponse : public PsychicResponse { + using File = fs::File; + using FS = fs::FS; + + private: + File _content; + String _path; + bool _sendContentLength; + bool _chunked; + String _contentType; + void _setContentType(const String & path); + + public: + PsychicFileResponse(PsychicRequest * request, FS & fs, const String & path, const String & contentType = String(), bool download = false); + PsychicFileResponse(PsychicRequest * request, File content, const String & path, const String & contentType = String(), bool download = false); + ~PsychicFileResponse(); + esp_err_t send(); +}; + +#endif // PsychicFileResponse_h \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicHandler.cpp b/lib/PsychicHttp/src/PsychicHandler.cpp new file mode 100644 index 000000000..f02971fbf --- /dev/null +++ b/lib/PsychicHttp/src/PsychicHandler.cpp @@ -0,0 +1,103 @@ +#include "PsychicHandler.h" + +PsychicHandler::PsychicHandler() : + _filter(NULL), + _server(NULL), + _username(""), + _password(""), + _method(DIGEST_AUTH), + _realm(""), + _authFailMsg("") + {} + +PsychicHandler::~PsychicHandler() { + // actual PsychicClient deletion handled by PsychicServer + // for (PsychicClient *client : _clients) + // delete(client); + _clients.clear(); +} + +PsychicHandler* PsychicHandler::setFilter(PsychicRequestFilterFunction fn) { + _filter = fn; + return this; +} + +bool PsychicHandler::filter(PsychicRequest *request){ + return _filter == NULL || _filter(request); +} + +PsychicHandler* PsychicHandler::setAuthentication(const char *username, const char *password, HTTPAuthMethod method, const char *realm, const char *authFailMsg) { + _username = String(username); + _password = String(password); + _method = method; + _realm = String(realm); + _authFailMsg = String(authFailMsg); + return this; +}; + +bool PsychicHandler::needsAuthentication(PsychicRequest *request) { + return (_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str()); +} + +esp_err_t PsychicHandler::authenticate(PsychicRequest *request) { + return request->requestAuthentication(_method, _realm.c_str(), _authFailMsg.c_str()); +} + +PsychicClient * PsychicHandler::checkForNewClient(PsychicClient *client) +{ + PsychicClient *c = PsychicHandler::getClient(client); + if (c == NULL) + { + c = client; + addClient(c); + c->isNew = true; + } + else + c->isNew = false; + + return c; +} + +void PsychicHandler::checkForClosedClient(PsychicClient *client) +{ + if (hasClient(client)) + { + closeCallback(client); + removeClient(client); + } +} + +void PsychicHandler::addClient(PsychicClient *client) { + _clients.push_back(client); +} + +void PsychicHandler::removeClient(PsychicClient *client) { + _clients.remove(client); +} + +PsychicClient * PsychicHandler::getClient(int socket) +{ + //make sure the server has it too. + if (!_server->hasClient(socket)) + return NULL; + + //what about us? + for (PsychicClient *client : _clients) + if (client->socket() == socket) + return client; + + //nothing found. + return NULL; +} + +PsychicClient * PsychicHandler::getClient(PsychicClient *client) { + return PsychicHandler::getClient(client->socket()); +} + +bool PsychicHandler::hasClient(PsychicClient *socket) { + return PsychicHandler::getClient(socket) != NULL; +} + +const std::list& PsychicHandler::getClientList() { + return _clients; +} \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicHandler.h b/lib/PsychicHttp/src/PsychicHandler.h new file mode 100644 index 000000000..071238124 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicHandler.h @@ -0,0 +1,67 @@ +#ifndef PsychicHandler_h +#define PsychicHandler_h + +#include "PsychicCore.h" +#include "PsychicRequest.h" + +class PsychicEndpoint; + +/* +* HANDLER :: Can be attached to any endpoint or as a generic request handler. +*/ + +class PsychicHandler { + friend PsychicEndpoint; + + protected: + PsychicRequestFilterFunction _filter; + PsychicHttpServer * _server; + + String _username; + String _password; + HTTPAuthMethod _method; + String _realm; + String _authFailMsg; + + std::list _clients; + + public: + PsychicHandler(); + ~PsychicHandler(); + + PsychicHandler * setFilter(PsychicRequestFilterFunction fn); + bool filter(PsychicRequest * request); + + PsychicHandler * + setAuthentication(const char * username, const char * password, HTTPAuthMethod method = BASIC_AUTH, const char * realm = "", const char * authFailMsg = ""); + bool needsAuthentication(PsychicRequest * request); + esp_err_t authenticate(PsychicRequest * request); + + virtual bool isWebSocket() { + return false; + }; + + PsychicClient * checkForNewClient(PsychicClient * client); + void checkForClosedClient(PsychicClient * client); + + virtual void addClient(PsychicClient * client); + virtual void removeClient(PsychicClient * client); + virtual PsychicClient * getClient(int socket); + virtual PsychicClient * getClient(PsychicClient * client); + virtual void openCallback(PsychicClient * client){}; + virtual void closeCallback(PsychicClient * client){}; + + bool hasClient(PsychicClient * client); + int count() { + return _clients.size(); + }; + const std::list & getClientList(); + + //derived classes must implement these functions + virtual bool canHandle(PsychicRequest * request) { + return true; + }; + virtual esp_err_t handleRequest(PsychicRequest * request) = 0; +}; + +#endif \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicHttp.h b/lib/PsychicHttp/src/PsychicHttp.h new file mode 100644 index 000000000..dd863162a --- /dev/null +++ b/lib/PsychicHttp/src/PsychicHttp.h @@ -0,0 +1,23 @@ +#ifndef PsychicHttp_h +#define PsychicHttp_h + +//#define ENABLE_ASYNC // This is something added in ESP-IDF 5.1.x where each request can be handled in its own thread + +#include +#include "PsychicHttpServer.h" +#include "PsychicRequest.h" +#include "PsychicResponse.h" +#include "PsychicEndpoint.h" +#include "PsychicHandler.h" +#include "PsychicStaticFileHandler.h" +#include "PsychicFileResponse.h" +#include "PsychicUploadHandler.h" +#include "PsychicWebSocket.h" +#include "PsychicEventSource.h" +#include "PsychicJson.h" + +#ifdef ENABLE_ASYNC +#include "async_worker.h" +#endif + +#endif /* PsychicHttp_h */ \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicHttpServer.cpp b/lib/PsychicHttp/src/PsychicHttpServer.cpp new file mode 100644 index 000000000..c7938ad63 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicHttpServer.cpp @@ -0,0 +1,329 @@ +#include "PsychicHttpServer.h" +#include "PsychicEndpoint.h" +#include "PsychicHandler.h" +#include "PsychicWebHandler.h" +#include "PsychicStaticFileHandler.h" +#include "PsychicWebSocket.h" +#include "WiFi.h" +#include "PsychicJson.h" // added by proddy + +PsychicHttpServer::PsychicHttpServer() + : _onOpen(NULL) + , _onClose(NULL) { + maxRequestBodySize = MAX_REQUEST_BODY_SIZE; + maxUploadSize = MAX_UPLOAD_SIZE; + + defaultEndpoint = new PsychicEndpoint(this, HTTP_GET, ""); + onNotFound(PsychicHttpServer::defaultNotFoundHandler); + + //for a regular server + config = HTTPD_DEFAULT_CONFIG(); + config.open_fn = PsychicHttpServer::openCallback; + config.close_fn = PsychicHttpServer::closeCallback; + config.uri_match_fn = httpd_uri_match_wildcard; + config.global_user_ctx = this; + config.global_user_ctx_free_fn = destroy; + config.max_uri_handlers = 20; + +#ifdef ENABLE_ASYNC + // It is advisable that httpd_config_t->max_open_sockets > MAX_ASYNC_REQUESTS + // Why? This leaves at least one socket still available to handle + // quick synchronous requests. Otherwise, all the sockets will + // get taken by the long async handlers, and your server will no + // longer be responsive. + config.max_open_sockets = ASYNC_WORKER_COUNT + 1; + config.lru_purge_enable = true; +#endif +} + +PsychicHttpServer::~PsychicHttpServer() { + for (auto * client : _clients) + delete (client); + _clients.clear(); + + for (auto * endpoint : _endpoints) + delete (endpoint); + _endpoints.clear(); + + for (auto * handler : _handlers) + delete (handler); + _handlers.clear(); + + delete defaultEndpoint; +} + +void PsychicHttpServer::destroy(void * ctx) { + PsychicHttpServer * temp = (PsychicHttpServer *)ctx; + delete temp; +} + +esp_err_t PsychicHttpServer::listen(uint16_t port) { + this->_use_ssl = false; + this->config.server_port = port; + + return this->_start(); +} + +esp_err_t PsychicHttpServer::_start() { + esp_err_t ret; + +#ifdef ENABLE_ASYNC + // start workers + start_async_req_workers(); +#endif + + //fire it up. + ret = _startServer(); + if (ret != ESP_OK) { + ESP_LOGE(PH_TAG, "Server start failed (%s)", esp_err_to_name(ret)); + return ret; + } + + // Register handler + ret = httpd_register_err_handler(server, HTTPD_404_NOT_FOUND, PsychicHttpServer::notFoundHandler); + if (ret != ESP_OK) + ESP_LOGE(PH_TAG, "Add 404 handler failed (%s)", esp_err_to_name(ret)); + + return ret; +} + +esp_err_t PsychicHttpServer::_startServer() { + return httpd_start(&this->server, &this->config); +} + +void PsychicHttpServer::stop() { + httpd_stop(this->server); +} + +PsychicHandler & PsychicHttpServer::addHandler(PsychicHandler * handler) { + _handlers.push_back(handler); + return *handler; +} + +void PsychicHttpServer::removeHandler(PsychicHandler * handler) { + _handlers.remove(handler); +} + +PsychicEndpoint * PsychicHttpServer::on(const char * uri) { + return on(uri, HTTP_GET); +} + +PsychicEndpoint * PsychicHttpServer::on(const char * uri, PsychicHttpRequestCallback fn) { + return on(uri, HTTP_GET, fn); +} + +PsychicEndpoint * PsychicHttpServer::on(const char * uri, http_method method, PsychicHttpRequestCallback fn) { + //these basic requests need a basic web handler + PsychicWebHandler * handler = new PsychicWebHandler(); + handler->onRequest(fn); + + return on(uri, method, handler); +} + +// added by Proddy +PsychicEndpoint * PsychicHttpServer::on(const char * uri, http_method method, PsychicJsonRequestCallback fn) { + PsychicJsonHandler * handler = new PsychicJsonHandler(fn); + + return on(uri, method, handler); +} + +PsychicEndpoint * PsychicHttpServer::on(const char * uri, http_method method) { + PsychicWebHandler * handler = new PsychicWebHandler(); + + return on(uri, method, handler); +} + +PsychicEndpoint * PsychicHttpServer::on(const char * uri, PsychicHandler * handler) { + return on(uri, HTTP_GET, handler); +} + +PsychicEndpoint * PsychicHttpServer::on(const char * uri, http_method method, PsychicHandler * handler) { + //make our endpoint + PsychicEndpoint * endpoint = new PsychicEndpoint(this, method, uri); + + //set our handler + endpoint->setHandler(handler); + + // URI handler structure + httpd_uri_t my_uri{.uri = uri, .method = method, .handler = PsychicEndpoint::requestCallback, .user_ctx = endpoint, .is_websocket = handler->isWebSocket()}; + + // Register endpoint with ESP-IDF server + esp_err_t ret = httpd_register_uri_handler(this->server, &my_uri); + if (ret != ESP_OK) { + ESP_LOGE(PH_TAG, "Add endpoint %s failed (%s)", uri, esp_err_to_name(ret)); // modified by proddy + } + + //save it for later + _endpoints.push_back(endpoint); + + return endpoint; +} + +void PsychicHttpServer::onNotFound(PsychicHttpRequestCallback fn) { + PsychicWebHandler * handler = new PsychicWebHandler(); + handler->onRequest(fn); + + this->defaultEndpoint->setHandler(handler); +} + +esp_err_t PsychicHttpServer::notFoundHandler(httpd_req_t * req, httpd_err_code_t err) { + PsychicHttpServer * server = (PsychicHttpServer *)httpd_get_global_user_ctx(req->handle); + PsychicRequest request(server, req); + + //loop through our global handlers and see if anyone wants it + for (auto * handler : server->_handlers) { + //are we capable of handling this? + if (handler->filter(&request) && handler->canHandle(&request)) { + //check our credentials + if (handler->needsAuthentication(&request)) + return handler->authenticate(&request); + else + return handler->handleRequest(&request); + } + } + + //nothing found, give it to our defaultEndpoint + PsychicHandler * handler = server->defaultEndpoint->handler(); + if (handler->filter(&request) && handler->canHandle(&request)) + return handler->handleRequest(&request); + + //not sure how we got this far. + return ESP_ERR_HTTPD_INVALID_REQ; +} + +esp_err_t PsychicHttpServer::defaultNotFoundHandler(PsychicRequest * request) { + request->reply(404, "text/html", "That URI does not exist."); + + return ESP_OK; +} + +void PsychicHttpServer::onOpen(PsychicClientCallback handler) { + this->_onOpen = handler; +} + +esp_err_t PsychicHttpServer::openCallback(httpd_handle_t hd, int sockfd) { + ESP_LOGI(PH_TAG, "New client connected %d", sockfd); + + //get our global server reference + PsychicHttpServer * server = (PsychicHttpServer *)httpd_get_global_user_ctx(hd); + + //lookup our client + PsychicClient * client = server->getClient(sockfd); + if (client == NULL) { + client = new PsychicClient(hd, sockfd); + server->addClient(client); + } + + //user callback + if (server->_onOpen != NULL) + server->_onOpen(client); + + return ESP_OK; +} + +void PsychicHttpServer::onClose(PsychicClientCallback handler) { + this->_onClose = handler; +} + +void PsychicHttpServer::closeCallback(httpd_handle_t hd, int sockfd) { + ESP_LOGI(PH_TAG, "Client disconnected %d", sockfd); + + PsychicHttpServer * server = (PsychicHttpServer *)httpd_get_global_user_ctx(hd); + + //lookup our client + PsychicClient * client = server->getClient(sockfd); + if (client != NULL) { + //give our handlers a chance to handle a disconnect first + for (PsychicEndpoint * endpoint : server->_endpoints) { + PsychicHandler * handler = endpoint->handler(); + handler->checkForClosedClient(client); + } + + //do we have a callback attached? + if (server->_onClose != NULL) + server->_onClose(client); + + //remove it from our list + server->removeClient(client); + } else + ESP_LOGE(PH_TAG, "No client record %d", sockfd); + + //finally close it out. + close(sockfd); +} + +PsychicStaticFileHandler * PsychicHttpServer::serveStatic(const char * uri, fs::FS & fs, const char * path, const char * cache_control) { + PsychicStaticFileHandler * handler = new PsychicStaticFileHandler(uri, fs, path, cache_control); + this->addHandler(handler); + + return handler; +} + +void PsychicHttpServer::addClient(PsychicClient * client) { + _clients.push_back(client); +} + +void PsychicHttpServer::removeClient(PsychicClient * client) { + _clients.remove(client); + delete client; +} + +PsychicClient * PsychicHttpServer::getClient(int socket) { + for (PsychicClient * client : _clients) + if (client->socket() == socket) + return client; + + return NULL; +} + +PsychicClient * PsychicHttpServer::getClient(httpd_req_t * req) { + return getClient(httpd_req_to_sockfd(req)); +} + +bool PsychicHttpServer::hasClient(int socket) { + return getClient(socket) != NULL; +} + +const std::list & PsychicHttpServer::getClientList() { + return _clients; +} + +bool ON_STA_FILTER(PsychicRequest * request) { + return WiFi.localIP() == request->client()->localIP(); +} + +bool ON_AP_FILTER(PsychicRequest * request) { + return WiFi.softAPIP() == request->client()->localIP(); +} + +String urlDecode(const char * encoded) { + size_t length = strlen(encoded); + char * decoded = (char *)malloc(length + 1); + if (!decoded) { + return ""; + } + + size_t i, j = 0; + for (i = 0; i < length; ++i) { + if (encoded[i] == '%' && isxdigit(encoded[i + 1]) && isxdigit(encoded[i + 2])) { + // Valid percent-encoded sequence + int hex; + sscanf(encoded + i + 1, "%2x", &hex); + decoded[j++] = (char)hex; + i += 2; // Skip the two hexadecimal characters + } else if (encoded[i] == '+') { + // Convert '+' to space + decoded[j++] = ' '; + } else { + // Copy other characters as they are + decoded[j++] = encoded[i]; + } + } + + decoded[j] = '\0'; // Null-terminate the decoded string + + String output(decoded); + free(decoded); + + return output; +} \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicHttpServer.h b/lib/PsychicHttp/src/PsychicHttpServer.h new file mode 100644 index 000000000..0407ceb19 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicHttpServer.h @@ -0,0 +1,86 @@ +#ifndef PsychicHttpServer_h +#define PsychicHttpServer_h + +#include "PsychicCore.h" +#include "PsychicClient.h" +#include "PsychicHandler.h" +#include // added by proddy + +class PsychicEndpoint; +class PsychicHandler; +class PsychicStaticFileHandler; + +//callback definitions +typedef std::function PsychicHttpRequestCallback; +typedef std::function PsychicJsonRequestCallback; // added by proddy + +class PsychicHttpServer { + protected: + bool _use_ssl = false; + std::list _endpoints; + std::list _handlers; + std::list _clients; + + PsychicClientCallback _onOpen; + PsychicClientCallback _onClose; + + esp_err_t _start(); + virtual esp_err_t _startServer(); + + public: + PsychicHttpServer(); + ~PsychicHttpServer(); + + //esp-idf specific stuff + httpd_handle_t server; + httpd_config_t config; + + //some limits on what we will accept + unsigned long maxUploadSize; + unsigned long maxRequestBodySize; + + PsychicEndpoint * defaultEndpoint; + + static void destroy(void * ctx); + + esp_err_t listen(uint16_t port); + + virtual void stop(); + + PsychicHandler & addHandler(PsychicHandler * handler); + void removeHandler(PsychicHandler * handler); + + void addClient(PsychicClient * client); + void removeClient(PsychicClient * client); + PsychicClient * getClient(int socket); + PsychicClient * getClient(httpd_req_t * req); + bool hasClient(int socket); + int count() { + return _clients.size(); + }; + const std::list & getClientList(); + + PsychicEndpoint * on(const char * uri); + PsychicEndpoint * on(const char * uri, http_method method); + PsychicEndpoint * on(const char * uri, PsychicHttpRequestCallback onRequest); + PsychicEndpoint * on(const char * uri, http_method method, PsychicHttpRequestCallback onRequest); + PsychicEndpoint * on(const char * uri, PsychicHandler * handler); + PsychicEndpoint * on(const char * uri, http_method method, PsychicHandler * handler); + PsychicEndpoint * on(const char * uri, http_method method, PsychicJsonRequestCallback onRequest); // added proddy + + static esp_err_t notFoundHandler(httpd_req_t * req, httpd_err_code_t err); + static esp_err_t defaultNotFoundHandler(PsychicRequest * request); + void onNotFound(PsychicHttpRequestCallback fn); + + void onOpen(PsychicClientCallback handler); + void onClose(PsychicClientCallback handler); + static esp_err_t openCallback(httpd_handle_t hd, int sockfd); + static void closeCallback(httpd_handle_t hd, int sockfd); + + PsychicStaticFileHandler * serveStatic(const char * uri, fs::FS & fs, const char * path, const char * cache_control = NULL); +}; + +bool ON_STA_FILTER(PsychicRequest * request); +bool ON_AP_FILTER(PsychicRequest * request); + +#endif // PsychicHttpServer_h \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicHttpsServer.cpp b/lib/PsychicHttp/src/PsychicHttpsServer.cpp new file mode 100644 index 000000000..489beb6a3 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicHttpsServer.cpp @@ -0,0 +1,48 @@ +#include "PsychicHttpsServer.h" + +PsychicHttpsServer::PsychicHttpsServer() + : PsychicHttpServer() { + //for a SSL server + ssl_config = HTTPD_SSL_CONFIG_DEFAULT(); + ssl_config.httpd.open_fn = PsychicHttpServer::openCallback; + ssl_config.httpd.close_fn = PsychicHttpServer::closeCallback; + ssl_config.httpd.uri_match_fn = httpd_uri_match_wildcard; + ssl_config.httpd.global_user_ctx = this; + ssl_config.httpd.global_user_ctx_free_fn = destroy; + ssl_config.httpd.max_uri_handlers = 20; + + // each SSL connection takes about 45kb of heap + // a barebones sketch with PsychicHttp has ~150kb of heap available + // if we set it higher than 2 and use all the connections, we get lots of memory errors. + // not to mention there is no heap left over for the program itself. + ssl_config.httpd.max_open_sockets = 2; +} + +PsychicHttpsServer::~PsychicHttpsServer() { +} + +esp_err_t PsychicHttpsServer::listen(uint16_t port, const char * cert, const char * private_key) { + this->_use_ssl = true; + + this->ssl_config.port_secure = port; + this->ssl_config.cacert_pem = (uint8_t *)cert; + this->ssl_config.cacert_len = strlen(cert) + 1; + this->ssl_config.prvtkey_pem = (uint8_t *)private_key; + this->ssl_config.prvtkey_len = strlen(private_key) + 1; + + return this->_start(); +} + +esp_err_t PsychicHttpsServer::_startServer() { + if (this->_use_ssl) + return httpd_ssl_start(&this->server, &this->ssl_config); + else + return httpd_start(&this->server, &this->config); +} + +void PsychicHttpsServer::stop() { + if (this->_use_ssl) + httpd_ssl_stop(this->server); + else + httpd_stop(this->server); +} \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicHttpsServer.h b/lib/PsychicHttp/src/PsychicHttpsServer.h new file mode 100644 index 000000000..fa20c81e8 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicHttpsServer.h @@ -0,0 +1,31 @@ +#ifndef PsychicHttpsServer_h +#define PsychicHttpsServer_h + +#include "PsychicCore.h" +#include "PsychicHttpServer.h" +#include + +#if !CONFIG_HTTPD_WS_SUPPORT +#error PsychicHttpsServer cannot be used unless HTTPD_WS_SUPPORT is enabled in esp-http-server component configuration +#endif + +#define PSY_ENABLE_SSL //you can use this define in your code to enable/disable these features + +class PsychicHttpsServer : public PsychicHttpServer { + protected: + bool _use_ssl = false; + + public: + PsychicHttpsServer(); + ~PsychicHttpsServer(); + + httpd_ssl_config_t ssl_config; + + using PsychicHttpServer::listen; //keep the regular version + esp_err_t listen(uint16_t port, const char * cert, const char * private_key); + + virtual esp_err_t _startServer() override final; + virtual void stop() override final; +}; + +#endif // PsychicHttpsServer_h \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicJson.h b/lib/PsychicHttp/src/PsychicJson.h new file mode 100644 index 000000000..5f98a404d --- /dev/null +++ b/lib/PsychicHttp/src/PsychicJson.h @@ -0,0 +1,247 @@ +// PsychicJson.h +/* + Async Response to use with ArduinoJson and AsyncWebServer + Written by Andrew Melvin (SticilFace) with help from me-no-dev and BBlanchon. + Ported to PsychicHttp by Zach Hoeken + +*/ +#ifndef PSYCHIC_JSON_H_ +#define PSYCHIC_JSON_H_ + +#include +#include "PsychicCore.h" +#include "PsychicResponse.h" +#include "ChunkPrinter.h" + +#if ARDUINOJSON_VERSION_MAJOR == 5 +#define ARDUINOJSON_5_COMPATIBILITY +#else +#ifndef DYNAMIC_JSON_DOCUMENT_SIZE +#define DYNAMIC_JSON_DOCUMENT_SIZE 4096 +#endif +#endif + +#ifndef JSON_BUFFER_SIZE +//#define JSON_BUFFER_SIZE 256 +#define JSON_BUFFER_SIZE 4 * 1024 +#endif + +constexpr const char * JSON_MIMETYPE = "application/json"; + +/* + * Json Response + * */ + +class PsychicJsonResponse : public PsychicResponse { + protected: +#ifdef ARDUINOJSON_5_COMPATIBILITY + DynamicJsonBuffer _jsonBuffer; +#else + DynamicJsonDocument _jsonBuffer; +#endif + + JsonVariant _root; + size_t _contentLength; + bool _msgPack; // added by proddy + + public: +#ifdef ARDUINOJSON_5_COMPATIBILITY + PsychicJsonResponse(PsychicRequest * request, bool isArray = false) + : PsychicResponse(request) { + setContentType(JSON_MIMETYPE); + if (isArray) + _root = _jsonBuffer.createArray(); + else + _root = _jsonBuffer.createObject(); + } +#else + PsychicJsonResponse(PsychicRequest * request, bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE, bool msgPack = false) // added by proddy + : PsychicResponse(request) + , _jsonBuffer(maxJsonBufferSize) + , _msgPack(msgPack) // added by proddy + { + setContentType(JSON_MIMETYPE); + if (isArray) + _root = _jsonBuffer.createNestedArray(); + else + _root = _jsonBuffer.createNestedObject(); + } +#endif + + ~PsychicJsonResponse() { + } + + JsonVariant & getRoot() { + return _root; + } + + size_t getLength() { +#ifdef ARDUINOJSON_5_COMPATIBILITY + return _root.measureLength(); +#else + // return measureJson(_root); + return (_msgPack) ? measureMsgPack(_root) : measureJson(_root); // added by proddy +#endif + } + + // size_t _fillBuffer(uint8_t *data, size_t len) + // { + // //TODO: fix + // //ChunkPrint dest(data, _sentLength, len); + + // #ifdef ARDUINOJSON_5_COMPATIBILITY + // _root.printTo(dest); + // #else + // //serializeJson(_root, dest); + // #endif + // return len; + // } + + virtual esp_err_t send() override { + esp_err_t err = ESP_OK; + size_t length = getLength(); + size_t buffer_size; + char * buffer; + + DUMP(length); + + //how big of a buffer do we want? + if (length < JSON_BUFFER_SIZE) + buffer_size = length + 1; + else + buffer_size = JSON_BUFFER_SIZE; + + DUMP(buffer_size); + + buffer = (char *)malloc(buffer_size); + if (buffer == NULL) { + httpd_resp_send_err(this->_request->request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Unable to allocate memory."); + return ESP_FAIL; + } + + //send it in one shot or no? + if (length < JSON_BUFFER_SIZE) { + TRACE(); + +#ifdef ARDUINOJSON_5_COMPATIBILITY + _root.printTo(buffer, buffer_size); +#else + // serializeJson(_root, buffer, buffer_size) + (_msgPack) ? serializeMsgPack(_root, buffer, buffer_size) : serializeJson(_root, buffer, buffer_size); // added by proddy +#endif + + this->setContent((uint8_t *)buffer, length); + this->setContentType(JSON_MIMETYPE); + + err = PsychicResponse::send(); + } else { + //helper class that acts as a stream to print chunked responses + ChunkPrinter dest(this, (uint8_t *)buffer, buffer_size); + + //keep our headers + this->sendHeaders(); + +//these commands write to the ChunkPrinter which does the sending +#ifdef ARDUINOJSON_5_COMPATIBILITY + _root.printTo(dest); +#else + // serializeJson(_root, dest); // added by proddy + (_msgPack) ? serializeMsgPack(_root, dest) : serializeJson(_root, dest); // added by proddy + +#endif + + //send the last bits + dest.flush(); + + //done with our chunked response too + err = this->finishChunking(); + } + + //let the buffer go + free(buffer); + + return err; + } +}; + +// class PrettyPsychicJsonResponse : public PsychicJsonResponse +// { +// public: +// #ifdef ARDUINOJSON_5_COMPATIBILITY +// PrettyPsychicJsonResponse(PsychicRequest *request, bool isArray = false) : PsychicJsonResponse(request, isArray) {} +// #else +// PrettyPsychicJsonResponse( +// PsychicRequest *request, +// bool isArray = false, +// size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE) +// : PsychicJsonResponse{request, isArray, maxJsonBufferSize} {} +// #endif + +// size_t setLength() +// { +// #ifdef ARDUINOJSON_5_COMPATIBILITY +// _contentLength = _root.measurePrettyLength(); +// #else +// _contentLength = measureJsonPretty(_root); +// #endif +// return _contentLength; +// } +// }; + +typedef std::function PsychicJsonRequestCallback; + +class PsychicJsonHandler : public PsychicWebHandler { + protected: + PsychicJsonRequestCallback _onRequest; +#ifndef ARDUINOJSON_5_COMPATIBILITY + const size_t _maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE; +#endif + + public: +#ifdef ARDUINOJSON_5_COMPATIBILITY + PsychicJsonHandler() + : _onRequest(NULL){}; + + PsychicJsonHandler(PsychicJsonRequestCallback onRequest) + : _onRequest(onRequest) { + } +#else + PsychicJsonHandler(size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE) + : _onRequest(NULL) + , _maxJsonBufferSize(maxJsonBufferSize){}; + + PsychicJsonHandler(PsychicJsonRequestCallback onRequest, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE) + : _onRequest(onRequest) + , _maxJsonBufferSize(maxJsonBufferSize) { + } +#endif + + void onRequest(PsychicJsonRequestCallback fn) { + _onRequest = fn; + } + + virtual esp_err_t handleRequest(PsychicRequest * request) override { + //process basic stuff + PsychicWebHandler::handleRequest(request); + + if (_onRequest) { +#ifdef ARDUINOJSON_5_COMPATIBILITY + DynamicJsonBuffer jsonBuffer; + JsonVariant json = jsonBuffer.parse(); + if (!json.success()) + return request->reply(400); +#else + DynamicJsonDocument jsonBuffer(this->_maxJsonBufferSize); + DeserializationError error = deserializeJson(jsonBuffer, request->body()); + if (error) + return request->reply(400); + + JsonVariant json = jsonBuffer.as(); +#endif + + return _onRequest(request, json); + } else + return request->reply(500); + } +}; +#endif \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicRequest.cpp b/lib/PsychicHttp/src/PsychicRequest.cpp new file mode 100644 index 000000000..50a163ca8 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicRequest.cpp @@ -0,0 +1,548 @@ +#include "PsychicRequest.h" +#include "PsychicResponse.h" +#include + +PsychicRequest::PsychicRequest(PsychicHttpServer *server, httpd_req_t *req) : + _server(server), + _req(req), + _method(HTTP_GET), + _query(""), + _body(""), + _tempObject(NULL) +{ + //load up our client. + this->_client = server->getClient(req); + + //handle our session data + if (req->sess_ctx != NULL) + this->_session = (SessionData *)req->sess_ctx; + else + { + this->_session = new SessionData(); + req->sess_ctx = this->_session; + } + + //callback for freeing the session later + req->free_ctx = this->freeSession; + + //load up some data + this->_uri = String(this->_req->uri); +} + +PsychicRequest::~PsychicRequest() +{ + //temorary user object + if (_tempObject != NULL) + free(_tempObject); + + //our web parameters + for (auto *param : _params) + delete(param); + _params.clear(); +} + +void PsychicRequest::freeSession(void *ctx) +{ + if (ctx != NULL) + { + SessionData *session = (SessionData*)ctx; + delete session; + } +} + +PsychicHttpServer * PsychicRequest::server() { + return _server; +} + +httpd_req_t * PsychicRequest::request() { + return _req; +} + +PsychicClient * PsychicRequest::client() { + return _client; +} + +const String PsychicRequest::getFilename() +{ + //parse the content-disposition header + if (this->hasHeader("Content-Disposition")) + { + ContentDisposition cd = this->getContentDisposition(); + if (cd.filename != "") + return cd.filename; + } + + //fall back to passed in query string + PsychicWebParameter *param = getParam("_filename"); + if (param != NULL) + return param->name(); + + //fall back to parsing it from url (useful for wildcard uploads) + String uri = this->uri(); + int filenameStart = uri.lastIndexOf('/') + 1; + String filename = uri.substring(filenameStart); + if (filename != "") + return filename; + + //finally, unknown. + ESP_LOGE(PH_TAG, "Did not get a valid filename from the upload."); + return "unknown.txt"; +} + +const ContentDisposition PsychicRequest::getContentDisposition() +{ + ContentDisposition cd; + String header = this->header("Content-Disposition"); + int start; + int end; + + if (header.indexOf("form-data") == 0) + cd.disposition = FORM_DATA; + else if (header.indexOf("attachment") == 0) + cd.disposition = ATTACHMENT; + else if (header.indexOf("inline") == 0) + cd.disposition = INLINE; + else + cd.disposition = NONE; + + start = header.indexOf("filename="); + if (start) + { + end = header.indexOf('"', start+10); + cd.filename = header.substring(start+10, end-1); + } + + start = header.indexOf("name="); + if (start) + { + end = header.indexOf('"', start+6); + cd.name = header.substring(start+6, end-1); + } + + return cd; +} + +esp_err_t PsychicRequest::loadBody() +{ + esp_err_t err = ESP_OK; + + this->_body = String(); + + //Get header value string length and allocate memory for length + 1, extra byte for null termination + size_t remaining = this->_req->content_len; + char *buf = (char *)malloc(remaining+1); + + //while loop for retries + while (remaining > 0) + { + //read our data from the socket + int received = httpd_req_recv(this->_req, buf, this->_req->content_len); + + //Retry if timeout occurred + if (received == HTTPD_SOCK_ERR_TIMEOUT) + continue; + //bail if we got an error + else if (received == HTTPD_SOCK_ERR_FAIL) + { + err = ESP_FAIL; + break; + } + + //keep track of our + remaining -= received; + } + + //null terminate and make our string + buf[this->_req->content_len] = '\0'; + this->_body = String(buf); + + //keep track of that pesky memory + free(buf); + + return err; +} + +http_method PsychicRequest::method() { + return (http_method)this->_req->method; +} + +const String PsychicRequest::methodStr() { + return String(http_method_str((http_method)this->_req->method)); +} + +const String PsychicRequest::path() { + int index = _uri.indexOf("?"); + if(index == -1) + return _uri; + else + return _uri.substring(0, index); +} + +const String& PsychicRequest::uri() { + return this->_uri; +} + +const String& PsychicRequest::query() { + return this->_query; +} + +// no way to get list of headers yet.... +// int PsychicRequest::headers() +// { +// } + +const String PsychicRequest::header(const char *name) +{ + size_t header_len = httpd_req_get_hdr_value_len(this->_req, name); + + //if we've got one, allocated it and load it + if (header_len) + { + char header[header_len+1]; + httpd_req_get_hdr_value_str(this->_req, name, header, sizeof(header)); + return String(header); + } + else + return ""; +} + +bool PsychicRequest::hasHeader(const char *name) +{ + return httpd_req_get_hdr_value_len(this->_req, name) > 0; +} + +const String PsychicRequest::host() { + return this->header("Host"); +} + +const String PsychicRequest::contentType() { + return header("Content-Type"); +} + +size_t PsychicRequest::contentLength() { + return this->_req->content_len; +} + +const String& PsychicRequest::body() +{ + return this->_body; +} + +bool PsychicRequest::isMultipart() +{ + const String& type = this->contentType(); + + return (this->contentType().indexOf("multipart/form-data") >= 0); +} + +esp_err_t PsychicRequest::redirect(const char *url) +{ + PsychicResponse response(this); + response.setCode(301); + response.addHeader("Location", url); + + return response.send(); +} + +bool PsychicRequest::hasCookie(const char *key) +{ + char cookie[MAX_COOKIE_SIZE]; + size_t cookieSize = MAX_COOKIE_SIZE; + esp_err_t err = httpd_req_get_cookie_val(this->_req, key, cookie, &cookieSize); + + //did we get anything? + if (err == ESP_OK) + return true; + else if (err == ESP_ERR_HTTPD_RESULT_TRUNC) + ESP_LOGE(PH_TAG, "cookie too large (%d bytes).\n", cookieSize); + + return false; +} + +const String PsychicRequest::getCookie(const char *key) +{ + char cookie[MAX_COOKIE_SIZE]; + size_t cookieSize = MAX_COOKIE_SIZE; + esp_err_t err = httpd_req_get_cookie_val(this->_req, key, cookie, &cookieSize); + + //did we get anything? + if (err == ESP_OK) + return String(cookie); + else + return ""; +} + +void PsychicRequest::loadParams() +{ + //did we get a query string? + size_t query_len = httpd_req_get_url_query_len(_req); + if (query_len) + { + char query[query_len+1]; + httpd_req_get_url_query_str(_req, query, sizeof(query)); + _query.concat(query); + + //parse them. + _addParams(_query); + } + + //did we get form data as body? + if (this->method() == HTTP_POST && this->contentType() == "application/x-www-form-urlencoded") + { + _addParams(_body); + } +} + +void PsychicRequest::_addParams(const String& params){ + size_t start = 0; + while (start < params.length()){ + int end = params.indexOf('&', start); + if (end < 0) end = params.length(); + int equal = params.indexOf('=', start); + if (equal < 0 || equal > end) equal = end; + String name = params.substring(start, equal); + String value = equal + 1 < end ? params.substring(equal + 1, end) : String(); + addParam(name, value); + start = end + 1; + } +} + +PsychicWebParameter * PsychicRequest::addParam(const String &name, const String &value, bool decode) +{ + if (decode) + return addParam(new PsychicWebParameter(urlDecode(name.c_str()), urlDecode(value.c_str()))); + else + return addParam(new PsychicWebParameter(name, value)); +} + +PsychicWebParameter * PsychicRequest::addParam(PsychicWebParameter *param) { + _params.push_back(param); + return param; +} + +bool PsychicRequest::hasParam(const char *key) +{ + return getParam(key) != NULL; +} + +PsychicWebParameter * PsychicRequest::getParam(const char *key) +{ + for (auto *param : _params) + if (param->name().equals(key)) + return param; + + return NULL; +} + +bool PsychicRequest::hasSessionKey(const String& key) +{ + return this->_session->find(key) != this->_session->end(); +} + +const String PsychicRequest::getSessionKey(const String& key) +{ + auto it = this->_session->find(key); + if (it != this->_session->end()) + return it->second; + else + return ""; +} + +void PsychicRequest::setSessionKey(const String& key, const String& value) +{ + this->_session->insert(std::pair(key, value)); +} + +static const String md5str(const String &in){ + MD5Builder md5 = MD5Builder(); + md5.begin(); + md5.add(in); + md5.calculate(); + return md5.toString(); +} + +bool PsychicRequest::authenticate(const char * username, const char * password) +{ + if(hasHeader("Authorization")) + { + String authReq = header("Authorization"); + if(authReq.startsWith("Basic")){ + authReq = authReq.substring(6); + authReq.trim(); + char toencodeLen = strlen(username)+strlen(password)+1; + char *toencode = new char[toencodeLen + 1]; + if(toencode == NULL){ + authReq = ""; + return false; + } + char *encoded = new char[base64_encode_expected_len(toencodeLen)+1]; + if(encoded == NULL){ + authReq = ""; + delete[] toencode; + return false; + } + sprintf(toencode, "%s:%s", username, password); + if(base64_encode_chars(toencode, toencodeLen, encoded) > 0 && authReq.equalsConstantTime(encoded)) { + authReq = ""; + delete[] toencode; + delete[] encoded; + return true; + } + delete[] toencode; + delete[] encoded; + } + else if(authReq.startsWith(F("Digest"))) + { + authReq = authReq.substring(7); + String _username = _extractParam(authReq,F("username=\""),'\"'); + if(!_username.length() || _username != String(username)) { + authReq = ""; + return false; + } + // extracting required parameters for RFC 2069 simpler Digest + String _realm = _extractParam(authReq, F("realm=\""),'\"'); + String _nonce = _extractParam(authReq, F("nonce=\""),'\"'); + String _uri = _extractParam(authReq, F("uri=\""),'\"'); + String _resp = _extractParam(authReq, F("response=\""),'\"'); + String _opaque = _extractParam(authReq, F("opaque=\""),'\"'); + + if((!_realm.length()) || (!_nonce.length()) || (!_uri.length()) || (!_resp.length()) || (!_opaque.length())) { + authReq = ""; + return false; + } + if((_opaque != this->getSessionKey("opaque")) || (_nonce != this->getSessionKey("nonce")) || (_realm != this->getSessionKey("realm"))) + { + // DUMP(_opaque); + // DUMP(this->getSessionKey("opaque")); + // DUMP(_nonce); + // DUMP(this->getSessionKey("nonce")); + // DUMP(_realm); + // DUMP(this->getSessionKey("realm")); + authReq = ""; + return false; + } + // parameters for the RFC 2617 newer Digest + String _nc,_cnonce; + if(authReq.indexOf("qop=auth") != -1 || authReq.indexOf("qop=\"auth\"") != -1) { + _nc = _extractParam(authReq, F("nc="), ','); + _cnonce = _extractParam(authReq, F("cnonce=\""),'\"'); + } + String _H1 = md5str(String(username) + ':' + _realm + ':' + String(password)); + ESP_LOGD(PH_TAG, "Hash of user:realm:pass=%s", _H1); + String _H2 = ""; + if(_method == HTTP_GET){ + _H2 = md5str(String(F("GET:")) + _uri); + }else if(_method == HTTP_POST){ + _H2 = md5str(String(F("POST:")) + _uri); + }else if(_method == HTTP_PUT){ + _H2 = md5str(String(F("PUT:")) + _uri); + }else if(_method == HTTP_DELETE){ + _H2 = md5str(String(F("DELETE:")) + _uri); + }else{ + _H2 = md5str(String(F("GET:")) + _uri); + } + ESP_LOGD(PH_TAG, "Hash of GET:uri=%s", _H2); + String _responsecheck = ""; + if(authReq.indexOf("qop=auth") != -1 || authReq.indexOf("qop=\"auth\"") != -1) { + _responsecheck = md5str(_H1 + ':' + _nonce + ':' + _nc + ':' + _cnonce + F(":auth:") + _H2); + } else { + _responsecheck = md5str(_H1 + ':' + _nonce + ':' + _H2); + } + ESP_LOGD(PH_TAG, "The Proper response=%s", _responsecheck); + if(_resp == _responsecheck){ + authReq = ""; + return true; + } + } + authReq = ""; + } + return false; +} + +const String PsychicRequest::_extractParam(const String& authReq, const String& param, const char delimit) +{ + int _begin = authReq.indexOf(param); + if (_begin == -1) + return ""; + return authReq.substring(_begin+param.length(),authReq.indexOf(delimit,_begin+param.length())); +} + +const String PsychicRequest::_getRandomHexString() +{ + char buffer[33]; // buffer to hold 32 Hex Digit + /0 + int i; + for(i = 0; i < 4; i++) { + sprintf (buffer + (i*8), "%08lx", esp_random()); + } + return String(buffer); +} + +esp_err_t PsychicRequest::requestAuthentication(HTTPAuthMethod mode, const char* realm, const char* authFailMsg) +{ + //what is thy realm, sire? + if(!strcmp(realm, "")) + this->setSessionKey("realm", "Login Required"); + else + this->setSessionKey("realm", realm); + + PsychicResponse response(this); + String authStr; + + //what kind of auth? + if(mode == BASIC_AUTH) + { + authStr = "Basic realm=\"" + this->getSessionKey("realm") + "\""; + response.addHeader("WWW-Authenticate", authStr.c_str()); + } + else + { + //only make new ones if we havent sent them yet + if (this->getSessionKey("nonce").isEmpty()) + this->setSessionKey("nonce", _getRandomHexString()); + if (this->getSessionKey("opaque").isEmpty()) + this->setSessionKey("opaque", _getRandomHexString()); + + authStr = "Digest realm=\"" + this->getSessionKey("realm") + "\", qop=\"auth\", nonce=\"" + this->getSessionKey("nonce") + "\", opaque=\"" + this->getSessionKey("opaque") + "\""; + response.addHeader("WWW-Authenticate", authStr.c_str()); + } + + //DUMP(authStr); + + response.setCode(401); + response.setContentType("text/html"); + response.setContent(authStr.c_str()); + return response.send(); +} + +esp_err_t PsychicRequest::reply(int code) +{ + PsychicResponse response(this); + + response.setCode(code); + response.setContentType("text/plain"); + response.setContent(http_status_reason(code)); + + return response.send(); +} + +esp_err_t PsychicRequest::reply(const char *content) +{ + PsychicResponse response(this); + + response.setCode(200); + response.setContentType("text/html"); + response.setContent(content); + + return response.send(); +} + +esp_err_t PsychicRequest::reply(int code, const char *contentType, const char *content) +{ + PsychicResponse response(this); + + response.setCode(code); + response.setContentType(contentType); + response.setContent(content); + + return response.send(); +} \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicRequest.h b/lib/PsychicHttp/src/PsychicRequest.h new file mode 100644 index 000000000..8ab6340ca --- /dev/null +++ b/lib/PsychicHttp/src/PsychicRequest.h @@ -0,0 +1,97 @@ +#ifndef PsychicRequest_h +#define PsychicRequest_h + +#include "PsychicCore.h" +#include "PsychicHttpServer.h" +#include "PsychicClient.h" +#include "PsychicWebParameter.h" + +typedef std::map SessionData; + +enum Disposition { NONE, INLINE, ATTACHMENT, FORM_DATA}; + +struct ContentDisposition { + Disposition disposition; + String filename; + String name; +}; + +class PsychicRequest { + friend PsychicHttpServer; + + protected: + PsychicHttpServer *_server; + httpd_req_t *_req; + SessionData *_session; + PsychicClient *_client; + + http_method _method; + String _uri; + String _query; + String _body; + + std::list _params; + + void _addParams(const String& params); + void _parseGETParams(); + void _parsePOSTParams(); + + const String _extractParam(const String& authReq, const String& param, const char delimit); + const String _getRandomHexString(); + + public: + PsychicRequest(PsychicHttpServer *server, httpd_req_t *req); + virtual ~PsychicRequest(); + + void *_tempObject; + + PsychicHttpServer * server(); + httpd_req_t * request(); + virtual PsychicClient * client(); + + bool isMultipart(); + esp_err_t loadBody(); + + const String header(const char *name); + bool hasHeader(const char *name); + + static void freeSession(void *ctx); + bool hasSessionKey(const String& key); + const String getSessionKey(const String& key); + void setSessionKey(const String& key, const String& value); + + bool hasCookie(const char * key); + const String getCookie(const char * key); + + http_method method(); // returns the HTTP method used as enum value (eg. HTTP_GET) + const String methodStr(); // returns the HTTP method used as a string (eg. "GET") + const String path(); // returns the request path (eg /page?foo=bar returns "/page") + const String& uri(); // returns the full request uri (eg /page?foo=bar) + const String& query(); // returns the request query data (eg /page?foo=bar returns "foo=bar") + const String host(); // returns the requested host (request to http://psychic.local/foo will return "psychic.local") + const String contentType(); // returns the Content-Type header value + size_t contentLength(); // returns the Content-Length header value + const String& body(); // returns the body of the request + const ContentDisposition getContentDisposition(); + + const String& queryString() { return query(); } //compatability function. same as query() + const String& url() { return uri(); } //compatability function. same as uri() + + void loadParams(); + PsychicWebParameter * addParam(PsychicWebParameter *param); + PsychicWebParameter * addParam(const String &name, const String &value, bool decode = true); + bool hasParam(const char *key); + PsychicWebParameter * getParam(const char *name); + + const String getFilename(); + + bool authenticate(const char * username, const char * password); + esp_err_t requestAuthentication(HTTPAuthMethod mode, const char* realm, const char* authFailMsg); + + esp_err_t redirect(const char *url); + esp_err_t reply(int code); + esp_err_t reply(const char *content); + esp_err_t reply(int code, const char *contentType, const char *content); +}; + +#endif // PsychicRequest_h \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicResponse.cpp b/lib/PsychicHttp/src/PsychicResponse.cpp new file mode 100644 index 000000000..0aa3ae8fc --- /dev/null +++ b/lib/PsychicHttp/src/PsychicResponse.cpp @@ -0,0 +1,138 @@ +#include "PsychicResponse.h" +#include "PsychicRequest.h" +#include + +PsychicResponse::PsychicResponse(PsychicRequest * request) + : _request(request) + , _code(200) + , _status("") + , _contentLength(0) + , _body("") { +} + +PsychicResponse::~PsychicResponse() { + //clean up our header variables. we have to do this since httpd_resp_send doesn't store copies + for (HTTPHeader header : _headers) { + free(header.field); + free(header.value); + } + _headers.clear(); +} + +void PsychicResponse::addHeader(const char * field, const char * value) { + //these get freed during send() + HTTPHeader header; + header.field = (char *)malloc(strlen(field) + 1); + header.value = (char *)malloc(strlen(value) + 1); + + strlcpy(header.field, field, strlen(field) + 1); + strlcpy(header.value, value, strlen(value) + 1); + + _headers.push_back(header); +} + +void PsychicResponse::setCookie(const char * name, const char * value, unsigned long secondsFromNow, const char * extras) { + time_t now = time(nullptr); + + String output; + output = urlEncode(name) + "=" + urlEncode(value); + + //if current time isn't modern, default to using max age + if (now < 1700000000) + output += "; Max-Age=" + String(secondsFromNow); + //otherwise, set an expiration date + else { + time_t expirationTimestamp = now + secondsFromNow; + + // Convert the expiration timestamp to a formatted string for the "expires" attribute + struct tm * tmInfo = gmtime(&expirationTimestamp); + char expires[30]; + strftime(expires, sizeof(expires), "%a, %d %b %Y %H:%M:%S GMT", tmInfo); + output += "; Expires=" + String(expires); + } + + //did we get any extras? + if (strlen(extras)) + output += "; " + String(extras); + + //okay, add it in. + addHeader("Set-Cookie", output.c_str()); +} + +// time_t now = time(nullptr); +// // Set the cookie with the "expires" attribute + +void PsychicResponse::setCode(int code) { + _code = code; +} + +void PsychicResponse::setContentType(const char * contentType) { + httpd_resp_set_type(_request->request(), contentType); +} + +void PsychicResponse::setContent(const char * content) { + _body = content; + setContentLength(strlen(content)); +} + +void PsychicResponse::setContent(const uint8_t * content, size_t len) { + _body = (char *)content; + setContentLength(len); +} + +const char * PsychicResponse::getContent() { + return _body; +} + +size_t PsychicResponse::getContentLength() { + return _contentLength; +} + +esp_err_t PsychicResponse::send() { + //esp-idf makes you set the whole status. + sprintf(_status, "%u %s", _code, http_status_reason(_code)); + httpd_resp_set_status(_request->request(), _status); + + //our headers too + this->sendHeaders(); + + //now send it off + esp_err_t err = httpd_resp_send(_request->request(), getContent(), getContentLength()); + + //did something happen? + if (err != ESP_OK) + ESP_LOGE(PH_TAG, "Send response failed (%s)", esp_err_to_name(err)); + + return err; +} + +void PsychicResponse::sendHeaders() { + //get our global headers out of the way first + for (HTTPHeader header : DefaultHeaders::Instance().getHeaders()) + httpd_resp_set_hdr(_request->request(), header.field, header.value); + + //now do our individual headers + for (HTTPHeader header : _headers) + httpd_resp_set_hdr(this->_request->request(), header.field, header.value); +} + +esp_err_t PsychicResponse::sendChunk(uint8_t * chunk, size_t chunksize) { + /* Send the buffer contents as HTTP response chunk */ + esp_err_t err = httpd_resp_send_chunk(this->_request->request(), (char *)chunk, chunksize); + if (err != ESP_OK) { + ESP_LOGE(PH_TAG, "File sending failed (%s)", esp_err_to_name(err)); + + /* Abort sending file */ + httpd_resp_sendstr_chunk(this->_request->request(), NULL); + + /* Respond with 500 Internal Server Error */ + httpd_resp_send_err(this->_request->request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to send file"); + } + + return err; +} + +esp_err_t PsychicResponse::finishChunking() { + /* Respond with an empty chunk to signal HTTP response completion */ + return httpd_resp_send_chunk(this->_request->request(), NULL, 0); +} \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicResponse.h b/lib/PsychicHttp/src/PsychicResponse.h new file mode 100644 index 000000000..9fd11f420 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicResponse.h @@ -0,0 +1,49 @@ +#ifndef PsychicResponse_h +#define PsychicResponse_h + +#include "PsychicCore.h" +#include "time.h" + +class PsychicRequest; + +class PsychicResponse { + protected: + PsychicRequest * _request; + + int _code; + char _status[60]; + std::list _headers; + int64_t _contentLength; + const char * _body; + + public: + PsychicResponse(PsychicRequest * request); + virtual ~PsychicResponse(); + + void setCode(int code); + + void setContentType(const char * contentType); + void setContentLength(int64_t contentLength) { + _contentLength = contentLength; + } + int64_t getContentLength(int64_t contentLength) { + return _contentLength; + } + + void addHeader(const char * field, const char * value); + + void setCookie(const char * key, const char * value, unsigned long max_age = 60 * 60 * 24 * 30, const char * extras = ""); + + void setContent(const char * content); + void setContent(const uint8_t * content, size_t len); + + const char * getContent(); + size_t getContentLength(); + + virtual esp_err_t send(); + void sendHeaders(); + esp_err_t sendChunk(uint8_t * chunk, size_t chunksize); + esp_err_t finishChunking(); +}; + +#endif // PsychicResponse_h \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicStaticFileHander.cpp b/lib/PsychicHttp/src/PsychicStaticFileHander.cpp new file mode 100644 index 000000000..20d6d6b61 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicStaticFileHander.cpp @@ -0,0 +1,195 @@ +#include "PsychicStaticFileHandler.h" + +/*************************************/ +/* PsychicStaticFileHandler */ +/*************************************/ + +PsychicStaticFileHandler::PsychicStaticFileHandler(const char * uri, FS & fs, const char * path, const char * cache_control) + : _fs(fs) + , _uri(uri) + , _path(path) + , _default_file("index.html") + , _cache_control(cache_control) + , _last_modified("") { + // Ensure leading '/' + if (_uri.length() == 0 || _uri[0] != '/') + _uri = "/" + _uri; + if (_path.length() == 0 || _path[0] != '/') + _path = "/" + _path; + + // If path ends with '/' we assume a hint that this is a directory to improve performance. + // However - if it does not end with '/' we, can't assume a file, path can still be a directory. + _isDir = _path[_path.length() - 1] == '/'; + + // Remove the trailing '/' so we can handle default file + // Notice that root will be "" not "/" + if (_uri[_uri.length() - 1] == '/') + _uri = _uri.substring(0, _uri.length() - 1); + if (_path[_path.length() - 1] == '/') + _path = _path.substring(0, _path.length() - 1); + + // Reset stats + _gzipFirst = false; + _gzipStats = 0xF8; +} + +PsychicStaticFileHandler & PsychicStaticFileHandler::setIsDir(bool isDir) { + _isDir = isDir; + return *this; +} + +PsychicStaticFileHandler & PsychicStaticFileHandler::setDefaultFile(const char * filename) { + _default_file = String(filename); + return *this; +} + +PsychicStaticFileHandler & PsychicStaticFileHandler::setCacheControl(const char * cache_control) { + _cache_control = String(cache_control); + return *this; +} + +PsychicStaticFileHandler & PsychicStaticFileHandler::setLastModified(const char * last_modified) { + _last_modified = String(last_modified); + return *this; +} + +PsychicStaticFileHandler & PsychicStaticFileHandler::setLastModified(struct tm * last_modified) { + char result[30]; + strftime(result, 30, "%a, %d %b %Y %H:%M:%S %Z", last_modified); + return setLastModified((const char *)result); +} + +bool PsychicStaticFileHandler::canHandle(PsychicRequest * request) { + if (request->method() != HTTP_GET || !request->uri().startsWith(_uri)) + return false; + + if (_getFile(request)) + return true; + + return false; +} + +bool PsychicStaticFileHandler::_getFile(PsychicRequest * request) { + // Remove the found uri + String path = request->uri().substring(_uri.length()); + + // We can skip the file check and look for default if request is to the root of a directory or that request path ends with '/' + bool canSkipFileCheck = (_isDir && path.length() == 0) || (path.length() && path[path.length() - 1] == '/'); + + path = _path + path; + + // Do we have a file or .gz file + if (!canSkipFileCheck && _fileExists(path)) + return true; + + // Can't handle if not default file + if (_default_file.length() == 0) + return false; + + // Try to add default file, ensure there is a trailing '/' ot the path. + if (path.length() == 0 || path[path.length() - 1] != '/') + path += "/"; + path += _default_file; + + return _fileExists(path); +} + +#define FILE_IS_REAL(f) (f == true && !f.isDirectory()) + +bool PsychicStaticFileHandler::_fileExists(const String & path) { + bool fileFound = false; + bool gzipFound = false; + + String gzip = path + ".gz"; + + if (_gzipFirst) { + _file = _fs.open(gzip, "r"); + gzipFound = FILE_IS_REAL(_file); + if (!gzipFound) { + _file = _fs.open(path, "r"); + fileFound = FILE_IS_REAL(_file); + } + } else { + _file = _fs.open(path, "r"); + fileFound = FILE_IS_REAL(_file); + if (!fileFound) { + _file = _fs.open(gzip, "r"); + gzipFound = FILE_IS_REAL(_file); + } + } + + bool found = fileFound || gzipFound; + + if (found) { + _filename = path; + + // Calculate gzip statistic + _gzipStats = (_gzipStats << 1) + (gzipFound ? 1 : 0); + if (_gzipStats == 0x00) + _gzipFirst = false; // All files are not gzip + else if (_gzipStats == 0xFF) + _gzipFirst = true; // All files are gzip + else + _gzipFirst = _countBits(_gzipStats) > 4; // IF we have more gzip files - try gzip first + } + + return found; +} + +uint8_t PsychicStaticFileHandler::_countBits(const uint8_t value) const { + uint8_t w = value; + uint8_t n; + for (n = 0; w != 0; n++) + w &= w - 1; + return n; +} + +esp_err_t PsychicStaticFileHandler::handleRequest(PsychicRequest * request) { + if (_file == true) { + DUMP(_filename); + + //is it not modified? + String etag = String(_file.size()); + if (_last_modified.length() && _last_modified == request->header("If-Modified-Since")) { + DUMP("Last Modified Hit"); + TRACE(); + _file.close(); + request->reply(304); // Not modified + } + //does our Etag match? + else if (_cache_control.length() && request->hasHeader("If-None-Match") && request->header("If-None-Match").equals(etag)) { + DUMP("Etag Hit"); + DUMP(etag); + DUMP(_cache_control); + + _file.close(); + + PsychicResponse response(request); + response.addHeader("Cache-Control", _cache_control.c_str()); + response.addHeader("ETag", etag.c_str()); + response.setCode(304); + response.send(); + } + //nope, send them the full file. + else { + DUMP("No cache hit"); + DUMP(_last_modified); + DUMP(_cache_control); + + PsychicFileResponse response(request, _fs, _filename); + + if (_last_modified.length()) + response.addHeader("Last-Modified", _last_modified.c_str()); + if (_cache_control.length()) { + response.addHeader("Cache-Control", _cache_control.c_str()); + response.addHeader("ETag", etag.c_str()); + } + + return response.send(); + } + } else { + return request->reply(404); + } + + return ESP_OK; +} \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicStaticFileHandler.h b/lib/PsychicHttp/src/PsychicStaticFileHandler.h new file mode 100644 index 000000000..126b9beb7 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicStaticFileHandler.h @@ -0,0 +1,44 @@ +#ifndef PsychicStaticFileHandler_h +#define PsychicStaticFileHandler_h + +#include "PsychicCore.h" +#include "PsychicWebHandler.h" +#include "PsychicRequest.h" +#include "PsychicResponse.h" +#include "PsychicFileResponse.h" + +class PsychicStaticFileHandler : public PsychicWebHandler { + using File = fs::File; + using FS = fs::FS; + + private: + bool _getFile(PsychicRequest * request); + bool _fileExists(const String & path); + uint8_t _countBits(const uint8_t value) const; + + protected: + FS _fs; + File _file; + String _filename; + String _uri; + String _path; + String _default_file; + String _cache_control; + String _last_modified; + bool _isDir; + bool _gzipFirst; + uint8_t _gzipStats; + + public: + PsychicStaticFileHandler(const char * uri, FS & fs, const char * path, const char * cache_control); + bool canHandle(PsychicRequest * request) override; + esp_err_t handleRequest(PsychicRequest * request) override; + PsychicStaticFileHandler & setIsDir(bool isDir); + PsychicStaticFileHandler & setDefaultFile(const char * filename); + PsychicStaticFileHandler & setCacheControl(const char * cache_control); + PsychicStaticFileHandler & setLastModified(const char * last_modified); + PsychicStaticFileHandler & setLastModified(struct tm * last_modified); + //PsychicStaticFileHandler& setTemplateProcessor(AwsTemplateProcessor newCallback) {_callback = newCallback; return *this;} +}; + +#endif /* PsychicHttp_h */ \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicUploadHandler.cpp b/lib/PsychicHttp/src/PsychicUploadHandler.cpp new file mode 100644 index 000000000..7c8d07173 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicUploadHandler.cpp @@ -0,0 +1,395 @@ +#include "PsychicUploadHandler.h" + +PsychicUploadHandler::PsychicUploadHandler() : + PsychicWebHandler() + , _temp() + , _parsedLength(0) + , _multiParseState(EXPECT_BOUNDARY) + , _boundaryPosition(0) + , _itemStartIndex(0) + , _itemSize(0) + , _itemName() + , _itemFilename() + , _itemType() + , _itemValue() + , _itemBuffer(0) + , _itemBufferIndex(0) + , _itemIsFile(false) + {} +PsychicUploadHandler::~PsychicUploadHandler() {} + +bool PsychicUploadHandler::canHandle(PsychicRequest *request) { + return true; +} + +esp_err_t PsychicUploadHandler::handleRequest(PsychicRequest *request) +{ + esp_err_t err = ESP_OK; + + //save it for later (multipart) + _request = request; + + /* File cannot be larger than a limit */ + if (request->contentLength() > request->server()->maxUploadSize) + { + ESP_LOGE(PH_TAG, "File too large : %d bytes", request->contentLength()); + + /* Respond with 400 Bad Request */ + char error[50]; + sprintf(error, "File size must be less than %u bytes!", request->server()->maxUploadSize); + httpd_resp_send_err(request->request(), HTTPD_400_BAD_REQUEST, error); + + /* Return failure to close underlying connection else the incoming file content will keep the socket busy */ + return ESP_FAIL; + } + + //we might want to access some of these params + request->loadParams(); + + //TODO: support for the 100 header. not sure if we can do it. + // if (request->header("Expect").equals("100-continue")) + // { + // char response[] = "100 Continue"; + // httpd_socket_send(self->server, httpd_req_to_sockfd(req), response, strlen(response), 0); + // } + + //2 types of upload requests + if (request->isMultipart()) + err = _multipartUploadHandler(request); + else + err = _basicUploadHandler(request); + + //we can also call onRequest for some final processing and response + if (err == ESP_OK) + { + if (_requestCallback != NULL) + err = _requestCallback(request); + else + err = request->reply("Upload Successful."); + } + else + request->reply(500, "text/html", "Error processing upload."); + + return err; +} + +esp_err_t PsychicUploadHandler::_basicUploadHandler(PsychicRequest *request) +{ + esp_err_t err = ESP_OK; + + String filename = request->getFilename(); + + /* Retrieve the pointer to scratch buffer for temporary storage */ + char *buf = (char *)malloc(FILE_CHUNK_SIZE); + int received; + unsigned long index = 0; + + /* Content length of the request gives the size of the file being uploaded */ + int remaining = request->contentLength(); + + while (remaining > 0) + { + #ifdef ENABLE_ASYNC + httpd_sess_update_lru_counter(request->server()->server, request->client()->socket()); + #endif + + ESP_LOGI(PH_TAG, "Remaining size : %d", remaining); + + /* Receive the file part by part into a buffer */ + if ((received = httpd_req_recv(request->request(), buf, min(remaining, FILE_CHUNK_SIZE))) <= 0) + { + /* Retry if timeout occurred */ + if (received == HTTPD_SOCK_ERR_TIMEOUT) + continue; + //bail if we got an error + else if (received == HTTPD_SOCK_ERR_FAIL) + { + ESP_LOGE(PH_TAG, "Socket error"); + err = ESP_FAIL; + break; + } + } + + //call our upload callback here. + if (_uploadCallback != NULL) + { + err = _uploadCallback(request, filename, index, (uint8_t *)buf, received, (remaining - received == 0)); + if (err != ESP_OK) + break; + } + else + { + ESP_LOGE(PH_TAG, "No upload callback specified!"); + err = ESP_FAIL; + break; + } + + /* Keep track of remaining size of the file left to be uploaded */ + remaining -= received; + index += received; + } + + //dont forget to free our buffer + free(buf); + + return err; +} + +esp_err_t PsychicUploadHandler::_multipartUploadHandler(PsychicRequest *request) +{ + esp_err_t err = ESP_OK; + + String value = request->header("Content-Type"); + if (value.startsWith("multipart/")){ + _boundary = value.substring(value.indexOf('=')+1); + _boundary.replace("\"",""); + } else { + ESP_LOGE(PH_TAG, "No multipart boundary found."); + return request->reply(400, "text/html", "No multipart boundary found."); + } + + char *buf = (char *)malloc(FILE_CHUNK_SIZE); + int received; + unsigned long index = 0; + + /* Content length of the request gives the size of the file being uploaded */ + int remaining = request->contentLength(); + + while (remaining > 0) + { + #ifdef ENABLE_ASYNC + httpd_sess_update_lru_counter(request->server()->server, request->client()->socket()); + #endif + + ESP_LOGI(PH_TAG, "Remaining size : %d", remaining); + + /* Receive the file part by part into a buffer */ + if ((received = httpd_req_recv(request->request(), buf, min(remaining, FILE_CHUNK_SIZE))) <= 0) + { + /* Retry if timeout occurred */ + if (received == HTTPD_SOCK_ERR_TIMEOUT) + continue; + //bail if we got an error + else if (received == HTTPD_SOCK_ERR_FAIL) + { + ESP_LOGE(PH_TAG, "Socket error"); + err = ESP_FAIL; + break; + } + } + + //parse it 1 byte at a time. + for (int i=0; i 12 && _temp.substring(0, 12).equalsIgnoreCase("Content-Type")){ + _itemType = _temp.substring(14); + _itemIsFile = true; + } else if(_temp.length() > 19 && _temp.substring(0, 19).equalsIgnoreCase("Content-Disposition")){ + _temp = _temp.substring(_temp.indexOf(';') + 2); + while(_temp.indexOf(';') > 0){ + String name = _temp.substring(0, _temp.indexOf('=')); + String nameVal = _temp.substring(_temp.indexOf('=') + 2, _temp.indexOf(';') - 1); + if(name == "name"){ + _itemName = nameVal; + } else if(name == "filename"){ + _itemFilename = nameVal; + _itemIsFile = true; + } + _temp = _temp.substring(_temp.indexOf(';') + 2); + } + String name = _temp.substring(0, _temp.indexOf('=')); + String nameVal = _temp.substring(_temp.indexOf('=') + 2, _temp.length() - 1); + if(name == "name"){ + _itemName = nameVal; + } else if(name == "filename"){ + _itemFilename = nameVal; + _itemIsFile = true; + } + } + _temp = String(); + } else { + _multiParseState = WAIT_FOR_RETURN1; + //value starts from here + _itemSize = 0; + _itemStartIndex = _parsedLength; + _itemValue = String(); + if(_itemIsFile){ + if(_itemBuffer) + free(_itemBuffer); + _itemBuffer = (uint8_t*)malloc(FILE_CHUNK_SIZE); + if(_itemBuffer == NULL){ + ESP_LOGE(PH_TAG, "Multipart: Failed to allocate buffer"); + _multiParseState = PARSE_ERROR; + return; + } + _itemBufferIndex = 0; + } + } + } + } else if(_multiParseState == EXPECT_FEED1){ + if(data != '\n'){ + _multiParseState = WAIT_FOR_RETURN1; + itemWriteByte('\r'); _parseMultipartPostByte(data, last); + } else { + _multiParseState = EXPECT_DASH1; + } + } else if(_multiParseState == EXPECT_DASH1){ + if(data != '-'){ + _multiParseState = WAIT_FOR_RETURN1; + itemWriteByte('\r'); itemWriteByte('\n'); _parseMultipartPostByte(data, last); + } else { + _multiParseState = EXPECT_DASH2; + } + } else if(_multiParseState == EXPECT_DASH2){ + if(data != '-'){ + _multiParseState = WAIT_FOR_RETURN1; + itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); _parseMultipartPostByte(data, last); + } else { + _multiParseState = BOUNDARY_OR_DATA; + _boundaryPosition = 0; + } + } else if(_multiParseState == BOUNDARY_OR_DATA){ + if(_boundaryPosition < _boundary.length() && _boundary.c_str()[_boundaryPosition] != data){ + _multiParseState = WAIT_FOR_RETURN1; + itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); itemWriteByte('-'); + uint8_t i; + for(i=0; i<_boundaryPosition; i++) + itemWriteByte(_boundary.c_str()[i]); + _parseMultipartPostByte(data, last); + } else if(_boundaryPosition == _boundary.length() - 1){ + _multiParseState = DASH3_OR_RETURN2; + if(!_itemIsFile){ + _request->addParam(_itemName, _itemValue); + //_addParam(new AsyncWebParameter(_itemName, _itemValue, true)); + } else { + if(_itemSize){ + if(_uploadCallback) + _uploadCallback(_request, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, true); + _itemBufferIndex = 0; + _request->addParam(new PsychicWebParameter(_itemName, _itemFilename, true, true, _itemSize)); + } + free(_itemBuffer); + _itemBuffer = NULL; + } + + } else { + _boundaryPosition++; + } + } else if(_multiParseState == DASH3_OR_RETURN2){ + if(data == '-' && (_request->contentLength() - _parsedLength - 4) != 0){ + ESP_LOGE(PH_TAG, "ERROR: The parser got to the end of the POST but is expecting more bytes!"); + _multiParseState = PARSE_ERROR; + return; + } + if(data == '\r'){ + _multiParseState = EXPECT_FEED2; + } else if(data == '-' && _request->contentLength() == (_parsedLength + 4)){ + _multiParseState = PARSING_FINISHED; + } else { + _multiParseState = WAIT_FOR_RETURN1; + itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); itemWriteByte('-'); + uint8_t i; for(i=0; i<_boundary.length(); i++) itemWriteByte(_boundary.c_str()[i]); + _parseMultipartPostByte(data, last); + } + } else if(_multiParseState == EXPECT_FEED2){ + if(data == '\n'){ + _multiParseState = PARSE_HEADERS; + _itemIsFile = false; + } else { + _multiParseState = WAIT_FOR_RETURN1; + itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); itemWriteByte('-'); + uint8_t i; for(i=0; i<_boundary.length(); i++) itemWriteByte(_boundary.c_str()[i]); + itemWriteByte('\r'); _parseMultipartPostByte(data, last); + } + } +} \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicUploadHandler.h b/lib/PsychicHttp/src/PsychicUploadHandler.h new file mode 100644 index 000000000..df8497334 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicUploadHandler.h @@ -0,0 +1,68 @@ +#ifndef PsychicUploadHandler_h +#define PsychicUploadHandler_h + +#include "PsychicCore.h" +#include "PsychicHttpServer.h" +#include "PsychicRequest.h" +#include "PsychicWebHandler.h" +#include "PsychicWebParameter.h" + +//callback definitions +typedef std::function PsychicUploadCallback; + +/* +* HANDLER :: Can be attached to any endpoint or as a generic request handler. +*/ + +class PsychicUploadHandler : public PsychicWebHandler { + protected: + PsychicUploadCallback _uploadCallback; + + PsychicRequest * _request; + + String _temp; + size_t _parsedLength; + uint8_t _multiParseState; + String _boundary; + uint8_t _boundaryPosition; + size_t _itemStartIndex; + size_t _itemSize; + String _itemName; + String _itemFilename; + String _itemType; + String _itemValue; + uint8_t * _itemBuffer; + size_t _itemBufferIndex; + bool _itemIsFile; + + esp_err_t _basicUploadHandler(PsychicRequest * request); + esp_err_t _multipartUploadHandler(PsychicRequest * request); + + void _handleUploadByte(uint8_t data, bool last); + void _parseMultipartPostByte(uint8_t data, bool last); + + public: + PsychicUploadHandler(); + ~PsychicUploadHandler(); + + bool canHandle(PsychicRequest * request) override; + esp_err_t handleRequest(PsychicRequest * request) override; + + PsychicUploadHandler * onUpload(PsychicUploadCallback fn); +}; + +enum { + EXPECT_BOUNDARY, + PARSE_HEADERS, + WAIT_FOR_RETURN1, + EXPECT_FEED1, + EXPECT_DASH1, + EXPECT_DASH2, + BOUNDARY_OR_DATA, + DASH3_OR_RETURN2, + EXPECT_FEED2, + PARSING_FINISHED, + PARSE_ERROR +}; + +#endif // PsychicUploadHandler_h \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicWebHandler.cpp b/lib/PsychicHttp/src/PsychicWebHandler.cpp new file mode 100644 index 000000000..dbe8ee051 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicWebHandler.cpp @@ -0,0 +1,46 @@ +#include "PsychicWebHandler.h" + +PsychicWebHandler::PsychicWebHandler() + : PsychicHandler() + , _requestCallback(NULL) { +} +PsychicWebHandler::~PsychicWebHandler() { +} + +bool PsychicWebHandler::canHandle(PsychicRequest * request) { + return true; +} + +esp_err_t PsychicWebHandler::handleRequest(PsychicRequest * request) { + /* Request body cannot be larger than a limit */ + if (request->contentLength() > request->server()->maxRequestBodySize) { + ESP_LOGE(PH_TAG, "Request body too large : %d bytes", request->contentLength()); + + /* Respond with 400 Bad Request */ + char error[60]; + sprintf(error, "Request body must be less than %u bytes!", request->server()->maxRequestBodySize); + httpd_resp_send_err(request->request(), HTTPD_400_BAD_REQUEST, error); + + /* Return failure to close underlying connection else the incoming file content will keep the socket busy */ + return ESP_FAIL; + } + + //get our body loaded up. + esp_err_t err = request->loadBody(); + if (err != ESP_OK) + return err; + + //load our params in. + request->loadParams(); + + //okay, pass on to our callback. + if (this->_requestCallback != NULL) + err = this->_requestCallback(request); + + return err; +} + +PsychicWebHandler * PsychicWebHandler::onRequest(PsychicHttpRequestCallback fn) { + _requestCallback = fn; + return this; +} \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicWebHandler.h b/lib/PsychicHttp/src/PsychicWebHandler.h new file mode 100644 index 000000000..5562dfd10 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicWebHandler.h @@ -0,0 +1,26 @@ +#ifndef PsychicWebHandler_h +#define PsychicWebHandler_h + +#include "PsychicCore.h" +#include "PsychicHttpServer.h" +#include "PsychicRequest.h" +#include "PsychicHandler.h" + +/* +* HANDLER :: Can be attached to any endpoint or as a generic request handler. +*/ + +class PsychicWebHandler : public PsychicHandler { + protected: + PsychicHttpRequestCallback _requestCallback; + + public: + PsychicWebHandler(); + ~PsychicWebHandler(); + + virtual bool canHandle(PsychicRequest * request) override; + virtual esp_err_t handleRequest(PsychicRequest * request) override; + PsychicWebHandler * onRequest(PsychicHttpRequestCallback fn); +}; + +#endif \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicWebParameter.h b/lib/PsychicHttp/src/PsychicWebParameter.h new file mode 100644 index 000000000..fe6efc7e0 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicWebParameter.h @@ -0,0 +1,41 @@ +#ifndef PsychicWebParameter_h +#define PsychicWebParameter_h + +/* + * PARAMETER :: Chainable object to hold GET/POST and FILE parameters + * */ + +class PsychicWebParameter { + private: + String _name; + String _value; + size_t _size; + bool _isForm; + bool _isFile; + + public: + PsychicWebParameter(const String & name, const String & value, bool form = false, bool file = false, size_t size = 0) + : _name(name) + , _value(value) + , _size(size) + , _isForm(form) + , _isFile(file) { + } + const String & name() const { + return _name; + } + const String & value() const { + return _value; + } + size_t size() const { + return _size; + } + bool isPost() const { + return _isForm; + } + bool isFile() const { + return _isFile; + } +}; + +#endif //PsychicWebParameter_h \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicWebSocket.cpp b/lib/PsychicHttp/src/PsychicWebSocket.cpp new file mode 100644 index 000000000..24c99ca73 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicWebSocket.cpp @@ -0,0 +1,243 @@ +#include "PsychicWebSocket.h" + +/*************************************/ +/* PsychicWebSocketRequest */ +/*************************************/ + +PsychicWebSocketRequest::PsychicWebSocketRequest(PsychicRequest * req) + : PsychicRequest(req->server(), req->request()) + , _client(req->client()) { +} + +PsychicWebSocketRequest::~PsychicWebSocketRequest() { +} + +PsychicWebSocketClient * PsychicWebSocketRequest::client() { + return &_client; +} + +esp_err_t PsychicWebSocketRequest::reply(httpd_ws_frame_t * ws_pkt) { + return httpd_ws_send_frame(this->_req, ws_pkt); +} + +esp_err_t PsychicWebSocketRequest::reply(httpd_ws_type_t op, const void * data, size_t len) { + httpd_ws_frame_t ws_pkt; + memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t)); + + ws_pkt.payload = (uint8_t *)data; + ws_pkt.len = len; + ws_pkt.type = op; + + return this->reply(&ws_pkt); +} + +esp_err_t PsychicWebSocketRequest::reply(const char * buf) { + return this->reply(HTTPD_WS_TYPE_TEXT, buf, strlen(buf)); +} + +/*************************************/ +/* PsychicWebSocketClient */ +/*************************************/ + +PsychicWebSocketClient::PsychicWebSocketClient(PsychicClient * client) + : PsychicClient(client->server(), client->socket()) { +} + +PsychicWebSocketClient::~PsychicWebSocketClient() { +} + +esp_err_t PsychicWebSocketClient::sendMessage(httpd_ws_frame_t * ws_pkt) { + return httpd_ws_send_frame_async(this->server(), this->socket(), ws_pkt); +} + +esp_err_t PsychicWebSocketClient::sendMessage(httpd_ws_type_t op, const void * data, size_t len) { + httpd_ws_frame_t ws_pkt; + memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t)); + + ws_pkt.payload = (uint8_t *)data; + ws_pkt.len = len; + ws_pkt.type = op; + + return this->sendMessage(&ws_pkt); +} + +esp_err_t PsychicWebSocketClient::sendMessage(const char * buf) { + return this->sendMessage(HTTPD_WS_TYPE_TEXT, buf, strlen(buf)); +} + +PsychicWebSocketHandler::PsychicWebSocketHandler() + : PsychicHandler() + , _onOpen(NULL) + , _onFrame(NULL) + , _onClose(NULL) { +} + +PsychicWebSocketHandler::~PsychicWebSocketHandler() { +} + +PsychicWebSocketClient * PsychicWebSocketHandler::getClient(int socket) { + PsychicClient * client = PsychicHandler::getClient(socket); + if (client == NULL) + return NULL; + + if (client->_friend == NULL) { + DUMP(socket); + return NULL; + } + + return (PsychicWebSocketClient *)client->_friend; +} + +PsychicWebSocketClient * PsychicWebSocketHandler::getClient(PsychicClient * client) { + return getClient(client->socket()); +} + +void PsychicWebSocketHandler::addClient(PsychicClient * client) { + client->_friend = new PsychicWebSocketClient(client); + PsychicHandler::addClient(client); +} + +void PsychicWebSocketHandler::removeClient(PsychicClient * client) { + PsychicHandler::removeClient(client); + delete (PsychicWebSocketClient *)client->_friend; + client->_friend = NULL; +} + +void PsychicWebSocketHandler::openCallback(PsychicClient * client) { + PsychicWebSocketClient * buddy = getClient(client); + if (buddy == NULL) { + TRACE(); + return; + } + + if (_onOpen != NULL) + _onOpen(getClient(buddy)); +} + +void PsychicWebSocketHandler::closeCallback(PsychicClient * client) { + PsychicWebSocketClient * buddy = getClient(client); + if (buddy == NULL) { + TRACE(); + return; + } + + if (_onClose != NULL) + _onClose(getClient(buddy)); +} + +bool PsychicWebSocketHandler::isWebSocket() { + return true; +} + +esp_err_t PsychicWebSocketHandler::handleRequest(PsychicRequest * request) { + //lookup our client + PsychicClient * client = checkForNewClient(request->client()); + + // beginning of the ws URI handler and our onConnect hook + if (request->method() == HTTP_GET) { + if (client->isNew) + openCallback(client); + + return ESP_OK; + } + + //prep our request + PsychicWebSocketRequest wsRequest(request); + + //init our memory for storing the packet + httpd_ws_frame_t ws_pkt; + memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t)); + ws_pkt.type = HTTPD_WS_TYPE_TEXT; + uint8_t * buf = NULL; + + /* Set max_len = 0 to get the frame len */ + esp_err_t ret = httpd_ws_recv_frame(wsRequest.request(), &ws_pkt, 0); + if (ret != ESP_OK) { + ESP_LOGE(PH_TAG, "httpd_ws_recv_frame failed to get frame len with %s", esp_err_to_name(ret)); + return ret; + } + + //okay, now try to load the packet + ESP_LOGI(PH_TAG, "frame len is %d", ws_pkt.len); + if (ws_pkt.len) { + /* ws_pkt.len + 1 is for NULL termination as we are expecting a string */ + buf = (uint8_t *)calloc(1, ws_pkt.len + 1); + if (buf == NULL) { + ESP_LOGE(PH_TAG, "Failed to calloc memory for buf"); + return ESP_ERR_NO_MEM; + } + ws_pkt.payload = buf; + /* Set max_len = ws_pkt.len to get the frame payload */ + ret = httpd_ws_recv_frame(wsRequest.request(), &ws_pkt, ws_pkt.len); + if (ret != ESP_OK) { + ESP_LOGE(PH_TAG, "httpd_ws_recv_frame failed with %s", esp_err_to_name(ret)); + free(buf); + return ret; + } + ESP_LOGI(PH_TAG, "Got packet with message: %s", ws_pkt.payload); + } + + // Text messages are our payload. + if (ws_pkt.type == HTTPD_WS_TYPE_TEXT) { + if (this->_onFrame != NULL) + ret = this->_onFrame(&wsRequest, &ws_pkt); + } + + //logging housekeeping + if (ret != ESP_OK) + ESP_LOGE(PH_TAG, "httpd_ws_send_frame failed with %s", esp_err_to_name(ret)); + ESP_LOGI(PH_TAG, + "ws_handler: httpd_handle_t=%p, sockfd=%d, client_info:%d", + request->server(), + httpd_req_to_sockfd(request->request()), + httpd_ws_get_fd_info(request->server(), httpd_req_to_sockfd(request->request()))); + + //dont forget to release our buffer memory + free(buf); + + return ret; +} + +PsychicWebSocketHandler * PsychicWebSocketHandler::onOpen(PsychicWebSocketClientCallback fn) { + _onOpen = fn; + return this; +} + +PsychicWebSocketHandler * PsychicWebSocketHandler::onFrame(PsychicWebSocketFrameCallback fn) { + _onFrame = fn; + return this; +} + +PsychicWebSocketHandler * PsychicWebSocketHandler::onClose(PsychicWebSocketClientCallback fn) { + _onClose = fn; + return this; +} + +void PsychicWebSocketHandler::sendAll(httpd_ws_frame_t * ws_pkt) { + for (PsychicClient * client : _clients) { + ESP_LOGI(PH_TAG, "Active client (fd=%d) -> sending async message", client->socket()); + + if (client->_friend == NULL) { + TRACE(); + return; + } + + if (((PsychicWebSocketClient *)client->_friend)->sendMessage(ws_pkt) != ESP_OK) + break; + } +} + +void PsychicWebSocketHandler::sendAll(httpd_ws_type_t op, const void * data, size_t len) { + httpd_ws_frame_t ws_pkt; + memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t)); + + ws_pkt.payload = (uint8_t *)data; + ws_pkt.len = len; + ws_pkt.type = op; + + this->sendAll(&ws_pkt); +} + +void PsychicWebSocketHandler::sendAll(const char * buf) { + this->sendAll(HTTPD_WS_TYPE_TEXT, buf, strlen(buf)); +} \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicWebSocket.h b/lib/PsychicHttp/src/PsychicWebSocket.h new file mode 100644 index 000000000..bea702197 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicWebSocket.h @@ -0,0 +1,68 @@ +#ifndef PsychicWebSocket_h +#define PsychicWebSocket_h + +#include "PsychicCore.h" +#include "PsychicRequest.h" + +class PsychicWebSocketRequest; +class PsychicWebSocketClient; + +//callback function definitions +typedef std::function PsychicWebSocketClientCallback; +typedef std::function PsychicWebSocketFrameCallback; + +class PsychicWebSocketClient : public PsychicClient { + public: + PsychicWebSocketClient(PsychicClient * client); + ~PsychicWebSocketClient(); + + esp_err_t sendMessage(httpd_ws_frame_t * ws_pkt); + esp_err_t sendMessage(httpd_ws_type_t op, const void * data, size_t len); + esp_err_t sendMessage(const char * buf); +}; + +class PsychicWebSocketRequest : public PsychicRequest { + private: + PsychicWebSocketClient _client; + + public: + PsychicWebSocketRequest(PsychicRequest * req); + virtual ~PsychicWebSocketRequest(); + + PsychicWebSocketClient * client() override; + + esp_err_t reply(httpd_ws_frame_t * ws_pkt); + esp_err_t reply(httpd_ws_type_t op, const void * data, size_t len); + esp_err_t reply(const char * buf); +}; + +class PsychicWebSocketHandler : public PsychicHandler { + protected: + PsychicWebSocketClientCallback _onOpen; + PsychicWebSocketFrameCallback _onFrame; + PsychicWebSocketClientCallback _onClose; + + public: + PsychicWebSocketHandler(); + ~PsychicWebSocketHandler(); + + PsychicWebSocketClient * getClient(int socket) override; + PsychicWebSocketClient * getClient(PsychicClient * client) override; + void addClient(PsychicClient * client) override; + void removeClient(PsychicClient * client) override; + void openCallback(PsychicClient * client) override; + void closeCallback(PsychicClient * client) override; + + bool isWebSocket() override final; + esp_err_t handleRequest(PsychicRequest * request) override; + + PsychicWebSocketHandler * onOpen(PsychicWebSocketClientCallback fn); + PsychicWebSocketHandler * onFrame(PsychicWebSocketFrameCallback fn); + PsychicWebSocketHandler * onClose(PsychicWebSocketClientCallback fn); + + void sendAll(httpd_ws_frame_t * ws_pkt); + void sendAll(httpd_ws_type_t op, const void * data, size_t len); + void sendAll(const char * buf); +}; + +#endif // PsychicWebSocket_h \ No newline at end of file diff --git a/lib/PsychicHttp/src/async_worker.cpp b/lib/PsychicHttp/src/async_worker.cpp new file mode 100644 index 000000000..b5b7c730a --- /dev/null +++ b/lib/PsychicHttp/src/async_worker.cpp @@ -0,0 +1,203 @@ +#include "async_worker.h" + +bool is_on_async_worker_thread(void) +{ + // is our handle one of the known async handles? + TaskHandle_t handle = xTaskGetCurrentTaskHandle(); + for (int i = 0; i < ASYNC_WORKER_COUNT; i++) { + if (worker_handles[i] == handle) { + return true; + } + } + return false; +} + +// Submit an HTTP req to the async worker queue +esp_err_t submit_async_req(httpd_req_t *req, httpd_req_handler_t handler) +{ + // must create a copy of the request that we own + httpd_req_t* copy = NULL; + esp_err_t err = httpd_req_async_handler_begin(req, ©); + if (err != ESP_OK) { + return err; + } + + httpd_async_req_t async_req = { + .req = copy, + .handler = handler, + }; + + // How should we handle resource exhaustion? + // In this example, we immediately respond with an + // http error if no workers are available. + int ticks = 0; + + // counting semaphore: if success, we know 1 or + // more asyncReqTaskWorkers are available. + if (xSemaphoreTake(worker_ready_count, ticks) == false) { + ESP_LOGE(PH_TAG, "No workers are available"); + httpd_req_async_handler_complete(copy); // cleanup + return ESP_FAIL; + } + + // Since worker_ready_count > 0 the queue should already have space. + // But lets wait up to 100ms just to be safe. + if (xQueueSend(async_req_queue, &async_req, pdMS_TO_TICKS(100)) == false) { + ESP_LOGE(PH_TAG, "worker queue is full"); + httpd_req_async_handler_complete(copy); // cleanup + return ESP_FAIL; + } + + return ESP_OK; +} + +void async_req_worker_task(void *p) +{ + ESP_LOGI(PH_TAG, "starting async req task worker"); + + while (true) { + + // counting semaphore - this signals that a worker + // is ready to accept work + xSemaphoreGive(worker_ready_count); + + // wait for a request + httpd_async_req_t async_req; + if (xQueueReceive(async_req_queue, &async_req, portMAX_DELAY)) { + + ESP_LOGI(PH_TAG, "invoking %s", async_req.req->uri); + + // call the handler + async_req.handler(async_req.req); + + // Inform the server that it can purge the socket used for + // this request, if needed. + if (httpd_req_async_handler_complete(async_req.req) != ESP_OK) { + ESP_LOGE(PH_TAG, "failed to complete async req"); + } + } + } + + ESP_LOGW(PH_TAG, "worker stopped"); + vTaskDelete(NULL); +} + +void start_async_req_workers(void) +{ + + // counting semaphore keeps track of available workers + worker_ready_count = xSemaphoreCreateCounting( + ASYNC_WORKER_COUNT, // Max Count + 0); // Initial Count + if (worker_ready_count == NULL) { + ESP_LOGE(PH_TAG, "Failed to create workers counting Semaphore"); + return; + } + + // create queue + async_req_queue = xQueueCreate(1, sizeof(httpd_async_req_t)); + if (async_req_queue == NULL){ + ESP_LOGE(PH_TAG, "Failed to create async_req_queue"); + vSemaphoreDelete(worker_ready_count); + return; + } + + // start worker tasks + for (int i = 0; i < ASYNC_WORKER_COUNT; i++) { + + bool success = xTaskCreate(async_req_worker_task, "async_req_worker", + ASYNC_WORKER_TASK_STACK_SIZE, // stack size + (void *)0, // argument + ASYNC_WORKER_TASK_PRIORITY, // priority + &worker_handles[i]); + + if (!success) { + ESP_LOGE(PH_TAG, "Failed to start asyncReqWorker"); + continue; + } + } +} + +/**** + * + * This code is backported from the 5.1.x branch + * +****/ + +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) + +/* Calculate the maximum size needed for the scratch buffer */ +#define HTTPD_SCRATCH_BUF MAX(HTTPD_MAX_REQ_HDR_LEN, HTTPD_MAX_URI_LEN) + +/** + * @brief Auxiliary data structure for use during reception and processing + * of requests and temporarily keeping responses + */ +struct httpd_req_aux { + struct sock_db *sd; /*!< Pointer to socket database */ + char scratch[HTTPD_SCRATCH_BUF + 1]; /*!< Temporary buffer for our operations (1 byte extra for null termination) */ + size_t remaining_len; /*!< Amount of data remaining to be fetched */ + char *status; /*!< HTTP response's status code */ + char *content_type; /*!< HTTP response's content type */ + bool first_chunk_sent; /*!< Used to indicate if first chunk sent */ + unsigned req_hdrs_count; /*!< Count of total headers in request packet */ + unsigned resp_hdrs_count; /*!< Count of additional headers in response packet */ + struct resp_hdr { + const char *field; + const char *value; + } *resp_hdrs; /*!< Additional headers in response packet */ + struct http_parser_url url_parse_res; /*!< URL parsing result, used for retrieving URL elements */ +#ifdef CONFIG_HTTPD_WS_SUPPORT + bool ws_handshake_detect; /*!< WebSocket handshake detection flag */ + httpd_ws_type_t ws_type; /*!< WebSocket frame type */ + bool ws_final; /*!< WebSocket FIN bit (final frame or not) */ + uint8_t mask_key[4]; /*!< WebSocket mask key for this payload */ +#endif +}; + +esp_err_t httpd_req_async_handler_begin(httpd_req_t *r, httpd_req_t **out) +{ + if (r == NULL || out == NULL) { + return ESP_ERR_INVALID_ARG; + } + + // alloc async req + httpd_req_t *async = (httpd_req_t *)malloc(sizeof(httpd_req_t)); + if (async == NULL) { + return ESP_ERR_NO_MEM; + } + memcpy(async, r, sizeof(httpd_req_t)); + + // alloc async aux + async->aux = (httpd_req_aux *)malloc(sizeof(struct httpd_req_aux)); + if (async->aux == NULL) { + free(async); + return ESP_ERR_NO_MEM; + } + memcpy(async->aux, r->aux, sizeof(struct httpd_req_aux)); + + // not available in 4.4.x + // mark socket as "in use" + // struct httpd_req_aux *ra = r->aux; + //ra->sd->for_async_req = true; + + *out = async; + + return ESP_OK; +} + +esp_err_t httpd_req_async_handler_complete(httpd_req_t *r) +{ + if (r == NULL) { + return ESP_ERR_INVALID_ARG; + } + + // not available in 4.4.x + // struct httpd_req_aux *ra = (httpd_req_aux *)r->aux; + // ra->sd->for_async_req = false; + + free(r->aux); + free(r); + + return ESP_OK; +} \ No newline at end of file diff --git a/lib/PsychicHttp/src/async_worker.h b/lib/PsychicHttp/src/async_worker.h new file mode 100644 index 000000000..e77c4a6a1 --- /dev/null +++ b/lib/PsychicHttp/src/async_worker.h @@ -0,0 +1,36 @@ +#ifndef async_worker_h +#define async_worker_h + +#include "PsychicCore.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" + +#define ASYNC_WORKER_TASK_PRIORITY 5 +#define ASYNC_WORKER_TASK_STACK_SIZE (4*1024) +#define ASYNC_WORKER_COUNT 8 + +// Async requests are queued here while they wait to be processed by the workers +static QueueHandle_t async_req_queue; + +// Track the number of free workers at any given time +static SemaphoreHandle_t worker_ready_count; + +// Each worker has its own thread +static TaskHandle_t worker_handles[ASYNC_WORKER_COUNT]; + +typedef esp_err_t (*httpd_req_handler_t)(httpd_req_t *req); + +typedef struct { + httpd_req_t* req; + httpd_req_handler_t handler; +} httpd_async_req_t; + +bool is_on_async_worker_thread(void); +esp_err_t submit_async_req(httpd_req_t *req, httpd_req_handler_t handler); +void async_req_worker_task(void *p); +void start_async_req_workers(void); + +esp_err_t httpd_req_async_handler_begin(httpd_req_t *r, httpd_req_t **out); +esp_err_t httpd_req_async_handler_complete(httpd_req_t *r); + +#endif //async_worker_h \ No newline at end of file diff --git a/lib/PsychicHttp/src/http_status.cpp b/lib/PsychicHttp/src/http_status.cpp new file mode 100644 index 000000000..ae4b0ee5a --- /dev/null +++ b/lib/PsychicHttp/src/http_status.cpp @@ -0,0 +1,185 @@ +#include "http_status.h" + +bool http_informational(int code) { + return code >= 100 && code < 200; +} + +bool http_success(int code) { + return code >= 200 && code < 300; +} + +bool http_redirection(int code) { + return code >= 300 && code < 400; +} + +bool http_client_error(int code) { + return code >= 400 && code < 500; +} + +bool http_server_error(int code) { + return code >= 500 && code < 600; +} + +bool http_failure(int code) { + return code >= 400 && code < 600; +} + +const char * http_status_group(int code) { + if (http_informational(code)) + return "Informational"; + + if (http_success(code)) + return "Success"; + + if (http_redirection(code)) + return "Redirection"; + + if (http_client_error(code)) + return "Client Error"; + + if (http_server_error(code)) + return "Server Error"; + + return "Unknown"; +} + +const char * http_status_reason(int code) { + switch (code) { + /*####### 1xx - Informational #######*/ + case 100: + return "Continue"; + case 101: + return "Switching Protocols"; + case 102: + return "Processing"; + case 103: + return "Early Hints"; + + /*####### 2xx - Successful #######*/ + case 200: + return "OK"; + case 201: + return "Created"; + case 202: + return "Accepted"; + case 203: + return "Non-Authoritative Information"; + case 204: + return "No Content"; + case 205: + return "Reset Content"; + case 206: + return "Partial Content"; + case 207: + return "Multi-Status"; + case 208: + return "Already Reported"; + case 226: + return "IM Used"; + + /*####### 3xx - Redirection #######*/ + case 300: + return "Multiple Choices"; + case 301: + return "Moved Permanently"; + case 302: + return "Found"; + case 303: + return "See Other"; + case 304: + return "Not Modified"; + case 305: + return "Use Proxy"; + case 307: + return "Temporary Redirect"; + case 308: + return "Permanent Redirect"; + + /*####### 4xx - Client Error #######*/ + case 400: + return "Bad Request"; + case 401: + return "Unauthorized"; + case 402: + return "Payment Required"; + case 403: + return "Forbidden"; + case 404: + return "Not Found"; + case 405: + return "Method Not Allowed"; + case 406: + return "Not Acceptable"; + case 407: + return "Proxy Authentication Required"; + case 408: + return "Request Timeout"; + case 409: + return "Conflict"; + case 410: + return "Gone"; + case 411: + return "Length Required"; + case 412: + return "Precondition Failed"; + case 413: + return "Content Too Large"; + case 414: + return "URI Too Long"; + case 415: + return "Unsupported Media Type"; + case 416: + return "Range Not Satisfiable"; + case 417: + return "Expectation Failed"; + case 418: + return "I'm a teapot"; + case 421: + return "Misdirected Request"; + case 422: + return "Unprocessable Content"; + case 423: + return "Locked"; + case 424: + return "Failed Dependency"; + case 425: + return "Too Early"; + case 426: + return "Upgrade Required"; + case 428: + return "Precondition Required"; + case 429: + return "Too Many Requests"; + case 431: + return "Request Header Fields Too Large"; + case 451: + return "Unavailable For Legal Reasons"; + + /*####### 5xx - Server Error #######*/ + case 500: + return "Internal Server Error"; + case 501: + return "Not Implemented"; + case 502: + return "Bad Gateway"; + case 503: + return "Service Unavailable"; + case 504: + return "Gateway Timeout"; + case 505: + return "HTTP Version Not Supported"; + case 506: + return "Variant Also Negotiates"; + case 507: + return "Insufficient Storage"; + case 508: + return "Loop Detected"; + case 510: + return "Not Extended"; + case 511: + return "Network Authentication Required"; + + default: + return "Unknown"; + } +} \ No newline at end of file diff --git a/lib/PsychicHttp/src/http_status.h b/lib/PsychicHttp/src/http_status.h new file mode 100644 index 000000000..12ad33281 --- /dev/null +++ b/lib/PsychicHttp/src/http_status.h @@ -0,0 +1,15 @@ +#ifndef MICRO_HTTP_STATUS_H +#define MICRO_HTTP_STATUS_H + +#include + +bool http_informational(int code); +bool http_success(int code); +bool http_redirection(int code); +bool http_client_error(int code); +bool http_server_error(int code); +bool http_failure(int code); +const char * http_status_group(int code); +const char * http_status_reason(int code); + +#endif // MICRO_HTTP_STATUS_H \ No newline at end of file diff --git a/lib/espMqttClient/src/Config.h b/lib/espMqttClient/src/Config.h index 940c2dea8..568cfef28 100644 --- a/lib/espMqttClient/src/Config.h +++ b/lib/espMqttClient/src/Config.h @@ -8,6 +8,12 @@ the LICENSE file. #pragma once +#ifndef TASMOTA_SDK +#define EMC_CLIENT_SECURE +#else +#undef EMC_CLIENT_SECURE +#endif + #ifndef EMC_TX_TIMEOUT #define EMC_TX_TIMEOUT 2000 #endif @@ -53,6 +59,10 @@ the LICENSE file. #define EMC_TASK_STACK_SIZE 5120 #endif +#ifndef EMC_MULTIPLE_CALLBACKS +#define EMC_MULTIPLE_CALLBACKS 0 +#endif + #ifndef EMC_USE_WATCHDOG #define EMC_USE_WATCHDOG 0 #endif diff --git a/lib/espMqttClient/src/MqttClient.h b/lib/espMqttClient/src/MqttClient.h index dba4bf245..318ad9c55 100644 --- a/lib/espMqttClient/src/MqttClient.h +++ b/lib/espMqttClient/src/MqttClient.h @@ -98,7 +98,6 @@ class MqttClient { uint32_t _timeout; // state is protected to allow state changes by the transport system, defined in child classes - // eg. to allow AsyncTCP enum class State { disconnected = 0, connectingTcp1 = 1, diff --git a/lib/espMqttClient/src/MqttClientSetup.h b/lib/espMqttClient/src/MqttClientSetup.h index 73458d477..67f46a079 100644 --- a/lib/espMqttClient/src/MqttClientSetup.h +++ b/lib/espMqttClient/src/MqttClientSetup.h @@ -11,6 +11,11 @@ the LICENSE file. #pragma once +#if EMC_MULTIPLE_CALLBACKS +#include +#include +#endif + #include "MqttClient.h" template @@ -73,36 +78,128 @@ class MqttClientSetup : public MqttClient { return static_cast(*this); } - T& onConnect(espMqttClientTypes::OnConnectCallback callback) { + T& onConnect(espMqttClientTypes::OnConnectCallback callback, uint32_t id = 0) { + #if EMC_MULTIPLE_CALLBACKS + _onConnectCallbacks.emplace_back(callback, id); + #else + (void) id; _onConnectCallback = callback; + #endif return static_cast(*this); } - T& onDisconnect(espMqttClientTypes::OnDisconnectCallback callback) { + T& onDisconnect(espMqttClientTypes::OnDisconnectCallback callback, uint32_t id = 0) { + #if EMC_MULTIPLE_CALLBACKS + _onDisconnectCallbacks.emplace_back(callback, id); + #else + (void) id; _onDisconnectCallback = callback; + #endif return static_cast(*this); } - T& onSubscribe(espMqttClientTypes::OnSubscribeCallback callback) { + T& onSubscribe(espMqttClientTypes::OnSubscribeCallback callback, uint32_t id = 0) { + #if EMC_MULTIPLE_CALLBACKS + _onSubscribeCallbacks.emplace_back(callback, id); + #else + (void) id; _onSubscribeCallback = callback; + #endif return static_cast(*this); } - T& onUnsubscribe(espMqttClientTypes::OnUnsubscribeCallback callback) { + T& onUnsubscribe(espMqttClientTypes::OnUnsubscribeCallback callback, uint32_t id = 0) { + #if EMC_MULTIPLE_CALLBACKS + _onUnsubscribeCallbacks.emplace_back(callback, id); + #else + (void) id; _onUnsubscribeCallback = callback; + #endif return static_cast(*this); } - T& onMessage(espMqttClientTypes::OnMessageCallback callback) { + T& onMessage(espMqttClientTypes::OnMessageCallback callback, uint32_t id = 0) { + #if EMC_MULTIPLE_CALLBACKS + _onMessageCallbacks.emplace_back(callback, id); + #else + (void) id; _onMessageCallback = callback; + #endif return static_cast(*this); } - T& onPublish(espMqttClientTypes::OnPublishCallback callback) { + T& onPublish(espMqttClientTypes::OnPublishCallback callback, uint32_t id = 0) { + #if EMC_MULTIPLE_CALLBACKS + _onPublishCallbacks.emplace_back(callback, id); + #else + (void) id; _onPublishCallback = callback; + #endif return static_cast(*this); } + #if EMC_MULTIPLE_CALLBACKS + T& removeOnConnect(uint32_t id) { + for (auto it = _onConnectCallbacks.begin(); it != _onConnectCallbacks.end(); ++it) { + if (it->second == id) { + _onConnectCallbacks.erase(it); + break; + } + } + return static_cast(*this); + } + + T& removeOnDisconnect(uint32_t id) { + for (auto it = _onDisconnectCallbacks.begin(); it != _onDisconnectCallbacks.end(); ++it) { + if (it->second == id) { + _onDisconnectCallbacks.erase(it); + break; + } + } + return static_cast(*this); + } + + T& removeOnSubscribe(uint32_t id) { + for (auto it = _onSubscribeCallbacks.begin(); it != _onSubscribeCallbacks.end(); ++it) { + if (it->second == id) { + _onSubscribeCallbacks.erase(it); + break; + } + } + return static_cast(*this); + } + + T& removeOnUnsubscribe(uint32_t id) { + for (auto it = _onUnsubscribeCallbacks.begin(); it != _onUnsubscribeCallbacks.end(); ++it) { + if (it->second == id) { + _onUnsubscribeCallbacks.erase(it); + break; + } + } + return static_cast(*this); + } + + T& removeOnMessage(uint32_t id) { + for (auto it = _onMessageCallbacks.begin(); it != _onMessageCallbacks.end(); ++it) { + if (it->second == id) { + _onMessageCallbacks.erase(it); + break; + } + } + return static_cast(*this); + } + + T& removeOnPublish(uint32_t id) { + for (auto it = _onPublishCallbacks.begin(); it != _onPublishCallbacks.end(); ++it) { + if (it->second == id) { + _onPublishCallbacks.erase(it); + break; + } + } + return static_cast(*this); + } + #endif + /* T& onError(espMqttClientTypes::OnErrorCallback callback) { _onErrorCallback = callback; @@ -112,5 +209,37 @@ class MqttClientSetup : public MqttClient { protected: explicit MqttClientSetup(espMqttClientTypes::UseInternalTask useInternalTask, uint8_t priority = 1, uint8_t core = 1) - : MqttClient(useInternalTask, priority, core) {} + : MqttClient(useInternalTask, priority, core) { + #if EMC_MULTIPLE_CALLBACKS + _onConnectCallback = [this](bool sessionPresent) { + for (auto callback : _onConnectCallbacks) if (callback.first) callback.first(sessionPresent); + }; + _onDisconnectCallback = [this](espMqttClientTypes::DisconnectReason reason) { + for (auto callback : _onDisconnectCallbacks) if (callback.first) callback.first(reason); + }; + _onSubscribeCallback = [this](uint16_t packetId, const espMqttClientTypes::SubscribeReturncode* returncodes, size_t len) { + for (auto callback : _onSubscribeCallbacks) if (callback.first) callback.first(packetId, returncodes, len); + }; + _onUnsubscribeCallback = [this](int16_t packetId) { + for (auto callback : _onUnsubscribeCallbacks) if (callback.first) callback.first(packetId); + }; + _onMessageCallback = [this](const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total) { + for (auto callback : _onMessageCallbacks) if (callback.first) callback.first(properties, topic, payload, len, index, total); + }; + _onPublishCallback = [this](uint16_t packetId) { + for (auto callback : _onPublishCallbacks) if (callback.first) callback.first(packetId); + }; + #else + // empty + #endif + } + + #if EMC_MULTIPLE_CALLBACKS + std::list> _onConnectCallbacks; + std::list> _onDisconnectCallbacks; + std::list> _onSubscribeCallbacks; + std::list> _onUnsubscribeCallbacks; + std::list> _onMessageCallbacks; + std::list> _onPublishCallbacks; + #endif }; diff --git a/lib/espMqttClient/src/Transport/ClientAsync.cpp b/lib/espMqttClient/src/Transport/ClientAsync.cpp deleted file mode 100644 index 4f8d69eeb..000000000 --- a/lib/espMqttClient/src/Transport/ClientAsync.cpp +++ /dev/null @@ -1,58 +0,0 @@ -/* -Copyright (c) 2022 Bert Melis. All rights reserved. - -This work is licensed under the terms of the MIT license. -For a copy, see or -the LICENSE file. -*/ - -#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) - -#include "ClientAsync.h" - -namespace espMqttClientInternals { - -ClientAsync::ClientAsync() -: client() -, availableData(0) -, bufData(nullptr) { - // empty -} - -bool ClientAsync::connect(IPAddress ip, uint16_t port) { - return client.connect(ip, port); -} - -bool ClientAsync::connect(const char* host, uint16_t port) { - return client.connect(host, port); -} - -size_t ClientAsync::write(const uint8_t* buf, size_t size) { - return client.write(reinterpret_cast(buf), size); -} - -int ClientAsync::read(uint8_t* buf, size_t size) { - size_t willRead = std::min(size, availableData); - memcpy(buf, bufData, std::min(size, availableData)); - if (availableData > size) { - emc_log_w("Buffer is smaller than available data: %zu - %zu", size, availableData); - } - availableData = 0; - return willRead; -} - -void ClientAsync::stop() { - client.close(false); -} - -bool ClientAsync::connected() { - return client.connected(); -} - -bool ClientAsync::disconnected() { - return client.disconnected(); -} - -} // namespace espMqttClientInternals - -#endif diff --git a/lib/espMqttClient/src/Transport/ClientAsync.h b/lib/espMqttClient/src/Transport/ClientAsync.h deleted file mode 100644 index c3ddd0388..000000000 --- a/lib/espMqttClient/src/Transport/ClientAsync.h +++ /dev/null @@ -1,44 +0,0 @@ -/* -Copyright (c) 2022 Bert Melis. All rights reserved. - -This work is licensed under the terms of the MIT license. -For a copy, see or -the LICENSE file. -*/ - -#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) - -#pragma once - -#if defined(ARDUINO_ARCH_ESP32) - #include "freertos/FreeRTOS.h" - #include -#elif defined(ARDUINO_ARCH_ESP8266) - #include -#endif - -#include "Transport.h" -#include "../Config.h" -#include "../Logging.h" - -namespace espMqttClientInternals { - -class ClientAsync : public Transport { - public: - ClientAsync(); - bool connect(IPAddress ip, uint16_t port) override; - bool connect(const char* host, uint16_t port) override; - size_t write(const uint8_t* buf, size_t size) override; - int read(uint8_t* buf, size_t size) override; - void stop() override; - bool connected() override; - bool disconnected() override; - - AsyncClient client; - size_t availableData; - uint8_t* bufData; -}; - -} // namespace espMqttClientInternals - -#endif diff --git a/lib/espMqttClient/src/Transport/ClientSecureSync.h b/lib/espMqttClient/src/Transport/ClientSecureSync.h index b81681e36..c29b03d2f 100644 --- a/lib/espMqttClient/src/Transport/ClientSecureSync.h +++ b/lib/espMqttClient/src/Transport/ClientSecureSync.h @@ -10,7 +10,12 @@ the LICENSE file. #if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) +#include "../Config.h" +#if defined(EMC_CLIENT_SECURE) #include // includes IPAddress +#else +#include +#endif #include "Transport.h" @@ -26,7 +31,11 @@ class ClientSecureSync : public Transport { void stop() override; bool connected() override; bool disconnected() override; +#if defined(EMC_CLIENT_SECURE) WiFiClientSecure client; +#else + WiFiClient client; +#endif }; } // namespace espMqttClientInternals diff --git a/lib/espMqttClient/src/espMqttClient.cpp b/lib/espMqttClient/src/espMqttClient.cpp index 833ece10b..bd9b6935a 100644 --- a/lib/espMqttClient/src/espMqttClient.cpp +++ b/lib/espMqttClient/src/espMqttClient.cpp @@ -78,27 +78,37 @@ espMqttClientSecure::espMqttClientSecure(uint8_t priority, uint8_t core) } espMqttClientSecure& espMqttClientSecure::setInsecure() { +#if defined(EMC_CLIENT_SECURE) _client.client.setInsecure(); +#endif return *this; } espMqttClientSecure& espMqttClientSecure::setCACert(const char* rootCA) { +#if defined(EMC_CLIENT_SECURE) _client.client.setCACert(rootCA); +#endif return *this; } espMqttClientSecure& espMqttClientSecure::setCertificate(const char* clientCa) { +#if defined(EMC_CLIENT_SECURE) _client.client.setCertificate(clientCa); +#endif return *this; } espMqttClientSecure& espMqttClientSecure::setPrivateKey(const char* privateKey) { +#if defined(EMC_CLIENT_SECURE) _client.client.setPrivateKey(privateKey); +#endif return *this; } espMqttClientSecure& espMqttClientSecure::setPreSharedKey(const char* pskIdent, const char* psKey) { +#if defined(EMC_CLIENT_SECURE) _client.client.setPreSharedKey(pskIdent, psKey); +#endif return *this; } diff --git a/lib/espMqttClient/src/espMqttClientAsync.cpp b/lib/espMqttClient/src/espMqttClientAsync.cpp deleted file mode 100644 index 98b7f1519..000000000 --- a/lib/espMqttClient/src/espMqttClientAsync.cpp +++ /dev/null @@ -1,61 +0,0 @@ -/* -Copyright (c) 2022 Bert Melis. All rights reserved. - -This work is licensed under the terms of the MIT license. -For a copy, see or -the LICENSE file. -*/ - -#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) - -#include "espMqttClientAsync.h" - -espMqttClientAsync::espMqttClientAsync() -: MqttClientSetup(espMqttClientTypes::UseInternalTask::NO) -, _clientAsync() { - _transport = &_clientAsync; - _clientAsync.client.onConnect(onConnectCb, this); - _clientAsync.client.onDisconnect(onDisconnectCb, this); - _clientAsync.client.onData(onDataCb, this); - _clientAsync.client.onPoll(onPollCb, this); -} - -bool espMqttClientAsync::connect() { - bool ret = MqttClient::connect(); - loop(); - return ret; -} - -void espMqttClientAsync::_setupClient(espMqttClientAsync* c) { - (void)c; -} - -void espMqttClientAsync::onConnectCb(void* a, AsyncClient* c) { - c->setNoDelay(true); - espMqttClientAsync* client = reinterpret_cast(a); - client->_state = MqttClient::State::connectingTcp2; - client->loop(); -} - -void espMqttClientAsync::onDataCb(void* a, AsyncClient* c, void* data, size_t len) { - (void)c; - espMqttClientAsync* client = reinterpret_cast(a); - client->_clientAsync.bufData = reinterpret_cast(data); - client->_clientAsync.availableData = len; - client->loop(); -} - -void espMqttClientAsync::onDisconnectCb(void* a, AsyncClient* c) { - (void)c; - espMqttClientAsync* client = reinterpret_cast(a); - client->_state = MqttClient::State::disconnectingTcp2; - client->loop(); -} - -void espMqttClientAsync::onPollCb(void* a, AsyncClient* c) { - (void)c; - espMqttClientAsync* client = reinterpret_cast(a); - client->loop(); -} - -#endif diff --git a/lib/espMqttClient/src/espMqttClientAsync.h b/lib/espMqttClient/src/espMqttClientAsync.h deleted file mode 100644 index 1b9ed8beb..000000000 --- a/lib/espMqttClient/src/espMqttClientAsync.h +++ /dev/null @@ -1,36 +0,0 @@ -/* -Copyright (c) 2022 Bert Melis. All rights reserved. - -API is based on the original work of Marvin Roger: -https://github.com/marvinroger/async-mqtt-client - -This work is licensed under the terms of the MIT license. -For a copy, see or -the LICENSE file. -*/ - -#pragma once - -#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) - -#include "Transport/ClientAsync.h" - -#include "MqttClientSetup.h" - -class espMqttClientAsync : public MqttClientSetup { - public: - espMqttClientAsync(); - bool connect(); - - protected: - espMqttClientInternals::ClientAsync _clientAsync; - static void _setupClient(espMqttClientAsync* c); - static void _disconnectClient(espMqttClientAsync* c); - - static void onConnectCb(void* a, AsyncClient* c); - static void onDataCb(void* a, AsyncClient* c, void* data, size_t len); - static void onDisconnectCb(void* a, AsyncClient* c); - static void onPollCb(void* a, AsyncClient* c); -}; - -#endif diff --git a/lib/framework/APSettingsService.cpp b/lib/framework/APSettingsService.cpp index acd602fc2..2ac636dbd 100644 --- a/lib/framework/APSettingsService.cpp +++ b/lib/framework/APSettingsService.cpp @@ -2,8 +2,10 @@ #include "../../src/emsesp_stub.hpp" -APSettingsService::APSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager) - : _httpEndpoint(APSettings::read, APSettings::update, this, server, AP_SETTINGS_SERVICE_PATH, securityManager) +APSettingsService::APSettingsService(PsychicHttpServer * server, FS * fs, SecurityManager * securityManager) + : _server(server) + , _securityManager(securityManager) + , _httpEndpoint(APSettings::read, APSettings::update, this, server, AP_SETTINGS_SERVICE_PATH, securityManager) , _fsPersistence(APSettings::read, APSettings::update, this, fs, AP_SETTINGS_FILE) , _dnsServer(nullptr) , _lastManaged(0) @@ -19,6 +21,10 @@ void APSettingsService::begin() { // reconfigureAP(); } +void APSettingsService::registerURI() { + _httpEndpoint.registerURI(); +} + // wait 10 sec on STA disconnect before starting AP void APSettingsService::WiFiEvent(WiFiEvent_t event) { uint8_t was_connected = _connected; diff --git a/lib/framework/APSettingsService.h b/lib/framework/APSettingsService.h index 68bca2135..1d2667ed0 100644 --- a/lib/framework/APSettingsService.h +++ b/lib/framework/APSettingsService.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -118,17 +119,21 @@ class APSettings { class APSettingsService : public StatefulService { public: - APSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager); + APSettingsService(PsychicHttpServer * server, FS * fs, SecurityManager * securityManager); + + void begin(); + void loop(); + void registerURI(); - void begin(); - void loop(); APNetworkStatus getAPNetworkStatus(); private: + SecurityManager * _securityManager; + PsychicHttpServer * _server; + HttpEndpoint _httpEndpoint; FSPersistence _fsPersistence; - // for the captive portal DNSServer * _dnsServer; // for the management delay loop diff --git a/lib/framework/APStatus.cpp b/lib/framework/APStatus.cpp index b60dd3ddd..095033a5b 100644 --- a/lib/framework/APStatus.cpp +++ b/lib/framework/APStatus.cpp @@ -2,22 +2,26 @@ using namespace std::placeholders; // for `_1` etc -APStatus::APStatus(AsyncWebServer * server, SecurityManager * securityManager, APSettingsService * apSettingsService) - : _apSettingsService(apSettingsService) { - server->on(AP_STATUS_SERVICE_PATH, - HTTP_GET, - securityManager->wrapRequest(std::bind(&APStatus::apStatus, this, _1), AuthenticationPredicates::IS_AUTHENTICATED)); +APStatus::APStatus(PsychicHttpServer * server, SecurityManager * securityManager, APSettingsService * apSettingsService) + : _apSettingsService(apSettingsService) + , _server(server) + , _securityManager(securityManager) { } -void APStatus::apStatus(AsyncWebServerRequest * request) { - AsyncJsonResponse * response = new AsyncJsonResponse(false, MAX_AP_STATUS_SIZE); - JsonObject root = response->getRoot(); +void APStatus::registerURI() { + _server->on(AP_STATUS_SERVICE_PATH, + HTTP_GET, + _securityManager->wrapRequest(std::bind(&APStatus::apStatus, this, _1), AuthenticationPredicates::IS_AUTHENTICATED)); +} + +esp_err_t APStatus::apStatus(PsychicRequest * request) { + PsychicJsonResponse response = PsychicJsonResponse(request, false, MAX_AP_STATUS_SIZE); + JsonObject root = response.getRoot(); root["status"] = _apSettingsService->getAPNetworkStatus(); root["ip_address"] = WiFi.softAPIP().toString(); root["mac_address"] = WiFi.softAPmacAddress(); root["station_num"] = WiFi.softAPgetStationNum(); - response->setLength(); - request->send(response); + return response.send(); } diff --git a/lib/framework/APStatus.h b/lib/framework/APStatus.h index 754c66618..32427aa7d 100644 --- a/lib/framework/APStatus.h +++ b/lib/framework/APStatus.h @@ -2,10 +2,9 @@ #define APStatus_h #include -#include #include -#include +#include #include #include #include @@ -15,11 +14,16 @@ class APStatus { public: - APStatus(AsyncWebServer * server, SecurityManager * securityManager, APSettingsService * apSettingsService); + APStatus(PsychicHttpServer * server, SecurityManager * securityManager, APSettingsService * apSettingsService); + void registerURI(); private: + SecurityManager * _securityManager; + PsychicHttpServer * _server; + APSettingsService * _apSettingsService; - void apStatus(AsyncWebServerRequest * request); + + esp_err_t apStatus(PsychicRequest * request); }; #endif diff --git a/lib/framework/AuthenticationService.cpp b/lib/framework/AuthenticationService.cpp index 0c1822efe..fdbdb55e8 100644 --- a/lib/framework/AuthenticationService.cpp +++ b/lib/framework/AuthenticationService.cpp @@ -2,46 +2,31 @@ using namespace std::placeholders; // for `_1` etc -#if FT_ENABLED(FT_SECURITY) - -AuthenticationService::AuthenticationService(AsyncWebServer * server, SecurityManager * securityManager) - : _securityManager(securityManager) - , _signInHandler(SIGN_IN_PATH, std::bind(&AuthenticationService::signIn, this, _1, _2)) { - server->on(VERIFY_AUTHORIZATION_PATH, HTTP_GET, std::bind(&AuthenticationService::verifyAuthorization, this, _1)); - _signInHandler.setMethod(HTTP_POST); - _signInHandler.setMaxContentLength(MAX_AUTHENTICATION_SIZE); - server->addHandler(&_signInHandler); +AuthenticationService::AuthenticationService(PsychicHttpServer * server, SecurityManager * securityManager) + : _server(server) + , _securityManager(securityManager) { } -/** - * Verifies that the request supplied a valid JWT. - */ -void AuthenticationService::verifyAuthorization(AsyncWebServerRequest * request) { - Authentication authentication = _securityManager->authenticateRequest(request); - request->send(authentication.authenticated ? 200 : 401); -} - -/** - * Signs in a user if the username and password match. Provides a JWT to be used in the Authorization header in - * subsequent requests. - */ -void AuthenticationService::signIn(AsyncWebServerRequest * request, JsonVariant & json) { - if (json.is()) { - String username = json["username"]; - String password = json["password"]; - Authentication authentication = _securityManager->authenticate(username, password); - if (authentication.authenticated) { - User * user = authentication.user; - AsyncJsonResponse * response = new AsyncJsonResponse(false, MAX_AUTHENTICATION_SIZE); - JsonObject jsonObject = response->getRoot(); - jsonObject["access_token"] = _securityManager->generateJWT(user); - response->setLength(); - request->send(response); - return; +void AuthenticationService::registerURI() { + // Signs in a user if the username and password match. Provides a JWT to be used in the Authorization header in subsequent requests + _server->on(SIGN_IN_PATH, HTTP_POST, [this](PsychicRequest * request, JsonVariant & json) { + if (json.is()) { + String username = json["username"]; + String password = json["password"]; + Authentication authentication = _securityManager->authenticate(username, password); + if (authentication.authenticated) { + PsychicJsonResponse response = PsychicJsonResponse(request, false, 256); + JsonObject root = response.getRoot(); + root["access_token"] = _securityManager->generateJWT(authentication.user); + return response.send(); + } } - } - AsyncWebServerResponse * response = request->beginResponse(401); - request->send(response); -} + return request->reply(401); + }); -#endif + // Verifies that the request supplied a valid JWT + _server->on(VERIFY_AUTHORIZATION_PATH, HTTP_GET, [this](PsychicRequest * request) { + Authentication authentication = _securityManager->authenticateRequest(request); + return request->reply(authentication.authenticated ? 200 : 401); + }); +} diff --git a/lib/framework/AuthenticationService.h b/lib/framework/AuthenticationService.h index 60ed100b6..5da57324d 100644 --- a/lib/framework/AuthenticationService.h +++ b/lib/framework/AuthenticationService.h @@ -2,29 +2,20 @@ #define AuthenticationService_H_ #include -#include +#include #include #define VERIFY_AUTHORIZATION_PATH "/rest/verifyAuthorization" #define SIGN_IN_PATH "/rest/signIn" -#define MAX_AUTHENTICATION_SIZE 256 - -#if FT_ENABLED(FT_SECURITY) - class AuthenticationService { public: - AuthenticationService(AsyncWebServer * server, SecurityManager * securityManager); + AuthenticationService(PsychicHttpServer * server, SecurityManager * securityManager); + void registerURI(); private: - SecurityManager * _securityManager; - AsyncCallbackJsonWebHandler _signInHandler; - - // endpoint functions - void signIn(AsyncWebServerRequest * request, JsonVariant & json); - void verifyAuthorization(AsyncWebServerRequest * request); + SecurityManager * _securityManager; + PsychicHttpServer * _server; }; #endif - -#endif diff --git a/lib/framework/ESP8266React.cpp b/lib/framework/ESP8266React.cpp index ad2331428..61490b777 100644 --- a/lib/framework/ESP8266React.cpp +++ b/lib/framework/ESP8266React.cpp @@ -1,13 +1,11 @@ #include -#include - -ESP8266React::ESP8266React(AsyncWebServer * server, FS * fs) - : _featureService(server) - , _securitySettingsService(server, fs) - , _networkSettingsService(server, fs, &_securitySettingsService) - , _wifiScanner(server, &_securitySettingsService) +ESP8266React::ESP8266React(PsychicHttpServer * server, FS * fs) + : _networkSettingsService(server, fs, &_securitySettingsService) , _networkStatus(server, &_securitySettingsService) + , _featureService(server) + , _securitySettingsService(server, fs) + , _wifiScanner(server, &_securitySettingsService) , _apSettingsService(server, fs, &_securitySettingsService) , _apStatus(server, &_securitySettingsService, &_apSettingsService) , _ntpSettingsService(server, fs, &_securitySettingsService) @@ -20,40 +18,11 @@ ESP8266React::ESP8266React(AsyncWebServer * server, FS * fs) , _restartService(server, &_securitySettingsService) , _factoryResetService(server, fs, &_securitySettingsService) , _systemStatus(server, &_securitySettingsService) { - // Serve static resources from PROGMEM - WWWData::registerRoutes([server, this](const String & uri, const String & contentType, const uint8_t * content, size_t len) { - ArRequestHandlerFunction requestHandler = [contentType, content, len](AsyncWebServerRequest * request) { - AsyncWebServerResponse * response = request->beginResponse_P(200, contentType, content, len); - response->addHeader("Content-Encoding", "gzip"); - // response->addHeader("Content-Encoding", "br"); // only works over HTTPS - request->send(response); - }; - server->on(uri.c_str(), HTTP_GET, requestHandler); - // Serving non matching get requests with "/index.html" - // OPTIONS get a straight up 200 response - if (uri.equals("/index.html")) { - server->onNotFound([requestHandler](AsyncWebServerRequest * request) { - if (request->method() == HTTP_GET) { - requestHandler(request); - } else if (request->method() == HTTP_OPTIONS) { - request->send(200); - } else { - request->send(404); - } - }); - } - }); } +// register services void ESP8266React::begin() { _networkSettingsService.begin(); - _networkSettingsService.read([&](NetworkSettings & networkSettings) { - if (networkSettings.enableCORS) { - DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", networkSettings.CORSOrigin); - DefaultHeaders::Instance().addHeader("Access-Control-Allow-Headers", "Accept, Content-Type, Authorization"); - DefaultHeaders::Instance().addHeader("Access-Control-Allow-Credentials", "true"); - } - }); _apSettingsService.begin(); _ntpSettingsService.begin(); _otaSettingsService.begin(); @@ -61,6 +30,35 @@ void ESP8266React::begin() { _securitySettingsService.begin(); } +// create the web server endpoints +void ESP8266React::registerURI() { + _featureService.registerURI(); + _authenticationService.registerURI(); + _systemStatus.registerURI(); + + _networkSettingsService.registerURI(); + _networkStatus.registerURI(); + + _apSettingsService.registerURI(); + _apStatus.registerURI(); + + _ntpSettingsService.registerURI(); + _ntpStatus.registerURI(); + + _mqttSettingsService.registerURI(); + _mqttStatus.registerURI(); + + _securitySettingsService.registerURI(); + _otaSettingsService.registerURI(); + + _restartService.registerURI(); + _factoryResetService.registerURI(); + + _wifiScanner.registerURI(); + + _uploadFileService.registerURI(); +} + void ESP8266React::loop() { _networkSettingsService.loop(); _apSettingsService.loop(); diff --git a/lib/framework/ESP8266React.h b/lib/framework/ESP8266React.h index 2bab9e190..7606f8ff3 100644 --- a/lib/framework/ESP8266React.h +++ b/lib/framework/ESP8266React.h @@ -2,8 +2,6 @@ #define ESP8266React_h #include - -#include #include #include @@ -26,10 +24,11 @@ class ESP8266React { public: - ESP8266React(AsyncWebServer * server, FS * fs); + ESP8266React(PsychicHttpServer * server, FS * fs); void begin(); void loop(); + void registerURI(); SecurityManager * getSecurityManager() { return &_securitySettingsService; diff --git a/lib/framework/FSPersistence.h b/lib/framework/FSPersistence.h index 41819fc83..434ab78ee 100644 --- a/lib/framework/FSPersistence.h +++ b/lib/framework/FSPersistence.h @@ -68,7 +68,10 @@ class FSPersistence { return false; } - // serialize the data to the file +// serialize the data to the file +#ifdef EMSESP_DEBUG + Serial.println("Writing settings to " + String(_filePath)); +#endif serializeJson(jsonDocument, settingsFile); settingsFile.close(); return true; diff --git a/lib/framework/FactoryResetService.cpp b/lib/framework/FactoryResetService.cpp index 6bb631b02..6100203ac 100644 --- a/lib/framework/FactoryResetService.cpp +++ b/lib/framework/FactoryResetService.cpp @@ -2,21 +2,24 @@ using namespace std::placeholders; -FactoryResetService::FactoryResetService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager) - : fs(fs) { - server->on(FACTORY_RESET_SERVICE_PATH, - HTTP_POST, - securityManager->wrapRequest(std::bind(&FactoryResetService::handleRequest, this, _1), AuthenticationPredicates::IS_ADMIN)); +FactoryResetService::FactoryResetService(PsychicHttpServer * server, FS * fs, SecurityManager * securityManager) + : _server(server) + , _securityManager(securityManager) { } -void FactoryResetService::handleRequest(AsyncWebServerRequest * request) { - request->onDisconnect(std::bind(&FactoryResetService::factoryReset, this)); - request->send(200); +void FactoryResetService::registerURI() { + _server->on(FACTORY_RESET_SERVICE_PATH, + HTTP_POST, + _securityManager->wrapRequest(std::bind(&FactoryResetService::handleRequest, this, _1), AuthenticationPredicates::IS_ADMIN)); } -/** - * Delete function assumes that all files are stored flat, within the config directory. - */ +esp_err_t FactoryResetService::handleRequest(PsychicRequest * request) { + request->reply(200); + RestartService::restartNow(); + return ESP_OK; +} + +// Delete function assumes that all files are stored flat, within the config directory. void FactoryResetService::factoryReset() { // TODO To replaced with fs.rmdir(FS_CONFIG_DIRECTORY) now we're using IDF 4.2 File root = fs->open(FS_CONFIG_DIRECTORY); diff --git a/lib/framework/FactoryResetService.h b/lib/framework/FactoryResetService.h index a6a4bee12..cbab112de 100644 --- a/lib/framework/FactoryResetService.h +++ b/lib/framework/FactoryResetService.h @@ -2,7 +2,7 @@ #define FactoryResetService_h #include -#include +#include #include #include #include @@ -14,12 +14,16 @@ class FactoryResetService { FS * fs; public: - FactoryResetService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager); + FactoryResetService(PsychicHttpServer * server, FS * fs, SecurityManager * securityManager); + void registerURI(); void factoryReset(); private: - void handleRequest(AsyncWebServerRequest * request); + SecurityManager * _securityManager; + PsychicHttpServer * _server; + + esp_err_t handleRequest(PsychicRequest * request); }; #endif diff --git a/lib/framework/Features.h b/lib/framework/Features.h index 204caaa18..2666e759f 100644 --- a/lib/framework/Features.h +++ b/lib/framework/Features.h @@ -1,8 +1,6 @@ #ifndef Features_h #define Features_h -// modified by Proddy - #define FT_ENABLED(feature) feature // project feature on by default diff --git a/lib/framework/FeaturesService.cpp b/lib/framework/FeaturesService.cpp index f8a82f441..650669148 100644 --- a/lib/framework/FeaturesService.cpp +++ b/lib/framework/FeaturesService.cpp @@ -1,19 +1,19 @@ #include #include "../../src/emsesp_stub.hpp" -using namespace std::placeholders; // for `_1` etc - -FeaturesService::FeaturesService(AsyncWebServer * server) { - server->on(FEATURES_SERVICE_PATH, HTTP_GET, std::bind(&FeaturesService::features, this, _1)); +FeaturesService::FeaturesService(PsychicHttpServer * server) + : _server(server) { } -void FeaturesService::features(AsyncWebServerRequest * request) { - AsyncJsonResponse * response = new AsyncJsonResponse(false, MAX_FEATURES_SIZE); - JsonObject root = response->getRoot(); +void FeaturesService::registerURI() { + // return feature set + _server->on(FEATURES_SERVICE_PATH, HTTP_GET, [](PsychicRequest * request) { + PsychicJsonResponse response = PsychicJsonResponse(request, false, 256); + JsonObject root = response.getRoot(); - root["version"] = EMSESP_APP_VERSION; - root["platform"] = EMSESP_PLATFORM; + root["version"] = EMSESP_APP_VERSION; + root["platform"] = EMSESP_PLATFORM; - response->setLength(); - request->send(response); + return response.send(); + }); } diff --git a/lib/framework/FeaturesService.h b/lib/framework/FeaturesService.h index 257d0e1cf..0462565cf 100644 --- a/lib/framework/FeaturesService.h +++ b/lib/framework/FeaturesService.h @@ -5,18 +5,18 @@ #include #include -#include -#include +#include -#define MAX_FEATURES_SIZE 256 #define FEATURES_SERVICE_PATH "/rest/features" class FeaturesService { public: - FeaturesService(AsyncWebServer * server); + FeaturesService(PsychicHttpServer * server); + + void registerURI(); private: - void features(AsyncWebServerRequest * request); + PsychicHttpServer * _server; }; #endif diff --git a/lib/framework/HttpEndpoint.h b/lib/framework/HttpEndpoint.h index 926a6f772..aa8bdea54 100644 --- a/lib/framework/HttpEndpoint.h +++ b/lib/framework/HttpEndpoint.h @@ -3,146 +3,90 @@ #include -#include +#include #include #include #define HTTP_ENDPOINT_ORIGIN_ID "http" +#define HTTPS_ENDPOINT_ORIGIN_ID "https" using namespace std::placeholders; // for `_1` etc template -class HttpGetEndpoint { - public: - HttpGetEndpoint(JsonStateReader stateReader, - StatefulService * statefulService, - AsyncWebServer * server, - const String & servicePath, - SecurityManager * securityManager, - AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN, - size_t bufferSize = DEFAULT_BUFFER_SIZE) - : _stateReader(stateReader) - , _statefulService(statefulService) - , _bufferSize(bufferSize) { - server->on(servicePath.c_str(), HTTP_GET, securityManager->wrapRequest(std::bind(&HttpGetEndpoint::fetchSettings, this, _1), authenticationPredicate)); - } - - HttpGetEndpoint(JsonStateReader stateReader, - StatefulService * statefulService, - AsyncWebServer * server, - const String & servicePath, - size_t bufferSize = DEFAULT_BUFFER_SIZE) - : _stateReader(stateReader) - , _statefulService(statefulService) - , _bufferSize(bufferSize) { - server->on(servicePath.c_str(), HTTP_GET, std::bind(&HttpGetEndpoint::fetchSettings, this, _1)); - } - +class HttpEndpoint { protected: - JsonStateReader _stateReader; - StatefulService * _statefulService; - size_t _bufferSize; + JsonStateReader _stateReader; + JsonStateUpdater _stateUpdater; + StatefulService * _statefulService; + size_t _bufferSize; + SecurityManager * _securityManager; + AuthenticationPredicate _authenticationPredicate; + PsychicHttpServer * _server; + const char * _servicePath; - void fetchSettings(AsyncWebServerRequest * request) { - AsyncJsonResponse * response = new AsyncJsonResponse(false, _bufferSize); - JsonObject jsonObject = response->getRoot().to(); - _statefulService->read(jsonObject, _stateReader); - - response->setLength(); - request->send(response); - } -}; - -template -class HttpPostEndpoint { - public: - HttpPostEndpoint(JsonStateReader stateReader, - JsonStateUpdater stateUpdater, - StatefulService * statefulService, - AsyncWebServer * server, - const String & servicePath, - SecurityManager * securityManager, - AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN, - size_t bufferSize = DEFAULT_BUFFER_SIZE) - : _stateReader(stateReader) - , _stateUpdater(stateUpdater) - , _statefulService(statefulService) - , _updateHandler(servicePath, securityManager->wrapCallback(std::bind(&HttpPostEndpoint::updateSettings, this, _1, _2), authenticationPredicate), bufferSize) - , _bufferSize(bufferSize) { - _updateHandler.setMethod(HTTP_POST); - server->addHandler(&_updateHandler); - } - - HttpPostEndpoint(JsonStateReader stateReader, - JsonStateUpdater stateUpdater, - StatefulService * statefulService, - AsyncWebServer * server, - const String & servicePath, - size_t bufferSize = DEFAULT_BUFFER_SIZE) - : _stateReader(stateReader) - , _stateUpdater(stateUpdater) - , _statefulService(statefulService) - , _updateHandler(servicePath, std::bind(&HttpPostEndpoint::updateSettings, this, _1, _2), bufferSize) - , _bufferSize(bufferSize) { - _updateHandler.setMethod(HTTP_POST); - server->addHandler(&_updateHandler); - } - - protected: - JsonStateReader _stateReader; - JsonStateUpdater _stateUpdater; - StatefulService * _statefulService; - AsyncCallbackJsonWebHandler _updateHandler; - size_t _bufferSize; - - void updateSettings(AsyncWebServerRequest * request, JsonVariant & json) { - if (!json.is()) { - request->send(400); - return; - } - JsonObject jsonObject = json.as(); - StateUpdateResult outcome = _statefulService->updateWithoutPropagation(jsonObject, _stateUpdater); - if (outcome == StateUpdateResult::ERROR) { - request->send(400); - return; - } else if ((outcome == StateUpdateResult::CHANGED) || (outcome == StateUpdateResult::CHANGED_RESTART)) { - request->onDisconnect([this]() { _statefulService->callUpdateHandlers(HTTP_ENDPOINT_ORIGIN_ID); }); - } - AsyncJsonResponse * response = new AsyncJsonResponse(false, _bufferSize); - jsonObject = response->getRoot().to(); - _statefulService->read(jsonObject, _stateReader); - if (outcome == StateUpdateResult::CHANGED_RESTART) { - response->setCode(205); // reboot required - } - response->setLength(); - request->send(response); - } -}; - -template -class HttpEndpoint : public HttpGetEndpoint, public HttpPostEndpoint { public: HttpEndpoint(JsonStateReader stateReader, JsonStateUpdater stateUpdater, StatefulService * statefulService, - AsyncWebServer * server, - const String & servicePath, + PsychicHttpServer * server, + const char * servicePath, SecurityManager * securityManager, AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN, size_t bufferSize = DEFAULT_BUFFER_SIZE) - : HttpGetEndpoint(stateReader, statefulService, server, servicePath, securityManager, authenticationPredicate, bufferSize) - , HttpPostEndpoint(stateReader, stateUpdater, statefulService, server, servicePath, securityManager, authenticationPredicate, bufferSize) { + : _stateReader(stateReader) + , _stateUpdater(stateUpdater) + , _statefulService(statefulService) + , _server(server) + , _servicePath(servicePath) + , _securityManager(securityManager) + , _authenticationPredicate(authenticationPredicate) + , _bufferSize(bufferSize) { } - HttpEndpoint(JsonStateReader stateReader, - JsonStateUpdater stateUpdater, - StatefulService * statefulService, - AsyncWebServer * server, - const String & servicePath, - size_t bufferSize = DEFAULT_BUFFER_SIZE) - : HttpGetEndpoint(stateReader, statefulService, server, servicePath, bufferSize) - , HttpPostEndpoint(stateReader, stateUpdater, statefulService, server, servicePath, bufferSize) { + // register the web server on() endpoints + void registerURI() { + _server->on(_servicePath, + HTTP_GET, + _securityManager->wrapRequest( + [this](PsychicRequest * request) { + PsychicJsonResponse response = PsychicJsonResponse(request, false, _bufferSize); + JsonObject jsonObject = response.getRoot(); + _statefulService->read(jsonObject, _stateReader); + return response.send(); + }, + _authenticationPredicate)); + + _server->on(_servicePath, + HTTP_POST, + _securityManager->wrapCallback( + [this](PsychicRequest * request, JsonVariant & json) { + if (!json.is()) { + return request->reply(400); + } + + JsonObject jsonObject = json.as(); + StateUpdateResult outcome = _statefulService->updateWithoutPropagation(jsonObject, _stateUpdater); + + if (outcome == StateUpdateResult::ERROR) { + return request->reply(400); + } else if ((outcome == StateUpdateResult::CHANGED) || (outcome == StateUpdateResult::CHANGED_RESTART)) { + // persist the changes to the FS + _statefulService->callUpdateHandlers(HTTP_ENDPOINT_ORIGIN_ID); + } + + PsychicJsonResponse response = PsychicJsonResponse(request, false, _bufferSize); + jsonObject = response.getRoot(); + + _statefulService->read(jsonObject, _stateReader); + + if (outcome == StateUpdateResult::CHANGED_RESTART) { + response.setCode(205); // reboot required + } + + return response.send(); + }, + _authenticationPredicate)); } }; diff --git a/lib/framework/MqttSettingsService.cpp b/lib/framework/MqttSettingsService.cpp index eb10ef718..7ec8aade7 100644 --- a/lib/framework/MqttSettingsService.cpp +++ b/lib/framework/MqttSettingsService.cpp @@ -24,8 +24,10 @@ static char * retainCstr(const char * cstr, char ** ptr) { return *ptr; } -MqttSettingsService::MqttSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager) - : _httpEndpoint(MqttSettings::read, MqttSettings::update, this, server, MQTT_SETTINGS_SERVICE_PATH, securityManager) +MqttSettingsService::MqttSettingsService(PsychicHttpServer * server, FS * fs, SecurityManager * securityManager) + : _server(server) + , _securityManager(securityManager) + , _httpEndpoint(MqttSettings::read, MqttSettings::update, this, server, MQTT_SETTINGS_SERVICE_PATH, securityManager) , _fsPersistence(MqttSettings::read, MqttSettings::update, this, fs, MQTT_SETTINGS_FILE) , _retainedHost(nullptr) , _retainedClientId(nullptr) @@ -47,6 +49,10 @@ void MqttSettingsService::begin() { startClient(); } +void MqttSettingsService::registerURI() { + _httpEndpoint.registerURI(); +} + void MqttSettingsService::startClient() { static bool isSecure = false; if (_mqttClient != nullptr) { @@ -218,8 +224,10 @@ bool MqttSettingsService::configureMqtt() { void MqttSettings::read(MqttSettings & settings, JsonObject & root) { #if CONFIG_IDF_TARGET_ESP32S3 +#ifndef TASMOTA_SDK root["enableTLS"] = settings.enableTLS; root["rootCA"] = settings.rootCA; +#endif #endif root["enabled"] = settings.enabled; root["host"] = settings.host; @@ -255,8 +263,10 @@ StateUpdateResult MqttSettings::update(JsonObject & root, MqttSettings & setting bool changed = false; #if CONFIG_IDF_TARGET_ESP32S3 +#ifndef TASMOTA_SDK newSettings.enableTLS = root["enableTLS"] | false; newSettings.rootCA = root["rootCA"] | ""; +#endif #endif newSettings.enabled = root["enabled"] | FACTORY_MQTT_ENABLED; newSettings.host = root["host"] | FACTORY_MQTT_HOST; diff --git a/lib/framework/MqttSettingsService.h b/lib/framework/MqttSettingsService.h index e2541dcda..c374a1e0a 100644 --- a/lib/framework/MqttSettingsService.h +++ b/lib/framework/MqttSettingsService.h @@ -8,6 +8,8 @@ #include #include +#include +#include #define MQTT_RECONNECTION_DELAY 2000 // 2 seconds @@ -103,12 +105,14 @@ class MqttSettings { class MqttSettingsService : public StatefulService { public: - MqttSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager); + MqttSettingsService(PsychicHttpServer * server, FS * fs, SecurityManager * securityManager); ~MqttSettingsService(); - void begin(); - void startClient(); - void loop(); + void begin(); + void startClient(); + void loop(); + void registerURI(); + bool isEnabled(); bool isConnected(); const char * getClientId(); @@ -120,6 +124,9 @@ class MqttSettingsService : public StatefulService { void onConfigUpdated(); private: + SecurityManager * _securityManager; + PsychicHttpServer * _server; + HttpEndpoint _httpEndpoint; FSPersistence _fsPersistence; diff --git a/lib/framework/MqttStatus.cpp b/lib/framework/MqttStatus.cpp index 2a12ac16f..b171e9e97 100644 --- a/lib/framework/MqttStatus.cpp +++ b/lib/framework/MqttStatus.cpp @@ -4,16 +4,21 @@ using namespace std::placeholders; // for `_1` etc -MqttStatus::MqttStatus(AsyncWebServer * server, MqttSettingsService * mqttSettingsService, SecurityManager * securityManager) - : _mqttSettingsService(mqttSettingsService) { - server->on(MQTT_STATUS_SERVICE_PATH, - HTTP_GET, - securityManager->wrapRequest(std::bind(&MqttStatus::mqttStatus, this, _1), AuthenticationPredicates::IS_AUTHENTICATED)); +MqttStatus::MqttStatus(PsychicHttpServer * server, MqttSettingsService * mqttSettingsService, SecurityManager * securityManager) + : _mqttSettingsService(mqttSettingsService) + , _server(server) + , _securityManager(securityManager) { } -void MqttStatus::mqttStatus(AsyncWebServerRequest * request) { - AsyncJsonResponse * response = new AsyncJsonResponse(false, MAX_MQTT_STATUS_SIZE); - JsonObject root = response->getRoot(); +void MqttStatus::registerURI() { + _server->on(MQTT_STATUS_SERVICE_PATH, + HTTP_GET, + _securityManager->wrapRequest(std::bind(&MqttStatus::mqttStatus, this, _1), AuthenticationPredicates::IS_AUTHENTICATED)); +} + +esp_err_t MqttStatus::mqttStatus(PsychicRequest * request) { + PsychicJsonResponse response = PsychicJsonResponse(request, false, MAX_MQTT_STATUS_SIZE); + JsonObject root = response.getRoot(); root["enabled"] = _mqttSettingsService->isEnabled(); root["connected"] = _mqttSettingsService->isConnected(); @@ -24,6 +29,5 @@ void MqttStatus::mqttStatus(AsyncWebServerRequest * request) { root["mqtt_fails"] = emsesp::Mqtt::publish_fails(); root["connect_count"] = emsesp::Mqtt::connect_count(); - response->setLength(); - request->send(response); + return response.send(); } diff --git a/lib/framework/MqttStatus.h b/lib/framework/MqttStatus.h index cb4b2172a..bae505fd2 100644 --- a/lib/framework/MqttStatus.h +++ b/lib/framework/MqttStatus.h @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include #define MAX_MQTT_STATUS_SIZE 1024 @@ -12,12 +12,16 @@ class MqttStatus { public: - MqttStatus(AsyncWebServer * server, MqttSettingsService * mqttSettingsService, SecurityManager * securityManager); + MqttStatus(PsychicHttpServer * server, MqttSettingsService * mqttSettingsService, SecurityManager * securityManager); + void registerURI(); private: + SecurityManager * _securityManager; + PsychicHttpServer * _server; + MqttSettingsService * _mqttSettingsService; - void mqttStatus(AsyncWebServerRequest * request); + esp_err_t mqttStatus(PsychicRequest * request); }; #endif diff --git a/lib/framework/NTPSettingsService.cpp b/lib/framework/NTPSettingsService.cpp index 751de6751..13e70ead9 100644 --- a/lib/framework/NTPSettingsService.cpp +++ b/lib/framework/NTPSettingsService.cpp @@ -4,16 +4,12 @@ using namespace std::placeholders; // for `_1` etc -NTPSettingsService::NTPSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager) - : _httpEndpoint(NTPSettings::read, NTPSettings::update, this, server, NTP_SETTINGS_SERVICE_PATH, securityManager) - , _fsPersistence(NTPSettings::read, NTPSettings::update, this, fs, NTP_SETTINGS_FILE) - , _timeHandler(TIME_PATH, securityManager->wrapCallback(std::bind(&NTPSettingsService::configureTime, this, _1, _2), AuthenticationPredicates::IS_ADMIN)) { - _timeHandler.setMethod(HTTP_POST); - _timeHandler.setMaxContentLength(MAX_TIME_SIZE); - server->addHandler(&_timeHandler); - +NTPSettingsService::NTPSettingsService(PsychicHttpServer * server, FS * fs, SecurityManager * securityManager) + : _server(server) + , _securityManager(securityManager) + , _httpEndpoint(NTPSettings::read, NTPSettings::update, this, server, NTP_SETTINGS_SERVICE_PATH, securityManager) + , _fsPersistence(NTPSettings::read, NTPSettings::update, this, fs, NTP_SETTINGS_FILE) { WiFi.onEvent(std::bind(&NTPSettingsService::WiFiEvent, this, _1)); - addUpdateHandler([&](const String & originId) { configureNTP(); }, false); } @@ -22,6 +18,14 @@ void NTPSettingsService::begin() { configureNTP(); } +void NTPSettingsService::registerURI() { + _httpEndpoint.registerURI(); + + _server->on(TIME_PATH, + HTTP_POST, + _securityManager->wrapCallback(std::bind(&NTPSettingsService::configureTime, this, _1, _2), AuthenticationPredicates::IS_ADMIN)); +} + // handles both WiFI and Ethernet void NTPSettingsService::WiFiEvent(WiFiEvent_t event) { switch (event) { @@ -61,7 +65,7 @@ void NTPSettingsService::configureNTP() { } } -void NTPSettingsService::configureTime(AsyncWebServerRequest * request, JsonVariant & json) { +esp_err_t NTPSettingsService::configureTime(PsychicRequest * request, JsonVariant & json) { if (json.is()) { struct tm tm = {0}; String timeLocal = json["local_time"]; @@ -71,14 +75,11 @@ void NTPSettingsService::configureTime(AsyncWebServerRequest * request, JsonVari time_t time = mktime(&tm); struct timeval now = {.tv_sec = time}; settimeofday(&now, nullptr); - AsyncWebServerResponse * response = request->beginResponse(200); - request->send(response); - return; + return request->reply(200); } } - AsyncWebServerResponse * response = request->beginResponse(400); - request->send(response); + return request->reply(400); } void NTPSettingsService::ntp_received(struct timeval * tv) { diff --git a/lib/framework/NTPSettingsService.h b/lib/framework/NTPSettingsService.h index f26273ecf..8f331d310 100644 --- a/lib/framework/NTPSettingsService.h +++ b/lib/framework/NTPSettingsService.h @@ -1,6 +1,8 @@ #ifndef NTPSettingsService_h #define NTPSettingsService_h +#include + #include #include @@ -26,7 +28,6 @@ #define NTP_SETTINGS_FILE "/config/ntpSettings.json" #define NTP_SETTINGS_SERVICE_PATH "/rest/ntpSettings" -#define MAX_TIME_SIZE 256 #define TIME_PATH "/rest/time" class NTPSettings { @@ -54,20 +55,26 @@ class NTPSettings { class NTPSettingsService : public StatefulService { public: - NTPSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager); + NTPSettingsService(PsychicHttpServer * server, FS * fs, SecurityManager * securityManager); + + void begin(); + void registerURI(); - void begin(); static void ntp_received(struct timeval * tv); private: - HttpEndpoint _httpEndpoint; - FSPersistence _fsPersistence; - AsyncCallbackJsonWebHandler _timeHandler; + SecurityManager * _securityManager; + PsychicHttpServer * _server; + + HttpEndpoint _httpEndpoint; + FSPersistence _fsPersistence; bool connected_ = false; void WiFiEvent(WiFiEvent_t event); void configureNTP(); - void configureTime(AsyncWebServerRequest * request, JsonVariant & json); + + // POST + esp_err_t configureTime(PsychicRequest * request, JsonVariant & json); }; #endif diff --git a/lib/framework/NTPStatus.cpp b/lib/framework/NTPStatus.cpp index efe18c53f..b9a14ffff 100644 --- a/lib/framework/NTPStatus.cpp +++ b/lib/framework/NTPStatus.cpp @@ -3,10 +3,16 @@ using namespace std::placeholders; // for `_1` etc -NTPStatus::NTPStatus(AsyncWebServer * server, SecurityManager * securityManager) { - server->on(NTP_STATUS_SERVICE_PATH, - HTTP_GET, - securityManager->wrapRequest(std::bind(&NTPStatus::ntpStatus, this, _1), AuthenticationPredicates::IS_AUTHENTICATED)); +NTPStatus::NTPStatus(PsychicHttpServer * server, SecurityManager * securityManager) + : _server(server) + , _securityManager(securityManager) { +} + + +void NTPStatus::registerURI() { + _server->on(NTP_STATUS_SERVICE_PATH, + HTTP_GET, + _securityManager->wrapRequest(std::bind(&NTPStatus::ntpStatus, this, _1), AuthenticationPredicates::IS_AUTHENTICATED)); } /* @@ -28,9 +34,9 @@ String toLocalTimeString(tm * time) { return formatTime(time, "%FT%T"); } -void NTPStatus::ntpStatus(AsyncWebServerRequest * request) { - AsyncJsonResponse * response = new AsyncJsonResponse(false, MAX_NTP_STATUS_SIZE); - JsonObject root = response->getRoot(); +esp_err_t NTPStatus::ntpStatus(PsychicRequest * request) { + PsychicJsonResponse response = PsychicJsonResponse(request, false, MAX_NTP_STATUS_SIZE); + JsonObject root = response.getRoot(); // grab the current instant in unix seconds time_t now = time(nullptr); @@ -47,6 +53,5 @@ void NTPStatus::ntpStatus(AsyncWebServerRequest * request) { // the sntp server name root["server"] = esp_sntp_getservername(0); - response->setLength(); - request->send(response); + return response.send(); } diff --git a/lib/framework/NTPStatus.h b/lib/framework/NTPStatus.h index 0ebe80495..a1e78ebe0 100644 --- a/lib/framework/NTPStatus.h +++ b/lib/framework/NTPStatus.h @@ -7,7 +7,7 @@ #include #include -#include +#include #include #include @@ -16,10 +16,14 @@ class NTPStatus { public: - NTPStatus(AsyncWebServer * server, SecurityManager * securityManager); + NTPStatus(PsychicHttpServer * server, SecurityManager * securityManager); + void registerURI(); private: - void ntpStatus(AsyncWebServerRequest * request); + SecurityManager * _securityManager; + PsychicHttpServer * _server; + + esp_err_t ntpStatus(PsychicRequest * request); }; #endif diff --git a/lib/framework/NetworkSettingsService.cpp b/lib/framework/NetworkSettingsService.cpp index ad2cf0e9c..962384df3 100644 --- a/lib/framework/NetworkSettingsService.cpp +++ b/lib/framework/NetworkSettingsService.cpp @@ -2,7 +2,7 @@ using namespace std::placeholders; // for `_1` etc -NetworkSettingsService::NetworkSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager) +NetworkSettingsService::NetworkSettingsService(PsychicHttpServer * server, FS * fs, SecurityManager * securityManager) : _httpEndpoint(NetworkSettings::read, NetworkSettings::update, this, server, NETWORK_SETTINGS_SERVICE_PATH, securityManager) , _fsPersistence(NetworkSettings::read, NetworkSettings::update, this, fs, NETWORK_SETTINGS_FILE) , _lastConnectionAttempt(0) { @@ -13,24 +13,28 @@ NetworkSettingsService::NetworkSettingsService(AsyncWebServer * server, FS * fs, void NetworkSettingsService::begin() { // We want the device to come up in opmode=0 (WIFI_OFF), when erasing the flash this is not the default. // If needed, we save opmode=0 before disabling persistence so the device boots with WiFi disabled in the future. - if (WiFi.getMode() != WIFI_OFF) { - WiFi.mode(WIFI_OFF); - } + // if (WiFi.getMode() != WIFI_OFF) { + // WiFi.mode(WIFI_OFF); + // } - // Disable WiFi config persistance and auto reconnect + // WiFi.useStaticBuffers(true); // uses 40kb more heap and max alloc, so not recommended WiFi.persistent(false); WiFi.setAutoReconnect(false); WiFi.mode(WIFI_MODE_MAX); WiFi.mode(WIFI_MODE_NULL); + // WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN); // default is FAST_SCAN, connect issues in 2.0.14 // WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL); // is default, no need to set - _fsPersistence.readFromFS(); // reconfigureWiFiConnection(); } +void NetworkSettingsService::registerURI() { + _httpEndpoint.registerURI(); +} + void NetworkSettingsService::reconfigureWiFiConnection() { // do not disconnect for switching to eth, restart is needed if (WiFi.isConnected() && _state.ssid.length() == 0) { diff --git a/lib/framework/NetworkSettingsService.h b/lib/framework/NetworkSettingsService.h index 901345ea9..4c6a58332 100644 --- a/lib/framework/NetworkSettingsService.h +++ b/lib/framework/NetworkSettingsService.h @@ -29,7 +29,7 @@ class NetworkSettings { public: - // core wifi configuration + // core network configuration String ssid; String bssid; String password; @@ -119,12 +119,15 @@ class NetworkSettings { class NetworkSettingsService : public StatefulService { public: - NetworkSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager); + NetworkSettingsService(PsychicHttpServer * server, FS * fs, SecurityManager * securityManager); void begin(); void loop(); + void registerURI(); private: + SecurityManager * _securityManager; + PsychicHttpServer * _server; HttpEndpoint _httpEndpoint; FSPersistence _fsPersistence; unsigned long _lastConnectionAttempt; diff --git a/lib/framework/NetworkStatus.cpp b/lib/framework/NetworkStatus.cpp index 6aea4580f..7402965cb 100644 --- a/lib/framework/NetworkStatus.cpp +++ b/lib/framework/NetworkStatus.cpp @@ -4,15 +4,20 @@ using namespace std::placeholders; // for `_1` etc -NetworkStatus::NetworkStatus(AsyncWebServer * server, SecurityManager * securityManager) { - server->on(NETWORK_STATUS_SERVICE_PATH, - HTTP_GET, - securityManager->wrapRequest(std::bind(&NetworkStatus::networkStatus, this, _1), AuthenticationPredicates::IS_AUTHENTICATED)); +NetworkStatus::NetworkStatus(PsychicHttpServer * server, SecurityManager * securityManager) + : _server(server) + , _securityManager(securityManager) { } -void NetworkStatus::networkStatus(AsyncWebServerRequest * request) { - AsyncJsonResponse * response = new AsyncJsonResponse(false, MAX_NETWORK_STATUS_SIZE); - JsonObject root = response->getRoot(); +void NetworkStatus::registerURI() { + _server->on(NETWORK_STATUS_SERVICE_PATH, + HTTP_GET, + _securityManager->wrapRequest(std::bind(&NetworkStatus::networkStatus, this, _1), AuthenticationPredicates::IS_AUTHENTICATED)); +} + +esp_err_t NetworkStatus::networkStatus(PsychicRequest * request) { + PsychicJsonResponse response = PsychicJsonResponse(request, false, MAX_NETWORK_STATUS_SIZE); + JsonObject root = response.getRoot(); bool ethernet_connected = emsesp::EMSESP::system_.ethernet_connected(); wl_status_t wifi_status = WiFi.status(); @@ -64,6 +69,5 @@ void NetworkStatus::networkStatus(AsyncWebServerRequest * request) { } } - response->setLength(); - request->send(response); + return response.send(); } diff --git a/lib/framework/NetworkStatus.h b/lib/framework/NetworkStatus.h index 1e73439e9..cf28230f8 100644 --- a/lib/framework/NetworkStatus.h +++ b/lib/framework/NetworkStatus.h @@ -2,12 +2,11 @@ #define NetworkStatus_h #include -#include #include #include -#include +#include #include #include #include @@ -17,10 +16,14 @@ class NetworkStatus { public: - NetworkStatus(AsyncWebServer * server, SecurityManager * securityManager); + NetworkStatus(PsychicHttpServer * server, SecurityManager * securityManager); + void registerURI(); private: - void networkStatus(AsyncWebServerRequest * request); + SecurityManager * _securityManager; + PsychicHttpServer * _server; + + esp_err_t networkStatus(PsychicRequest * request); }; #endif diff --git a/lib/framework/OTASettingsService.cpp b/lib/framework/OTASettingsService.cpp index c20f4cadd..bd28f65ad 100644 --- a/lib/framework/OTASettingsService.cpp +++ b/lib/framework/OTASettingsService.cpp @@ -4,8 +4,10 @@ using namespace std::placeholders; // for `_1` etc -OTASettingsService::OTASettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager) - : _httpEndpoint(OTASettings::read, OTASettings::update, this, server, OTA_SETTINGS_SERVICE_PATH, securityManager) +OTASettingsService::OTASettingsService(PsychicHttpServer * server, FS * fs, SecurityManager * securityManager) + : _server(server) + , _securityManager(securityManager) + , _httpEndpoint(OTASettings::read, OTASettings::update, this, server, OTA_SETTINGS_SERVICE_PATH, securityManager) , _fsPersistence(OTASettings::read, OTASettings::update, this, fs, OTA_SETTINGS_FILE) , _arduinoOTA(nullptr) { WiFi.onEvent(std::bind(&OTASettingsService::WiFiEvent, this, _1, _2)); @@ -17,6 +19,10 @@ void OTASettingsService::begin() { configureArduinoOTA(); } +void OTASettingsService::registerURI() { + _httpEndpoint.registerURI(); +} + void OTASettingsService::loop() { if (_state.enabled && _arduinoOTA) { _arduinoOTA->handle(); diff --git a/lib/framework/OTASettingsService.h b/lib/framework/OTASettingsService.h index 2594f98db..fffa45263 100644 --- a/lib/framework/OTASettingsService.h +++ b/lib/framework/OTASettingsService.h @@ -44,15 +44,19 @@ class OTASettings { class OTASettingsService : public StatefulService { public: - OTASettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager); + OTASettingsService(PsychicHttpServer * server, FS * fs, SecurityManager * securityManager); void begin(); void loop(); + void registerURI(); private: + SecurityManager * _securityManager; + PsychicHttpServer * _server; HttpEndpoint _httpEndpoint; FSPersistence _fsPersistence; - ArduinoOTAClass * _arduinoOTA; + + ArduinoOTAClass * _arduinoOTA; void configureArduinoOTA(); void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info); diff --git a/lib/framework/RestartService.cpp b/lib/framework/RestartService.cpp index b767b9cf0..cde84cf83 100644 --- a/lib/framework/RestartService.cpp +++ b/lib/framework/RestartService.cpp @@ -5,41 +5,49 @@ using namespace std::placeholders; // for `_1` etc -RestartService::RestartService(AsyncWebServer * server, SecurityManager * securityManager) { - server->on(RESTART_SERVICE_PATH, HTTP_POST, securityManager->wrapRequest(std::bind(&RestartService::restart, this, _1), AuthenticationPredicates::IS_ADMIN)); - server->on(PARTITION_SERVICE_PATH, - HTTP_POST, - securityManager->wrapRequest(std::bind(&RestartService::partition, this, _1), AuthenticationPredicates::IS_ADMIN)); +RestartService::RestartService(PsychicHttpServer * server, SecurityManager * securityManager) + : _server(server) + , _securityManager(securityManager) { } -void RestartService::restart(AsyncWebServerRequest * request) { +void RestartService::registerURI() { + _server->on(RESTART_SERVICE_PATH, HTTP_POST, _securityManager->wrapRequest(std::bind(&RestartService::restart, this, _1), AuthenticationPredicates::IS_ADMIN)); + _server->on(PARTITION_SERVICE_PATH, + HTTP_POST, + _securityManager->wrapRequest(std::bind(&RestartService::partition, this, _1), AuthenticationPredicates::IS_ADMIN)); +} + +esp_err_t RestartService::restart(PsychicRequest * request) { emsesp::EMSESP::system_.store_nvs_values(); - request->onDisconnect(RestartService::restartNow); - request->send(200); + request->reply(200); + restartNow(); + return ESP_OK; } -void RestartService::partition(AsyncWebServerRequest * request) { +esp_err_t RestartService::partition(PsychicRequest * request) { const esp_partition_t * factory_partition = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_FACTORY, NULL); if (factory_partition) { esp_ota_set_boot_partition(factory_partition); emsesp::EMSESP::system_.store_nvs_values(); - request->onDisconnect(RestartService::restartNow); - request->send(200); - return; + request->reply(200); + restartNow(); + return ESP_OK; } + const esp_partition_t * ota_partition = esp_ota_get_next_update_partition(NULL); if (!ota_partition) { - request->send(400); // bad request - return; + return request->reply(400); // bad request } + uint64_t buffer; esp_partition_read(ota_partition, 0, &buffer, 8); if (buffer == 0xFFFFFFFFFFFFFFFF) { // partition empty - request->send(400); // bad request - return; + return request->reply(400); // bad request } + esp_ota_set_boot_partition(ota_partition); emsesp::EMSESP::system_.store_nvs_values(); - request->onDisconnect(RestartService::restartNow); - request->send(200); + request->reply(200); + restartNow(); + return ESP_OK; } diff --git a/lib/framework/RestartService.h b/lib/framework/RestartService.h index 8a010716f..b9ad39bc0 100644 --- a/lib/framework/RestartService.h +++ b/lib/framework/RestartService.h @@ -2,9 +2,8 @@ #define RestartService_h #include -#include -#include +#include #include #define RESTART_SERVICE_PATH "/rest/restart" @@ -12,17 +11,21 @@ class RestartService { public: - RestartService(AsyncWebServer * server, SecurityManager * securityManager); + RestartService(PsychicHttpServer * server, SecurityManager * securityManager); + + void registerURI(); static void restartNow() { - WiFi.disconnect(true); - delay(500); + delay(500); // wait for async tcp to catch up ESP.restart(); } private: - void restart(AsyncWebServerRequest * request); - void partition(AsyncWebServerRequest * request); + SecurityManager * _securityManager; + PsychicHttpServer * _server; + + esp_err_t restart(PsychicRequest * request); + esp_err_t partition(PsychicRequest * request); }; #endif diff --git a/lib/framework/SecurityManager.h b/lib/framework/SecurityManager.h index 4d364253c..b3b41cf0b 100644 --- a/lib/framework/SecurityManager.h +++ b/lib/framework/SecurityManager.h @@ -3,9 +3,8 @@ #include #include -#include +#include #include -#include #include #ifndef FACTORY_JWT_SECRET @@ -70,7 +69,6 @@ class AuthenticationPredicates { class SecurityManager { public: -#if FT_ENABLED(FT_SECURITY) /* * Authenticate, returning the user if found */ @@ -81,27 +79,25 @@ class SecurityManager { */ virtual String generateJWT(User * user) = 0; -#endif - /* * Check the request header for the Authorization token */ - virtual Authentication authenticateRequest(AsyncWebServerRequest * request) = 0; + virtual Authentication authenticateRequest(PsychicRequest * request) = 0; /** * Filter a request with the provided predicate, only returning true if the predicate matches. */ - virtual ArRequestFilterFunction filterRequest(AuthenticationPredicate predicate) = 0; + virtual PsychicRequestFilterFunction filterRequest(AuthenticationPredicate predicate) = 0; /** * Wrap the provided request to provide validation against an AuthenticationPredicate. */ - virtual ArRequestHandlerFunction wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate) = 0; + virtual PsychicHttpRequestCallback wrapRequest(PsychicHttpRequestCallback onRequest, AuthenticationPredicate predicate) = 0; /** * Wrap the provided json request callback to provide validation against an AuthenticationPredicate. */ - virtual ArJsonRequestHandlerFunction wrapCallback(ArJsonRequestHandlerFunction onRequest, AuthenticationPredicate predicate) = 0; + virtual PsychicJsonRequestCallback wrapCallback(PsychicJsonRequestCallback onRequest, AuthenticationPredicate predicate) = 0; }; #endif diff --git a/lib/framework/SecuritySettingsService.cpp b/lib/framework/SecuritySettingsService.cpp index 5427a2fa8..79db256dc 100644 --- a/lib/framework/SecuritySettingsService.cpp +++ b/lib/framework/SecuritySettingsService.cpp @@ -1,17 +1,13 @@ #include -#if FT_ENABLED(FT_SECURITY) - #include "../../src/emsesp_stub.hpp" -SecuritySettingsService::SecuritySettingsService(AsyncWebServer * server, FS * fs) - : _httpEndpoint(SecuritySettings::read, SecuritySettings::update, this, server, SECURITY_SETTINGS_PATH, this) +SecuritySettingsService::SecuritySettingsService(PsychicHttpServer * server, FS * fs) + : _server(server) + , _httpEndpoint(SecuritySettings::read, SecuritySettings::update, this, server, SECURITY_SETTINGS_PATH, this) , _fsPersistence(SecuritySettings::read, SecuritySettings::update, this, fs, SECURITY_SETTINGS_FILE) , _jwtHandler(FACTORY_JWT_SECRET) { addUpdateHandler([&](const String & originId) { configureJWTHandler(); }, false); - server->on(GENERATE_TOKEN_PATH, - HTTP_GET, - wrapRequest(std::bind(&SecuritySettingsService::generateToken, this, std::placeholders::_1), AuthenticationPredicates::IS_ADMIN)); } void SecuritySettingsService::begin() { @@ -19,17 +15,20 @@ void SecuritySettingsService::begin() { configureJWTHandler(); } -Authentication SecuritySettingsService::authenticateRequest(AsyncWebServerRequest * request) { - AsyncWebHeader * authorizationHeader = request->getHeader(AUTHORIZATION_HEADER); - if (authorizationHeader) { - String value = authorizationHeader->value(); +void SecuritySettingsService::registerURI() { + _httpEndpoint.registerURI(); + _server->on(GENERATE_TOKEN_PATH, HTTP_GET, wrapRequest(std::bind(&SecuritySettingsService::generateToken, this, _1), AuthenticationPredicates::IS_ADMIN)); +} + +Authentication SecuritySettingsService::authenticateRequest(PsychicRequest * request) { + if (request->hasHeader(AUTHORIZATION_HEADER)) { + auto value = request->header(AUTHORIZATION_HEADER); if (value.startsWith(AUTHORIZATION_HEADER_PREFIX)) { value = value.substring(AUTHORIZATION_HEADER_PREFIX_LEN); return authenticateJWT(value); } } else if (request->hasParam(ACCESS_TOKEN_PARAMATER)) { - AsyncWebParameter * tokenParamater = request->getParam(ACCESS_TOKEN_PARAMATER); - String value = tokenParamater->value(); + String value = request->getParam(ACCESS_TOKEN_PARAMATER)->value(); return authenticateJWT(value); } return Authentication(); @@ -82,76 +81,42 @@ String SecuritySettingsService::generateJWT(User * user) { return _jwtHandler.buildJWT(payload); } -ArRequestFilterFunction SecuritySettingsService::filterRequest(AuthenticationPredicate predicate) { - return [this, predicate](AsyncWebServerRequest * request) { +PsychicRequestFilterFunction SecuritySettingsService::filterRequest(AuthenticationPredicate predicate) { + return [this, predicate](PsychicRequest * request) { Authentication authentication = authenticateRequest(request); return predicate(authentication); }; } -ArRequestHandlerFunction SecuritySettingsService::wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate) { - return [this, onRequest, predicate](AsyncWebServerRequest * request) { +PsychicHttpRequestCallback SecuritySettingsService::wrapRequest(PsychicHttpRequestCallback onRequest, AuthenticationPredicate predicate) { + return [this, onRequest, predicate](PsychicRequest * request) { Authentication authentication = authenticateRequest(request); if (!predicate(authentication)) { - request->send(401); - return; + return request->reply(401); } - onRequest(request); + return onRequest(request); }; } -ArJsonRequestHandlerFunction SecuritySettingsService::wrapCallback(ArJsonRequestHandlerFunction onRequest, AuthenticationPredicate predicate) { - return [this, onRequest, predicate](AsyncWebServerRequest * request, JsonVariant & json) { +PsychicJsonRequestCallback SecuritySettingsService::wrapCallback(PsychicJsonRequestCallback onRequest, AuthenticationPredicate predicate) { + return [this, onRequest, predicate](PsychicRequest * request, JsonVariant & json) { Authentication authentication = authenticateRequest(request); if (!predicate(authentication)) { - request->send(401); - return; + return request->reply(401); } - onRequest(request, json); + return onRequest(request, json); }; } -void SecuritySettingsService::generateToken(AsyncWebServerRequest * request) { - AsyncWebParameter * usernameParam = request->getParam("username"); +esp_err_t SecuritySettingsService::generateToken(PsychicRequest * request) { + String usernameParam = request->getParam("username")->value(); for (User _user : _state.users) { - if (_user.username == usernameParam->value()) { - AsyncJsonResponse * response = new AsyncJsonResponse(false, GENERATE_TOKEN_SIZE); - JsonObject root = response->getRoot(); + if (_user.username == usernameParam) { + PsychicJsonResponse response = PsychicJsonResponse(request, false, GENERATE_TOKEN_SIZE); + JsonObject root = response.getRoot(); root["token"] = generateJWT(&_user); - response->setLength(); - request->send(response); - return; + return response.send(); } } - request->send(401); + return request->reply(401); } - -#else - -User ADMIN_USER = User(FACTORY_ADMIN_USERNAME, FACTORY_ADMIN_PASSWORD, true); - -SecuritySettingsService::SecuritySettingsService(AsyncWebServer * server, FS * fs) - : SecurityManager() { -} -SecuritySettingsService::~SecuritySettingsService() { -} - -ArRequestFilterFunction SecuritySettingsService::filterRequest(AuthenticationPredicate predicate) { - return [this, predicate](AsyncWebServerRequest * request) { return true; }; -} - -// Return the admin user on all request - disabling security features -Authentication SecuritySettingsService::authenticateRequest(AsyncWebServerRequest * request) { - return Authentication(ADMIN_USER); -} - -// Return the function unwrapped -ArRequestHandlerFunction SecuritySettingsService::wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate) { - return onRequest; -} - -ArJsonRequestHandlerFunction SecuritySettingsService::wrapCallback(ArJsonRequestHandlerFunction onRequest, AuthenticationPredicate predicate) { - return onRequest; -} - -#endif diff --git a/lib/framework/SecuritySettingsService.h b/lib/framework/SecuritySettingsService.h index cdbd8d085..f84565636 100644 --- a/lib/framework/SecuritySettingsService.h +++ b/lib/framework/SecuritySettingsService.h @@ -28,8 +28,6 @@ #define GENERATE_TOKEN_SIZE 512 #define GENERATE_TOKEN_PATH "/rest/generateToken" -#if FT_ENABLED(FT_SECURITY) - class SecuritySettings { public: String jwtSecret; @@ -69,51 +67,33 @@ class SecuritySettings { class SecuritySettingsService : public StatefulService, public SecurityManager { public: - SecuritySettingsService(AsyncWebServer * server, FS * fs); + SecuritySettingsService(PsychicHttpServer * server, FS * fs); void begin(); + void registerURI(); // Functions to implement SecurityManager - Authentication authenticate(const String & username, const String & password); - Authentication authenticateRequest(AsyncWebServerRequest * request); - String generateJWT(User * user); - ArRequestFilterFunction filterRequest(AuthenticationPredicate predicate); - ArRequestHandlerFunction wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate); - ArJsonRequestHandlerFunction wrapCallback(ArJsonRequestHandlerFunction callback, AuthenticationPredicate predicate); + Authentication authenticate(const String & username, const String & password); + Authentication authenticateRequest(PsychicRequest * request); + String generateJWT(User * user); + + PsychicRequestFilterFunction filterRequest(AuthenticationPredicate predicate); + PsychicHttpRequestCallback wrapRequest(PsychicHttpRequestCallback onRequest, AuthenticationPredicate predicate); + PsychicJsonRequestCallback wrapCallback(PsychicJsonRequestCallback onRequest, AuthenticationPredicate predicate); private: + PsychicHttpServer * _server; + HttpEndpoint _httpEndpoint; FSPersistence _fsPersistence; ArduinoJsonJWT _jwtHandler; - void generateToken(AsyncWebServerRequest * request); + esp_err_t generateToken(PsychicRequest * request); void configureJWTHandler(); - /* - * Lookup the user by JWT - */ - Authentication authenticateJWT(String & jwt); - - /* - * Verify the payload is correct - */ - boolean validatePayload(JsonObject & parsedPayload, User * user); -}; - -#else - -class SecuritySettingsService : public SecurityManager { - public: - SecuritySettingsService(AsyncWebServer * server, FS * fs); - ~SecuritySettingsService(); - - // minimal set of functions to support framework with security settings disabled - Authentication authenticateRequest(AsyncWebServerRequest * request); - ArRequestFilterFunction filterRequest(AuthenticationPredicate predicate); - ArRequestHandlerFunction wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate); - ArJsonRequestHandlerFunction wrapCallback(ArJsonRequestHandlerFunction onRequest, AuthenticationPredicate predicate); + Authentication authenticateJWT(String & jwt); // Lookup the user by JWT + boolean validatePayload(JsonObject & parsedPayload, User * user); // Verify the payload is correct }; #endif -#endif diff --git a/lib/framework/SystemStatus.cpp b/lib/framework/SystemStatus.cpp index 10c7a7856..989bd9040 100644 --- a/lib/framework/SystemStatus.cpp +++ b/lib/framework/SystemStatus.cpp @@ -5,24 +5,34 @@ using namespace std::placeholders; // for `_1` etc -SystemStatus::SystemStatus(AsyncWebServer * server, SecurityManager * securityManager) { - server->on(SYSTEM_STATUS_SERVICE_PATH, - HTTP_GET, - securityManager->wrapRequest(std::bind(&SystemStatus::systemStatus, this, _1), AuthenticationPredicates::IS_AUTHENTICATED)); +SystemStatus::SystemStatus(PsychicHttpServer * server, SecurityManager * securityManager) + : _server(server) + , _securityManager(securityManager) { } -void SystemStatus::systemStatus(AsyncWebServerRequest * request) { +void SystemStatus::registerURI() { emsesp::EMSESP::system_.refreshHeapMem(); // refresh free heap and max alloc heap - AsyncJsonResponse * response = new AsyncJsonResponse(false, MAX_ESP_STATUS_SIZE); - JsonObject root = response->getRoot(); + _server->on(SYSTEM_STATUS_SERVICE_PATH, + HTTP_GET, + _securityManager->wrapRequest(std::bind(&SystemStatus::systemStatus, this, _1), AuthenticationPredicates::IS_AUTHENTICATED)); +} + +esp_err_t SystemStatus::systemStatus(PsychicRequest * request) { + PsychicJsonResponse response = PsychicJsonResponse(request, false, MAX_ESP_STATUS_SIZE); + JsonObject root = response.getRoot(); root["emsesp_version"] = EMSESP_APP_VERSION; root["esp_platform"] = EMSESP_PLATFORM; + root["cpu_type"] = ESP.getChipModel(); + root["cpu_rev"] = ESP.getChipRevision(); + root["cpu_cores"] = ESP.getChipCores(); root["cpu_freq_mhz"] = ESP.getCpuFreqMHz(); root["max_alloc_heap"] = emsesp::EMSESP::system_.getMaxAllocMem(); root["free_heap"] = emsesp::EMSESP::system_.getHeapMem(); + root["arduino_version"] = ARDUINO_VERSION; root["sdk_version"] = ESP.getSdkVersion(); + root["partition"] = esp_ota_get_running_partition()->label; root["flash_chip_size"] = ESP.getFlashChipSize() / 1024; root["flash_chip_speed"] = ESP.getFlashChipSpeed(); root["app_used"] = emsesp::EMSESP::system_.appUsed(); @@ -36,10 +46,11 @@ void SystemStatus::systemStatus(AsyncWebServerRequest * request) { root["psram_size"] = emsesp::EMSESP::system_.PSram(); root["free_psram"] = ESP.getFreePsram() / 1024; } + const esp_partition_t * partition = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_FACTORY, NULL); if (partition != NULL) { // factory partition found root["has_loader"] = true; - } else { // check for not empty, smaller OTA partition + } else { // check for not empty, smaller OTA partition partition = esp_ota_get_next_update_partition(NULL); if (partition) { uint64_t buffer; @@ -49,6 +60,5 @@ void SystemStatus::systemStatus(AsyncWebServerRequest * request) { } } - response->setLength(); - request->send(response); + return response.send(); } diff --git a/lib/framework/SystemStatus.h b/lib/framework/SystemStatus.h index 73226dfe0..193af2ee1 100644 --- a/lib/framework/SystemStatus.h +++ b/lib/framework/SystemStatus.h @@ -2,12 +2,11 @@ #define SystemStatus_h #include -#include #include #include #include -#include +#include #include #define MAX_ESP_STATUS_SIZE 1024 @@ -15,10 +14,14 @@ class SystemStatus { public: - SystemStatus(AsyncWebServer * server, SecurityManager * securityManager); + SystemStatus(PsychicHttpServer * server, SecurityManager * securityManager); + void registerURI(); private: - void systemStatus(AsyncWebServerRequest * request); + SecurityManager * _securityManager; + PsychicHttpServer * _server; + + esp_err_t systemStatus(PsychicRequest * request); }; #endif diff --git a/lib/framework/UploadFileService.cpp b/lib/framework/UploadFileService.cpp index 0d7b652d5..9fcc9cb9f 100644 --- a/lib/framework/UploadFileService.cpp +++ b/lib/framework/UploadFileService.cpp @@ -6,25 +6,34 @@ using namespace std::placeholders; // for `_1` etc -static bool is_firmware = false; -static char md5[33] = "\0"; +static char md5[33] = "\0"; -UploadFileService::UploadFileService(AsyncWebServer * server, SecurityManager * securityManager) - : _securityManager(securityManager) { - server->on(UPLOAD_FILE_PATH, - HTTP_POST, - std::bind(&UploadFileService::uploadComplete, this, _1), - std::bind(&UploadFileService::handleUpload, this, _1, _2, _3, _4, _5, _6)); +static FileType fileType = ft_none; + +UploadFileService::UploadFileService(PsychicHttpServer * server, SecurityManager * securityManager) + : _server(server) + , _securityManager(securityManager) { } -void UploadFileService::handleUpload(AsyncWebServerRequest * request, const String & filename, size_t index, uint8_t * data, size_t len, bool final) { +void UploadFileService::registerURI() { + _server->maxUploadSize = 2300000; // 2.3 MB + + PsychicUploadHandler * uploadHandler = new PsychicUploadHandler(); + + uploadHandler->onUpload(std::bind(&UploadFileService::handleUpload, this, _1, _2, _3, _4, _5, _6)); + uploadHandler->onRequest(std::bind(&UploadFileService::uploadComplete, this, _1)); //gets called after upload has been handled + _server->on(UPLOAD_FILE_PATH, HTTP_POST, uploadHandler); +} + +esp_err_t UploadFileService::handleUpload(PsychicRequest * request, const String & filename, uint64_t index, uint8_t * data, size_t len, bool final) { // quit if not authorized Authentication authentication = _securityManager->authenticateRequest(request); if (!AuthenticationPredicates::IS_ADMIN(authentication)) { - handleError(request, 403); // send the forbidden response - return; + return handleError(request, 403); // forbidden } + File jsonFile; + // at init if (!index) { // check details of the file, to see if its a valid bin or json file @@ -33,44 +42,41 @@ void UploadFileService::handleUpload(AsyncWebServerRequest * request, const Stri std::string extension = fname.substr(position + 1); size_t fsize = request->contentLength(); - is_firmware = false; + fileType = ft_none; if ((extension == "bin") && (fsize > 1000000)) { - is_firmware = true; + fileType = ft_firmware; } else if (extension == "json") { - md5[0] = '\0'; // clear md5 + fileType = ft_json; + md5[0] = '\0'; // clear md5 } else if (extension == "md5") { + fileType = ft_md5; if (len == 32) { memcpy(md5, data, 32); md5[32] = '\0'; } - return; + return ESP_OK; } else { md5[0] = '\0'; - handleError(request, 406); // Not Acceptable - unsupported file type - return; + return handleError(request, 406); // Not Acceptable - unsupported file type } - if (is_firmware) { + if (fileType == ft_firmware) { // Check firmware header, 0xE9 magic offset 0 indicates esp bin, chip offset 12: esp32:0, S2:2, C3:5 -#if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4 +#if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4 if (len > 12 && (data[0] != 0xE9 || data[12] != 0)) { - handleError(request, 503); // service unavailable - return; + return handleError(request, 503); // service unavailable } #elif CONFIG_IDF_TARGET_ESP32S2 if (len > 12 && (data[0] != 0xE9 || data[12] != 2)) { - handleError(request, 503); // service unavailable - return; + return handleError(request, 503); // service unavailable } #elif CONFIG_IDF_TARGET_ESP32C3 if (len > 12 && (data[0] != 0xE9 || data[12] != 5)) { - handleError(request, 503); // service unavailable - return; + return handleError(request, 503); // service unavailable } #elif CONFIG_IDF_TARGET_ESP32S3 if (len > 12 && (data[0] != 0xE9 || data[12] != 9)) { - handleError(request, 503); // service unavailable - return; + return handleError(request, 503); // service unavailable } #endif // it's firmware - initialize the ArduinoOTA updater @@ -79,21 +85,19 @@ void UploadFileService::handleUpload(AsyncWebServerRequest * request, const Stri Update.setMD5(md5); md5[0] = '\0'; } - request->onDisconnect(UploadFileService::handleEarlyDisconnect); // success, let's make sure we end the update if the client hangs up } else { - handleError(request, 507); // failed to begin, send an error response Insufficient Storage - return; + return handleError(request, 507); // failed to begin, send an error response Insufficient Storage } } else { // its a normal file, open a new temp file to write the contents too - request->_tempFile = LittleFS.open(TEMP_FILENAME_PATH, "w"); + jsonFile = LittleFS.open(TEMP_FILENAME_PATH, FILE_WRITE); } } - if (!is_firmware) { + if (fileType == ft_json) { if (len) { - if (len != request->_tempFile.write(data, len)) { // stream the incoming chunk to the opened file - handleError(request, 507); // 507-Insufficient Storage + if (len != jsonFile.write(data, len)) { // stream the incoming chunk to the opened file + return handleError(request, 507); // failed to write chunk to file, send an error response Insufficient Storage } } } else { @@ -109,60 +113,42 @@ void UploadFileService::handleUpload(AsyncWebServerRequest * request, const Stri } } } + + return ESP_OK; } -void UploadFileService::uploadComplete(AsyncWebServerRequest * request) { - // did we complete uploading a json file? - if (request->_tempFile) { - request->_tempFile.close(); // close the file handle as the upload is now done - emsesp::EMSESP::system_.store_nvs_values(); - request->onDisconnect(RestartService::restartNow); - AsyncWebServerResponse * response = request->beginResponse(200); - request->send(response); - return; +esp_err_t UploadFileService::uploadComplete(PsychicRequest * request) { + // did we complete uploading a json file? no need to close the file + if (fileType == ft_md5) { + if (strlen(md5) == 32) { + PsychicJsonResponse response = PsychicJsonResponse(request, false, 256); + JsonObject root = response.getRoot(); + root["md5"] = md5; + return response.send(); + } + return ESP_OK; } - // check if it was a firmware upgrade - // if no error, send the success response as a JSON - if (is_firmware && !request->_tempObject) { - emsesp::EMSESP::system_.store_nvs_values(); - request->onDisconnect(RestartService::restartNow); - AsyncWebServerResponse * response = request->beginResponse(200); - request->send(response); - return; - } - - if (strlen(md5) == 32) { - auto * response = new AsyncJsonResponse(false, 256); - JsonObject root = response->getRoot(); - root["md5"] = md5; - response->setLength(); - request->send(response); - return; - } - - handleError(request, 500); + // store and restart regardless of whether it worked or not + emsesp::EMSESP::system_.store_nvs_values(); + request->reply(200); + RestartService::restartNow(); + return ESP_OK; } -void UploadFileService::handleError(AsyncWebServerRequest * request, int code) { +esp_err_t UploadFileService::handleError(PsychicRequest * request, int code) { // if we have had an error already, do nothing if (request->_tempObject) { - return; + return ESP_OK; } - // send the error code to the client and record the error code in the temp object - AsyncWebServerResponse * response = request->beginResponse(code); - request->send(response); - // check for invalid extension and immediately kill the connection, which will through an error // that is caught by the web code. Unfortunately the http error code is not sent to the client on fast network connections if (code == 406) { - request->client()->close(true); - handleEarlyDisconnect(); + request->client()->close(); + fileType = ft_none; + Update.abort(); } -} -void UploadFileService::handleEarlyDisconnect() { - is_firmware = false; - Update.abort(); + return request->reply(code); } diff --git a/lib/framework/UploadFileService.h b/lib/framework/UploadFileService.h index 4abcdf7ce..0eb811651 100644 --- a/lib/framework/UploadFileService.h +++ b/lib/framework/UploadFileService.h @@ -8,23 +8,28 @@ #include -#include +#include #include #include #define UPLOAD_FILE_PATH "/rest/uploadFile" #define TEMP_FILENAME_PATH "/tmp_upload" +enum FileType { ft_none = 0, ft_firmware = 1, ft_md5 = 2, ft_json = 3 }; + class UploadFileService { public: - UploadFileService(AsyncWebServer * server, SecurityManager * securityManager); + UploadFileService(PsychicHttpServer * server, SecurityManager * securityManager); + + void registerURI(); private: - SecurityManager * _securityManager; - void handleUpload(AsyncWebServerRequest * request, const String & filename, size_t index, uint8_t * data, size_t len, bool final); - void uploadComplete(AsyncWebServerRequest * request); - void handleError(AsyncWebServerRequest * request, int code); - static void handleEarlyDisconnect(); + SecurityManager * _securityManager; + PsychicHttpServer * _server; + + esp_err_t handleUpload(PsychicRequest * request, const String & filename, uint64_t index, uint8_t * data, size_t len, bool final); + esp_err_t uploadComplete(PsychicRequest * request); + esp_err_t handleError(PsychicRequest * request, int code); }; #endif diff --git a/lib/framework/WebSocketTxRx.h b/lib/framework/WebSocketTxRx.h index 86f5fd22a..066670d2e 100644 --- a/lib/framework/WebSocketTxRx.h +++ b/lib/framework/WebSocketTxRx.h @@ -2,7 +2,7 @@ #define WebSocketTxRx_h #include -#include +#include #include #define WEB_SOCKET_CLIENT_ID_MSG_SIZE 128 @@ -16,12 +16,12 @@ template class WebSocketConnector { protected: StatefulService * _statefulService; - AsyncWebServer * _server; + PsychicHttpServer * _server; AsyncWebSocket _webSocket; size_t _bufferSize; WebSocketConnector(StatefulService * statefulService, - AsyncWebServer * server, + PsychicHttpServer * server, const char * webSocketPath, SecurityManager * securityManager, AuthenticationPredicate authenticationPredicate, @@ -36,7 +36,7 @@ class WebSocketConnector { _server->on(webSocketPath, HTTP_GET, std::bind(&WebSocketConnector::forbidden, this, _1)); } - WebSocketConnector(StatefulService * statefulService, AsyncWebServer * server, const char * webSocketPath, size_t bufferSize) + WebSocketConnector(StatefulService * statefulService, PsychicHttpServer * server, const char * webSocketPath, size_t bufferSize) : _statefulService(statefulService) , _server(server) , _webSocket(webSocketPath) @@ -52,7 +52,7 @@ class WebSocketConnector { } private: - void forbidden(AsyncWebServerRequest * request) { + void forbidden(PsychicRequest * request) { request->send(403); } }; @@ -62,7 +62,7 @@ class WebSocketTx : virtual public WebSocketConnector { public: WebSocketTx(JsonStateReader stateReader, StatefulService * statefulService, - AsyncWebServer * server, + PsychicHttpServer * server, const char * webSocketPath, SecurityManager * securityManager, AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN, @@ -74,7 +74,7 @@ class WebSocketTx : virtual public WebSocketConnector { WebSocketTx(JsonStateReader stateReader, StatefulService * statefulService, - AsyncWebServer * server, + PsychicHttpServer * server, const char * webSocketPath, size_t bufferSize = DEFAULT_BUFFER_SIZE) : WebSocketConnector(statefulService, server, webSocketPath, bufferSize) @@ -140,7 +140,7 @@ class WebSocketRx : virtual public WebSocketConnector { public: WebSocketRx(JsonStateUpdater stateUpdater, StatefulService * statefulService, - AsyncWebServer * server, + PsychicHttpServer * server, const char * webSocketPath, SecurityManager * securityManager, AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN, @@ -151,7 +151,7 @@ class WebSocketRx : virtual public WebSocketConnector { WebSocketRx(JsonStateUpdater stateUpdater, StatefulService * statefulService, - AsyncWebServer * server, + PsychicHttpServer * server, const char * webSocketPath, size_t bufferSize = DEFAULT_BUFFER_SIZE) : WebSocketConnector(statefulService, server, webSocketPath, bufferSize) @@ -185,7 +185,7 @@ class WebSocketTxRx : public WebSocketTx, public WebSocketRx { WebSocketTxRx(JsonStateReader stateReader, JsonStateUpdater stateUpdater, StatefulService * statefulService, - AsyncWebServer * server, + PsychicHttpServer * server, const char * webSocketPath, SecurityManager * securityManager, AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN, @@ -198,7 +198,7 @@ class WebSocketTxRx : public WebSocketTx, public WebSocketRx { WebSocketTxRx(JsonStateReader stateReader, JsonStateUpdater stateUpdater, StatefulService * statefulService, - AsyncWebServer * server, + PsychicHttpServer * server, const char * webSocketPath, size_t bufferSize = DEFAULT_BUFFER_SIZE) : WebSocketConnector(statefulService, server, webSocketPath, bufferSize) diff --git a/lib/framework/WiFiScanner.cpp b/lib/framework/WiFiScanner.cpp index 3813c9ff6..5f8093b4a 100644 --- a/lib/framework/WiFiScanner.cpp +++ b/lib/framework/WiFiScanner.cpp @@ -2,30 +2,36 @@ using namespace std::placeholders; // for `_1` etc -WiFiScanner::WiFiScanner(AsyncWebServer * server, SecurityManager * securityManager) { - server->on(SCAN_NETWORKS_SERVICE_PATH, - HTTP_GET, - securityManager->wrapRequest(std::bind(&WiFiScanner::scanNetworks, this, _1), AuthenticationPredicates::IS_ADMIN)); - server->on(LIST_NETWORKS_SERVICE_PATH, - HTTP_GET, - securityManager->wrapRequest(std::bind(&WiFiScanner::listNetworks, this, _1), AuthenticationPredicates::IS_ADMIN)); +WiFiScanner::WiFiScanner(PsychicHttpServer * server, SecurityManager * securityManager) + : _server(server) + , _securityManager(securityManager){}; + +void WiFiScanner::registerURI() { + _server->on(SCAN_NETWORKS_SERVICE_PATH, + HTTP_GET, + _securityManager->wrapRequest(std::bind(&WiFiScanner::scanNetworks, this, _1), AuthenticationPredicates::IS_ADMIN)); + _server->on(LIST_NETWORKS_SERVICE_PATH, + HTTP_GET, + _securityManager->wrapRequest(std::bind(&WiFiScanner::listNetworks, this, _1), AuthenticationPredicates::IS_ADMIN)); }; -void WiFiScanner::scanNetworks(AsyncWebServerRequest * request) { - request->send(202); // special code to indicate scan in progress +esp_err_t WiFiScanner::scanNetworks(PsychicRequest * request) { if (WiFi.scanComplete() != -1) { WiFi.scanDelete(); WiFi.scanNetworks(true); } + + return request->reply(202); // special code to indicate scan in progress } -void WiFiScanner::listNetworks(AsyncWebServerRequest * request) { +esp_err_t WiFiScanner::listNetworks(PsychicRequest * request) { int numNetworks = WiFi.scanComplete(); if (numNetworks > -1) { - AsyncJsonResponse * response = new AsyncJsonResponse(false, MAX_WIFI_SCANNER_SIZE); - JsonObject root = response->getRoot(); - JsonArray networks = root.createNestedArray("networks"); + PsychicJsonResponse response = PsychicJsonResponse(request, false, MAX_WIFI_SCANNER_SIZE); + JsonObject root = response.getRoot(); + + JsonArray networks = root.createNestedArray("networks"); for (int i = 0; i < numNetworks; i++) { JsonObject network = networks.createNestedObject(); network["rssi"] = WiFi.RSSI(i); @@ -34,11 +40,12 @@ void WiFiScanner::listNetworks(AsyncWebServerRequest * request) { network["channel"] = WiFi.channel(i); network["encryption_type"] = (uint8_t)WiFi.encryptionType(i); } - response->setLength(); - request->send(response); + + return response.send(); + } else if (numNetworks == -1) { - request->send(202); // special code to indicate scan in progress + return request->reply(202); // special code to indicate scan in progress } else { - scanNetworks(request); + return scanNetworks(request); } } diff --git a/lib/framework/WiFiScanner.h b/lib/framework/WiFiScanner.h index aab364e7a..52ce8e1ca 100644 --- a/lib/framework/WiFiScanner.h +++ b/lib/framework/WiFiScanner.h @@ -2,10 +2,9 @@ #define WiFiScanner_h #include -#include #include -#include +#include #include #define SCAN_NETWORKS_SERVICE_PATH "/rest/scanNetworks" @@ -15,11 +14,16 @@ class WiFiScanner { public: - WiFiScanner(AsyncWebServer * server, SecurityManager * securityManager); + WiFiScanner(PsychicHttpServer * server, SecurityManager * securityManager); + + void registerURI(); private: - void scanNetworks(AsyncWebServerRequest * request); - void listNetworks(AsyncWebServerRequest * request); + SecurityManager * _securityManager; + PsychicHttpServer * _server; + + esp_err_t scanNetworks(PsychicRequest * request); + esp_err_t listNetworks(PsychicRequest * request); }; #endif diff --git a/lib/uuid-console/src/shell_print.cpp b/lib/uuid-console/src/shell_print.cpp index b5b21b3d2..503e17bf3 100644 --- a/lib/uuid-console/src/shell_print.cpp +++ b/lib/uuid-console/src/shell_print.cpp @@ -90,7 +90,7 @@ void Shell::print_all_available_commands() { } void Shell::erase_current_line() { - print("\033[0G\033[K"); + print("\033[G\033[K"); } void Shell::erase_characters(size_t count) { diff --git a/lib/uuid-telnet/src/telnet.cpp b/lib/uuid-telnet/src/telnet.cpp index 3faeefd01..33b022811 100644 --- a/lib/uuid-telnet/src/telnet.cpp +++ b/lib/uuid-telnet/src/telnet.cpp @@ -138,7 +138,9 @@ void TelnetService::loop() { } } - WiFiClient client = server_.available(); + // WiFiClient client = server_.available(); + WiFiClient client = server_.accept(); + if (client) { if (connections_.size() >= maximum_connections_) { #if UUID_TELNET_HAVE_WIFICLIENT_REMOTE diff --git a/lib_standalone/AsyncJson.h b/lib_standalone/AsyncJson.h deleted file mode 100644 index 2ec4a6322..000000000 --- a/lib_standalone/AsyncJson.h +++ /dev/null @@ -1,258 +0,0 @@ - -#ifndef ASYNC_JSON_H_ -#define ASYNC_JSON_H_ -#include -#include - -#define DYNAMIC_JSON_DOCUMENT_SIZE 1024 - -constexpr const char * JSON_MIMETYPE = "application/json"; - -class ChunkPrint : public Print { - private: - uint8_t * _destination; - size_t _to_skip; - size_t _to_write; - size_t _pos; - - public: - ChunkPrint(uint8_t * destination, size_t from, size_t len) - : _destination(destination) - , _to_skip(from) - , _to_write(len) - , _pos{0} { - } - virtual ~ChunkPrint() { - } - size_t write(uint8_t c) { - if (_to_skip > 0) { - _to_skip--; - return 1; - } else if (_to_write > 0) { - _to_write--; - _destination[_pos++] = c; - return 1; - } - return 0; - } - size_t write(const uint8_t * buffer, size_t size) { - return this->Print::write(buffer, size); - } -}; - -class PrettyAsyncJsonResponse { - protected: - DynamicJsonDocument _jsonBuffer; - - JsonVariant _root; - bool _isValid; - - public: - PrettyAsyncJsonResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE) - : _jsonBuffer(maxJsonBufferSize) - , _isValid{false} { - if (isArray) - _root = _jsonBuffer.createNestedArray(); - else - _root = _jsonBuffer.createNestedObject(); - } - - ~PrettyAsyncJsonResponse() { - } - - JsonVariant & getRoot() { - return _root; - } - - bool _sourceValid() const { - return _isValid; - } - - size_t setLength() { - return 0; - } - - void setContentType(const char * s) { - } - - size_t getSize() { - return _jsonBuffer.size(); - } - - size_t _fillBuffer(uint8_t * data, size_t len) { - return len; - } - - void setCode(uint16_t) { - } -}; - -class MsgpackAsyncJsonResponse { - protected: - DynamicJsonDocument _jsonBuffer; - JsonVariant _root; - bool _isValid; - - public: - MsgpackAsyncJsonResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE) - : _jsonBuffer(maxJsonBufferSize) - , _isValid{false} { - if (isArray) - _root = _jsonBuffer.createNestedArray(); - else - _root = _jsonBuffer.createNestedObject(); - } - - ~MsgpackAsyncJsonResponse() { - } - - JsonVariant & getRoot() { - return _root; - } - - bool _sourceValid() const { - return _isValid; - } - - size_t setLength() { - return 0; - } - - void setContentType(const char * s) { - } - - size_t getSize() { - return _jsonBuffer.size(); - } - - size_t _fillBuffer(uint8_t * data, size_t len) { - return len; - } - - void setCode(uint16_t) { - } -}; - -class AsyncJsonResponse { - protected: - DynamicJsonDocument _jsonBuffer; - - JsonVariant _root; - bool _isValid; - - public: - AsyncJsonResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE) - : _jsonBuffer(maxJsonBufferSize) - , _isValid{false} { - if (isArray) - _root = _jsonBuffer.createNestedArray(); - else - _root = _jsonBuffer.createNestedObject(); - } - - ~AsyncJsonResponse() { - } - - JsonVariant & getRoot() { - return _root; - } - - bool _sourceValid() const { - return _isValid; - } - - size_t setLength() { - return 0; - } - - size_t getSize() { - return _jsonBuffer.size(); - } - - size_t _fillBuffer(uint8_t * data, size_t len) { - return len; - } - - void setCode(uint16_t) { - } -}; - -typedef std::function ArJsonRequestHandlerFunction; - -class AsyncCallbackJsonWebHandler : public AsyncWebHandler { - private: - protected: - const String _uri; - WebRequestMethodComposite _method; - ArJsonRequestHandlerFunction _onRequest; - size_t _contentLength; - size_t _maxContentLength; - size_t _maxJsonBufferSize; - - public: - AsyncCallbackJsonWebHandler(const String & uri, ArJsonRequestHandlerFunction onRequest, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE) - : _uri(uri) - , _method(HTTP_POST | HTTP_PUT | HTTP_PATCH) - , _onRequest(onRequest) - , _maxContentLength(16384) { - } - - void setMethod(WebRequestMethodComposite method) { - _method = method; - } - void setMaxContentLength(int maxContentLength) { - _maxContentLength = maxContentLength; - } - void setMaxJsonBufferSize(int maxJsonBufferSize) { - _maxJsonBufferSize = maxJsonBufferSize; - } - void onRequest(ArJsonRequestHandlerFunction fn) { - _onRequest = fn; - } - - virtual bool canHandle(AsyncWebServerRequest * request) override final { - if (!_onRequest) - return false; - - if (!(_method & request->method())) - return false; - - request->addInterestingHeader("ANY"); - return true; - } - - virtual void handleRequest(AsyncWebServerRequest * request) override final { - if (_onRequest) { - if (request->_tempObject != NULL) { - DynamicJsonDocument jsonBuffer(1024); - DeserializationError error = deserializeJson(jsonBuffer, (uint8_t *)(request->_tempObject)); - if (!error) { - JsonVariant json = jsonBuffer.as(); - - _onRequest(request, json); - return; - } - } - request->send(_contentLength > _maxContentLength ? 413 : 400); - } else { - request->send(500); - } - } - virtual void handleUpload(AsyncWebServerRequest * request, const String & filename, size_t index, uint8_t * data, size_t len, bool final) override final { - } - virtual void handleBody(AsyncWebServerRequest * request, uint8_t * data, size_t len, size_t index, size_t total) override final { - if (_onRequest) { - _contentLength = total; - if (total > 0 && request->_tempObject == NULL && total < _maxContentLength) { - request->_tempObject = malloc(total); - } - if (request->_tempObject != NULL) { - memcpy((uint8_t *)(request->_tempObject) + index, data, len); - } - } - } - virtual bool isRequestHandlerTrivial() override final { - return _onRequest ? false : true; - } -}; -#endif diff --git a/lib_standalone/AsyncTCP.h b/lib_standalone/AsyncTCP.h deleted file mode 100644 index c06bbd0a5..000000000 --- a/lib_standalone/AsyncTCP.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef ASYNCTCP_H_ -#define ASYNCTCP_H_ - -#include "Arduino.h" -#include - -class AsyncClient; - -struct tcp_pcb; -struct ip_addr; - -class AsyncClient { - public: - AsyncClient(tcp_pcb * pcb = 0); - ~AsyncClient(); -}; - -class AsyncServer { - public: - AsyncServer(uint16_t port) - : _port(port){}; - ~AsyncServer(){}; - - protected: - uint16_t _port; -}; - - -#endif diff --git a/lib_standalone/ESP8266React.h b/lib_standalone/ESP8266React.h index b19417273..0339b1e97 100644 --- a/lib_standalone/ESP8266React.h +++ b/lib_standalone/ESP8266React.h @@ -1,13 +1,10 @@ #ifndef ESP8266React_h #define ESP8266React_h - #include #include -#include #include -#include #include @@ -85,7 +82,7 @@ class DummySettings { class DummySettingsService : public StatefulService { public: - DummySettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager){}; + DummySettingsService(PsychicHttpServer * server, FS * fs, SecurityManager * securityManager){}; void begin(); void loop(); @@ -98,16 +95,17 @@ class DummySettingsService : public StatefulService { class ESP8266React { public: - ESP8266React(AsyncWebServer * server, FS * fs) + ESP8266React(PsychicHttpServer * server, FS * fs) : _settings(server, fs, nullptr) , _securitySettingsService(server, fs){}; void begin() { - // initialize mqtt _mqttClient = new espMqttClient(); }; void loop(){}; + void registerURI(){}; + SecurityManager * getSecurityManager() { return &_securitySettingsService; } @@ -146,7 +144,7 @@ class ESP8266React { class EMSESPSettingsService { public: - EMSESPSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager); + EMSESPSettingsService(PsychicHttpServer * server, FS * fs, SecurityManager * securityManager); void begin(); }; diff --git a/lib_standalone/ESPAsyncWebServer.h b/lib_standalone/ESPAsyncWebServer.h deleted file mode 100644 index 50b341849..000000000 --- a/lib_standalone/ESPAsyncWebServer.h +++ /dev/null @@ -1,251 +0,0 @@ -#ifndef _ESPAsyncWebServer_H_ -#define _ESPAsyncWebServer_H_ - -#include "Arduino.h" - -#include -#include -#include - -class AsyncWebServer; -class AsyncWebServerRequest; -class AsyncWebServerResponse; -class AsyncJsonResponse; -class PrettyAsyncJsonResponse; -class MsgpackAsyncJsonResponse; -class AsyncEventSource; - -class AsyncWebParameter { - private: - String _name; - String _value; - size_t _size; - bool _isForm; - bool _isFile; - - public: - AsyncWebParameter(const String & name, const String & value, bool form = false, bool file = false, size_t size = 0) - : _name(name) - , _value(value) - , _size(size) - , _isForm(form) - , _isFile(file) { - } - const String & name() const { - return _name; - } - const String & value() const { - return _value; - } - size_t size() const { - return _size; - } - bool isPost() const { - return _isForm; - } - bool isFile() const { - return _isFile; - } -}; - -typedef enum { - HTTP_GET = 0b00000001, - HTTP_POST = 0b00000010, - HTTP_DELETE = 0b00000100, - HTTP_PUT = 0b00001000, - HTTP_PATCH = 0b00010000, - HTTP_HEAD = 0b00100000, - HTTP_OPTIONS = 0b01000000, - HTTP_ANY = 0b01111111, -} WebRequestMethod; - -typedef uint8_t WebRequestMethodComposite; -typedef std::function ArDisconnectHandler; - -class AsyncWebServerRequest { - friend class AsyncWebServer; - friend class AsyncCallbackWebHandler; - - private: - AsyncClient * _client; - AsyncWebServer * _server; - WebRequestMethodComposite _method; - - String _url; - - public: - void * _tempObject; - - AsyncWebServerRequest(AsyncWebServer *, AsyncClient *){}; - AsyncWebServerRequest(){}; - ~AsyncWebServerRequest(){}; - - AsyncClient * client() { - return _client; - } - - WebRequestMethodComposite method() const { - return _method; - } - - void method(WebRequestMethodComposite method_s) { - _method = method_s; - } - - void addInterestingHeader(const String & name){}; - - size_t args() const { - return 0; - } - - void send(AsyncWebServerResponse * response){}; - void send(AsyncJsonResponse * response){}; - void send(PrettyAsyncJsonResponse * response){}; - void send(MsgpackAsyncJsonResponse * response){}; - void send(int code, const String & contentType = String(), const String & content = String()){}; - void send(int code, const String & contentType, const __FlashStringHelper *){}; - - const String & url() const { - return _url; - } - - void url(const String & url_s) { - _url = url_s; - } - - bool hasParam(const String & name, bool post, bool file) const { - return false; - } - - bool hasParam(const char * name, bool post, bool file) const { - return false; - } - - bool hasParam(const char * name) const { - return false; - } - - bool hasParam(const __FlashStringHelper * data) const { - return false; - } - - bool hasParam(const __FlashStringHelper * data, bool post, bool file) const { - return false; - } - - AsyncWebParameter * getParam(const String & name, bool post, bool file) const { - return nullptr; - } - - AsyncWebParameter * getParam(const __FlashStringHelper * data, bool post, bool file) const { - return nullptr; - } - - AsyncWebParameter * getParam(const __FlashStringHelper * data) const { - return nullptr; - } - - AsyncWebParameter * getParam(const char * name) const { - return nullptr; - } - - AsyncWebParameter * getParam(size_t num) const { - return nullptr; - } - - AsyncWebServerResponse * beginResponse(int code, const String & contentType = String(), const String & content = String()) { - return nullptr; - } - - size_t headers() const; // get header count - size_t params() const; // get arguments count -}; - -typedef std::function ArRequestFilterFunction; - -class AsyncWebHandler { - protected: - String _username; - String _password; - - public: - AsyncWebHandler() - : _username("") - , _password("") { - } - - virtual ~AsyncWebHandler() { - } - virtual bool canHandle(AsyncWebServerRequest * request __attribute__((unused))) { - return false; - } - virtual void handleRequest(AsyncWebServerRequest * request __attribute__((unused))) { - } - virtual void handleUpload(AsyncWebServerRequest * request __attribute__((unused)), - const String & filename __attribute__((unused)), - size_t index __attribute__((unused)), - uint8_t * data __attribute__((unused)), - size_t len __attribute__((unused)), - bool final __attribute__((unused))) { - } - virtual void handleBody(AsyncWebServerRequest * request __attribute__((unused)), - uint8_t * data __attribute__((unused)), - size_t len __attribute__((unused)), - size_t index __attribute__((unused)), - size_t total __attribute__((unused))) { - } - - virtual bool isRequestHandlerTrivial() { - return true; - } - - AsyncWebHandler & setFilter(ArRequestFilterFunction fn) { - return *this; - } -}; - -class AsyncWebServerResponse { - public: - AsyncWebServerResponse(); - virtual ~AsyncWebServerResponse(); -}; - -typedef std::function ArRequestHandlerFunction; -typedef std::function ArUploadHandlerFunction; -typedef std::function ArBodyHandlerFunction; - -class AsyncWebServer { - protected: - AsyncServer _server; - - public: - AsyncWebServer(uint16_t port) - : _server(port){}; - - ~AsyncWebServer(){}; - - void begin(){}; - void end(); - - AsyncWebHandler & addHandler(AsyncWebHandler * handler) { - return *handler; - } - - void on(const char * uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest){}; -}; - - -class AsyncEventSource : public AsyncWebHandler { - public: - AsyncEventSource(const String & url){}; - ~AsyncEventSource(){}; - - size_t count() const { - return 1; - } - - void send(const char * message, const char * event = NULL, uint32_t id = 0, uint32_t reconnect = 0){}; -}; - - -#endif diff --git a/lib_standalone/HttpEndpoint.h b/lib_standalone/HttpEndpoint.h index f06662702..ca3293b09 100644 --- a/lib_standalone/HttpEndpoint.h +++ b/lib_standalone/HttpEndpoint.h @@ -3,45 +3,55 @@ #include -#include -#include +#include #include #include #define HTTP_ENDPOINT_ORIGIN_ID "http" +#define HTTPS_ENDPOINT_ORIGIN_ID "https" + +using namespace std::placeholders; // for `_1` etc template class HttpGetEndpoint { public: HttpGetEndpoint(JsonStateReader stateReader, StatefulService * statefulService, - AsyncWebServer * server, - const String & servicePath, + PsychicHttpServer * server, + const char * servicePath, SecurityManager * securityManager, AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN, size_t bufferSize = DEFAULT_BUFFER_SIZE) : _stateReader(stateReader) , _statefulService(statefulService) - , _bufferSize(bufferSize) { - } - - HttpGetEndpoint(JsonStateReader stateReader, - StatefulService * statefulService, - AsyncWebServer * server, - const String & servicePath, - size_t bufferSize = DEFAULT_BUFFER_SIZE) - : _stateReader(stateReader) - , _statefulService(statefulService) - , _bufferSize(bufferSize) { + , _bufferSize(bufferSize) + , _server(server) + , _servicePath(servicePath) + , _securityManager(securityManager) + , _authenticationPredicate(authenticationPredicate) { } protected: - JsonStateReader _stateReader; - StatefulService * _statefulService; - size_t _bufferSize; + JsonStateReader _stateReader; + StatefulService * _statefulService; + size_t _bufferSize; + PsychicHttpServer * _server; + const char * _servicePath; + SecurityManager * _securityManager; + AuthenticationPredicate _authenticationPredicate; - void fetchSettings(AsyncWebServerRequest * request) { + void registerURI() { + _server->on(_servicePath, + HTTP_GET, + _securityManager->wrapRequest( + [this](PsychicRequest * request) { + PsychicJsonResponse response = PsychicJsonResponse(request, false, _bufferSize); + JsonObject jsonObject = response.getRoot(); + _statefulService->read(jsonObject, _stateReader); + return response.send(); + }, + _authenticationPredicate)); } }; @@ -51,46 +61,61 @@ class HttpPostEndpoint { HttpPostEndpoint(JsonStateReader stateReader, JsonStateUpdater stateUpdater, StatefulService * statefulService, - AsyncWebServer * server, - const String & servicePath, + PsychicHttpServer * server, + const char * servicePath, SecurityManager * securityManager, AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN, size_t bufferSize = DEFAULT_BUFFER_SIZE) : _stateReader(stateReader) , _stateUpdater(stateUpdater) , _statefulService(statefulService) + , _server(server) + , _servicePath(servicePath) + , _securityManager(securityManager) + , _authenticationPredicate(authenticationPredicate) , _bufferSize(bufferSize) { } - HttpPostEndpoint(JsonStateReader stateReader, - JsonStateUpdater stateUpdater, - StatefulService * statefulService, - AsyncWebServer * server, - const String & servicePath, - size_t bufferSize = DEFAULT_BUFFER_SIZE) - : _stateReader(stateReader) - , _stateUpdater(stateUpdater) - , _statefulService(statefulService) - , _bufferSize(bufferSize) { - } protected: - JsonStateReader _stateReader; - JsonStateUpdater _stateUpdater; - StatefulService * _statefulService; - size_t _bufferSize; + JsonStateReader _stateReader; + JsonStateUpdater _stateUpdater; + StatefulService * _statefulService; + size_t _bufferSize; + SecurityManager * _securityManager; + AuthenticationPredicate _authenticationPredicate; + PsychicHttpServer * _server; + const char * _servicePath; - void updateSettings(AsyncWebServerRequest * request, JsonVariant & json) { - if (!json.is()) { - return; - } - JsonObject jsonObject = json.as(); - StateUpdateResult outcome = _statefulService->updateWithoutPropagation(jsonObject, _stateUpdater); - if (outcome == StateUpdateResult::ERROR) { - return; - } - if (outcome == StateUpdateResult::CHANGED) { - } + void registerURI() { + _server->on(_servicePath, + HTTP_POST, + _securityManager->wrapCallback( + [this](PsychicRequest * request, JsonVariant & json) { + if (!json.is()) { + return request->reply(400); + } + + JsonObject jsonObject = json.as(); + StateUpdateResult outcome = _statefulService->updateWithoutPropagation(jsonObject, _stateUpdater); + + if (outcome == StateUpdateResult::ERROR) { + return request->reply(400); + } else if ((outcome == StateUpdateResult::CHANGED) || (outcome == StateUpdateResult::CHANGED_RESTART)) { + _statefulService->callUpdateHandlers(HTTP_ENDPOINT_ORIGIN_ID); // persist the changes to the FS + } + + PsychicJsonResponse response = PsychicJsonResponse(request, false, _bufferSize); + jsonObject = response.getRoot(); + + _statefulService->read(jsonObject, _stateReader); + + if (outcome == StateUpdateResult::CHANGED_RESTART) { + return request->reply(205); // reboot required + } + return response.send(); + }, + _authenticationPredicate)); } }; @@ -100,8 +125,8 @@ class HttpEndpoint : public HttpGetEndpoint, public HttpPostEndpoint { HttpEndpoint(JsonStateReader stateReader, JsonStateUpdater stateUpdater, StatefulService * statefulService, - AsyncWebServer * server, - const String & servicePath, + PsychicHttpServer * server, + const char * servicePath, SecurityManager * securityManager, AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN, size_t bufferSize = DEFAULT_BUFFER_SIZE) @@ -109,14 +134,10 @@ class HttpEndpoint : public HttpGetEndpoint, public HttpPostEndpoint { , HttpPostEndpoint(stateReader, stateUpdater, statefulService, server, servicePath, securityManager, authenticationPredicate, bufferSize) { } - HttpEndpoint(JsonStateReader stateReader, - JsonStateUpdater stateUpdater, - StatefulService * statefulService, - AsyncWebServer * server, - const String & servicePath, - size_t bufferSize = DEFAULT_BUFFER_SIZE) - : HttpGetEndpoint(stateReader, statefulService, server, servicePath, bufferSize) - , HttpPostEndpoint(stateReader, stateUpdater, statefulService, server, servicePath, bufferSize) { + // register the web server on() endpoints + void registerURI() { + HttpGetEndpoint::registerURI(); + HttpPostEndpoint::registerURI(); } }; diff --git a/lib_standalone/PsychicHttp.h b/lib_standalone/PsychicHttp.h new file mode 100644 index 000000000..270d96601 --- /dev/null +++ b/lib_standalone/PsychicHttp.h @@ -0,0 +1,505 @@ +/* + * PsychicHttp.h + * Standalone hacky version + */ + +#ifndef PsychicHttp_h +#define PsychicHttp_h + +#include "Arduino.h" +#include +#include +#include +#include + +#define esp_err_t uint8_t + +class PsychicHttpServer; +class PsychicHttpsServer; +class PsychicEndpoint; +class PsychicHandler; +class PsychicStaticFileHandler; +class PsychicClient; +class PsychicRequest; +class PsychicResponse; +class PsychicEventSource; +class PsychicEventSourceResponse; +class PsychicEventSourceClient; +class PsychicResponse; + +typedef std::function PsychicEventSourceClientCallback; +typedef std::function PsychicJsonRequestCallback; +typedef std::function PsychicRequestFilterFunction; +typedef std::function PsychicClientCallback; +typedef std::function PsychicHttpRequestCallback; +typedef std::function PsychicJsonRequestCallback; // added by proddy + +/* Request Methods */ +#define HTTP_METHOD_MAP(XX) \ + XX(1, GET, GET) \ + XX(3, POST, POST) + +enum http_method { +#define XX(num, name, string) HTTP_##name = num, + HTTP_METHOD_MAP(XX) +#undef XX +}; + +constexpr const char * JSON_MIMETYPE = "application/json"; + +enum HTTPAuthMethod { BASIC_AUTH, DIGEST_AUTH }; + +typedef void * httpd_handle_t; + +typedef struct httpd_req { + httpd_handle_t handle; /*!< Handle to server instance */ + int method; /*!< The type of HTTP request, -1 if unsupported method */ + const char uri[513]; /*!< The URI of this request (1 byte extra for null termination) */ + size_t content_len; /*!< Length of the request body */ + void * aux; /*!< Internally used members */ + +} httpd_req_t; + +typedef std::map SessionData; +enum Disposition { NONE, INLINE, ATTACHMENT, FORM_DATA }; + +struct ContentDisposition { + Disposition disposition; + String filename; + String name; +}; + +class PsychicWebParameter { + private: + String _name; + String _value; + size_t _size; + bool _isForm; + bool _isFile; + + public: + PsychicWebParameter(const String & name, const String & value, bool form = false, bool file = false, size_t size = 0) + : _name(name) + , _value(value) + , _size(size) + , _isForm(form) + , _isFile(file) { + } + const String & name() const { + return _name; + } + const String & value() const { + return _value; + } + size_t size() const { + return _size; + } + bool isPost() const { + return _isForm; + } + bool isFile() const { + return _isFile; + } +}; + +class PsychicRequest { + protected: + PsychicHttpServer * _server; + httpd_req_t * _req; + SessionData * _session; + PsychicClient * _client; + + http_method _method; + String _uri; + String _query; + String _body; + + std::list _params; + + void _addParams(const String & params); + void _parseGETParams(); + void _parsePOSTParams(); + + const String _extractParam(const String & authReq, const String & param, const char delimit); + const String _getRandomHexString(); + + public: + PsychicRequest(PsychicHttpServer * server, httpd_req_t * req); + virtual ~PsychicRequest(); + + void * _tempObject; + + PsychicHttpServer * server(); + httpd_req_t * request(); + virtual PsychicClient * client(); + + bool isMultipart(); + esp_err_t loadBody(); + + const String header(const char * name); + bool hasHeader(const char * name); + + static void freeSession(void * ctx); + bool hasSessionKey(const String & key); + const String getSessionKey(const String & key); + void setSessionKey(const String & key, const String & value); + + bool hasCookie(const char * key); + const String getCookie(const char * key); + + http_method method() { + return HTTP_GET; + } // returns the HTTP method used as enum value (eg. HTTP_GET) + const String methodStr(); // returns the HTTP method used as a string (eg. "GET") + const String path(); // returns the request path (eg /page?foo=bar returns "/page") + const String & uri(); // returns the full request uri (eg /page?foo=bar) + const String & query(); // returns the request query data (eg /page?foo=bar returns "foo=bar") + const String host(); // returns the requested host (request to http://psychic.local/foo will return "psychic.local") + const String contentType(); // returns the Content-Type header value + size_t contentLength(); // returns the Content-Length header value + const String & body(); // returns the body of the request + const ContentDisposition getContentDisposition(); + + const String & queryString() { + return query(); + } //compatability function. same as query() + const String & url() { + return uri(); + } //compatability function. same as uri() + + void loadParams(); + PsychicWebParameter * addParam(PsychicWebParameter * param); + PsychicWebParameter * addParam(const String & name, const String & value, bool decode = true); + bool hasParam(const char * key); + PsychicWebParameter * getParam(const char * name); + + const String getFilename(); + + bool authenticate(const char * username, const char * password); + esp_err_t requestAuthentication(HTTPAuthMethod mode, const char * realm, const char * authFailMsg); + + esp_err_t redirect(const char * url); + esp_err_t reply(int code); + esp_err_t reply(const char * content); + esp_err_t reply(int code, const char * contentType, const char * content); +}; + +class PsychicHandler { + protected: + PsychicRequestFilterFunction _filter; + PsychicHttpServer * _server; + + String _username; + String _password; + HTTPAuthMethod _method; + String _realm; + String _authFailMsg; + + std::list _clients; + + public: + PsychicHandler(); + ~PsychicHandler(); + + PsychicHandler * setFilter(PsychicRequestFilterFunction fn); + bool filter(PsychicRequest * request); + + PsychicHandler * + setAuthentication(const char * username, const char * password, HTTPAuthMethod method = BASIC_AUTH, const char * realm = "", const char * authFailMsg = ""); + bool needsAuthentication(PsychicRequest * request); + esp_err_t authenticate(PsychicRequest * request); + + virtual bool isWebSocket() { + return false; + }; + + PsychicClient * checkForNewClient(PsychicClient * client); + void checkForClosedClient(PsychicClient * client); + + virtual void addClient(PsychicClient * client); + virtual void removeClient(PsychicClient * client); + virtual PsychicClient * getClient(int socket); + virtual PsychicClient * getClient(PsychicClient * client); + virtual void openCallback(PsychicClient * client){}; + virtual void closeCallback(PsychicClient * client){}; + + bool hasClient(PsychicClient * client); + int count() { + return _clients.size(); + }; + const std::list & getClientList(); + + virtual bool canHandle(PsychicRequest * request) { + return true; + }; + virtual esp_err_t handleRequest(PsychicRequest * request) = 0; +}; + +class PsychicEndpoint { + friend PsychicHttpServer; + + private: + PsychicHttpServer * _server; + String _uri; + http_method _method; + PsychicHandler * _handler; + + public: + PsychicEndpoint(); + PsychicEndpoint(PsychicHttpServer * server, http_method method, const char * uri); + + PsychicEndpoint * setHandler(PsychicHandler * handler); + PsychicHandler * handler(); + + PsychicEndpoint * setFilter(PsychicRequestFilterFunction fn); + PsychicEndpoint * + setAuthentication(const char * username, const char * password, HTTPAuthMethod method = BASIC_AUTH, const char * realm = "", const char * authFailMsg = ""); + + String uri(); + + static esp_err_t requestCallback(httpd_req_t * req); +}; + +typedef struct httpd_config { + uint16_t max_uri_handlers; /*!< Maximum allowed uri handlers */ +} httpd_config_t; + +httpd_config_t config; + +class PsychicHttpServer { + public: + PsychicHttpServer(); + ~PsychicHttpServer(); + + httpd_handle_t server; + httpd_config_t config; + + unsigned long maxUploadSize; + unsigned long maxRequestBodySize; + + PsychicEndpoint * defaultEndpoint; + PsychicHandler & addHandler(PsychicHandler * handler); + + esp_err_t listen(uint16_t port); + + PsychicEndpoint * on(const char * uri); + PsychicEndpoint * on(const char * uri, http_method method); + PsychicEndpoint * on(const char * uri, PsychicHttpRequestCallback onRequest); + PsychicEndpoint * on(const char * uri, http_method method, PsychicHttpRequestCallback onRequest); + PsychicEndpoint * on(const char * uri, PsychicHandler * handler); + PsychicEndpoint * on(const char * uri, http_method method, PsychicHandler * handler); + PsychicEndpoint * on(const char * uri, http_method method, PsychicJsonRequestCallback onRequest); // added proddy + + void onNotFound(PsychicHttpRequestCallback fn); + + void onOpen(PsychicClientCallback handler); + void onClose(PsychicClientCallback handler); + static esp_err_t openCallback(httpd_handle_t hd, int sockfd); + static void closeCallback(httpd_handle_t hd, int sockfd); +}; + +class PsychicHttpsServer : public PsychicHttpServer { + public: + PsychicHttpsServer(); + ~PsychicHttpsServer(); + + uint8_t listen(uint16_t port, const char * cert, const char * private_key); +}; + +struct HTTPHeader { + char * field; + char * value; +}; + +class DefaultHeaders { + std::list _headers; + + public: + DefaultHeaders() { + } + + void addHeader(const String & field, const String & value) { + addHeader(field.c_str(), value.c_str()); + } + + void addHeader(const char * field, const char * value) { + HTTPHeader header; + _headers.push_back(header); + } + + const std::list & getHeaders() { + return _headers; + } + + DefaultHeaders(DefaultHeaders const &) = delete; + DefaultHeaders & operator=(DefaultHeaders const &) = delete; + + static DefaultHeaders & Instance() { + static DefaultHeaders instance; + return instance; + } +}; + +class PsychicWebHandler : public PsychicHandler { + protected: + PsychicHttpRequestCallback _requestCallback; + + public: + PsychicWebHandler(); + ~PsychicWebHandler(); + + virtual bool canHandle(PsychicRequest * request) override; + virtual esp_err_t handleRequest(PsychicRequest * request) override; + PsychicWebHandler * onRequest(PsychicHttpRequestCallback fn); +}; + +class PsychicResponse { + protected: + PsychicRequest * _request; + + int _code; + char _status[60]; + std::list _headers; + int64_t _contentLength; + const char * _body; + + public: + PsychicResponse(PsychicRequest * request); + virtual ~PsychicResponse(); + + void setCode(int code); + + void setContentType(const char * contentType); + void setContentLength(int64_t contentLength) { + _contentLength = contentLength; + } + int64_t getContentLength(int64_t contentLength) { + return _contentLength; + } + + void addHeader(const char * field, const char * value); + void setCookie(const char * key, const char * value, unsigned long max_age = 60 * 60 * 24 * 30, const char * extras = ""); + void setContent(const char * content); + void setContent(const uint8_t * content, size_t len); + const char * getContent(); + size_t getContentLength(); + esp_err_t send(); + void sendHeaders(); + esp_err_t sendChunk(uint8_t * chunk, size_t chunksize); + esp_err_t finishChunking(); +}; + +class PsychicJsonResponse : public PsychicResponse { + protected: +#ifdef ARDUINOJSON_5_COMPATIBILITY + DynamicJsonBuffer _jsonBuffer; +#else + DynamicJsonDocument _jsonBuffer; +#endif + + JsonVariant _root; + size_t _contentLength; + bool _msgPack; // added by proddy + + public: + PsychicJsonResponse(PsychicRequest * request, bool isArray = false, size_t maxJsonBufferSize = 4096, bool msgPack = false) // added by proddy + : PsychicResponse(request) + , _jsonBuffer(maxJsonBufferSize) + , _msgPack(msgPack) // added by proddy + { + } + + ~PsychicJsonResponse() { + } + + JsonVariant & getRoot() { + return _root; + } + + size_t getLength() { + // return measureJson(_root); + return (_msgPack) ? measureMsgPack(_root) : measureJson(_root); // added by proddy + } + + esp_err_t send() { + return 0; + } +}; + +class PsychicClient { + protected: + httpd_handle_t _server; + int _socket; + + public: + PsychicClient(httpd_handle_t server, int socket); + ~PsychicClient(); + + //no idea if this is the right way to do it or not, but lets see. + //pointer to our derived class (eg. PsychicWebSocketConnection) + void * _friend; + + bool isNew = false; + + bool operator==(PsychicClient & rhs) const { + return _socket == rhs.socket(); + } + + httpd_handle_t server(); + int socket(); + esp_err_t close(); + + IPAddress localIP(); + IPAddress remoteIP(); +}; + +class PsychicEventSourceClient : public PsychicClient { + protected: + uint32_t _lastId; + + public: + PsychicEventSourceClient(PsychicClient * client); + ~PsychicEventSourceClient(); + + uint32_t lastId() const { + return _lastId; + } + void send(const char * message, const char * event = NULL, uint32_t id = 0, uint32_t reconnect = 0); + void sendEvent(const char * event); +}; + +class PsychicEventSource : public PsychicHandler { + private: + PsychicEventSourceClientCallback _onOpen; + PsychicEventSourceClientCallback _onClose; + + public: + PsychicEventSource(); + ~PsychicEventSource(); + + PsychicEventSourceClient * getClient(int socket) override; + PsychicEventSourceClient * getClient(PsychicClient * client) override; + void addClient(PsychicClient * client) override; + void removeClient(PsychicClient * client) override; + void openCallback(PsychicClient * client) override; + void closeCallback(PsychicClient * client) override; + + PsychicEventSource * onOpen(PsychicEventSourceClientCallback fn); + PsychicEventSource * onClose(PsychicEventSourceClientCallback fn); + + esp_err_t handleRequest(PsychicRequest * request) override final; + + void send(const char * message, const char * event = NULL, uint32_t id = 0, uint32_t reconnect = 0); +}; + +class PsychicEventSourceResponse : public PsychicResponse { + public: + PsychicEventSourceResponse(PsychicRequest * request); + esp_err_t send(); +}; + +String generateEventMessage(const char * message, const char * event, uint32_t id, uint32_t reconnect); + + + +#endif \ No newline at end of file diff --git a/lib_standalone/SecurityManager.h b/lib_standalone/SecurityManager.h index cebff2229..81e8fd0cd 100644 --- a/lib_standalone/SecurityManager.h +++ b/lib_standalone/SecurityManager.h @@ -2,17 +2,12 @@ #define SecurityManager_h #include +#include #include -#include -#include #include -#ifndef FACTORY_JWT_SECRET -#define FACTORY_JWT_SECRET ESPUtils::defaultDeviceValue() -#endif - +#define FACTORY_JWT_SECRET "secret" #define ACCESS_TOKEN_PARAMATER "access_token" - #define AUTHORIZATION_HEADER "Authorization" #define AUTHORIZATION_HEADER_PREFIX "Bearer " #define AUTHORIZATION_HEADER_PREFIX_LEN 7 @@ -69,15 +64,35 @@ class AuthenticationPredicates { class SecurityManager { public: -#if FT_ENABLED(FT_SECURITY) + /* + * Authenticate, returning the user if found + */ virtual Authentication authenticate(const String & username, const String & password) = 0; - virtual String generateJWT(User * user) = 0; -#endif - virtual Authentication authenticateRequest(AsyncWebServerRequest * request) = 0; - virtual ArRequestFilterFunction filterRequest(AuthenticationPredicate predicate) = 0; - virtual ArRequestHandlerFunction wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate) = 0; - virtual ArJsonRequestHandlerFunction wrapCallback(ArJsonRequestHandlerFunction onRequest, AuthenticationPredicate predicate) = 0; + /* + * Generate a JWT for the user provided + */ + virtual String generateJWT(User * user) = 0; + + /* + * Check the request header for the Authorization token + */ + virtual Authentication authenticateRequest(PsychicRequest * request) = 0; + + /** + * Filter a request with the provided predicate, only returning true if the predicate matches. + */ + virtual PsychicRequestFilterFunction filterRequest(AuthenticationPredicate predicate) = 0; + + /** + * Wrap the provided request to provide validation against an AuthenticationPredicate. + */ + virtual PsychicHttpRequestCallback wrapRequest(PsychicHttpRequestCallback onRequest, AuthenticationPredicate predicate) = 0; + + /** + * Wrap the provided json request callback to provide validation against an AuthenticationPredicate. + */ + virtual PsychicJsonRequestCallback wrapCallback(PsychicJsonRequestCallback onRequest, AuthenticationPredicate predicate) = 0; }; #endif diff --git a/lib_standalone/SecuritySettingsService.cpp b/lib_standalone/SecuritySettingsService.cpp index c77130caa..8a46af22f 100644 --- a/lib_standalone/SecuritySettingsService.cpp +++ b/lib_standalone/SecuritySettingsService.cpp @@ -9,7 +9,8 @@ SecuritySettingsService::SecuritySettingsService(AsyncWebServer * server, FS * fs) : _httpEndpoint(SecuritySettings::read, SecuritySettings::update, this, server, SECURITY_SETTINGS_PATH, this) , _fsPersistence(SecuritySettings::read, SecuritySettings::update, this, fs, SECURITY_SETTINGS_FILE) - , _jwtHandler(FACTORY_JWT_SECRET) { +// , _jwtHandler(FACTORY_JWT_SECRET) { +{ addUpdateHandler([&](const String & originId) { configureJWTHandler(); }, false); } @@ -18,29 +19,32 @@ void SecuritySettingsService::begin() { configureJWTHandler(); } -Authentication SecuritySettingsService::authenticateRequest(AsyncWebServerRequest * request) { - AsyncWebHeader * authorizationHeader = request->getHeader(AUTHORIZATION_HEADER); - if (authorizationHeader) { - String value = authorizationHeader->value(); +void SecuritySettingsService::registerURI() { + _httpEndpoint.registerURI(); + _server->on(GENERATE_TOKEN_PATH, HTTP_GET, wrapRequest(std::bind(&SecuritySettingsService::generateToken, this, _1), AuthenticationPredicates::IS_ADMIN)); +} + +Authentication SecuritySettingsService::authenticateRequest(PsychicRequest * request) { + if (request->hasHeader(AUTHORIZATION_HEADER)) { + auto value = request->header(AUTHORIZATION_HEADER); if (value.startsWith(AUTHORIZATION_HEADER_PREFIX)) { value = value.substring(AUTHORIZATION_HEADER_PREFIX_LEN); return authenticateJWT(value); } } else if (request->hasParam(ACCESS_TOKEN_PARAMATER)) { - AsyncWebParameter * tokenParamater = request->getParam(ACCESS_TOKEN_PARAMATER); - String value = tokenParamater->value(); + String value = request->getParam(ACCESS_TOKEN_PARAMATER)->value(); return authenticateJWT(value); } return Authentication(); } void SecuritySettingsService::configureJWTHandler() { - _jwtHandler.setSecret(_state.jwtSecret); + // _jwtHandler.setSecret(_state.jwtSecret); } Authentication SecuritySettingsService::authenticateJWT(String & jwt) { DynamicJsonDocument payloadDocument(MAX_JWT_SIZE); - _jwtHandler.parseJWT(jwt, payloadDocument); + // _jwtHandler.parseJWT(jwt, payloadDocument); if (payloadDocument.is()) { JsonObject parsedPayload = payloadDocument.as(); String username = parsedPayload["username"]; @@ -78,7 +82,7 @@ String SecuritySettingsService::generateJWT(User * user) { DynamicJsonDocument jsonDocument(MAX_JWT_SIZE); JsonObject payload = jsonDocument.to(); populateJWTPayload(payload, user); - return _jwtHandler.buildJWT(payload); + return ""; } ArRequestFilterFunction SecuritySettingsService::filterRequest(AuthenticationPredicate predicate) { @@ -88,56 +92,26 @@ ArRequestFilterFunction SecuritySettingsService::filterRequest(AuthenticationPre }; } -ArRequestHandlerFunction SecuritySettingsService::wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate) { - return [this, onRequest, predicate](AsyncWebServerRequest * request) { +PsychicHttpRequestCallback SecuritySettingsService::wrapRequest(PsychicHttpRequestCallback onRequest, AuthenticationPredicate predicate) { + return [this, onRequest, predicate](PsychicRequest * request) { Authentication authentication = authenticateRequest(request); if (!predicate(authentication)) { - request->send(401); - return; + return request->reply(401); } - onRequest(request); + return onRequest(request); }; } -ArJsonRequestHandlerFunction SecuritySettingsService::wrapCallback(ArJsonRequestHandlerFunction onRequest, AuthenticationPredicate predicate) { - return [this, onRequest, predicate](AsyncWebServerRequest * request, JsonVariant & json) { +PsychicJsonRequestCallback SecuritySettingsService::wrapCallback(PsychicJsonRequestCallback onRequest, AuthenticationPredicate predicate) { + return [this, onRequest, predicate](PsychicRequest * request, JsonVariant & json) { Authentication authentication = authenticateRequest(request); if (!predicate(authentication)) { - request->send(401); - return; + return request->reply(401); } - onRequest(request, json); + return onRequest(request, json); }; } -#else - -User ADMIN_USER = User(FACTORY_ADMIN_USERNAME, FACTORY_ADMIN_PASSWORD, true); - -SecuritySettingsService::SecuritySettingsService(AsyncWebServer * server, FS * fs) - : SecurityManager() { -} -SecuritySettingsService::~SecuritySettingsService() { -} - -ArRequestFilterFunction SecuritySettingsService::filterRequest(AuthenticationPredicate predicate) { - return [this, predicate](AsyncWebServerRequest * request) { return true; }; -} - -// Return the admin user on all request - disabling security features -Authentication SecuritySettingsService::authenticateRequest(AsyncWebServerRequest * request) { - return Authentication(ADMIN_USER); -} - -// Return the function unwrapped -ArRequestHandlerFunction SecuritySettingsService::wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate) { - return onRequest; -} - -ArJsonRequestHandlerFunction SecuritySettingsService::wrapCallback(ArJsonRequestHandlerFunction onRequest, AuthenticationPredicate predicate) { - return onRequest; -} - #endif #endif \ No newline at end of file diff --git a/lib_standalone/SecuritySettingsService.h b/lib_standalone/SecuritySettingsService.h index acc4dec7d..d9161cfb2 100644 --- a/lib_standalone/SecuritySettingsService.h +++ b/lib_standalone/SecuritySettingsService.h @@ -25,7 +25,8 @@ #define SECURITY_SETTINGS_FILE "/config/securitySettings.json" #define SECURITY_SETTINGS_PATH "/rest/securitySettings" -#if FT_ENABLED(FT_SECURITY) +#define GENERATE_TOKEN_SIZE 512 +#define GENERATE_TOKEN_PATH "/rest/generateToken" class SecuritySettings { public: @@ -66,41 +67,32 @@ class SecuritySettings { class SecuritySettingsService : public StatefulService, public SecurityManager { public: - SecuritySettingsService(AsyncWebServer * server, FS * fs); + SecuritySettingsService(PsychicHttpServer * server, FS * fs); void begin(); + void registerURI(); // Functions to implement SecurityManager - Authentication authenticate(const String & username, const String & password); - Authentication authenticateRequest(AsyncWebServerRequest * request); - String generateJWT(User * user); - ArRequestFilterFunction filterRequest(AuthenticationPredicate predicate); - ArRequestHandlerFunction wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate); - ArJsonRequestHandlerFunction wrapCallback(ArJsonRequestHandlerFunction callback, AuthenticationPredicate predicate); + Authentication authenticate(const String & username, const String & password); + Authentication authenticateRequest(PsychicRequest * request); + String generateJWT(User * user); + + PsychicRequestFilterFunction filterRequest(AuthenticationPredicate predicate); + PsychicHttpRequestCallback wrapRequest(PsychicHttpRequestCallback onRequest, AuthenticationPredicate predicate); + PsychicJsonRequestCallback wrapCallback(PsychicJsonRequestCallback onRequest, AuthenticationPredicate predicate); private: + PsychicHttpServer * _server; + HttpEndpoint _httpEndpoint; FSPersistence _fsPersistence; - ArduinoJsonJWT _jwtHandler; + // ArduinoJsonJWT _jwtHandler; + + esp_err_t generateToken(PsychicRequest * request); void configureJWTHandler(); - Authentication authenticateJWT(String & jwt); - boolean validatePayload(JsonObject & parsedPayload, User * user); -}; - -#else - -class SecuritySettingsService : public SecurityManager { - public: - SecuritySettingsService(AsyncWebServer * server, FS * fs); - ~SecuritySettingsService(); - - // minimal set of functions to support framework with security settings disabled - Authentication authenticateRequest(AsyncWebServerRequest * request); - ArRequestFilterFunction filterRequest(AuthenticationPredicate predicate); - ArRequestHandlerFunction wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate); - ArJsonRequestHandlerFunction wrapCallback(ArJsonRequestHandlerFunction onRequest, AuthenticationPredicate predicate); + Authentication authenticateJWT(String & jwt); // Lookup the user by JWT + boolean validatePayload(JsonObject & parsedPayload, User * user); // Verify the payload is correct }; #endif -#endif diff --git a/mock-api/Handler.ts b/mock-api/Handler.ts index fc519a0eb..6b67901d9 100644 --- a/mock-api/Handler.ts +++ b/mock-api/Handler.ts @@ -5,7 +5,7 @@ import busboy from 'busboy'; const encoder = new Encoder(); const router = Router(); -// const upload = multer({ dest: '../mock-api/uploads' }); // TODO remove +// const upload = multer({ dest: '../mock-api/uploads' }); // TODO remove multer const REST_ENDPOINT_ROOT = '/rest/'; const API_ENDPOINT_ROOT = '/api/'; @@ -2351,11 +2351,11 @@ router .get(VERIFY_AUTHORIZATION_ENDPOINT, () => new Response(JSON.stringify(verify_authentication), { headers })) .post(RESTART_ENDPOINT, () => new Response('OK', { status: 200 })) .post(FACTORY_RESET_ENDPOINT, () => new Response('OK', { status: 200 })) - .post(UPLOAD_FILE_ENDPOINT, () => new Response('OK', { status: 404 })) // TODO remove + .post(UPLOAD_FILE_ENDPOINT, () => new Response('OK', { status: 404 })) // TODO remove upload filepoint .post(SIGN_IN_ENDPOINT, () => new Response(JSON.stringify(signin), { headers })) .get(GENERATE_TOKEN_ENDPOINT, () => new Response(JSON.stringify(generate_token), { headers })); -// uploads // TODO fix later +// uploads // TODO fix uploads later // const progress_middleware = async (req: any) => { // console.log('progress_middleware'); @@ -2775,6 +2775,7 @@ router }); // API and calls +const SYSTEM_API_ENDPOINT = API_ENDPOINT_ROOT + 'system'; const SYSTEM_INFO_ENDPOINT = API_ENDPOINT_ROOT + 'system/info'; const GET_SETTINGS_ENDPOINT = REST_ENDPOINT_ROOT + 'getSettings'; const GET_CUSTOMIZATIONS_ENDPOINT = REST_ENDPOINT_ROOT + 'getCustomizations'; @@ -2792,32 +2793,18 @@ router .get(GET_SCHEDULE_ENDPOINT, () => new Response(JSON.stringify(emsesp_schedule), { headers })) .get(SCHEDULE_ENDPOINT, () => new Response(JSON.stringify(emsesp_schedule), { headers })) .get(ENTITIES_ENDPOINT, () => new Response(JSON.stringify(emsesp_customentities), { headers })) - .post(API_ENDPOINT_ROOT, async (request: any) => { + .post(SYSTEM_API_ENDPOINT, async (request: any) => { const data = await request.json(); - if (data.device === 'system') { - if (data.entity === 'info') { - return new Response(JSON.stringify(emsesp_info), { headers }); - } - if (data.entity === 'allvalues') { - return new Response(JSON.stringify(emsesp_allvalues), { headers }); - } + if (data.entity === 'info') { + return new Response(JSON.stringify(emsesp_info), { headers }); } + if (data.entity === 'allvalues') { + return new Response(JSON.stringify(emsesp_allvalues), { headers }); + } + return new Response('Not Found', { status: 404 }); }); -// Event Source // TODO fix later - -// const data = { -// t: '000+00:00:00.000', -// l: 3, // error -// i: 1, -// n: 'system', -// m: 'incoming message #1' -// }; -// const sseFormattedResponse = `data: ${JSON.stringify(data)}\n\n`; -// router.get('/es/log', () => new Response(sseFormattedResponse, { headers: ESheaders })); - -var count = 8; var log_index = 0; const ES_LOG_ENDPOINT = ES_ENDPOINT_ROOT + 'log'; diff --git a/mock-api/package.json b/mock-api/package.json index 89545aa8c..d74eb41bd 100644 --- a/mock-api/package.json +++ b/mock-api/package.json @@ -11,7 +11,7 @@ "dependencies": { "@msgpack/msgpack": "^2.8.0", "busboy": "^1.6.0", - "itty-router": "^4.0.23" + "itty-router": "^4.0.25" }, "packageManager": "yarn@4.0.2", "devDependencies": { diff --git a/mock-api/server_notused.js b/mock-api/server_old.js similarity index 100% rename from mock-api/server_notused.js rename to mock-api/server_old.js diff --git a/mock-api/yarn.lock b/mock-api/yarn.lock index ef925865d..559bd51b0 100644 --- a/mock-api/yarn.lock +++ b/mock-api/yarn.lock @@ -129,7 +129,7 @@ __metadata: "@msgpack/msgpack": "npm:^2.8.0" "@types/multer": "npm:^1.4.11" busboy: "npm:^1.6.0" - itty-router: "npm:^4.0.23" + itty-router: "npm:^4.0.25" languageName: unknown linkType: soft @@ -142,10 +142,10 @@ __metadata: languageName: node linkType: hard -"itty-router@npm:^4.0.23": - version: 4.0.23 - resolution: "itty-router@npm:4.0.23" - checksum: b412f80f7d3bf78293144a43c47053c9873c98565e58aa188a66306cc6018ad3801fa157b0d694e9e1719cbc0424fcd89951f1916699eff6606f30b0645afa3e +"itty-router@npm:^4.0.25": + version: 4.0.25 + resolution: "itty-router@npm:4.0.25" + checksum: befa18a3cda3ab5fcbd21bcdeb87fd5c115e9538c0f5372170a7a73fb36906e373ea1be677d6b9c3cf1864c0f957caf37376e1f2b9e19fc275340fb0cbcbcb18 languageName: node linkType: hard diff --git a/platformio.ini b/platformio.ini index 69521fe02..eec50c32d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -2,10 +2,11 @@ ; override any settings with your own local ones in pio_local.ini [platformio] -default_envs = esp32_4M -; default_envs = lolin_s3 +; default_envs = esp32_4M +default_envs = lolin_s3 ; default_envs = esp32_16M ; default_envs = standalone +; default_envs = https extra_configs = factory_settings.ini @@ -13,8 +14,6 @@ extra_configs = [common] core_build_flags = - -D CORE_DEBUG_LEVEL=0 - -D NDEBUG -D ARDUINO_ARCH_ESP32=1 -D ESP32=1 ; -std=gnu++17 @@ -33,13 +32,14 @@ build_flags = -D NO_GLOBAL_ARDUINOOTA -D ARDUINOJSON_ENABLE_STD_STRING=1 -D ARDUINOJSON_USE_DOUBLE=0 + -D ARDUINOTRACE_ENABLE=0 ; -D CONFIG_UART_ISR_IN_IRAM unbuild_flags = ${common.core_unbuild_flags} [espressi32_base] -platform = espressif32@6.4.0 +platform = espressif32@6.5.0 framework = arduino board_build.filesystem = littlefs build_flags = ${common.build_flags} @@ -51,11 +51,17 @@ extra_scripts = [espressi32_base_tasmota] ; use Tasmota's libary which removes some libs (like mbedtsl) and increases available heap ; platform = https://github.com/tasmota/platform-espressif32.git ; latest development +; latest release with WiFi_secure.h platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.10.03/platform-espressif32-2023.10.03.zip ; latest stable -; platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.10.02/platform-espressif32.zip +; latest arduino 2.xx release: +; platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.11.01/platform-espressif32.zip +; latest arduino 3.0/IDF 5.1.(alpha 3): +; platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.12.10/platform-espressif32.zip framework = arduino board_build.filesystem = littlefs -build_flags = ${common.build_flags} +build_flags = + ${common.build_flags} + -DTASMOTA_SDK build_unflags = ${common.unbuild_flags} extra_scripts = pre:scripts/build_interface.py @@ -63,7 +69,7 @@ extra_scripts = [env] monitor_speed = 115200 -monitor_raw = yes +monitor_filters = esp32_exception_decoder upload_speed = 921600 build_type = release lib_ldf_mode = chain+ @@ -83,8 +89,6 @@ extra_scripts = scripts/rename_fw.py board = esp32dev board_build.partitions = esp32_partition_4M.csv board_build.extra_flags = -DBOARD_HAS_PSRAM -build_unflags = ${common.unbuild_flags} -build_flags = ${common.build_flags} [env:ci_s3] extends = espressi32_base @@ -95,7 +99,7 @@ board_upload.flash_size = 16MB board_build.partitions = esp32_partition_16M.csv build_unflags = ${common.unbuild_flags} build_flags = - ${common.build_flags} + ${espressi32_base.build_flags} -O2 '-DEMSESP_DEFAULT_BOARD_PROFILE="S32S3"' @@ -105,7 +109,7 @@ board = esp32dev board_upload.flash_size = 4MB board_build.partitions = esp32_partition_4M.csv build_flags = - ${common.build_flags} + ${espressi32_base_tasmota.build_flags} -Os [env:esp32_4Mplus] @@ -113,7 +117,6 @@ extends = espressi32_base_tasmota board = esp32dev board_upload.flash_size = 4MB board_build.partitions = esp32_asym_partition_4M.csv -build_flags = ${common.build_flags} [env:esp32_16M] extends = espressi32_base_tasmota @@ -121,7 +124,6 @@ board = esp32dev board_build.extra_flags = -DBOARD_HAS_PSRAM board_upload.flash_size = 16MB board_build.partitions = esp32_partition_16M.csv -build_flags = ${common.build_flags} [env:lolin_c3_mini] extends = espressi32_base_tasmota @@ -129,7 +131,7 @@ board = lolin_c3_mini board_upload.flash_size = 4MB board_build.partitions = esp32_partition_4M.csv build_flags = - ${common.build_flags} + ${espressi32_base_tasmota.build_flags} '-DEMSESP_DEFAULT_BOARD_PROFILE="C3MINI"' ; lolin C3 mini v1 needs special wifi init. @@ -140,7 +142,7 @@ board = lolin_c3_mini board_upload.flash_size = 4MB board_build.partitions = esp32_partition_4M.csv build_flags = - ${common.build_flags} + ${espressi32_base_tasmota.build_flags} -DBOARD_C3_MINI_V1 '-DEMSESP_DEFAULT_BOARD_PROFILE="C3MINI"' @@ -150,7 +152,7 @@ board = lolin_s2_mini board_upload.flash_size = 4MB board_build.partitions = esp32_partition_4M.csv build_flags = - ${common.build_flags} + ${espressi32_base_tasmota.build_flags} '-DEMSESP_DEFAULT_BOARD_PROFILE="S2MINI"' [env:lolin_s3] @@ -162,10 +164,58 @@ board_build.partitions = esp32_partition_16M.csv board_upload.use_1200bps_touch = false board_upload.wait_for_upload_port = false build_flags = - ${common.build_flags} + ${espressi32_base.build_flags} -O2 '-DEMSESP_DEFAULT_BOARD_PROFILE="S32S3"' +[env:https] +; use Tasmota's libary which removes some libs (like mbedtsl) and increases available heap +; platform = https://github.com/tasmota/platform-espressif32.git ; latest development +; +; latest release with WiFi_secure.h +; platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.10.03/platform-espressif32-2023.10.03.zip ; latest stable +; +; latest arduino 2.xx release: +; platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.11.01/platform-espressif32.zip +; +; latest arduino 3.0/IDF 5.1.(alpha 3): +; platform = https://github.com/platformio/platform-espressif32.git +; platform = https://github.com/Jason2866/platform-espressif32.git#Arduino/IDF5 +; platform_packages = https://github.com/espressif/arduino-esp32.git#3.0.0-alpha2 +platform = espressif32@6.5.0 +framework = arduino +board = esp32dev +board_build.filesystem = littlefs +board_build.f_cpu = 240000000L +board_upload.flash_size = 4MB +board_build.partitions = esp32_partition_4M.csv +; board_upload.flash_size = 16MB +; board_build.partitions = esp32_partition_16M.csv +board_upload.use_1200bps_touch = false +board_upload.wait_for_upload_port = true +upload_port = /dev/ttyUSB0 +extra_scripts = + ; pre:scripts/build_interface.py + scripts/rename_fw.py +build_unflags = ${common.unbuild_flags} +build_flags = + ${common.core_build_flags} + ${factory_settings.build_flags} + ${common.my_build_flags} + -D ONEWIRE_CRC16=0 + -D NO_GLOBAL_ARDUINOOTA + -D ARDUINOJSON_ENABLE_STD_STRING=1 + -D ARDUINOJSON_USE_DOUBLE=0 + ; -D ARDUINOTRACE_ENABLE=1 + -D ARDUINOTRACE_ENABLE=0 + -D TASMOTA_SDK + ; -D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_WARN + -D EMSESP_TEST + -D EMSESP_DEBUG + -D CONFIG_ETH_ENABLED + ; -D BOARD_HAS_PSRAM + '-DEMSESP_DEFAULT_BOARD_PROFILE="Test"' + ; to build and run: pio run -e standalone -t exec [env:standalone] platform = native @@ -176,7 +226,8 @@ build_flags = -lpthread -std=gnu++11 -Og -ggdb build_src_flags = - -Wall -Wextra -Werror -Wswitch-enum -Wno-unused-parameter -Wno-inconsistent-missing-override -Wno-unused-lambda-capture -Wno-sign-compare + ; -Wall -Wextra -Werror -Wswitch-enum -Wno-unused-parameter -Wno-inconsistent-missing-override -Wno-unused-lambda-capture -Wno-sign-compare + ; -Wall -Wextra -Werror -Wno-missing-braces -I./lib_standalone -I./lib/ArduinoJson/src diff --git a/scripts/api_test.http b/scripts/api_test.http index 07d6f49c2..2f5f4893d 100755 --- a/scripts/api_test.http +++ b/scripts/api_test.http @@ -2,20 +2,22 @@ # to be used with "REST Client" extension in Visual Studio Code (https://marketplace.visualstudio.com/items?itemName=humao.rest-client) # Open this file in VSC, modify the token, go to the API call and click on 'Send Request' (or Ctrl+Alt+R) # The response will be shown in the right panel +# +# You can also test with the command line +# test using CLI with: +# curl -X POST http://10.10.10.135/rest/signIn \ +# -H 'Content-Type: application/json' \ +# -d '{ "value" : 22 }' @host = http://ems-esp.local -@host_dev = http://ems-esp2.local +@host_dev = http://10.10.10.190 @token = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiYWRtaW4iOnRydWV9.2bHpWya2C7Q12WjNUBD6_7N3RCD7CMl-EGhyQVzFdDg GET {{host}}/api/system/info - ### - GET {{host}}/api/thermostat/seltemp - ### - POST {{host}}/api/thermostat/seltemp Content-Type: application/json Authorization: Bearer {{token}} @@ -23,9 +25,7 @@ Authorization: Bearer {{token}} { "value" : 21.0 } - ### - POST {{host}}/api/thermostat Content-Type: application/json Authorization: Bearer {{token}} @@ -34,9 +34,7 @@ Authorization: Bearer {{token}} "entity" : "seltemp", "value" : 21.0 } - ### - POST {{host}}/api Content-Type: application/json Authorization: Bearer {{token}} @@ -46,21 +44,14 @@ Authorization: Bearer {{token}} "entity" : "wwtapactivated", "value" : "on" } - ### - GET {{host}}/api/system/restart Authorization: Bearer {{token}} - ### - GET {{host}}/api/boiler/coldshot Authorization: Bearer {{token}} - ### - GET {{host}}/api/temperaturesensor/info - ### # @@ -68,30 +59,62 @@ GET {{host}}/api/temperaturesensor/info # GET {{host_dev}}/api/system/info - -# Run a test. EMS-ESP must be compiled with -DEMSESP_TEST -# Use this to load up a dummy thermostat and boiler with data - ### - GET {{host_dev}}/api?device=system&cmd=test&data=general - ### - +GET {{host_dev}}/api?device=system&cmd=info +### GET {{host_dev}}/api/boiler/info - ### GET {{host_dev}}/api/boiler/values - ### - GET {{host_dev}}/api/analogsensor/info - ### - GET {{host_dev}}/api/boiler/coldshot Authorization: Bearer {{token}} - ### GET {{host_dev}}/api/system/commands +### +POST {{host_dev}}/api/system +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "entity" : "info", + "id" : 0 +} +### +POST {{host_dev}}/api +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "device" : "system", + "entity" : "info", + "id" : 0 +} +### +GET {{host_dev}}/rest/features +### +GET {{host_dev}}/rest/getSettings +Authorization: Bearer {{token}} +### +POST {{host_dev}}/rest/signIn +Content-Type: application/json +### +GET {{host_dev}}/rest/coreData +Authorization: Bearer {{token}} +### +GET {{host_dev}}/rest/logSettings +### +GET {{host_dev}}/rest/systemStatus +Authorization: Bearer {{token}} +### +GET {{host_dev}}/rest/deviceData?id=1 +Authorization: Bearer {{token}} +### +GET {{host_dev}}/rest/networkSettings +Authorization: Bearer {{token}} + + diff --git a/src/analogsensor.cpp b/src/analogsensor.cpp index b94cb2c6d..25b32b57f 100644 --- a/src/analogsensor.cpp +++ b/src/analogsensor.cpp @@ -1,7 +1,7 @@ /* * EMS-ESP - https://github.com/emsesp/EMS-ESP * Copyright 2020 Paul Derbyshire - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or @@ -116,7 +116,7 @@ void AnalogSensor::reload() { } if (!found) { sensors_.emplace_back(sensor.gpio, sensor.name, sensor.offset, sensor.factor, sensor.uom, sensor.type); - sensors_.back().ha_registered = false; // this will trigger recrate of the HA config + sensors_.back().ha_registered = false; // this will trigger recreate of the HA config if (sensor.type == AnalogType::COUNTER || sensor.type >= AnalogType::DIGITAL_OUT) { sensors_.back().set_value(sensor.offset); } else { @@ -616,9 +616,9 @@ void AnalogSensor::publish_values(const bool force) { } JsonObject dev = config.createNestedObject("dev"); - dev["name"] = name; + dev["name"] = Mqtt::basename() + " Analog"; JsonArray ids = dev.createNestedArray("ids"); - ids.add(Mqtt::basename()); + ids.add(Mqtt::basename() + "-analog"); // add "availability" section Mqtt::add_avty_to_doc(stat_t, config.as(), val_cond); diff --git a/src/console.cpp b/src/console.cpp index 06fd98aba..85dd96e7a 100644 --- a/src/console.cpp +++ b/src/console.cpp @@ -608,13 +608,13 @@ void EMSESPShell::stopped() { // show welcome banner void EMSESPShell::display_banner() { println(); - printfln("┌───────────────────────────────────────┐"); - printfln("│ %sEMS-ESP version %-12s%s │", COLOR_BOLD_ON, EMSESP_APP_VERSION, COLOR_BOLD_OFF); - printfln("│ %s%shttps://github.com/emsesp/EMS-ESP32%s │", COLOR_BRIGHT_GREEN, COLOR_UNDERLINE, COLOR_RESET); - printfln("│ │"); - printfln("│ type %shelp%s to show available commands │", COLOR_UNDERLINE, COLOR_RESET); - printfln("│ use %ssu%s to access Admin commands │", COLOR_UNDERLINE, COLOR_RESET); - printfln("└───────────────────────────────────────┘"); + printfln("┌────────────────────────────────────────┐"); + printfln("│ %sEMS-ESP version %-13s%s │", COLOR_BOLD_ON, EMSESP_APP_VERSION, COLOR_BOLD_OFF); + printfln("│ %s%shttps://github.com/emsesp/EMS-ESP32%s │", COLOR_BRIGHT_GREEN, COLOR_UNDERLINE, COLOR_RESET); + printfln("│ │"); + printfln("│ type %shelp%s to show available commands │", COLOR_UNDERLINE, COLOR_RESET); + printfln("│ use %ssu%s to access Admin commands │", COLOR_UNDERLINE, COLOR_RESET); + printfln("└────────────────────────────────────────┘"); println(); // set console name diff --git a/src/default_settings.h b/src/default_settings.h index 92ae49a8e..dd9deffaa 100644 --- a/src/default_settings.h +++ b/src/default_settings.h @@ -252,4 +252,16 @@ enum { #error Target CONFIG_IDF_TARGET is not supported #endif +#ifndef ARDUINO_VERSION +#ifndef STRINGIZE +#define STRINGIZE(s) #s +#endif +#if TASMOTA_SDK +#define ARDUINO_VERSION_STR(major, minor, patch) "Tasmota Arduino v" STRINGIZE(major) "." STRINGIZE(minor) "." STRINGIZE(patch) +#else +#define ARDUINO_VERSION_STR(major, minor, patch) "ESP32 Arduino v" STRINGIZE(major) "." STRINGIZE(minor) "." STRINGIZE(patch) +#endif +#define ARDUINO_VERSION ARDUINO_VERSION_STR(ESP_ARDUINO_VERSION_MAJOR, ESP_ARDUINO_VERSION_MINOR, ESP_ARDUINO_VERSION_PATCH) +#endif + #endif diff --git a/src/device_library.h b/src/device_library.h index a955cf52e..59f995d6a 100644 --- a/src/device_library.h +++ b/src/device_library.h @@ -1,7 +1,7 @@ /* * EMS-ESP - https://github.com/emsesp/EMS-ESP * Copyright 2020-2023 Paul Derbyshire - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or @@ -20,7 +20,7 @@ /* * These is the EMS devices that we currently recognize - * The types and flags are stored in emsdevice.h + * The types and flags are stored in emsdevice.h */ // Boilers - 0x08 @@ -107,6 +107,7 @@ { 66, DeviceType::THERMOSTAT, "ES72/RC20", DeviceFlags::EMS_DEVICE_FLAG_RC20_N}, // 0x17 or remote { 76, DeviceType::THERMOSTAT, "ES73", DeviceFlags::EMS_DEVICE_FLAG_RC30_N}, // 0x10 {113, DeviceType::THERMOSTAT, "ES72/RC20", DeviceFlags::EMS_DEVICE_FLAG_RC20_N}, // 0x17 +{159, DeviceType::THERMOSTAT, "ES79", DeviceFlags::EMS_DEVICE_FLAG_RC35}, // 0x10 // Thermostat - Junkers - 0x10 {105, DeviceType::THERMOSTAT, "FW100", DeviceFlags::EMS_DEVICE_FLAG_JUNKERS}, @@ -181,4 +182,4 @@ // Generic - 0x40 or other with no product-id and no version {0, DeviceType::GENERIC, "unknown", DeviceFlags::EMS_DEVICE_FLAG_NONE} -// clang-format on \ No newline at end of file +// clang-format on diff --git a/src/devices/boiler.cpp b/src/devices/boiler.cpp index ceebb5e36..90ee562b1 100644 --- a/src/devices/boiler.cpp +++ b/src/devices/boiler.cpp @@ -2420,7 +2420,7 @@ bool Boiler::set_ww_mode(const char * value, const int8_t id) { if (is_received(EMS_TYPE_UBAParameterWWPlus)) { if (Helpers::value2enum(value, set, FL_(enum_comfort1))) { write_command(EMS_TYPE_UBAParameterWWPlus, 13, comfort[set], EMS_TYPE_UBAParameterWWPlus); - write_command(0x05, 70, set ? 0xAA : 0x55); // + write_command(0x05, 70, set == 0 ? 0xAA : 0x55); // return true; } } else { diff --git a/src/devices/thermostat.cpp b/src/devices/thermostat.cpp index 30602656a..d6ff921a6 100644 --- a/src/devices/thermostat.cpp +++ b/src/devices/thermostat.cpp @@ -4226,7 +4226,7 @@ void Thermostat::register_device_values() { #if defined(EMSESP_STANDALONE_DUMP) // if we're just dumping out values, create a single dummy hc - register_device_values_hc(std::make_shared(1, this->model())); // hc=1 + register_device_values_hc(std::make_shared(1, this->model())); // hc=1 #endif } diff --git a/src/emsdevice.cpp b/src/emsdevice.cpp index c9c6bc6a5..f62fa278b 100644 --- a/src/emsdevice.cpp +++ b/src/emsdevice.cpp @@ -1755,7 +1755,7 @@ void EMSdevice::mqtt_ha_entity_config_create() { } #ifndef EMSESP_STANDALONE // always create minimum one config - if (ESP.getMaxAllocHeap() < (6 * 1024) || (!emsesp::EMSESP::system_.PSram() && ESP.getFreeHeap() < (65 * 1024))) { + if (ESP.getMaxAllocHeap() < (6 * 1024) || (!EMSESP::system_.PSram() && ESP.getFreeHeap() < (65 * 1024))) { break; } #endif diff --git a/src/emsesp.cpp b/src/emsesp.cpp index 86b67b178..6054adf92 100644 --- a/src/emsesp.cpp +++ b/src/emsesp.cpp @@ -1,7 +1,7 @@ /* * EMS-ESP - https://github.com/emsesp/EMS-ESP * Copyright 2020-2023 Paul Derbyshire - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or @@ -22,9 +22,13 @@ static_assert(uuid::thread_safe, "uuid-common must be thread-safe"); static_assert(uuid::log::thread_safe, "uuid-log must be thread-safe"); static_assert(uuid::console::thread_safe, "uuid-console must be thread-safe"); +#ifndef EMSESP_STANDALONE +#include +#endif + namespace emsesp { -AsyncWebServer webServer(80); +PsychicHttpServer webServer; // web server is static and global #if defined(EMSESP_STANDALONE) FS dummyFS; @@ -61,7 +65,7 @@ uuid::log::Logger EMSESP::logger() { uuid::syslog::SyslogService System::syslog_; #endif -// The services +// Core EMS-ESP services RxService EMSESP::rxservice_; // incoming Telegram Rx handler TxService EMSESP::txservice_; // outgoing Telegram Tx handler Mqtt EMSESP::mqtt_; // mqtt handler @@ -69,7 +73,9 @@ System EMSESP::system_; // core system services TemperatureSensor EMSESP::temperaturesensor_; // Temperature sensors AnalogSensor EMSESP::analogsensor_; // Analog sensors Shower EMSESP::shower_; // Shower logic -Preferences EMSESP::nvs_; // NV Storage + +// NV Storage +Preferences EMSESP::nvs_; // static/common variables uint16_t EMSESP::watch_id_ = WATCH_ID_NONE; // for when log is TRACE. 0 means no trace set @@ -1153,7 +1159,7 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, const emsdevices.back()->unique_id(++unique_id_count_); // sort devices based on type - std::sort(emsdevices.begin(), emsdevices.end(), [](const std::unique_ptr & a, const std::unique_ptr & b) { + std::sort(emsdevices.begin(), emsdevices.end(), [](const std::unique_ptr & a, const std::unique_ptr & b) { return a->device_type() < b->device_type(); }); @@ -1389,17 +1395,81 @@ void EMSESP::scheduled_fetch_values() { } } -// EMSESP main class - EMSESP::EMSESP() #ifndef EMSESP_STANDALONE : telnet_([this](Stream & stream, const IPAddress & addr, uint16_t port) -> std::shared_ptr { - return std::make_shared(*this, stream, addr, port); + return std::make_shared(*this, stream, addr, port); }) #endif { } +// add web server endpoint +void EMSESP::handler(const char * uri, const char * contentType, const uint8_t * content, size_t len) { + PsychicHttpRequestCallback fn = [contentType, content, len](PsychicRequest * request) { + PsychicResponse response(request); + response.setCode(200); + response.setContentType(contentType); + response.addHeader("Content-Encoding", "gzip"); + // response.addHeader("Content-Encoding", "br"); // Brotli - only works over HTTPS + response.setContent(content, len); + return response.send(); + }; + + PsychicWebHandler * handler = new PsychicWebHandler(); + handler->onRequest(fn); + webServer.on(uri, HTTP_GET, handler); + + // Set default end-point for all non matching requests + // this is easier than using webServer.onNotFound() + if (strcmp(uri, "/index.html") == 0) { + webServer.defaultEndpoint->setHandler(handler); + } +}; + +// configure web server +// this can be only be done after the network has been initialized +void EMSESP::setupWeb() { + // set maximum number of uri handlers (.on() calls). default was 8 + // WWWData has 19 (in registerRoutes(handler) + // esp8266React services has 13 + // custom projects has around 23 + webServer.config.max_uri_handlers = 80; + // webServer.config.uri_match_fn = NULL; // don't use wildcards + + // webServer.config.task_priority = uxTaskPriorityGet(nullptr); // seems to make it slightly slower + + // TODO add support for https + webServer.listen(80); // start the web server + + DefaultHeaders::Instance().addHeader("Server", "EMS-ESP"); + +#ifndef EMSESP_STANDALONE + WWWData::registerRoutes(handler); // add webServer.on() endpoints from the generated web code +#endif + + esp8266React.registerURI(); // load up the core system web endpoints + + // init EMS-ESP web endpoints + webDataService.registerURI(); // /rest{deviceData,coreData,sensorData...} + webStatusService.registerURI(); // /rest/status + webSettingsService.registerURI(); // /rest/{settings,boardprofile} + webAPIService.registerURI(); // /api/*, /rest/{getSettings,getSchedule,getCustomizations,getEntities} + webLogService.registerURI(); // /rest/logSettings and EventSource + webCustomizationService.registerURI(); // /rest{customization,customizationEntities,resetCustomizations,devices,deviceEntities} + webSchedulerService.registerURI(); // /rest/schedule + webCustomEntityService.registerURI(); // /rest/customentities + + // Add CORS if specified in the network settings + esp8266React.getNetworkSettingsService()->read([&](NetworkSettings & networkSettings) { + if (networkSettings.enableCORS) { + DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", networkSettings.CORSOrigin); + DefaultHeaders::Instance().addHeader("Access-Control-Allow-Headers", "Accept, Content-Type, Authorization"); + DefaultHeaders::Instance().addHeader("Access-Control-Allow-Credentials", "true"); + } + }); +} + // start all the core services // the services must be loaded in the correct order void EMSESP::start() { @@ -1421,7 +1491,7 @@ void EMSESP::start() { // start the file system #ifndef EMSESP_STANDALONE if (!LittleFS.begin(true)) { - Serial.println("LittleFS Mount Failed. EMS-ESP stopped."); + Serial.println("LittleFS Mount Failed. Using default settings."); return; } #endif @@ -1429,7 +1499,7 @@ void EMSESP::start() { // do a quick scan of the filesystem to see if we have a /config folder // so we know if this is a new install or not #ifndef EMSESP_STANDALONE - File root = LittleFS.open("/config"); + File root = LittleFS.open("/config"); // FS_CONFIG_DIRECTORY bool factory_settings = !root; if (!root) { #if defined(EMSESP_DEBUG) @@ -1441,8 +1511,13 @@ void EMSESP::start() { bool factory_settings = false; #endif - esp8266React.begin(); // loads core system services settings (network, mqtt, ap, ntp etc) + esp8266React.begin(); // loads core system services settings (network, mqtt, ap, ntp etc) + + setupWeb(); // configure web server, now that the network has been initialized + webLogService.begin(); // start web log service. now we can start capturing logs to the web log + + // start Prefences, the internal persistance storage nvs_.begin("ems-esp", false, "nvs"); LOG_INFO("Starting EMS-ESP version %s", EMSESP_APP_VERSION); // welcome message @@ -1496,11 +1571,11 @@ void EMSESP::start() { #if defined(EMSESP_STANDALONE) Mqtt::on_connect(); // simulate an MQTT connection #endif - - webServer.begin(); // start the web server } // main loop calling all services + + void EMSESP::loop() { esp8266React.loop(); // web services system_.loop(); // does LED and checks system health, and syslog service @@ -1527,6 +1602,7 @@ void EMSESP::loop() { if (system_.telnet_enabled()) { telnet_.loop(); } + #else if (!shell_->running()) { ::exit(0); diff --git a/src/emsesp.h b/src/emsesp.h index e5d074460..ea02110b6 100644 --- a/src/emsesp.h +++ b/src/emsesp.h @@ -1,7 +1,7 @@ /* * EMS-ESP - https://github.com/emsesp/EMS-ESP * Copyright 2020-2023 Paul Derbyshire - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or @@ -69,7 +69,7 @@ // uses StaticJsonDocument #define EMSESP_JSON_SIZE_SMALL 256 #define EMSESP_JSON_SIZE_MEDIUM 768 -#define EMSESP_JSON_SIZE_LARGE 1024 // used in forming HA config payloads, also in *AsyncJsonResponse +#define EMSESP_JSON_SIZE_LARGE 1024 // used in forming HA config payloads // used in larger buffers like DynamicJsonDocument #define EMSESP_JSON_SIZE_XLARGE 2048 @@ -83,11 +83,11 @@ namespace emsesp { -using DeviceValueUOM = emsesp::DeviceValue::DeviceValueUOM; -using DeviceValueType = emsesp::DeviceValue::DeviceValueType; -using DeviceValueState = emsesp::DeviceValue::DeviceValueState; -using DeviceValueTAG = emsesp::DeviceValue::DeviceValueTAG; -using DeviceValueNumOp = emsesp::DeviceValue::DeviceValueNumOp; +using DeviceValueUOM = DeviceValue::DeviceValueUOM; +using DeviceValueType = DeviceValue::DeviceValueType; +using DeviceValueState = DeviceValue::DeviceValueState; +using DeviceValueTAG = DeviceValue::DeviceValueTAG; +using DeviceValueNumOp = DeviceValue::DeviceValueNumOp; // forward declarations for compiler class EMSESPShell; @@ -228,8 +228,10 @@ class EMSESP { static TxService txservice_; static Preferences nvs_; - // web controllers - static ESP8266React esp8266React; + // esp8266React framework + static ESP8266React esp8266React; + + // EMS-ESP web controllers static WebSettingsService webSettingsService; static WebStatusService webStatusService; static WebDataService webDataService; @@ -239,6 +241,8 @@ class EMSESP { static WebSchedulerService webSchedulerService; static WebCustomEntityService webCustomEntityService; + static void handler(const char * uri, const char * contentType, const uint8_t * content, size_t len); + private: static std::string device_tostring(const uint8_t device_id); static void process_UBADevices(std::shared_ptr telegram); @@ -282,8 +286,9 @@ class EMSESP { uuid::telnet::TelnetService telnet_; #endif + void setupWeb(); + protected: - // EMSESP(); static uuid::log::Logger logger_; }; diff --git a/src/helpers.cpp b/src/helpers.cpp index c9c5eb9b5..8158c2f7b 100644 --- a/src/helpers.cpp +++ b/src/helpers.cpp @@ -683,7 +683,6 @@ std::string Helpers::toUpper(std::string const & s) { // capitalizes one UTF-8 character in char array // works with Latin1 (1 byte), Polish amd some other (2 bytes) characters -// TODO add special characters that occur in other supported languages #if defined(EMSESP_STANDALONE) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wtype-limits" diff --git a/src/locale_translations.h b/src/locale_translations.h index f4e4f7b66..fb3492af2 100644 --- a/src/locale_translations.h +++ b/src/locale_translations.h @@ -29,818 +29,819 @@ #define EMSESP_LOCALE_FR "fr" #define EMSESP_LOCALE_TR "tr" #define EMSESP_LOCALE_IT "it" +#define EMSESP_LOCALE_SK "sk" -// IMPORTANT! translations are in the order:,en, de, nl, sv, pl, no, fr, tr, it +// IMPORTANT! translations are in the order:,en, de, nl, sv, pl, no, fr, tr, it, sk // // if there is no translation, it will default to en // - +// critcal characters: č, ď // device types, as display in Web and Console -MAKE_WORD_TRANSLATION(boiler_device, "Boiler", "Kessel", "CV ketel", "Värmepanna", "Kocioł", "Varmekjele", "", "Kazan", "Caldaia") // TODO translate -MAKE_WORD_TRANSLATION(thermostat_device, "Thermostat", "Thermostat", "Thermostaat", "Termostat", "Termostat", "Termostat", "", "Termostat", "Termostato") // TODO translate -MAKE_WORD_TRANSLATION(heatpump_device, "Heat Pump", "Wärmepumpe", "Warmtepomp", "Värmepump", "Pompa ciepła", "Varmepumpe", "", "Isı Pompası", "Pompa di Calore") // TODO translate -MAKE_WORD_TRANSLATION(solar_device, "Solar Module", "Solarmodul", "Solar Module", "Solmodul", "Moduł solarny", "Solmodul", "", "Güneş Enerjisi Cihazı", "Modulo Solare") // TODO translate -MAKE_WORD_TRANSLATION(connect_device, "Connect Module", "Verbindungsmodul", "Connect Module", "Uppkopplingsmodul", "Moduł przyłączeń", "Sammenkoblingsmodul", "", "Güneş Enerjisi Cihazı", "Modulo connessione") // TODO translate -MAKE_WORD_TRANSLATION(mixer_device, "Mixer Module", "Mischermodul", "Mixer Module", "Blandningsmodul", "Moduł mieszacza", "Miksermodul", "", "Karışım Cihazı", "Modulo Miscela") // TODO translate -MAKE_WORD_TRANSLATION(controller_device, "Controller Module", "Kontrollmodul", "Controller Module", "Styrmodul", "Moduł sterujący", "Styremodul", "", "Kontrol Ünitesi", "Modulo Controllo") // TODO translate -MAKE_WORD_TRANSLATION(switch_device, "Switch Module", "Schaltmodul", "Switch Module", "Relämodul", "Moduł przełączający", "Switch modul", "", "Anahtar", "Modulo Switch") // TODO translate -MAKE_WORD_TRANSLATION(gateway_device, "Gateway Module", "Gateway Modul", "Gateway Module", "Gateway", "Moduł IP", "Gateway", "", "Ağ Geçidi", "Modulo Gateway") // TODO translate -MAKE_WORD_TRANSLATION(alert_device, "Alert Module", "Alarmmodul", "Alert Module", "Larmmodul", "Moduł alarmowy", "Alarmmodul", "", "Alarm Cihazı", "Module Avviso") // TODO translate -//MAKE_WORD_TRANSLATION(pump_device, "Pump Module", "Pumpenmodul", "Pump Module", "Pumpmodul", "Moduł pompy", "Pumpemodul", "", "Pompa", "Module Pompa") // TODO translate -MAKE_WORD_TRANSLATION(extension_device, "Extension Module", "Erweiterungsnmodul", "Module", "Modul", "Moduł rozszerzeń", "Modul", "", "", "Module") // TODO translate -MAKE_WORD_TRANSLATION(heatsource_device, "Heatsource", "Heizquelle", "Heatsource", "Värmekälla", "Źródło ciepła", "Varmekilde", "", "Isı Kaynağı", "Fonte di calore") // TODO translate -MAKE_WORD_TRANSLATION(sensors_device, "Sensors", "Sensoren", "Sensoren", "Sensorer", "Czujniki", "Sensorer", "Capteurs", "Sensör Cihazı", "Sensori") -MAKE_WORD_TRANSLATION(unknown_device, "Unknown", "Unbekannt", "Onbekend", "Okänt", "Nieznane urządzenie", "Ukjent", "Inconnu", "Bilinmeyen", "Sconosciuto") -MAKE_WORD_TRANSLATION(custom_device, "Custom", "Nutzerdefiniert", "Aangepast", "", "Niestandardowe", "", "", "Özel", "Personalizzato") // TODO translate -MAKE_WORD_TRANSLATION(custom_device_name, "User defined entities", "Nutzer deklarierte Entitäten", "Gebruiker gedefineerd", "", "Encje zdefiniowane przez użytkownika", "", "", "Kullanıcı tarafından tanımlanmış varlıklar", "Entità definita da utente") // TODO translate -MAKE_WORD_TRANSLATION(ventilation_device, "Ventilation", "Lüftung", "Ventilatie", "", "Wentylacja", "", "", "Havalandırma", "Ventilazione") // TODO translate +MAKE_WORD_TRANSLATION(boiler_device, "Boiler", "Kessel", "CV ketel", "Värmepanna", "Kocioł", "Varmekjele", "", "Kazan", "Caldaia", "Bojler") // TODO translate +MAKE_WORD_TRANSLATION(thermostat_device, "Thermostat", "Thermostat", "Thermostaat", "Termostat", "Termostat", "Termostat", "", "Termostat", "Termostato", "Termostat") // TODO translate +MAKE_WORD_TRANSLATION(heatpump_device, "Heat Pump", "Wärmepumpe", "Warmtepomp", "Värmepump", "Pompa ciepła", "Varmepumpe", "", "Isı Pompası", "Pompa di Calore", "Tepelné čerpadlo") // TODO translate +MAKE_WORD_TRANSLATION(solar_device, "Solar Module", "Solarmodul", "Solar Module", "Solmodul", "Moduł solarny", "Solmodul", "", "Güneş Enerjisi Cihazı", "Modulo Solare", "Solárny modul") // TODO translate +MAKE_WORD_TRANSLATION(connect_device, "Connect Module", "Verbindungsmodul", "Connect Module", "Uppkopplingsmodul", "Moduł przyłączeń", "Sammenkoblingsmodul", "", "Güneş Enerjisi Cihazı", "Modulo connessione", "Pripojte modul") // TODO translate +MAKE_WORD_TRANSLATION(mixer_device, "Mixer Module", "Mischermodul", "Mixer Module", "Blandningsmodul", "Moduł mieszacza", "Miksermodul", "", "Karışım Cihazı", "Modulo Miscela", "Mixer modul") // TODO translate +MAKE_WORD_TRANSLATION(controller_device, "Controller Module", "Kontrollmodul", "Controller Module", "Styrmodul", "Moduł sterujący", "Styremodul", "", "Kontrol Ünitesi", "Modulo Controllo", "Modul ovládača") // TODO translate +MAKE_WORD_TRANSLATION(switch_device, "Switch Module", "Schaltmodul", "Switch Module", "Relämodul", "Moduł przełączający", "Switch modul", "", "Anahtar", "Modulo Switch", "Spínací modul") // TODO translate +MAKE_WORD_TRANSLATION(gateway_device, "Gateway Module", "Gateway Modul", "Gateway Module", "Gateway", "Moduł IP", "Gateway", "", "Ağ Geçidi", "Modulo Gateway", "Gateway modul") // TODO translate +MAKE_WORD_TRANSLATION(alert_device, "Alert Module", "Alarmmodul", "Alert Module", "Larmmodul", "Moduł alarmowy", "Alarmmodul", "", "Alarm Cihazı", "Module Avviso", "Modul upozornení") // TODO translate +//MAKE_WORD_TRANSLATION(pump_device, "Pump Module", "Pumpenmodul", "Pump Module", "Pumpmodul", "Moduł pompy", "Pumpemodul", "", "Pompa", "Module Pompa", "Modul čerpadla") // TODO translate +MAKE_WORD_TRANSLATION(extension_device, "Extension Module", "Erweiterungsnmodul", "Module", "Modul", "Moduł rozszerzeń", "Modul", "", "", "Module", "Rozširujúci modul") // TODO translate +MAKE_WORD_TRANSLATION(heatsource_device, "Heatsource", "Heizquelle", "Heatsource", "Värmekälla", "Źródło ciepła", "Varmekilde", "", "Isı Kaynağı", "Fonte di calore", "Tepelný zdroj") // TODO translate +MAKE_WORD_TRANSLATION(sensors_device, "Sensors", "Sensoren", "Sensoren", "Sensorer", "Czujniki", "Sensorer", "Capteurs", "Sensör Cihazı", "Sensori", "Snímače") +MAKE_WORD_TRANSLATION(unknown_device, "Unknown", "Unbekannt", "Onbekend", "Okänt", "Nieznane urządzenie", "Ukjent", "Inconnu", "Bilinmeyen", "Sconosciuto", "Neznámy") +MAKE_WORD_TRANSLATION(custom_device, "Custom", "Nutzerdefiniert", "Aangepast", "", "Niestandardowe", "", "", "Özel", "Personalizzato", "Vlastné") // TODO translate +MAKE_WORD_TRANSLATION(custom_device_name, "User defined entities", "Nutzer deklarierte Entitäten", "Gebruiker gedefineerd", "", "Encje zdefiniowane przez użytkownika", "", "", "Kullanıcı tarafından tanımlanmış varlıklar", "Entità definita da utente", "Používateľom definované entity") // TODO translate +MAKE_WORD_TRANSLATION(ventilation_device, "Ventilation", "Lüftung", "Ventilatie", "", "Wentylacja", "", "", "Havalandırma", "Ventilazione", "Vetranie") // TODO translate // commands -MAKE_WORD_TRANSLATION(info_cmd, "lists all values", "Liste aller Werte", "lijst van alle waardes", "", "wyświetl wszystkie wartości", "Viser alle verdier", "", "Tüm değerleri listele", "elenca tutti i valori") // TODO translate -MAKE_WORD_TRANSLATION(commands_cmd, "lists all commands", "Liste aller Kommandos", "lijst van alle commando's", "", "wyświetl wszystkie komendy", "Viser alle kommandoer", "", "Tüm komutları listele", "elencaa tutti i comandi") // TODO translate -MAKE_WORD_TRANSLATION(entities_cmd, "lists all entities", "Liste aller Entitäten", "lijst van alle entiteiten", "", "wyświetl wszsytkie encje", "Viser alle enheter", "", "Tüm varlıkları listele", "elenca tutte le entità") // TODO translate -MAKE_WORD_TRANSLATION(send_cmd, "send a telegram", "Sende EMS-Telegramm", "stuur een telegram", "", "wyślij telegram", "send et telegram", "", "Bir telegram gönder", "invia un telegramma") // TODO translate -MAKE_WORD_TRANSLATION(setiovalue_cmd, "set io value", "Setze Wertevorgabe", "instellen standaardwaarde", "", "ustaw wartość", "sett en io verdi", "", "Giriş/Çıkış değerlerini ayarla", "imposta valore io") // TODO translate -MAKE_WORD_TRANSLATION(changeloglevel_cmd, "change log level", "Ändere Sysloglevel", "aanpassen log niveau", "", "zmień poziom log-u", "endre loggnivå", "", "Kayıt seviyesini değiştir", "cambia livello registrazione") // TODO translate -MAKE_WORD_TRANSLATION(fetch_cmd, "refresh all EMS values", "Lese alle EMS-Werte neu", "Verversen alle EMS waardes", "", "odśwież wszystkie wartości EMS", "oppfrisk alle EMS verdier", "", "Bütün EMS değerlerini yenile", "aggiornare tutti i valori EMS") // TODO translate -MAKE_WORD_TRANSLATION(restart_cmd, "restart EMS-ESP", "Neustart", "opnieuw opstarten", "", "uruchom ponownie EMS-ESP", "restart EMS-ESP", "redémarrer EMS-ESP", "EMS-ESPyi yeniden başlat", "riavvia EMS-ESP") // TODO translate -MAKE_WORD_TRANSLATION(watch_cmd, "watch incoming telegrams", "Watch auf eingehende Telegramme", "inkomende telegrammen bekijken", "", "obserwuj przyczodzące telegramy", "se innkommende telegrammer", "", "Gelen telegramları ", "guardare i telegrammi in arrivo") // TODO translate -MAKE_WORD_TRANSLATION(publish_cmd, "publish all to MQTT", "Publiziere MQTT", "publiceer alles naar MQTT", "", "opublikuj wszystko na MQTT", "Publiser alt til MQTT", "", "Hepsini MQTTye gönder", "pubblica tutto su MQTT") // TODO translate -MAKE_WORD_TRANSLATION(system_info_cmd, "show system status", "Zeige System-Status", "toon systeemstatus", "", "pokaż status systemu", "vis system status", "", "Sistem Durumunu Göster", "visualizza stati di sistema") // TODO translate -MAKE_WORD_TRANSLATION(schedule_cmd, "enable schedule item", "Aktiviere Zeitplan", "activeer tijdschema item", "", "aktywuj wybrany harmonogram", "", "", "program öğesini etkinleştir", "abilitare l'elemento programmato") // TODO translate -MAKE_WORD_TRANSLATION(entity_cmd, "set custom value on ems", "Sende eigene Entitäten zu EMS", "verstuur custom waarde naar EMS", "", "wyślij własną wartość na EMS", "", "", "emp üzerinde özel değer ayarla", "imposta valori personalizzati su EMS") // TODO translate -MAKE_WORD_TRANSLATION(commands_response, "get response","Hole Antwort","Verzoek om antwoord", "", "", "", "uzyskaj odpowiedź", "", "", "gelen cevap", "") // TODO translate -MAKE_WORD_TRANSLATION(coldshot_cmd, "send a cold shot of water", "", "", "", "uruchom tryśnięcie zimnej wody", "", "", "soğuk su gönder", "") // TODO translate -MAKE_WORD_TRANSLATION(allvalues_cmd, "output all values", "", "", "", "", "", "", "", "") // TODO translate +MAKE_WORD_TRANSLATION(info_cmd, "lists all values", "Liste aller Werte", "lijst van alle waardes", "", "wyświetl wszystkie wartości", "Viser alle verdier", "", "Tüm değerleri listele", "elenca tutti i valori", "zobraziť všetky hodnoty") // TODO translate +MAKE_WORD_TRANSLATION(commands_cmd, "lists all commands", "Liste aller Kommandos", "lijst van alle commando's", "", "wyświetl wszystkie komendy", "Viser alle kommandoer", "", "Tüm komutları listele", "elencaa tutti i comandi", "zobraziť všetky príkazy") // TODO translate +MAKE_WORD_TRANSLATION(entities_cmd, "lists all entities", "Liste aller Entitäten", "lijst van alle entiteiten", "", "wyświetl wszsytkie encje", "Viser alle enheter", "", "Tüm varlıkları listele", "elenca tutte le entità", "zobraziť všetky entity") // TODO translate +MAKE_WORD_TRANSLATION(send_cmd, "send a telegram", "Sende EMS-Telegramm", "stuur een telegram", "", "wyślij telegram", "send et telegram", "", "Bir telegram gönder", "invia un telegramma", "poslať telegram") // TODO translate +MAKE_WORD_TRANSLATION(setiovalue_cmd, "set io value", "Setze Wertevorgabe", "instellen standaardwaarde", "", "ustaw wartość", "sett en io verdi", "", "Giriş/Çıkış değerlerini ayarla", "imposta valore io", "nastaviť hodnotu io") // TODO translate +MAKE_WORD_TRANSLATION(changeloglevel_cmd, "change log level", "Ändere Sysloglevel", "aanpassen log niveau", "", "zmień poziom log-u", "endre loggnivå", "", "Kayıt seviyesini değiştir", "cambia livello registrazione", "") // TODO translate +MAKE_WORD_TRANSLATION(fetch_cmd, "refresh all EMS values", "Lese alle EMS-Werte neu", "Verversen alle EMS waardes", "", "odśwież wszystkie wartości EMS", "oppfrisk alle EMS verdier", "", "Bütün EMS değerlerini yenile", "aggiornare tutti i valori EMS", "obnoviť všetky hodnoty EMS") // TODO translate +MAKE_WORD_TRANSLATION(restart_cmd, "restart EMS-ESP", "Neustart", "opnieuw opstarten", "", "uruchom ponownie EMS-ESP", "restart EMS-ESP", "redémarrer EMS-ESP", "EMS-ESPyi yeniden başlat", "riavvia EMS-ESP", "reštart EMS-ESP") // TODO translate +MAKE_WORD_TRANSLATION(watch_cmd, "watch incoming telegrams", "Watch auf eingehende Telegramme", "inkomende telegrammen bekijken", "", "obserwuj przyczodzące telegramy", "se innkommende telegrammer", "", "Gelen telegramları", "guardare i telegrammi in arrivo", "sledovať prichádzajúce telegramy") // TODO translate +MAKE_WORD_TRANSLATION(publish_cmd, "publish all to MQTT", "Publiziere MQTT", "publiceer alles naar MQTT", "", "opublikuj wszystko na MQTT", "Publiser alt til MQTT", "", "Hepsini MQTTye gönder", "pubblica tutto su MQTT", "zverejniť všetko na MQTT") // TODO translate +MAKE_WORD_TRANSLATION(system_info_cmd, "show system status", "Zeige System-Status", "toon systeemstatus", "", "pokaż status systemu", "vis system status", "", "Sistem Durumunu Göster", "visualizza stati di sistema", "zobraziť stav systému") // TODO translate +MAKE_WORD_TRANSLATION(schedule_cmd, "enable schedule item", "Aktiviere Zeitplan", "activeer tijdschema item", "", "aktywuj wybrany harmonogram", "", "", "program öğesini etkinleştir", "abilitare l'elemento programmato", "povoliť položku plánu") // TODO translate +MAKE_WORD_TRANSLATION(entity_cmd, "set custom value on ems", "Sende eigene Entitäten zu EMS", "verstuur custom waarde naar EMS", "", "wyślij własną wartość na EMS", "", "", "emp üzerinde özel değer ayarla", "imposta valori personalizzati su EMS", "nastaviť vlastnú hodnotu na ems") // TODO translate +MAKE_WORD_TRANSLATION(commands_response, "get response", "Hole Antwort", "Verzoek om antwoord", "", "uzyskaj odpowiedź", "", "", "gelen cevap", "", "získať odpoveď") // TODO translate +MAKE_WORD_TRANSLATION(coldshot_cmd, "send a cold shot of water", "", "", "", "uruchom tryśnięcie zimnej wody", "", "", "soğuk su gönder", "", "pošlite studenú dávku vody") // TODO translate +MAKE_WORD_TRANSLATION(allvalues_cmd, "output all values", "", "", "", "", "", "", "", "", "vypísať všetky hodnoty") // TODO translate // tags -MAKE_WORD_TRANSLATION(tag_boiler_data_ww, "dhw", "WW", "dhw", "VV", "CWU", "dhw", "ecs", "SKS", "dhw") -MAKE_WORD_TRANSLATION(tag_device_data_ww, "dhw", "WW", "dhw", "VV", "CWU", "dhw", "ecs", "SKS", "dhw") -MAKE_WORD_TRANSLATION(tag_hc1, "hc1", "HK1", "hc1", "VK1", "OG1", "hc1", "hc1", "ID1", "hc1") -MAKE_WORD_TRANSLATION(tag_hc2, "hc2", "HK2", "hc2", "VK2", "OG2", "hc2", "hc2", "ID2", "hc2") -MAKE_WORD_TRANSLATION(tag_hc3, "hc3", "HK3", "hc3", "VK3", "OG3", "hc3", "hc3", "ID3", "hc3") -MAKE_WORD_TRANSLATION(tag_hc4, "hc4", "HK4", "hc4", "VK4", "OG4", "hc4", "hc4", "ID4", "hc4") -MAKE_WORD_TRANSLATION(tag_hc5, "hc5", "HK5", "hc5", "VK5", "OG5", "hc5", "hc5", "ID5", "hc5") -MAKE_WORD_TRANSLATION(tag_hc6, "hc6", "HK6", "hc6", "vk6", "OG6", "hc6", "hc6", "ID6", "hc6") -MAKE_WORD_TRANSLATION(tag_hc7, "hc7", "HK7", "hc7", "VK7", "OG7", "hc7", "hc7", "ID7", "hc7") -MAKE_WORD_TRANSLATION(tag_hc8, "hc8", "HK8", "hc8", "VK8", "OG8", "hc8", "hc8", "ID8", "hc8") -MAKE_WORD_TRANSLATION(tag_wwc1, "wwc1", "WWK1", "wwc1", "VVK1", "CWU1", "wwc1", "wwc1", "SKS1", "wwc1") -MAKE_WORD_TRANSLATION(tag_wwc2, "wwc2", "WWK2", "wwc2", "VVK2", "CWU2", "wwc2", "wwc2", "SKS2", "wwc2") -MAKE_WORD_TRANSLATION(tag_wwc3, "wwc3", "WWK3", "wwc3", "VVK3", "CWU3", "wwc3", "wwc3", "SKS3", "wwc3") -MAKE_WORD_TRANSLATION(tag_wwc4, "wwc4", "WWK4", "wwc4", "VVK4", "CWU4", "wwc4", "wwc4", "SKS4", "wwc4") -MAKE_WORD_TRANSLATION(tag_wwc5, "wwc5", "WWK5", "wwc5", "VVK5", "CWU5", "wwc5", "wwc5", "SKS5", "wwc5") -MAKE_WORD_TRANSLATION(tag_wwc6, "wwc6", "WWK6", "wwc6", "VVK6", "CWU6", "wwc6", "wwc6", "SKS6", "wwc6") -MAKE_WORD_TRANSLATION(tag_wwc7, "wwc7", "WWK7", "wwc7", "VVK7", "CWU7", "wwc7", "wwc7", "SKS7", "wwc7") -MAKE_WORD_TRANSLATION(tag_wwc8, "wwc8", "WWK8", "wwc8", "VVK8", "CWU8", "wwc8", "wwc8", "SKS8", "wwc8") -MAKE_WORD_TRANSLATION(tag_wwc9, "wwc9", "WWK9", "wwc9", "VVK9", "CWU9", "wwc9", "wwc9", "SKS9", "wwc9") -MAKE_WORD_TRANSLATION(tag_wwc10, "wwc10", "WWK10", "wwc10", "VVK10", "CWU10", "wwc10", "wwc10", "SKS10", "wwc10") -MAKE_WORD_TRANSLATION(tag_ahs1, "ahs1", "AHQ1", "ahs1", "AVK1", "AŹC1", "ahs1", "ahs1", "ahs1", "ahs1") -MAKE_WORD_TRANSLATION(tag_hs1, "hs1", "hs1", "hs1", "VK1", "ŹC1", "hs1", "hs1", "hs1", "hs1") -MAKE_WORD_TRANSLATION(tag_hs2, "hs2", "hs2", "hs2", "VK2", "ŹC2", "hs2", "hs2", "hs2", "hs2") -MAKE_WORD_TRANSLATION(tag_hs3, "hs3", "hs3", "hs3", "VK3", "ŹC3", "hs3", "hs3", "hs3", "hs3") -MAKE_WORD_TRANSLATION(tag_hs4, "hs4", "hs4", "hs4", "VK4", "ŹC4", "hs4", "hs4", "hs4", "hs4") -MAKE_WORD_TRANSLATION(tag_hs5, "hs5", "hs5", "hs5", "VK5", "ŹC5", "hs5", "hs5", "hs5", "hs5") -MAKE_WORD_TRANSLATION(tag_hs6, "hs6", "hs6", "hs6", "VK6", "ŹC6", "hs6", "hs6", "hs6", "hs6") -MAKE_WORD_TRANSLATION(tag_hs7, "hs7", "hs7", "hs7", "VK7", "ŹC7", "hs7", "hs7", "hs7", "hs7") -MAKE_WORD_TRANSLATION(tag_hs8, "hs8", "hs8", "hs8", "VK8", "ŹC8", "hs8", "hs8", "hs8", "hs8") -MAKE_WORD_TRANSLATION(tag_hs9, "hs9", "hs9", "hs9", "VK9", "ŹC9", "hs9", "hs9", "hs9", "hs9") -MAKE_WORD_TRANSLATION(tag_hs10, "hs10", "hs10", "hs10", "VK10", "ŹC10", "hs10", "hs10", "hs10", "hs10") -MAKE_WORD_TRANSLATION(tag_hs11, "hs11", "hs11", "hs11", "VK11", "ŹC11", "hs11", "hs11", "hs11", "hs11") -MAKE_WORD_TRANSLATION(tag_hs12, "hs12", "hs12", "hs12", "VK12", "ŹC12", "hs12", "hs12", "hs12", "hs12") -MAKE_WORD_TRANSLATION(tag_hs13, "hs13", "hs13", "hs13", "VK13", "ŹC13", "hs13", "hs13", "hs13", "hs13") -MAKE_WORD_TRANSLATION(tag_hs14, "hs14", "hs14", "hs14", "VK14", "ŹC14", "hs14", "hs14", "hs14", "hs14") -MAKE_WORD_TRANSLATION(tag_hs15, "hs15", "hs15", "hs15", "VK15", "ŹC15", "hs15", "hs15", "hs15", "hs15") -MAKE_WORD_TRANSLATION(tag_hs16, "hs16", "hs16", "hs16", "VK16", "ŹC16", "hs16", "hs16", "hs16", "hs16") +MAKE_WORD_TRANSLATION(tag_boiler_data_ww, "dhw", "WW", "dhw", "VV", "CWU", "dhw", "ecs", "SKS", "dhw", "TÚV") +MAKE_WORD_TRANSLATION(tag_device_data_ww, "dhw", "WW", "dhw", "VV", "CWU", "dhw", "ecs", "SKS", "dhw", "TÚV") +MAKE_WORD_TRANSLATION(tag_hc1, "hc1", "HK1", "hc1", "VK1", "OG1", "hc1", "hc1", "ID1", "hc1", "hc1") +MAKE_WORD_TRANSLATION(tag_hc2, "hc2", "HK2", "hc2", "VK2", "OG2", "hc2", "hc2", "ID2", "hc2", "hc2") +MAKE_WORD_TRANSLATION(tag_hc3, "hc3", "HK3", "hc3", "VK3", "OG3", "hc3", "hc3", "ID3", "hc3", "hc3") +MAKE_WORD_TRANSLATION(tag_hc4, "hc4", "HK4", "hc4", "VK4", "OG4", "hc4", "hc4", "ID4", "hc4", "hc4") +MAKE_WORD_TRANSLATION(tag_hc5, "hc5", "HK5", "hc5", "VK5", "OG5", "hc5", "hc5", "ID5", "hc5", "hc5") +MAKE_WORD_TRANSLATION(tag_hc6, "hc6", "HK6", "hc6", "vk6", "OG6", "hc6", "hc6", "ID6", "hc6", "hc6") +MAKE_WORD_TRANSLATION(tag_hc7, "hc7", "HK7", "hc7", "VK7", "OG7", "hc7", "hc7", "ID7", "hc7", "hc7") +MAKE_WORD_TRANSLATION(tag_hc8, "hc8", "HK8", "hc8", "VK8", "OG8", "hc8", "hc8", "ID8", "hc8", "hc8") +MAKE_WORD_TRANSLATION(tag_wwc1, "wwc1", "WWK1", "wwc1", "VVK1", "CWU1", "wwc1", "wwc1", "SKS1", "wwc1", "wwc1") +MAKE_WORD_TRANSLATION(tag_wwc2, "wwc2", "WWK2", "wwc2", "VVK2", "CWU2", "wwc2", "wwc2", "SKS2", "wwc2", "wwc2") +MAKE_WORD_TRANSLATION(tag_wwc3, "wwc3", "WWK3", "wwc3", "VVK3", "CWU3", "wwc3", "wwc3", "SKS3", "wwc3", "wwc3") +MAKE_WORD_TRANSLATION(tag_wwc4, "wwc4", "WWK4", "wwc4", "VVK4", "CWU4", "wwc4", "wwc4", "SKS4", "wwc4", "wwc4") +MAKE_WORD_TRANSLATION(tag_wwc5, "wwc5", "WWK5", "wwc5", "VVK5", "CWU5", "wwc5", "wwc5", "SKS5", "wwc5", "wwc5") +MAKE_WORD_TRANSLATION(tag_wwc6, "wwc6", "WWK6", "wwc6", "VVK6", "CWU6", "wwc6", "wwc6", "SKS6", "wwc6", "wwc6") +MAKE_WORD_TRANSLATION(tag_wwc7, "wwc7", "WWK7", "wwc7", "VVK7", "CWU7", "wwc7", "wwc7", "SKS7", "wwc7", "wwc7") +MAKE_WORD_TRANSLATION(tag_wwc8, "wwc8", "WWK8", "wwc8", "VVK8", "CWU8", "wwc8", "wwc8", "SKS8", "wwc8", "wwc8") +MAKE_WORD_TRANSLATION(tag_wwc9, "wwc9", "WWK9", "wwc9", "VVK9", "CWU9", "wwc9", "wwc9", "SKS9", "wwc9", "wwc9") +MAKE_WORD_TRANSLATION(tag_wwc10, "wwc10", "WWK10", "wwc10", "VVK10", "CWU10", "wwc10", "wwc10", "SKS10", "wwc10", "wwc10") +MAKE_WORD_TRANSLATION(tag_ahs1, "ahs1", "AHQ1", "ahs1", "AVK1", "AŹC1", "ahs1", "ahs1", "ahs1", "ahs1", "ahs1") +MAKE_WORD_TRANSLATION(tag_hs1, "hs1", "hs1", "hs1", "VK1", "ŹC1", "hs1", "hs1", "hs1", "hs1", "hs1") +MAKE_WORD_TRANSLATION(tag_hs2, "hs2", "hs2", "hs2", "VK2", "ŹC2", "hs2", "hs2", "hs2", "hs2", "hs2") +MAKE_WORD_TRANSLATION(tag_hs3, "hs3", "hs3", "hs3", "VK3", "ŹC3", "hs3", "hs3", "hs3", "hs3", "hs3") +MAKE_WORD_TRANSLATION(tag_hs4, "hs4", "hs4", "hs4", "VK4", "ŹC4", "hs4", "hs4", "hs4", "hs4", "hs4") +MAKE_WORD_TRANSLATION(tag_hs5, "hs5", "hs5", "hs5", "VK5", "ŹC5", "hs5", "hs5", "hs5", "hs5", "hs5") +MAKE_WORD_TRANSLATION(tag_hs6, "hs6", "hs6", "hs6", "VK6", "ŹC6", "hs6", "hs6", "hs6", "hs6", "hs6") +MAKE_WORD_TRANSLATION(tag_hs7, "hs7", "hs7", "hs7", "VK7", "ŹC7", "hs7", "hs7", "hs7", "hs7", "hs7") +MAKE_WORD_TRANSLATION(tag_hs8, "hs8", "hs8", "hs8", "VK8", "ŹC8", "hs8", "hs8", "hs8", "hs8", "hs8") +MAKE_WORD_TRANSLATION(tag_hs9, "hs9", "hs9", "hs9", "VK9", "ŹC9", "hs9", "hs9", "hs9", "hs9", "hs9") +MAKE_WORD_TRANSLATION(tag_hs10, "hs10", "hs10", "hs10", "VK10", "ŹC10", "hs10", "hs10", "hs10", "hs10", "hs10") +MAKE_WORD_TRANSLATION(tag_hs11, "hs11", "hs11", "hs11", "VK11", "ŹC11", "hs11", "hs11", "hs11", "hs11", "hs11") +MAKE_WORD_TRANSLATION(tag_hs12, "hs12", "hs12", "hs12", "VK12", "ŹC12", "hs12", "hs12", "hs12", "hs12", "hs12") +MAKE_WORD_TRANSLATION(tag_hs13, "hs13", "hs13", "hs13", "VK13", "ŹC13", "hs13", "hs13", "hs13", "hs13", "hs13") +MAKE_WORD_TRANSLATION(tag_hs14, "hs14", "hs14", "hs14", "VK14", "ŹC14", "hs14", "hs14", "hs14", "hs14", "hs14") +MAKE_WORD_TRANSLATION(tag_hs15, "hs15", "hs15", "hs15", "VK15", "ŹC15", "hs15", "hs15", "hs15", "hs15", "hs15") +MAKE_WORD_TRANSLATION(tag_hs16, "hs16", "hs16", "hs16", "VK16", "ŹC16", "hs16", "hs16", "hs16", "hs16", "hs16") // General -MAKE_WORD_TRANSLATION(on, "on", "an", "aan", "på", "włączono", "på", "on", "açık", "on") -MAKE_WORD_TRANSLATION(off, "off", "aus", "uit", "av", "wyłączono", "av", "off", "kapalı", "off") -MAKE_WORD_TRANSLATION(ON, "ON", "AN", "AAN", "PÅ", "wł.", "PÅ", "ON", "AÇIK", "ON") -MAKE_WORD_TRANSLATION(OFF, "OFF", "AUS", "UIT", "AV", "wył.", "AV", "OFF", "KAPALI", "OFF") +MAKE_WORD_TRANSLATION(on, "on", "an", "aan", "på", "włączono", "på", "on", "açık", "on", "zap") +MAKE_WORD_TRANSLATION(off, "off", "aus", "uit", "av", "wyłączono", "av", "off", "kapalı", "off", "vyp") +MAKE_WORD_TRANSLATION(ON, "ON", "AN", "AAN", "PÅ", "wł.", "PÅ", "ON", "AÇIK", "ON", "ZAP") +MAKE_WORD_TRANSLATION(OFF, "OFF", "AUS", "UIT", "AV", "wył.", "AV", "OFF", "KAPALI", "OFF", "VYP") // Unit Of Measurement mapping - maps to DeviceValueUOM_s in emsdevice.cpp // uom - also used with HA see https://github.com/home-assistant/core/blob/d7ac4bd65379e11461c7ce0893d3533d8d8b8cbf/homeassistant/const.py#L384 -MAKE_WORD_TRANSLATION(minutes, "minutes", "Minuten", "Minuten", "Minuter", "minut", "Minutter", "minutes", "dakika", "Minuti") -MAKE_WORD_TRANSLATION(hours, "hours", "Stunden", "Uren", "Timmar", "godzin", "Timer", "heures", "saat", "ore") -MAKE_WORD_TRANSLATION(days, "days", "Tage", "Dagen", "Dagar", "dni", "Dager", "jours", "gün", "giorni") -MAKE_WORD_TRANSLATION(seconds, "seconds", "Sekunden", "Seconden", "Sekunder", "sekund", "Sekunder", "secondes", "saniye", "Secondi") +MAKE_WORD_TRANSLATION(minutes, "minutes", "Minuten", "Minuten", "Minuter", "minut", "Minutter", "minutes", "dakika", "Minuti", "minúty") +MAKE_WORD_TRANSLATION(hours, "hours", "Stunden", "Uren", "Timmar", "godzin", "Timer", "heures", "saat", "ore", "hodiny") +MAKE_WORD_TRANSLATION(days, "days", "Tage", "Dagen", "Dagar", "dni", "Dager", "jours", "gün", "giorni", "dni") +MAKE_WORD_TRANSLATION(seconds, "seconds", "Sekunden", "Seconden", "Sekunder", "sekund", "Sekunder", "secondes", "saniye", "Secondi", "sekundy") // Enum translations // general -MAKE_WORD_TRANSLATION(day_mo, "mo", "Mo", "Mo", "Må", "poniedziałek", "ma", "lun", "Pzt", "lu") -MAKE_WORD_TRANSLATION(day_tu, "tu", "Di", "Di", "Ti", "wtorek", "ti", "mar", "Sal", "ma") -MAKE_WORD_TRANSLATION(day_we, "we", "Mi", "Wo", "On", "środa", "on", "mer", "Çar", "me") -MAKE_WORD_TRANSLATION(day_th, "th", "Do", "Do", "To", "czwartek", "to", "jeu", "Per", "gio") -MAKE_WORD_TRANSLATION(day_fr, "fr", "Fr", "Vr", "Fr", "piątek", "fr", "ven", "Cum", "ve") -MAKE_WORD_TRANSLATION(day_sa, "sa", "Sa", "Za", "Lö", "sobota", "lø", "sam", "Cts", "sa") -MAKE_WORD_TRANSLATION(day_su, "su", "So", "Zo", "Sö", "niedziela", "sø", "dim", "Paz", "do") -MAKE_WORD_TRANSLATION(all, "all", "Alle", "Alle", "Alla", "codziennie", "alle", "tous", "tüm", "tutti") -MAKE_WORD_TRANSLATION(own_1, "own 1", "Eigen 1", "Eigen 1", "Egen 1", "własny 1", "egen 1", "propre 1", "kendi 1", "proprio 1") -MAKE_WORD_TRANSLATION(family, "family", "Familie", "Familie", "Familj", "rodzina", "familie", "famille", "aile", "famiglia") -MAKE_WORD_TRANSLATION(morning, "morning", "Morgends", "'s ochtends", "Morgon", "zmiana 1", "morgen", "matin", "sabah", "mattina") -MAKE_WORD_TRANSLATION(evening, "evening", "Abends", "'s avonds", "Kväll", "zmiana 2", "kveld", "soir", "akşam", "sera") -MAKE_WORD_TRANSLATION(seniors, "seniors", "Senioren", "senioren", "Seniorer", "senior", "seniorer", "séniors", "yaşlılar", "vecchi") -MAKE_WORD_TRANSLATION(no, "no", "nein", "nee", "nej", "nie", "nei", "non", "hayır", "no") -MAKE_WORD_TRANSLATION(new, "new", "Neu", "Nieuw", "Ny", "nowy", "ny", "nouveau", "yeni", "nuovo") -MAKE_WORD_TRANSLATION(own_2, "own 2", "Eigen 2", "Eigen 2", "Egen 2", "własny 2", "egen 2", "propre 2", "kendi 2", "proprio 2") -MAKE_WORD_TRANSLATION(singles, "singles", "Singles", "singles", "Singlar", "osoba samotna", "single", "seuls", "tekliler", "singoli") -MAKE_WORD_TRANSLATION(am, "am", "Vormittag", "ochtend", "Förmiddag", "do południa", "formiddag", "matin", "sabah", "mattina") -MAKE_WORD_TRANSLATION(pm, "pm", "Nachmittag", "namiddag", "Eftermiddag", "po południu", "ettermiddag", "après-midi", "akşam", "pomeriggio") -MAKE_WORD_TRANSLATION(midday, "midday", "Mittag", "middag", "Middag", "południe", "middag", "midi", "öğlen", "mezzogiorno") -MAKE_WORD_TRANSLATION(unknown, "unknown", "Unbekannt", "onbekend", "Okänt", "nieznany", "ukjent", "inconnu", "bilinmeyen", "sconosciuto") -MAKE_WORD_TRANSLATION(flat, "flat", "flach", "vlak", "Platt", "płaski", "flat", "plat", "düz", "piatto") -MAKE_WORD_TRANSLATION(vacuum, "vacuum", "Vakuum", "vacuum", "Vakuum", "próżnia", "vakum", "vide", "vakum", "vacuum") -MAKE_WORD_TRANSLATION(co2_optimized, "co2 optimized", "CO2 optimiert", "CO2 geoptimaliseerd", "CO2-optimerad", "optymalizacja CO2", "co2 optimalisert", "optimisé en CO2", "CO2 verimli", "CO2 ottimizzato") -MAKE_WORD_TRANSLATION(cost_optimized, "cost optimized", "kostenoptimiert", "kosten geoptimaliseerd", "kostnadsoptimerad", "optymalizacja kosztów", "kostnadsoptimalisert", "optimisé en coût", "maliyet odaklı", "costo ottimizzato") -MAKE_WORD_TRANSLATION(outside_temp_switched, "outside temp switched", "Außentemp. gesteuert", "buitentemp. gestuurd", "Utomhustemp korrigerad", "temperatura zewn. przeł.", "utetemp optimalisert", "contrôle par temp. ext.", "dış hava sıcaklığına bağlı", "temperatura esterna cambiata") -MAKE_WORD_TRANSLATION(co2_cost_mix, "co2 cost mix", "Kostenmix", "kostenmix", "Kostnadsmix", "mieszany koszt CO2", "", "coût mixte CO2", "karışık maliyet", "co2 cost mix") // TODO translate -MAKE_WORD_TRANSLATION(analog, "analog", "analog", "analoog", "analog", "analogowy", "analog", "analogique", "analog", "analogico") -MAKE_WORD_TRANSLATION(normal, "normal", "normal", "normaal", "normal", "normalny", "normal", "normal", "normal", "normale") -MAKE_WORD_TRANSLATION(blocking, "blocking", "Blockierung", "blokkering", "Blockering", "blokowanie", "blokkering", "bloquant", "engelleme", "bloccaggio") -MAKE_WORD_TRANSLATION(extern, "extern", "extern", "extern", "extern", "zewnętrzny", "ekstern", "externe", "dış", "eesterno") -MAKE_WORD_TRANSLATION(intern, "intern", "intern", "intern", "intern", "wewnętrzny", "intern", "interne", "iç", "interno") -MAKE_WORD_TRANSLATION(lower, "lower", "niedirger", "lager", "lägre", "mniejszy", "nedre", "inférieur", "daha düşük", "basso") -MAKE_WORD_TRANSLATION(error, "error", "Fehler", "error", "Fel", "błąd", "feil", "erreur", "Hata", "errore") -MAKE_WORD_TRANSLATION(na, "n/a", "n/a", "n/a", "n/a", "nd.", "n/a", "n/c", "mevcut değil", "n/a") +MAKE_WORD_TRANSLATION(day_mo, "mo", "Mo", "Mo", "Må", "poniedziałek", "ma", "lun", "Pzt", "lu", "po") +MAKE_WORD_TRANSLATION(day_tu, "tu", "Di", "Di", "Ti", "wtorek", "ti", "mar", "Sal", "ma", "ut") +MAKE_WORD_TRANSLATION(day_we, "we", "Mi", "Wo", "On", "środa", "on", "mer", "Çar", "me", "st") +MAKE_WORD_TRANSLATION(day_th, "th", "Do", "Do", "To", "czwartek", "to", "jeu", "Per", "gio", "št") +MAKE_WORD_TRANSLATION(day_fr, "fr", "Fr", "Vr", "Fr", "piątek", "fr", "ven", "Cum", "ve", "pi") +MAKE_WORD_TRANSLATION(day_sa, "sa", "Sa", "Za", "Lö", "sobota", "lø", "sam", "Cts", "sa", "so") +MAKE_WORD_TRANSLATION(day_su, "su", "So", "Zo", "Sö", "niedziela", "sø", "dim", "Paz", "do", "ne") +MAKE_WORD_TRANSLATION(all, "all", "Alle", "Alle", "Alla", "codziennie", "alle", "tous", "tüm", "tutti", "všetko") +MAKE_WORD_TRANSLATION(own_1, "own 1", "Eigen 1", "Eigen 1", "Egen 1", "własny 1", "egen 1", "propre 1", "kendi 1", "proprio 1", "vlastné 1") +MAKE_WORD_TRANSLATION(family, "family", "Familie", "Familie", "Familj", "rodzina", "familie", "famille", "aile", "famiglia", "rodina") +MAKE_WORD_TRANSLATION(morning, "morning", "Morgends", "'s ochtends", "Morgon", "zmiana 1", "morgen", "matin", "sabah", "mattina", "ráno") +MAKE_WORD_TRANSLATION(evening, "evening", "Abends", "'s avonds", "Kväll", "zmiana 2", "kveld", "soir", "akşam", "sera", "večer") +MAKE_WORD_TRANSLATION(seniors, "seniors", "Senioren", "senioren", "Seniorer", "senior", "seniorer", "séniors", "yaşlılar", "vecchi", "dôchodcovia") +MAKE_WORD_TRANSLATION(no, "no", "nein", "nee", "nej", "nie", "nei", "non", "hayır", "no", "nie") +MAKE_WORD_TRANSLATION(new, "new", "Neu", "Nieuw", "Ny", "nowy", "ny", "nouveau", "yeni", "nuovo", "nové") +MAKE_WORD_TRANSLATION(own_2, "own 2", "Eigen 2", "Eigen 2", "Egen 2", "własny 2", "egen 2", "propre 2", "kendi 2", "proprio 2", "vlastné 2") +MAKE_WORD_TRANSLATION(singles, "singles", "Singles", "singles", "Singlar", "osoba samotna", "single", "seuls", "tekliler", "singoli", "slobodní") +MAKE_WORD_TRANSLATION(am, "am", "Vormittag", "ochtend", "Förmiddag", "do południa", "formiddag", "matin", "sabah", "mattina", "doobeda") +MAKE_WORD_TRANSLATION(pm, "pm", "Nachmittag", "namiddag", "Eftermiddag", "po południu", "ettermiddag", "après-midi", "akşam", "pomeriggio", "poobede") +MAKE_WORD_TRANSLATION(midday, "midday", "Mittag", "middag", "Middag", "południe", "middag", "midi", "öğlen", "mezzogiorno", "poludnie") +MAKE_WORD_TRANSLATION(unknown, "unknown", "Unbekannt", "onbekend", "Okänt", "nieznany", "ukjent", "inconnu", "bilinmeyen", "sconosciuto", "neznáme") +MAKE_WORD_TRANSLATION(flat, "flat", "flach", "vlak", "Platt", "płaski", "flat", "plat", "düz", "piatto", "plochý") +MAKE_WORD_TRANSLATION(vacuum, "vacuum", "Vakuum", "vacuum", "Vakuum", "próżnia", "vakum", "vide", "vakum", "vacuum", "vákuum") +MAKE_WORD_TRANSLATION(co2_optimized, "co2 optimized", "CO2 optimiert", "CO2 geoptimaliseerd", "CO2-optimerad", "optymalizacja CO2", "co2 optimalisert", "optimisé en CO2", "CO2 verimli", "CO2 ottimizzato", "co2 optimalizované") +MAKE_WORD_TRANSLATION(cost_optimized, "cost optimized", "kostenoptimiert", "kosten geoptimaliseerd", "kostnadsoptimerad", "optymalizacja kosztów", "kostnadsoptimalisert", "optimisé en coût", "maliyet odaklı", "costo ottimizzato", "nákladovo optimalizované") +MAKE_WORD_TRANSLATION(outside_temp_switched, "outside temp switched", "Außentemp. gesteuert", "buitentemp. gestuurd", "Utomhustemp korrigerad", "temperatura zewn. przeł.", "utetemp optimalisert", "contrôle par temp. ext.", "dış hava sıcaklığına bağlı", "temperatura esterna cambiata", "prepínaná vonkajšia teplota") +MAKE_WORD_TRANSLATION(co2_cost_mix, "co2 cost mix", "Kostenmix", "kostenmix", "Kostnadsmix", "mieszany koszt CO2", "", "coût mixte CO2", "karışık maliyet", "co2 cost mix", "co2 náklady mix") // TODO translate +MAKE_WORD_TRANSLATION(analog, "analog", "analog", "analoog", "analog", "analogowy", "analog", "analogique", "analog", "analogico", "analógový") +MAKE_WORD_TRANSLATION(normal, "normal", "normal", "normaal", "normal", "normalny", "normal", "normal", "normal", "normale", "normálny") +MAKE_WORD_TRANSLATION(blocking, "blocking", "Blockierung", "blokkering", "Blockering", "blokowanie", "blokkering", "bloquant", "engelleme", "bloccaggio", "blokovanie") +MAKE_WORD_TRANSLATION(extern, "extern", "extern", "extern", "extern", "zewnętrzny", "ekstern", "externe", "dış", "eesterno", "externý") +MAKE_WORD_TRANSLATION(intern, "intern", "intern", "intern", "intern", "wewnętrzny", "intern", "interne", "iç", "interno", "interný") +MAKE_WORD_TRANSLATION(lower, "lower", "niedirger", "lager", "lägre", "mniejszy", "nedre", "inférieur", "daha düşük", "basso", "nízky") +MAKE_WORD_TRANSLATION(error, "error", "Fehler", "error", "Fel", "błąd", "feil", "erreur", "Hata", "errore", "error") +MAKE_WORD_TRANSLATION(na, "n/a", "n/a", "n/a", "n/a", "nd.", "n/a", "n/c", "mevcut değil", "n/a", "n/a") // boiler -MAKE_WORD_TRANSLATION(time, "time", "Zeit", "tijd", "Tid", "godzina", "tid", "heure", "zaman", "ora") -MAKE_WORD_TRANSLATION(date, "date", "Datum", "datum", "Datum", "data", "dato", "date", "tarih", "data") -MAKE_WORD_TRANSLATION(continuous, "continuous", "kontinuierlich", "continue", "kontinuerlig", "ciągły", "kontinuerlig", "continu", "devam eden", "continuo") -MAKE_WORD_TRANSLATION(3wayvalve, "3-way valve", "3-Wege Ventil", "3-weg klep", "trevägsventil", "zawór 3-drogowy", "treveisventil", "vanne 3 voies" , "3 yollu vana", "valvola 3 vie") -MAKE_WORD_TRANSLATION(chargepump, "chargepump", "Ladepumpe", "laadpomp", "laddpump", "pompa ładująca", "ladepumpe", "pompe de charge", "besleme pompası", "pompa di carica") -MAKE_WORD_TRANSLATION(hot, "hot", "Heiß", "heet", "Het", "gorący", "het", "chaud", "sıcak", "caldo") -MAKE_WORD_TRANSLATION(high_comfort, "high comfort", "gehobener Komfort", "verhoogd comfort", "Förhöjd komfort", "wysoki komfort", "høy komfort", "comfort", "komfor", "comfort alto") -MAKE_WORD_TRANSLATION(eco, "eco", "Eco", "Eco", "Eko", "eko", "øko", "éco", "eko", "Eco") -MAKE_WORD_TRANSLATION(intelligent, "intelligent", "Intelligent", "intelligent", "Intelligent", "inteligentny", "intelligent", "intelligent", "akıllı", "intelligente") -MAKE_WORD_TRANSLATION(flow, "flow", "Durchfluss", "volumestroom", "Flöde", "przepływ", "strømme", "débit", "akım", "flusso") -MAKE_WORD_TRANSLATION(manual, "manual", "Manuell", "handmatig", "Manuell", "ręczny", "manuell", "manuel", "manuel", "manuale") -MAKE_WORD_TRANSLATION(buffer, "buffer", "Speicher", "buffer", "Buffert", "bufor", "buffer", "buffer", "tampon", "Buffer") -MAKE_WORD_TRANSLATION(bufferedflow, "buffered flow", "Durchlaufspeicher", "doorstroombuffer", "Buffertflöde", "przepływ buforowany", "bufret strømning", "", "tampon akım", "memoria flusso") // TODO translate -MAKE_WORD_TRANSLATION(layeredbuffer, "layered buffer", "Schichtspeicher", "gelaagde buffer", "Lagrad buffert", "bufor warstwowy", "lagdelt buffer", "", "katmanlı akım", "strato memoria") // TODO translate -MAKE_WORD_TRANSLATION(maintenance, "maintenance", "Wartung", "onderhoud", "Underhåll", "przegląd", "vedlikehold", "maintenance", "bakım", "servizio") -MAKE_WORD_TRANSLATION(heating, "heating", "Heizen", "verwarmen", "Uppvärmning", "ogrzewanie", "oppvarming", "chauffage", "ısıtma", "riscaldamento") -MAKE_WORD_TRANSLATION(cooling, "cooling", "Kühlen", "koelen", "Kyler", "chłodzenie", "kjøling", "refroidissement", "soğuma", "raffreddamento") -MAKE_WORD_TRANSLATION(heatandcool, "heating&cooling", "Heizen&Kühlen", "verwarmen&koelen", "Uppvärmning&Kyler", "ogrzewanie i chłodzenie", "", "", "ısıtma&soğutma", "") // TODO translate -MAKE_WORD_TRANSLATION(disinfecting, "disinfecting", "Desinfizieren", "desinfecteren", "Desinficerar", "dezynfekcja termiczna", "desinfisering", "désinfection", "dezenfeksiyon", "disinfezione") -MAKE_WORD_TRANSLATION(no_heat, "no heat", "keine Wärme", "geen warmte", "Ingen värme", "brak ciepła", "ingen varme", "pas de chauffage", "ısınma yok", "nessun calore") -MAKE_WORD_TRANSLATION(heatrequest, "heat request", "Wärmeanforderung", "verwarmingsverzoek", "Värmeförfrågan", "zapotrzebowanie na ciepło", "varmeforespørsel", "demande de chauffage", "ısınma ihtiyacı", "richiesta calore") -MAKE_WORD_TRANSLATION(valve, "valve", "Ventil", "klep", "Ventil", "zawór", "ventil", "valve", "vana", "valvola") -MAKE_WORD_TRANSLATION(proportional, "proportional", "proportional", "proportioneel", "", "proporcjonalny", "proposjonal", "", "oransal", "proporzionale") // TODO translate -MAKE_WORD_TRANSLATION(deltaP1, "deltaP-1", "deltaP-1", "deltaP-1", "", "delta P-1", "deltaP-1", "", "deltaP-1", "deltaP-1") // TODO translate -MAKE_WORD_TRANSLATION(deltaP2, "deltaP-2", "deltaP-2", "deltaP-2", "", "delta P-2", "deltaP-2", "", "deltaP-2", "deltaP-2") // TODO translate -MAKE_WORD_TRANSLATION(deltaP3, "deltaP-3", "deltaP-3", "deltaP-3", "", "delta P-3", "deltaP-3", "", "deltaP-3", "deltaP-3") // TODO translate -MAKE_WORD_TRANSLATION(deltaP4, "deltaP-4", "deltaP-4", "deltaP-4", "", "delta P-4", "deltaP-4", "", "deltaP-4", "deltaP-4") // TODO translate +MAKE_WORD_TRANSLATION(time, "time", "Zeit", "tijd", "Tid", "godzina", "tid", "heure", "zaman", "ora", "čas") +MAKE_WORD_TRANSLATION(date, "date", "Datum", "datum", "Datum", "data", "dato", "date", "tarih", "data", "dátum") +MAKE_WORD_TRANSLATION(continuous, "continuous", "kontinuierlich", "continue", "kontinuerlig", "ciągły", "kontinuerlig", "continu", "devam eden", "continuo", "nepretržité") +MAKE_WORD_TRANSLATION(3wayvalve, "3-way valve", "3-Wege Ventil", "3-weg klep", "trevägsventil", "zawór 3-drogowy", "treveisventil", "vanne 3 voies", "3 yollu vana", "valvola 3 vie", "3-cestný ventil") +MAKE_WORD_TRANSLATION(chargepump, "chargepump", "Ladepumpe", "laadpomp", "laddpump", "pompa ładująca", "ladepumpe", "pompe de charge", "besleme pompası", "pompa di carica", "nabíjacie čerpadlo") +MAKE_WORD_TRANSLATION(hot, "hot", "Heiß", "heet", "Het", "gorący", "het", "chaud", "sıcak", "caldo", "horúci") +MAKE_WORD_TRANSLATION(high_comfort, "high comfort", "gehobener Komfort", "verhoogd comfort", "Förhöjd komfort", "wysoki komfort", "høy komfort", "comfort", "komfor", "comfort alto", "vysoký komfort") +MAKE_WORD_TRANSLATION(eco, "eco", "Eco", "Eco", "Eko", "eko", "øko", "éco", "eko", "Eco", "eko") +MAKE_WORD_TRANSLATION(intelligent, "intelligent", "Intelligent", "intelligent", "Intelligent", "inteligentny", "intelligent", "intelligent", "akıllı", "intelligente", "inteligentný") +MAKE_WORD_TRANSLATION(flow, "flow", "Durchfluss", "volumestroom", "Flöde", "przepływ", "strømme", "débit", "akım", "flusso", "tok") +MAKE_WORD_TRANSLATION(manual, "manual", "Manuell", "handmatig", "Manuell", "ręczny", "manuell", "manuel", "manuel", "manuale", "manuálny") +MAKE_WORD_TRANSLATION(buffer, "buffer", "Speicher", "buffer", "Buffert", "bufor", "buffer", "buffer", "tampon", "Buffer", "zásobník") +MAKE_WORD_TRANSLATION(bufferedflow, "buffered flow", "Durchlaufspeicher", "doorstroombuffer", "Buffertflöde", "przepływ buforowany", "bufret strømning", "", "tampon akım", "memoria flusso", "zásobníkový tok") // TODO translate +MAKE_WORD_TRANSLATION(layeredbuffer, "layered buffer", "Schichtspeicher", "gelaagde buffer", "Lagrad buffert", "bufor warstwowy", "lagdelt buffer", "", "katmanlı akım", "strato memoria", "vrstvený zásobník") // TODO translate +MAKE_WORD_TRANSLATION(maintenance, "maintenance", "Wartung", "onderhoud", "Underhåll", "przegląd", "vedlikehold", "maintenance", "bakım", "servizio", "údržba") +MAKE_WORD_TRANSLATION(heating, "heating", "Heizen", "verwarmen", "Uppvärmning", "ogrzewanie", "oppvarming", "chauffage", "ısıtma", "riscaldamento", "kúrenie") +MAKE_WORD_TRANSLATION(cooling, "cooling", "Kühlen", "koelen", "Kyler", "chłodzenie", "kjøling", "refroidissement", "soğuma", "raffreddamento", "chladenie") +MAKE_WORD_TRANSLATION(heatandcool, "heating&cooling", "Heizen&Kühlen", "verwarmen&koelen", "Uppvärmning&Kyler", "ogrzewanie i chłodzenie", "", "", "ısıtma&soğutma", "", "kúrenie a chladenie") // TODO translate +MAKE_WORD_TRANSLATION(disinfecting, "disinfecting", "Desinfizieren", "desinfecteren", "Desinficerar", "dezynfekcja termiczna", "desinfisering", "désinfection", "dezenfeksiyon", "disinfezione", "dezinfekcia") +MAKE_WORD_TRANSLATION(no_heat, "no heat", "keine Wärme", "geen warmte", "Ingen värme", "brak ciepła", "ingen varme", "pas de chauffage", "ısınma yok", "nessun calore", "žiadne teplo") +MAKE_WORD_TRANSLATION(heatrequest, "heat request", "Wärmeanforderung", "verwarmingsverzoek", "Värmeförfrågan", "zapotrzebowanie na ciepło", "varmeforespørsel", "demande de chauffage", "ısınma ihtiyacı", "richiesta calore", "požiadavka na teplo") +MAKE_WORD_TRANSLATION(valve, "valve", "Ventil", "klep", "Ventil", "zawór", "ventil", "valve", "vana", "valvola", "ventil") +MAKE_WORD_TRANSLATION(proportional, "proportional", "proportional", "proportioneel", "", "proporcjonalny", "proposjonal", "", "oransal", "proporzionale", "proporcionálne") // TODO translate +MAKE_WORD_TRANSLATION(deltaP1, "deltaP-1", "deltaP-1", "deltaP-1", "", "delta P-1", "deltaP-1", "", "deltaP-1", "deltaP-1", "deltaP-1") // TODO translate +MAKE_WORD_TRANSLATION(deltaP2, "deltaP-2", "deltaP-2", "deltaP-2", "", "delta P-2", "deltaP-2", "", "deltaP-2", "deltaP-2", "deltaP-2") // TODO translate +MAKE_WORD_TRANSLATION(deltaP3, "deltaP-3", "deltaP-3", "deltaP-3", "", "delta P-3", "deltaP-3", "", "deltaP-3", "deltaP-3", "deltaP-3") // TODO translate +MAKE_WORD_TRANSLATION(deltaP4, "deltaP-4", "deltaP-4", "deltaP-4", "", "delta P-4", "deltaP-4", "", "deltaP-4", "deltaP-4", "deltaP-4") // TODO translate // heatpump -MAKE_WORD_TRANSLATION(none, "none", "keine", "geen", "ingen", "brak", "ingen", "aucun", "hiçbiri", "nessuno") -MAKE_WORD_TRANSLATION(hot_water, "hot water", "Warmwasser", "warm water", "varmvatten", "c.w.u.", "varmtvann", "eau chaude", "sıcak su", "acqua calda") -MAKE_WORD_TRANSLATION(pool, "pool", "Pool", "zwembad", "pool", "basen", "basseng", "piscine", "havuz", "piscina") -MAKE_WORD_TRANSLATION(outside_temp_alt, "outside temperature alt.", "Außentemp. alternativ", "alternatieve buitentemperatuur", "Alternativ utomhustemp.", "temp. zewn. alternat.", "alternativ utendørstemp.", "température extérieure alternative", "alternatif dış sıcaklık", "temperatura esterna alternativa") -MAKE_WORD_TRANSLATION(outside_temp_par, "outside temperature parallel", "Außentemp. parallel", "buitentemperatuur parallel", "Parallell utomhustemp.", "temp. zewn. równoległa", "parallell utendørstemp.", "température extérieure parallèle", "paralel dış sıcaklık", "temperatura esterna parallela") -MAKE_WORD_TRANSLATION(hp_prefered, "heatpump prefered", "Wärmepumpe bevorzugt", "voorkeur warmtepomp", "Värmepump föredraget", "preferowana pompa ciepła", "varmepumpe prioritert", "pompe à chaleur préférée", "tercih edilen pompa", "pompa di calore preferita") -MAKE_WORD_TRANSLATION(boiler_only, "boiler only", "nur Kessel", "uitsluitend cv ketel", "Värmepanna enbart", "tylko kocioł", "kun kjele", "chaudière uniquement", "sadece kazan", "solo caldaia") -MAKE_WORD_TRANSLATION(reduced_output, "reduced output", "Reduzierte Leistung", "gereduceerde output", "Reducerad produktion", "zmniejszona wydajność", "redusert ytelse", "sortie réduite", "düşürülmüş çıkış", "riduzione uscita") -MAKE_WORD_TRANSLATION(switchoff, "switch off hp", "WP ausschalten", "WP uitschakelen", "Värmepump avstängd", "wyłącz pompę ciepła", "slå av varmepumpe", "éteindre la PAC", "ısı pompasını kapat", "spegnimento pompa calore") -MAKE_WORD_TRANSLATION(perm, "perm. reduced", "perm. reduziert", "permanent gereduceerd", "Permanent reducerad", "stale zmniejszona wydajność", "permanent redusert", "réduction permanente", "sürekli azaltılmış", "riduzione permanente") -MAKE_WORD_TRANSLATION(heat_ww, "heating & dhw", "Heizen & Warmwasser", "", "", "", "", "", "", "") -MAKE_WORD_TRANSLATION(cool_defrost, "cooling & defrost", "Kühlen & Abtauen", "", "", "", "", "", "", "") +MAKE_WORD_TRANSLATION(none, "none", "keine", "geen", "ingen", "brak", "ingen", "aucun", "hiçbiri", "nessuno", "žiadny") +MAKE_WORD_TRANSLATION(hot_water, "hot water", "Warmwasser", "warm water", "varmvatten", "c.w.u.", "varmtvann", "eau chaude", "sıcak su", "acqua calda", "horúca voda") +MAKE_WORD_TRANSLATION(pool, "pool", "Pool", "zwembad", "pool", "basen", "basseng", "piscine", "havuz", "piscina", "bazén") +MAKE_WORD_TRANSLATION(outside_temp_alt, "outside temperature alt.", "Außentemp. alternativ", "alternatieve buitentemperatuur", "Alternativ utomhustemp.", "temp. zewn. alternat.", "alternativ utendørstemp.", "température extérieure alternative", "alternatif dış sıcaklık", "temperatura esterna alternativa", "vonkajšia teplota altern.") +MAKE_WORD_TRANSLATION(outside_temp_par, "outside temperature parallel", "Außentemp. parallel", "buitentemperatuur parallel", "Parallell utomhustemp.", "temp. zewn. równoległa", "parallell utendørstemp.", "température extérieure parallèle", "paralel dış sıcaklık", "temperatura esterna parallela", "paralelne s vonkajšou teplotou") +MAKE_WORD_TRANSLATION(hp_prefered, "heatpump prefered", "Wärmepumpe bevorzugt", "voorkeur warmtepomp", "Värmepump föredraget", "preferowana pompa ciepła", "varmepumpe prioritert", "pompe à chaleur préférée", "tercih edilen pompa", "pompa di calore preferita", "preferované tepelné čerpadlo") +MAKE_WORD_TRANSLATION(boiler_only, "boiler only", "nur Kessel", "uitsluitend cv ketel", "Värmepanna enbart", "tylko kocioł", "kun kjele", "chaudière uniquement", "sadece kazan", "solo caldaia", "len bojler") +MAKE_WORD_TRANSLATION(reduced_output, "reduced output", "Reduzierte Leistung", "gereduceerde output", "Reducerad produktion", "zmniejszona wydajność", "redusert ytelse", "sortie réduite", "düşürülmüş çıkış", "riduzione uscita", "znížený výkon") +MAKE_WORD_TRANSLATION(switchoff, "switch off hp", "WP ausschalten", "WP uitschakelen", "Värmepump avstängd", "wyłącz pompę ciepła", "slå av varmepumpe", "éteindre la PAC", "ısı pompasını kapat", "spegnimento pompa calore", "vypnúť tep. čerpadlo") +MAKE_WORD_TRANSLATION(perm, "perm. reduced", "perm. reduziert", "permanent gereduceerd", "Permanent reducerad", "stale zmniejszona wydajność", "permanent redusert", "réduction permanente", "sürekli azaltılmış", "riduzione permanente", "trvalo znížené") +MAKE_WORD_TRANSLATION(heat_ww, "heating & dhw", "Heizen & Warmwasser", "", "", "ogrzewanie i c.w.u.", "", "", "", "", "kúrenie a TÚV") +MAKE_WORD_TRANSLATION(cool_defrost, "cooling & defrost", "Kühlen & Abtauen", "", "", "chłodzenie i odladzanie", "", "", "", "", "chladenie a rozmrazovanie") // thermostat -MAKE_WORD_TRANSLATION(seltemp, "selTemp", "Solltemperatur", "doeltemperatuur", "Börtemperatur", "temperatura zadana", "innstilt temperatur", "consigne température", "ayarlanmış sıcaklık", "temperatura di consegna") -MAKE_WORD_TRANSLATION(roomtemp, "roomTemp", "Raumtemperatur", "kamertemperatuur", "Rumstemperatur", "temperatura w pomieszczeniu", "romstemperatur", "température de la pièce", "oda sıcaklığı", "temperatura camera") -MAKE_WORD_TRANSLATION(own_prog, "own prog", "Eigenprog.", "eigen prog.", "Egen prog.", "program własny", "eget prog.", "programme propre", "isteğe göre ayarlanmış program", "proprio prog.") -MAKE_WORD_TRANSLATION(std_prog, "std prog", "Standardprog.", "standaard prog.", "Standardprog.", "program standardowy", "standardprog.", "programme standard", "sandart pogram", "programma standard") -MAKE_WORD_TRANSLATION(light, "light", "Leicht", "licht", "Lätt", "lekki", "lett", "léger", "düşük", "luce") -MAKE_WORD_TRANSLATION(medium, "medium", "Mittel", "middel", "Medel", "średni", "medium", "medium", "orta", "medio") -MAKE_WORD_TRANSLATION(heavy, "heavy", "Schwer", "zwaar", "Tung", "ciężki", "tung", "lourd", "yüksek", "pesante") -MAKE_WORD_TRANSLATION(start, "start", "Start", "start", "Start", "start", "start", "début", "başlat", "avvia") -MAKE_WORD_TRANSLATION(heat, "heat", "Heizen", "verwarmen", "Värme", "ciepło", "varmer", "chaleur", "ısıtma", "caldo") -MAKE_WORD_TRANSLATION(hold, "hold", "Halten", "pauzeren", "Paus", "pauza", "pause", "pause", "durdur", "pausa") -MAKE_WORD_TRANSLATION(cool, "cool", "Kühlen", "koelen", "Kyla", "zimno", "kjøler", "froid", "soğutma", "freddo") -MAKE_WORD_TRANSLATION(end, "end", "Ende", "einde", "Slut", "koniec", "slutt", "fin", "bitti", "fine") -MAKE_WORD_TRANSLATION(german, "german", "Deutsch", "Duits", "Tyska", "niemiecki", "tysk", "allemand", "Almanca", "Tedesco") -MAKE_WORD_TRANSLATION(dutch, "dutch", "Niederländisch", "Nederlands", "Nederländska", "niderlandzki", "nederlandsk", "néerlandais", "Flemenkçe", "Olandese") -MAKE_WORD_TRANSLATION(french, "french", "Französisch", "Frans", "Franska", "francuski", "fransk", "français", "Fransızca", "Francese") -MAKE_WORD_TRANSLATION(italian, "italian", "Italienisch", "Italiaans", "Italienska", "włoski", "italiensk", "italien", "İtalyanca", "Italiano") -MAKE_WORD_TRANSLATION(high, "high", "hoch", "hoog", "Hög", "wysoki", "høy", "haut", "yüksek", "alto") -MAKE_WORD_TRANSLATION(low, "low", "niedrig", "laag", "Låg", "niski", "lav", "bas", "düşük", "basso") -MAKE_WORD_TRANSLATION(radiator, "radiator", "Heizkörper", "radiator", "Radiator", "grzejniki", "radiator", "radiateur", "radyatör", "radiatore") -MAKE_WORD_TRANSLATION(convector, "convector", "Konvektor", "convector", "Konvektor", "konwektory", "konvektor", "convecteur", "convector", "convettore") -MAKE_WORD_TRANSLATION(floor, "floor", "Fussboden", "vloer", "Golv", "podłoga", "gulv", "sol", "yer", "pavimento") -MAKE_WORD_TRANSLATION(summer, "summer", "Sommer", "zomer", "Sommar", "lato", "sommer", "été", "yaz", "estate") -MAKE_WORD_TRANSLATION(winter, "winter", "Winter", "winter", "Vinter", "zima", "vinter", "hiver", "kış", "inverno") -MAKE_WORD_TRANSLATION(outdoor, "outdoor", "Außen", "buiten", "Utomhus", "temp. zewnętrzna", "utendørs", "extérieur", "dış", "esterno") -MAKE_WORD_TRANSLATION(room, "room", "Raum", "kamer", "Rum", "temp. w pomieszczeniu", "", "pièce", "oda", "camera") // TODO translate -MAKE_WORD_TRANSLATION(room_outdoor, "room outdoor", "Raum+Außen", "kamer+buiten", "Rum+Ute", "temp. w pom. i zewn.", "rom utendørs", "pièce extérieure", "oda ve dış", "camera esterna") -MAKE_WORD_TRANSLATION(power, "power", "Leistung", "vermogen", "Effekt", "moc", "effekt", "puissance", "güç", "potenza") -MAKE_WORD_TRANSLATION(constant, "constant", "konstant", "constant", "Konstant", "stały", "konstant", "constant", "sabit", "costante") -MAKE_WORD_TRANSLATION(simple, "simple", "einfach", "simpel", "enkel", "prosty", "enkel", "simple", "basit", "semplice") -MAKE_WORD_TRANSLATION(optimized, "optimized", "optimiert", "geoptimaliseerd", "optimerad", "zoptymalizowany", "optimalisert", "optimisé", "optimize", "ottimizzato") -MAKE_WORD_TRANSLATION(nofrost, "nofrost", "Frostschutz", "vorstbescherming", "Frostskydd", "ochrona przed zamarzaniem", "frostsikring", "protection gel", "Donma koruması", "protezione gelo") -MAKE_WORD_TRANSLATION(defrost, "defrost", "Abtauen", "ontdooien", "avfrostning", "rozmrażać", "tine", "dégivrage", "buz çözücü", "scongelamento") -MAKE_WORD_TRANSLATION(comfort, "comfort", "Komfort", "comfort", "Komfort", "komfort", "komfort", "comfort", "konfor", "comfort") -MAKE_WORD_TRANSLATION(night, "night", "Nacht", "nacht", "Natt", "noc", "natt", "nuit", "gece", "notte") -MAKE_WORD_TRANSLATION(day, "day", "Tag", "dag", "Dag", "dzień", "dag", "jour", "gün", "giorno") -MAKE_WORD_TRANSLATION(holiday, "holiday", "Urlaub", "vakantie", "Helgdag", "urlop", "ferie", "vacances", "tatil", "vacanza") -MAKE_WORD_TRANSLATION(reduce, "reduce", "reduziert", "gereduceerd", "Reducera", "zredukowany", "redusere", "réduit", "düşür", "riduzione") -MAKE_WORD_TRANSLATION(noreduce, "no reduce", "unreduziert", "niet gereduceerd", "oreducerad", "bez redukcji", "ingen reduksjon", "pas de réduction", "düşürme", "non ridurre") -MAKE_WORD_TRANSLATION(offset, "offset", "Anhebung", "offset", "Förskutning", "przesunięcie", "kompensasjon", "offset", "kompansasyon", "offset") -MAKE_WORD_TRANSLATION(design, "design", "Auslegung", "ontwerp", "Design", "projekt", "design", "design", "tasarım", "disegno") -MAKE_WORD_TRANSLATION(minflow, "min flow", "min. Durchfluss", "min. doorstroom", "Min flöde", "minimalny przepływ", "min strømming", "flux min", "minimum akış", "flusso minimo") -MAKE_WORD_TRANSLATION(maxflow, "max flow", "max. Durchfluss", "max. doorstroom", "Max flöde", "maksymalny przepływ", "maks strømming", "flux max", "maksimum akış", "flusso massimo") -MAKE_WORD_TRANSLATION(fast, "fast", "schnell", "snel", "snabb", "szybkie", "hurtig", "rapide", "hızlı", "veloce") -MAKE_WORD_TRANSLATION(slow, "slow", "langsam", "langzaam", "långsam", "powolne", "langsom", "lent", "yavaş", "lento") -MAKE_WORD_TRANSLATION(internal_temperature, "internal temperature", "Interne Temperatur", "interne temperatuur", "Interntemperatur", "temperatura wewnętrzna", "interntemperatur", "température interne", "oda sıcaklığı", "temperatura interna") -MAKE_WORD_TRANSLATION(internal_setpoint, "internal setpoint", "Interner Sollwert", "interne streeftemperatuur", "Internt börvärde", "nastawa wewnętrzna", "internt settpunkt", "consigne interne", "istenen oda sıcaklığı", "setpoint interno") -MAKE_WORD_TRANSLATION(external_temperature, "external temperature", "Externe Temperatur", "externe temperatuur", "Extern temperatur", "temperatura zewnętrzna", "ekstern temperatur", "température externe", "dış sıcaklık", "temperatura esterna") -MAKE_WORD_TRANSLATION(burner_temperature, "burner temperature", "Brennertemperatur", "brander temperatuur", "Brännartemperatur", "temperatura palnika", "brennertemperatur", "température du brûleur", "kazan sıcaklığı", "temperatura bruciatore") -MAKE_WORD_TRANSLATION(ww_temperature, "ww temperature", "Wassertemperatur", "watertemperatuur", "Vattentemperatur", "temperatura c.w.u.", "vanntemperatur", "température de l'eau", "Kullanım suyu sıcaklığı", "temperatura acqua") -MAKE_WORD_TRANSLATION(smoke_temperature, "smoke temperature", "Abgastemperatur", "rookgastemperatuur", "Rökgastemperatur", "temperatura dymu", "røykgasstemperatur", "température des gaz d'échappement", "baca gazı sıcaklığı", "temperatura fumo") -MAKE_WORD_TRANSLATION(weather_compensated, "weather compensated", "Wetter kompensiert", "weer gecompenseerd", "Väderkompenserad", "skompensow. pogodą", "værkompensert", "compensation par l'extérieur", "hava durumuna göre dengelenmiş", "acqua compensata") -MAKE_WORD_TRANSLATION(outside_basepoint, "outside basepoint", "Basispunkt Außentemp.", "buiten basispunt", "Utomhus baspunkt", "temp. zewn. z pkt. pocz.", "utendørs basispunkt", "point de base temp. ext.", "dış hava sıcaklığı taban noktası", "basepoint esterno") -MAKE_WORD_TRANSLATION(functioning_mode, "functioning mode", "Funktionsweise", "functiemodus", "Driftläge", "tryb pracy", "driftsmodus", "mode de fonctionnement", "işletme konumu", "modalità di funzionamento") +MAKE_WORD_TRANSLATION(seltemp, "selTemp", "Solltemperatur", "doeltemperatuur", "Börtemperatur", "temperatura zadana", "innstilt temperatur", "consigne température", "ayarlanmış sıcaklık", "temperatura di consegna", "zadaná teplota") +MAKE_WORD_TRANSLATION(roomtemp, "roomTemp", "Raumtemperatur", "kamertemperatuur", "Rumstemperatur", "temperatura w pomieszczeniu", "romstemperatur", "température de la pièce", "oda sıcaklığı", "temperatura camera", "teplota izby") +MAKE_WORD_TRANSLATION(own_prog, "own prog", "Eigenprog.", "eigen prog.", "Egen prog.", "program własny", "eget prog.", "programme propre", "isteğe göre ayarlanmış program", "proprio prog.", "vlastný prog") +MAKE_WORD_TRANSLATION(std_prog, "std prog", "Standardprog.", "standaard prog.", "Standardprog.", "program standardowy", "standardprog.", "programme standard", "sandart pogram", "programma standard", "Štandardprog.") +MAKE_WORD_TRANSLATION(light, "light", "Leicht", "licht", "Lätt", "lekki", "lett", "léger", "düşük", "luce", "ľahký") +MAKE_WORD_TRANSLATION(medium, "medium", "Mittel", "middel", "Medel", "średni", "medium", "medium", "orta", "medio", "stredný") +MAKE_WORD_TRANSLATION(heavy, "heavy", "Schwer", "zwaar", "Tung", "ciężki", "tung", "lourd", "yüksek", "pesante", "ťažký") +MAKE_WORD_TRANSLATION(start, "start", "Start", "start", "Start", "start", "start", "début", "başlat", "avvia", "štart") +MAKE_WORD_TRANSLATION(heat, "heat", "Heizen", "verwarmen", "Värme", "ciepło", "varmer", "chaleur", "ısıtma", "caldo", "kúrenie") +MAKE_WORD_TRANSLATION(hold, "hold", "Halten", "pauzeren", "Paus", "pauza", "pause", "pause", "durdur", "pausa", "pauza") +MAKE_WORD_TRANSLATION(cool, "cool", "Kühlen", "koelen", "Kyla", "zimno", "kjøler", "froid", "soğutma", "freddo", "chladenie") +MAKE_WORD_TRANSLATION(end, "end", "Ende", "einde", "Slut", "koniec", "slutt", "fin", "bitti", "fine", "koniec") +MAKE_WORD_TRANSLATION(german, "german", "Deutsch", "Duits", "Tyska", "niemiecki", "tysk", "allemand", "Almanca", "Tedesco", "nemecky") +MAKE_WORD_TRANSLATION(dutch, "dutch", "Niederländisch", "Nederlands", "Nederländska", "niderlandzki", "nederlandsk", "néerlandais", "Flemenkçe", "Olandese", "holandsky") +MAKE_WORD_TRANSLATION(french, "french", "Französisch", "Frans", "Franska", "francuski", "fransk", "français", "Fransızca", "Francese", "francúzsky") +MAKE_WORD_TRANSLATION(italian, "italian", "Italienisch", "Italiaans", "Italienska", "włoski", "italiensk", "italien", "İtalyanca", "Italiano", "taliansky") +MAKE_WORD_TRANSLATION(high, "high", "hoch", "hoog", "Hög", "wysoki", "høy", "haut", "yüksek", "alto", "vysoký") +MAKE_WORD_TRANSLATION(low, "low", "niedrig", "laag", "Låg", "niski", "lav", "bas", "düşük", "basso", "nízky") +MAKE_WORD_TRANSLATION(radiator, "radiator", "Heizkörper", "radiator", "Radiator", "grzejniki", "radiator", "radiateur", "radyatör", "radiatore", "radiátor") +MAKE_WORD_TRANSLATION(convector, "convector", "Konvektor", "convector", "Konvektor", "konwektory", "konvektor", "convecteur", "convector", "convettore", "konvektor") +MAKE_WORD_TRANSLATION(floor, "floor", "Fussboden", "vloer", "Golv", "podłoga", "gulv", "sol", "yer", "pavimento", "podlaha") +MAKE_WORD_TRANSLATION(summer, "summer", "Sommer", "zomer", "Sommar", "lato", "sommer", "été", "yaz", "estate", "leto") +MAKE_WORD_TRANSLATION(winter, "winter", "Winter", "winter", "Vinter", "zima", "vinter", "hiver", "kış", "inverno", "zima") +MAKE_WORD_TRANSLATION(outdoor, "outdoor", "Außen", "buiten", "Utomhus", "temp. zewnętrzna", "utendørs", "extérieur", "dış", "esterno", "vonku") +MAKE_WORD_TRANSLATION(room, "room", "Raum", "kamer", "Rum", "temp. w pomieszczeniu", "", "pièce", "oda", "camera", "izba") // TODO translate +MAKE_WORD_TRANSLATION(room_outdoor, "room outdoor", "Raum+Außen", "kamer+buiten", "Rum+Ute", "temp. w pom. i zewn.", "rom utendørs", "pièce extérieure", "oda ve dış", "camera esterna", "izba externá") +MAKE_WORD_TRANSLATION(power, "power", "Leistung", "vermogen", "Effekt", "moc", "effekt", "puissance", "güç", "potenza", "výkon") +MAKE_WORD_TRANSLATION(constant, "constant", "konstant", "constant", "Konstant", "stały", "konstant", "constant", "sabit", "costante", "konštantný") +MAKE_WORD_TRANSLATION(simple, "simple", "einfach", "simpel", "enkel", "prosty", "enkel", "simple", "basit", "semplice", "jednoduchý") +MAKE_WORD_TRANSLATION(optimized, "optimized", "optimiert", "geoptimaliseerd", "optimerad", "zoptymalizowany", "optimalisert", "optimisé", "optimize", "ottimizzato", "optimalizovaný") +MAKE_WORD_TRANSLATION(nofrost, "nofrost", "Frostschutz", "vorstbescherming", "Frostskydd", "ochrona przed zamarzaniem", "frostsikring", "protection gel", "Donma koruması", "protezione gelo", "bez námrazy") +MAKE_WORD_TRANSLATION(defrost, "defrost", "Abtauen", "ontdooien", "avfrostning", "odladzanie", "tine", "dégivrage", "buz çözücü", "scongelamento", "odmrazenie") +MAKE_WORD_TRANSLATION(comfort, "comfort", "Komfort", "comfort", "Komfort", "komfort", "komfort", "comfort", "konfor", "comfort", "komfortný") +MAKE_WORD_TRANSLATION(night, "night", "Nacht", "nacht", "Natt", "noc", "natt", "nuit", "gece", "notte", "noc") +MAKE_WORD_TRANSLATION(day, "day", "Tag", "dag", "Dag", "dzień", "dag", "jour", "gün", "giorno", "deň") +MAKE_WORD_TRANSLATION(holiday, "holiday", "Urlaub", "vakantie", "Helgdag", "urlop", "ferie", "vacances", "tatil", "vacanza", "dovolenka") +MAKE_WORD_TRANSLATION(reduce, "reduce", "reduziert", "gereduceerd", "Reducera", "zredukowany", "redusere", "réduit", "düşür", "riduzione", "znížený") +MAKE_WORD_TRANSLATION(noreduce, "no reduce", "unreduziert", "niet gereduceerd", "oreducerad", "bez redukcji", "ingen reduksjon", "pas de réduction", "düşürme", "non ridurre", "bez zníženia") +MAKE_WORD_TRANSLATION(offset, "offset", "Anhebung", "offset", "Förskutning", "przesunięcie", "kompensasjon", "offset", "kompansasyon", "offset", "ofset") +MAKE_WORD_TRANSLATION(design, "design", "Auslegung", "ontwerp", "Design", "projekt", "design", "design", "tasarım", "disegno", "design") +MAKE_WORD_TRANSLATION(minflow, "min flow", "min. Durchfluss", "min. doorstroom", "Min flöde", "minimalny przepływ", "min strømming", "flux min", "minimum akış", "flusso minimo", "min. tok") +MAKE_WORD_TRANSLATION(maxflow, "max flow", "max. Durchfluss", "max. doorstroom", "Max flöde", "maksymalny przepływ", "maks strømming", "flux max", "maksimum akış", "flusso massimo", "max. tok") +MAKE_WORD_TRANSLATION(fast, "fast", "schnell", "snel", "snabb", "szybkie", "hurtig", "rapide", "hızlı", "veloce", "rýchly") +MAKE_WORD_TRANSLATION(slow, "slow", "langsam", "langzaam", "långsam", "powolne", "langsom", "lent", "yavaş", "lento", "pomalý") +MAKE_WORD_TRANSLATION(internal_temperature, "internal temperature", "Interne Temperatur", "interne temperatuur", "Interntemperatur", "temperatura wewnętrzna", "interntemperatur", "température interne", "oda sıcaklığı", "temperatura interna", "vnútorná teplota") +MAKE_WORD_TRANSLATION(internal_setpoint, "internal setpoint", "Interner Sollwert", "interne streeftemperatuur", "Internt börvärde", "nastawa wewnętrzna", "internt settpunkt", "consigne interne", "istenen oda sıcaklığı", "setpoint interno", "interná pož. hodnota") +MAKE_WORD_TRANSLATION(external_temperature, "external temperature", "Externe Temperatur", "externe temperatuur", "Extern temperatur", "temperatura zewnętrzna", "ekstern temperatur", "température externe", "dış sıcaklık", "temperatura esterna", "vonkajšie teplota") +MAKE_WORD_TRANSLATION(burner_temperature, "burner temperature", "Brennertemperatur", "brander temperatuur", "Brännartemperatur", "temperatura palnika", "brennertemperatur", "température du brûleur", "kazan sıcaklığı", "temperatura bruciatore", "teplota horáka") +MAKE_WORD_TRANSLATION(ww_temperature, "ww temperature", "Wassertemperatur", "watertemperatuur", "Vattentemperatur", "temperatura c.w.u.", "vanntemperatur", "température de l'eau", "Kullanım suyu sıcaklığı", "temperatura acqua", "teplota vody") +MAKE_WORD_TRANSLATION(smoke_temperature, "smoke temperature", "Abgastemperatur", "rookgastemperatuur", "Rökgastemperatur", "temperatura dymu", "røykgasstemperatur", "température des gaz d'échappement", "baca gazı sıcaklığı", "temperatura fumo", "teplota dymu") +MAKE_WORD_TRANSLATION(weather_compensated, "weather compensated", "Wetter kompensiert", "weer gecompenseerd", "Väderkompenserad", "skompensow. pogodą", "værkompensert", "compensation par l'extérieur", "hava durumuna göre dengelenmiş", "acqua compensata", "kompenzácia počasia") +MAKE_WORD_TRANSLATION(outside_basepoint, "outside basepoint", "Basispunkt Außentemp.", "buiten basispunt", "Utomhus baspunkt", "temp. zewn. z pkt. pocz.", "utendørs basispunkt", "point de base temp. ext.", "dış hava sıcaklığı taban noktası", "basepoint esterno", "vonkajší základný bod") +MAKE_WORD_TRANSLATION(functioning_mode, "functioning mode", "Funktionsweise", "functiemodus", "Driftläge", "tryb pracy", "driftsmodus", "mode de fonctionnement", "işletme konumu", "modalità di funzionamento", "funkčný režim") // mixer -MAKE_WORD_TRANSLATION(stopped, "stopped", "gestoppt", "gestopt", "stoppad", "zatrzymany", "stoppet", "arrêté", "durdu", "fermato") -MAKE_WORD_TRANSLATION(opening, "opening", "öffnen", "openen", "öppnar", "otwieranie", "åpner", "ouverture", "açılıyor", "aperto") -MAKE_WORD_TRANSLATION(closing, "closing", "schließen", "sluiten", "stänger", "zamykanie", "stenger", "fermeture", "kapanıyor", "chiuso") -MAKE_WORD_TRANSLATION(open, "open", "offen", "open", "Öppen", "otwórz", "åpen", "ouvert", "açık", "aprire") -MAKE_WORD_TRANSLATION(close, "close", "geschlossen", "Gesloten", "Stängd", "zamknij", "stengt", "fermé", "kapalı", "chiudere") +MAKE_WORD_TRANSLATION(stopped, "stopped", "gestoppt", "gestopt", "stoppad", "zatrzymany", "stoppet", "arrêté", "durdu", "fermato", "zastavený") +MAKE_WORD_TRANSLATION(opening, "opening", "öffnen", "openen", "öppnar", "otwieranie", "åpner", "ouverture", "açılıyor", "aperto", "otvorenie") +MAKE_WORD_TRANSLATION(closing, "closing", "schließen", "sluiten", "stänger", "zamykanie", "stenger", "fermeture", "kapanıyor", "chiuso", "zatvorenie") +MAKE_WORD_TRANSLATION(open, "open", "offen", "open", "Öppen", "otwórz", "åpen", "ouvert", "açık", "aprire", "otvoriť") +MAKE_WORD_TRANSLATION(close, "close", "geschlossen", "Gesloten", "Stängd", "zamknij", "stengt", "fermé", "kapalı", "chiudere", "zatvoriť") // solar ww -MAKE_WORD_TRANSLATION(cyl1, "cyl 1", "Zyl_1", "Cil 1", "Cyl 1", "cyl 1", "cyl 1", "cyl 1", "cly 1", "Cil 1") -MAKE_WORD_TRANSLATION(cyl2, "cyl 2", "Zyl_2", "Cil 2", "Cyl 2", "cyl 2", "cyl 2", "cyl 2", "cly 1", "Cil 2") +MAKE_WORD_TRANSLATION(cyl1, "cyl 1", "Zyl_1", "Cil 1", "Cyl 1", "cyl 1", "cyl 1", "cyl 1", "cly 1", "Cil 1", "cyl 1") +MAKE_WORD_TRANSLATION(cyl2, "cyl 2", "Zyl_2", "Cil 2", "Cyl 2", "cyl 2", "cyl 2", "cyl 2", "cly 1", "Cil 2", "cyl 2") // ventilation -MAKE_WORD_TRANSLATION(demand, "demand", "Bedarf", "vereist", "", "zapotrzebowanie", "", "", "talep", "richiesta") // TODO translate -MAKE_WORD_TRANSLATION(intense, "intense", "Intensiv", "intensief", "", "intensywne", "", "", "yoğun", "intensivo") // TODO translate -MAKE_WORD_TRANSLATION(sleep, "sleep", "Einschlafen", "slaapmodus", "", "sen", "", "", "uyku", "notturno") // TODO translate -MAKE_WORD_TRANSLATION(partymode, "party", "Party", "party", "", "impreza", "", "", "parti", "festa") // TODO translate -MAKE_WORD_TRANSLATION(fireplace, "fireplace", "Kamin", "haard", "", "kominek", "", "", "şömine", "camino") // TODO translate +MAKE_WORD_TRANSLATION(demand, "demand", "Bedarf", "vereist", "", "zapotrzebowanie", "", "", "talep", "richiesta", "požiadavka") // TODO translate +MAKE_WORD_TRANSLATION(intense, "intense", "Intensiv", "intensief", "", "intensywne", "", "", "yoğun", "intensivo", "intenzívne") // TODO translate +MAKE_WORD_TRANSLATION(sleep, "sleep", "Einschlafen", "slaapmodus", "", "sen", "", "", "uyku", "notturno", "spiace") // TODO translate +MAKE_WORD_TRANSLATION(partymode, "party", "Party", "party", "", "impreza", "", "", "parti", "festa", "párty režim") // TODO translate +MAKE_WORD_TRANSLATION(fireplace, "fireplace", "Kamin", "haard", "", "kominek", "", "", "şömine", "camino", "krb") // TODO translate // MQTT Discovery - this is special device entity for 'climate' -MAKE_TRANSLATION(haclimate, "haclimate", "Discovery current room temperature", "Discovery Temperatur", "Discovery huidige kamertemperatuur", "", "termostat w HA", "HA Avlest temp", "", "Güncel osa sıcaklığı", "verifica temperatura ambiente attuale") // TODO translate +MAKE_TRANSLATION(haclimate, "haclimate", "Discovery current room temperature", "Discovery Temperatur", "Discovery huidige kamertemperatuur", "", "termostat w HA", "HA Avlest temp", "", "Güncel osa sıcaklığı", "verifica temperatura ambiente attuale", "Zistiť aktuálnu teplotu v miestnosti") // TODO translate -// Entity translations: tag, mqtt, en, de, nl, sv, pl, no, fr, tr, it +// Entity translations: tag, mqtt, en, de, nl, sv, pl, no, fr, tr, it, sk // Boiler -MAKE_TRANSLATION(forceHeatingOff, "heatingoff", "force heating off", "Heizen abschalten", "", "", "wymuś wyłączenie grzania", "", "", "", "") // TODO translate -MAKE_TRANSLATION(wwtapactivated, "wwtapactivated", "turn on/off", "Durchlauferhitzer aktiv", "zet aan/uit", "på/av", "system przygotowywania", "Varmtvann active", "ecs activée", "aç/kapa", "commuta on/off") -MAKE_TRANSLATION(reset, "reset", "reset", "Reset", "Reset", "Nollställ", "kasowanie komunikatu", "nullstill", "reset", "Sıfırla", "Reset") -MAKE_TRANSLATION(oilPreHeat, "oilpreheat", "oil preheating", "Ölvorwärmung", "Olie voorverwarming", "Förvärmning olja", "podgrzewanie oleju", "oljeforvarming", "préchauffage de l'huile", "Yakıt Ön ısıtma devrede", "preriscaldamento olio") -MAKE_TRANSLATION(heatingActive, "heatingactive", "heating active", "Heizen aktiv", "Verwarming actief", "Uppvärmning aktiv", "c.o. aktywne", "oppvarming aktiv", "chauffage actif", "ısıtma devrede", "riscaldamento attivo") -MAKE_TRANSLATION(heatingOn, "heating", "heating", "Heizen", "verwarmen", "Uppvärmning", "ogrzewanie", "oppvarming", "chauffage", "ısıtma", "riscaldamento") -MAKE_TRANSLATION(tapwaterActive, "tapwateractive", "tapwater active", "Warmwasser aktiv", "Warm water actief", "Varmvatten aktiv", "c.w.u. aktywne", "varmtvann aktiv", "eau chaude active", "sıcak kullanım suyu devrede", "acqua calda attiva") -MAKE_TRANSLATION(selFlowTemp, "selflowtemp", "selected flow temperature", "Sollwert Vorlauftemperatur", "Ingestelde aanvoertemperatuur", "Börvärde Flödestemperatur", "wybrana temperatura zasilania", "valgt turtemperatur", "température de flux selectionnée", "seçili akış sıcaklığı", "flusso temperatura selezionato") -MAKE_TRANSLATION(selBurnPow, "selburnpow", "burner selected max power", "Sollwert Brennerleistung", "Ingestelde maximale brandervermogen", "Brännare vald maxeffekt", "wybrana moc źródła ciepła", "settpunkt brennerkapasitet", "puissance max du brûleur selectionnée", "seçili kazan maksimum güç", "Setpoint potenza bruciatore") -MAKE_TRANSLATION(absBurnPow, "absburnpow", "burner current power (absolute)", "Brennerleistung (absolut)", "Brandervermogen (abs)", "Värmepanna aktuell effekt (abs)", "aktualna moc źródła ciepła (absolutna)", "brennereffekt", "puissance du brûleur actuelle (abs)", "kazan anlık akış gücü(mutlak)", "potenza attuale del bruciatore (assoluta)") -MAKE_TRANSLATION(heatingPumpMod, "heatingpumpmod", "heating pump modulation", "Heizungspumpe 1 Modulation", "Modulatie verwarmingspomp", "Modulering Värmepump", "wysterowanie pompy c.o.", "varmepumpemodulering", "modulation de la pompe à chaleur", "ısı pompası modülasyonu", "modulazione pompa di calore") -MAKE_TRANSLATION(outdoorTemp, "outdoortemp", "outside temperature", "Aussentemperatur", "Buitentemperatuur", "Utomhustemperatur", "temperatura zewnętrzna", "utetemperatur", "température extérieure", "dış ortam sıcaklığı", "tempertura esterna") -MAKE_TRANSLATION(curFlowTemp, "curflowtemp", "current flow temperature", "aktuelle Vorlauftemperatur", "Huidige aanvoertemperatuur", "Flödestemperatur", "temperatura zasilania", "aktuell strømmetemperatur", "température actuelle du flux", "akış sıcaklığı", "temperatura di mandata attuale") -MAKE_TRANSLATION(retTemp, "rettemp", "return temperature", "Rücklauftemperatur", "Retourtemperatuur", "Returtemperatur", "temperatura powrotu", "returtemperatur", "température de retour", "dönüş sıcaklığı", "temperatura di ritorno attuale") -MAKE_TRANSLATION(switchTemp, "switchtemp", "mixing switch temperature", "Mischer Schalttemperatur", "Mixer temperatuur", "Blandartemperatur", "temperatura przełączania mieszacza", "Blandertemperatur", "température de bascule du mélangeur", "karışım hücresi sıcaklığı", "Temperatura di commutazione del miscelatore") -MAKE_TRANSLATION(sysPress, "syspress", "system pressure", "Systemdruck", "Systeemdruk", "Systemtryck", "ciśnienie w systemie", "systemtrykk", "pression du système", "sistem basıncı", "pressione sistema") -MAKE_TRANSLATION(boilTemp, "boiltemp", "actual boiler temperature", "Kesseltemperatur", "Keteltemperatuur", "Temperatur Värmepanna", "temperatura zasobnika", "varmepumpetemp.", "température de la chaudière", "gerçek boyler sıcaklığı", "temperatura attuale caldaia") -MAKE_TRANSLATION(exhaustTemp, "exhausttemp", "exhaust temperature", "Abgastemperatur", "Uitlaattemperatuur", "Avgastemperatur", "temperatura spalin", "røykgasstemp", "température des gaz d'échappement", "baca sıcaklığı", "temperatura di scarico") -MAKE_TRANSLATION(burnGas, "burngas", "gas", "Gas", "Gas", "Gas", "gaz", "gass", "gaz", "gaz", "Gas") -MAKE_TRANSLATION(burnGas2, "burngas2", "gas stage 2", "Gas Stufe 2", "gas fase 2", "Gas Fas 2", "gaz 2 stopień", "gass fase 2", "gaz état 2", "gaz 2.seviye", "gas fase 2") -MAKE_TRANSLATION(flameCurr, "flamecurr", "flame current", "Flammenstrom", "Vlammenstroom", "Lågström", "prąd palnika", "flammetemp", "courrant de flamme", "alev akımı", "corrente di fiamma") -MAKE_TRANSLATION(heatingPump, "heatingpump", "heating pump", "Heizungspumpe", "Verwarmingspomp", "Värmepump", "pompa ciepła", "varmepumpe", "pompe à chaleur", "ısı pompası", "pompa di calore") -MAKE_TRANSLATION(fanWork, "fanwork", "fan", "Gebläse", "Ventilator", "Fläkt", "wentylator", "vifte", "ventilateur", "fan", "Ventilatore") -MAKE_TRANSLATION(ignWork, "ignwork", "ignition", "Zündung", "Ontsteking", "Tändning", "zapłon", "tenning", "ignition", "ateşleme", "accensione") -MAKE_TRANSLATION(heatingActivated, "heatingactivated", "heating activated", "Heizen aktiviert", "Verwarmen geactiveerd", "Uppvärmning aktiv", "system c.o.", "oppvarming aktivert", "chauffage activé", "ısıtma başladı", "riscaldamento attivato") -MAKE_TRANSLATION(heatingTemp, "heatingtemp", "heating temperature", "Heizungstemperatur", "Verwarmingstemperatuur", "Uppvärmningstemperatur", "temperatura grzania", "oppvarmingstemperatur", "température de chauffage", "ısıtma sıcaklığı", "temperatura riscaldamento") -MAKE_TRANSLATION(pumpModMax, "pumpmodmax", "boiler pump max power", "Kesselpumpen Maximalleistung", "Ketelpomp max vermogen", "Värmepannepump max effekt", "maksymalna moc pompy zasobnika", "varmepumpe maks effekt", "puissance max pompe à chaleur", "boyler pompası maksimum güç", "max potenza pompa caldaia") -MAKE_TRANSLATION(pumpModMin, "pumpmodmin", "boiler pump min power", "Kesselpumpen Minmalleistung", "Ketelpomp min vermogen", "Värmepannepump min effekt", "minimalna moc pompy zasobnika", "varmepumpe min effekt", "puissance min pompe à chaleur", "boyler pompası minimum güç", "min potenza pompa caldaia") -MAKE_TRANSLATION(pumpDelay, "pumpdelay", "pump delay", "Pumpennachlaufzeit", "Pomp nalooptijd", "Pumpfördröjning", "opóźnienie pompy", "pumpeforsinkelse", "délai d'attente pompe", "pompa gecikmesi", "ritardo pompa") -MAKE_TRANSLATION(burnMinPeriod, "burnminperiod", "burner min period", "Antipendelzeit", "Antipendeltijd", "Värmepanna Min Period", "minimalny czas pracy palnika", "varmekjele min periode", "délai d'attente du brûleur", "kazan minmum periyod", "periodo minimo del bruciatore") -MAKE_TRANSLATION(burnMinPower, "burnminpower", "burner min power", "minimale Brennerleistung", "Minimaal brandervermogen", "Värmepanna Min Effekt", "minimalna moc źródła ciepła", "varmekjele min effekt", "puissance min brûleur", "kazan minimum güç", "potenza minima bruciatore") -MAKE_TRANSLATION(burnMaxPower, "burnmaxpower", "burner max power", "maximale Brennerleistung", "Maximaal brandervermogen", "Värmepanna Max Effekt", "maksymalna moc źródła ciepła", "varmekjele maks effekt", "puissance max brûleur", "kazan maksimum güç", "potenza massima bruciatore") -MAKE_TRANSLATION(boilHystOn, "boilhyston", "hysteresis on temperature", "Einschaltdifferenz", "ketel aan hysterese verschil", "Hysteres aktiveringstemperatur", "histereza załączania", "hysterese på temperatur", "hysteresis température d'allumage", "gecikme sıcaklığı devrede", "isteresi sulla temperatura") -MAKE_TRANSLATION(boilHystOff, "boilhystoff", "hysteresis off temperature", "Ausschaltdifferenz", "ketel uit hysterese verschil", "Hysteres inaktiveringstemperatur", "histereza wyłączania", "hysterese av temperatur", "hysteresis température d'extinction", "gecikme sıcaklığı kapalı", "isteresi fuori temperatura") -MAKE_TRANSLATION(boil2HystOn, "boil2hyston", "hysteresis stage 2 on temperature", "Einschaltdifferenz Stufe 2", "ketel aan hysterese verschil 2", "Hysteres aktiveringstemperatur 2", "histereza załączania stopnia 2", "", "hysteresis état 2 température d'allumage", "2. seviye gecikme sıcaklığı devrede", "stadio di isteresi 2 sulla temperatura") // TODO translate -MAKE_TRANSLATION(boil2HystOff, "boil2hystoff", "hysteresis stage 2 off temperature", "Ausschaltdifferenz Stufe 2", "ketel uit hysterese verschil 2", "Hysteres inaktiveringstemperatur 2", "histereza wyłączania stopnia 2", "hysterese inaktiveringstemperatur 2", "hysteresis état 2 température d'extinction", "2. seviye gecikme sıcaklığı kapalı", "isteresi stadio 2 fuori temperatura") -MAKE_TRANSLATION(setFlowTemp, "setflowtemp", "set flow temperature", "Sollwert Vorlauftemperatur", "Ingestelde aanvoertemperatuur", "Börvärde Flödestemperatur", "zadana temperatura zasilania", "innstilt turtemperatur", "température du flux définie", "akış sıcaklığını ayarla", "impostare temperatura di mandata") -MAKE_TRANSLATION(setBurnPow, "setburnpow", "burner set power", "Sollwert Brennerleistung", "Ingesteld brandervermogen", "Värmepanna vald Effekt", "zadana moc palnika", "varmekjele valgt effekt", "puissance du brûleur définie", "kazan gücünü ayarla", "impostare potenza bruciatore") -MAKE_TRANSLATION(curBurnPow, "curburnpow", "burner current power", "Brennerleistung", "Brandervermogen", "Värmepanna aktuell effekt", "aktualna moc źródła ciepła", "brennereffekt", "puissance du brûleur actuelle", "kazan güncel gücü", "potenza attuale bruciatore") -MAKE_TRANSLATION(burnStarts, "burnstarts", "burner starts", "Brenner Starts", "Aantal brander starts", "Värmepanna antal starter", "liczba uruchomień palnika", "antall varmepumpe starter", "démarrages du brûleur", "kazan başlıyor", "avvii bruciatore") -MAKE_TRANSLATION(burnWorkMin, "burnworkmin", "total burner operating time", "Brenner Laufzeit", "Totale branderlooptijd", "Värmepanna aktiva timmar", "łączny czas pracy palnika", "brennersteg tid i min", "durée de fonctionnement totale du brûleur", "toplam kazan çalışma süresi", "tempo totale di funzionamento del bruciatore") -MAKE_TRANSLATION(burn2WorkMin, "burn2workmin", "burner stage 2 operating time", "Brenner Stufe 2 Laufzeit", "Totale looptijd brander fase 2", "Värmepanna steg 2 aktiva timmar", "łączny czas pracy palnika 2 stopnia", "brennersteg2 tid i min", "durée de fonctionnement totale du brûleur état 2", "2. seviye toplam kazan çalışma süresi", "tempo di funzionamento del bruciatore 2° stadio") -MAKE_TRANSLATION(heatWorkMin, "heatworkmin", "total heat operating time", "Heizung Laufzeit", "Totale looptijd verwarming", "Uppvärmning aktiva timmar", "łączny czas grzania", "varmetid i min", "durée de fonctionnement du chauffage", "toplam ısıtma çalışma süresi", "tempo totale di funzionamento in riscaldamento") -MAKE_TRANSLATION(heatStarts, "heatstarts", "burner starts heating", "Brenner Starts Heizung", "Aantal brander starts verwarming", "Uppvärmning antal starter", "liczba uruchomień palnika na ogrzewanie", "antall oppvarmninger", "démarrages du chauffage", "kazan ısıtmaya başlıyor", "preriscaldamento bruciatore") -MAKE_TRANSLATION(UBAuptime, "ubauptime", "total UBA operating time", "Anlagen-Gesamtlaufzeit", "totale looptijd branderautomaat (UBA)", "Total Tid", "łączny czas pracy układu sterowania", "totaltid", "durée de fonctionnement totale de l'appareil (UBA)", "kazanın toplam işletme süresi", "Tempo di funzionamento totale del sistema") -MAKE_TRANSLATION(lastCode, "lastcode", "last error code", "Letzter Fehler", "Laatste foutcode", "Senaste Felkod", "ostatni błąd", "siste feilkode", "dernier code d'erreur", "son hata kodu", "ultimo codice errore") -MAKE_TRANSLATION(serviceCode, "servicecode", "service code", "Statusmeldung", "Statuscode", "Servicekod", "kod serwisowy", "servicekode", "code de service", "servis kodu", "codice messaggio di stato") -MAKE_TRANSLATION(serviceCodeNumber, "servicecodenumber", "service code number", "Statusmeldungsnummer", "Status codenummer", "Servicekod", "numer kodu serwisowego", "servicekodenummer", "numéro du code de service", "servis kod numarası", "numero del messaggio di stato") -MAKE_TRANSLATION(maintenanceMessage, "maintenancemessage", "maintenance message", "Wartungsmeldung", "Onderhoudsmelding", "Servicemeddelande", "komunikat przeglądu", "vedlikeholdsmelding", "message de maintenance", "bakım mesajı", "messaggio di manutenzione") -MAKE_TRANSLATION(maintenanceDate, "maintenancedate", "next maintenance date", "Wartungsdatum", "Onderhoudsdatum", "Datum nästa Service", "termin następnego przeglądu", "vedlikeholdsdato", "prochaine date de maintenance", "bakım tarihi", "prossima data di manutenzione") -MAKE_TRANSLATION(maintenanceType, "maintenance", "maintenance scheduled", "Wartungsplan", "Onderhoud gepland", "Underhall schemlagt", "rodzaj przeglądu", "vedlikeholdstype", "maintenance prévue", "planlı bakım", "manutenzione programmata") -MAKE_TRANSLATION(maintenanceTime, "maintenancetime", "time to next maintenance", "Wartung in", "Onderhoud in", "Tid till nästa underhall", "czas do kolejnego przeglądu", "vedlikeholdstid", "durée avant la prochaine maintenance", "bakıma kalan süre", "tempo alla prossima manutenzione") -MAKE_TRANSLATION(emergencyOps, "emergencyops", "emergency operation", "Notoperation", "Noodoperatie", "Nöddrift", "praca w trybie awaryjnym", "nøddrift", "opération d'urgence", "acil durum çalışması", "operazione di emergenza") -MAKE_TRANSLATION(emergencyTemp, "emergencytemp", "emergency temperature", "Nottemperatur", "Noodtemperatuur", "Nöddrift temperatur", "temperatura w trybie awaryjnym", "nødtemperatur", "température d'urgence", "acil durum sıcaklığı", "temperatura di emergenza") -MAKE_TRANSLATION(pumpMode, "pumpmode", "boiler pump mode", "Kesselpumpen Modus", "Ketelpomp modus", "", "tryb pracy pompy kotła", "pumpemodus", "", "pompa modu", "modalità pompa caldaia") // TODO translate -MAKE_TRANSLATION(headertemp, "headertemp", "low loss header", "Hydr. Weiche", "open verdeler", "", "sprzęgło hydrauliczne", "", "bouteille de déc. hydr.", "isı bloğu gidiş suyu sıc.", "comp. idr.") // TODO translate -MAKE_TRANSLATION(heatblock, "heatblock", "heating block", "Wärmezelle", "Aanvoertemp. warmtecel", "", "blok grzewczy", "", "départ corps de chauffe", "Hid.denge kabı sıcaklığı", "mandata scamb. pr.") // TODO translate +MAKE_TRANSLATION(forceHeatingOff, "heatingoff", "force heating off", "Heizen abschalten", "", "", "wymuś wyłączenie grzania", "", "", "", "", "vynútiť kúrenie") // TODO translate +MAKE_TRANSLATION(wwtapactivated, "wwtapactivated", "turn on/off", "Durchlauferhitzer aktiv", "zet aan/uit", "på/av", "system przygotowywania", "Varmtvann active", "ecs activée", "aç/kapa", "commuta on/off", "zapnúť/vypnúť") +MAKE_TRANSLATION(reset, "reset", "reset", "Reset", "Reset", "Nollställ", "kasowanie komunikatu", "nullstill", "reset", "Sıfırla", "Reset", "reset") +MAKE_TRANSLATION(oilPreHeat, "oilpreheat", "oil preheating", "Ölvorwärmung", "Olie voorverwarming", "Förvärmning olja", "podgrzewanie oleju", "oljeforvarming", "préchauffage de l'huile", "Yakıt Ön ısıtma devrede", "preriscaldamento olio", "predohrev oleja") +MAKE_TRANSLATION(heatingActive, "heatingactive", "heating active", "Heizen aktiv", "Verwarming actief", "Uppvärmning aktiv", "c.o. aktywne", "oppvarming aktiv", "chauffage actif", "ısıtma devrede", "riscaldamento attivo", "vykurovanie aktívne") +MAKE_TRANSLATION(heatingOn, "heating", "heating", "Heizen", "verwarmen", "Uppvärmning", "ogrzewanie", "oppvarming", "chauffage", "ısıtma", "riscaldamento", "vykurovanie") +MAKE_TRANSLATION(tapwaterActive, "tapwateractive", "tapwater active", "Warmwasser aktiv", "Warm water actief", "Varmvatten aktiv", "c.w.u. aktywne", "varmtvann aktiv", "eau chaude active", "sıcak kullanım suyu devrede", "acqua calda attiva", "aktívna voda z vodovodu") +MAKE_TRANSLATION(selFlowTemp, "selflowtemp", "selected flow temperature", "Sollwert Vorlauftemperatur", "Ingestelde aanvoertemperatuur", "Börvärde Flödestemperatur", "wybrana temperatura zasilania", "valgt turtemperatur", "température de flux selectionnée", "seçili akış sıcaklığı", "flusso temperatura selezionato", "zvolená teplota prívodu") +MAKE_TRANSLATION(selBurnPow, "selburnpow", "burner selected max power", "Sollwert Brennerleistung", "Ingestelde maximale brandervermogen", "Brännare vald maxeffekt", "wybrana moc źródła ciepła", "settpunkt brennerkapasitet", "puissance max du brûleur selectionnée", "seçili kazan maksimum güç", "Setpoint potenza bruciatore", "horák zvolený maximálny výkon") +MAKE_TRANSLATION(absBurnPow, "absburnpow", "burner current power (absolute)", "Brennerleistung (absolut)", "Brandervermogen (abs)", "Värmepanna aktuell effekt (abs)", "aktualna moc źródła ciepła (absolutna)", "brennereffekt", "puissance du brûleur actuelle (abs)", "kazan anlık akış gücü(mutlak)", "potenza attuale del bruciatore (assoluta)", "aktuálny výkon horáka (absolútny)") +MAKE_TRANSLATION(heatingPumpMod, "heatingpumpmod", "heating pump modulation", "Heizungspumpe 1 Modulation", "Modulatie verwarmingspomp", "Modulering Värmepump", "wysterowanie pompy c.o.", "varmepumpemodulering", "modulation de la pompe à chaleur", "ısı pompası modülasyonu", "modulazione pompa di calore", "modulácia tepelného čerpadla") +MAKE_TRANSLATION(outdoorTemp, "outdoortemp", "outside temperature", "Aussentemperatur", "Buitentemperatuur", "Utomhustemperatur", "temperatura zewnętrzna", "utetemperatur", "température extérieure", "dış ortam sıcaklığı", "tempertura esterna", "vonkajšia teplota") +MAKE_TRANSLATION(curFlowTemp, "curflowtemp", "current flow temperature", "aktuelle Vorlauftemperatur", "Huidige aanvoertemperatuur", "Flödestemperatur", "temperatura zasilania", "aktuell strømmetemperatur", "température actuelle du flux", "akış sıcaklığı", "temperatura di mandata attuale", "aktuálna teplota prívodu") +MAKE_TRANSLATION(retTemp, "rettemp", "return temperature", "Rücklauftemperatur", "Retourtemperatuur", "Returtemperatur", "temperatura powrotu", "returtemperatur", "température de retour", "dönüş sıcaklığı", "temperatura di ritorno attuale", "teplota spiatočky") +MAKE_TRANSLATION(switchTemp, "switchtemp", "mixing switch temperature", "Mischer Schalttemperatur", "Mixer temperatuur", "Blandartemperatur", "temperatura przełączania mieszacza", "Blandertemperatur", "température de bascule du mélangeur", "karışım hücresi sıcaklığı", "Temperatura di commutazione del miscelatore", "teplota zmiešavacieho spínača") +MAKE_TRANSLATION(sysPress, "syspress", "system pressure", "Systemdruck", "Systeemdruk", "Systemtryck", "ciśnienie w systemie", "systemtrykk", "pression du système", "sistem basıncı", "pressione sistema", "tlak v systéme") +MAKE_TRANSLATION(boilTemp, "boiltemp", "actual boiler temperature", "Kesseltemperatur", "Keteltemperatuur", "Temperatur Värmepanna", "temperatura zasobnika", "varmepumpetemp.", "température de la chaudière", "gerçek boyler sıcaklığı", "temperatura attuale caldaia", "skutočná teplota kotla") +MAKE_TRANSLATION(exhaustTemp, "exhausttemp", "exhaust temperature", "Abgastemperatur", "Uitlaattemperatuur", "Avgastemperatur", "temperatura spalin", "røykgasstemp", "température des gaz d'échappement", "baca sıcaklığı", "temperatura di scarico", "teplota výfukových plynov") +MAKE_TRANSLATION(burnGas, "burngas", "gas", "Gas", "Gas", "Gas", "gaz", "gass", "gaz", "gaz", "Gas", "plyn") +MAKE_TRANSLATION(burnGas2, "burngas2", "gas stage 2", "Gas Stufe 2", "gas fase 2", "Gas Fas 2", "gaz 2 stopień", "gass fase 2", "gaz état 2", "gaz 2.seviye", "gas fase 2", "plynový stupeň 2") +MAKE_TRANSLATION(flameCurr, "flamecurr", "flame current", "Flammenstrom", "Vlammenstroom", "Lågström", "prąd palnika", "flammetemp", "courrant de flamme", "alev akımı", "corrente di fiamma", "") // TODO translate +MAKE_TRANSLATION(heatingPump, "heatingpump", "heating pump", "Heizungspumpe", "Verwarmingspomp", "Värmepump", "pompa ciepła", "varmepumpe", "pompe à chaleur", "ısı pompası", "pompa di calore", "tepelné čerpadlo") +MAKE_TRANSLATION(fanWork, "fanwork", "fan", "Gebläse", "Ventilator", "Fläkt", "wentylator", "vifte", "ventilateur", "fan", "Ventilatore", "ventilátor") +MAKE_TRANSLATION(ignWork, "ignwork", "ignition", "Zündung", "Ontsteking", "Tändning", "zapłon", "tenning", "ignition", "ateşleme", "accensione", "zapálenie") +MAKE_TRANSLATION(heatingActivated, "heatingactivated", "heating activated", "Heizen aktiviert", "Verwarmen geactiveerd", "Uppvärmning aktiv", "system c.o.", "oppvarming aktivert", "chauffage activé", "ısıtma başladı", "riscaldamento attivato", "kúrenie aktivované") +MAKE_TRANSLATION(heatingTemp, "heatingtemp", "heating temperature", "Heizungstemperatur", "Verwarmingstemperatuur", "Uppvärmningstemperatur", "temperatura grzania", "oppvarmingstemperatur", "température de chauffage", "ısıtma sıcaklığı", "temperatura riscaldamento", "teplota vykurovania") +MAKE_TRANSLATION(pumpModMax, "pumpmodmax", "boiler pump max power", "Kesselpumpen Maximalleistung", "Ketelpomp max vermogen", "Värmepannepump max effekt", "maksymalna moc pompy zasobnika", "varmepumpe maks effekt", "puissance max pompe à chaleur", "boyler pompası maksimum güç", "max potenza pompa caldaia", "maximálny výkon kotlového čerpadla") +MAKE_TRANSLATION(pumpModMin, "pumpmodmin", "boiler pump min power", "Kesselpumpen Minmalleistung", "Ketelpomp min vermogen", "Värmepannepump min effekt", "minimalna moc pompy zasobnika", "varmepumpe min effekt", "puissance min pompe à chaleur", "boyler pompası minimum güç", "min potenza pompa caldaia", "min. výkon čerpadla kotla") +MAKE_TRANSLATION(pumpDelay, "pumpdelay", "pump delay", "Pumpennachlaufzeit", "Pomp nalooptijd", "Pumpfördröjning", "opóźnienie pompy", "pumpeforsinkelse", "délai d'attente pompe", "pompa gecikmesi", "ritardo pompa", "oneskorenie čerpadla") +MAKE_TRANSLATION(burnMinPeriod, "burnminperiod", "burner min period", "Antipendelzeit", "Antipendeltijd", "Värmepanna Min Period", "minimalny czas pracy palnika", "varmekjele min periode", "délai d'attente du brûleur", "kazan minmum periyod", "periodo minimo del bruciatore", "minimálne obdobie horáka") +MAKE_TRANSLATION(burnMinPower, "burnminpower", "burner min power", "minimale Brennerleistung", "Minimaal brandervermogen", "Värmepanna Min Effekt", "minimalna moc źródła ciepła", "varmekjele min effekt", "puissance min brûleur", "kazan minimum güç", "potenza minima bruciatore", "min. výkon horáka") +MAKE_TRANSLATION(burnMaxPower, "burnmaxpower", "burner max power", "maximale Brennerleistung", "Maximaal brandervermogen", "Värmepanna Max Effekt", "maksymalna moc źródła ciepła", "varmekjele maks effekt", "puissance max brûleur", "kazan maksimum güç", "potenza massima bruciatore", "max. výkon horáka") +MAKE_TRANSLATION(boilHystOn, "boilhyston", "hysteresis on temperature", "Einschaltdifferenz", "ketel aan hysterese verschil", "Hysteres aktiveringstemperatur", "histereza załączania", "hysterese på temperatur", "hysteresis température d'allumage", "gecikme sıcaklığı devrede", "isteresi sulla temperatura", "hysterézia teploty pri zapnutí") +MAKE_TRANSLATION(boilHystOff, "boilhystoff", "hysteresis off temperature", "Ausschaltdifferenz", "ketel uit hysterese verschil", "Hysteres inaktiveringstemperatur", "histereza wyłączania", "hysterese av temperatur", "hysteresis température d'extinction", "gecikme sıcaklığı kapalı", "isteresi fuori temperatura", "hysterézia teploty pri vypnutí") +MAKE_TRANSLATION(boil2HystOn, "boil2hyston", "hysteresis stage 2 on temperature", "Einschaltdifferenz Stufe 2", "ketel aan hysterese verschil 2", "Hysteres aktiveringstemperatur 2", "histereza załączania stopnia 2", "", "hysteresis état 2 température d'allumage", "2. seviye gecikme sıcaklığı devrede", "stadio di isteresi 2 sulla temperatura", "2. stupeň hysterézie pri teplote") // TODO translate +MAKE_TRANSLATION(boil2HystOff, "boil2hystoff", "hysteresis stage 2 off temperature", "Ausschaltdifferenz Stufe 2", "ketel uit hysterese verschil 2", "Hysteres inaktiveringstemperatur 2", "histereza wyłączania stopnia 2", "hysterese inaktiveringstemperatur 2", "hysteresis état 2 température d'extinction", "2. seviye gecikme sıcaklığı kapalı", "isteresi stadio 2 fuori temperatura", "teplota vypnutia hysterézneho stupňa 2") +MAKE_TRANSLATION(setFlowTemp, "setflowtemp", "set flow temperature", "Sollwert Vorlauftemperatur", "Ingestelde aanvoertemperatuur", "Börvärde Flödestemperatur", "zadana temperatura zasilania", "innstilt turtemperatur", "température du flux définie", "akış sıcaklığını ayarla", "impostare temperatura di mandata", "nastavená teplota prívodu") +MAKE_TRANSLATION(setBurnPow, "setburnpow", "burner set power", "Sollwert Brennerleistung", "Ingesteld brandervermogen", "Värmepanna vald Effekt", "zadana moc palnika", "varmekjele valgt effekt", "puissance du brûleur définie", "kazan gücünü ayarla", "impostare potenza bruciatore", "výkon nastaveného horáka") +MAKE_TRANSLATION(curBurnPow, "curburnpow", "burner current power", "Brennerleistung", "Brandervermogen", "Värmepanna aktuell effekt", "aktualna moc źródła ciepła", "brennereffekt", "puissance du brûleur actuelle", "kazan güncel gücü", "potenza attuale bruciatore", "aktuálny výkon horáka") +MAKE_TRANSLATION(burnStarts, "burnstarts", "burner starts", "Brenner Starts", "Aantal brander starts", "Värmepanna antal starter", "liczba uruchomień palnika", "antall varmepumpe starter", "démarrages du brûleur", "kazan başlıyor", "avvii bruciatore", "horák sa spustí") +MAKE_TRANSLATION(burnWorkMin, "burnworkmin", "total burner operating time", "Brenner Laufzeit", "Totale branderlooptijd", "Värmepanna aktiva timmar", "łączny czas pracy palnika", "brennersteg tid i min", "durée de fonctionnement totale du brûleur", "toplam kazan çalışma süresi", "tempo totale di funzionamento del bruciatore", "celkový prevádzkový čas horáka") +MAKE_TRANSLATION(burn2WorkMin, "burn2workmin", "burner stage 2 operating time", "Brenner Stufe 2 Laufzeit", "Totale looptijd brander fase 2", "Värmepanna steg 2 aktiva timmar", "łączny czas pracy palnika 2 stopnia", "brennersteg2 tid i min", "durée de fonctionnement totale du brûleur état 2", "2. seviye toplam kazan çalışma süresi", "tempo di funzionamento del bruciatore 2° stadio", "doba prevádzky 2. stupňa horáka") +MAKE_TRANSLATION(heatWorkMin, "heatworkmin", "total heat operating time", "Heizung Laufzeit", "Totale looptijd verwarming", "Uppvärmning aktiva timmar", "łączny czas grzania", "varmetid i min", "durée de fonctionnement du chauffage", "toplam ısıtma çalışma süresi", "tempo totale di funzionamento in riscaldamento", "celkový prevádzkový čas tepla") +MAKE_TRANSLATION(heatStarts, "heatstarts", "burner starts heating", "Brenner Starts Heizung", "Aantal brander starts verwarming", "Uppvärmning antal starter", "liczba uruchomień palnika na ogrzewanie", "antall oppvarmninger", "démarrages du chauffage", "kazan ısıtmaya başlıyor", "preriscaldamento bruciatore", "horák sa začne zahrievať") +MAKE_TRANSLATION(UBAuptime, "ubauptime", "total UBA operating time", "Anlagen-Gesamtlaufzeit", "totale looptijd branderautomaat (UBA)", "Total Tid", "łączny czas pracy układu sterowania", "totaltid", "durée de fonctionnement totale de l'appareil (UBA)", "kazanın toplam işletme süresi", "Tempo di funzionamento totale del sistema", "Celkový čas chodu systému") +MAKE_TRANSLATION(lastCode, "lastcode", "last error code", "Letzter Fehler", "Laatste foutcode", "Senaste Felkod", "ostatni błąd", "siste feilkode", "dernier code d'erreur", "son hata kodu", "ultimo codice errore", "posledný chybový kód") +MAKE_TRANSLATION(serviceCode, "servicecode", "service code", "Statusmeldung", "Statuscode", "Servicekod", "kod serwisowy", "servicekode", "code de service", "servis kodu", "codice messaggio di stato", "servisný kód") +MAKE_TRANSLATION(serviceCodeNumber, "servicecodenumber", "service code number", "Statusmeldungsnummer", "Status codenummer", "Servicekod", "numer kodu serwisowego", "servicekodenummer", "numéro du code de service", "servis kod numarası", "numero del messaggio di stato", "číslo kódu služby") +MAKE_TRANSLATION(maintenanceMessage, "maintenancemessage", "maintenance message", "Wartungsmeldung", "Onderhoudsmelding", "Servicemeddelande", "komunikat przeglądu", "vedlikeholdsmelding", "message de maintenance", "bakım mesajı", "messaggio di manutenzione", "správa o údržbe") +MAKE_TRANSLATION(maintenanceDate, "maintenancedate", "next maintenance date", "Wartungsdatum", "Onderhoudsdatum", "Datum nästa Service", "termin następnego przeglądu", "vedlikeholdsdato", "prochaine date de maintenance", "bakım tarihi", "prossima data di manutenzione", "dátum ďalšej údržby") +MAKE_TRANSLATION(maintenanceType, "maintenance", "maintenance scheduled", "Wartungsplan", "Onderhoud gepland", "Underhall schemlagt", "rodzaj przeglądu", "vedlikeholdstype", "maintenance prévue", "planlı bakım", "manutenzione programmata", "plánovaná údržba") +MAKE_TRANSLATION(maintenanceTime, "maintenancetime", "time to next maintenance", "Wartung in", "Onderhoud in", "Tid till nästa underhall", "czas do kolejnego przeglądu", "vedlikeholdstid", "durée avant la prochaine maintenance", "bakıma kalan süre", "tempo alla prossima manutenzione", "čas do ďalšej údržby") +MAKE_TRANSLATION(emergencyOps, "emergencyops", "emergency operation", "Notoperation", "Noodoperatie", "Nöddrift", "praca w trybie awaryjnym", "nøddrift", "opération d'urgence", "acil durum çalışması", "operazione di emergenza", "núdzová prevádzka") +MAKE_TRANSLATION(emergencyTemp, "emergencytemp", "emergency temperature", "Nottemperatur", "Noodtemperatuur", "Nöddrift temperatur", "temperatura w trybie awaryjnym", "nødtemperatur", "température d'urgence", "acil durum sıcaklığı", "temperatura di emergenza", "núdzová teplota") +MAKE_TRANSLATION(pumpMode, "pumpmode", "boiler pump mode", "Kesselpumpen Modus", "Ketelpomp modus", "", "tryb pracy pompy kotła", "pumpemodus", "", "pompa modu", "modalità pompa caldaia", "režim kotlového čerpadla") // TODO translate +MAKE_TRANSLATION(headertemp, "headertemp", "low loss header", "Hydr. Weiche", "open verdeler", "", "sprzęgło hydrauliczne", "", "bouteille de déc. hydr.", "isı bloğu gidiş suyu sıc.", "comp. idr.", "nízkostratová hlavica") // TODO translate +MAKE_TRANSLATION(heatblock, "heatblock", "heating block", "Wärmezelle", "Aanvoertemp. warmtecel", "", "blok grzewczy", "", "départ corps de chauffe", "Hid.denge kabı sıcaklığı", "mandata scamb. pr.", "vykurovací blok") // TODO translate // heatpump/compress specific -MAKE_TRANSLATION(upTimeTotal, "uptimetotal", "heatpump total uptime", "Wärmpepumpe Gesamtbetriebszeit", "", "", "", "", "", "", "") // TODO translate -MAKE_TRANSLATION(upTimeControl, "uptimecontrol", "total operating time heat", "Betriebszeit Heizen gesamt", "Totale bedrijfstijd", "Total tid uppvärmning", "łączny czas generowania ciepła", "total driftstid", "durée totale de fonctionnement chauffage", "ısınma toplam işletme süresi", "Tempo di funzionamento totale riscaldamento") -MAKE_TRANSLATION(upTimeCompHeating, "uptimecompheating", "operating time compressor heating", "Betriebszeit Kompressor heizen", "Bedrijfstijd compressor verwarmingsbedrijf", "Total tid kompressor uppvärmning", "łączny czas ogrzewania (sprężarka)", "totaltid kompressor", "durée de fonctionnement compresseur chauffage", "ısı pompası ısınma işletme süresi", "tempo di funzionamento del compressore riscaldamento") -MAKE_TRANSLATION(upTimeCompCooling, "uptimecompcooling", "operating time compressor cooling", "Betriebszeit Kompressor kühlen", "Bedrijfstijd compressor koelbedrijf", "Total tid kompressor kyla", "łączny czas chłodzenia (sprężarka)", "Total tid kompressor kjøling", "durée de fonctionnement compresseur refroidissement", "ısı pompası soğuma işletme süresi", "tempo di funzionamento del compressore raffreddamento") -MAKE_TRANSLATION(upTimeCompWw, "uptimecompww", "operating time compressor", "Betriebszeit Kompressor", "Bedrijfstijd compressor", "Total tid kompressor", "łączny czas grzania c.w.u. (sprężarka)", "Total tid kompressor", "durée de fonctionnement compresseur", "ısı pompası sıcak kullanım suyu işletme süresi", "tempo di funzionamento del compressore") -MAKE_TRANSLATION(upTimeCompPool, "uptimecomppool", "operating time compressor pool", "Betriebszeit Kompressor Pool", "Bedrijfstijd compressor voor zwembadbedrijf", "Total tid kompressor pool", "łączny czas podgrzewania basenu (sprężarka)", "Total tid kompressor basseng", "durée de fonctionnement compresseur piscine", "ısı pompası havuz işletme süresi", "tempo di funzionamento del compressore piscina") -MAKE_TRANSLATION(totalCompStarts, "totalcompstarts", "total compressor control starts", "Kompressor Starts gesamt", "Totaal compressorstarts", "Kompressorstarter Totalt", "liczba załączeń sprężarki", "kompressorstarter totalt", "nombre démarrages total contrôle compresseur", "ısı pompası kontrolü toplam başlatma", "avvii totali del compressore") -MAKE_TRANSLATION(heatingStarts, "heatingstarts", "heating control starts", "Heizen Starts", "Starts verwarmingsbedrijf", "Kompressorstarter Uppvärmning", "liczba załączeń ogrzewania", "kompressorstarter oppvarming", "démarrages contrôle chauffage", "ısıtma kontrolü toplam başlatma", "avvii riscaldamento") -MAKE_TRANSLATION(coolingStarts, "coolingstarts", "cooling control starts", "Kühlen Starts", "Starts koelbedrijf", "Kompressorstarter Kyla", "liczba załączeń chłodzenia", "kompressorstarter kjøling", "démarrages contrôle refroidissement", "soğutma kontrolü toplam başlatma", "avvii raffreddamento") -MAKE_TRANSLATION(poolStarts, "poolstarts", "pool control starts", "Pool Starts", "Starts zwembadbedrijf", "Kompressorstarter Pool", "liczba załączeń podgrzewania basenu", "kompressorstarter basseng", "démarrages contrôle piscine", "havuz kontrolü toplam başlatma", "avvio controllato piscina") -MAKE_TRANSLATION(nrgConsTotal, "nrgconstotal", "total energy consumption", "Energieverbrauch gesamt", "Energieverbrauch gesamt", "Energiförbrukning totalt", "energia pobrana (sumarycznie)", "energiforbruk totalt", "consommation totale énergie", "toplam enerji tüketimi", "totale energia consumata") -MAKE_TRANSLATION(nrgConsCompTotal, "nrgconscomptotal", "total energy consumption compressor", "Energieverbrauch Kompressor gesamt", "Energieverbruik compressor totaal", "Energiförbrukning kompressor", "energia pobrana przez sprężarkę", "energiforbruk kompressor", "consommation totale énergie compresseur", "ısı pompası toplam enerji tüketimi", "totale energia consumata compressore") -MAKE_TRANSLATION(nrgConsCompHeating, "nrgconscompheating", "energy consumption compressor heating", "Energieverbrauch Kompressor heizen", "Energieverbruik compressor verwarmingsbedrijf", "Energiförbrukning uppvärmning", "energia pobrana przez sprężarkę na ogrzewanie", "energiforbruk oppvarming", "consommation énergie compresseur chauffage", "ısı pompası ısıtma toplam enerji tüketimi", "consumo energia compressore riscaldamento") -MAKE_TRANSLATION(nrgConsCompWw, "nrgconscompww", "energy consumption compressor", "Energieverbrauch Kompressor", "Energieverbruik compressor", "Energiförbrukning", "energia pobrana przez sprężarkę na c.w.u.", "energiforbruk", "consommation énergie compresseur", "ısı pompası sıcak kullanım suyu toplam enerji tüketimi", "consumo energia compressore") -MAKE_TRANSLATION(nrgConsCompCooling, "nrgconscompcooling", "energy consumption compressor cooling", "Energieverbrauch Kompressor kühlen", "Energieverbruik compressor koelbedrijf", "Energiförbrukning kyla", "energia pobrana przez sprężarkę na chłodzenie", "energiforbruk kjøling", "consommation énergie compresseur refroidissement", "ısı pompası soğutma toplam enerji tüketimi", "consumo energia compressore raffreddamento") -MAKE_TRANSLATION(nrgConsCompPool, "nrgconscomppool", "energy consumption compressor pool", "Energieverbrauch Kompressor Pool", "Energiebedrijf compressor zwembadbedrijf", "Energiförbrukning pool", "energia pobrana przez sprężarkę na podgrzewanie basenu", "energiforbruk basseng", "consommation énergie compresseur piscine", "ısı pompası havuz toplam enerji tüketimi", "consumo energia compressore piscina") -MAKE_TRANSLATION(nrgSuppTotal, "nrgsupptotal", "total energy supplied", "gesamte Energieabgabe", "Totaal opgewekte energie", "Genererad energi", "energia oddana (sumarycznie)", "tilført energi", "énergie totale fournie", "sağlanan toplam enerji", "totale energia fornita") -MAKE_TRANSLATION(nrgSuppHeating, "nrgsuppheating", "total energy supplied heating", "gesamte Energieabgabe heizen", "Opgewekte energie verwarmingsbedrijf", "Genererad energi Uppvärmning", "energia oddana na ogrzewanie", "tilført energi oppvarming", "énergie totale fournie chauffage", "ısıtma sağlanan toplam enerji", "energia totale fornita - riscaldamento") -MAKE_TRANSLATION(nrgSuppWw, "nrgsuppww", "total energy warm supplied", "gesamte Energieabgabe", "Opgewekte energie", "Genererad energi", "energia oddana na c.w.u.", "tilført energi", "énergie chaude totale fournie", "sıcak kullanım suyu sağlanan toplam enerji", "totale energia calorica fornita") -MAKE_TRANSLATION(nrgSuppCooling, "nrgsuppcooling", "total energy supplied cooling", "gesamte Energieabgabe kühlen", "Opgewekte energie koelbedrijf", "Genererad energi Kyla", "energia oddana na chłodzenie", "Tillført energi kjøling", "énergie totale fournie refroidissement", "soğutma sağlanan toplam enerji", "energia totale fornita - raffreddamento") -MAKE_TRANSLATION(nrgSuppPool, "nrgsupppool", "total energy supplied pool", "gesamte Energieabgabe Pool", "Opgewekte energie zwembadbedrijf", "Genererad energi Pool", "energia oddana na podgrzewanie basenu", "tilført energi basseng", "énergie totale fournie piscine", "havuz sağlanan toplam enerji", "totale di energia fornita- piscina") -MAKE_TRANSLATION(auxElecHeatNrgConsTotal, "auxelecheatnrgconstotal", "total aux elec. heater energy consumption", "Energieverbrauch el. Zusatzheizung", "Totaal energieverbruik electrisch verwarmingselement", "Energiförbrukning Eltillkott", "energia pobrana przez grzałki", "energiforbruk varmekolbe", "consommation totale énergie electrique auxiliaire chauffage", "ilave elektrikli ısıtıcı toplam enerji tüketimi", "consumo energetico riscaldamento elettrico supplementare") -MAKE_TRANSLATION(auxElecHeatNrgConsHeating, "auxelecheatnrgconsheating", "aux elec. heater energy consumption heating", "Energieverbrauch el. Zusatzheizung Heizen", "Energieverbruik electrisch verwarmingselement voor verwarmingsbedrijf", "Energiförbrukning Eltillskott Uppvärmning", "energia pobrana przez grzałki na ogrzewanie", "energiforbruk varmekolbe oppvarming", "consommation énergie electrique auxiliaire chauffage", "ilave elektrikli ısıtıcı ısınma toplam enerji tüketimi", "consumo di energia riscaldamento elettrico ausiliario") -MAKE_TRANSLATION(auxElecHeatNrgConsWW, "auxelecheatnrgconsww", "aux elec. heater energy consumption", "Energieverbrauch el. Zusatzheizung", "Energieverbruik electrisch verwarmingselement voor", "Energiförbrukning Eltillskott", "energia pobrana przez grzałki na c.w.u.", "energiförbruk varmekolbe", "consommation énergie electrique auxiliaire chauffage", "ilave elektrikli ısıtıcı sıcak kullanım suyu toplam enerji tüketimi", "consumo di energia riscaldamento elettrico ausiliario") -MAKE_TRANSLATION(auxElecHeatNrgConsPool, "auxelecheatnrgconspool", "aux elec. heater energy consumption pool", "Energieverbrauch el. Zusatzheizung Pool", "Energieverbruik electrisch verwarmingselement voor zwembadbedrijf", "Energiförbrukning Eltillskott Pool", "energia pobrana przez grzałki na podgrzewanie basenu", "energiforbruk el. tilleggsvarme basseng", "consommation énergie electrique auxiliaire chauffage piscine", "ilave elektrikli ısıtıcı havuz toplam enerji tüketimi", "consumo di energia riscaldamento elettrico ausiliario piscina") +MAKE_TRANSLATION(upTimeTotal, "uptimetotal", "heatpump total uptime", "Wärmpepumpe Gesamtbetriebszeit", "", "", "łączny czas pracy pompy ciepła", "", "", "", "", "celková doba prevádzky tepelného čerpadla") // TODO translate +MAKE_TRANSLATION(upTimeControl, "uptimecontrol", "total operating time heat", "Betriebszeit Heizen gesamt", "Totale bedrijfstijd", "Total tid uppvärmning", "łączny czas generowania ciepła", "total driftstid", "durée totale de fonctionnement chauffage", "ısınma toplam işletme süresi", "Tempo di funzionamento totale riscaldamento", "celkový prevádzkový čas tepla") +MAKE_TRANSLATION(upTimeCompHeating, "uptimecompheating", "operating time compressor heating", "Betriebszeit Kompressor heizen", "Bedrijfstijd compressor verwarmingsbedrijf", "Total tid kompressor uppvärmning", "łączny czas ogrzewania (sprężarka)", "totaltid kompressor", "durée de fonctionnement compresseur chauffage", "ısı pompası ısınma işletme süresi", "tempo di funzionamento del compressore riscaldamento", "prevádzková doba vykurovania kompresora") +MAKE_TRANSLATION(upTimeCompCooling, "uptimecompcooling", "operating time compressor cooling", "Betriebszeit Kompressor kühlen", "Bedrijfstijd compressor koelbedrijf", "Total tid kompressor kyla", "łączny czas chłodzenia (sprężarka)", "Total tid kompressor kjøling", "durée de fonctionnement compresseur refroidissement", "ısı pompası soğuma işletme süresi", "tempo di funzionamento del compressore raffreddamento", "doba prevádzky chladenia kompresora") +MAKE_TRANSLATION(upTimeCompWw, "uptimecompww", "operating time compressor", "Betriebszeit Kompressor", "Bedrijfstijd compressor", "Total tid kompressor", "łączny czas grzania c.w.u. (sprężarka)", "Total tid kompressor", "durée de fonctionnement compresseur", "ısı pompası sıcak kullanım suyu işletme süresi", "tempo di funzionamento del compressore", "prevádzková doba kompresora") +MAKE_TRANSLATION(upTimeCompPool, "uptimecomppool", "operating time compressor pool", "Betriebszeit Kompressor Pool", "Bedrijfstijd compressor voor zwembadbedrijf", "Total tid kompressor pool", "łączny czas podgrzewania basenu (sprężarka)", "Total tid kompressor basseng", "durée de fonctionnement compresseur piscine", "ısı pompası havuz işletme süresi", "tempo di funzionamento del compressore piscina", "prevádzková doba kompresorového bazéna") +MAKE_TRANSLATION(totalCompStarts, "totalcompstarts", "total compressor control starts", "Kompressor Starts gesamt", "Totaal compressorstarts", "Kompressorstarter Totalt", "liczba załączeń sprężarki", "kompressorstarter totalt", "nombre démarrages total contrôle compresseur", "ısı pompası kontrolü toplam başlatma", "avvii totali del compressore", "spustí sa celkové riadenie kompresora") +MAKE_TRANSLATION(heatingStarts, "heatingstarts", "heating control starts", "Heizen Starts", "Starts verwarmingsbedrijf", "Kompressorstarter Uppvärmning", "liczba załączeń ogrzewania", "kompressorstarter oppvarming", "démarrages contrôle chauffage", "ısıtma kontrolü toplam başlatma", "avvii riscaldamento", "ovládanie vykurovania sa spustí") +MAKE_TRANSLATION(coolingStarts, "coolingstarts", "cooling control starts", "Kühlen Starts", "Starts koelbedrijf", "Kompressorstarter Kyla", "liczba załączeń chłodzenia", "kompressorstarter kjøling", "démarrages contrôle refroidissement", "soğutma kontrolü toplam başlatma", "avvii raffreddamento", "ovládanie chladenia sa spustí") +MAKE_TRANSLATION(poolStarts, "poolstarts", "pool control starts", "Pool Starts", "Starts zwembadbedrijf", "Kompressorstarter Pool", "liczba załączeń podgrzewania basenu", "kompressorstarter basseng", "démarrages contrôle piscine", "havuz kontrolü toplam başlatma", "avvio controllato piscina", "riadenie bazéna sa spustí") +MAKE_TRANSLATION(nrgConsTotal, "nrgconstotal", "total energy consumption", "Energieverbrauch gesamt", "Energieverbrauch gesamt", "Energiförbrukning totalt", "energia pobrana (sumarycznie)", "energiforbruk totalt", "consommation totale énergie", "toplam enerji tüketimi", "totale energia consumata", "celková spotreba energie") +MAKE_TRANSLATION(nrgConsCompTotal, "nrgconscomptotal", "total energy consumption compressor", "Energieverbrauch Kompressor gesamt", "Energieverbruik compressor totaal", "Energiförbrukning kompressor", "energia pobrana przez sprężarkę", "energiforbruk kompressor", "consommation totale énergie compresseur", "ısı pompası toplam enerji tüketimi", "totale energia consumata compressore", "kompresor s celkovou spotrebou energie") +MAKE_TRANSLATION(nrgConsCompHeating, "nrgconscompheating", "energy consumption compressor heating", "Energieverbrauch Kompressor heizen", "Energieverbruik compressor verwarmingsbedrijf", "Energiförbrukning uppvärmning", "energia pobrana przez sprężarkę na ogrzewanie", "energiforbruk oppvarming", "consommation énergie compresseur chauffage", "ısı pompası ısıtma toplam enerji tüketimi", "consumo energia compressore riscaldamento", "spotreba energie vykurovanie kompresorom") +MAKE_TRANSLATION(nrgConsCompWw, "nrgconscompww", "energy consumption compressor", "Energieverbrauch Kompressor", "Energieverbruik compressor", "Energiförbrukning", "energia pobrana przez sprężarkę na c.w.u.", "energiforbruk", "consommation énergie compresseur", "ısı pompası sıcak kullanım suyu toplam enerji tüketimi", "consumo energia compressore", "kompresor spotreby energie") +MAKE_TRANSLATION(nrgConsCompCooling, "nrgconscompcooling", "energy consumption compressor cooling", "Energieverbrauch Kompressor kühlen", "Energieverbruik compressor koelbedrijf", "Energiförbrukning kyla", "energia pobrana przez sprężarkę na chłodzenie", "energiforbruk kjøling", "consommation énergie compresseur refroidissement", "ısı pompası soğutma toplam enerji tüketimi", "consumo energia compressore raffreddamento", "spotreba energie kompresorové chladenie") +MAKE_TRANSLATION(nrgConsCompPool, "nrgconscomppool", "energy consumption compressor pool", "Energieverbrauch Kompressor Pool", "Energiebedrijf compressor zwembadbedrijf", "Energiförbrukning pool", "energia pobrana przez sprężarkę na podgrzewanie basenu", "energiforbruk basseng", "consommation énergie compresseur piscine", "ısı pompası havuz toplam enerji tüketimi", "consumo energia compressore piscina", "spotreba energie kompresorový bazén") +MAKE_TRANSLATION(nrgSuppTotal, "nrgsupptotal", "total energy supplied", "gesamte Energieabgabe", "Totaal opgewekte energie", "Genererad energi", "energia oddana (sumarycznie)", "tilført energi", "énergie totale fournie", "sağlanan toplam enerji", "totale energia fornita", "celková dodaná energia") +MAKE_TRANSLATION(nrgSuppHeating, "nrgsuppheating", "total energy supplied heating", "gesamte Energieabgabe heizen", "Opgewekte energie verwarmingsbedrijf", "Genererad energi Uppvärmning", "energia oddana na ogrzewanie", "tilført energi oppvarming", "énergie totale fournie chauffage", "ısıtma sağlanan toplam enerji", "energia totale fornita - riscaldamento", "celková dodaná energia na vykurovanie") +MAKE_TRANSLATION(nrgSuppWw, "nrgsuppww", "total energy warm supplied", "gesamte Energieabgabe", "Opgewekte energie", "Genererad energi", "energia oddana na c.w.u.", "tilført energi", "énergie chaude totale fournie", "sıcak kullanım suyu sağlanan toplam enerji", "totale energia calorica fornita", "celková dodaná teplá energia") +MAKE_TRANSLATION(nrgSuppCooling, "nrgsuppcooling", "total energy supplied cooling", "gesamte Energieabgabe kühlen", "Opgewekte energie koelbedrijf", "Genererad energi Kyla", "energia oddana na chłodzenie", "Tillført energi kjøling", "énergie totale fournie refroidissement", "soğutma sağlanan toplam enerji", "energia totale fornita - raffreddamento", "chladenie s celkovou dodanou energiou") +MAKE_TRANSLATION(nrgSuppPool, "nrgsupppool", "total energy supplied pool", "gesamte Energieabgabe Pool", "Opgewekte energie zwembadbedrijf", "Genererad energi Pool", "energia oddana na podgrzewanie basenu", "tilført energi basseng", "énergie totale fournie piscine", "havuz sağlanan toplam enerji", "totale di energia fornita- piscina", "celkový bazén dodanej energie") +MAKE_TRANSLATION(auxElecHeatNrgConsTotal, "auxelecheatnrgconstotal", "total aux elec. heater energy consumption", "Energieverbrauch el. Zusatzheizung", "Totaal energieverbruik electrisch verwarmingselement", "Energiförbrukning Eltillkott", "energia pobrana przez grzałki", "energiforbruk varmekolbe", "consommation totale énergie electrique auxiliaire chauffage", "ilave elektrikli ısıtıcı toplam enerji tüketimi", "consumo energetico riscaldamento elettrico supplementare", "celková spotreba energie prídavného elektrického ohrievača") +MAKE_TRANSLATION(auxElecHeatNrgConsHeating, "auxelecheatnrgconsheating", "aux elec. heater energy consumption heating", "Energieverbrauch el. Zusatzheizung Heizen", "Energieverbruik electrisch verwarmingselement voor verwarmingsbedrijf", "Energiförbrukning Eltillskott Uppvärmning", "energia pobrana przez grzałki na ogrzewanie", "energiforbruk varmekolbe oppvarming", "consommation énergie electrique auxiliaire chauffage", "ilave elektrikli ısıtıcı ısınma toplam enerji tüketimi", "consumo di energia riscaldamento elettrico ausiliario", "pomocný elektrický ohrievač spotreba energie vykurovanie") +MAKE_TRANSLATION(auxElecHeatNrgConsWW, "auxelecheatnrgconsww", "aux elec. heater energy consumption", "Energieverbrauch el. Zusatzheizung", "Energieverbruik electrisch verwarmingselement voor", "Energiförbrukning Eltillskott", "energia pobrana przez grzałki na c.w.u.", "energiförbruk varmekolbe", "consommation énergie electrique auxiliaire chauffage", "ilave elektrikli ısıtıcı sıcak kullanım suyu toplam enerji tüketimi", "consumo di energia riscaldamento elettrico ausiliario", "spotreba energie pomocného elektrického ohrievača") +MAKE_TRANSLATION(auxElecHeatNrgConsPool, "auxelecheatnrgconspool", "aux elec. heater energy consumption pool", "Energieverbrauch el. Zusatzheizung Pool", "Energieverbruik electrisch verwarmingselement voor zwembadbedrijf", "Energiförbrukning Eltillskott Pool", "energia pobrana przez grzałki na podgrzewanie basenu", "energiforbruk el. tilleggsvarme basseng", "consommation énergie electrique auxiliaire chauffage piscine", "ilave elektrikli ısıtıcı havuz toplam enerji tüketimi", "consumo di energia riscaldamento elettrico ausiliario piscina", "bazén spotreby energie pomocného elektrického ohrievača") -MAKE_TRANSLATION(hpCompOn, "hpcompon", "hp compressor", "WP Kompressor", "WP compressor", "VP Kompressor", "sprężarka pompy ciepła", "vp kompressor", "compresseur pompe à chaleur", "hp ısı pompası", "compressore pompa calore") -MAKE_TRANSLATION(coolingOn, "coolingon", "cooling", "Kühlen", "koelbedrijf", "Kyla", "chłodzenie", "kjøling", "refroidissement", "hp sıcak kullanım suyu", "") // TODO translate -// MAKE_TRANSLATION(hpHeatingOn, "hpheatingon", "hp heating", "WP Heizen", "WP verwarmingsbedrijf", "VP Uppvärmning", "pompa ciepła, ogrzewanie", "vp oppvarmning", "", "hp ısınıyor", "riscaldamento pompa calore") // TODO translate -// MAKE_TRANSLATION(hpCoolingOn, "hpcoolingon", "hp cooling", "WP Kühlen", "WP koelbedrijf", "VP Kyla", "pompa ciepła, chłodzenie", "vp kjøling", "", "hp soğuyor", "raffreddamento pompa calore") // TODO translate -// MAKE_TRANSLATION(hpWwOn, "hpwwon", "hp", "WP", "WP", "VP", "pompa ciepła", "vp", "pompe à chaleur", "hp", "pompa calore") -// MAKE_TRANSLATION(hpPoolOn, "hppoolon", "hp pool", "WP Pool", "WP zwembadbedrijf", "VP Pool", "pompa ciepła, podgrzewanie basenu", "vp basseng", "", "tuzlu su pompası hızı", "pompa calore piscina") -MAKE_TRANSLATION(hpBrinePumpSpd, "hpbrinepumpspd", "brine pump speed", "Solepumpen-Geschw.", "Snelheid pekelpomp", "Hastighet Brine-pump", "wysterowanie pompy glikolu", "hastighet brine-pumpe", "vitesse pompe à saumure", "ısı pompası hızı", "velocità pompa sbrinamento") -MAKE_TRANSLATION(hpCompSpd, "hpcompspd", "compressor speed", "Kompressor-Geschw.", "Snelheid compressor", "Kompressorhastighet", "wysterowanie sprężarki", "kompressorhastighet", "vitesse du compresseur", "sirkülasyon pompası hızı", "velocità compressore") -MAKE_TRANSLATION(hpCircSpd, "hpcircspd", "circulation pump speed", "Zirkulationspumpen-Geschw.", "Snelheid circulatiepomp", "Hastighet Cirkulationspump", "wysterowanie pompy obiegu grzewczego", "hastighet sirkulationspumpe", "vitesse pompe à circulation", "evaporatör tuzlu su giişi", "velocità pompa circolazione") -MAKE_TRANSLATION(hpBrineIn, "hpbrinein", "brine in/evaporator", "Sole in/Verdampfer", "pekel in/verdamper", "Brine in (förangare)", "temperatura glikolu na wejściu kolektora (TB0)", "brine in/fordamper", "entrée saumure/évaporateur", "kondenser tuzlu su çıkışı", "salamoia nell evaporatore") -MAKE_TRANSLATION(hpBrineOut, "hpbrineout", "brine out/condenser", "Sole aus/Kondensator", "pekel uit/condensor", "Brine ut (kondensor)", "temperatura glikolu na wyjściu kolektora (TB1)", "Brine ut/kondensor", "sortie saumure/condenseur", "anahtar valfi", "salamoia nell uscita evaporatore") -MAKE_TRANSLATION(hpSwitchValve, "hpswitchvalve", "switch valve", "Schaltventil", "schakelklep", "Växelventil", "zawór przełączający", "skifteventil", "valve de commutation", "ısı pompası aktivitesi", "valvola commutazione pompa di calore") -MAKE_TRANSLATION(hpActivity, "hpactivity", "compressor activity", "Kompressor-Betriebsmodus", "Compressoractiviteit", "Kompressoraktivitet", "pompa ciepła, aktywność sprężarki", "kompressoraktivitet", "", "hp ısı pompası", "attività compressore") +MAKE_TRANSLATION(hpCompOn, "hpcompon", "hp compressor", "WP Kompressor", "WP compressor", "VP Kompressor", "sprężarka pompy ciepła", "vp kompressor", "compresseur pompe à chaleur", "hp ısı pompası", "compressore pompa calore", "hp kompresor") +MAKE_TRANSLATION(coolingOn, "coolingon", "cooling", "Kühlen", "koelbedrijf", "Kyla", "chłodzenie włączone", "kjøling", "refroidissement", "soğutma", "", "chladenie") // TODO translate +// MAKE_TRANSLATION(hpHeatingOn, "hpheatingon", "hp heating", "WP Heizen", "WP verwarmingsbedrijf", "VP Uppvärmning", "pompa ciepła, ogrzewanie", "vp oppvarmning", "", "hp ısınıyor", "riscaldamento pompa calore", "vykurovanie hp") // TODO translate +// MAKE_TRANSLATION(hpCoolingOn, "hpcoolingon", "hp cooling", "WP Kühlen", "WP koelbedrijf", "VP Kyla", "pompa ciepła, chłodzenie", "vp kjøling", "", "hp soğuyor", "raffreddamento pompa calore", "chladenie hp") // TODO translate +// MAKE_TRANSLATION(hpWwOn, "hpwwon", "hp", "WP", "WP", "VP", "pompa ciepła", "vp", "pompe à chaleur", "hp", "pompa calore", "hp") +// MAKE_TRANSLATION(hpPoolOn, "hppoolon", "hp pool", "WP Pool", "WP zwembadbedrijf", "VP Pool", "pompa ciepła, podgrzewanie basenu", "vp basseng", "", "tuzlu su pompası hızı", "pompa calore piscina", "hp bazén") // TODO translate +MAKE_TRANSLATION(hpBrinePumpSpd, "hpbrinepumpspd", "brine pump speed", "Solepumpen-Geschw.", "Snelheid pekelpomp", "Hastighet Brine-pump", "wysterowanie pompy glikolu", "hastighet brine-pumpe", "vitesse pompe à saumure", "ısı pompası hızı", "velocità pompa sbrinamento", "rýchlosť čerpadla soľanky") +MAKE_TRANSLATION(hpCompSpd, "hpcompspd", "compressor speed", "Kompressor-Geschw.", "Snelheid compressor", "Kompressorhastighet", "wysterowanie sprężarki", "kompressorhastighet", "vitesse du compresseur", "sirkülasyon pompası hızı", "velocità compressore", "rýchlosť kompresora") +MAKE_TRANSLATION(hpCircSpd, "hpcircspd", "circulation pump speed", "Zirkulationspumpen-Geschw.", "Snelheid circulatiepomp", "Hastighet Cirkulationspump", "wysterowanie pompy obiegu grzewczego", "hastighet sirkulationspumpe", "vitesse pompe à circulation", "evaporatör tuzlu su giişi", "velocità pompa circolazione", "otáčky obehového čerpadla") +MAKE_TRANSLATION(hpBrineIn, "hpbrinein", "brine in/evaporator", "Sole in/Verdampfer", "pekel in/verdamper", "Brine in (förangare)", "temperatura glikolu na wejściu kolektora (TB0)", "brine in/fordamper", "entrée saumure/évaporateur", "kondenser tuzlu su çıkışı", "salamoia nell evaporatore", "soľanka v/výparník") +MAKE_TRANSLATION(hpBrineOut, "hpbrineout", "brine out/condenser", "Sole aus/Kondensator", "pekel uit/condensor", "Brine ut (kondensor)", "temperatura glikolu na wyjściu kolektora (TB1)", "Brine ut/kondensor", "sortie saumure/condenseur", "anahtar valfi", "salamoia nell uscita evaporatore", "výstup soľanky/kondenzátor") +MAKE_TRANSLATION(hpSwitchValve, "hpswitchvalve", "switch valve", "Schaltventil", "schakelklep", "Växelventil", "zawór przełączający", "skifteventil", "valve de commutation", "ısı pompası aktivitesi", "valvola commutazione pompa di calore", "prepínací ventil") +MAKE_TRANSLATION(hpActivity, "hpactivity", "compressor activity", "Kompressor-Betriebsmodus", "Compressoractiviteit", "Kompressoraktivitet", "pompa ciepła, aktywność sprężarki", "kompressoraktivitet", "", "hp ısı pompası", "attività compressore", "činnosť kompresora") // TODO translate -MAKE_TRANSLATION(hpMaxPower, "hpmaxpower", "compressor max power", "max. Kompressorleistung", "", "", "", "", "", "", "") // TODO translate -MAKE_TRANSLATION(hpPower, "hppower", "compressor power output", "Kompressorleistung", "Compressorvermogen", "Kompressoreffekt", "moc wyjściowa sprężarki", "kompressoreffekt", "puissance de sortie compresseur", "ısı pompası güç çıkışı", "potenza uscita compressore") -MAKE_TRANSLATION(hpTc0, "hptc0", "heat carrier return (TC0)", "Kältemittel Rücklauf (TC0)", "Koudemiddel retour (TC0)", "Värmebärare Retur (TC0)", "temperatura nośnika ciepła na powrocie (TC0)", "kjølemiddel retur (TC0)", "retour caloporteur (TC0)", "sıcak su dönüşü (TC0)", "ritorno del refrigerante (TC0)") -MAKE_TRANSLATION(hpTc1, "hptc1", "heat carrier forward (TC1)", "Kältemittel Vorlauf (TC1)", "Koudemiddel aanvoer (TC1)", "Värmebärare Framledning (TC1)", "temperatura nośnika ciepła pierwotna (TC1)", "kjølemiddel tur (TC1)", "avance caloporteur (TC1)", "sıcak su çıkışı (TC1)", "flusso di refrigerante (TC1)") -MAKE_TRANSLATION(hpTc3, "hptc3", "condenser temperature (TC3)", "Verflüssigertemperatur (TC3)", "Condensortemperatuur (TC3)", "Kondensortemperatur (TC3)", "temperatura skraplacza/na wyjściu sprężarki (TC3)", "kondensortemperatur (TC3)", "température condensateur (TC3)", "kondenser sıcaklığı (TC3)", "temperatura condensatore (TC3)") -MAKE_TRANSLATION(hpTr1, "hptr1", "compressor temperature (TR1)", "Kompessortemperatur (TR1)", "Compressor temperatuur (TR1)", "Kompressor temp (TR1)", "temperatura sprężarki (TR1)", "kompressor temperatur (TR1)", "température compresseur (TR1)", "ısı pompası sıcaklığı (TR1)", "temperatura compressore (TR1)") -MAKE_TRANSLATION(hpTr3, "hptr3", "refrigerant temperature liquid side (condenser output) (TR3)", "Kältemittel (flüssig) (TR3)", "Temperatuur koudemiddel vloeibare zijde (TR3)", "Köldmedium temperatur (kondensorutlopp) (TR3)", "temperatura skraplacza ogrzew. (TR3)", "kjølemiddeltemperatur på væskesiden (TR3)", "température réfrigérant côté liquide (sortie condensateur) (TR3)", "ısı pompası çıkışı (TR3)", "temperatura refrigerante lato liquido (uscita condensatore) (TR3)") -MAKE_TRANSLATION(hpTr4, "hptr4", "evaporator inlet temperature (TR4)", "Verdampfer Eingang (TR4)", "Verdamper ingangstemperatuur (TR4)", "Förångare inloppstemp (TR4)", "temperatura na wejściu parownika (TR4)", "innløpstemperatur for fordamperen (TR4)", "température entrée évaporateur (TR4)", "evaporatör giriş sıcaklığı (TR4)", "temperatura di ingresso dell'evaporatore (TR4)") -MAKE_TRANSLATION(hpTr5, "hptr5", "compressor inlet temperature (TR5)", "Kompessoreingang (TR5)", "Compressor ingangstemperatuur (TR5)", "Kompressor inloppstemp (TR5)", "temperatura na wejściu sprężarki (TR5)", "kompressor innløpstemp (TR5)", "température entrée compresseur (TR5)", "ısı pompası giriş sıcaklığı (TR5)", "temperatura di ingresso del compressore (TR5)") -MAKE_TRANSLATION(hpTr6, "hptr6", "compressor outlet temperature (TR6)", "Kompressorausgang (TR6)", "Compressor uitgangstemperatuur (TR6)", "Kompressor utloppstemp (TR6)", "temperatura na wyjściu sprężarki (TR6)", "kompressor utløpstemp (TR6)", "température sortie compresseur (TR6)", "ısı pompası çıkış sıcaklığı (TR6)", "temperatura di uscita del compressore (TR6)") -MAKE_TRANSLATION(hpTr7, "hptr7", "refrigerant temperature gas side (condenser input) (TR7)", "Kältemittel (gasförmig) (TR7)", "Temperatuur koudemiddel gasvormig (TR7)", "Köldmedium temperatur gassida (kondensorinlopp) (TR7)", "temperatura czynnika chłodniczego po stronie gazu (wejście skraplacza) (TR7)", "kjølemedium temperatur gassida (kondensatorinløp) (TR7)", "température réfrigérant côté gaz (sortie condensateur) (TR7)", "kondenser giriş sıcaklığı (TR7)", "temperatura refrigerante lato gas (ingresso condensatore) (TR7)") -MAKE_TRANSLATION(hpTl2, "hptl2", "air inlet temperature (TL2)", "Außenluft-Einlasstemperatur (TL2)", "Temperatuur luchtinlaat (TL2)", "Luftintagstemperatur (TL2)", "temperatura wlotu powietrza (TL2)", "luftinntakstemperatur (TL2)", "température entrée air (TL2)", "hava giriş sıcaklığı (TL2)", "temperatura ingresso aria (TL2)") -MAKE_TRANSLATION(hpPl1, "hppl1", "low pressure side temperature (PL1)", "Niederdruckfühler (PL1)", "Temperatuur lage drukzijde (PL1)", "Temperatur Lågtryckssidan (PL1)", "temperatura po stronie niskiego ciśnienia (PL1)", "temperatur lavtrykksiden (PL1)", "température côté basse pression (PL1)", "düşük basınç tarafı sıcaklığı (PL1)", "temperatura lato bassa pressione (PL1)") -MAKE_TRANSLATION(hpPh1, "hpph1", "high pressure side temperature (PH1)", "Hochdruckfühler (PH1)", "Temperatuur hoge drukzijde (PH1)", "Temperatur Högtryckssidan (PH1)", "temperatura po stronie wysokiego ciśnienia (PH1)", "Temperatur Høytrykksiden (PH1)", "température côté bhauteasse pression (PH1)", "yüksek basınç tarafı sıcaklığı (PH1)", "temperatura lato alta pressione (PH1)") -MAKE_TRANSLATION(hpTa4, "hpta4", "drain pan temp (TA4)", "Kondensatorwanne (TA4)", "Temperatuur condensorafvoerbak (TA4)", " (TA4)", "temperatura ociekacza (TA4)", "kondens temperatur (TA4)", " (TA4)", "tahliye sıcaklığı (TA4)", "temperatura condensatore (TA4)") // TODO translate -MAKE_TRANSLATION(hpTw1, "hptw1", "reservoir temp (TW1)", "WW Reservoir (TW1)", "(TW1)", "(TW1)", "temperatura zbiornika (TW1)", "(TW1)", "(TW1)", "(TW1)", "(TW1)") // TODO translate +MAKE_TRANSLATION(hpMaxPower, "hpmaxpower", "compressor max power", "max. Kompressorleistung", "", "", "maksymalna wydajność sprężarki", "", "", "", "", "") // TODO translate +MAKE_TRANSLATION(hpPower, "hppower", "compressor power output", "Kompressorleistung", "Compressorvermogen", "Kompressoreffekt", "moc wyjściowa sprężarki", "kompressoreffekt", "puissance de sortie compresseur", "ısı pompası güç çıkışı", "potenza uscita compressore", "výkon kompresora") +MAKE_TRANSLATION(hpTc0, "hptc0", "heat carrier return (TC0)", "Kältemittel Rücklauf (TC0)", "Koudemiddel retour (TC0)", "Värmebärare Retur (TC0)", "temperatura nośnika ciepła na powrocie (TC0)", "kjølemiddel retur (TC0)", "retour caloporteur (TC0)", "sıcak su dönüşü (TC0)", "ritorno del refrigerante (TC0)", "návrat nosiča tepla (TC0)") +MAKE_TRANSLATION(hpTc1, "hptc1", "heat carrier forward (TC1)", "Kältemittel Vorlauf (TC1)", "Koudemiddel aanvoer (TC1)", "Värmebärare Framledning (TC1)", "temperatura nośnika ciepła pierwotna (TC1)", "kjølemiddel tur (TC1)", "avance caloporteur (TC1)", "sıcak su çıkışı (TC1)", "flusso di refrigerante (TC1)", "nosič tepla vpred (TC1)") +MAKE_TRANSLATION(hpTc3, "hptc3", "condenser temperature (TC3)", "Verflüssigertemperatur (TC3)", "Condensortemperatuur (TC3)", "Kondensortemperatur (TC3)", "temperatura skraplacza/na wyjściu sprężarki (TC3)", "kondensortemperatur (TC3)", "température condensateur (TC3)", "kondenser sıcaklığı (TC3)", "temperatura condensatore (TC3)", "teplota kondenzátora (TC3)") +MAKE_TRANSLATION(hpTr1, "hptr1", "compressor temperature (TR1)", "Kompressortemperatur (TR1)", "Compressor temperatuur (TR1)", "Kompressor temp (TR1)", "temperatura sprężarki (TR1)", "kompressor temperatur (TR1)", "température compresseur (TR1)", "ısı pompası sıcaklığı (TR1)", "temperatura compressore (TR1)", "teplota kompresora (TR1)") +MAKE_TRANSLATION(hpTr3, "hptr3", "refrigerant temperature liquid side (condenser output) (TR3)", "Kältemittel (flüssig) (TR3)", "Temperatuur koudemiddel vloeibare zijde (TR3)", "Köldmedium temperatur (kondensorutlopp) (TR3)", "temperatura skraplacza ogrzew. (TR3)", "kjølemiddeltemperatur på væskesiden (TR3)", "température réfrigérant côté liquide (sortie condensateur) (TR3)", "ısı pompası çıkışı (TR3)", "temperatura refrigerante lato liquido (uscita condensatore) (TR3)", "teplota chladiva na strane kvapaliny (výstup kondenzátora) (TR3)") +MAKE_TRANSLATION(hpTr4, "hptr4", "evaporator inlet temperature (TR4)", "Verdampfer Eingang (TR4)", "Verdamper ingangstemperatuur (TR4)", "Förångare inloppstemp (TR4)", "temperatura na wejściu parownika (TR4)", "innløpstemperatur for fordamperen (TR4)", "température entrée évaporateur (TR4)", "evaporatör giriş sıcaklığı (TR4)", "temperatura di ingresso dell'evaporatore (TR4)", "Vstupná teplota výparníka (TR4)") +MAKE_TRANSLATION(hpTr5, "hptr5", "compressor inlet temperature (TR5)", "Kompressoreingang (TR5)", "Compressor ingangstemperatuur (TR5)", "Kompressor inloppstemp (TR5)", "temperatura na wejściu sprężarki (TR5)", "kompressor innløpstemp (TR5)", "température entrée compresseur (TR5)", "ısı pompası giriş sıcaklığı (TR5)", "temperatura di ingresso del compressore (TR5)", "vstupná teplota kompresora (TR5)") +MAKE_TRANSLATION(hpTr6, "hptr6", "compressor outlet temperature (TR6)", "Kompressorausgang (TR6)", "Compressor uitgangstemperatuur (TR6)", "Kompressor utloppstemp (TR6)", "temperatura na wyjściu sprężarki (TR6)", "kompressor utløpstemp (TR6)", "température sortie compresseur (TR6)", "ısı pompası çıkış sıcaklığı (TR6)", "temperatura di uscita del compressore (TR6)", "výstupná teplota kompresora (TR6)") +MAKE_TRANSLATION(hpTr7, "hptr7", "refrigerant temperature gas side (condenser input) (TR7)", "Kältemittel (gasförmig) (TR7)", "Temperatuur koudemiddel gasvormig (TR7)", "Köldmedium temperatur gassida (kondensorinlopp) (TR7)", "temperatura czynnika chłodniczego po stronie gazu (wejście skraplacza) (TR7)", "kjølemedium temperatur gassida (kondensatorinløp) (TR7)", "température réfrigérant côté gaz (sortie condensateur) (TR7)", "kondenser giriş sıcaklığı (TR7)", "temperatura refrigerante lato gas (ingresso condensatore) (TR7)", "teplota chladiva na strane plynu (vstup kondenzátora) (TR7)") +MAKE_TRANSLATION(hpTl2, "hptl2", "air inlet temperature (TL2)", "Außenluft-Einlasstemperatur (TL2)", "Temperatuur luchtinlaat (TL2)", "Luftintagstemperatur (TL2)", "temperatura wlotu powietrza (TL2)", "luftinntakstemperatur (TL2)", "température entrée air (TL2)", "hava giriş sıcaklığı (TL2)", "temperatura ingresso aria (TL2)", "teplota prívodu vzduchu (TL2)") +MAKE_TRANSLATION(hpPl1, "hppl1", "low pressure side temperature (PL1)", "Niederdruckfühler (PL1)", "Temperatuur lage drukzijde (PL1)", "Temperatur Lågtryckssidan (PL1)", "temperatura po stronie niskiego ciśnienia (PL1)", "temperatur lavtrykksiden (PL1)", "température côté basse pression (PL1)", "düşük basınç tarafı sıcaklığı (PL1)", "temperatura lato bassa pressione (PL1)", "teplota na strane nízkeho tlaku (PL1)") +MAKE_TRANSLATION(hpPh1, "hpph1", "high pressure side temperature (PH1)", "Hochdruckfühler (PH1)", "Temperatuur hoge drukzijde (PH1)", "Temperatur Högtryckssidan (PH1)", "temperatura po stronie wysokiego ciśnienia (PH1)", "Temperatur Høytrykksiden (PH1)", "température côté bhauteasse pression (PH1)", "yüksek basınç tarafı sıcaklığı (PH1)", "temperatura lato alta pressione (PH1)", "teplota na strane vysokého tlaku (PH1)") +MAKE_TRANSLATION(hpTa4, "hpta4", "drain pan temp (TA4)", "Kondensatorwanne (TA4)", "Temperatuur condensorafvoerbak (TA4)", " (TA4)", "temperatura ociekacza (TA4)", "kondens temperatur (TA4)", " (TA4)", "tahliye sıcaklığı (TA4)", "temperatura condensatore (TA4)", "teplota vypúšťacej misky (TA4)") // TODO translate +MAKE_TRANSLATION(hpTw1, "hptw1", "reservoir temp (TW1)", "WW Reservoir (TW1)", "(TW1)", "(TW1)", "temperatura zbiornika (TW1)", "(TW1)", "(TW1)", "(TW1)", "(TW1)", "teplota zásobníka (TW1)") // TODO translate -MAKE_TRANSLATION(hpInput1, "hpin1", "input 1 state", "Eingang 1 Status", "Status input 1", "Status Ingång 1", "stan wejścia 1", "status inggang 1", "état entrée 1", "giriş 1 durumu", "stato ingresso 1") -MAKE_TRANSLATION(hpInput2, "hpin2", "input 2 state", "Eingang 2 Status", "Status input 2", "Status Ingång 2", "stan wejścia 2", "status inggang 2", "état entrée 2", "giriş 2 durumu", "stato ingresso 2") -MAKE_TRANSLATION(hpInput3, "hpin3", "input 3 state", "Eingang 3 Status", "Status input 3", "Status Ingång 3", "stan wejścia 3", "status inggang 3", "état entrée 3", "giriş 3 durumu", "stato ingresso 3") -MAKE_TRANSLATION(hpInput4, "hpin4", "input 4 state", "Eingang 4 Status", "Status input 4", "Status Ingång 4", "stan wejścia 4", "status inggang 4", "état entrée 4", "giriş 4 durumu", "stato ingresso 4") -MAKE_TRANSLATION(hpIn1Opt, "hpin1opt", "input 1 options", "Eingang 1 Einstellung", "Instelling input 1", "Inställningar Ingång 1", "opcje wejścia 1", "innstillinger inngang 1", "options entrée 1", "giriş 1 seçenekleri", "impostazioni ingresso 1") -MAKE_TRANSLATION(hpIn2Opt, "hpin2opt", "input 2 options", "Eingang 2 Einstellung", "Instelling input 2", "Inställningar Ingång 2", "opcje wejścia 2", "innstillinger inngang 2", "options entrée 2", "giriş 2 seçenekleri", "impostazioni ingresso 2") -MAKE_TRANSLATION(hpIn3Opt, "hpin3opt", "input 3 options", "Eingang 3 Einstellung", "Instelling input 3", "Inställningar Ingång 3", "opcje wejścia 3", "innstillinger inngang 3", "options entrée 3", "giriş 3 seçenekleri", "impostazioni ingresso 3") -MAKE_TRANSLATION(hpIn4Opt, "hpin4opt", "input 4 options", "Eingang 4 Einstellung", "Instelling input 4", "Inställningar Ingång 4", "opcje wejścia 4", "innstillinger inngang 4", "options entrée 4", "giriş 4 seçenekleri", "impostazioni ingresso 4") -MAKE_TRANSLATION(maxHeatComp, "maxheatcomp", "heat limit compressor", "Heizgrenze Kompressor", "heat limit compressor", "heat limit compressor", "ograniczenie mocy sprężarki", "max varmegrense kompressor", "limite chaleur compresseur", "ısı pompası ısıtma sınırı", "limite riscaldamento compressore") -MAKE_TRANSLATION(maxHeatHeat, "maxheatheat", "heat limit heating", "Heizgrenze Heizen", "heat limit heating", "heat limit heating", "ograniczenie mocy w trybie ogrzewania", "maks varmegrense oppvarming", "limite chaleur chauffage", "ısınma ısıtma sınırı", "limite calore riscaldamento") -MAKE_TRANSLATION(maxHeatDhw, "maxheatdhw", "heat limit", "Heizgrenze", "heat limit", "heat limit", "ograniczenie mocy w trybie c.w.u.", "varmegrense", "limite chaleur", "sıcak kullanım suyu ısınma sınırı", "limite calore") +MAKE_TRANSLATION(hpInput1, "hpin1", "input 1 state", "Eingang 1 Status", "Status input 1", "Status Ingång 1", "stan wejścia 1", "status inggang 1", "état entrée 1", "giriş 1 durumu", "stato ingresso 1", "stav vstupu 1") +MAKE_TRANSLATION(hpInput2, "hpin2", "input 2 state", "Eingang 2 Status", "Status input 2", "Status Ingång 2", "stan wejścia 2", "status inggang 2", "état entrée 2", "giriş 2 durumu", "stato ingresso 2", "stav vstupu 2") +MAKE_TRANSLATION(hpInput3, "hpin3", "input 3 state", "Eingang 3 Status", "Status input 3", "Status Ingång 3", "stan wejścia 3", "status inggang 3", "état entrée 3", "giriş 3 durumu", "stato ingresso 3", "stav vstupu 3") +MAKE_TRANSLATION(hpInput4, "hpin4", "input 4 state", "Eingang 4 Status", "Status input 4", "Status Ingång 4", "stan wejścia 4", "status inggang 4", "état entrée 4", "giriş 4 durumu", "stato ingresso 4", "stav vstupu 4") +MAKE_TRANSLATION(hpIn1Opt, "hpin1opt", "input 1 options", "Eingang 1 Einstellung", "Instelling input 1", "Inställningar Ingång 1", "opcje wejścia 1", "innstillinger inngang 1", "options entrée 1", "giriş 1 seçenekleri", "impostazioni ingresso 1", "možnosti vstupu 1") +MAKE_TRANSLATION(hpIn2Opt, "hpin2opt", "input 2 options", "Eingang 2 Einstellung", "Instelling input 2", "Inställningar Ingång 2", "opcje wejścia 2", "innstillinger inngang 2", "options entrée 2", "giriş 2 seçenekleri", "impostazioni ingresso 2", "možnosti vstupu 2") +MAKE_TRANSLATION(hpIn3Opt, "hpin3opt", "input 3 options", "Eingang 3 Einstellung", "Instelling input 3", "Inställningar Ingång 3", "opcje wejścia 3", "innstillinger inngang 3", "options entrée 3", "giriş 3 seçenekleri", "impostazioni ingresso 3", "možnosti vstupu 3") +MAKE_TRANSLATION(hpIn4Opt, "hpin4opt", "input 4 options", "Eingang 4 Einstellung", "Instelling input 4", "Inställningar Ingång 4", "opcje wejścia 4", "innstillinger inngang 4", "options entrée 4", "giriş 4 seçenekleri", "impostazioni ingresso 4", "možnosti vstupu 4") +MAKE_TRANSLATION(maxHeatComp, "maxheatcomp", "heat limit compressor", "Heizgrenze Kompressor", "heat limit compressor", "heat limit compressor", "ograniczenie mocy sprężarki", "max varmegrense kompressor", "limite chaleur compresseur", "ısı pompası ısıtma sınırı", "limite riscaldamento compressore", "tepelný limit kompresora") +MAKE_TRANSLATION(maxHeatHeat, "maxheatheat", "heat limit heating", "Heizgrenze Heizen", "heat limit heating", "heat limit heating", "ograniczenie mocy w trybie ogrzewania", "maks varmegrense oppvarming", "limite chaleur chauffage", "ısınma ısıtma sınırı", "limite calore riscaldamento", "vyhrievanie limitu tepla") +MAKE_TRANSLATION(maxHeatDhw, "maxheatdhw", "heat limit", "Heizgrenze", "heat limit", "heat limit", "ograniczenie mocy w trybie c.w.u.", "varmegrense", "limite chaleur", "sıcak kullanım suyu ısınma sınırı", "limite calore", "tepelný limit") -MAKE_TRANSLATION(auxHeaterOff, "auxheateroff", "disable aux heater", "Verbiete Zusatzheizer", "Bijverwarming uitsc", "Blockera eltillskott", "wyłącz dogrzewacz", "deaktiver tilleggsvarme", "Désactiver chauff. d'app", "ilave ısıtıcıyı kapat", "disattivare i riscaldatori addizionali") -MAKE_TRANSLATION(auxHeaterStatus, "auxheaterstatus", "aux heater status", "Status Zusatzheizer", "Bijverwarming", "Eltillskott Status", "status dogrzewacza", "status el. tillegsvarme", "Chauffage auxiliaire", "ilave ısıtıcı durumu", "stato riscaldatori addizionali") -MAKE_TRANSLATION(auxHeaterOnly, "auxheateronly", "aux heater only", "nur Zusatzheizer", "Alleen bijverwarming", "Eltillskott Enbart", "tylko dogrzewacz", "kun el tilleggsvarme", "Que chauffage auxiliaire", "sadece ilave ısıtıvcı", "solo riscaldatori addizionali") -MAKE_TRANSLATION(auxHeaterDelay, "auxheaterdelay", "aux heater on delay", "Zusatzheizer verzögert ein", "Bijverw. vertraagd aan", "Eltillskottfördröjning på", "opóźnienie włączenia dogrzewacza", "Tilleggsvarmer forsinket på", "Chauff app tempo marche", "ilave ısıtıcı beklemede", "ritardo riscaldatori addizionali") -MAKE_TRANSLATION(silentMode, "silentmode", "silent mode", "Silentmodus", "Stiller gebruik", "Tyst läge", "tryb cichy", "stille modus", "Fct silencieux", "sessiz mod", "modalità silenziosa") -MAKE_TRANSLATION(minTempSilent, "mintempsilent", "min outside temp for silent mode", "Minimale Aussentemperatur Silentmodus", "Stiller gebruik min. buitentemp", "Tyst läge min temp", "minimalna temperatura zewnętrzna dla trybu cichego", "atille modus min temp", "Fct silencieux: Temp. extérieure min.", "sessiz mod için min. dış ortam sıcaklığı", "modalità silenziosa temperatura esterna minima") -MAKE_TRANSLATION(tempParMode, "tempparmode", "outside temp parallel mode", "Aussentemperatur Parallelmodus", "Buitentemp. parallelbedr", "Parallelläge Utomhustemp.", "maksymalna temperatura zewnętrzna dla dogrzewacza", "", "Temp. ext. fct parallèle", "paralel mod dış ortam sıcaklığı", "modalità parallela temperatura esterna") // TODO translate -MAKE_TRANSLATION(auxHeatMixValve, "auxheatmix", "aux heater mixing valve", "Mischer Zusatzheizer", "Bijverwarming menger", "Eltilskott Blandarventil", "mieszacz dogrzewacza", "eltilskudd blandeventil", "Chauffage auxiliaire mélangeur", "ilave ısıtıcı karışım vanası", "miscela riscaldatori addizionali") -MAKE_TRANSLATION(hpHystHeat, "hphystheat", "on/off hyst heat", "Schalthysterese Heizen", "Aan/uit-hysteresis in verw. bedrijf", "Hstereses Uppvärm.", "histereza wł./wył. ogrzewania", "På/av hysterese Oppvar.", "Hystérésis Marche en mode chauffage", "ısıtma gecikmesi", "isteresi di commutazione riscaldamento") -MAKE_TRANSLATION(hpHystCool, "hphystcool", "on/off hyst cool", "Schalthysterese Kühlen", "Aan/uit-hysteresis in koelbedrijf ", "Hystereses Kyla", "histereza wł./wył. chłodzenia", "hystrese kjøling", "Hystérésis Marche en mode refroidissement", "soğutma gecikmesi", "isteresi di commutazione raffreddamento") -MAKE_TRANSLATION(hpHystPool, "hphystpool", "on/off hyst pool", "Schalthysterese Pool", "an/uit-hysteresis in zwembadbedri", "Hystereses Pool", "histereza wł./wył. podgrzewania basenu", "hystrese basseng", "Hystérésis Marche en mode piscine", "havuz gecikmesi", "isteresi di commutazione piscina") -MAKE_TRANSLATION(tempDiffHeat, "tempdiffheat", "temp diff TC3/TC0 heat", "Temp.diff. TC3/TC0 Heizen", "Temp.vers. TC3/TC0 verw", "Delta(T) TC3/TC0 Uppvärm.", "różnica temperatur TC3/TC0 w trakcie ogrzewania", "temp. diff. TC3/TC0 oppvarm", "Delta T TC3/TC0 Chauff", "TC3-TC0 ısıtma sıcaklık farkı", "Delta T riscaldamento TC3/TC0") -MAKE_TRANSLATION(tempDiffCool, "tempdiffcool", "temp diff TC3/TC0 cool", "Temp.diff. TC3/TC0 Kühlen", "Temp.vers. TC3/TC0 koel.", "Delta(T) TC3/TC0 Kyla", "różnica temperatur TC3/TC0 w trakcie chłodzenia", "temp. diff. TC3/TC0 kjøling", "Delta T TC3/TC0 Refroid.", "TC3-TC0 soğutma sıcaklık farkı", "Delta T raffreddamento TC3/TC0") -MAKE_TRANSLATION(silentFrom, "silentfrom", "silent mode from", "Silentmodus Start", "Start stille modus", "", "początek trybu cichego", "stillemodus starter", "", "sessiz mod başlangıcı", "avvio della modalità silenziosa") // TODO translate -MAKE_TRANSLATION(silentTo, "silentto", "silent mode to", "Silentmodus Ende", "Einde stille modus", "", "koniec trybu cichego", "komfortmodus av", "", "sessiz mod bitişi", "spegnere modalità silenziosa") // TODO translate +MAKE_TRANSLATION(auxHeaterOff, "auxheateroff", "disable aux heater", "Verbiete Zusatzheizer", "Bijverwarming uitsc", "Blockera eltillskott", "wyłącz dogrzewacz", "deaktiver tilleggsvarme", "Désactiver chauff. d'app", "ilave ısıtıcıyı kapat", "disattivare i riscaldatori addizionali", "vypnúť pomocný ohrievač") +MAKE_TRANSLATION(auxHeaterStatus, "auxheaterstatus", "aux heater status", "Status Zusatzheizer", "Bijverwarming", "Eltillskott Status", "status dogrzewacza", "status el. tillegsvarme", "Chauffage auxiliaire", "ilave ısıtıcı durumu", "stato riscaldatori addizionali", "stav pomocného ohrievača") +MAKE_TRANSLATION(auxHeaterOnly, "auxheateronly", "aux heater only", "nur Zusatzheizer", "Alleen bijverwarming", "Eltillskott Enbart", "tylko dogrzewacz", "kun el tilleggsvarme", "Que chauffage auxiliaire", "sadece ilave ısıtıvcı", "solo riscaldatori addizionali", "iba pomocný ohrievač") +MAKE_TRANSLATION(auxHeaterDelay, "auxheaterdelay", "aux heater on delay", "Zusatzheizer verzögert ein", "Bijverw. vertraagd aan", "Eltillskottfördröjning på", "opóźnienie włączenia dogrzewacza", "Tilleggsvarmer forsinket på", "Chauff app tempo marche", "ilave ısıtıcı beklemede", "ritardo riscaldatori addizionali", "oneskorenie prídavného ohrievača") +MAKE_TRANSLATION(silentMode, "silentmode", "silent mode", "Silentmodus", "Stiller gebruik", "Tyst läge", "tryb cichy", "stille modus", "Fct silencieux", "sessiz mod", "modalità silenziosa", "tichý režim") +MAKE_TRANSLATION(minTempSilent, "mintempsilent", "min outside temp for silent mode", "Minimale Aussentemperatur Silentmodus", "Stiller gebruik min. buitentemp", "Tyst läge min temp", "minimalna temperatura zewnętrzna dla trybu cichego", "atille modus min temp", "Fct silencieux: Temp. extérieure min.", "sessiz mod için min. dış ortam sıcaklığı", "modalità silenziosa temperatura esterna minima", "min. vonkajšia teplota pre tichý režim") +MAKE_TRANSLATION(tempParMode, "tempparmode", "outside temp parallel mode", "Aussentemperatur Parallelmodus", "Buitentemp. parallelbedr", "Parallelläge Utomhustemp.", "maksymalna temperatura zewnętrzna dla dogrzewacza", "", "Temp. ext. fct parallèle", "paralel mod dış ortam sıcaklığı", "modalità parallela temperatura esterna", "paralelný režim mimo teploty") // TODO translate +MAKE_TRANSLATION(auxHeatMixValve, "auxheatmix", "aux heater mixing valve", "Mischer Zusatzheizer", "Bijverwarming menger", "Eltilskott Blandarventil", "mieszacz dogrzewacza", "eltilskudd blandeventil", "Chauffage auxiliaire mélangeur", "ilave ısıtıcı karışım vanası", "miscela riscaldatori addizionali", "zmiešavací ventil pomocného ohrievača") +MAKE_TRANSLATION(hpHystHeat, "hphystheat", "on/off hyst heat", "Schalthysterese Heizen", "Aan/uit-hysteresis in verw. bedrijf", "Hstereses Uppvärm.", "histereza wł./wył. ogrzewania", "På/av hysterese Oppvar.", "Hystérésis Marche en mode chauffage", "ısıtma gecikmesi", "isteresi di commutazione riscaldamento", "zapnutie/vypnutie hyst ohrevu") +MAKE_TRANSLATION(hpHystCool, "hphystcool", "on/off hyst cool", "Schalthysterese Kühlen", "Aan/uit-hysteresis in koelbedrijf", "Hystereses Kyla", "histereza wł./wył. chłodzenia", "hystrese kjøling", "Hystérésis Marche en mode refroidissement", "soğutma gecikmesi", "isteresi di commutazione raffreddamento", "zapnutie/vypnutie hyst chladenia") +MAKE_TRANSLATION(hpHystPool, "hphystpool", "on/off hyst pool", "Schalthysterese Pool", "an/uit-hysteresis in zwembadbedri", "Hystereses Pool", "histereza wł./wył. podgrzewania basenu", "hystrese basseng", "Hystérésis Marche en mode piscine", "havuz gecikmesi", "isteresi di commutazione piscina", "zapnutie/vypnutie hyst bazénu") +MAKE_TRANSLATION(tempDiffHeat, "tempdiffheat", "temp diff TC3/TC0 heat", "Temp.diff. TC3/TC0 Heizen", "Temp.vers. TC3/TC0 verw", "Delta(T) TC3/TC0 Uppvärm.", "różnica temperatur TC3/TC0 w trakcie ogrzewania", "temp. diff. TC3/TC0 oppvarm", "Delta T TC3/TC0 Chauff", "TC3-TC0 ısıtma sıcaklık farkı", "Delta T riscaldamento TC3/TC0", "teplotný rozdiel TC3/TC0 tepla") +MAKE_TRANSLATION(tempDiffCool, "tempdiffcool", "temp diff TC3/TC0 cool", "Temp.diff. TC3/TC0 Kühlen", "Temp.vers. TC3/TC0 koel.", "Delta(T) TC3/TC0 Kyla", "różnica temperatur TC3/TC0 w trakcie chłodzenia", "temp. diff. TC3/TC0 kjøling", "Delta T TC3/TC0 Refroid.", "TC3-TC0 soğutma sıcaklık farkı", "Delta T raffreddamento TC3/TC0", "teplotný rozdiel TC3/TC0 chladenie") +MAKE_TRANSLATION(silentFrom, "silentfrom", "silent mode from", "Silentmodus Start", "Start stille modus", "", "początek trybu cichego", "stillemodus starter", "", "sessiz mod başlangıcı", "avvio della modalità silenziosa", "tichý režim od") // TODO translate +MAKE_TRANSLATION(silentTo, "silentto", "silent mode to", "Silentmodus Ende", "Einde stille modus", "", "koniec trybu cichego", "komfortmodus av", "", "sessiz mod bitişi", "spegnere modalità silenziosa", "tichý režim do") // TODO translate -MAKE_TRANSLATION(wwComfOffTemp, "wwcomfoff", "comfort switch off", "Komfort Ausschalttemp", "Comfort Uitschakeltemp.", "Komfortläge avstängingstemp.", "temperatura wyłączania w trybie komfort", "eco modus utkoblingstem", "Confort Temp. d'arrêt", "konfor kapalı", "spegnimento modalità comfort") -MAKE_TRANSLATION(wwEcoOffTemp, "wwecooff", "eco switch off", "ECO Ausschalttemp", "Eco Uitschakeltemp.", "Ekoläge avstängningstemp.", "temperatura wyłączania w trybie eko", "Øko avstengningstemp.", "Eco Temp. d'arrêt", "eko kapalı", "spegnimento modalità ECO") -MAKE_TRANSLATION(wwEcoPlusOffTemp, "wwecoplusoff", "eco+ switch off", "ECO+ Ausschalttemp", "Eco+ Uitschakeltemp.", "Eko+ avstängningstemp.", "temperatura wyłączania w trybie eko+", "Øko+ avstengningstemp.", "Eco+ Temp. d'arrêt", "eko+ kapalı", "spegnimento modalità ECO+") +MAKE_TRANSLATION(wwComfOffTemp, "wwcomfoff", "comfort switch off", "Komfort Ausschalttemp", "Comfort Uitschakeltemp.", "Komfortläge avstängingstemp.", "temperatura wyłączania w trybie komfort", "eco modus utkoblingstem", "Confort Temp. d'arrêt", "konfor kapalı", "spegnimento modalità comfort", "komfortné vypnutie") +MAKE_TRANSLATION(wwEcoOffTemp, "wwecooff", "eco switch off", "ECO Ausschalttemp", "Eco Uitschakeltemp.", "Ekoläge avstängningstemp.", "temperatura wyłączania w trybie eko", "Øko avstengningstemp.", "Eco Temp. d'arrêt", "eko kapalı", "spegnimento modalità ECO", "eko vypínač") +MAKE_TRANSLATION(wwEcoPlusOffTemp, "wwecoplusoff", "eco+ switch off", "ECO+ Ausschalttemp", "Eco+ Uitschakeltemp.", "Eko+ avstängningstemp.", "temperatura wyłączania w trybie eko+", "Øko+ avstengningstemp.", "Eco+ Temp. d'arrêt", "eko+ kapalı", "spegnimento modalità ECO+", "eko+ vypnutie") -MAKE_TRANSLATION(auxHeatMode, "auxheatrmode", "aux heater mode", "Modus Zusatzheizer", "Modus bijverwarmer", "", "tryb pracy dogrzewacza po blokadzie z Zakładu Energetycznego", "tilleggsvarmer modus", "", "ilave ısıtıcı modu", "modalità riscaldatore addizionale") // TODO translate -MAKE_TRANSLATION(auxMaxLimit, "auxmaxlimit", "aux heater max limit", "Zusatzheizer max. Grenze", "Bijverwarmer grensinstelling maximaal", "", "dogrzewacz, maksymalny limit", "tillegsvarme maksgrense", "", "ilave ısıtıcı maks limit", "limite massimo riscaldatore addizionale") // TODO translate -MAKE_TRANSLATION(auxLimitStart, "auxlimitstart", "aux heater limit start", "Zusatzheizer Grenze Start", "Bijverwarmer grens voor start", "", "dogrzewacz, początek ograniczenia", "tillegsvarme startgrense", "", "ilave ısıtıcı limir başlangıcı", "avvio limite massimo riscaldatore addizionale") // TODO translate -MAKE_TRANSLATION(manDefrost, "mandefrost", "manual defrost", "Manuelle Enteisung", "Handmatige ontdooicyclus", "", "ręczne odladzanie", "manuell avisning", "", "manuel buz çözme", "sbrinamento manuale") // TODO translate -MAKE_TRANSLATION(pvCooling, "pvcooling", "Cooling only with PV", "Kühlen nur mit PV", "Koelen alleen met solar PV", "", "chłodzenie tylko z PV", "kjøling med solpanel", "", "sadece PV ile soğutma", "solo raffrescamento con solare") // TODO translate -MAKE_TRANSLATION(hpCircPumpWw, "hpcircpumpww", "circulation pump available during dhw", "Zirkulation möglich bei WW-Bereitung", "Circulatiepomp WP beschikbaar tijdens ww", "", "pompa cyrkulacji dostępna w trakcie c.w.u.", "sirkulasjonspumpe tilgjengelig under varmtvann", "", "SKS esnasında sirkülasyon pompasu uygun", "pompa di circolazione disponibile durante ACS") // TODO translate -MAKE_TRANSLATION(vp_cooling, "vpcooling", "valve/pump cooling", "Ventil/Pumpe für Kühlen", "Klep koeling", "", "zawór/pompa chłodzenia", "varmepumpe kjøling", "", "vana/pompa soğuyor", "valvola/pompa raffrescamento") // TODO translate -MAKE_TRANSLATION(VC0valve, "vc0valve", "VC0 valve", "VC0 Ventil", "Klep VC0", "", "zawór VC0", "vc0 ventil", "", "VC0 vana", "valvola VC0") // TODO translate -MAKE_TRANSLATION(primePump, "primepump", "primary heatpump", "Hauptpumpe", "Hoofdpomp", "", "główna pompa ciepła", "primærpumpe", "", "ana ısı pompası", "pompa principale riscaldamento") // TODO translate -MAKE_TRANSLATION(primePumpMod, "primepumpmod", "primary heatpump modulation", "Modulation Hauptpumpe", "Modulatie hoofdpomp", "", "wysterowanie głównej pompy ciepła", "primærpumpelast", "", "ana ısı pompası modülasyon", "pompa principale modulazione riscaldamento") // TODO translate -MAKE_TRANSLATION(hp3wayValve, "hp3way", "3-way valve", "3-Wege-Ventil", "3-weg klep", "", "zawór 3-drogowy pompy ciepła", "3-veisventil", "", "3 yollu vana", "valvola 3-vie") // TODO translate -MAKE_TRANSLATION(hp4wayValve, "hp4way", "4-way valve (VR4)", "4-Wege-Ventil (VR4)", "4-weg klep (VR4)", "(VR4)", "zawór 4-drogowy pompy ciepła (VR4)", "4-veisventil (VR4)", "(VR4)", "4 yollu vana (VR4)", "valvola 4-vie (VR4)") // TODO translate -MAKE_TRANSLATION(elHeatStep1, "elheatstep1", "el. heater step 1", "El. Heizer Stufe 1", "Electrische bijverwarmer niveau 1", "", "dogrzewacz poziom 1", "el-kolbe steg 1", "", "el.ısıtıcı adım 1", "riscaldatore elettrico livello 1") // TODO translate -MAKE_TRANSLATION(elHeatStep2, "elheatstep2", "el. heater step 2", "El. Heizer Stufe 2", "Electrische bijverwarmer niveau 2", "", "dogrzewacz poziom 2", "el-kolbe steg 2", "", "el.ısıtıcı adım 2", "riscaldatore elettrico livello 2") // TODO translate -MAKE_TRANSLATION(elHeatStep3, "elheatstep3", "el. heater step 3", "El. Heizer Stufe 3", "Electrische bijverwarmer niveau 3", "", "dogrzewacz poziom 3", "el-kolbe steg 3", "", "el.ısıtıcı adım 3", "riscaldatore elettrico livello 3") // TODO translate -MAKE_TRANSLATION(wwAlternatingOper, "wwalternatingop", "alternating operation", "Wechselbetrieb", "Wisselbedrijf ww", "", "praca naprzemienna", "alternativ drift", "", "sıcak kullanım suyu alternatif işletim", "funzionamento alternato") // TODO translate -MAKE_TRANSLATION(wwAltOpPrioHeat, "wwaltopprioheat", "prioritise heating during dhw", "Heizen bevorzugt vor WW", "Proriteit verwarming boven ww", "", "czas na ogrzewanie w trakcie c.w.u", "prioritert oppvarmning", "", "sıcak kullanım suyu esnasında ısıtmayı öne al", "dare la priorità al riscaldamento durante l'ACS") // TODO translate -MAKE_TRANSLATION(wwAltOpPrioWw, "wwaltopprioww", "prioritise dhw during heating", "WW bevorzugt vor Heizen", "Prioriteit ww boven verwarming", "", "czas na c.w.u w trakcie ogrzewania", "prioritert varmtvann", "", "ısıtma esnasında sıcak kullanım suyunu öne al", "dare priorità all'acqua calda durante il riscaldamento") // TODO translate -MAKE_TRANSLATION(hpEA0, "hpea0", "condensate reservoir heating (EA0)", "Heizung Kondensatwanne (EA0)", "", "", "", "", "", "", "") // TODO translate -MAKE_TRANSLATION(boost, "boost", "boost mode", "Boost", "", "", "", "", "", "", "") // TODO translate -MAKE_TRANSLATION(boosttime, "boosttime", "boost time", "Boost Dauer", "", "", "", "", "", "", "") // TODO translate -MAKE_TRANSLATION(hpPumpMode, "hppumpmode", "primary heatpump mode", "Modus Hauptpumpe", "", "", "", "", "", "", "") // TODO translate -MAKE_TRANSLATION(instantstart, "instantstart", "instant start", "Sofortstart", "", "", "", "", "", "", "") // TODO translate -MAKE_TRANSLATION(heatondelay, "heatondelay", "heat-on delay", "Einschaltverzögerung Heizen", "", "", "", "", "", "", "") // TODO translate -MAKE_TRANSLATION(heatoffdelay, "heatoffdelay", "heat-off delay", "Ausschaltverzögerung Heizen", "", "", "", "", "", "", "") // TODO translate +MAKE_TRANSLATION(auxHeatMode, "auxheatrmode", "aux heater mode", "Modus Zusatzheizer", "Modus bijverwarmer", "", "tryb pracy dogrzewacza po blokadzie z Zakładu Energetycznego", "tilleggsvarmer modus", "", "ilave ısıtıcı modu", "modalità riscaldatore addizionale", "režim pomocného ohrievača") // TODO translate +MAKE_TRANSLATION(auxMaxLimit, "auxmaxlimit", "aux heater max limit", "Zusatzheizer max. Grenze", "Bijverwarmer grensinstelling maximaal", "", "dogrzewacz, maksymalny limit", "tillegsvarme maksgrense", "", "ilave ısıtıcı maks limit", "limite massimo riscaldatore addizionale", "maximálny limit pomocného ohrievača") // TODO translate +MAKE_TRANSLATION(auxLimitStart, "auxlimitstart", "aux heater limit start", "Zusatzheizer Grenze Start", "Bijverwarmer grens voor start", "", "dogrzewacz, początek ograniczenia", "tillegsvarme startgrense", "", "ilave ısıtıcı limir başlangıcı", "avvio limite massimo riscaldatore addizionale", "spustenie limitu pomocného ohrievača") // TODO translate +MAKE_TRANSLATION(manDefrost, "mandefrost", "manual defrost", "Manuelle Enteisung", "Handmatige ontdooicyclus", "", "ręczne odladzanie", "manuell avisning", "", "manuel buz çözme", "sbrinamento manuale", "manuálne odmrazovanie") // TODO translate +MAKE_TRANSLATION(pvCooling, "pvcooling", "cooling only with PV", "Kühlen nur mit PV", "Koelen alleen met solar PV", "", "chłodzenie tylko z PV", "kjøling med solpanel", "", "sadece PV ile soğutma", "solo raffrescamento con solare", "Chladenie len s FV") // TODO translate +MAKE_TRANSLATION(hpCircPumpWw, "hpcircpumpww", "circulation pump available during dhw", "Zirkulation möglich bei WW-Bereitung", "Circulatiepomp WP beschikbaar tijdens ww", "", "pompa cyrkulacji dostępna w trakcie c.w.u.", "sirkulasjonspumpe tilgjengelig under varmtvann", "", "SKS esnasında sirkülasyon pompasu uygun", "pompa di circolazione disponibile durante ACS", "obehové čerpadlo k dispozícii počas TÚV") // TODO translate +MAKE_TRANSLATION(vp_cooling, "vpcooling", "valve/pump cooling", "Ventil/Pumpe für Kühlen", "Klep koeling", "", "zawór/pompa chłodzenia", "varmepumpe kjøling", "", "vana/pompa soğuyor", "valvola/pompa raffrescamento", "chladenie ventilu/čerpadla") // TODO translate +MAKE_TRANSLATION(VC0valve, "vc0valve", "VC0 valve", "VC0 Ventil", "Klep VC0", "", "zawór VC0", "vc0 ventil", "", "VC0 vana", "valvola VC0", "VC0 ventil") // TODO translate +MAKE_TRANSLATION(primePump, "primepump", "primary heatpump", "Hauptpumpe", "Hoofdpomp", "", "główna pompa ciepła", "primærpumpe", "", "ana ısı pompası", "pompa principale riscaldamento", "primárne tepelné čerpadlo") // TODO translate +MAKE_TRANSLATION(primePumpMod, "primepumpmod", "primary heatpump modulation", "Modulation Hauptpumpe", "Modulatie hoofdpomp", "", "wysterowanie głównej pompy ciepła", "primærpumpelast", "", "ana ısı pompası modülasyon", "pompa principale modulazione riscaldamento", "primárna modulácia tepelného čerpadla") // TODO translate +MAKE_TRANSLATION(hp3wayValve, "hp3way", "3-way valve", "3-Wege-Ventil", "3-weg klep", "", "zawór 3-drogowy pompy ciepła", "3-veisventil", "", "3 yollu vana", "valvola 3-vie", "3-cestný ventil") // TODO translate +MAKE_TRANSLATION(hp4wayValve, "hp4way", "4-way valve (VR4)", "4-Wege-Ventil (VR4)", "4-weg klep (VR4)", "(VR4)", "zawór 4-drogowy pompy ciepła (VR4)", "4-veisventil (VR4)", "(VR4)", "4 yollu vana (VR4)", "valvola 4-vie (VR4)", "4-cestný ventil (VR4)") // TODO translate +MAKE_TRANSLATION(elHeatStep1, "elheatstep1", "el. heater step 1", "El. Heizer Stufe 1", "Electrische bijverwarmer niveau 1", "", "dogrzewacz poziom 1", "el-kolbe steg 1", "", "el.ısıtıcı adım 1", "riscaldatore elettrico livello 1", "krok 1 elektrického ohrievača") // TODO translate +MAKE_TRANSLATION(elHeatStep2, "elheatstep2", "el. heater step 2", "El. Heizer Stufe 2", "Electrische bijverwarmer niveau 2", "", "dogrzewacz poziom 2", "el-kolbe steg 2", "", "el.ısıtıcı adım 2", "riscaldatore elettrico livello 2", "krok 2 elektrického ohrievača") // TODO translate +MAKE_TRANSLATION(elHeatStep3, "elheatstep3", "el. heater step 3", "El. Heizer Stufe 3", "Electrische bijverwarmer niveau 3", "", "dogrzewacz poziom 3", "el-kolbe steg 3", "", "el.ısıtıcı adım 3", "riscaldatore elettrico livello 3", "krok 3 elektrického ohrievača") // TODO translate +MAKE_TRANSLATION(wwAlternatingOper, "wwalternatingop", "alternating operation", "Wechselbetrieb", "Wisselbedrijf ww", "", "praca naprzemienna", "alternativ drift", "", "sıcak kullanım suyu alternatif işletim", "funzionamento alternato", "striedavá prevádzka") // TODO translate +MAKE_TRANSLATION(wwAltOpPrioHeat, "wwaltopprioheat", "prioritise heating during dhw", "Heizen bevorzugt vor WW", "Proriteit verwarming boven ww", "", "czas na ogrzewanie w trakcie c.w.u", "prioritert oppvarmning", "", "sıcak kullanım suyu esnasında ısıtmayı öne al", "dare la priorità al riscaldamento durante l'ACS", "Uprednostniť ohrev počas TÚV") // TODO translate +MAKE_TRANSLATION(wwAltOpPrioWw, "wwaltopprioww", "prioritise dhw during heating", "WW bevorzugt vor Heizen", "Prioriteit ww boven verwarming", "", "czas na c.w.u w trakcie ogrzewania", "prioritert varmtvann", "", "ısıtma esnasında sıcak kullanım suyunu öne al", "dare priorità all'acqua calda durante il riscaldamento", "uprednostniť TÚV počas ohrevu") // TODO translate +MAKE_TRANSLATION(hpEA0, "hpea0", "condensate reservoir heating (EA0)", "Heizung Kondensatwanne (EA0)", "", "", "ogrzewanie zbiornika kondensatu (EA0)", "", "", "", "", "ohrievanie zásobníka kondenzátu (EA0)") // TODO translate +MAKE_TRANSLATION(boost, "boost", "boost mode", "Boost", "", "", "tryb wzmocnienia (boost)", "", "", "", "", "") // TODO translate +MAKE_TRANSLATION(boosttime, "boosttime", "boost time", "Boost Dauer", "", "", "czas trwania wzmocnienia", "", "", "", "", "") // TODO translate +MAKE_TRANSLATION(hpPumpMode, "hppumpmode", "primary heatpump mode", "Modus Hauptpumpe", "", "", "tryb pracy głównej pompy ciepła", "", "", "", "", "") // TODO translate +MAKE_TRANSLATION(instantstart, "instantstart", "instant start", "Sofortstart", "", "", "natychmiastowy start", "", "", "", "", "") // TODO translate +MAKE_TRANSLATION(heatondelay, "heatondelay", "heat-on delay", "Einschaltverzögerung Heizen", "", "", "opóźnienie włączania ogrzewania", "", "", "", "", "") // TODO translate +MAKE_TRANSLATION(heatoffdelay, "heatoffdelay", "heat-off delay", "Ausschaltverzögerung Heizen", "", "", "opóźnienie włączania ogrzewania", "", "", "", "", "") // TODO translate // hybrid heatpump -MAKE_TRANSLATION(hybridStrategy, "hybridstrategy", "hybrid control strategy", "Hybrid Strategie", "Hybride strategie", "Hybrid kontrollstrategi", "strategia sterowania hybrydowego", "hybrid kontrollstrategi", "stratégie contrôle hybride", "hibrit kontrol stratejisi", "strategia comtrollo ibrido") -MAKE_TRANSLATION(switchOverTemp, "switchovertemp", "outside switchover temperature", "Außentemperatur für Umschaltung", "Schakeltemperatuur buitentemperatuur", "Utomhus Omställningstemperatur", "zewnętrzna temperatura przełączania", "utendørstemp styring", "basculement par température extérieure", "geçiş için dış sıcaklık", "temperatura esterna per commutazione") -MAKE_TRANSLATION(energyCostRatio, "energycostratio", "energy cost ratio", "Energie/Kosten-Verhältnis", "Energiekostenratio", "Energi/Kostnads-förhållande", "współczynnik energia/koszt", "energi/kostnads forhold", "ratio coût énergie", "enerji maliyet oranı", "rapporto energia/costo") -MAKE_TRANSLATION(fossileFactor, "fossilefactor", "fossile energy factor", "Energiefaktor Fossil", "Energiefactor fossiele brandstof", "Energifaktor fossilenergi", "udział energii z paliw kopalnych", "energifaktor fossilenergi", "facteur énergie fossile", "fosil yakıt faktörü", "fattore energia fossile") -MAKE_TRANSLATION(electricFactor, "electricfactor", "electric energy factor", "Energiefaktor elektrisch", "Energiefactor electrisch", "Elektrisk energifaktor", "udział energii elektrycznej", "elektrisk energifaktor", "facteur énergie électrique", "elektrik enerjisi faktörü", "fattore energia elettrica") +MAKE_TRANSLATION(hybridStrategy, "hybridstrategy", "hybrid control strategy", "Hybrid Strategie", "Hybride strategie", "Hybrid kontrollstrategi", "strategia sterowania hybrydowego", "hybrid kontrollstrategi", "stratégie contrôle hybride", "hibrit kontrol stratejisi", "strategia comtrollo ibrido", "hybridná stratégia riadenia") +MAKE_TRANSLATION(switchOverTemp, "switchovertemp", "outside switchover temperature", "Außentemperatur für Umschaltung", "Schakeltemperatuur buitentemperatuur", "Utomhus Omställningstemperatur", "zewnętrzna temperatura przełączania", "utendørstemp styring", "basculement par température extérieure", "geçiş için dış sıcaklık", "temperatura esterna per commutazione", "vonkajšia prepínacia teplota") +MAKE_TRANSLATION(energyCostRatio, "energycostratio", "energy cost ratio", "Energie/Kosten-Verhältnis", "Energiekostenratio", "Energi/Kostnads-förhållande", "współczynnik energia/koszt", "energi/kostnads forhold", "ratio coût énergie", "enerji maliyet oranı", "rapporto energia/costo", "pomer nákladov na energiu") +MAKE_TRANSLATION(fossileFactor, "fossilefactor", "fossile energy factor", "Energiefaktor Fossil", "Energiefactor fossiele brandstof", "Energifaktor fossilenergi", "udział energii z paliw kopalnych", "energifaktor fossilenergi", "facteur énergie fossile", "fosil yakıt faktörü", "fattore energia fossile", "faktor fosílnej energie") +MAKE_TRANSLATION(electricFactor, "electricfactor", "electric energy factor", "Energiefaktor elektrisch", "Energiefactor electrisch", "Elektrisk energifaktor", "udział energii elektrycznej", "elektrisk energifaktor", "facteur énergie électrique", "elektrik enerjisi faktörü", "fattore energia elettrica", "faktor elektrickej energie") MAKE_TRANSLATION(delayBoiler, "delayboiler", "delay boiler support", "Verzögerungs-Option", "Vertragingsoptie", "Fördröjningsoption", "opcja opóźnienia", "Fördörjningsoption", "option retardement chaudière", "kazan desteğini ötele", "opzione ritardo caldaia") -MAKE_TRANSLATION(tempDiffBoiler, "tempdiffboiler", "temp diff boiler support", "Temperaturdifferenz-Option", "Verschiltemperatuuroptie", "Temperaturskillnadsoption", "opcja różnicy temperatur", "temperatursforskjell kjele", "option différence température", "sıcaklık farkı kazan desteği", "opzione differenza temperatura") -MAKE_TRANSLATION(lowNoiseMode, "lownoisemode", "low noise mode", "Geräuscharmer Betrieb", "Stil bedrijf", "Tyst läge", "tryb cichy", "stillemodus", "mode faible bruit", "düşük ses modu", "modalità a basso rumore") -MAKE_TRANSLATION(lowNoiseStart, "lownoisestart", "low noise starttime", "Start geräuscharmer Betrieb", "Start stil bedrijf", "Tyst läge starttid", "początek trybu cichego", "stille modu starttid", "heure démarrage faible bruit", "düşük ses başlangıç", "ora di avvio a basso rumore") -MAKE_TRANSLATION(lowNoiseStop, "lownoisestop", "low noise stoptime", "Stopp geräuscharmer Betrieb", "Stop stil bedrijf", "Tyst läge stopptid", "koniec trybu cichego", "stille modus stopptid", "heure arrêt faible bruit", "düşük ses bitiş", "ora di arresto funzionamento silenzioso") -MAKE_TRANSLATION(energyPriceGas, "energypricegas", "energy price gas", "Energiepreis Gas", "Energieprijs gas", "Gaspris", "cena energii z gazu", "energipris gass", "prix énergie gaz", "gaz enerjisi fiyatı", "prezzo energia gas") -MAKE_TRANSLATION(energyPriceEl, "energypriceel", "energy price electric", "Energiepreis Eletrizität", "energieprijs electriciteit", "Elpris", "cena energii elektrycznej", "strømpris", "prix énergie électrique", "elektrik enerjisi fiyatı", "prezzo energia elettrica") -MAKE_TRANSLATION(energyPricePV, "energyfeedpv", "feed in PV", "PV Einspeisevergütung", "PV teruglevertarief", "PV Energi", "zasilanie energią PV", "strømpris PV", "alimentation PV", "giren güneş enerjisi", "energia fotovoltaico") -MAKE_TRANSLATION(hybridDHW, "hybriddhw", "hybrid DHW", "Hybrid Warmwasser", "Hybride ww", "Hybridläge varmvatten", "hybrydowa c.w.u.", "hybridmodus varmtvann", "ecs hybride", "hibrit SKS", "ACS ibrida") -MAKE_TRANSLATION(airPurgeMode, "airpurgemode", "air purge mode", "Luftspülung", "Luchtzuivering", "Luftreningsläge", "tryb oczyszczania powietrza", "luftsrensningsmodus", "mode purge air", "hava temizleme modu", "modalita spurgo aria") -MAKE_TRANSLATION(heatPumpOutput, "heatpumpoutput", "heatpump output", "WP Leistung", "WP output", "Värmepumpseffekt", "moc wyjściowa pompy ciepła", "varmepumpeeffekt", "sortie pompe à chaleur", "ısı pompası çıkışı", "prestazione pompa calore") -MAKE_TRANSLATION(coolingCircuit, "coolingcircuit", "cooling circuit", "Kühlkreislauf", "Koelcircuit", "Kylkrets", "obwód chłodzący", "kjølekrets", "circuit refroidissement", "soğutma devresi", "circuito raffreddante") -MAKE_TRANSLATION(compStartMod, "compstartmod", "compressor start modulation", "Kompressor Startleistung", "Beginvermogen compressor", "Kompressor startmodulering", "początkowa modulacja sprężarki", "kompressor startmodulering", "modulation démarrage compresseur", "kazan başlangıç modülasyonu", "avvio modulazione compressore") -MAKE_TRANSLATION(heatDrainPan, "heatdrainpan", "heat drain pan", "Wärmeausgleichsgefäß", "Vereffeningsvat", "Uppvärm. dränering", "zbiornik wyrównawczy ciepła", "oppvarming drenering", "bac récupération chaleur", "ısı tahliye tablası", "serbatoio scarico condensa") -MAKE_TRANSLATION(heatCable, "heatcable", "heating cable", "Heizband", "heating cable", "värmekabel", "przewód grzejny", "varmekabel", "câble chauffant", "ısıtma kablosu", "cavo riscaldante") +MAKE_TRANSLATION(tempDiffBoiler, "tempdiffboiler", "temp diff boiler support", "Temperaturdifferenz-Option", "Verschiltemperatuuroptie", "Temperaturskillnadsoption", "opcja różnicy temperatur", "temperatursforskjell kjele", "option différence température", "sıcaklık farkı kazan desteği", "opzione differenza temperatura", "oneskorená podpora kotla") +MAKE_TRANSLATION(lowNoiseMode, "lownoisemode", "low noise mode", "Geräuscharmer Betrieb", "Stil bedrijf", "Tyst läge", "tryb cichy", "stillemodus", "mode faible bruit", "düşük ses modu", "modalità a basso rumore", "režim nízkej hlučnosti") +MAKE_TRANSLATION(lowNoiseStart, "lownoisestart", "low noise starttime", "Start geräuscharmer Betrieb", "Start stil bedrijf", "Tyst läge starttid", "początek trybu cichego", "stille modu starttid", "heure démarrage faible bruit", "düşük ses başlangıç", "ora di avvio a basso rumore", "nízka hlučnosť spustenia") +MAKE_TRANSLATION(lowNoiseStop, "lownoisestop", "low noise stoptime", "Stopp geräuscharmer Betrieb", "Stop stil bedrijf", "Tyst läge stopptid", "koniec trybu cichego", "stille modus stopptid", "heure arrêt faible bruit", "düşük ses bitiş", "ora di arresto funzionamento silenzioso", "doba zastavenia s nízkou hlučnosťou") +MAKE_TRANSLATION(energyPriceGas, "energypricegas", "energy price gas", "Energiepreis Gas", "Energieprijs gas", "Gaspris", "cena energii z gazu", "energipris gass", "prix énergie gaz", "gaz enerjisi fiyatı", "prezzo energia gas", "cena energie plyn") +MAKE_TRANSLATION(energyPriceEl, "energypriceel", "energy price electric", "Energiepreis Eletrizität", "energieprijs electriciteit", "Elpris", "cena energii elektrycznej", "strømpris", "prix énergie électrique", "elektrik enerjisi fiyatı", "prezzo energia elettrica", "cena elektrickej energie") +MAKE_TRANSLATION(energyPricePV, "energyfeedpv", "feed in PV", "PV Einspeisevergütung", "PV teruglevertarief", "PV Energi", "cena energii PV", "strømpris PV", "alimentation PV", "giren güneş enerjisi", "energia fotovoltaico", "Výkupná cena FV") +MAKE_TRANSLATION(hybridDHW, "hybriddhw", "hybrid DHW", "Hybrid Warmwasser", "Hybride ww", "Hybridläge varmvatten", "hybrydowa c.w.u.", "hybridmodus varmtvann", "ecs hybride", "hibrit SKS", "ACS ibrida", "hybridná TÚV") +MAKE_TRANSLATION(airPurgeMode, "airpurgemode", "air purge mode", "Luftspülung", "Luchtzuivering", "Luftreningsläge", "tryb oczyszczania powietrza", "luftsrensningsmodus", "mode purge air", "hava temizleme modu", "modalita spurgo aria", "režim čistenia vzduchu") +MAKE_TRANSLATION(heatPumpOutput, "heatpumpoutput", "heatpump output", "WP Leistung", "WP output", "Värmepumpseffekt", "moc wyjściowa pompy ciepła", "varmepumpeeffekt", "sortie pompe à chaleur", "ısı pompası çıkışı", "prestazione pompa calore", "Výkon tepelného čerpadla") +MAKE_TRANSLATION(coolingCircuit, "coolingcircuit", "cooling circuit", "Kühlkreislauf", "Koelcircuit", "Kylkrets", "obwód chłodzący", "kjølekrets", "circuit refroidissement", "soğutma devresi", "circuito raffreddante", "chladiaci okruh") +MAKE_TRANSLATION(compStartMod, "compstartmod", "compressor start modulation", "Kompressor Startleistung", "Beginvermogen compressor", "Kompressor startmodulering", "początkowa modulacja sprężarki", "kompressor startmodulering", "modulation démarrage compresseur", "kazan başlangıç modülasyonu", "avvio modulazione compressore", "modulácia štartu kompresora") +MAKE_TRANSLATION(heatDrainPan, "heatdrainpan", "heat drain pan", "Wärmeausgleichsgefäß", "Vereffeningsvat", "Uppvärm. dränering", "zbiornik wyrównawczy ciepła", "oppvarming drenering", "bac récupération chaleur", "ısı tahliye tablası", "serbatoio scarico condensa", "odkvapkávacia nádoba na teplo") +MAKE_TRANSLATION(heatCable, "heatcable", "heating cable", "Heizband", "heating cable", "värmekabel", "przewód grzejny", "varmekabel", "câble chauffant", "ısıtma kablosu", "cavo riscaldante", "vykurovací kábel") // alternative heatsource AM200 -MAKE_TRANSLATION(aCylTopTemp, "cyltoptemp", "cylinder top temperature", "Speichertemperatur Oben", "Buffer temperatuur boven", "Cylindertemperatur Toppen", "temperatura na górze cylindra", "beredertemperatur topp", "température haut cylindre", "silindir üst yüzey sıcaklığı", "temperatura superiore accumulo") -MAKE_TRANSLATION(aCylCenterTemp, "cylcentertemp", "cylinder center temperature", "Speichertemperatur Mitte", "Buffer temperatuur midden", "Cylindertemperatur Mitten", "temperatura na środku cylindra", "beredertemperatur midten", "température centre cylindre", "silindir merkez sıcaklığı", "temperatura centrale accumulo") -MAKE_TRANSLATION(aCylBottomTemp, "cylbottomtemp", "cylinder bottom temperature", "Speichertemperatur Unten", "Buffer temperatuur onder", "Cylindertemperatur Botten", "temperatura na dole cylindra", "beredertemperatur nederst", "température fond cylindre", "silindir taban sıcaklığı", "temperatura inferiore accumulo") -MAKE_TRANSLATION(aFlowTemp, "altflowtemp", "alternative hs flow temperature", "Alternativer WE Vorlauftemperatur", "Alternatieve warmtebron aanvoertemperatuur", "Alternativ flödestemp värmekälla", "temperatura zasilania z alternatywnego źródła", "alternativ varmekilde tilførselstemperatur", "température flux hs alternative", "alternatif ısı kaynağı besleme sıcaklığı", "temperatura alternativa mandata hs") -MAKE_TRANSLATION(aRetTemp, "altrettemp", "alternative hs return temperature", "Alternativer WE Rücklauftemperatur", "Alternatieve warmtebron retourtemperatuur", "Alternativ returtemp värmekälla", "temperatura powrotu z alternatywnego źródła", "alternativ varmekilde returtemperatur", "température retour hs alternative", "alternatif ısı kaynağı dönüş sıcaklığı", "temperatura alternativa ritorno hs") -MAKE_TRANSLATION(sysFlowTemp, "sysflowtemp", "system flow temperature", "System Vorlauftemperatur", "Systeem aanvoertemperatuur", "Systemflödestemperatur", "temperatura zasilania systemu", "systemturtemperatur", "température flux système", "sistem besleme sıcaklığı", "temperatura di mandata impianto") -MAKE_TRANSLATION(sysRetTemp, "sysrettemp", "system return temperature", "System Rücklauftemperatur", "Systeem retourtemperatuur", "Systemreturtemperatur", "temperatura powrotu z systemu", "systemreturtemperatur", "température retour système", "sistem dönüş sıcaklığı", "temperatura di ritorno impianto") -MAKE_TRANSLATION(valveByPass, "valvebypass", "bypass valve", "Bypass-Ventil", "Bypass klep", "Bypassventil", "zawór obejścia", "bypassventil", "vanne dérivation", "baypas vanası", "valvola Bypass") -MAKE_TRANSLATION(valveBuffer, "valvebuffer", "buffer valve", "Puffer-Ventil", "Bufferklep", "Buffertventil", "zawór bufora", "buffertventil", "vanne tampon", "tampon vanası", "valvola tampone") -MAKE_TRANSLATION(valveReturn, "valvereturn", "return valve", "Rückfluss-Ventil", "Retourklep", "Returventil", "zawór powrotu", "returventil", "vanne retour", "dönüş vanası", "valvola ritorno") -MAKE_TRANSLATION(aPumpMod, "apumpmod", "alternative hs pump modulation", "Alternativer WE Pumpenmodulation", "Alternatieve warmtebron pomp modulatie", "Alternativ Pumpmodulering Värmekälla", "modulacja pompy alternatywnego źródła ciepła", "alternativ pumpemodulering varmekilde", "modulation alternative pompe hs", "alternatif ısı kaynağı pompa modülasyonu", "pompa modulazione alternativa hs") -MAKE_TRANSLATION(heatSource, "heatsource", "alternative heating active", "Alternativer Wärmeerzeuger aktiv", "Alternatieve warmtebron aktief", "Alternativ Värmekälla aktiv", "aktywne alternatywne źródło ciepła", "alternativ varmekilde aktiv", "chauffage alternatif actif", "alternatif ısınma devrede", "riscaldamento alternativo attivo") -MAKE_TRANSLATION(aPump, "apump", "alternative hs pump", "Alternativer WE Pumpe", "Alternatieve warmtebron pomp", "Alternativ Pump Värmekälla", "pompy alternatywnego źródła ciepła", "alternativ pumpe varmekilde", "alternative pompe hs", "alternatif ısı kaynağı pompası", "pompa alternativa hs") -MAKE_TRANSLATION(burner, "burner", "burner", "Brenner", "Brander", "", "palnik", "", "", "kazan", "bruciatore") // TODO translate -MAKE_TRANSLATION(heatRequest, "heatrequest", "heat request", "Wärmeanforderung", "Warmtevraag", "", "zapotrzebowanie na ciepło", "varmeforespørsel", "", "ısı talebi", "richiesta calore") // TODO translate -MAKE_TRANSLATION(blockRemain, "blockremain", "remaining blocktime", "verbleibende Blockzeit", "Resterende bloktijd", "", "czas do końca blokady", "gjenstående blokkeringstid", "", "kalan blok süresi", "tempo di blocco rimanente") // TODO translate -MAKE_TRANSLATION(blockRemainWw, "blockremainww", "remaining blocktime dhw", "verbleibende Blockzeit WW", "Resterende bloktijd ww", "", "czas do końca blokady c.w.u.", "gjenværende blokkeringstid bereder", "", "kalan sıcak kullanım suyu blok süresi", "tempo di blocco rimanente ACS") // TODO translate -MAKE_TRANSLATION(flueGasTemp, "fluegastemp", "flue gas temperature", "Abgastemperatur", "Rookafvoertemperatuur", "", "temperatura spalin", "røykgasstemperatur", "", "baca gazı sıcaklığı", "temperatura gas di scarico") // TODO translate +MAKE_TRANSLATION(aCylTopTemp, "cyltoptemp", "cylinder top temperature", "Speichertemperatur Oben", "Buffer temperatuur boven", "Cylindertemperatur Toppen", "temperatura na górze cylindra", "beredertemperatur topp", "température haut cylindre", "silindir üst yüzey sıcaklığı", "temperatura superiore accumulo", "vrchná teplota valca") +MAKE_TRANSLATION(aCylCenterTemp, "cylcentertemp", "cylinder center temperature", "Speichertemperatur Mitte", "Buffer temperatuur midden", "Cylindertemperatur Mitten", "temperatura na środku cylindra", "beredertemperatur midten", "température centre cylindre", "silindir merkez sıcaklığı", "temperatura centrale accumulo", "stredná teplota valca") +MAKE_TRANSLATION(aCylBottomTemp, "cylbottomtemp", "cylinder bottom temperature", "Speichertemperatur Unten", "Buffer temperatuur onder", "Cylindertemperatur Botten", "temperatura na dole cylindra", "beredertemperatur nederst", "température fond cylindre", "silindir taban sıcaklığı", "temperatura inferiore accumulo", "teplota dna valca") +MAKE_TRANSLATION(aFlowTemp, "altflowtemp", "alternative hs flow temperature", "Alternativer WE Vorlauftemperatur", "Alternatieve warmtebron aanvoertemperatuur", "Alternativ flödestemp värmekälla", "temperatura zasilania z alternatywnego źródła", "alternativ varmekilde tilførselstemperatur", "température flux hs alternative", "alternatif ısı kaynağı besleme sıcaklığı", "temperatura alternativa mandata hs", "alternatívna výstupná teplota hs") +MAKE_TRANSLATION(aRetTemp, "altrettemp", "alternative hs return temperature", "Alternativer WE Rücklauftemperatur", "Alternatieve warmtebron retourtemperatuur", "Alternativ returtemp värmekälla", "temperatura powrotu z alternatywnego źródła", "alternativ varmekilde returtemperatur", "température retour hs alternative", "alternatif ısı kaynağı dönüş sıcaklığı", "temperatura alternativa ritorno hs", "alternatívna teplota spiatočky hs") +MAKE_TRANSLATION(sysFlowTemp, "sysflowtemp", "system flow temperature", "System Vorlauftemperatur", "Systeem aanvoertemperatuur", "Systemflödestemperatur", "temperatura zasilania systemu", "systemturtemperatur", "température flux système", "sistem besleme sıcaklığı", "temperatura di mandata impianto", "teplota prívodu systému") +MAKE_TRANSLATION(sysRetTemp, "sysrettemp", "system return temperature", "System Rücklauftemperatur", "Systeem retourtemperatuur", "Systemreturtemperatur", "temperatura powrotu z systemu", "systemreturtemperatur", "température retour système", "sistem dönüş sıcaklığı", "temperatura di ritorno impianto", "teplota spiatočky systému") +MAKE_TRANSLATION(valveByPass, "valvebypass", "bypass valve", "Bypass-Ventil", "Bypass klep", "Bypassventil", "zawór obejścia", "bypassventil", "vanne dérivation", "baypas vanası", "valvola Bypass", "obtokový ventil") +MAKE_TRANSLATION(valveBuffer, "valvebuffer", "buffer valve", "Puffer-Ventil", "Bufferklep", "Buffertventil", "zawór bufora", "buffertventil", "vanne tampon", "tampon vanası", "valvola tampone", "nárazový ventil") +MAKE_TRANSLATION(valveReturn, "valvereturn", "return valve", "Rückfluss-Ventil", "Retourklep", "Returventil", "zawór powrotu", "returventil", "vanne retour", "dönüş vanası", "valvola ritorno", "spätný ventil") +MAKE_TRANSLATION(aPumpMod, "apumpmod", "alternative hs pump modulation", "Alternativer WE Pumpenmodulation", "Alternatieve warmtebron pomp modulatie", "Alternativ Pumpmodulering Värmekälla", "modulacja pompy alternatywnego źródła ciepła", "alternativ pumpemodulering varmekilde", "modulation alternative pompe hs", "alternatif ısı kaynağı pompa modülasyonu", "pompa modulazione alternativa hs", "alternatívna modulácia čerpadla hs") +MAKE_TRANSLATION(heatSource, "heatsource", "alternative heating active", "Alternativer Wärmeerzeuger aktiv", "Alternatieve warmtebron aktief", "Alternativ Värmekälla aktiv", "aktywne alternatywne źródło ciepła", "alternativ varmekilde aktiv", "chauffage alternatif actif", "alternatif ısınma devrede", "riscaldamento alternativo attivo", "alternatívne kúrenie aktívne") +MAKE_TRANSLATION(aPump, "apump", "alternative hs pump", "Alternativer WE Pumpe", "Alternatieve warmtebron pomp", "Alternativ Pump Värmekälla", "pompy alternatywnego źródła ciepła", "alternativ pumpe varmekilde", "alternative pompe hs", "alternatif ısı kaynağı pompası", "pompa alternativa hs", "alternatívne čerpadlo hs") +MAKE_TRANSLATION(burner, "burner", "burner", "Brenner", "Brander", "", "palnik", "", "", "kazan", "bruciatore", "horák") // TODO translate +MAKE_TRANSLATION(heatRequest, "heatrequest", "heat request", "Wärmeanforderung", "Warmtevraag", "", "zapotrzebowanie na ciepło", "varmeforespørsel", "", "ısı talebi", "richiesta calore", "požiadavka na teplo") // TODO translate +MAKE_TRANSLATION(blockRemain, "blockremain", "remaining blocktime", "verbleibende Blockzeit", "Resterende bloktijd", "", "czas do końca blokady", "gjenstående blokkeringstid", "", "kalan blok süresi", "tempo di blocco rimanente", "zostávajúci čas blokovania") // TODO translate +MAKE_TRANSLATION(blockRemainWw, "blockremainww", "remaining blocktime dhw", "verbleibende Blockzeit WW", "Resterende bloktijd ww", "", "czas do końca blokady c.w.u.", "gjenværende blokkeringstid bereder", "", "kalan sıcak kullanım suyu blok süresi", "tempo di blocco rimanente ACS", "zostávajúci čas blokovania TÚV") // TODO translate +MAKE_TRANSLATION(flueGasTemp, "fluegastemp", "flue gas temperature", "Abgastemperatur", "Rookafvoertemperatuur", "", "temperatura spalin", "røykgasstemperatur", "", "baca gazı sıcaklığı", "temperatura gas di scarico", "teplota spalín") // TODO translate -MAKE_TRANSLATION(vr2Config, "vr2config", "vr2 configuration", "VR2 Konfiguration", "VR2 configuratie", "VR2 Konfiguration", "konfiguracja VR2", "vr2 konfigurasjon", "configuration vr2", "vr2 ayarı", "configurazione VR2") -MAKE_TRANSLATION(ahsActivated, "ahsactivated", "alternate heat source activation", "Alt. Wärmeerzeuger aktiviert", "Altenatieve warmtebron geactiveerd", "Alternativ värmekälla aktivering", "aktywacja alternatywnego źródła ciepła", "alternativ varmekilde aktivering", "activation source chaleur alternative", "alternatif ısı kaynağı devrede", "attivazione fonte di calore alternativa") -MAKE_TRANSLATION(aPumpConfig, "apumpconfig", "primary pump config", "Konfig. Hauptpumpe", "Primaire pomp configuratie", "Konfiguration Primärpump", "konfiguracja pompy głównej", "konfiguration primærpumpe", "configuration pompe primaire", "ana pompa ayarı", "configurazione pompa primaria") -MAKE_TRANSLATION(aPumpSignal, "apumpsignal", "output for pr1 pump", "Ausgang Pumpe PR1", "Output voor pomp PR1", "Utgång från pump PR1", "wyjście pompy PR1", "utgang fra pumpe PR1", "sortie pompe pr1", "p1 pompa çıkışı", "uscita per pompa PR1") -MAKE_TRANSLATION(aPumpMin, "apumpmin", "min output pump pr1", "Minimale Pumpenansteuerung", "Minimale output pomp PR1", "Min Output Pump PR1", "minimalne wysterowanie pompy PR1", "minimal output pumpe PR1", "sortie min pompe pr1", "p1 pompa minimum çıkış", "uscita minima pompa PR1") -MAKE_TRANSLATION(tempRise, "temprise", "ahs return temp rise", "Rücklauf Temperaturerhöhung", "Verhoging retourtemperatuur", "Förhöjd returtemperatur", "wzrost temperatury powrotu", "forhøyd returtemperatur", "augmentation température retour ahs", "alternatif ısı kaynağı dönüş sıcaklığı yükseldi", "aumento della temperatura di ritorno") -MAKE_TRANSLATION(setReturnTemp, "setreturntemp", "set temp return", "Soll-Rücklauftemperatur", "Streeftemperatuur retour", "Vald returtemperatur", "zadana temperatura powrotu", "valgt returtemperatur", "régler température retour", "hedef dönüş sıcaklığı", "imposta temperatura di ritorno") -MAKE_TRANSLATION(mixRuntime, "mixruntime", "mixer run time", "Mischer-Laufzeit", "Mixer looptijd", "Blandningsventil drifttid", "czas pracy miksera", "blandingsventil drifttid", "durée fonctionnement mélangeur", "karışım çalışma süresi", "tempo di funzionamento del miscelatore") -MAKE_TRANSLATION(bufBypass, "bufbypass", "buffer bypass config", "Puffer-Bypass Konfig.", "Buffer bypass configuratie", "Konfiguration Buffer bypass", "konfiguracja z obejściem bufora", "konfigurasjon buffer bypass", "configuration contournement buffer", "tampon baypas ayarı", "configurazione bypass del tampone ") -MAKE_TRANSLATION(bufMixRuntime, "bufmixruntime", "bypass mixer run time", "Speicher-Mischer-Laufzeit", "Buffer mixer looptijd", "Blandningsventil Bypass drifttid", "czas pracy mieszacza obejścia", "blandningsventil bypass drifttid", "durée fonctionnement contournement mélangeur", "baypas karıştırıcı çalışma süresi", "tempo funzionamento bypass miscelatore") -MAKE_TRANSLATION(bufConfig, "bufconfig", "dhw buffer config", "Konfig. Warmwasserspeicher", "Warmwater boiler configuratie", "Konfiguration Varmvattentank", "konfiguracja bufora c.w.u.", "konfigurasjon varmvannstank", "configuration buffer ecs", "sıcak su tampon ayarı", "configurazione tampone ACS") -MAKE_TRANSLATION(blockMode, "blockmode", "config htg. blocking mode", "Konfig. Sperr-Modus", "Configuratie blokeermodus", "Konfiguration Blockeringsläge", "konfiguracja trybu blokady", "konfigurasjon blokkeringsmodus", "config mode blocage htg.", "blok modu yapılandırması", "configurazione modalità di blocco") -MAKE_TRANSLATION(blockTerm, "blockterm", "config of block terminal", "Konfig. Sperrterminal", "Configuratie blookerterminal", "Konfiguration Blockeringsterminal", "konfiguracja terminala blokującego", "konfigurasjon blokkeringsterminal", "config. du bloque terminal", "blok terminal yapılandırması", "configurazione terminale di blocco") -MAKE_TRANSLATION(blockHyst, "blockhyst", "hyst. for boiler block", "Hysterese Sperrmodus", "Hysterese blokeerterminal", "Hysteres Blockeringsmodul", "tryb blokowania histerezy", "hystrese blokkeringsmodus", "hyst. Blocage chaudière", "kazan blok geçikmesi", "modalità blocco isteresi") -MAKE_TRANSLATION(releaseWait, "releasewait", "boiler release wait time", "Wartezeit Kessel-Freigabe", "Wachttijd ketel vrijgave", "Väntetid Frisläppning", "czas oczekiwania na zwolnienie kotła", "kjele frigjøringsventetid", "temps attente libération chaudière", "kazan tahliyesi bekleme süresi", "tempo di attesa sblocco caldaia") +MAKE_TRANSLATION(vr2Config, "vr2config", "vr2 configuration", "VR2 Konfiguration", "VR2 configuratie", "VR2 Konfiguration", "konfiguracja VR2", "vr2 konfigurasjon", "configuration vr2", "vr2 ayarı", "configurazione VR2", "konfigurácia vr2") +MAKE_TRANSLATION(ahsActivated, "ahsactivated", "alternate heat source activation", "Alt. Wärmeerzeuger aktiviert", "Altenatieve warmtebron geactiveerd", "Alternativ värmekälla aktivering", "aktywacja alternatywnego źródła ciepła", "alternativ varmekilde aktivering", "activation source chaleur alternative", "alternatif ısı kaynağı devrede", "attivazione fonte di calore alternativa", "aktivácia alternatívneho zdroja tepla") +MAKE_TRANSLATION(aPumpConfig, "apumpconfig", "primary pump config", "Konfig. Hauptpumpe", "Primaire pomp configuratie", "Konfiguration Primärpump", "konfiguracja pompy głównej", "konfiguration primærpumpe", "configuration pompe primaire", "ana pompa ayarı", "configurazione pompa primaria", "konfigurácia primárneho čerpadla") +MAKE_TRANSLATION(aPumpSignal, "apumpsignal", "output for pr1 pump", "Ausgang Pumpe PR1", "Output voor pomp PR1", "Utgång från pump PR1", "wyjście pompy PR1", "utgang fra pumpe PR1", "sortie pompe pr1", "p1 pompa çıkışı", "uscita per pompa PR1", "výstup pre čerpadlo pr1") +MAKE_TRANSLATION(aPumpMin, "apumpmin", "min output pump pr1", "Minimale Pumpenansteuerung", "Minimale output pomp PR1", "Min Output Pump PR1", "minimalne wysterowanie pompy PR1", "minimal output pumpe PR1", "sortie min pompe pr1", "p1 pompa minimum çıkış", "uscita minima pompa PR1", "min. výstupné čerpadlo pr1") +MAKE_TRANSLATION(tempRise, "temprise", "ahs return temp rise", "Rücklauf Temperaturerhöhung", "Verhoging retourtemperatuur", "Förhöjd returtemperatur", "wzrost temperatury powrotu", "forhøyd returtemperatur", "augmentation température retour ahs", "alternatif ısı kaynağı dönüş sıcaklığı yükseldi", "aumento della temperatura di ritorno", "Zvýšenie teploty spiatočky") +MAKE_TRANSLATION(setReturnTemp, "setreturntemp", "set temp return", "Soll-Rücklauftemperatur", "Streeftemperatuur retour", "Vald returtemperatur", "zadana temperatura powrotu", "valgt returtemperatur", "régler température retour", "hedef dönüş sıcaklığı", "imposta temperatura di ritorno", "cieľová teplota spiatočky") +MAKE_TRANSLATION(mixRuntime, "mixruntime", "mixer run time", "Mischer-Laufzeit", "Mixer looptijd", "Blandningsventil drifttid", "czas pracy miksera", "blandingsventil drifttid", "durée fonctionnement mélangeur", "karışım çalışma süresi", "tempo di funzionamento del miscelatore", "doba chodu mixéra") +MAKE_TRANSLATION(bufBypass, "bufbypass", "buffer bypass config", "Puffer-Bypass Konfig.", "Buffer bypass configuratie", "Konfiguration Buffer bypass", "konfiguracja z obejściem bufora", "konfigurasjon buffer bypass", "configuration contournement buffer", "tampon baypas ayarı", "configurazione bypass del tampone", "konfigurácia vynechania vyrovnávacej pamäte") +MAKE_TRANSLATION(bufMixRuntime, "bufmixruntime", "bypass mixer run time", "Speicher-Mischer-Laufzeit", "Buffer mixer looptijd", "Blandningsventil Bypass drifttid", "czas pracy mieszacza obejścia", "blandningsventil bypass drifttid", "durée fonctionnement contournement mélangeur", "baypas karıştırıcı çalışma süresi", "tempo funzionamento bypass miscelatore", "doba chodu obtokového mixéra") +MAKE_TRANSLATION(bufConfig, "bufconfig", "dhw buffer config", "Konfig. Warmwasserspeicher", "Warmwater boiler configuratie", "Konfiguration Varmvattentank", "konfiguracja bufora c.w.u.", "konfigurasjon varmvannstank", "configuration buffer ecs", "sıcak su tampon ayarı", "configurazione tampone ACS", "konfigurácia zásobníka TÚV") +MAKE_TRANSLATION(blockMode, "blockmode", "config htg. blocking mode", "Konfig. Sperr-Modus", "Configuratie blokeermodus", "Konfiguration Blockeringsläge", "konfiguracja trybu blokady", "konfigurasjon blokkeringsmodus", "config mode blocage htg.", "blok modu yapılandırması", "configurazione modalità di blocco", "Režim uzamknutia konfigurácie") +MAKE_TRANSLATION(blockTerm, "blockterm", "config of block terminal", "Konfig. Sperrterminal", "Configuratie blookerterminal", "Konfiguration Blockeringsterminal", "konfiguracja terminala blokującego", "konfigurasjon blokkeringsterminal", "config. du bloque terminal", "blok terminal yapılandırması", "configurazione terminale di blocco", "Konfiguračný blokovací terminál") +MAKE_TRANSLATION(blockHyst, "blockhyst", "hyst. for boiler block", "Hysterese Sperrmodus", "Hysterese blokeerterminal", "Hysteres Blockeringsmodul", "tryb blokowania histerezy", "hystrese blokkeringsmodus", "hyst. Blocage chaudière", "kazan blok geçikmesi", "modalità blocco isteresi", "Režim hysterézneho zámku") +MAKE_TRANSLATION(releaseWait, "releasewait", "boiler release wait time", "Wartezeit Kessel-Freigabe", "Wachttijd ketel vrijgave", "Väntetid Frisläppning", "czas oczekiwania na zwolnienie kotła", "kjele frigjøringsventetid", "temps attente libération chaudière", "kazan tahliyesi bekleme süresi", "tempo di attesa sblocco caldaia", "doba čakania na uvoľnenie kotla") // energy -MAKE_TRANSLATION(nrgTotal, "nrgtotal", "total energy", "Energie gesamt", "", "", "całkowita energia", "", "", "", "") // TODO translate -MAKE_TRANSLATION(nrgHeat, "nrgheat", "energy heating", "Energie Heizen", "", "", "energia grzania", "", "", "ısıtma enerjisi", "") // TODO translate -MAKE_TRANSLATION(nrgWw, "nrgww", "energy", "Energie", "", "", "energia", "", "", "sıcak kullanım suyu enerjisi", "") // TODO translate -MAKE_TRANSLATION(nomPower, "nompower", "nominal Power", "Brennerleistung", "", "", "moc nominalna", "", "", "nominal güç", "") // TODO translate -MAKE_TRANSLATION(meterTotal, "metertotal", "meter total", "Messung gesamt", "", "", "licznik całkowity", "", "", "", "") // TODO translate -MAKE_TRANSLATION(meterComp, "metercomp", "meter compressor", "Messung Kompressor", "", "", "licznik sprężarki", "", "", "", "") // TODO translate -MAKE_TRANSLATION(meterEHeat, "metereheat", "meter e-heater", "Messung E-Heizer", "", "", "licznik e-heater", "", "", "", "") // TODO translate -MAKE_TRANSLATION(meterHeat, "meterheat", "meter heating", "Messung Heizen", "", "", "licznik grzania", "", "", "", "") // TODO translate +MAKE_TRANSLATION(nrgTotal, "nrgtotal", "total energy", "Energie gesamt", "", "", "całkowita energia", "", "", "", "", "celková energia") // TODO translate +MAKE_TRANSLATION(nrgHeat, "nrgheat", "energy heating", "Energie Heizen", "", "", "energia na ogrzewanie", "", "", "ısıtma enerjisi", "", "energetické vykurovanie") // TODO translate +MAKE_TRANSLATION(nrgWw, "nrgww", "energy", "Energie", "", "", "energia na c.w.u.", "", "", "sıcak kullanım suyu enerjisi", "", "energia") // TODO translate +MAKE_TRANSLATION(nomPower, "nompower", "nominal Power", "Brennerleistung", "", "", "moc nominalna", "", "", "nominal güç", "", "nominálny výkon") // TODO translate +MAKE_TRANSLATION(meterTotal, "metertotal", "meter total", "Messung gesamt", "", "", "licznik całkowity", "", "", "", "", "meter celkom") // TODO translate +MAKE_TRANSLATION(meterComp, "metercomp", "meter compressor", "Messung Kompressor", "", "", "licznik sprężarki", "", "", "", "", "meter kompresor") // TODO translate +MAKE_TRANSLATION(meterEHeat, "metereheat", "meter e-heater", "Messung E-Heizer", "", "", "licznik dogrzewacza", "", "", "", "", "elektrický ohrievač") // TODO translate +MAKE_TRANSLATION(meterHeat, "meterheat", "meter heating", "Messung Heizen", "", "", "licznik ogrzewania", "", "", "", "", "") // TODO translate // HIU -MAKE_TRANSLATION(netFlowTemp, "netflowtemp", "heat network flow temp", "System Vorlauftemperatur", "Netto aanvoertemperatuur", "", "temp. zasilania sieci cieplnej", "", "", "ısıtma şebekesi akış derecesi", "temperatura di mandata della rete di riscaldamento") // TODO translate -// MAKE_TRANSLATION(cwFlowRate, "cwflowrate", "cold water flow rate", "Kaltwasser Durchfluss", "Stroomsnelheid koud water ", "", "przepływ zimnej wody", "", "", "soğuk su akış hızı", "portata acqua fredda") // TODO translate -MAKE_TRANSLATION(keepWarmTemp, "keepwarmtemp", "keep warm temperature","Warmhaltetemperatur", "Warmhoudtemperatuur", "", "", "temperatura utrzymania ciepłej wody", "", "sıcaklığı koruma derecesi", "mantenere la temperatura calda") // TODO translate -MAKE_TRANSLATION(heatValve, "heatvalve", "heating valve", "Ventil Heizen", "", "", "zawór grzeczy", "", "", "", "") // TODO translate -MAKE_TRANSLATION(wwValve, "wwvalve", "valve", "Ventil", "", "", "zawór", "", "", "", "") // TODO translate +MAKE_TRANSLATION(netFlowTemp, "netflowtemp", "heat network flow temp", "System Vorlauftemperatur", "Netto aanvoertemperatuur", "", "temp. zasilania sieci cieplnej", "", "", "ısıtma şebekesi akış derecesi", "temperatura di mandata della rete di riscaldamento", "teplota prívodu tepelnej siete") // TODO translate +// MAKE_TRANSLATION(cwFlowRate, "cwflowrate", "cold water flow rate", "Kaltwasser Durchfluss", "Stroomsnelheid koud water", "", "przepływ zimnej wody", "", "", "soğuk su akış hızı", "portata acqua fredda", "prietok studenej vody") // TODO translate +MAKE_TRANSLATION(keepWarmTemp, "keepwarmtemp", "keep warm temperature", "Warmhaltetemperatur", "Warmhoudtemperatuur", "", "", "temperatura podtrzymywania ciepła", "", "sıcaklığı koruma derecesi", "mantenere la temperatura calda", "udržať teplú teplotu") // TODO translate +MAKE_TRANSLATION(heatValve, "heatvalve", "heating valve", "Ventil Heizen", "", "", "zawór ogrzewania", "", "", "", "", "vykurovací ventil") // TODO translate +MAKE_TRANSLATION(wwValve, "wwvalve", "valve", "Ventil", "", "", "zawór", "", "", "", "", "ventil") // TODO translate // the following are dhw for the boiler and automatically tagged with 'dhw' -MAKE_TRANSLATION(wwSelTemp, "wwseltemp", "selected temperature", "gewählte Temperatur", "Geselecteerd temperatuur", "Vald Temperatur", "temperatura wyższa/komfort", "valgt temperatur", "température sélectionnée", "seçili sıcaklık", "temperatura selezionata") -MAKE_TRANSLATION(wwSelTempLow, "wwseltemplow", "selected lower temperature", "untere Solltemperatur", "Onderste streeftemperatuur", "Vald lägstatemperatur", "temperatura niższa/eko", "valgt nedre temperatur", "température basse sélectionnée", "seçili düşük sıcaklık", "bassa temperatura selezionata") -MAKE_TRANSLATION(wwSelTempEco, "wwtempecoplus", "selected eco+ temperature", "ECO+ Solltemperatur", "eco+ streeftemperatuur", "eco+ lägstatemperatur", "temperatura niższa/eko+", "valgt eco+ temperatur", "température eco+ sélectionnée", "seçili eco+ sıcaklık", "eco+ temperatura selezionata") -MAKE_TRANSLATION(wwSelTempOff, "wwseltempoff", "selected temperature for off", "Solltemperatur bei AUS", "Streeftemperatuur bij UIT", "Vald tempereatur för AV", "temperatura gdy grzanie wyłączone", "valgt tempereatur for av", "température sélectionnée pour arrêt", "kapanma için seçili sıcaklık", "temperatura selezionata per spegnimento") -MAKE_TRANSLATION(wwSelTempSingle, "wwseltempsingle", "single charge temperature", "Solltemperatur Einmalladung", "Streeftemperatuur enkele lading", "Temperatur Engångsladdning", "temperatura dodatkowej ciepłej wody", "temp engangsoppvarming", "température charge unique", "tek şarj sıcaklığı", "temperatura singolaa carica") -MAKE_TRANSLATION(wwCylMiddleTemp, "wwcylmiddletemp", "cylinder middle temperature (TS3)", "Speichertemperatur Mitte", "Buffer temperatuur midden", "Cylinder Temperatur Mitten (TS3)", "temperatura środka cylindra (TS3)", "vanntank midten temperatur (TS3)", "température moyenne ballon (TS3)", "Silindir orta sıcaklığı", "temperatura centrale accumulo (TS3)") -MAKE_TRANSLATION(wwSetTemp, "wwsettemp", "set temperature", "Solltemperatur", "Streeftemperatuut", "Börtempertur", "temperatura zadana", "innstilt temperatur", "régler température", "hedef sıcaklık", "imposta temperatura") -MAKE_TRANSLATION(wwType, "wwtype", "type", "Typ", "type", "Typ", "typ", "type", "type", "tip", "tipo") -MAKE_TRANSLATION(wwComfort, "wwcomfort", "comfort", "Komfort", "Comfort", "Komfort", "komfort", "komfort", "confort", "konfor", "Comfort") -MAKE_TRANSLATION(wwComfort1, "wwcomfort1", "comfort mode", "Komfort-Modus", "Comfort modus", "Komfortläge", "tryb komfortu", "komfort modus", "mode confort", "konfor modu", "modalità comfort") -MAKE_TRANSLATION(wwFlowTempOffset, "wwflowtempoffset", "flow temperature offset", "Vorlauftemperaturanhebung", "Aanvoertemperatuur offset", "Flödestemperatur förskjutning", "korekta temperatury wypływu", "turtemperaturforskyvning", "offset température flux", "akış sıcaklığı artışı", "aumento della temperatura di ritorno") -MAKE_TRANSLATION(wwMaxPower, "wwmaxpower", "max power", "max Leistung", "Maximaal vermogen", "Max Effekt", "moc maksymalna", "maks effekt", "puissance max", "maksimum güç", "potenza massima") -MAKE_TRANSLATION(wwCircPump, "wwcircpump", "circulation pump available", "Zirkulationspumpe vorhanden", "Circulatiepomp aanwezig", "Cirkulationspump tillgänglig", "pompa cyrkulacji zainstalowana", "sirkulasjonspumpe tilgjengelig", "pompe circulation disponible", "sikülasyon pompası müsait", "pompa circolazione disponibile") -MAKE_TRANSLATION(wwChargeType, "wwchargetype", "charging type", "Speicher-Ladungstyp", "Buffer laadtype", "Laddningstyp", "sposób grzania zasobnika", "varmetype", "type chargement", "şarj tipi", "tipo caricamento") -MAKE_TRANSLATION(wwDisinfectionTemp, "wwdisinfectiontemp", "disinfection temperature", "Desinfektionstemperatur", "Desinfectietemperatuur", "Desinfektionstemperatur", "temperatura dezynfekcji termicznej", "desinfeksjonstemperatur", "température désinfection", "dezenfeksiyon sıcaklığı", "temperatura disinfezione") -MAKE_TRANSLATION(wwCircMode, "wwcircmode", "circulation pump mode", "Zirkulationspumpen-Modus", "Modus circulatiepomp", "Läge Cirkulationspump", "tryb pracy cyrkulacji", "sikulasjonspumpemodus", "mode pompe circulation", "sirkülasyon pompa modu", "modalità pompa circolazione") -MAKE_TRANSLATION(wwCirc, "wwcirc", "circulation active", "Zirkulation aktiv", "Circulatiepomp actief", "Cirkulation aktiv", "pompa cyrkulacji", "sirkulasjon aktiv", "circulation active", "sirkülasyon devrede", "circolazione attiva") -MAKE_TRANSLATION(wwCurTemp, "wwcurtemp", "current intern temperature", "aktuelle interne Temperatur", "Huidige interne temperatuur", "Intern Temperatur", "temperatura zasobnika", "gjeldende intern temperatur", "température interne actuelle", "güncel iç sıcaklık", "temperatura interna attuale") -MAKE_TRANSLATION(wwCurTemp2, "wwcurtemp2", "current extern temperature", "aktuelle externe Temperatur", "Huidige externe temperatuur", "Extern Temperatur", "temperatura wypływu", "gjeldende ekstern temperaur", "température externe actuelle", "güncel dış sıcaklık", "temperatura esterna attuale") -MAKE_TRANSLATION(wwCurFlow, "wwcurflow", "current tap water flow", "aktueller Durchfluss", "Hudige warmwater doorstroming", "Aktuellt tappvattenflöde", "aktualny przepływ", "gjeldende tappevannshastighet", "débit actuel eau robinet", "güncel musluk suyu akışı", "portata corrente dell'acqua del rubinetto") -MAKE_TRANSLATION(wwStorageTemp1, "wwstoragetemp1", "storage intern temperature", "interne Speichertemperatur", "Interne buffertemperatuur", "Beredare Intern Temperatur", "temperatura wewnątrz zasobnika", "intern temperatur bereder", "température interne stockage", "depo iç sıcaklığı", "temperatura di conservazione interna") -MAKE_TRANSLATION(wwStorageTemp2, "wwstoragetemp2", "storage extern temperature", "externer Speichertemperatur", "Externe buffertemperatuur", "Beredare Extern Tempereatur", "temperatura na wyjściu zasobnika", "ekstern temperatur bereder", "température externe stockage", "depo dış sıcaklığı", "temperatura di conservazione esterna") -MAKE_TRANSLATION(wwActivated, "wwactivated", "activated", "aktiviert", "geactiveerd", "Aktiverad", "system przygotowywania c.w.u.", "aktivert", "activé", "devreye girdi", "attivato") -MAKE_TRANSLATION(wwOneTime, "wwonetime", "one time charging", "Einmalladung", "Buffer eenmalig laden", "Engångsladdning", "jednorazowa dodatkowa ciepła woda", "engangsoppvarming", "charge unique", "tek seferlik doldurma", "carica singola") -MAKE_TRANSLATION(wwDisinfecting, "wwdisinfecting", "disinfecting", "Desinfizieren", "Desinfectie", "Desinficerar", "dezynfekcja termiczna", "desinfiserer", "désinfection", "dezenfekte ediliyor", "disinfezione") -MAKE_TRANSLATION(wwCharging, "wwcharging", "charging", "Laden", "Laden", "Värmer", "grzanie", "varmer", "chargement", "dolduruluyor", "caricamento") -MAKE_TRANSLATION(wwChargeOptimization, "wwchargeoptimization", "charge optimization", "Ladungsoptimierung", "laadoptimalisatie", "Laddningsoptimering", "optymalizacja grzania", "oppvarmingsoptimalisering", "optimisation charge", "dolum optimizasyonu", "ottimizzazione carica") -MAKE_TRANSLATION(wwRecharging, "wwrecharging", "recharging", "Nachladen", "herladen", "Laddar om", "ponowne grzanie", "varm på nytt", "en recharge", "tekrar dolduruluyor", "in ricarica") -MAKE_TRANSLATION(wwTempOK, "wwtempok", "temperature ok", "Temperatur ok", "Temperatuur OK", "Temperatur OK", "temperatura OK", "temperatur ok!", "température ok", "sıcaklık tamam", "Temperatura OK") -MAKE_TRANSLATION(wwActive, "wwactive", "active", "aktiv", "Actief", "Aktiv", "aktywna", "aktiv", "actif", "devrede", "attivo") -MAKE_TRANSLATION(ww3wayValve, "ww3wayvalve", "3-way valve active", "3-Wegeventil aktiv", "3-wegklep actief", "Trevägsventil aktiv", "zawór 3-drogowy aktywny", "aktiv trevisventil", "vanne 3 voies active", "3 yollu vana", "valvola 3-vie") -MAKE_TRANSLATION(wwSetPumpPower, "wwsetpumppower", "set pump power", "Soll Pumpenleistung", "Streefwaarde pompvermogen", "Vald pumpeffekt", "ustawione wysterowanie pompy", "valgt pumpeeffekt", "régler puissance pompe", "ayarlı pompa gücü", "imposta potenza pompa") -MAKE_TRANSLATION(wwMixerTemp, "wwmixertemp", "mixer temperature", "Mischertemperatur", "Mixertemperatuur", "Blandningsventil-tempertur", "temperatura mieszacza", "temperatur blandeventil", "température mélangeur", "karıştırıcı sıcaklığı", "temperatura miscelatore") -MAKE_TRANSLATION(wwStarts, "wwstarts", "starts", "Anzahl Starts", "Aantal starts", "Antal starter", "liczba załączeń", "antall starter", "démarrages", "başlıyor", "avvii") -MAKE_TRANSLATION(wwStarts2, "wwstarts2", "control starts2", "Kreis 2 Anzahl Starts", "Aantal starts circuit 2", "Antal starter Krets 2", "liczba załączeń 2", "antall starter krets 2", "démarrages contrôle 2", "devre 2 başlıyor", "avvii controllati 2") -MAKE_TRANSLATION(wwWorkM, "wwworkm", "active time", "aktive Zeit", "Actieve tijd", "Aktiv Tid", "czas aktywności", "driftstid", "temps actif", "aktif zaman", "tempo attivo") -MAKE_TRANSLATION(wwHystOn, "wwhyston", "hysteresis on temperature", "Einschalttemperaturdifferenz", "Inschakeltemperatuurverschil", "Hysteres PÅ-temperatur", "histereza załączania", "innkoblingstemperaturforskjell", "hystérésis température allumage", "çalışma sıcaklığı farkı", "differenza di temperatura di accensione") -MAKE_TRANSLATION(wwHystOff, "wwhystoff", "hysteresis off temperature", "Ausschalttemperaturdifferenz", "Uitschakeltemperatuurverschil", "Hysteres AV-temperatur", "histereza wyłączania", "utkoblingstemperaturforskjell", "hystérésis température extinction", "kapatma sıcaklığı farkı", "differenza di temperatura di spegnimento") -MAKE_TRANSLATION(wwProgMode, "wwprogmode", "program", "Programmmodus", "Programma", "Program", "program", "program", "programme", "program", "Programma") -MAKE_TRANSLATION(wwCircProg, "wwcircprog", "circulation program", "Zirkulationsprogramm", "Circulatieprogramma", "Cirkulationsprogram", "program cyrkulacji c.w.u.", "sirkulationsprogram", "programme circulation", "sirkülasyon programı", "programma circolazione") -MAKE_TRANSLATION(wwMaxTemp, "wwmaxtemp", "maximum temperature", "Maximale Temperatur", "Maximale temperatuur", "Maximal Temperatur", "temperatura maksymalna", "maksimal temperatur", "température max", "maksimum sıcaklık", "temperatura massima") -MAKE_TRANSLATION(wwOneTimeKey, "wwonetimekey", "one time key function", "Einmalladungstaste", "Knop voor eenmalig laden buffer", "Engångsfunktion", "przycisk jednorazowego ogrzania", "engangsknapp varme", "fonction touche unique", "tek seferlik doldurma fonksiyonu", "pulsante funzione singola") -MAKE_TRANSLATION(wwSolarTemp, "wwsolartemp", "solar boiler temperature", "Solarboiler Temperatur", "Zonneboiler temperatuur", "Solpanel Temp", "temperatura zasobnika solarnego", "solpaneltemp", "température chaudière solaire", "güneş enerjisi kazan sıcaklığı", "temperatura pannello solare") +MAKE_TRANSLATION(wwSelTemp, "wwseltemp", "selected temperature", "gewählte Temperatur", "Geselecteerd temperatuur", "Vald Temperatur", "temperatura wyższa/komfort", "valgt temperatur", "température sélectionnée", "seçili sıcaklık", "temperatura selezionata", "zvolená teplota") +MAKE_TRANSLATION(wwSelTempLow, "wwseltemplow", "selected lower temperature", "untere Solltemperatur", "Onderste streeftemperatuur", "Vald lägstatemperatur", "temperatura niższa/eko", "valgt nedre temperatur", "température basse sélectionnée", "seçili düşük sıcaklık", "bassa temperatura selezionata", "zvolená nižšia teplota") +MAKE_TRANSLATION(wwSelTempEco, "wwtempecoplus", "selected eco+ temperature", "ECO+ Solltemperatur", "eco+ streeftemperatuur", "eco+ lägstatemperatur", "temperatura niższa/eko+", "valgt eco+ temperatur", "température eco+ sélectionnée", "seçili eco+ sıcaklık", "eco+ temperatura selezionata", "zvolená teplota eco+") +MAKE_TRANSLATION(wwSelTempOff, "wwseltempoff", "selected temperature for off", "Solltemperatur bei AUS", "Streeftemperatuur bij UIT", "Vald tempereatur för AV", "temperatura gdy grzanie wyłączone", "valgt tempereatur for av", "température sélectionnée pour arrêt", "kapanma için seçili sıcaklık", "temperatura selezionata per spegnimento", "zvolená teplota pre vypnutie") +MAKE_TRANSLATION(wwSelTempSingle, "wwseltempsingle", "single charge temperature", "Solltemperatur Einmalladung", "Streeftemperatuur enkele lading", "Temperatur Engångsladdning", "temperatura dodatkowej ciepłej wody", "temp engangsoppvarming", "température charge unique", "tek şarj sıcaklığı", "temperatura singolaa carica", "teplota na jedno nabitie") +MAKE_TRANSLATION(wwCylMiddleTemp, "wwcylmiddletemp", "cylinder middle temperature (TS3)", "Speichertemperatur Mitte", "Buffer temperatuur midden", "Cylinder Temperatur Mitten (TS3)", "temperatura środka cylindra (TS3)", "vanntank midten temperatur (TS3)", "température moyenne ballon (TS3)", "Silindir orta sıcaklığı", "temperatura centrale accumulo (TS3)", "stredná teplota valca (TS3)") +MAKE_TRANSLATION(wwSetTemp, "wwsettemp", "set temperature", "Solltemperatur", "Streeftemperatuut", "Börtempertur", "temperatura zadana", "innstilt temperatur", "régler température", "hedef sıcaklık", "imposta temperatura", "nastavená teplota") +MAKE_TRANSLATION(wwType, "wwtype", "type", "Typ", "type", "Typ", "typ", "type", "type", "tip", "tipo", "typ") +MAKE_TRANSLATION(wwComfort, "wwcomfort", "comfort", "Komfort", "Comfort", "Komfort", "komfort", "komfort", "confort", "konfor", "Comfort", "komfort") +MAKE_TRANSLATION(wwComfort1, "wwcomfort1", "comfort mode", "Komfort-Modus", "Comfort modus", "Komfortläge", "tryb komfortu", "komfort modus", "mode confort", "konfor modu", "modalità comfort", "komfortný režim") +MAKE_TRANSLATION(wwFlowTempOffset, "wwflowtempoffset", "flow temperature offset", "Vorlauftemperaturanhebung", "Aanvoertemperatuur offset", "Flödestemperatur förskjutning", "korekta temperatury wypływu", "turtemperaturforskyvning", "offset température flux", "akış sıcaklığı artışı", "aumento della temperatura di ritorno", "Posun teploty prívodu") +MAKE_TRANSLATION(wwMaxPower, "wwmaxpower", "max power", "max Leistung", "Maximaal vermogen", "Max Effekt", "moc maksymalna", "maks effekt", "puissance max", "maksimum güç", "potenza massima", "maximálny výkon") +MAKE_TRANSLATION(wwCircPump, "wwcircpump", "circulation pump available", "Zirkulationspumpe vorhanden", "Circulatiepomp aanwezig", "Cirkulationspump tillgänglig", "pompa cyrkulacji zainstalowana", "sirkulasjonspumpe tilgjengelig", "pompe circulation disponible", "sikülasyon pompası müsait", "pompa circolazione disponibile", "dostupné obehové čerpadlo") +MAKE_TRANSLATION(wwChargeType, "wwchargetype", "charging type", "Speicher-Ladungstyp", "Buffer laadtype", "Laddningstyp", "sposób grzania zasobnika", "varmetype", "type chargement", "şarj tipi", "tipo caricamento", "typ nabíjania") +MAKE_TRANSLATION(wwDisinfectionTemp, "wwdisinfectiontemp", "disinfection temperature", "Desinfektionstemperatur", "Desinfectietemperatuur", "Desinfektionstemperatur", "temperatura dezynfekcji termicznej", "desinfeksjonstemperatur", "température désinfection", "dezenfeksiyon sıcaklığı", "temperatura disinfezione", "teplota dezinfekcie") +MAKE_TRANSLATION(wwCircMode, "wwcircmode", "circulation pump mode", "Zirkulationspumpen-Modus", "Modus circulatiepomp", "Läge Cirkulationspump", "tryb pracy cyrkulacji", "sikulasjonspumpemodus", "mode pompe circulation", "sirkülasyon pompa modu", "modalità pompa circolazione", "režim obehového čerpadla") +MAKE_TRANSLATION(wwCirc, "wwcirc", "circulation active", "Zirkulation aktiv", "Circulatiepomp actief", "Cirkulation aktiv", "pompa cyrkulacji", "sirkulasjon aktiv", "circulation active", "sirkülasyon devrede", "circolazione attiva", "obeh aktívny") +MAKE_TRANSLATION(wwCurTemp, "wwcurtemp", "current intern temperature", "aktuelle interne Temperatur", "Huidige interne temperatuur", "Intern Temperatur", "temperatura zasobnika", "gjeldende intern temperatur", "température interne actuelle", "güncel iç sıcaklık", "temperatura interna attuale", "aktuálna vnútorná teplota") +MAKE_TRANSLATION(wwCurTemp2, "wwcurtemp2", "current extern temperature", "aktuelle externe Temperatur", "Huidige externe temperatuur", "Extern Temperatur", "temperatura wypływu", "gjeldende ekstern temperaur", "température externe actuelle", "güncel dış sıcaklık", "temperatura esterna attuale", "aktuálna vonkajšia teplota") +MAKE_TRANSLATION(wwCurFlow, "wwcurflow", "current tap water flow", "aktueller Durchfluss", "Hudige warmwater doorstroming", "Aktuellt tappvattenflöde", "aktualny przepływ", "gjeldende tappevannshastighet", "débit actuel eau robinet", "güncel musluk suyu akışı", "portata corrente dell'acqua del rubinetto", "aktuálny prietok vody z vodovodu") +MAKE_TRANSLATION(wwStorageTemp1, "wwstoragetemp1", "storage intern temperature", "interne Speichertemperatur", "Interne buffertemperatuur", "Beredare Intern Temperatur", "temperatura wewnątrz zasobnika", "intern temperatur bereder", "température interne stockage", "depo iç sıcaklığı", "temperatura di conservazione interna", "interná teplota skladovania") +MAKE_TRANSLATION(wwStorageTemp2, "wwstoragetemp2", "storage extern temperature", "externer Speichertemperatur", "Externe buffertemperatuur", "Beredare Extern Tempereatur", "temperatura na wyjściu zasobnika", "ekstern temperatur bereder", "température externe stockage", "depo dış sıcaklığı", "temperatura di conservazione esterna", "vonkajšia teplota skladovania") +MAKE_TRANSLATION(wwActivated, "wwactivated", "activated", "aktiviert", "geactiveerd", "Aktiverad", "system przygotowywania c.w.u.", "aktivert", "activé", "devreye girdi", "attivato", "aktivovaný") +MAKE_TRANSLATION(wwOneTime, "wwonetime", "one time charging", "Einmalladung", "Buffer eenmalig laden", "Engångsladdning", "jednorazowa dodatkowa ciepła woda", "engangsoppvarming", "charge unique", "tek seferlik doldurma", "carica singola", "jednorazové nabíjanie") +MAKE_TRANSLATION(wwDisinfecting, "wwdisinfecting", "disinfecting", "Desinfizieren", "Desinfectie", "Desinficerar", "dezynfekcja termiczna", "desinfiserer", "désinfection", "dezenfekte ediliyor", "disinfezione", "dezinfekcia") +MAKE_TRANSLATION(wwCharging, "wwcharging", "charging", "Laden", "Laden", "Värmer", "grzanie", "varmer", "chargement", "dolduruluyor", "caricamento", "nabíjanie") +MAKE_TRANSLATION(wwChargeOptimization, "wwchargeoptimization", "charge optimization", "Ladungsoptimierung", "laadoptimalisatie", "Laddningsoptimering", "optymalizacja grzania", "oppvarmingsoptimalisering", "optimisation charge", "dolum optimizasyonu", "ottimizzazione carica", "optimalizácia poplatkov") +MAKE_TRANSLATION(wwRecharging, "wwrecharging", "recharging", "Nachladen", "herladen", "Laddar om", "ponowne grzanie", "varm på nytt", "en recharge", "tekrar dolduruluyor", "in ricarica", "nabíjanie") +MAKE_TRANSLATION(wwTempOK, "wwtempok", "temperature ok", "Temperatur ok", "Temperatuur OK", "Temperatur OK", "temperatura OK", "temperatur ok!", "température ok", "sıcaklık tamam", "Temperatura OK", "teplota ok") +MAKE_TRANSLATION(wwActive, "wwactive", "active", "aktiv", "Actief", "Aktiv", "aktywna", "aktiv", "actif", "devrede", "attivo", "aktívny") +MAKE_TRANSLATION(ww3wayValve, "ww3wayvalve", "3-way valve active", "3-Wegeventil aktiv", "3-wegklep actief", "Trevägsventil aktiv", "zawór 3-drogowy aktywny", "aktiv trevisventil", "vanne 3 voies active", "3 yollu vana", "valvola 3-vie", "3-cestný ventil aktívny") +MAKE_TRANSLATION(wwSetPumpPower, "wwsetpumppower", "set pump power", "Soll Pumpenleistung", "Streefwaarde pompvermogen", "Vald pumpeffekt", "ustawione wysterowanie pompy", "valgt pumpeeffekt", "régler puissance pompe", "ayarlı pompa gücü", "imposta potenza pompa", "nastaviť výkon čerpadla") +MAKE_TRANSLATION(wwMixerTemp, "wwmixertemp", "mixer temperature", "Mischertemperatur", "Mixertemperatuur", "Blandningsventil-tempertur", "temperatura mieszacza", "temperatur blandeventil", "température mélangeur", "karıştırıcı sıcaklığı", "temperatura miscelatore", "teplota mixéra") +MAKE_TRANSLATION(wwStarts, "wwstarts", "starts", "Anzahl Starts", "Aantal starts", "Antal starter", "liczba załączeń", "antall starter", "démarrages", "başlıyor", "avvii", "Počet štartov") +MAKE_TRANSLATION(wwStarts2, "wwstarts2", "control starts2", "Kreis 2 Anzahl Starts", "Aantal starts circuit 2", "Antal starter Krets 2", "liczba załączeń 2", "antall starter krets 2", "démarrages contrôle 2", "devre 2 başlıyor", "avvii controllati 2", "Okruh 2 počet štartov") +MAKE_TRANSLATION(wwWorkM, "wwworkm", "active time", "aktive Zeit", "Actieve tijd", "Aktiv Tid", "czas aktywności", "driftstid", "temps actif", "aktif zaman", "tempo attivo", "aktívny čas") +MAKE_TRANSLATION(wwHystOn, "wwhyston", "hysteresis on temperature", "Einschalttemperaturdifferenz", "Inschakeltemperatuurverschil", "Hysteres PÅ-temperatur", "histereza załączania", "innkoblingstemperaturforskjell", "hystérésis température allumage", "çalışma sıcaklığı farkı", "differenza di temperatura di accensione", "hysterézia teploty") +MAKE_TRANSLATION(wwHystOff, "wwhystoff", "hysteresis off temperature", "Ausschalttemperaturdifferenz", "Uitschakeltemperatuurverschil", "Hysteres AV-temperatur", "histereza wyłączania", "utkoblingstemperaturforskjell", "hystérésis température extinction", "kapatma sıcaklığı farkı", "differenza di temperatura di spegnimento", "teplota hysterézie") +MAKE_TRANSLATION(wwProgMode, "wwprogmode", "program", "Programmmodus", "Programma", "Program", "program", "program", "programme", "program", "Programma", "program") +MAKE_TRANSLATION(wwCircProg, "wwcircprog", "circulation program", "Zirkulationsprogramm", "Circulatieprogramma", "Cirkulationsprogram", "program cyrkulacji c.w.u.", "sirkulationsprogram", "programme circulation", "sirkülasyon programı", "programma circolazione", "obehový program") +MAKE_TRANSLATION(wwMaxTemp, "wwmaxtemp", "maximum temperature", "Maximale Temperatur", "Maximale temperatuur", "Maximal Temperatur", "temperatura maksymalna", "maksimal temperatur", "température max", "maksimum sıcaklık", "temperatura massima", "maximálna teplota") +MAKE_TRANSLATION(wwOneTimeKey, "wwonetimekey", "one time key function", "Einmalladungstaste", "Knop voor eenmalig laden buffer", "Engångsfunktion", "przycisk jednorazowego ogrzania", "engangsknapp varme", "fonction touche unique", "tek seferlik doldurma fonksiyonu", "pulsante funzione singola", "jednorazová kľúčová funkcia") +MAKE_TRANSLATION(wwSolarTemp, "wwsolartemp", "solar boiler temperature", "Solarboiler Temperatur", "Zonneboiler temperatuur", "Solpanel Temp", "temperatura zasobnika solarnego", "solpaneltemp", "température chaudière solaire", "güneş enerjisi kazan sıcaklığı", "temperatura pannello solare", "teplota solárneho kotla") // mqtt values / commands -MAKE_TRANSLATION(switchtime, "switchtime", "program switchtime", "Programm Schaltzeit", "Programma schakeltijd", "Program Bytestid", "program czasowy", "programbyttetid", "heure commutation programme", "program değiştirme süresi", "ora commutazione programmata") -MAKE_TRANSLATION(switchtime1, "switchtime1", "own1 program switchtime", "Programm 1 Schaltzeit", "Schakeltijd programma 1", "Program 1 Bytestid", "program przełączania 1", "byttetidprogram 1", "heure de commutation programme 1", "program1 değiştirme süresi", "ora commutazione programma 1") -MAKE_TRANSLATION(switchtime2, "switchtime2", "own2 program switchtime", "Programm 2 Schaltzeit", "Schakeltijd programma 2", "Program 2 Bytestid", "program przełączania 2", "byttetid program 2", "heure de changement programme 2", "program1 değiştirme süresi", "ora commutazione programma 2") -MAKE_TRANSLATION(wwswitchtime, "wwswitchtime", "program switchtime", "Programm Schaltzeit", "Warm water programma schakeltijd", "Varmvattenprogram Bytestid", "program czasowy", "byttetid varmtvannsprogram", "heure commutation programme", "sıcak kullanıom suyu program değiştirme süresi", "Tempo di commutazione del programma") -MAKE_TRANSLATION(wwcircswitchtime, "wwcircswitchtime", "circulation program switchtime", "Zirculationsprogramm Schaltzeit", "Schakeltijd circulatieprogramma", "Cirkulationsprogram Bytestid", "program cyrkulacji", "byttetid sirkulasjonsprogram", "heure commutation programme circulation", "sirkülasyon program değiştirme süresi", "ora commutazione programma circolazione") -MAKE_TRANSLATION(dateTime, "datetime", "date/time", "Datum/Zeit", "Datum/Tijd", "Datum/Tid", "data i godzina", "dato/tid", "date/heure", "zaman/saat", "Data/Ora") -MAKE_TRANSLATION(errorCode, "errorcode", "error code", "Fehlernummer", "Foutmeldingscode", "Felkod", "kod błędu", "feikode", "code erreur", "hata kodu", "codice errore") -MAKE_TRANSLATION(ibaMainDisplay, "display", "display", "Anzeige", "Display", "Display", "wyświetlacz", "skjerm", "affichage", "ekran", "Display") -MAKE_TRANSLATION(ibaLanguage, "language", "language", "Sprache", "Taal", "Sprak", "język", "språk", "langue", "dil", "Lingua") -MAKE_TRANSLATION(ibaClockOffset, "clockoffset", "clock offset", "Uhrkorrektur", "Klokcorrectie", "Tidskorrigering", "korekta zegara", "tidskorrigering", "offset horloge", "saat farkı", "correzione orario") -MAKE_TRANSLATION(ibaBuildingType, "building", "building type", "Gebäudetyp", "Type gebouw", "Byggnadstyp", "typ budynku", "bygningstype", "type bâtiment", "bina tipi", "tipo di edificio") -MAKE_TRANSLATION(heatingPID, "heatingpid", "heating PID", "Heizungs-PID", "PID verwarming", "Uppvärmning PID", "PID ogrzewania", "oppvarmings PID", "PID chauffage", "PID ısınıyor", "PID-riscaldamento") -MAKE_TRANSLATION(ibaCalIntTemperature, "intoffset", "internal temperature offset", "Korrektur interner Temperatur", "Offset interne temperatuur", "Korrigering interntemperatur", "korekta temperatury w pomieszczeniu", "Korrigering interntemperatur", "offset température interne", "iç sıcaklık artışı", "scostamento della temperatura interna") -MAKE_TRANSLATION(ibaMinExtTemperature, "minexttemp", "minimal external temperature", "min. Aussentemperatur", "Min. buitentemperatuur", "Min Extern Temperatur", "minimalna miejscowa temperatura zewnętrzna", "minimal eksterntemperatur", "température extérieure minimale", "en düşük sış sıcaklık", "temperatura esterna minima") -MAKE_TRANSLATION(backlight, "backlight", "key backlight", "Gegenlicht", "Toetsverlichting", "Bakgrundsbelysning", "podświetlenie klawiatury", "bakgrunnsbelysning", "rétroéclairage touches", "tuş takımı aydınlatması", "retroilluminazione dei tasti") -MAKE_TRANSLATION(damping, "damping", "damping outdoor temperature", "Dämpfung der Außentemperatur", "Demping buitentemperatuur", "Utomhustemperatur dämpning", "tłumienie temperatury zewnętrznej", "demping av utetemperatur", "température extérieure minimale", "dış sıcaklığın sönümlenmesi", "smorzamento della temperatura esterna") -MAKE_TRANSLATION(tempsensor1, "inttemp1", "temperature sensor 1", "Temperatursensor 1", "Temperatuursensor 1", "Temperatursensor 1", "czujnik temperatury 1", "temperatursensor 1", "sonde température 1", "sıcaklık sensörü 1", "sensore temperatura 1") -MAKE_TRANSLATION(tempsensor2, "inttemp2", "temperature sensor 2", "Temperatursensor 2", "Temperatuursensor 2", "Temperatursensor 2", "czujnik temperatury 2", "temperatursensor 2", "capteur température 2", "sıcaklık sensörü 2", "sensore temperatura 2") -MAKE_TRANSLATION(dampedoutdoortemp, "dampedoutdoortemp", "damped outdoor temperature", "gedämpfte Außentemperatur", "Gedempte buitentemperatuur", "Utomhustemperatur dämpad", "tłumiona temperatura zewnętrzna", "dempet utetemperatur", "température extérieure amortie", "sönümlenmiş dış sıcaklık", "temperatura esterna smorzata") -MAKE_TRANSLATION(floordrystatus, "floordry", "floor drying", "Estrichtrocknung", "Vloerdroogprogramma", "Golvtorkning", "suszenie jastrychu", "gulvtørkeprogram", "séchage sol", "yerden ısıtma", "asciugatura pavimento") -MAKE_TRANSLATION(floordrytemp, "floordrytemp", "floor drying temperature", "Estrichtrocknungs Temperatur", "Temperatuur vloerdroogprogramma", "Golvtorkning Temperatur", "temperatura suszenia jastrychu", "gulvtørketemperatur", "température séchage sol", "yerden ısıtma sıcaklığı", "Temperatura asciugatura pavimento") -MAKE_TRANSLATION(brightness, "brightness", "screen brightness", "Bildschirmhelligkeit", "Schermhelderheid", "Ljusstyrka", "jasność", "lysstyrke", "luminosité écran", "ekran parlaklığı", "luminosita display") -MAKE_TRANSLATION(autodst, "autodst", "automatic change daylight saving time", "automatische Sommerzeit Umstellung", "Automatische omschakeling zomer-wintertijd", "Automatisk växling sommar/vinter-tid", "automatycznie przełączaj na czas letni/zimowy", "automatisk skifte av sommer/vinter-tid", "changement automatique heure d'été", "gün ışığından yararlanma saatini otomatik olarak değiştir", "cambio automatico dell'ora legale") -MAKE_TRANSLATION(preheating, "preheating", "preheating in the clock program", "Vorheizen im Zeitprogramm", "Voorverwarming in het klokprogramma", "Förvärmning i tidsprogram", "podgrzewanie w programie czasowym", "forvarming i tidsprogram", "préchauffage dans programme horloge", "saat programında ön ısıtma", "preriscaldamento nel programma orologio") -MAKE_TRANSLATION(offtemp, "offtemp", "temperature when mode is off", "Temperatur bei AUS", "Temperatuur bij UIT", "Temperatur Avslagen", "temperatura w trybie \"wył.\"", "temperatur avslått", "température lorsque mode désactivé", "mod kapalı iken sıcaklık", "temperatura quando la modalità è disattivata") -MAKE_TRANSLATION(mixingvalves, "mixingvalves", "mixing valves", "Mischventile", "Mengkleppen", "Blandningsventiler", "zawory mieszające", "blandeventiler", "vannes mélange", "karışım vanaları", "valvole miscela") -MAKE_TRANSLATION(pvEnableWw, "pvenableww", "enable raise dhw", "aktiviere Anhebung WW", "Verhoging WW activeren", "", "podwyższenie c.w.u. z PV", "aktivere hevet temperatur bereder", "", "sıcak kullanım suyu yükseltmeyi etkinleştir", "abilitare aumento ACS") // TODO translate -MAKE_TRANSLATION(pvRaiseHeat, "pvraiseheat", "raise heating with PV", "Anhebung Heizen mit PV", "Verwarmen met PV activeren", "", "podwyższenie grzania z PV", "heve varmen med solpanel", "", "ısıtmayı G.E. İle yükselt", "Aumentare il riscaldamento con il solare") // TODO translate -MAKE_TRANSLATION(pvLowerCool, "pvlowercool", "lower cooling with PV", "Kühlabsenkung mit PV", "Verlagen koeling met PV activeren", "", "obniżenie chłodzenia z PV", "nedre kjøling solpanel", "", "soğutmayı G.E. İle düşür", "Riduzione del raffreddamento con il solare") // TODO translate +MAKE_TRANSLATION(switchtime, "switchtime", "program switchtime", "Programm Schaltzeit", "Programma schakeltijd", "Program Bytestid", "program czasowy", "programbyttetid", "heure commutation programme", "program değiştirme süresi", "ora commutazione programmata", "čas prepnutia programu") +MAKE_TRANSLATION(switchtime1, "switchtime1", "own1 program switchtime", "Programm 1 Schaltzeit", "Schakeltijd programma 1", "Program 1 Bytestid", "program przełączania 1", "byttetidprogram 1", "heure de commutation programme 1", "program1 değiştirme süresi", "ora commutazione programma 1", "vlastný 1 program prepnutia") +MAKE_TRANSLATION(switchtime2, "switchtime2", "own2 program switchtime", "Programm 2 Schaltzeit", "Schakeltijd programma 2", "Program 2 Bytestid", "program przełączania 2", "byttetid program 2", "heure de changement programme 2", "program1 değiştirme süresi", "ora commutazione programma 2", "vlastný 2 program prepnutia") +MAKE_TRANSLATION(wwswitchtime, "wwswitchtime", "program switchtime", "Programm Schaltzeit", "Warm water programma schakeltijd", "Varmvattenprogram Bytestid", "program czasowy", "byttetid varmtvannsprogram", "heure commutation programme", "sıcak kullanıom suyu program değiştirme süresi", "Tempo di commutazione del programma", "čas prepnutia programu") +MAKE_TRANSLATION(wwcircswitchtime, "wwcircswitchtime", "circulation program switchtime", "Zirculationsprogramm Schaltzeit", "Schakeltijd circulatieprogramma", "Cirkulationsprogram Bytestid", "program cyrkulacji", "byttetid sirkulasjonsprogram", "heure commutation programme circulation", "sirkülasyon program değiştirme süresi", "ora commutazione programma circolazione", "čas prepnutia cirkulačného programu") +MAKE_TRANSLATION(dateTime, "datetime", "date/time", "Datum/Zeit", "Datum/Tijd", "Datum/Tid", "data i godzina", "dato/tid", "date/heure", "zaman/saat", "Data/Ora", "dátum/čas") +MAKE_TRANSLATION(errorCode, "errorcode", "error code", "Fehlernummer", "Foutmeldingscode", "Felkod", "kod błędu", "feikode", "code erreur", "hata kodu", "codice errore", "error kód") +MAKE_TRANSLATION(ibaMainDisplay, "display", "display", "Anzeige", "Display", "Display", "wyświetlacz", "skjerm", "affichage", "ekran", "Display", "display") +MAKE_TRANSLATION(ibaLanguage, "language", "language", "Sprache", "Taal", "Sprak", "język", "språk", "langue", "dil", "Lingua", "jazyk") +MAKE_TRANSLATION(ibaClockOffset, "clockoffset", "clock offset", "Uhrkorrektur", "Klokcorrectie", "Tidskorrigering", "korekta zegara", "tidskorrigering", "offset horloge", "saat farkı", "correzione orario", "korekcia času") +MAKE_TRANSLATION(ibaBuildingType, "building", "building type", "Gebäudetyp", "Type gebouw", "Byggnadstyp", "typ budynku", "bygningstype", "type bâtiment", "bina tipi", "tipo di edificio", "typ budovy") +MAKE_TRANSLATION(heatingPID, "heatingpid", "heating PID", "Heizungs-PID", "PID verwarming", "Uppvärmning PID", "PID ogrzewania", "oppvarmings PID", "PID chauffage", "PID ısınıyor", "PID-riscaldamento", "PID kúrenia") +MAKE_TRANSLATION(ibaCalIntTemperature, "intoffset", "internal temperature offset", "Korrektur interner Temperatur", "Offset interne temperatuur", "Korrigering interntemperatur", "korekta temperatury w pomieszczeniu", "Korrigering interntemperatur", "offset température interne", "iç sıcaklık artışı", "scostamento della temperatura interna", "odchýlka vnútornej teploty") +MAKE_TRANSLATION(ibaMinExtTemperature, "minexttemp", "minimal external temperature", "min. Aussentemperatur", "Min. buitentemperatuur", "Min Extern Temperatur", "minimalna miejscowa temperatura zewnętrzna", "minimal eksterntemperatur", "température extérieure minimale", "en düşük sış sıcaklık", "temperatura esterna minima", "minimálna vonkajšia teplota") +MAKE_TRANSLATION(backlight, "backlight", "key backlight", "Gegenlicht", "Toetsverlichting", "Bakgrundsbelysning", "podświetlenie klawiatury", "bakgrunnsbelysning", "rétroéclairage touches", "tuş takımı aydınlatması", "retroilluminazione dei tasti", "podsvietenie kláves") +MAKE_TRANSLATION(damping, "damping", "damping outdoor temperature", "Dämpfung der Außentemperatur", "Demping buitentemperatuur", "Utomhustemperatur dämpning", "tłumienie temperatury zewnętrznej", "demping av utetemperatur", "température extérieure minimale", "dış sıcaklığın sönümlenmesi", "smorzamento della temperatura esterna", "tlmenie vonkajšej teploty") +MAKE_TRANSLATION(tempsensor1, "inttemp1", "temperature sensor 1", "Temperatursensor 1", "Temperatuursensor 1", "Temperatursensor 1", "czujnik temperatury 1", "temperatursensor 1", "sonde température 1", "sıcaklık sensörü 1", "sensore temperatura 1", "snímač teploty 1") +MAKE_TRANSLATION(tempsensor2, "inttemp2", "temperature sensor 2", "Temperatursensor 2", "Temperatuursensor 2", "Temperatursensor 2", "czujnik temperatury 2", "temperatursensor 2", "capteur température 2", "sıcaklık sensörü 2", "sensore temperatura 2", "snímač teploty 2") +MAKE_TRANSLATION(dampedoutdoortemp, "dampedoutdoortemp", "damped outdoor temperature", "gedämpfte Außentemperatur", "Gedempte buitentemperatuur", "Utomhustemperatur dämpad", "tłumiona temperatura zewnętrzna", "dempet utetemperatur", "température extérieure amortie", "sönümlenmiş dış sıcaklık", "temperatura esterna smorzata", "tlmená vonkajšia teplota") +MAKE_TRANSLATION(floordrystatus, "floordry", "floor drying", "Estrichtrocknung", "Vloerdroogprogramma", "Golvtorkning", "suszenie jastrychu", "gulvtørkeprogram", "séchage sol", "yerden ısıtma", "asciugatura pavimento", "sušenie podlahy") +MAKE_TRANSLATION(floordrytemp, "floordrytemp", "floor drying temperature", "Estrichtrocknungs Temperatur", "Temperatuur vloerdroogprogramma", "Golvtorkning Temperatur", "temperatura suszenia jastrychu", "gulvtørketemperatur", "température séchage sol", "yerden ısıtma sıcaklığı", "Temperatura asciugatura pavimento", "teplota sušenia podlahy") +MAKE_TRANSLATION(brightness, "brightness", "screen brightness", "Bildschirmhelligkeit", "Schermhelderheid", "Ljusstyrka", "jasność", "lysstyrke", "luminosité écran", "ekran parlaklığı", "luminosita display", "jas obrazovky") +MAKE_TRANSLATION(autodst, "autodst", "automatic change daylight saving time", "automatische Sommerzeit Umstellung", "Automatische omschakeling zomer-wintertijd", "Automatisk växling sommar/vinter-tid", "automatycznie przełączaj na czas letni/zimowy", "automatisk skifte av sommer/vinter-tid", "changement automatique heure d'été", "gün ışığından yararlanma saatini otomatik olarak değiştir", "cambio automatico dell'ora legale", "automatická zmena letného času") +MAKE_TRANSLATION(preheating, "preheating", "preheating in the clock program", "Vorheizen im Zeitprogramm", "Voorverwarming in het klokprogramma", "Förvärmning i tidsprogram", "podgrzewanie w programie czasowym", "forvarming i tidsprogram", "préchauffage dans programme horloge", "saat programında ön ısıtma", "preriscaldamento nel programma orologio", "predohrev v programe hodín") +MAKE_TRANSLATION(offtemp, "offtemp", "temperature when mode is off", "Temperatur bei AUS", "Temperatuur bij UIT", "Temperatur Avslagen", "temperatura w trybie \"wył.\"", "temperatur avslått", "température lorsque mode désactivé", "mod kapalı iken sıcaklık", "temperatura quando la modalità è disattivata", "teplota, keď je režim vypnutý") +MAKE_TRANSLATION(mixingvalves, "mixingvalves", "mixing valves", "Mischventile", "Mengkleppen", "Blandningsventiler", "zawory mieszające", "blandeventiler", "vannes mélange", "karışım vanaları", "valvole miscela", "zmiešavacie ventily") +MAKE_TRANSLATION(pvEnableWw, "pvenableww", "enable raise dhw", "aktiviere Anhebung WW", "Verhoging WW activeren", "", "podwyższenie c.w.u. z PV", "aktivere hevet temperatur bereder", "", "sıcak kullanım suyu yükseltmeyi etkinleştir", "abilitare aumento ACS", "povoliť zvýšenie TÚV") // TODO translate +MAKE_TRANSLATION(pvRaiseHeat, "pvraiseheat", "raise heating with PV", "Anhebung Heizen mit PV", "Verwarmen met PV activeren", "", "podwyższenie grzania z PV", "heve varmen med solpanel", "", "ısıtmayı G.E. İle yükselt", "Aumentare il riscaldamento con il solare", "zvýšiť kúrenie s FV") // TODO translate +MAKE_TRANSLATION(pvLowerCool, "pvlowercool", "lower cooling with PV", "Kühlabsenkung mit PV", "Verlagen koeling met PV activeren", "", "obniżenie chłodzenia z PV", "nedre kjøling solpanel", "", "soğutmayı G.E. İle düşür", "Riduzione del raffreddamento con il solare", "nižšie chladenie s PV") // TODO translate // thermostat ww -MAKE_TRANSLATION(wwMode, "wwmode", "mode", "Modus", "Modus", "Läge", "tryb pracy", "modus", "mode", "mod", "modalità") -MAKE_TRANSLATION(wwSetTempLow, "wwsettemplow", "set low temperature", "untere Solltemperatur", "Onderste streeftemperatuur", "Nedre Börvärde", "zadana temperatura obniżona", "nedre settverdi", "réglage température basse", "hedef düşük sıcaklık", "imposta bassa temperatura") -MAKE_TRANSLATION(wwWhenModeOff, "wwwhenmodeoff", "when thermostat mode off", "bei Thermostatmodus AUS", "Als Thermostaat op UIT", "när Termostatläge är AV", "gdy wyłączono na termostacie", "når modus er av", "lorsque mode thermostat off", "termostat modu kapalı olduğunda", "quando termostato modalita OFF") -MAKE_TRANSLATION(wwExtra1, "wwextra1", "circuit 1 extra", "Kreis 1 Extra", "Circuit 1 extra", "Krets 1 Extra", "obieg dodatkowy 1", "ekstra krets 1", "circuit 1 extra", "devre 1 ekstra", "Circuito 1 extra") -MAKE_TRANSLATION(wwExtra2, "wwextra2", "circuit 2 extra", "Kreis 2 Extra", "Circuit 2 extra", "Kets 2 Extra", "obieg dodatkowy 2", "ekstra krets 2", "circuit 2 extra", "devre 2 ekstra", "Circuito 2 extra") -MAKE_TRANSLATION(wwCharge, "wwcharge", "charge", "Laden", "Laden", "Ladda", "grzanie", "lade", "charge", "doldurma", "carica") -MAKE_TRANSLATION(wwChargeDuration, "wwchargeduration", "charge duration", "Ladedauer", "Laadtijd", "Laddtid", "czas grzania dodatkowej ciepłej wody", "ladetid", "durée charge", "doldurma süresi", "durata carica") -MAKE_TRANSLATION(wwDisinfect, "wwdisinfect", "disinfection", "Desinfektion", "Desinfectie", "Desinfektion", "dezynfekcja termiczna", "desinfeksjon", "désinfection", "dezenfeksiyon", "disinfezione") -MAKE_TRANSLATION(wwDisinfectDay, "wwdisinfectday", "disinfection day", "Desinfektionstag", "Desinfectiedag", "Desinfektionsdag", "dzień dezynfekcji termicznej", "desinfeksjonsdag", "jour désinfection", "dezenfeksiyon günü", "giorno disinfezione") -MAKE_TRANSLATION(wwDisinfectHour, "wwdisinfecthour", "disinfection hour", "Desinfektionsstunde", "Desinfectieuur", "Desinfektionstimme", "godzina dezynfekcji termicznej", "desinfeksjonstime", "heure désinfection", "dezenfeksiyon saati", "ora disinfezione") -MAKE_TRANSLATION(wwDisinfectTime, "wwdisinfecttime", "disinfection time", "Desinfektionszeit", "Desinfectietijd", "Desinfektionstid", "maksymalny czas trwania dezynfekcji termicznej", "desinfeksjonstid", "durée désinfection", "dezenfeksiyon zamanı", "orario disinfezione") -MAKE_TRANSLATION(wwDailyHeating, "wwdailyheating", "daily heating", "täglich Heizen", "Dagelijks opwarmen", "Daglig Uppvärmning", "codzienne nagrzewanie", "daglig oppvarming", "chauffage quotidien", "günlük ısıtma", "riscaldamento giornaliero") -MAKE_TRANSLATION(wwDailyHeatTime, "wwdailyheattime", "daily heating time", "tägliche Heizzeit", "Tijd dagelijkse opwarming", "Daglig Uppvärmningstid", "czas trwania codziennego nagrzewania", "daglig oppvarmingstid", "heure chauffage quotidien", "günlük ısıtma süresi", "orario riscaldamento giornaliero") +MAKE_TRANSLATION(wwMode, "wwmode", "mode", "Modus", "Modus", "Läge", "tryb pracy", "modus", "mode", "mod", "modalità", "režim") +MAKE_TRANSLATION(wwSetTempLow, "wwsettemplow", "set low temperature", "untere Solltemperatur", "Onderste streeftemperatuur", "Nedre Börvärde", "zadana temperatura obniżona", "nedre settverdi", "réglage température basse", "hedef düşük sıcaklık", "imposta bassa temperatura", "nastaviť nízku teplotu") +MAKE_TRANSLATION(wwWhenModeOff, "wwwhenmodeoff", "when thermostat mode off", "bei Thermostatmodus AUS", "Als Thermostaat op UIT", "när Termostatläge är AV", "gdy wyłączono na termostacie", "når modus er av", "lorsque mode thermostat off", "termostat modu kapalı olduğunda", "quando termostato modalita OFF", "keď je režim termostatu vypnutý") +MAKE_TRANSLATION(wwExtra1, "wwextra1", "circuit 1 extra", "Kreis 1 Extra", "Circuit 1 extra", "Krets 1 Extra", "obieg dodatkowy 1", "ekstra krets 1", "circuit 1 extra", "devre 1 ekstra", "Circuito 1 extra", "okruh 1 extra") +MAKE_TRANSLATION(wwExtra2, "wwextra2", "circuit 2 extra", "Kreis 2 Extra", "Circuit 2 extra", "Kets 2 Extra", "obieg dodatkowy 2", "ekstra krets 2", "circuit 2 extra", "devre 2 ekstra", "Circuito 2 extra", "okruh 2 extra") +MAKE_TRANSLATION(wwCharge, "wwcharge", "charge", "Laden", "Laden", "Ladda", "grzanie", "lade", "charge", "doldurma", "carica", "nabiť") +MAKE_TRANSLATION(wwChargeDuration, "wwchargeduration", "charge duration", "Ladedauer", "Laadtijd", "Laddtid", "czas grzania dodatkowej ciepłej wody", "ladetid", "durée charge", "doldurma süresi", "durata carica", "doba nabíjania") +MAKE_TRANSLATION(wwDisinfect, "wwdisinfect", "disinfection", "Desinfektion", "Desinfectie", "Desinfektion", "dezynfekcja termiczna", "desinfeksjon", "désinfection", "dezenfeksiyon", "disinfezione", "dezinfekcia") +MAKE_TRANSLATION(wwDisinfectDay, "wwdisinfectday", "disinfection day", "Desinfektionstag", "Desinfectiedag", "Desinfektionsdag", "dzień dezynfekcji termicznej", "desinfeksjonsdag", "jour désinfection", "dezenfeksiyon günü", "giorno disinfezione", "deň dezinfekcie") +MAKE_TRANSLATION(wwDisinfectHour, "wwdisinfecthour", "disinfection hour", "Desinfektionsstunde", "Desinfectieuur", "Desinfektionstimme", "godzina dezynfekcji termicznej", "desinfeksjonstime", "heure désinfection", "dezenfeksiyon saati", "ora disinfezione", "hodina dezinfekcie") +MAKE_TRANSLATION(wwDisinfectTime, "wwdisinfecttime", "disinfection time", "Desinfektionszeit", "Desinfectietijd", "Desinfektionstid", "maksymalny czas trwania dezynfekcji termicznej", "desinfeksjonstid", "durée désinfection", "dezenfeksiyon zamanı", "orario disinfezione", "čas na dezinfekciu") +MAKE_TRANSLATION(wwDailyHeating, "wwdailyheating", "daily heating", "täglich Heizen", "Dagelijks opwarmen", "Daglig Uppvärmning", "codzienne nagrzewanie", "daglig oppvarming", "chauffage quotidien", "günlük ısıtma", "riscaldamento giornaliero", "denné kúrenie") +MAKE_TRANSLATION(wwDailyHeatTime, "wwdailyheattime", "daily heating time", "tägliche Heizzeit", "Tijd dagelijkse opwarming", "Daglig Uppvärmningstid", "czas trwania codziennego nagrzewania", "daglig oppvarmingstid", "heure chauffage quotidien", "günlük ısıtma süresi", "orario riscaldamento giornaliero", "denný čas vykurovania") // thermostat hc -MAKE_TRANSLATION(selRoomTemp, "seltemp", "selected room temperature", "Sollwert Raumtemperatur", "Streeftemperatuur kamer", "Vald Rumstemperatur", "zadana temperatura w pomieszczeniu", "valgt rumstemperatur", "température ambiante sélectionnée", "seçili oda sıcaklığı", "temperatura ambiente selezionata") -MAKE_TRANSLATION(roomTemp, "currtemp", "current room temperature", "aktuelle Raumtemperatur", "Huidige kamertemperatuur", "Aktuell Rumstemperatur", "temperatura w pomieszczeniu", "gjeldende romstemperatur", "température ambiante actuelle", "güncel oda sıcaklığı", "temperatura ambiente attuale") -MAKE_TRANSLATION(mode, "mode", "mode", "Modus", "Modus", "Läge", "sposób sterowania", "modus", "mode", "mod", "modalità") -MAKE_TRANSLATION(modetype, "modetype", "mode type", "Modus Typ", "Type modus", "Typ av läge", "aktualny tryb pracy", "modusrype", "type mode", "mod tipi", "tipo di modalita") -MAKE_TRANSLATION(fastheatup, "fastheatup", "fast heatup", "schnelles Aufheizen", "Snel opwarmen", "Snabb Uppvärmning", "szybkie nagrzewanie", "rask oppvarming", "chauffage rapide", "hızlı ısıtma", "riscaldamento rapido") -MAKE_TRANSLATION(daytemp, "daytemp", "day temperature", "Tagestemperatur", "temperatuur dag", "Dagstemperatur", "temperatura w dzień", "dagtemperatur", "température jour", "gündüz sıcaklığı", "temperatura giornaliera") -MAKE_TRANSLATION(daylowtemp, "daytemp2", "day temperature T2", "Tagestemperatur T2", "Temperatuur dag T2", "Dagstemperatur T2", "temperatura w dzień T2", "dagtemperatur T2", "température jour T2", "gündüz sıcaklığı T2", "temperatura giornaliera T2") -MAKE_TRANSLATION(daymidtemp, "daytemp3", "day temperature T3", "Tagestemperatur T3", "Temperatuur dag T3", "Dagstemperatur T3", "temperatura w dzień T3", "dagtemperatur T3", "température jour T3", "gündüz sıcaklığı T3", "temperatura giornaliera T3") -MAKE_TRANSLATION(dayhightemp, "daytemp4", "day temperature T4", "Tagestemperatur T4", "Temperatuur dag T4", "Dagstemperatur T4", "temperatura w dzień T4", "dagtemperatur T4", "température jour T4", "gündüz sıcaklığı T4", "temperatura giornaliera T4") -MAKE_TRANSLATION(heattemp, "heattemp", "heat temperature", "Heizen Temperatur", "Temperatuur verwarming", "Temperatur Uppvärmning", "temperatura ogrzewania", "oppvarmingstemperatur", "température chauffage", "ısıtma sıcaklığı", "temperatura riscaldamento") -MAKE_TRANSLATION(nighttemp, "nighttemp", "night temperature", "Nachttemperatur", "Nachttemperatuur", "Nattemperatur", "temperatura w nocy", "nattemperatur", "température de nuit", "gece sıcaklığı", "temperatura notturna") -MAKE_TRANSLATION(nighttemp2, "nighttemp", "night temperature T1", "Nachttemperatur T1", "Nachttemperatuur T1", "Nattemperatur T1", "temperatura w nocy T1", "nattemperatur T1", "température nuit T1", "gece sıcaklığı T1", "temperatura notturna T1") -MAKE_TRANSLATION(ecotemp, "ecotemp", "eco temperature", "eco Temperatur", "Temperatuur eco", "Eko-temperatur", "temperatura w trybie eko", "øko temperatur", "température éco", "eko sıcaklık", "Temperatura eco") -MAKE_TRANSLATION(manualtemp, "manualtemp", "manual temperature", "manuelle Temperatur", "Temperatuur handmatig", "Temperatur Manuell", "temperatura ustawiona ręcznie", "manuell temperatur", "température manuelle", "manuel sıcaklık", "temperatura manuale") -MAKE_TRANSLATION(tempautotemp, "tempautotemp", "temporary set temperature automode", "temporäre Solltemperatur", "Streeftemperatuur automodus tijdelijk", "Temporär Aktivering av Auto-läge", "zadana temperatura w pomieszczenia w trybie \"auto\" (tymczasowa)", "temporær valgt temp i automodus", "température temporaire mode automatique", "geçici ayarlı sıcaklık otomatik mod", "impostare temporaneamente temperatura automatica") -MAKE_TRANSLATION(remoteseltemp, "remoteseltemp", "temporary set temperature from remote", "temporäre Solltemperatur Remote", "Temperatuur van afstandsbedieding", "Temperatur från fjärruppkoppling", "zadana zdalnie temperatura a pomieszczeniu (tymczasowa)", "temporær valgt temp fra fjernbetjening", "température temporaire depuis télécommande", "geçici ayarlı sıcaklık uzaktan", "Temperatura temporanea da remoto") -MAKE_TRANSLATION(comforttemp, "comforttemp", "comfort temperature", "Komforttemperatur", "Comforttemperatuur", "Komforttemperatur", "temperatura w trybie komfort", "komforttemperatur", "température confort", "konfor sıcaklığı", "temperatura comfort") -MAKE_TRANSLATION(summertemp, "summertemp", "summer temperature", "Sommertemperatur", "Zomertemperatuur", "Sommartemperatur", "temperatura przełączania lato/zima", "Sommertemperatur", "température été", "yaz sıcaklığı", "temperatura estiva") -MAKE_TRANSLATION(designtemp, "designtemp", "design temperature", "Auslegungstemperatur", "Ontwerptemperatuur", "Design-temperatur", "temperatura projektowa", "designtemperatur", "température conception", "özel sıcaklık", "temperatura predefinita") -MAKE_TRANSLATION(offsettemp, "offsettemp", "offset temperature", "Temperaturanhebung", "Temperatuur offset", "Temperaturkorrigering", "korekta temperatury", "temperaturkorrigering", "température offset", "artış sıcaklığı", "aumento della temperatura") -MAKE_TRANSLATION(minflowtemp, "minflowtemp", "min flow temperature", "min Vorlauftemperatur", "Minimale aanvoertemperatuur", "Min Flödestemperatur", "minimalna temperatura zasilania", "min turtemperatur", "température min flux", "minimun akış sıcaklığı", "temperatura minima di mandata") -MAKE_TRANSLATION(maxflowtemp, "maxflowtemp", "max flow temperature", "max Vorlauftemperatur", "Maximale aanvoertemperatuur", "Max Flödestemperatur", "maksymalna temperatura zasilania", "maks turtemperatur", "température max flux", "maksimum akış sıcaklığı", "temperatura massima di mandata") -MAKE_TRANSLATION(roominfluence, "roominfluence", "room influence", "Raumeinfluss", "Ruimteinvloed", "Rumspåverkan", "wpływ pomieszczenia", "rominnflytelse", "influence pièce", "oda etkisi", "influenza della camera") -MAKE_TRANSLATION(roominfl_factor, "roominflfactor", "room influence factor", "Raumeinflussfaktor", "Factor ruimteinvloed", "Rumspåverkansfaktor", "współczynnik wpływu pomieszczenia", "rominnflytelsesfaktor", "facteur d'influence pièce", "oda etkisi faktörü", "fattore influenza camera") -MAKE_TRANSLATION(curroominfl, "curroominfl", "current room influence", "aktueller Raumeinfluss", "Huidige ruimteinvloed", "Aktuell Rumspåverkan", "aktualny wpływ pomieszczenia", "gjeldende rominnflytelse", "influence actuelle pièce", "güncel oda etkisi", "fattore corrente influenza camera") -MAKE_TRANSLATION(nofrosttemp, "nofrosttemp", "nofrost temperature", "Frostschutztemperatur", "Temperatuur vorstbeveiliging", "Temperatur Frostskydd", "temperatura ochrony przed zamarzaniem", "frostbeskyttelsestemperatur", "température protection gel", "donma koruması sıcaklığı", "temperatura protezione antigelo") -MAKE_TRANSLATION(targetflowtemp, "targetflowtemp", "target flow temperature", "berechnete Vorlauftemperatur", "Berekende aanvoertemperatuur", "Målvärde Flödestemperatur", "zadana temperatura zasilania", "målverdi turtemperatur", "température cible flux", "hedef akış sıcaklığı", "temperatura di mandata calcolata") -MAKE_TRANSLATION(heatingtype, "heatingtype", "heating type", "Heizungstyp", "Verwarmingstype", "Värmesystem", "system grzewczy", "varmesystem", "type chauffage", "ısıtma tipi", "tipo riscaldamento") -MAKE_TRANSLATION(summersetmode, "summersetmode", "set summer mode", "Einstellung Sommerbetrieb", "Instelling zomerbedrijf", "Aktivera Sommarläge", "tryb lato/zima", "aktiver sommermodus", "activer mode été", "yaz modu ayarı", "Impostazione della modalità estiva") -MAKE_TRANSLATION(hpoperatingmode, "hpoperatingmode", "heatpump operating mode", "Wärmepumpe Betriebsmodus", "Bedrijfsmodus warmtepomp", "Värmepump Driftläge", "tryb pracy pompy ciepła", "driftsmodus varmepumpe", "mode fonctionnement pompe à chaleur", "ısı pompası çalışma modu", "Modalità di funzionamento della pompa di calore") -MAKE_TRANSLATION(hpoperatingstate, "hpoperatingstate", "heatpump operating state", "WP Arbeitsweise", "Huidige modus warmtepomp", "Värmepump driftläge", "aktualny tryb pracy pompy ciepła", "driftstatus varmepumpe", "état fonctionnement pompe à chaleur", "ısı pompası çalışma durumu", "stato funzionamento pompa di calore") -MAKE_TRANSLATION(controlmode, "controlmode", "control mode", "Kontrollmodus", "Comtrolemodus", "Kontrolläge", "tryb sterowania", "kontrollmodus", "mode régulation", "kontrol modu", "modalità di controllo") -MAKE_TRANSLATION(control, "control", "control device", "Fernsteuerung", "Afstandsbedieding", "Kontrollenhet", "sterownik", "kontrollenhet", "dispositif régulation", "kontrol cihazı", "dispositivo di comando") -MAKE_TRANSLATION(roomsensor, "roomsensor", "room sensor", "Raumsensor", "Ruimtesensor", "Rumssensor", "czujnik temperatury pomieszczenia", "romsensor", "capteur pièce", "oda sensörü", "sensore ambiente") -MAKE_TRANSLATION(program, "program", "program", "Programm", "Programma", "Program", "program", "program", "programme", "program", "Programma") -MAKE_TRANSLATION(pause, "pause", "pause time", "Pausenzeit", "Pausetijd", "Paustid", "czas przerwy", "pausetid", "temps de pause", "süreyi durdur", "pausa") -MAKE_TRANSLATION(party, "party", "party time", "Partyzeit", "Partytijd", "Partytid", "czas przyjęcia", "partytid", "temps de fête", "parti zamanı", "festivo") -MAKE_TRANSLATION(holidaytemp, "holidaytemp", "holiday temperature", "Urlaubstemperatur", "Vakantietemperatuur", "Helgtemperatur", "temperatura w trybie urlopowym", "ferietemperatur", "température vacances", "tatil sıcaklığı", "temperatura festiva") -MAKE_TRANSLATION(summermode, "summermode", "summer mode", "Sommerbetrieb", "Zomerbedrijf", "Sommarläge", "aktualny tryb lato/zima", "sommermodus", "mode été", "yaz modu", "funzionamento estivo") -MAKE_TRANSLATION(holidaymode, "holidaymode", "holiday mode", "Urlaubsbetrieb", "Vakantiebedrijf", "Helgläge", "tryb urlopowy", "feriemodus", "mode vacances", "tatil modu", "modalita vacanze") -MAKE_TRANSLATION(flowtempoffset, "flowtempoffset", "flow temperature offset for mixer", "Vorlauftemperaturanhebung", "Mixer aanvoertemperatuur offset", "Temperaturkorrigering Flödestemp. Blandningsventil", "korekta temperatury przepływu dla miksera", "temperaturkorrigering av blandingsventil", "décalage température de bascule pour mélangeur", "karıştırıcı için akış sıcaklığı farkı", "aumento della temperatura di ritorno") -MAKE_TRANSLATION(reducemode, "reducemode", "reduce mode", "Absenkmodus", "Gereduceerde modus", "Reducerat Läge", "tryb zredukowany/obniżony", "redusert modus", "mode réduction", "düşürme modu", "modalità assente") -MAKE_TRANSLATION(noreducetemp, "noreducetemp", "no reduce below temperature", "Durchheizen unter", "Reduceermodus onderbreken onder", "Inaktivera reducering under", "bez redukcji poniżej temperatury", "inaktiver redusert nedre temp", "pas de réduction en dessous température", "bu sıcaklığın altına düşürme", "non ridurre temperatura sotto") -MAKE_TRANSLATION(reducetemp, "reducetemp", "off/reduce switch temperature", "Absenkmodus unter", "Onderste afschakeltemperatuur", "Avslag/Reducera under", "tryb zredukowany poniżej temperatury", "nedre avstengningstemperatur", "arrêt/réduction température bascule", "sıcaklık kapama/düşürme modu", "interruttore riduzione temperatura") -MAKE_TRANSLATION(vacreducetemp, "vacreducetemp", "vacations off/reduce switch temperature", "Urlaub Absenkmodus unter", "Vakantiemodus onderste afschakeltemperatuur", "Helg Avslag/Reducering under", "tryb urlopowy poniżej temperatury", "feriemodus nedre utkoblingstemperatur", "vacances – arrêt/réduction température bascule", "tatil sıcaklık kapama/düşürme modu", "interruttore riduzione temperatura vacanze") -MAKE_TRANSLATION(vacreducemode, "vacreducemode", "vacations reduce mode", "Urlaub Absenkmodus", "Vakantie afschakelmodus", "Helg reduceringsläge", "redukcja w trakcie urlopu", "ferieavstengningsmodus", "mode réduction vacances", "tail düşürme modu", "modalita riduzione vacanze") -MAKE_TRANSLATION(nofrostmode, "nofrostmode", "nofrost mode", "Frostschutz Modus", "Vorstbeveiligingsmodus", "Frostskyddsläge", "temperatura wiodąca dla ochrony przed zamarzaniem", "frostbeskyttelsesmodus", "mode protection gel", "donma koruması modu", "Modalità protezione antigelo") -MAKE_TRANSLATION(remotetemp, "remotetemp", "room temperature from remote", "Raumtemperatur Remote", "Ruimtetemperatuur van afstandsbediening", "Rumstemperatur från fjärr", "temperatura w pomieszczeniu (z termostatu)", "romstemperatur fra fjernbetjening", "température pièce depuis télécommande", "uzaktan oda sıcaklığı", "temperatura ambiente da remoto") -MAKE_TRANSLATION(remotehum, "remotehum", "room humidity from remote", "Raumfeuchte Remote", "", "", "wilgotność w pomieszczeniu (z termostatu)", "", "", "uzaktan kumandadan oda nemi", "") // TODO translate -MAKE_TRANSLATION(wwHolidays, "wwholidays", "holiday dates", "Feiertage", "Feestdagen", "Helgdagar", "dni świąteczne", "feriedager varmtvann", "dates vacances", "tatil günleri", "feste pubbliche") -MAKE_TRANSLATION(wwVacations, "wwvacations", "vacation dates", "Urlaubstage", "Vakantiedagen", "Semesterdatum Varmvatten", "dni urlopowe", "ferie dato varmtvann", "dates vacances", "izin günleri", "date vacanze") -MAKE_TRANSLATION(holidays, "holidays", "holiday dates", "Feiertage", "Feestdagen", "Helgdatum", "święta", "helligdager", "dates vacances", "tatil günleri", "date feste pubbliche") -MAKE_TRANSLATION(vacations, "vacations", "vacation dates", "Urlaubstage", "Vakantiedagen", "Semesterdatum", "urlop", "feriedager", "dates vacances", "izin günleri", "date vacanze") -MAKE_TRANSLATION(wwprio, "wwprio", "dhw priority", "WW-Vorrang", "Prioriteit warm water", "Prioritera Varmvatten", "priorytet dla c.w.u.", "prioroter varmtvann", "priorité ecs", "sıcak kullanım suyu önceliği", "priorita acqua calda ") -MAKE_TRANSLATION(nofrostmode1, "nofrostmode1", "nofrost mode", "Frostschutz", "Vorstbeveiligingsmodus", "Frostskyddsläge", "ochrona przed zamarzaniem", "frostbeskyttelse", "mode protection gel", "donma koruması modu 1", "modalita protezione antigelo") -MAKE_TRANSLATION(reducehours, "reducehours", "duration for nighttemp", "Dauer Nachttemp.", "Duur nachtverlaging", "Timmar Nattsänkning", "czas trwania trybu nocnego", "timer nattsenkning", "durée température nuit", "gece sıcaklığı süresi", "durata temperatura notturna") -MAKE_TRANSLATION(reduceminutes, "reduceminutes", "remaining time for nightmode", "Restzeit Nachttemp.", "Resterende tijd nachtverlaging", "Återstående Tid Nattläge", "czas do końca trybu nocnego", "gjenværende tid i nattstilling", "temps restant mode nuit", "gece modu için kalan süre", "temperatura notturna residua") -MAKE_TRANSLATION(switchonoptimization, "switchonoptimization", "switch-on optimization", "Einschaltoptimierung", "Inschakeloptimalisering", "Växlingsoptimering", "optymalizacja załączania", "slå på optimalisering", "optimisation mise en marche", "optimizasyonu aç", "ottimizzazione all'accensione") +MAKE_TRANSLATION(selRoomTemp, "seltemp", "selected room temperature", "Sollwert Raumtemperatur", "Streeftemperatuur kamer", "Vald Rumstemperatur", "zadana temperatura w pomieszczeniu", "valgt rumstemperatur", "température ambiante sélectionnée", "seçili oda sıcaklığı", "temperatura ambiente selezionata", "zvolená izbová teplota") +MAKE_TRANSLATION(roomTemp, "currtemp", "current room temperature", "aktuelle Raumtemperatur", "Huidige kamertemperatuur", "Aktuell Rumstemperatur", "temperatura w pomieszczeniu", "gjeldende romstemperatur", "température ambiante actuelle", "güncel oda sıcaklığı", "temperatura ambiente attuale", "aktuálna izbová teplota") +MAKE_TRANSLATION(mode, "mode", "mode", "Modus", "Modus", "Läge", "sposób sterowania", "modus", "mode", "mod", "modalità", "režim") +MAKE_TRANSLATION(modetype, "modetype", "mode type", "Modus Typ", "Type modus", "Typ av läge", "aktualny tryb pracy", "modusrype", "type mode", "mod tipi", "tipo di modalita", "typ režimu") +MAKE_TRANSLATION(fastheatup, "fastheatup", "fast heatup", "schnelles Aufheizen", "Snel opwarmen", "Snabb Uppvärmning", "szybkie nagrzewanie", "rask oppvarming", "chauffage rapide", "hızlı ısıtma", "riscaldamento rapido", "rýchle zahriatie") +MAKE_TRANSLATION(daytemp, "daytemp", "day temperature", "Tagestemperatur", "temperatuur dag", "Dagstemperatur", "temperatura w dzień", "dagtemperatur", "température jour", "gündüz sıcaklığı", "temperatura giornaliera", "denná teplota") +MAKE_TRANSLATION(daylowtemp, "daytemp2", "day temperature T2", "Tagestemperatur T2", "Temperatuur dag T2", "Dagstemperatur T2", "temperatura w dzień T2", "dagtemperatur T2", "température jour T2", "gündüz sıcaklığı T2", "temperatura giornaliera T2", "denná teplota T2") +MAKE_TRANSLATION(daymidtemp, "daytemp3", "day temperature T3", "Tagestemperatur T3", "Temperatuur dag T3", "Dagstemperatur T3", "temperatura w dzień T3", "dagtemperatur T3", "température jour T3", "gündüz sıcaklığı T3", "temperatura giornaliera T3", "denná teplota T3") +MAKE_TRANSLATION(dayhightemp, "daytemp4", "day temperature T4", "Tagestemperatur T4", "Temperatuur dag T4", "Dagstemperatur T4", "temperatura w dzień T4", "dagtemperatur T4", "température jour T4", "gündüz sıcaklığı T4", "temperatura giornaliera T4", "denná teplota T4") +MAKE_TRANSLATION(heattemp, "heattemp", "heat temperature", "Heizen Temperatur", "Temperatuur verwarming", "Temperatur Uppvärmning", "temperatura ogrzewania", "oppvarmingstemperatur", "température chauffage", "ısıtma sıcaklığı", "temperatura riscaldamento", "teplota ohrevu") +MAKE_TRANSLATION(nighttemp, "nighttemp", "night temperature", "Nachttemperatur", "Nachttemperatuur", "Nattemperatur", "temperatura w nocy", "nattemperatur", "température de nuit", "gece sıcaklığı", "temperatura notturna", "nočná teplota") +MAKE_TRANSLATION(nighttemp2, "nighttemp", "night temperature T1", "Nachttemperatur T1", "Nachttemperatuur T1", "Nattemperatur T1", "temperatura w nocy T1", "nattemperatur T1", "température nuit T1", "gece sıcaklığı T1", "temperatura notturna T1", "nočná teplota T1") +MAKE_TRANSLATION(ecotemp, "ecotemp", "eco temperature", "eco Temperatur", "Temperatuur eco", "Eko-temperatur", "temperatura w trybie eko", "øko temperatur", "température éco", "eko sıcaklık", "Temperatura eco", "eko teplota") +MAKE_TRANSLATION(manualtemp, "manualtemp", "manual temperature", "manuelle Temperatur", "Temperatuur handmatig", "Temperatur Manuell", "temperatura ustawiona ręcznie", "manuell temperatur", "température manuelle", "manuel sıcaklık", "temperatura manuale", "manuálna teplota") +MAKE_TRANSLATION(tempautotemp, "tempautotemp", "temporary set temperature automode", "temporäre Solltemperatur", "Streeftemperatuur automodus tijdelijk", "Temporär Aktivering av Auto-läge", "zadana temperatura w pomieszczeniu w trybie \"auto\" (tymczasowa)", "temporær valgt temp i automodus", "température temporaire mode automatique", "geçici ayarlı sıcaklık otomatik mod", "impostare temporaneamente temperatura automatica", "automatický režim dočasnej nastavenej teploty") +MAKE_TRANSLATION(remoteseltemp, "remoteseltemp", "temporary set temperature from remote", "temporäre Solltemperatur Remote", "Temperatuur van afstandsbedieding", "Temperatur från fjärruppkoppling", "zadana zdalnie temperatura a pomieszczeniu (tymczasowa)", "temporær valgt temp fra fjernbetjening", "température temporaire depuis télécommande", "geçici ayarlı sıcaklık uzaktan", "Temperatura temporanea da remoto", "dočasne nastavená teplota z diaľkového ovládania") +MAKE_TRANSLATION(comforttemp, "comforttemp", "comfort temperature", "Komforttemperatur", "Comforttemperatuur", "Komforttemperatur", "temperatura w trybie komfort", "komforttemperatur", "température confort", "konfor sıcaklığı", "temperatura comfort", "komfortná teplota") +MAKE_TRANSLATION(summertemp, "summertemp", "summer temperature", "Sommertemperatur", "Zomertemperatuur", "Sommartemperatur", "temperatura przełączania lato/zima", "Sommertemperatur", "température été", "yaz sıcaklığı", "temperatura estiva", "letná teplota") +MAKE_TRANSLATION(designtemp, "designtemp", "design temperature", "Auslegungstemperatur", "Ontwerptemperatuur", "Design-temperatur", "temperatura projektowa", "designtemperatur", "température conception", "özel sıcaklık", "temperatura predefinita", "návrhová teplota") +MAKE_TRANSLATION(offsettemp, "offsettemp", "offset temperature", "Temperaturanhebung", "Temperatuur offset", "Temperaturkorrigering", "korekta temperatury", "temperaturkorrigering", "température offset", "artış sıcaklığı", "aumento della temperatura", "offsetová teplota") +MAKE_TRANSLATION(minflowtemp, "minflowtemp", "min flow temperature", "min Vorlauftemperatur", "Minimale aanvoertemperatuur", "Min Flödestemperatur", "minimalna temperatura zasilania", "min turtemperatur", "température min flux", "minimun akış sıcaklığı", "temperatura minima di mandata", "min. výstupná teplota") +MAKE_TRANSLATION(maxflowtemp, "maxflowtemp", "max flow temperature", "max Vorlauftemperatur", "Maximale aanvoertemperatuur", "Max Flödestemperatur", "maksymalna temperatura zasilania", "maks turtemperatur", "température max flux", "maksimum akış sıcaklığı", "temperatura massima di mandata", "maximálna teplota prívodu") +MAKE_TRANSLATION(roominfluence, "roominfluence", "room influence", "Raumeinfluss", "Ruimteinvloed", "Rumspåverkan", "wpływ pomieszczenia", "rominnflytelse", "influence pièce", "oda etkisi", "influenza della camera", "vplyv miestnosti") +MAKE_TRANSLATION(roominfl_factor, "roominflfactor", "room influence factor", "Raumeinflussfaktor", "Factor ruimteinvloed", "Rumspåverkansfaktor", "współczynnik wpływu pomieszczenia", "rominnflytelsesfaktor", "facteur d'influence pièce", "oda etkisi faktörü", "fattore influenza camera", "faktor vplyvu miestnosti") +MAKE_TRANSLATION(curroominfl, "curroominfl", "current room influence", "aktueller Raumeinfluss", "Huidige ruimteinvloed", "Aktuell Rumspåverkan", "aktualny wpływ pomieszczenia", "gjeldende rominnflytelse", "influence actuelle pièce", "güncel oda etkisi", "fattore corrente influenza camera", "aktuálny vplyv miestnosti") +MAKE_TRANSLATION(nofrosttemp, "nofrosttemp", "nofrost temperature", "Frostschutztemperatur", "Temperatuur vorstbeveiliging", "Temperatur Frostskydd", "temperatura ochrony przed zamarzaniem", "frostbeskyttelsestemperatur", "température protection gel", "donma koruması sıcaklığı", "temperatura protezione antigelo", "nofrost teplota") +MAKE_TRANSLATION(targetflowtemp, "targetflowtemp", "target flow temperature", "berechnete Vorlauftemperatur", "Berekende aanvoertemperatuur", "Målvärde Flödestemperatur", "zadana temperatura zasilania", "målverdi turtemperatur", "température cible flux", "hedef akış sıcaklığı", "temperatura di mandata calcolata", "cieľová teplota prívodu") +MAKE_TRANSLATION(heatingtype, "heatingtype", "heating type", "Heizungstyp", "Verwarmingstype", "Värmesystem", "system grzewczy", "varmesystem", "type chauffage", "ısıtma tipi", "tipo riscaldamento", "typ vykurovania") +MAKE_TRANSLATION(summersetmode, "summersetmode", "set summer mode", "Einstellung Sommerbetrieb", "Instelling zomerbedrijf", "Aktivera Sommarläge", "tryb lato/zima", "aktiver sommermodus", "activer mode été", "yaz modu ayarı", "Impostazione della modalità estiva", "nastaviť letný režim") +MAKE_TRANSLATION(hpoperatingmode, "hpoperatingmode", "heatpump operating mode", "Wärmepumpe Betriebsmodus", "Bedrijfsmodus warmtepomp", "Värmepump Driftläge", "tryb pracy pompy ciepła", "driftsmodus varmepumpe", "mode fonctionnement pompe à chaleur", "ısı pompası çalışma modu", "Modalità di funzionamento della pompa di calore", "prevádzkový režim tepelného čerpadla") +MAKE_TRANSLATION(hpoperatingstate, "hpoperatingstate", "heatpump operating state", "WP Arbeitsweise", "Huidige modus warmtepomp", "Värmepump driftläge", "aktualny tryb pracy pompy ciepła", "driftstatus varmepumpe", "état fonctionnement pompe à chaleur", "ısı pompası çalışma durumu", "stato funzionamento pompa di calore", "prevádzkový stav tepelného čerpadla") +MAKE_TRANSLATION(controlmode, "controlmode", "control mode", "Kontrollmodus", "Comtrolemodus", "Kontrolläge", "tryb sterowania", "kontrollmodus", "mode régulation", "kontrol modu", "modalità di controllo", "kontrolný režim") +MAKE_TRANSLATION(control, "control", "control device", "Fernsteuerung", "Afstandsbedieding", "Kontrollenhet", "sterownik", "kontrollenhet", "dispositif régulation", "kontrol cihazı", "dispositivo di comando", "ovládacie zariadenie") +MAKE_TRANSLATION(roomsensor, "roomsensor", "room sensor", "Raumsensor", "Ruimtesensor", "Rumssensor", "czujnik temperatury pomieszczenia", "romsensor", "capteur pièce", "oda sensörü", "sensore ambiente", "izbový snímač") +MAKE_TRANSLATION(program, "program", "program", "Programm", "Programma", "Program", "program", "program", "programme", "program", "Programma", "program") +MAKE_TRANSLATION(pause, "pause", "pause time", "Pausenzeit", "Pausetijd", "Paustid", "czas przerwy", "pausetid", "temps de pause", "süreyi durdur", "pausa", "prestávka") +MAKE_TRANSLATION(party, "party", "party time", "Partyzeit", "Partytijd", "Partytid", "czas przyjęcia", "partytid", "temps de fête", "parti zamanı", "festivo", "čas na párty") +MAKE_TRANSLATION(holidaytemp, "holidaytemp", "holiday temperature", "Urlaubstemperatur", "Vakantietemperatuur", "Helgtemperatur", "temperatura w trybie urlopowym", "ferietemperatur", "température vacances", "tatil sıcaklığı", "temperatura festiva", "prázdninová teplota") +MAKE_TRANSLATION(summermode, "summermode", "summer mode", "Sommerbetrieb", "Zomerbedrijf", "Sommarläge", "aktualny tryb lato/zima", "sommermodus", "mode été", "yaz modu", "funzionamento estivo", "letný režim") +MAKE_TRANSLATION(holidaymode, "holidaymode", "holiday mode", "Urlaubsbetrieb", "Vakantiebedrijf", "Helgläge", "tryb urlopowy", "feriemodus", "mode vacances", "tatil modu", "modalita vacanze", "dovolenkový režim") +MAKE_TRANSLATION(flowtempoffset, "flowtempoffset", "flow temperature offset for mixer", "Vorlauftemperaturanhebung", "Mixer aanvoertemperatuur offset", "Temperaturkorrigering Flödestemp. Blandningsventil", "korekta temperatury przepływu dla miksera", "temperaturkorrigering av blandingsventil", "décalage température de bascule pour mélangeur", "karıştırıcı için akış sıcaklığı farkı", "aumento della temperatura di ritorno", "Posun teploty prívodu pre zmiešavač") +MAKE_TRANSLATION(reducemode, "reducemode", "reduce mode", "Absenkmodus", "Gereduceerde modus", "Reducerat Läge", "tryb zredukowany/obniżony", "redusert modus", "mode réduction", "düşürme modu", "modalità assente", "znížený režim") +MAKE_TRANSLATION(noreducetemp, "noreducetemp", "no reduce below temperature", "Durchheizen unter", "Reduceermodus onderbreken onder", "Inaktivera reducering under", "bez redukcji poniżej temperatury", "inaktiver redusert nedre temp", "pas de réduction en dessous température", "bu sıcaklığın altına düşürme", "non ridurre temperatura sotto", "žiadne zníženie teploty pod teplotu") +MAKE_TRANSLATION(reducetemp, "reducetemp", "off/reduce switch temperature", "Absenkmodus unter", "Onderste afschakeltemperatuur", "Avslag/Reducera under", "tryb zredukowany poniżej temperatury", "nedre avstengningstemperatur", "arrêt/réduction température bascule", "sıcaklık kapama/düşürme modu", "interruttore riduzione temperatura", "vypnúť/znížiť teplotu spínača") +MAKE_TRANSLATION(vacreducetemp, "vacreducetemp", "vacations off/reduce switch temperature", "Urlaub Absenkmodus unter", "Vakantiemodus onderste afschakeltemperatuur", "Helg Avslag/Reducering under", "tryb urlopowy poniżej temperatury", "feriemodus nedre utkoblingstemperatur", "vacances – arrêt/réduction température bascule", "tatil sıcaklık kapama/düşürme modu", "interruttore riduzione temperatura vacanze", "dovolenky vypnúť/znížiť teplotu spínača") +MAKE_TRANSLATION(vacreducemode, "vacreducemode", "vacations reduce mode", "Urlaub Absenkmodus", "Vakantie afschakelmodus", "Helg reduceringsläge", "redukcja w trakcie urlopu", "ferieavstengningsmodus", "mode réduction vacances", "tail düşürme modu", "modalita riduzione vacanze", "režim zníženia dovoleniek") +MAKE_TRANSLATION(nofrostmode, "nofrostmode", "nofrost mode", "Frostschutz Modus", "Vorstbeveiligingsmodus", "Frostskyddsläge", "temperatura wiodąca dla ochrony przed zamarzaniem", "frostbeskyttelsesmodus", "mode protection gel", "donma koruması modu", "Modalità protezione antigelo", "nofrost režim") +MAKE_TRANSLATION(remotetemp, "remotetemp", "room temperature from remote", "Raumtemperatur Remote", "Ruimtetemperatuur van afstandsbediening", "Rumstemperatur från fjärr", "temperatura w pomieszczeniu (z termostatu)", "romstemperatur fra fjernbetjening", "température pièce depuis télécommande", "uzaktan oda sıcaklığı", "temperatura ambiente da remoto", "izbová teplota z diaľkového ovládania") +MAKE_TRANSLATION(remotehum, "remotehum", "room humidity from remote", "Raumfeuchte Remote", "", "", "wilgotność w pomieszczeniu (z termostatu)", "", "", "uzaktan kumandadan oda nemi", "", "Vlhkosť v miestnosti z diaľkového ovládania") // TODO translate +MAKE_TRANSLATION(wwHolidays, "wwholidays", "holiday dates", "Feiertage", "Feestdagen", "Helgdagar", "dni świąteczne", "feriedager varmtvann", "dates vacances", "tatil günleri", "feste pubbliche", "sviatočné termíny") +MAKE_TRANSLATION(wwVacations, "wwvacations", "vacation dates", "Urlaubstage", "Vakantiedagen", "Semesterdatum Varmvatten", "dni urlopowe", "ferie dato varmtvann", "dates vacances", "izin günleri", "date vacanze", "termíny dovolenky") +MAKE_TRANSLATION(holidays, "holidays", "holiday dates", "Feiertage", "Feestdagen", "Helgdatum", "święta", "helligdager", "dates vacances", "tatil günleri", "date feste pubbliche", "sviatočné termíny") +MAKE_TRANSLATION(vacations, "vacations", "vacation dates", "Urlaubstage", "Vakantiedagen", "Semesterdatum", "urlop", "feriedager", "dates vacances", "izin günleri", "date vacanze", "termíny dovolenky") +MAKE_TRANSLATION(wwprio, "wwprio", "dhw priority", "WW-Vorrang", "Prioriteit warm water", "Prioritera Varmvatten", "priorytet dla c.w.u.", "prioroter varmtvann", "priorité ecs", "sıcak kullanım suyu önceliği", "priorita acqua calda", "Priorita TÚV") +MAKE_TRANSLATION(nofrostmode1, "nofrostmode1", "nofrost mode", "Frostschutz", "Vorstbeveiligingsmodus", "Frostskyddsläge", "ochrona przed zamarzaniem", "frostbeskyttelse", "mode protection gel", "donma koruması modu 1", "modalita protezione antigelo", "nofrost režim") +MAKE_TRANSLATION(reducehours, "reducehours", "duration for nighttemp", "Dauer Nachttemp.", "Duur nachtverlaging", "Timmar Nattsänkning", "czas trwania trybu nocnego", "timer nattsenkning", "durée température nuit", "gece sıcaklığı süresi", "durata temperatura notturna", "trvanie nočnej teploty") +MAKE_TRANSLATION(reduceminutes, "reduceminutes", "remaining time for nightmode", "Restzeit Nachttemp.", "Resterende tijd nachtverlaging", "Återstående Tid Nattläge", "czas do końca trybu nocnego", "gjenværende tid i nattstilling", "temps restant mode nuit", "gece modu için kalan süre", "temperatura notturna residua", "zostávajúci čas pre nočný režim") +MAKE_TRANSLATION(switchonoptimization, "switchonoptimization", "switch-on optimization", "Einschaltoptimierung", "Inschakeloptimalisering", "Växlingsoptimering", "optymalizacja załączania", "slå på optimalisering", "optimisation mise en marche", "optimizasyonu aç", "ottimizzazione all'accensione", "optimalizácia pri zapnutí") -MAKE_TRANSLATION(hpmode, "hpmode", "HP Mode", "WP Modus", "Modus warmtepomp", "", "tryb pracy pompy ciepła", "", "", "yüksek güç modu", "Modalità Termopompa") // TODO translate -MAKE_TRANSLATION(dewoffset, "dewoffset", "dew point offset", "Taupunkt Differenz", "Offset dauwpunt", "", "przesunięcie punktu rosy", "", "", "çiğ noktası göreli", "differenza del punto di rugiada") // TODO translate -MAKE_TRANSLATION(roomtempdiff, "roomtempdiff", "room temp difference", "Raumtemperatur Differenz", "Verschiltemperatuur kamertemp", "", "różnica temp. pomieszczenia", "", "", "oda sıcaklığı farkı", "differenza temperatura ambiente") // TODO translate -MAKE_TRANSLATION(hpminflowtemp, "hpminflowtemp", "HP min. flow temp.", "WP minimale Vorlauftemperatur", "Minimale aanvoertemperatuur WP", "", "pompa ciepła minimalna temp przepływu ", "", "", "yüksek güç minimum akış sıcaklığı", "temperatura minima di mandata") // TODO translate -MAKE_TRANSLATION(hpcooling, "cooling", "cooling", "Kühlen", "Koelen", "Kyler", "chłodzenie", "kjøling", "refroidissement", "soğuma", "raffreddamento") +MAKE_TRANSLATION(hpmode, "hpmode", "HP Mode", "WP Modus", "Modus warmtepomp", "", "tryb pracy pompy ciepła", "", "", "yüksek güç modu", "Modalità Termopompa", "Režim HP") // TODO translate +MAKE_TRANSLATION(dewoffset, "dewoffset", "dew point offset", "Taupunkt Differenz", "Offset dauwpunt", "", "przesunięcie punktu rosy", "", "", "çiğ noktası göreli", "differenza del punto di rugiada", "posun rosného bodu") // TODO translate +MAKE_TRANSLATION(roomtempdiff, "roomtempdiff", "room temp difference", "Raumtemperatur Differenz", "Verschiltemperatuur kamertemp", "", "różnica temp. pomieszczenia", "", "", "oda sıcaklığı farkı", "differenza temperatura ambiente", "rozdiel izbovej teploty") // TODO translate +MAKE_TRANSLATION(hpminflowtemp, "hpminflowtemp", "HP min. flow temp.", "WP minimale Vorlauftemperatur", "Minimale aanvoertemperatuur WP", "", "pompa ciepła, min. temperatura przepływu", "", "", "yüksek güç minimum akış sıcaklığı", "temperatura minima di mandata", "VT min. teplota prietoku.") // TODO translate +MAKE_TRANSLATION(hpcooling, "cooling", "cooling", "Kühlen", "Koelen", "Kyler", "chłodzenie", "kjøling", "refroidissement", "soğuma", "raffreddamento", "chladenie") // heatpump and RC100H -MAKE_TRANSLATION(airHumidity, "airhumidity", "relative air humidity", "relative Luftfeuchte", "Relatieve luchtvochtigheid", "Relativ Luftfuktighet", "wilgotność względna w pomieszczeniu", "luftfuktighet", "humidité relative air", "havadaki bağıl nem", "umidità relativa aria") -MAKE_TRANSLATION(dewTemperature, "dewtemperature", "dew point temperature", "Taupunkttemperatur", "Dauwpunttemperatuur", "Daggpunkt", "punkt rosy w pomieszczeniu", "duggtemperatur", "température point rosée", "çiğ noktası sıcaklığı", "temperatura del punto di rugiada") -MAKE_TRANSLATION(battery, "battery", "battery", "Batterie", "", "", "bateria", "", "", "", "") +MAKE_TRANSLATION(airHumidity, "airhumidity", "relative air humidity", "relative Luftfeuchte", "Relatieve luchtvochtigheid", "Relativ Luftfuktighet", "wilgotność względna w pomieszczeniu", "luftfuktighet", "humidité relative air", "havadaki bağıl nem", "umidità relativa aria", "relatívna vlhkosť vzduchu") +MAKE_TRANSLATION(dewTemperature, "dewtemperature", "dew point temperature", "Taupunkttemperatur", "Dauwpunttemperatuur", "Daggpunkt", "punkt rosy w pomieszczeniu", "duggtemperatur", "température point rosée", "çiğ noktası sıcaklığı", "temperatura del punto di rugiada", "teplota rosného bodu") +MAKE_TRANSLATION(battery, "battery", "battery", "Batterie", "", "", "bateria", "", "", "", "", "batéria") // TODO translate // mixer -MAKE_TRANSLATION(flowSetTemp, "flowsettemp", "setpoint flow temperature", "Sollwert Vorlauftemperatur", "Streefwaarde aanvoertemperatuur", "Vald flödestemperatur", "zadana temperatura zasilania", "valgt turtemperatur", "consigne température flux", "akış sıcaklığı ayarı", "Setpoint temperatura di mandata") -MAKE_TRANSLATION(flowTempHc, "flowtemphc", "flow temperature (TC1)", "Vorlauftemperatur HK (TC1)", "Aanvoertemperatuut circuit (TC1)", "Flödestemperatur (TC1)", "temperatura zasilania (TC1)", "turtemperatur (TC1)", "température flux (TC1)", "akış sıcaklığı (TC1)", "temperatura di mandata (TC1)") -MAKE_TRANSLATION(pumpStatus, "pumpstatus", "pump status (PC1)", "Pumpenstatus HK (PC1)", "pompstatus circuit (PC1)", "Pumpstatus (PC1)", "status pompy (PC1)", "pumpestatus (PC1)", "état pompe (PC1)", "pompa durumu (PC1)", "stato pompa (PC1)") -MAKE_TRANSLATION(mixerStatus, "valvestatus", "mixing valve actuator (VC1)", "Mischerventil Position (VC1)", "positie mixerklep (VC1)", "Shuntventil Status (VC1)", "siłownik zaworu mieszającego (VC1)", "shuntventil status (VC1)", "actionnement vanne mélangeur (VC1)", "karışım vanası aktüatörü (VC1)", "posizione valvola miscela (VC1)") -MAKE_TRANSLATION(flowTempVf, "flowtempvf", "flow temperature in header (T0/Vf)", "Vorlauftemperatur am Verteiler (T0/Vf)", "aanvoertemperatuur verdeler (T0/Vf)", "Flödestemperatur Fördelare (T0/Vf)", "temperatura zasilania na rozdzielaczu (T0/Vf)", "turtemperatur ved fordeleren (T0/Vf)", "température départ collecteur (T0/Vf)", "başlıkta akış sıcaklığı", "Temperatura di mandata al distributore (T0/Vf)") -MAKE_TRANSLATION(mixerSetTime, "valvesettime", "time to set valve", "Zeit zum Einstellen des Ventils", "Inschakeltijd mengklep", "Inställningstid Ventil", "czas na ustawienie zaworu", "instillningstid ventil", "délai activation vanne", "vana ayar zamanı", "ritardo attivazione valvola") +MAKE_TRANSLATION(flowSetTemp, "flowsettemp", "setpoint flow temperature", "Sollwert Vorlauftemperatur", "Streefwaarde aanvoertemperatuur", "Vald flödestemperatur", "zadana temperatura zasilania", "valgt turtemperatur", "consigne température flux", "akış sıcaklığı ayarı", "Setpoint temperatura di mandata", "požadovaná hodnota výstupnej teploty") +MAKE_TRANSLATION(flowTempHc, "flowtemphc", "flow temperature (TC1)", "Vorlauftemperatur HK (TC1)", "Aanvoertemperatuut circuit (TC1)", "Flödestemperatur (TC1)", "temperatura zasilania (TC1)", "turtemperatur (TC1)", "température flux (TC1)", "akış sıcaklığı (TC1)", "temperatura di mandata (TC1)", "teplota prívodu (TC1)") +MAKE_TRANSLATION(pumpStatus, "pumpstatus", "pump status (PC1)", "Pumpenstatus HK (PC1)", "pompstatus circuit (PC1)", "Pumpstatus (PC1)", "status pompy (PC1)", "pumpestatus (PC1)", "état pompe (PC1)", "pompa durumu (PC1)", "stato pompa (PC1)", "stav čerpadla (PC1)") +MAKE_TRANSLATION(mixerStatus, "valvestatus", "mixing valve actuator (VC1)", "Mischerventil Position (VC1)", "positie mixerklep (VC1)", "Shuntventil Status (VC1)", "siłownik zaworu mieszającego (VC1)", "shuntventil status (VC1)", "actionnement vanne mélangeur (VC1)", "karışım vanası aktüatörü (VC1)", "posizione valvola miscela (VC1)", "pohon zmiešavacieho ventilu (VC1)") +MAKE_TRANSLATION(flowTempVf, "flowtempvf", "flow temperature in header (T0/Vf)", "Vorlauftemperatur am Verteiler (T0/Vf)", "aanvoertemperatuur verdeler (T0/Vf)", "Flödestemperatur Fördelare (T0/Vf)", "temperatura zasilania na rozdzielaczu (T0/Vf)", "turtemperatur ved fordeleren (T0/Vf)", "température départ collecteur (T0/Vf)", "başlıkta akış sıcaklığı", "Temperatura di mandata al distributore (T0/Vf)", "teplota prívodu v zberači (T0/Vf)") +MAKE_TRANSLATION(mixerSetTime, "valvesettime", "time to set valve", "Zeit zum Einstellen des Ventils", "Inschakeltijd mengklep", "Inställningstid Ventil", "czas na ustawienie zaworu", "instillningstid ventil", "délai activation vanne", "vana ayar zamanı", "ritardo attivazione valvola", "čas na nastavenie ventilu") // mixer prefixed with wwc -MAKE_TRANSLATION(wwPumpStatus, "pumpstatus", "pump status in assigned wwc (PC1)", "Pumpenstatus des wwk (PC1)", "Pompstatus in WW circuit (PC1)", "Pumpstatus i VV-krets (PC1)", "stan pompy w obwodzie c.w.u. (PC1)", "Pumpestatus i VV-krets (PC1)", "état pompe wwc (PC1)", "Kullanım suyu devresindeki(PC1) pompa durumu", "stato pompa assegnato nel ciruito WW (PC1)") -MAKE_TRANSLATION(wwTempStatus, "wwtempstatus", "temperature switch in assigned wwc (MC1)", "Temperaturschalter des wwk (MC1)", "Temperatuurschakeling in WW circuit (MC1)", "Temperaturventil i VV-krets (MC1)", "temperatura w obwodzie c.w.u. (MC1)", "temperaturventil i VV-krets (MC1)", "température bascule wwc (MC1).", "atanmış sıcak su devresinde sıcaklık", "interruttore di temperatura del wwk (MC1)") -MAKE_TRANSLATION(wwTemp, "wwtemp", "current temperature", "aktuelle Temperatur", "huidige temperatuur", "Aktuell Temperatur", "temperatura c.w.u.", "aktuell temperatur", "température actuelle", "güncel sıcaklık", "temperatura attuale") +MAKE_TRANSLATION(wwPumpStatus, "pumpstatus", "pump status in assigned wwc (PC1)", "Pumpenstatus des wwk (PC1)", "Pompstatus in WW circuit (PC1)", "Pumpstatus i VV-krets (PC1)", "stan pompy w obwodzie c.w.u. (PC1)", "Pumpestatus i VV-krets (PC1)", "état pompe wwc (PC1)", "Kullanım suyu devresindeki(PC1) pompa durumu", "stato pompa assegnato nel ciruito WW (PC1)", "stav čerpadla v pridelenom wwc (PC1)") +MAKE_TRANSLATION(wwTempStatus, "wwtempstatus", "temperature switch in assigned wwc (MC1)", "Temperaturschalter des wwk (MC1)", "Temperatuurschakeling in WW circuit (MC1)", "Temperaturventil i VV-krets (MC1)", "temperatura w obwodzie c.w.u. (MC1)", "temperaturventil i VV-krets (MC1)", "température bascule wwc (MC1).", "atanmış sıcak su devresinde sıcaklık", "interruttore di temperatura del wwk (MC1)", "teplotný spínač v priradenej wwc (MC1)") +MAKE_TRANSLATION(wwTemp, "wwtemp", "current temperature", "aktuelle Temperatur", "huidige temperatuur", "Aktuell Temperatur", "temperatura c.w.u.", "aktuell temperatur", "température actuelle", "güncel sıcaklık", "temperatura attuale", "aktuálna teplota") // mixer pool -MAKE_TRANSLATION(poolSetTemp, "poolsettemp", "pool set temperature", "Pool Solltemperatur", "Streeftemperatuur zwembad", "Pool Temperatur Börvärde", "zadana temperatura basenu", "valgt temp basseng", "température consigne piscine", "hedef havuz sıcaklığı", "temperatura nominale piscina") -MAKE_TRANSLATION(poolTemp, "pooltemp", "pool temperature", "Pool Temperatur", "Zwembadtemperatuur", "Pooltemperatur", "temperatura basenu", "bassengtemperatur", "température piscine", "havuz sıcaklığı", "temperatura piscina") -MAKE_TRANSLATION(poolShuntStatus, "poolshuntstatus", "pool shunt status opening/closing", "Pool Ventil öffnen/schließen", "Zwembadklep status openen/sluiten", "Pool Shunt-status öppnen/stängd", "status bocznika basenu", "bassengshunt-status åpen/stengt", "état shunt piscine ouvert/fermé", "havuz şant durumu açılıyor/kapanıyor", "aprire/chiudere valvola regolazione piscina") -MAKE_TRANSLATION(poolShunt, "poolshunt", "pool shunt open/close (0% = pool / 100% = heat)", "Pool Ventil Öffnung", "Mengklep zwembad stand", "Pool Shunt Öppen/Stängd", "bocznik basenu (0% = basen / 100% = grzanie)", "bassengshunt åpen/stengt (0% = basseng / 100% = varme)", "ouverture/fermeture shunt piscine (0% = piscine / 100% = chaleur).", "havuz şant açık/kapalı (0% = havuz / 100% = ısıtma)", "valvola regolazione piscina (0% = piscina / 100% = caldo)") -MAKE_TRANSLATION(hydrTemp, "hydrTemp", "hydraulic header temperature", "Verteilertemperatur", "Temperatuur open verdeler", "Fördelartemperatur", "temperatura kolektora hydraulicznego", "Fordelertemperatur", "température collecteur hydraulique", "hidrolik başlık sıcaklığı ", "temperatura del collettore") +MAKE_TRANSLATION(poolSetTemp, "poolsettemp", "pool set temperature", "Pool Solltemperatur", "Streeftemperatuur zwembad", "Pool Temperatur Börvärde", "zadana temperatura basenu", "valgt temp basseng", "température consigne piscine", "hedef havuz sıcaklığı", "temperatura nominale piscina", "nastavená teplota bazéna") +MAKE_TRANSLATION(poolTemp, "pooltemp", "pool temperature", "Pool Temperatur", "Zwembadtemperatuur", "Pooltemperatur", "temperatura basenu", "bassengtemperatur", "température piscine", "havuz sıcaklığı", "temperatura piscina", "teplota bazéna") +MAKE_TRANSLATION(poolShuntStatus, "poolshuntstatus", "pool shunt status opening/closing", "Pool Ventil öffnen/schließen", "Zwembadklep status openen/sluiten", "Pool Shunt-status öppnen/stängd", "status bocznika basenu", "bassengshunt-status åpen/stengt", "état shunt piscine ouvert/fermé", "havuz şant durumu açılıyor/kapanıyor", "aprire/chiudere valvola regolazione piscina", "stav bazénového bočníka otváranie/zatváranie") +MAKE_TRANSLATION(poolShunt, "poolshunt", "pool shunt open/close (0% = pool / 100% = heat)", "Pool Ventil Öffnung", "Mengklep zwembad stand", "Pool Shunt Öppen/Stängd", "bocznik basenu (0% = basen / 100% = grzanie)", "bassengshunt åpen/stengt (0% = basseng / 100% = varme)", "ouverture/fermeture shunt piscine (0% = piscine / 100% = chaleur).", "havuz şant açık/kapalı (0% = havuz / 100% = ısıtma)", "valvola regolazione piscina (0% = piscina / 100% = caldo)", "pohyb bazéna otvoriť/zatvoriť (0 % = bazén / 100 % = teplo)") +MAKE_TRANSLATION(hydrTemp, "hydrTemp", "hydraulic header temperature", "Verteilertemperatur", "Temperatuur open verdeler", "Fördelartemperatur", "temperatura kolektora hydraulicznego", "Fordelertemperatur", "température collecteur hydraulique", "hidrolik başlık sıcaklığı", "temperatura del collettore", "teplota hydraulickej hlavice") // solar -MAKE_TRANSLATION(cylMiddleTemp, "cylmiddletemp", "cylinder middle temperature (TS3)", "Speichertemperatur Mitte (TS3)", "Zonneboilertemperatuur midden (TS3)", "Cylindertemperatur Mitten (TS3)", "temperatura w środku zasobnika (TS3)", "beredertemperatur i midten (TS3)", "température moyenne cylindre (TS3)", "orta depolama sıcaklığı (TS3)", "temperatura di conservazione media accumulo (TS3)") -MAKE_TRANSLATION(retHeatAssist, "retheatassist", "return temperature heat assistance (TS4)", "Rücklaufanhebungs-Temp. (TS4)", "Retourtemperatuur verwarmingsassistentie (TS4)", "Returtemperatur värmestöd (TS4)", "temperatura powrotu wspomagania grzania (TS4)", "returtemperatur varmestøtte (TS4)", "température retour de assistance thermique (TS4)", "geri dönüş sıcaklığı artışı", "temperatura ritorno scambiatore (TS4)") -MAKE_TRANSLATION(m1Valve, "heatassistvalve", "heat assistance valve (M1)", "Ventil Heizungsunterstützung (M1)", "Klep verwarmingsassistentie (M1)", "Uppvärmningsstöd Ventil (M1)", "zawór wspomagania grzania (M1)", "varmehjelpsventil (M1)", "vanne assistance thermique (M1)", "ısıtma yardım vanası (M1)", "valvola scambiatore (M1)") -MAKE_TRANSLATION(m1Power, "heatassistpower", "heat assistance valve power (M1)", "Ventilleistung Heizungsunterstützung (M1)", "Vermogen klep verwarmingsassistentie (M1)", "Uppvärmningsstöd Ventil Effekt (M1)", "moc zaworu wspomagania grzania (M1)", "varmehjelpsventileffekt (M1)", "puissance vanne assistance thermique (M1)", "ısıtma yardım vanası gücü (M1)", "potenza valvola scambiatore (M1)") -MAKE_TRANSLATION(pumpMinMod, "pumpminmod", "minimum pump modulation", "minimale Pumpenmodulation", "Minimale pompmodulatie", "Min Pumpmodulering", "minimalna modulacja pompy", "minimum pumpmodulering", "modulation minimale pompe", "minimum pompa modülasyonu", "modulazione minima pompa") -MAKE_TRANSLATION(maxFlow, "maxflow", "maximum solar flow", "maximaler Durchfluss", "Maximale doorstroom solar", "Max Flöde Solpanel", "maksymalny przepływ solarów", "maks strømming solpanel ", "débit solaire maximum", "minimum G.E. akışı", "portata massima solare") -MAKE_TRANSLATION(solarPower, "solarpower", "actual solar power", "aktuelle Solarleistung", "Huidig solar vermogen", "Aktuellt Sol-effekt", "aktualna moc solarów", "aktuell soleffekt", "puissance solaire réelle", "gerçek G.E. gücü", "potenza attuale solare") -MAKE_TRANSLATION(solarPumpTurnonDiff, "turnondiff", "pump turn on difference", "Einschalthysterese Pumpe", "Inschakelhysterese pomp", "Aktiveringshysteres Pump", "histereza załączenia pompy", "slå på hysteresepumpe", "différence activation pompe", "pompa devreye alma farkı", "isteresi di accensione pompa") -MAKE_TRANSLATION(solarPumpTurnoffDiff, "turnoffdiff", "pump turn off difference", "Ausschalthysterese Pumpe", "Uitschakelhysterese pomp", "Avslagshysteres Pump", "histereza włączenia pompy", "slå av hysteresepumpe", "différence arrêt pompe", "pompa kapama farkı", "isteresi di spegnimento pompa") -MAKE_TRANSLATION(pump2MinMod, "pump2minmod", "minimum pump 2 modulation", "minimale Modulation Pumpe 2", "Minimale modulatie pomp 2", "Min Modulering Pump 2", "minimalna modulacja pompy 2", "minimum pumpmodulering 2", "modulation minimale pompe 2", "minimum pompa 2 modülasyonu", "modulazione minima pompa 2") -MAKE_TRANSLATION(solarPump2TurnonDiff, "turnondiff2", "pump 2 turn on difference", "Einschalthysterese Pumpe 2", "Inschakelhysterese pomp 2", "Aktiveringshysteres Pump 2", "histereza załączenia pompy 2", "slå på hysteresepumpe 2", "différence activation pompe 2", "pompa 2 devreye alma farkı", "isteresi di accensione pompa 2") -MAKE_TRANSLATION(solarPump2TurnoffDiff, "turnoffdiff2", "pump 2 turn off difference", "Ausschalthysterese Pumpe 2", "Uitschakelhysterese pomp 2", "Avslagshysteres Pump 2", "histereza wyłączenia pompy 2", "slå av hysteresepumpe 2", "différence arrêt pompe 2", "pompa 2 kapama farkı", "isteresi di spegnimento pompa") -MAKE_TRANSLATION(collectorTemp, "collectortemp", "collector temperature (TS1)", "Kollektortemperatur (TS1)", "Collectortemperatuur (TS1)", "Kollektor Temperatur (TS1)", "temperatura kolektora (TS1)", "kollektor temperatur (TS1)", "température collecteur (TS1)", "kollektör sıcaklığı (TS1)", "temperatura collettore (TS1)") -MAKE_TRANSLATION(collector2Temp, "collector2temp", "collector 2 temperature (TS7)", "Kollector 2 Temperatur (TS7)", "Collector 2 temperatuur (TS7)", "Kollektor 2 Temperatur (TS7)", "temperatura kolektora 2 (TS7)", "kollektor 2 temperatur (TS7)", "température collecteur 2 (TS7)", "kollektör 2 sıcaklığı (TS2)", "temperatura collettore 2 (TS7)") -MAKE_TRANSLATION(cylBottomTemp, "cylbottomtemp", "cylinder bottom temperature (TS2)", "Speicher Bodentemperatur (TS2)", "Bodemtemperatuur zonneboiler (TS2)", "Cylindertemperatur Botten (TS2)", "temperatura na spodzie zasobnika (TS2)", "beredertemp i bunn (TS2)", "température fond de cylindre (TS2)", "alt depolama sıcaklığıc(TS2)", "temperatura inferiore accumulo (TS2)") -MAKE_TRANSLATION(cyl2BottomTemp, "cyl2bottomtemp", "second cylinder bottom temperature (TS5)", "2. Speicher Bodentemperatur (TS5)", "Bodemtemperatuur 2e boiler", "Sekundär Cylindertemperatur Botten (TS5)", "temperatura na spodzie drugiego zasobnika (TS5)", "skundær beredertemp i bunn (TS5)", "température fond de cylindre (TS5)", "ikinci alt depolama sıcaklığıc(TS5)", "temperatura inferiore 2° accumulo (TS5)") -MAKE_TRANSLATION(heatExchangerTemp, "heatexchangertemp", "heat exchanger temperature (TS6)", "wärmetauscher Temperatur (TS6)", "Temperatuur warmtewisselaar (TS6)", "Värmeväxlare Temperatur (TS6)", "temperatura wymiennika ciepła (TS6)", "Varmeveksler temperatur (TS6)", "température échangeur de chaleur (TS6)", "eşanjör sıcaklığı (TS6)", "temperatura scambiatore calore (TS6)") -MAKE_TRANSLATION(collectorMaxTemp, "collectormaxtemp", "maximum collector temperature", "maximale Kollektortemperatur", "Maximale collectortemperatuur", "Max Kollektortemperatur", "maksymalna temperatura kolektora", "maks kollektortemperatur", "température max. collecteur", "maksimum kollektör sıcaklığı", " temperatura massima scambiatore calore") -MAKE_TRANSLATION(collectorMinTemp, "collectormintemp", "minimum collector temperature", "minimale Kollektortemperatur", "Minimale collectortemperatuur", "Min Kollektortemperatur", "minimalna temperatura kolektora", "min kollektortemperatur", "température min. collecteur", "minimum kollektör sıcaklığı", "temperatura minima scambiatore calore") -MAKE_TRANSLATION(cylMaxTemp, "cylmaxtemp", "maximum cylinder temperature", "maximale Speichertemperatur", "maximale temperatuur zonneboiler", "Max Cylindertemperatur", "maksymalna temperatura zasobnika", "maks beredertemperatur", "température max. cylindre", "maksimum silindir sıcaklığı", "temperatura massima vaso accumulo") -MAKE_TRANSLATION(solarPumpMod, "solarpumpmod", "pump modulation (PS1)", "Pumpenmodulation (PS1)", "Pompmodulatie (PS1)", "Pumpmodulering (PS1)", "modulacja pompy solarnej (PS1)", "solpumpmodulering (PS1)", "modulation pompe (PS1)", "pompa modülasyonu (PS1)", "modulazione pompa (PS1)") -MAKE_TRANSLATION(cylPumpMod, "cylpumpmod", "cylinder pump modulation (PS5)", "Speicherpumpenmodulation (PS5)", "Modulatie zonneboilerpomp (PS5)", "Cylinderpumpmodulering (PS5)", "modulacja pompy zasobnika (PS5)", "sylinderpumpemodulering (P55)", "modulation pompe cylindre (PS5)", "silindir pompa modülasyonu (PS5)", "pompa modulazione accumulo (PS5)") -MAKE_TRANSLATION(solarPump, "solarpump", "pump (PS1)", "Pumpe (PS1)", "Pomp (PS1)", "Pump (PS1)", "pompa solarna (PS1)", "solpumpe (PS1)", "pompe solaire (PS1)", "pompa (PS1)", "pompa solare (PS1)") -MAKE_TRANSLATION(solarPump2, "solarpump2", "pump 2 (PS4)", "Pumpe 2 (PS4)", "Pomp 2 (PS4)", "Pump 2 (PS4)", "pompa solarna 2 (PS4)", "solpumpe 2 (PS4)", "pompe 2 (PS4)", "pompa 2 (PS4)", "pompa solare 2 (PS4)") -MAKE_TRANSLATION(solarPump2Mod, "solarpump2mod", "pump 2 modulation (PS4)", "Pumpe 2 Modulation (PS4)", "Modulatie pomp 2 (PS4)", "Pump 2 Modulering (PS4)", "modulacja pompy solarnej 2 (PS4)", "solpumpe2modulering (PS4)", "modulation pompe solaire 2 (PS4)", "pompa2 modülasyonu(PS1)", "pompa modulazione 2 (PS4)") -MAKE_TRANSLATION(valveStatus, "valvestatus", "valve status", "Ventilstatus", "Klepstatus", "Ventilstatus", "stan zaworu", "ventilstatus", "statut valve", "vana durumu", "stato valvola") -MAKE_TRANSLATION(vs1Status, "vs1status", "valve status VS1", "Ventilstatus VS1", "Klepstatus VS1", "Ventilstatus VS1", "stan zaworu VS1", "ventilstatus VS1", "statut valve VS1", "vana durumu VS1", "stato valvola VS1") -MAKE_TRANSLATION(cylHeated, "cylheated", "cyl heated", "Speichertemperatur erreicht", "Boilertemperatuur behaald", "Värmepanna Uppvärmd", "zasobnik został nagrzany", "bereder oppvarmt", "cylindre chauffé", "depolama sıcakllığına ulaşıldı", "temperatura richiesta vaso accumulo raggiunta") -MAKE_TRANSLATION(collectorShutdown, "collectorshutdown", "collector shutdown", "Kollektorabschaltung", "Collector afschakeling", "Kollektor Avstängning", "wyłączenie kolektora", "kollektor stengt", "arrêt collecteur", "kollektör kapalı", "spegnimento del collettore") -MAKE_TRANSLATION(pumpWorkTime, "pumpworktime", "pump working time", "Pumpenlaufzeit", "Pomplooptijd", "Pump Drifttid", "czas pracy pompy", "driftstid pumpe", "durée fonctionnement pompe", "pompa çalışma süresi", "tempo funzionamento pompa") -MAKE_TRANSLATION(pump2WorkTime, "pump2worktime", "pump 2 working time", "Pumpe 2 Laufzeit", "Looptijd pomp 2", "Pump 2 Drifttid", "czas pracy pompy 2", "driftstid pumpe2", "durée fonctionnement pompe 2", "pompa 2 çalışma süresi", "tempo funzionamento pompa 2") -MAKE_TRANSLATION(m1WorkTime, "m1worktime", "differential control working time", "Differenzregelung Arbeitszeit", "Verschilregeling arbeidstijd", "Differentialreglering Drifttid", "czas pracy regulacji różnicowej", "differentialreguleringssrifttid", "durée fonctionnement contrôle différentiel", "çalışma saatlerinin farklı düzenlenmesi", "controllo differenziale durata funzionamento") -MAKE_TRANSLATION(energyLastHour, "energylasthour", "energy last hour", "Energie letzte Std", "Energie laatste uur", "Energi Senaste Timmen", "energia w ciągu ostatniej godziny", "energi siste time", "énergie dernière heure", "son saat enerji", "Eenergia ultima ora") -MAKE_TRANSLATION(energyTotal, "energytotal", "total energy", "Gesamtenergie", "Totale energie", "Total Energi", "energia całkowita", "total energi", "énergie totale", "toplam enerji", "energia totale") -MAKE_TRANSLATION(energyToday, "energytoday", "total energy today", "Energie heute", "Energie vandaag", "Total Energi Idag", "energia całkowita dzisiaj", "total energi i dag", "énergie totale aujourd'hui", "bugün toplam enerji", "totale energia giornaliera") +MAKE_TRANSLATION(cylMiddleTemp, "cylmiddletemp", "cylinder middle temperature (TS3)", "Speichertemperatur Mitte (TS3)", "Zonneboilertemperatuur midden (TS3)", "Cylindertemperatur Mitten (TS3)", "temperatura w środku zasobnika (TS3)", "beredertemperatur i midten (TS3)", "température moyenne cylindre (TS3)", "orta depolama sıcaklığı (TS3)", "temperatura di conservazione media accumulo (TS3)", "stredná teplota valca (TS3)") +MAKE_TRANSLATION(retHeatAssist, "retheatassist", "return temperature heat assistance (TS4)", "Rücklaufanhebungs-Temp. (TS4)", "Retourtemperatuur verwarmingsassistentie (TS4)", "Returtemperatur värmestöd (TS4)", "temperatura powrotu wspomagania grzania (TS4)", "returtemperatur varmestøtte (TS4)", "température retour de assistance thermique (TS4)", "geri dönüş sıcaklığı artışı", "temperatura ritorno scambiatore (TS4)", "pomoc pri teplote spiatočky (TS4)") +MAKE_TRANSLATION(m1Valve, "heatassistvalve", "heat assistance valve (M1)", "Ventil Heizungsunterstützung (M1)", "Klep verwarmingsassistentie (M1)", "Uppvärmningsstöd Ventil (M1)", "zawór wspomagania grzania (M1)", "varmehjelpsventil (M1)", "vanne assistance thermique (M1)", "ısıtma yardım vanası (M1)", "valvola scambiatore (M1)", "tepelný asistenčný ventil (M1)") +MAKE_TRANSLATION(m1Power, "heatassistpower", "heat assistance valve power (M1)", "Ventilleistung Heizungsunterstützung (M1)", "Vermogen klep verwarmingsassistentie (M1)", "Uppvärmningsstöd Ventil Effekt (M1)", "moc zaworu wspomagania grzania (M1)", "varmehjelpsventileffekt (M1)", "puissance vanne assistance thermique (M1)", "ısıtma yardım vanası gücü (M1)", "potenza valvola scambiatore (M1)", "výkon ventilu tepelného asistenta (M1)") +MAKE_TRANSLATION(pumpMinMod, "pumpminmod", "minimum pump modulation", "minimale Pumpenmodulation", "Minimale pompmodulatie", "Min Pumpmodulering", "minimalna modulacja pompy", "minimum pumpmodulering", "modulation minimale pompe", "minimum pompa modülasyonu", "modulazione minima pompa", "minimálna modulácia čerpadla") +MAKE_TRANSLATION(maxFlow, "maxflow", "maximum solar flow", "maximaler Durchfluss", "Maximale doorstroom solar", "Max Flöde Solpanel", "maksymalny przepływ solarów", "maks strømming solpanel", "débit solaire maximum", "minimum G.E. akışı", "portata massima solare", "maximálny solárny prietok") +MAKE_TRANSLATION(solarPower, "solarpower", "actual solar power", "aktuelle Solarleistung", "Huidig solar vermogen", "Aktuellt Sol-effekt", "aktualna moc solarów", "aktuell soleffekt", "puissance solaire réelle", "gerçek G.E. gücü", "potenza attuale solare", "skutočná slnečná energia") +MAKE_TRANSLATION(solarPumpTurnonDiff, "turnondiff", "pump turn on difference", "Einschalthysterese Pumpe", "Inschakelhysterese pomp", "Aktiveringshysteres Pump", "histereza załączenia pompy", "slå på hysteresepumpe", "différence activation pompe", "pompa devreye alma farkı", "isteresi di accensione pompa", "rozdiel v zapnutí čerpadla") +MAKE_TRANSLATION(solarPumpTurnoffDiff, "turnoffdiff", "pump turn off difference", "Ausschalthysterese Pumpe", "Uitschakelhysterese pomp", "Avslagshysteres Pump", "histereza włączenia pompy", "slå av hysteresepumpe", "différence arrêt pompe", "pompa kapama farkı", "isteresi di spegnimento pompa", "rozdiel vypnutia čerpadla") +MAKE_TRANSLATION(pump2MinMod, "pump2minmod", "minimum pump 2 modulation", "minimale Modulation Pumpe 2", "Minimale modulatie pomp 2", "Min Modulering Pump 2", "minimalna modulacja pompy 2", "minimum pumpmodulering 2", "modulation minimale pompe 2", "minimum pompa 2 modülasyonu", "modulazione minima pompa 2", "minimálna modulácia čerpadla 2") +MAKE_TRANSLATION(solarPump2TurnonDiff, "turnondiff2", "pump 2 turn on difference", "Einschalthysterese Pumpe 2", "Inschakelhysterese pomp 2", "Aktiveringshysteres Pump 2", "histereza załączenia pompy 2", "slå på hysteresepumpe 2", "différence activation pompe 2", "pompa 2 devreye alma farkı", "isteresi di accensione pompa 2", "rozdiel v zapnutí čerpadla 2") +MAKE_TRANSLATION(solarPump2TurnoffDiff, "turnoffdiff2", "pump 2 turn off difference", "Ausschalthysterese Pumpe 2", "Uitschakelhysterese pomp 2", "Avslagshysteres Pump 2", "histereza wyłączenia pompy 2", "slå av hysteresepumpe 2", "différence arrêt pompe 2", "pompa 2 kapama farkı", "isteresi di spegnimento pompa", "rozdiel vypnutia čerpadla 2") +MAKE_TRANSLATION(collectorTemp, "collectortemp", "collector temperature (TS1)", "Kollektortemperatur (TS1)", "Collectortemperatuur (TS1)", "Kollektor Temperatur (TS1)", "temperatura kolektora (TS1)", "kollektor temperatur (TS1)", "température collecteur (TS1)", "kollektör sıcaklığı (TS1)", "temperatura collettore (TS1)", "teplota kolektora (TS1)") +MAKE_TRANSLATION(collector2Temp, "collector2temp", "collector 2 temperature (TS7)", "Kollector 2 Temperatur (TS7)", "Collector 2 temperatuur (TS7)", "Kollektor 2 Temperatur (TS7)", "temperatura kolektora 2 (TS7)", "kollektor 2 temperatur (TS7)", "température collecteur 2 (TS7)", "kollektör 2 sıcaklığı (TS2)", "temperatura collettore 2 (TS7)", "teplota kolektora 2 (TS7)") +MAKE_TRANSLATION(cylBottomTemp, "cylbottomtemp", "cylinder bottom temperature (TS2)", "Speicher Bodentemperatur (TS2)", "Bodemtemperatuur zonneboiler (TS2)", "Cylindertemperatur Botten (TS2)", "temperatura na spodzie zasobnika (TS2)", "beredertemp i bunn (TS2)", "température fond de cylindre (TS2)", "alt depolama sıcaklığıc(TS2)", "temperatura inferiore accumulo (TS2)", "teplota dna valca (TS2)") +MAKE_TRANSLATION(cyl2BottomTemp, "cyl2bottomtemp", "second cylinder bottom temperature (TS5)", "2. Speicher Bodentemperatur (TS5)", "Bodemtemperatuur 2e boiler", "Sekundär Cylindertemperatur Botten (TS5)", "temperatura na spodzie drugiego zasobnika (TS5)", "skundær beredertemp i bunn (TS5)", "température fond de cylindre (TS5)", "ikinci alt depolama sıcaklığıc(TS5)", "temperatura inferiore 2° accumulo (TS5)", "teplota dna druhého valca (TS5)") +MAKE_TRANSLATION(heatExchangerTemp, "heatexchangertemp", "heat exchanger temperature (TS6)", "wärmetauscher Temperatur (TS6)", "Temperatuur warmtewisselaar (TS6)", "Värmeväxlare Temperatur (TS6)", "temperatura wymiennika ciepła (TS6)", "Varmeveksler temperatur (TS6)", "température échangeur de chaleur (TS6)", "eşanjör sıcaklığı (TS6)", "temperatura scambiatore calore (TS6)", "teplota výmenníka tepla (TS6)") +MAKE_TRANSLATION(collectorMaxTemp, "collectormaxtemp", "maximum collector temperature", "maximale Kollektortemperatur", "Maximale collectortemperatuur", "Max Kollektortemperatur", "maksymalna temperatura kolektora", "maks kollektortemperatur", "température max. collecteur", "maksimum kollektör sıcaklığı", " temperatura massima scambiatore calore", "maximálna teplota kolektora") +MAKE_TRANSLATION(collectorMinTemp, "collectormintemp", "minimum collector temperature", "minimale Kollektortemperatur", "Minimale collectortemperatuur", "Min Kollektortemperatur", "minimalna temperatura kolektora", "min kollektortemperatur", "température min. collecteur", "minimum kollektör sıcaklığı", "temperatura minima scambiatore calore", "minimálna teplota kolektora") +MAKE_TRANSLATION(cylMaxTemp, "cylmaxtemp", "maximum cylinder temperature", "maximale Speichertemperatur", "maximale temperatuur zonneboiler", "Max Cylindertemperatur", "maksymalna temperatura zasobnika", "maks beredertemperatur", "température max. cylindre", "maksimum silindir sıcaklığı", "temperatura massima vaso accumulo", "maximálna teplota valca") +MAKE_TRANSLATION(solarPumpMod, "solarpumpmod", "pump modulation (PS1)", "Pumpenmodulation (PS1)", "Pompmodulatie (PS1)", "Pumpmodulering (PS1)", "modulacja pompy solarnej (PS1)", "solpumpmodulering (PS1)", "modulation pompe (PS1)", "pompa modülasyonu (PS1)", "modulazione pompa (PS1)", "modulácia čerpadla (PS1)") +MAKE_TRANSLATION(cylPumpMod, "cylpumpmod", "cylinder pump modulation (PS5)", "Speicherpumpenmodulation (PS5)", "Modulatie zonneboilerpomp (PS5)", "Cylinderpumpmodulering (PS5)", "modulacja pompy zasobnika (PS5)", "sylinderpumpemodulering (P55)", "modulation pompe cylindre (PS5)", "silindir pompa modülasyonu (PS5)", "pompa modulazione accumulo (PS5)", "modulácia čerpadla valca (PS5)") +MAKE_TRANSLATION(solarPump, "solarpump", "pump (PS1)", "Pumpe (PS1)", "Pomp (PS1)", "Pump (PS1)", "pompa solarna (PS1)", "solpumpe (PS1)", "pompe solaire (PS1)", "pompa (PS1)", "pompa solare (PS1)", "čerpadlo (PS1)") +MAKE_TRANSLATION(solarPump2, "solarpump2", "pump 2 (PS4)", "Pumpe 2 (PS4)", "Pomp 2 (PS4)", "Pump 2 (PS4)", "pompa solarna 2 (PS4)", "solpumpe 2 (PS4)", "pompe 2 (PS4)", "pompa 2 (PS4)", "pompa solare 2 (PS4)", "čerpadlo 2 (PS4)") +MAKE_TRANSLATION(solarPump2Mod, "solarpump2mod", "pump 2 modulation (PS4)", "Pumpe 2 Modulation (PS4)", "Modulatie pomp 2 (PS4)", "Pump 2 Modulering (PS4)", "modulacja pompy solarnej 2 (PS4)", "solpumpe2modulering (PS4)", "modulation pompe solaire 2 (PS4)", "pompa2 modülasyonu(PS1)", "pompa modulazione 2 (PS4)", "modulácia pumpy 2 (PS4)") +MAKE_TRANSLATION(valveStatus, "valvestatus", "valve status", "Ventilstatus", "Klepstatus", "Ventilstatus", "stan zaworu", "ventilstatus", "statut valve", "vana durumu", "stato valvola", "stav ventilu") +MAKE_TRANSLATION(vs1Status, "vs1status", "valve status VS1", "Ventilstatus VS1", "Klepstatus VS1", "Ventilstatus VS1", "stan zaworu VS1", "ventilstatus VS1", "statut valve VS1", "vana durumu VS1", "stato valvola VS1", "stav ventilu VS1") +MAKE_TRANSLATION(cylHeated, "cylheated", "cyl heated", "Speichertemperatur erreicht", "Boilertemperatuur behaald", "Värmepanna Uppvärmd", "zasobnik został nagrzany", "bereder oppvarmt", "cylindre chauffé", "depolama sıcakllığına ulaşıldı", "temperatura richiesta vaso accumulo raggiunta", "Dosiahnutá teplota zásobníka") +MAKE_TRANSLATION(collectorShutdown, "collectorshutdown", "collector shutdown", "Kollektorabschaltung", "Collector afschakeling", "Kollektor Avstängning", "wyłączenie kolektora", "kollektor stengt", "arrêt collecteur", "kollektör kapalı", "spegnimento del collettore", "vypnutie kolektora") +MAKE_TRANSLATION(pumpWorkTime, "pumpworktime", "pump working time", "Pumpenlaufzeit", "Pomplooptijd", "Pump Drifttid", "czas pracy pompy", "driftstid pumpe", "durée fonctionnement pompe", "pompa çalışma süresi", "tempo funzionamento pompa", "pracovný čas čerpadla") +MAKE_TRANSLATION(pump2WorkTime, "pump2worktime", "pump 2 working time", "Pumpe 2 Laufzeit", "Looptijd pomp 2", "Pump 2 Drifttid", "czas pracy pompy 2", "driftstid pumpe2", "durée fonctionnement pompe 2", "pompa 2 çalışma süresi", "tempo funzionamento pompa 2", "pracovný čas čerpadla 2") +MAKE_TRANSLATION(m1WorkTime, "m1worktime", "differential control working time", "Differenzregelung Arbeitszeit", "Verschilregeling arbeidstijd", "Differentialreglering Drifttid", "czas pracy regulacji różnicowej", "differentialreguleringssrifttid", "durée fonctionnement contrôle différentiel", "çalışma saatlerinin farklı düzenlenmesi", "controllo differenziale durata funzionamento", "pracovný čas diferenciálnej kontroly") +MAKE_TRANSLATION(energyLastHour, "energylasthour", "energy last hour", "Energie letzte Std", "Energie laatste uur", "Energi Senaste Timmen", "energia w ciągu ostatniej godziny", "energi siste time", "énergie dernière heure", "son saat enerji", "Eenergia ultima ora", "energia za poslednú hodinu") +MAKE_TRANSLATION(energyTotal, "energytotal", "total energy", "Gesamtenergie", "Totale energie", "Total Energi", "energia całkowita", "total energi", "énergie totale", "toplam enerji", "energia totale", "celková energia") +MAKE_TRANSLATION(energyToday, "energytoday", "total energy today", "Energie heute", "Energie vandaag", "Total Energi Idag", "energia całkowita dzisiaj", "total energi i dag", "énergie totale aujourd'hui", "bugün toplam enerji", "totale energia giornaliera", "celková energia dnes") // solar ww -MAKE_TRANSLATION(wwTemp1, "wwtemp1", "temperature 1(TS17)", "Temperatur 1", "Temperatuur 1", "Temperatur 1", "temperatura 1", "temperatur 1", "température 1", "sıcaklık 1", "Temperatura 1") -MAKE_TRANSLATION(wwTemp3, "wwtemp3", "temperature 3 (TS21)", "Temperatur 3", "Temperatuur 3", "Temperatur 3", "temperatura 3", "Temperatur 3", "température 3", "sıcaklık 3", "Temperatura 3") -MAKE_TRANSLATION(wwTemp4, "wwtemp4", "cold water", "Temperatur 4", "Temperatuur 4", "Temperatur 4", "temperatura 4", "Temperatur 4", "température 4", "sıcaklık 4", "Temperatura 4") -MAKE_TRANSLATION(wwTemp5, "wwtemp5", "temperature 5", "Temperatur 5", "Temperatuur 5", "Temperatur 5", "temperatura 5", "Temperatur 5", "température 5", "sıcaklık 5", "Temperatura 5") - -MAKE_TRANSLATION(wwTemp7, "wwtemp7", "temperature 7", "Temperatur 7", "Temperatuur 7", "Temperatur 7", "temperatura 7", "Temperatur 7", "température 7", "sıcaklık 7", "Temperatura 7") -MAKE_TRANSLATION(wwPump, "wwpump", "pump", "Pumpe", "Pomp", "Pump", "pompa", "pumpe", "pompe", "pompa", "Pompa") +MAKE_TRANSLATION(wwTemp1, "wwtemp1", "temperature 1(TS17)", "Temperatur 1", "Temperatuur 1", "Temperatur 1", "temperatura 1", "temperatur 1", "température 1", "sıcaklık 1", "Temperatura 1", "teplota 1") +MAKE_TRANSLATION(wwTemp3, "wwtemp3", "temperature 3 (TS21)", "Temperatur 3", "Temperatuur 3", "Temperatur 3", "temperatura 3", "Temperatur 3", "température 3", "sıcaklık 3", "Temperatura 3", "teplota 3") +MAKE_TRANSLATION(wwTemp4, "wwtemp4", "cold water", "Temperatur 4", "Temperatuur 4", "Temperatur 4", "temperatura 4", "Temperatur 4", "température 4", "sıcaklık 4", "Temperatura 4", "studená voda") +MAKE_TRANSLATION(wwTemp5, "wwtemp5", "temperature 5", "Temperatur 5", "Temperatuur 5", "Temperatur 5", "temperatura 5", "Temperatur 5", "température 5", "sıcaklık 5", "Temperatura 5", "teplota 5") +MAKE_TRANSLATION(wwTemp6, "wwtemp6", "temperature 6", "Temperatur 6", "Temperatuur 6", "Temperatur 6", "temperatura 6", "temperatur 6", "température 6", "sıcaklık 6", "Temperatura 6", "teplota 6") +MAKE_TRANSLATION(wwTemp7, "wwtemp7", "temperature 7", "Temperatur 7", "Temperatuur 7", "Temperatur 7", "temperatura 7", "Temperatur 7", "température 7", "sıcaklık 7", "Temperatura 7", "teplota 7") +MAKE_TRANSLATION(wwPump, "wwpump", "pump", "Pumpe", "Pomp", "Pump", "pompa", "pumpe", "pompe", "pompa", "Pompa", "čerpadlo") // solar ww and mixer wwc -MAKE_TRANSLATION(wwMinTemp, "wwmintemp", "minimum temperature", "minimale Temperatur", "Minimale temperatuur", "Min Temperatur", "temperatura minimalna", "min temperatur", "température min", "minimum sıcaklık", "temperatura minima") -MAKE_TRANSLATION(wwRedTemp, "wwredtemp", "reduced temperature", "reduzierte Temperatur", "Gereduceerde temperatuur", "Reducerad Temperatur", "temperatura zredukowana", "reducert temperatur", "température réduite", "düşürülmüş sıcaklık", "temperatura ridotta") -MAKE_TRANSLATION(wwDailyTemp, "wwdailytemp", "daily temperature", "tägl. Temperatur", "Dagelijkse temperatuur", "Daglig temperatur", "temperatura dzienna", "dagtemperatur", "température en journée", "günlük sıcaklık", "temperatura giornaliera") -MAKE_TRANSLATION(wwKeepWarm, "wwkeepwarm", "keep warm", "Warmhalten", "Warm houde", "Varmhållning", "utrzymywanie ciepła", "holde varmen", "maintenir chaleur", "ılık tut", "mantenimento calore") -MAKE_TRANSLATION(wwStatus2, "wwstatus2", "status 2", "Status 2", "Status 2", "Status 2", "status 2", "status 2", "statut 2", "durum 2", "Status 2") -MAKE_TRANSLATION(wwPumpMod, "wwpumpmod", "pump modulation", "Pumpen Modulation", "Pompmodulatie", "Pumpmodulering", "modulacja pompy", "pumpemodulering", "modulation de pompe", "pompa modülasyonu", "modulazione pompa") -MAKE_TRANSLATION(wwFlow, "wwflow", "flow rate", "Volumenstrom", "Doorstroomsnelheid", "Flöde", "przepływ", "strømningshastighet", "débit", "akış hızı", "portata flusso") +MAKE_TRANSLATION(wwMinTemp, "wwmintemp", "minimum temperature", "minimale Temperatur", "Minimale temperatuur", "Min Temperatur", "temperatura minimalna", "min temperatur", "température min", "minimum sıcaklık", "temperatura minima", "minimálna teplota") +MAKE_TRANSLATION(wwRedTemp, "wwredtemp", "reduced temperature", "reduzierte Temperatur", "Gereduceerde temperatuur", "Reducerad Temperatur", "temperatura zredukowana", "reducert temperatur", "température réduite", "düşürülmüş sıcaklık", "temperatura ridotta", "znížená teplota") +MAKE_TRANSLATION(wwDailyTemp, "wwdailytemp", "daily temperature", "tägl. Temperatur", "Dagelijkse temperatuur", "Daglig temperatur", "temperatura dzienna", "dagtemperatur", "température en journée", "günlük sıcaklık", "temperatura giornaliera", "denná teplota") +MAKE_TRANSLATION(wwKeepWarm, "wwkeepwarm", "keep warm", "Warmhalten", "Warm houde", "Varmhållning", "utrzymywanie ciepła", "holde varmen", "maintenir chaleur", "ılık tut", "mantenimento calore", "udržovať v teple") +MAKE_TRANSLATION(wwStatus2, "wwstatus2", "status 2", "Status 2", "Status 2", "Status 2", "status 2", "status 2", "statut 2", "durum 2", "Status 2", "stav 2") +MAKE_TRANSLATION(wwPumpMod, "wwpumpmod", "pump modulation", "Pumpen Modulation", "Pompmodulatie", "Pumpmodulering", "modulacja pompy", "pumpemodulering", "modulation de pompe", "pompa modülasyonu", "modulazione pompa", "modulácia čerpadla") +MAKE_TRANSLATION(wwFlow, "wwflow", "flow rate", "Volumenstrom", "Doorstroomsnelheid", "Flöde", "przepływ", "strømningshastighet", "débit", "akış hızı", "portata flusso", "prietok") // extra mixer ww -MAKE_TRANSLATION(wwRequiredTemp, "wwrequiredtemp", "required temperature", "benötigte Temperatur", "Benodigde temperatuur", "Nödvändig Temperatur", "temperatura wymagana", "nødvendig temperatur", "température requise", "gerekli sıcaklık", "temperatura richiesta") -MAKE_TRANSLATION(wwDiffTemp, "wwdifftemp", "start differential temperature", "Start Differential Temperatur", "Start differentiele temperatuur", "Start Differentialtemperatur", "start temperatury różnicowej", "start differensialtemperatur", "température différentielle de départ", "diferansiyel sıcaklık", "avvia temperatura differenziale") +MAKE_TRANSLATION(wwRequiredTemp, "wwrequiredtemp", "required temperature", "benötigte Temperatur", "Benodigde temperatuur", "Nödvändig Temperatur", "temperatura wymagana", "nødvendig temperatur", "température requise", "gerekli sıcaklık", "temperatura richiesta", "požadovaná teplota") +MAKE_TRANSLATION(wwDiffTemp, "wwdifftemp", "start differential temperature", "Start Differential Temperatur", "Start differentiele temperatuur", "Start Differentialtemperatur", "start temperatury różnicowej", "start differensialtemperatur", "température différentielle de départ", "diferansiyel sıcaklık", "avvia temperatura differenziale", "začiatok diferenciálnej teploty") // SM100 -MAKE_TRANSLATION(heatTransferSystem, "heattransfersystem", "heattransfer system", "Wärmeübertragungs-System", "Warmteoverdrachtssysteem", "Värmeöverföringssystem", "system wymiany ciepła", "varmeoverføringssystem", "système de transfert de chaleur", "ıs transfer sistemi", "sistema di trasferimento del calore") -MAKE_TRANSLATION(externalCyl, "externalcyl", "external cylinder", "Externer Speicher", "Externe boiler", "Extern Cylinder", "zbiornik zewnętrzny", "ekstern bereder", "cylindre externe", "dış silindir", "vaso accumulo esterno") -MAKE_TRANSLATION(thermalDisinfect, "thermaldisinfect", "thermal disinfection", "Thermische Desinfektion", "Thermische desinfectie", "Termisk Desinfektion", "dezynfekcja termiczna", "termisk desinfeksjon", "désinfection thermique", "ısıl temizlik", "disinfezione termica") -MAKE_TRANSLATION(heatMetering, "heatmetering", "heatmetering", "Wärmemessung", "warmtemeting", "Värmemätning", "pomiar ciepła", "varmemåling", "mesure de chaleur", "ısı ölçümü", "misurazione del calore") -MAKE_TRANSLATION(solarIsEnabled, "solarenabled", "solarmodule enabled", "Solarmodul aktiviert", "Solarmodule geactiveerd", "Solmodul Aktiverad", "system solarny", "solmodul aktivert", "module solaire activé", "güneş modu etkinleştirildi", "modulo solare attivato") +MAKE_TRANSLATION(heatTransferSystem, "heattransfersystem", "heattransfer system", "Wärmeübertragungs-System", "Warmteoverdrachtssysteem", "Värmeöverföringssystem", "system wymiany ciepła", "varmeoverføringssystem", "système de transfert de chaleur", "ıs transfer sistemi", "sistema di trasferimento del calore", "systém prenosu tepla") +MAKE_TRANSLATION(externalCyl, "externalcyl", "external cylinder", "Externer Speicher", "Externe boiler", "Extern Cylinder", "zbiornik zewnętrzny", "ekstern bereder", "cylindre externe", "dış silindir", "vaso accumulo esterno", "vonkajší valec") +MAKE_TRANSLATION(thermalDisinfect, "thermaldisinfect", "thermal disinfection", "Thermische Desinfektion", "Thermische desinfectie", "Termisk Desinfektion", "dezynfekcja termiczna", "termisk desinfeksjon", "désinfection thermique", "ısıl temizlik", "disinfezione termica", "tepelná dezinfekcia") +MAKE_TRANSLATION(heatMetering, "heatmetering", "heatmetering", "Wärmemessung", "warmtemeting", "Värmemätning", "pomiar ciepła", "varmemåling", "mesure de chaleur", "ısı ölçümü", "misurazione del calore", "meranie tepla") +MAKE_TRANSLATION(solarIsEnabled, "solarenabled", "solarmodule enabled", "Solarmodul aktiviert", "Solarmodule geactiveerd", "Solmodul Aktiverad", "system solarny", "solmodul aktivert", "module solaire activé", "güneş modu etkinleştirildi", "modulo solare attivato", "solárny modul povolený") // telegram 0x035A -MAKE_TRANSLATION(solarPumpMode, "solarpumpmode", "pump mode", "Solar Pumpen Einst.", "Modus zonneboilerpomp", "Sol Pumpläge", "tryb pracy pompy", "solpumpemodus", "mode pompe solaire", "pompa modu", "modalità pompa solare") -MAKE_TRANSLATION(solarPumpKick, "pumpkick", "pump kick", "Röhrenkollektorfunktion", "Modus buizencollector", "Sol Kollektorfunktion", "wspomaganie startu pompy", "solkllektorfunksjon", "démarrage boost pompe solaire", "pompa zorunlu çalıştırma", "avvio forzato pompa") -MAKE_TRANSLATION(plainWaterMode, "plainwatermode", "plain water mode", "Südeuropafunktion", "Modus Zuid-Europa", "Sydeuropa-funktion", "tylko woda (dla Europy Południowej)", "vanlig vannmodus", "mode eau ordinaire", "sadece su modu", "modalità acqua normale") -MAKE_TRANSLATION(doubleMatchFlow, "doublematchflow", "doublematchflow", "Double Match Flow", "Double Match Flow", "Dubbelmatchning Flöde", "przepływ podwójnie dopasowany", "dobbelmatch flow", "double match flow", "doublematch akışı", "carico ottimizzato dell'accumulatore ad effetto termosifone ") -MAKE_TRANSLATION(solarPump2Mode, "pump2mode", "pump 2 mode", "Pumpe 2 Modus", "Modus pomp 2", "Pump 2 Läge", "tryb pracy pompy 2", "pump 2 modus", "mode pompe 2", "pompa 2 modu", "modalità pompa 2") -MAKE_TRANSLATION(solarPump2Kick, "pump2kick", "pump kick 2", "Pumpe 2 Startboost", "Startboost pomp 2", "Pump 2 Kollektorfunktion", "wspomaganie startu pompy 2", "startboost pumpe 2", "démarrage boost pompe 2", "pompa 2 zorunlu çalıştırma", "avvio forzato pompa 2") +MAKE_TRANSLATION(solarPumpMode, "solarpumpmode", "pump mode", "Solar Pumpen Einst.", "Modus zonneboilerpomp", "Sol Pumpläge", "tryb pracy pompy", "solpumpemodus", "mode pompe solaire", "pompa modu", "modalità pompa solare", "režim čerpadla") +MAKE_TRANSLATION(solarPumpKick, "pumpkick", "pump kick", "Röhrenkollektorfunktion", "Modus buizencollector", "Sol Kollektorfunktion", "wspomaganie startu pompy", "solkllektorfunksjon", "démarrage boost pompe solaire", "pompa zorunlu çalıştırma", "avvio forzato pompa", "kopnutie pumpy") +MAKE_TRANSLATION(plainWaterMode, "plainwatermode", "plain water mode", "Südeuropafunktion", "Modus Zuid-Europa", "Sydeuropa-funktion", "tylko woda (dla Europy Południowej)", "vanlig vannmodus", "mode eau ordinaire", "sadece su modu", "modalità acqua normale", "režim čistej vody") +MAKE_TRANSLATION(doubleMatchFlow, "doublematchflow", "doublematchflow", "Double Match Flow", "Double Match Flow", "Dubbelmatchning Flöde", "przepływ podwójnie dopasowany", "dobbelmatch flow", "double match flow", "doublematch akışı", "carico ottimizzato dell'accumulatore ad effetto termosifone", "Tok dvojitej zhody") +MAKE_TRANSLATION(solarPump2Mode, "pump2mode", "pump 2 mode", "Pumpe 2 Modus", "Modus pomp 2", "Pump 2 Läge", "tryb pracy pompy 2", "pump 2 modus", "mode pompe 2", "pompa 2 modu", "modalità pompa 2", "režim čerpadla 2") +MAKE_TRANSLATION(solarPump2Kick, "pump2kick", "pump kick 2", "Pumpe 2 Startboost", "Startboost pomp 2", "Pump 2 Kollektorfunktion", "wspomaganie startu pompy 2", "startboost pumpe 2", "démarrage boost pompe 2", "pompa 2 zorunlu çalıştırma", "avvio forzato pompa 2", "pump kick 2") // telegram 0x035F -MAKE_TRANSLATION(cylPriority, "cylpriority", "cylinder priority", "Speicher Priorität", "Prioriteit boiler", "Cylinderprioritering", "priorytet cylindra", "berederprioritering", "priorité de cylindre", "silindir önceliği", "priorità vaso accumulo") +MAKE_TRANSLATION(cylPriority, "cylpriority", "cylinder priority", "Speicher Priorität", "Prioriteit boiler", "Cylinderprioritering", "priorytet cylindra", "berederprioritering", "priorité de cylindre", "silindir önceliği", "priorità vaso accumulo", "Priorita valca") // telegram 0x380 -MAKE_TRANSLATION(climateZone, "climatezone", "climate zone", "Klimazone", "klimaatzone", "Klimatzon", "strefa klimatyczna", "klimasone", "zone de climat", "iklim alanı", "zona clima") -MAKE_TRANSLATION(collector1Area, "collector1area", "collector 1 area", "Kollektor 1 Fläche", "oppervlakte collector 1", "Kollektor 1 Area", "powierzchnia kolektora 1", "kollektor 1 område", "zone collecteur 1", "kollektör 1 alan", "area collettore 1") -MAKE_TRANSLATION(collector1Type, "collector1type", "collector 1 type", "Kollektor 1 Typ", "Type collector 1", "Kollektor 1 Typ", "typ kolektora 1", "kollektor 1 type", "type collecteur 1", "kollektör 1 tip", "tipo collettore 1") -MAKE_TRANSLATION(collector2Area, "collector2area", "collector 2 area", "Kollektor 2 Fläche", "Oppervlakte collector 2", "Kollektor 2 Area", "powierzchnia kolektora 2", "kollektor 2 område", "zone collecteur 2", "kollektör 2 alan", "area collettore 2") -MAKE_TRANSLATION(collector2Type, "collector2type", "collector 2 type", "Kollektor 2 Typ", "Type collector 2", "Kollektor 2 Typ", "typ kolektora 2", "kollektor 2 type", "type collecteur 2", "kollektör 2 tip", "tipo collettore 2") +MAKE_TRANSLATION(climateZone, "climatezone", "climate zone", "Klimazone", "klimaatzone", "Klimatzon", "strefa klimatyczna", "klimasone", "zone de climat", "iklim alanı", "zona clima", "klimatická zóna") +MAKE_TRANSLATION(collector1Area, "collector1area", "collector 1 area", "Kollektor 1 Fläche", "oppervlakte collector 1", "Kollektor 1 Area", "powierzchnia kolektora 1", "kollektor 1 område", "zone collecteur 1", "kollektör 1 alan", "area collettore 1", "oblasť kolektora 1") +MAKE_TRANSLATION(collector1Type, "collector1type", "collector 1 type", "Kollektor 1 Typ", "Type collector 1", "Kollektor 1 Typ", "typ kolektora 1", "kollektor 1 type", "type collecteur 1", "kollektör 1 tip", "tipo collettore 1", "kolektor 1 typ") +MAKE_TRANSLATION(collector2Area, "collector2area", "collector 2 area", "Kollektor 2 Fläche", "Oppervlakte collector 2", "Kollektor 2 Area", "powierzchnia kolektora 2", "kollektor 2 område", "zone collecteur 2", "kollektör 2 alan", "area collettore 2", "oblasť kolektora 2") +MAKE_TRANSLATION(collector2Type, "collector2type", "collector 2 type", "Kollektor 2 Typ", "Type collector 2", "Kollektor 2 Typ", "typ kolektora 2", "kollektor 2 type", "type collecteur 2", "kollektör 2 tip", "tipo collettore 2", "kolektor 2 typ") // telegram 0x0363 heatCounter -MAKE_TRANSLATION(heatCntFlowTemp, "heatcntflowtemp", "heat counter flow temperature", "Wärmezähler Vorlauf-Temperatur", "Aanvoertemperatuur warmteenergiemeter", "Värmeräknare Flödestemperatur", "temperatura zasilania ciepłomierza", "varmeenergimåler turtemperatur", "température flux compteur chaleur", "ısı sayacı akış sıcaklığı", "Temperatura di mandata del contatore di calore") -MAKE_TRANSLATION(heatCntRetTemp, "heatcntrettemp", "heat counter return temperature", "Wärmezähler Rücklauf-Temperatur", "Retourtemperatuur warmteenergiemeter", "Värmeräknare Returtemperatur", "temperatura powrotu ciepłomierza", "varmeenergimåler returtemperatur", "température retour compteur chaleur", "ısı sayacı dönüş sıcaklığı", "Temperatura di ritorno del contatore di calore") -MAKE_TRANSLATION(heatCnt, "heatcnt", "heat counter impulses", "Wärmezähler Impulse", "Warmteenergiemeter pulsen", "Värmeräknare Impuls", "liczba impulsów ciepłomierza", "varmemåler impuls", "impulsions compteur chaleur", "ısı sayacı atış adedi", "contacalore a impulsi") -MAKE_TRANSLATION(swapFlowTemp, "swapflowtemp", "swap flow temperature (TS14)", "Austausch Vorlauf-Temperatur (TS14)", "Aanvoertemperatuur verwisselaar (TS14)", "Växlingstemperatur Flöde (TS14)", "temperatura zasilania wymiennika", "veksler turledningstemperatur (TS14)", "température flux échangeur (TS14)", "değişim akış sıcaklığı(TS14)", "Scambiare la temperatura di mandata (TS14)") -MAKE_TRANSLATION(swapRetTemp, "swaprettemp", "swap return temperature (TS15)", "Austausch Rücklauf-Temperatur (TS15)", "Retourtemperatuur verwisselaar (TS15)", "Växlingstemperatur Returflöde (TS15)", "temperatura powrotu wymiennika", "veksler returledningstemperatur (TS15)", "température retour échangeur (TS15)", "değişim dönüş sıcaklığı(TS15)", "Scambiare la temperatura di ritorno (TS15)") +MAKE_TRANSLATION(heatCntFlowTemp, "heatcntflowtemp", "heat counter flow temperature", "Wärmezähler Vorlauf-Temperatur", "Aanvoertemperatuur warmteenergiemeter", "Värmeräknare Flödestemperatur", "temperatura zasilania ciepłomierza", "varmeenergimåler turtemperatur", "température flux compteur chaleur", "ısı sayacı akış sıcaklığı", "Temperatura di mandata del contatore di calore", "teplota prúdu počítadla tepla") +MAKE_TRANSLATION(heatCntRetTemp, "heatcntrettemp", "heat counter return temperature", "Wärmezähler Rücklauf-Temperatur", "Retourtemperatuur warmteenergiemeter", "Värmeräknare Returtemperatur", "temperatura powrotu ciepłomierza", "varmeenergimåler returtemperatur", "température retour compteur chaleur", "ısı sayacı dönüş sıcaklığı", "Temperatura di ritorno del contatore di calore", "teplota spiatočky počítadla tepla") +MAKE_TRANSLATION(heatCnt, "heatcnt", "heat counter impulses", "Wärmezähler Impulse", "Warmteenergiemeter pulsen", "Värmeräknare Impuls", "liczba impulsów ciepłomierza", "varmemåler impuls", "impulsions compteur chaleur", "ısı sayacı atış adedi", "contacalore a impulsi", "Impulzy počítadla tepla") +MAKE_TRANSLATION(swapFlowTemp, "swapflowtemp", "swap flow temperature (TS14)", "Austausch Vorlauf-Temperatur (TS14)", "Aanvoertemperatuur verwisselaar (TS14)", "Växlingstemperatur Flöde (TS14)", "temperatura zasilania wymiennika", "veksler turledningstemperatur (TS14)", "température flux échangeur (TS14)", "değişim akış sıcaklığı(TS14)", "Scambiare la temperatura di mandata (TS14)", "swap flow temperature (TS14)") +MAKE_TRANSLATION(swapRetTemp, "swaprettemp", "swap return temperature (TS15)", "Austausch Rücklauf-Temperatur (TS15)", "Retourtemperatuur verwisselaar (TS15)", "Växlingstemperatur Returflöde (TS15)", "temperatura powrotu wymiennika", "veksler returledningstemperatur (TS15)", "température retour échangeur (TS15)", "değişim dönüş sıcaklığı(TS15)", "Scambiare la temperatura di ritorno (TS15)", "výmena teploty spiatočky (TS15)") // switch -MAKE_TRANSLATION(activated, "activated", "activated", "Aktiviert", "Geactiveerd", "Aktiverad", "aktywowany", "aktivert", "activé", "başladı", "attivato") -MAKE_TRANSLATION(status, "status", "status", "Status", "Status", "Status", "status", "status", "statut", "durum", "Stato") +MAKE_TRANSLATION(activated, "activated", "activated", "Aktiviert", "Geactiveerd", "Aktiverad", "aktywowany", "aktivert", "activé", "başladı", "attivato", "aktivovaný") +MAKE_TRANSLATION(status, "status", "status", "Status", "Status", "Status", "status", "status", "statut", "durum", "Stato", "stav") // RF sensor, id 0x40, telegram 0x435 -MAKE_TRANSLATION(RFTemp, "rftemp", "RF room temperature sensor", "RF Raumtemperatur Sensor", "RF ruimtetemperatuur sensor", "RF Rumsgivare Temp", "bezprzewodowy czujnik temperatury pomieszczenia", "RF romsgiver temp", "capteur de température de pièce RF", "RF oda sıcaklık sensörü", "Sensore di temperatura ambiente RF") +MAKE_TRANSLATION(RFTemp, "rftemp", "RF room temperature sensor", "RF Raumtemperatur Sensor", "RF ruimtetemperatuur sensor", "RF Rumsgivare Temp", "bezprzewodowy czujnik temperatury pomieszczenia", "RF romsgiver temp", "capteur de température de pièce RF", "RF oda sıcaklık sensörü", "Sensore di temperatura ambiente RF", "RF snímač izbovej teploty") // ventilation -MAKE_TRANSLATION(outFresh, "outfresh", "outdoor fresh air", "Außenlufttemp.", "temperatuur buitenlucht", "", "świeże powietrze z zewnątrz", "", "", "dış ortam taze hava", "aria fresca esterna") // TODO translate -MAKE_TRANSLATION(inFresh, "infresh", "indoor fresh air", "Zulufttemp.", "temperatuur aanvoer", "", "nawiew", "", "", "iç ortam taze hava", "aria fresca interna") // TODO translate -MAKE_TRANSLATION(outEx, "outexhaust", "outdoor exhaust air", "Fortlufttemp.", "uitlaatemperatuur buiten", "", "zużyte powietrze z wewnątrz", "", "", "dış ortam egsoz", "aria di scarico esterna") // TODO translate -MAKE_TRANSLATION(inEx, "inexhaust", "indoor exhaust air", "Ablufttemp.", "uitlaattemperatuur binnen", "", "wywiew", "", "", "iç ortam egsoz", "aria di scarico interna") // TODO translate -MAKE_TRANSLATION(ventMode, "ventmode", "ventilation mode", "Belüftungsmodus", "ventilatiemodus", "", "tryb wentylacji", "", "", "havalandırma modu", "modalità di ventilazione") // TODO translate -MAKE_TRANSLATION(ventInSpeed, "ventinspeed", "in blower speed", "Zuluft-Drehzahl", "toerental aanvoerventilator", "", "prędkość wentylatora nawiewu", "", "", "iç fan hızı", "velocità aria di alimentazione") // TODO translate -MAKE_TRANSLATION(ventOutSpeed, "ventoutspeed", "out blower speed", "Abluft-Drehzahl", "toerental afvoerventilator", "", "prędjkość wentylatora wywiewu", "", "", "dış fan hızı", "velocità aria di scarico") // TODO translate -MAKE_TRANSLATION(airquality, "airquality", "air quality (voc)", "Luftqualität (VOC)", "luchtkwaliteit (VOC)", "", "jakość powietrza", "", "", "hava kalitesi(voc)", "qualità aria (VOC)") // TODO translate +MAKE_TRANSLATION(outFresh, "outfresh", "outdoor fresh air", "Außenlufttemp.", "temperatuur buitenlucht", "", "świeże powietrze z zewnątrz", "", "", "dış ortam taze hava", "aria fresca esterna", "čerstvý vzduch vonku") // TODO translate +MAKE_TRANSLATION(inFresh, "infresh", "indoor fresh air", "Zulufttemp.", "temperatuur aanvoer", "", "nawiew", "", "", "iç ortam taze hava", "aria fresca interna", "čerstvý vzduch v interiéri") // TODO translate +MAKE_TRANSLATION(outEx, "outexhaust", "outdoor exhaust air", "Fortlufttemp.", "uitlaatemperatuur buiten", "", "zużyte powietrze z wewnątrz", "", "", "dış ortam egsoz", "aria di scarico esterna", "vonkajší odpadový vzduch") // TODO translate +MAKE_TRANSLATION(inEx, "inexhaust", "indoor exhaust air", "Ablufttemp.", "uitlaattemperatuur binnen", "", "wywiew", "", "", "iç ortam egsoz", "aria di scarico interna", "") // TODO translate +MAKE_TRANSLATION(ventMode, "ventmode", "ventilation mode", "Belüftungsmodus", "ventilatiemodus", "", "tryb wentylacji", "", "", "havalandırma modu", "modalità di ventilazione", "režim vetrania") // TODO translate +MAKE_TRANSLATION(ventInSpeed, "ventinspeed", "in blower speed", "Zuluft-Drehzahl", "toerental aanvoerventilator", "", "prędkość wentylatora nawiewu", "", "", "iç fan hızı", "velocità aria di alimentazione", "rýchlosť ventilátora") // TODO translate +MAKE_TRANSLATION(ventOutSpeed, "ventoutspeed", "out blower speed", "Abluft-Drehzahl", "toerental afvoerventilator", "", "prędjkość wentylatora wywiewu", "", "", "dış fan hızı", "velocità aria di scarico", "rýchlosť výstupného ventilátora") // TODO translate +MAKE_TRANSLATION(airquality, "airquality", "air quality (voc)", "Luftqualität (VOC)", "luchtkwaliteit (VOC)", "", "jakość powietrza", "", "", "hava kalitesi(voc)", "qualità aria (VOC)", "kvalita vzduchu (voc)") // TODO translate // EM100 -MAKE_TRANSLATION(minV, "minv", "min volt.", "min Spannung", "", "", "minimalne napięcie", "", "", "", "") // TODO translate -MAKE_TRANSLATION(maxV, "maxv", "max volt.", "max Spannung", "", "", "maksymalne napięcie", "", "", "", "") // TODO translate -MAKE_TRANSLATION(minT, "mint", "min temp.", "min Temperatur", "", "", "minimalna temperatura", "", "", "", "") // TODO translate -MAKE_TRANSLATION(maxT, "maxt", "max temp.", "max Temperatur", "", "", "maksymalna temperatura", "", "", "", "") // TODO translate -MAKE_TRANSLATION(setPoint, "setpoint", "set temp.", "Sollemperatur", "", "", "temperatura nastawu", "", "", "", "") // TODO translate -MAKE_TRANSLATION(setPower, "setpower", "request power", "Sollleistung", "", "", "zadana moc", "", "", "", "") // TODO translate -MAKE_TRANSLATION(dip, "dip", "dip switch", "dip Schalter", "", "", "przełącznik DIP", "", "", "", "") // TODO translate -MAKE_TRANSLATION(outPower, "outpow", "output IO1", "Ausgang IO1", "", "", "wyjście IO1", "", "", "", "") // TODO translate -MAKE_TRANSLATION(input, "input", "input", "Eingang", "", "", "wejście", "", "", "", "") // TODO translate +MAKE_TRANSLATION(minV, "minv", "min volt.", "min Spannung", "", "", "minimalne napięcie", "", "", "", "", "min napätie") // TODO translate +MAKE_TRANSLATION(maxV, "maxv", "max volt.", "max Spannung", "", "", "maksymalne napięcie", "", "", "", "", "max napätie") // TODO translate +MAKE_TRANSLATION(minT, "mint", "min temp.", "min Temperatur", "", "", "minimalna temperatura", "", "", "", "", "min tepl.") // TODO translate +MAKE_TRANSLATION(maxT, "maxt", "max temp.", "max Temperatur", "", "", "maksymalna temperatura", "", "", "", "", "max tepl.") // TODO translate +MAKE_TRANSLATION(setPoint, "setpoint", "set temp.", "Sollemperatur", "", "", "temperatura nastawu", "", "", "", "", "pož. teplota") // TODO translate +MAKE_TRANSLATION(setPower, "setpower", "request power", "Sollleistung", "", "", "zadana moc", "", "", "", "", "pož. výkon") // TODO translate +MAKE_TRANSLATION(dip, "dip", "dip switch", "dip Schalter", "", "", "przełącznik DIP", "", "", "", "", "dip prepínač") // TODO translate +MAKE_TRANSLATION(outPower, "outpow", "output IO1", "Ausgang IO1", "", "", "wyjście IO1", "", "", "", "", "výstup IO1") // TODO translate +MAKE_TRANSLATION(input, "input", "input", "Eingang", "", "", "wejście", "", "", "", "", "vstup") // TODO translate /* // unknown fields to track (SM10), only for testing // **** NO TRANSLATION NEEDED **** -MAKE_TRANSLATION(data11, "data11", "unknown datafield 11", "", "", "", "nieznane pole danych 11", "", "", "", "") -MAKE_TRANSLATION(data12, "data12", "unknown datafield 12", "", "", "", "nieznane pole danych 12", "", "", "", "") -MAKE_TRANSLATION(data8, "data8", "unknown datafield 8", "", "", "", "nieznane pole danych 8", "", "", "", "") -MAKE_TRANSLATION(data0, "data0", "unknown datafield 0", "", "", "", "nieznane pole danych 0", "", "", "", "") -MAKE_TRANSLATION(data1, "data1", "unknown datafield 1", "", "", "", "nieznane pole danych 1", "", "", "", "") -MAKE_TRANSLATION(setting3, "setting3", "unknown setting 3", "", "", "", "nieznane ustawienie 3", "", "", "", "") -MAKE_TRANSLATION(setting4, "setting4", "unknown setting 4", "", "", "", "nieznane ustawienie 4", "", "", "", "") +MAKE_TRANSLATION(data11, "data11", "unknown datafield 11", "", "", "", "nieznane pole danych 11", "", "", "", "", "neznáme dátové pole 11") +MAKE_TRANSLATION(data12, "data12", "unknown datafield 12", "", "", "", "nieznane pole danych 12", "", "", "", "", "neznáme dátové pole 12") +MAKE_TRANSLATION(data8, "data8", "unknown datafield 8", "", "", "", "nieznane pole danych 8", "", "", "", "", "neznáme dátové pole 8") +MAKE_TRANSLATION(data0, "data0", "unknown datafield 0", "", "", "", "nieznane pole danych 0", "", "", "", "", "neznáme dátové pole 0") +MAKE_TRANSLATION(data1, "data1", "unknown datafield 1", "", "", "", "nieznane pole danych 1", "", "", "", "", "neznáme dátové pole 1") +MAKE_TRANSLATION(setting3, "setting3", "unknown setting 3", "", "", "", "nieznane ustawienie 3", "", "", "", "", "neznáme dátové pole 3") +MAKE_TRANSLATION(setting4, "setting4", "unknown setting 4", "", "", "", "nieznane ustawienie 4", "", "", "", "", "neznáme dátové pole 4") */ -// clang-format on +// clang-format on \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 0906f965a..722f5fb39 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -18,7 +18,9 @@ #include "emsesp.h" -static emsesp::EMSESP application; +using namespace emsesp; + +static EMSESP application; // the main application void setup() { application.start(); diff --git a/src/mqtt.cpp b/src/mqtt.cpp index ff2f42351..3b0745405 100644 --- a/src/mqtt.cpp +++ b/src/mqtt.cpp @@ -1194,12 +1194,16 @@ bool Mqtt::publish_ha_climate_config(const uint8_t tag, const bool has_roomtemp, snprintf(mode_str_tpl, sizeof(mode_str_tpl), - "{%%if %s%%}off{%%elif %s=='manual'%%}heat{%%elif %s=='day'%%}heat{%%elif %s=='night'%%}off{%%elif %s=='off'%%}off{%%else%%}auto{%%endif%%}", + "{%%if %s%%}off{%%elif %s=='%s'%%}heat{%%elif %s=='%s'%%}heat{%%elif %s=='%s'%%}off{%%elif %s=='%s'%%}off{%%else%%}auto{%%endif%%}", hc_mode_cond, hc_mode_s, + Helpers::translated_word(FL_(manual)), hc_mode_s, + Helpers::translated_word(FL_(day)), hc_mode_s, - hc_mode_s); + Helpers::translated_word(FL_(night)), + hc_mode_s, + Helpers::translated_word(FL_(off))); snprintf(name_s, sizeof(name_s), "Hc%d", hc_num); diff --git a/src/shower.cpp b/src/shower.cpp index f83813cb1..8c59abbf6 100644 --- a/src/shower.cpp +++ b/src/shower.cpp @@ -211,9 +211,9 @@ void Shower::set_shower_state(bool state, bool force) { } JsonObject dev = doc.createNestedObject("dev"); - dev["name"] = "EMS-ESP"; + dev["name"] = "EMS-ESP Shower"; JsonArray ids = dev.createNestedArray("ids"); - ids.add(Mqtt::basename()); + ids.add(Mqtt::basename() + "-shower"); Mqtt::add_avty_to_doc(stat_t, doc.as()); // add "availability" section @@ -221,7 +221,7 @@ void Shower::set_shower_state(bool state, bool force) { ha_configdone_ = Mqtt::queue_ha(topic, doc.as()); // publish the config payload with retain flag // - // shower duaration + // shower duration // doc.clear(); @@ -241,9 +241,9 @@ void Shower::set_shower_state(bool state, bool force) { // doc["ent_cat"] = "diagnostic"; JsonObject dev2 = doc.createNestedObject("dev"); - dev2["name"] = "EMS-ESP"; + dev2["name"] = "EMS-ESP Shower"; JsonArray ids2 = dev2.createNestedArray("ids"); - ids2.add(Mqtt::basename()); + ids2.add(Mqtt::basename() + "-shower"); Mqtt::add_avty_to_doc(stat_t, doc.as(), "value_json.duration is defined"); // add "availability" section @@ -268,9 +268,9 @@ void Shower::set_shower_state(bool state, bool force) { // doc["ent_cat"] = "diagnostic"; JsonObject dev3 = doc.createNestedObject("dev"); - dev3["name"] = "EMS-ESP"; + dev3["name"] = "EMS-ESP Shower"; JsonArray ids3 = dev3.createNestedArray("ids"); - ids3.add(Mqtt::basename()); + ids3.add(Mqtt::basename() + "-shower"); Mqtt::add_avty_to_doc(stat_t, doc.as(), "value_json.timestamp is defined"); // add "availability" section diff --git a/src/system.cpp b/src/system.cpp index 6677c5ff6..05ee5b866 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -64,7 +64,8 @@ const char * const languages[] = {EMSESP_LOCALE_EN, EMSESP_LOCALE_NO, EMSESP_LOCALE_FR, EMSESP_LOCALE_TR, - EMSESP_LOCALE_IT}; + EMSESP_LOCALE_IT, + EMSESP_LOCALE_SK}; #endif static constexpr uint8_t NUM_LANGUAGES = sizeof(languages) / sizeof(const char *); @@ -253,7 +254,9 @@ bool System::command_watch(const char * value, const int8_t id) { } void System::store_nvs_values() { - Command::call(EMSdevice::DeviceType::BOILER, "nompower", "-1"); // trigger a write + if (Command::find_command(EMSdevice::DeviceType::BOILER, 0, "nompower") != nullptr) { + Command::call(EMSdevice::DeviceType::BOILER, "nompower", "-1"); // trigger a write + } EMSESP::analogsensor_.store_counters(); EMSESP::nvs_.end(); } @@ -302,7 +305,7 @@ void System::syslog_init() { #ifndef EMSESP_STANDALONE if (syslog_enabled_) { // start & configure syslog - EMSESP::logger().info("Starting Syslog service"); + logger_.info("Starting Syslog service"); syslog_.start(); syslog_.log_level((uuid::log::Level)syslog_level_); @@ -316,7 +319,7 @@ void System::syslog_init() { } else if (syslog_.started()) { // in case service is still running, this flushes the queue // https://github.com/emsesp/EMS-ESP/issues/496 - EMSESP::logger().info("Stopping Syslog"); + logger_.info("Stopping Syslog service"); syslog_.log_level((uuid::log::Level)-1); // stop server syslog_.mark_interval(0); syslog_.destination(""); @@ -442,7 +445,6 @@ void System::start() { setCpuFrequencyMhz(160); #endif } - // get current memory values fstotal_ = LittleFS.totalBytes() / 1024; // read only once, it takes 500 ms to read psram_ = ESP.getPsramSize() / 1024; @@ -729,7 +731,11 @@ void System::network_init(bool refresh) { // ETH_CLOCK_GPIO17_OUT = 3 RMII clock output from GPIO17, for 50hz inverted clock auto clock_mode = (eth_clock_mode_t)eth_clock_mode_; - eth_present_ = ETH.begin((eth_phy_type_t)phy_addr, power, mdc, mdio, type, clock_mode); +#if ESP_ARDUINO_VERSION_MAJOR < 3 + eth_present_ = ETH.begin(phy_addr, power, mdc, mdio, type, clock_mode); +#else + eth_present_ = ETH.begin(type, phy_addr, mdc, mdio, power, clock_mode); +#endif #endif } @@ -1039,42 +1045,46 @@ bool System::check_restore() { #ifndef EMSESP_STANDALONE // see if we have a temp file, if so try and read it - File new_file = LittleFS.open(TEMP_FILENAME_PATH); - if (new_file) { - DynamicJsonDocument jsonDocument = DynamicJsonDocument(FS_BUFFER_SIZE); - DeserializationError error = deserializeJson(jsonDocument, new_file); - if (error == DeserializationError::Ok && jsonDocument.is()) { - JsonObject input = jsonDocument.as(); - // see what type of file it is, either settings or customization. anything else is ignored - std::string settings_type = input["type"]; - if (settings_type == "settings") { - // It's a settings file. Parse each section separately. If it's system related it will require a reboot - reboot_required = saveSettings(NETWORK_SETTINGS_FILE, "Network", input); - reboot_required |= saveSettings(AP_SETTINGS_FILE, "AP", input); - reboot_required |= saveSettings(MQTT_SETTINGS_FILE, "MQTT", input); - reboot_required |= saveSettings(NTP_SETTINGS_FILE, "NTP", input); - reboot_required |= saveSettings(SECURITY_SETTINGS_FILE, "Security", input); - reboot_required |= saveSettings(EMSESP_SETTINGS_FILE, "Settings", input); - reboot_required |= saveSettings(OTA_SETTINGS_FILE, "OTA", input); - } else if (settings_type == "customizations") { - // it's a customization file, just replace it and there's no need to reboot - saveSettings(EMSESP_CUSTOMIZATION_FILE, "Customizations", input); - } else if (settings_type == "schedule") { - // it's a schedule file, just replace it and there's no need to reboot - saveSettings(EMSESP_SCHEDULER_FILE, "Schedule", input); - } else if (settings_type == "entities") { - // it's a entity file, just replace it and there's no need to reboot - saveSettings(EMSESP_CUSTOMENTITY_FILE, "Entities", input); + // prevents open(): /littlefs/tmp_upload does not exist, no permits for creation + // but doesn't work! https://github.com/espressif/arduino-esp32/issues/7615 + if (LittleFS.exists(TEMP_FILENAME_PATH)) { + File new_file = LittleFS.open(TEMP_FILENAME_PATH); + if (new_file) { + DynamicJsonDocument jsonDocument = DynamicJsonDocument(FS_BUFFER_SIZE); + DeserializationError error = deserializeJson(jsonDocument, new_file); + if (error == DeserializationError::Ok && jsonDocument.is()) { + JsonObject input = jsonDocument.as(); + // see what type of file it is, either settings or customization. anything else is ignored + std::string settings_type = input["type"]; + if (settings_type == "settings") { + // It's a settings file. Parse each section separately. If it's system related it will require a reboot + reboot_required = saveSettings(NETWORK_SETTINGS_FILE, "Network", input); + reboot_required |= saveSettings(AP_SETTINGS_FILE, "AP", input); + reboot_required |= saveSettings(MQTT_SETTINGS_FILE, "MQTT", input); + reboot_required |= saveSettings(NTP_SETTINGS_FILE, "NTP", input); + reboot_required |= saveSettings(SECURITY_SETTINGS_FILE, "Security", input); + reboot_required |= saveSettings(EMSESP_SETTINGS_FILE, "Settings", input); + reboot_required |= saveSettings(OTA_SETTINGS_FILE, "OTA", input); + } else if (settings_type == "customizations") { + // it's a customization file, just replace it and there's no need to reboot + saveSettings(EMSESP_CUSTOMIZATION_FILE, "Customizations", input); + } else if (settings_type == "schedule") { + // it's a schedule file, just replace it and there's no need to reboot + saveSettings(EMSESP_SCHEDULER_FILE, "Schedule", input); + } else if (settings_type == "entities") { + // it's a entity file, just replace it and there's no need to reboot + saveSettings(EMSESP_CUSTOMENTITY_FILE, "Entities", input); + } else { + LOG_ERROR("Unrecognized file uploaded"); + } } else { - LOG_ERROR("Unrecognized file uploaded"); + LOG_ERROR("Unrecognized file uploaded, not json"); } - } else { - LOG_ERROR("Unrecognized file uploaded, not json"); - } - // close (just in case) and remove the temp file - new_file.close(); - LittleFS.remove(TEMP_FILENAME_PATH); + // close (just in case) and remove the temp file + new_file.close(); + LittleFS.remove(TEMP_FILENAME_PATH); + } } #endif @@ -1104,7 +1114,7 @@ bool System::check_upgrade(bool factory_settings) { #if defined(EMSESP_DEBUG) if (!missing_version) { - LOG_INFO("Current version from settings is %d.%d.%d-%s", + LOG_INFO("Checking version (settings has %d.%d.%d-%s)...", settings_version.major(), settings_version.minor(), settings_version.patch(), @@ -1112,26 +1122,20 @@ bool System::check_upgrade(bool factory_settings) { } #endif - // always save the new version to the settings - EMSESP::webSettingsService.update( - [&](WebSettings & settings) { - settings.version = EMSESP_APP_VERSION; - return StateUpdateResult::CHANGED; - }, - "local"); - if (factory_settings) { return false; // fresh install, do nothing } version::Semver200_version this_version(EMSESP_APP_VERSION); + bool save_version = true; + // compare versions - bool reboot_required = false; if (this_version > settings_version) { + // need upgrade LOG_NOTICE("Upgrading to version %d.%d.%d-%s", this_version.major(), this_version.minor(), this_version.patch(), this_version.prerelease().c_str()); - // if we're coming from 3.4.4 or 3.5.0b14 then we need to apply new settings + // if we're coming from 3.4.4 or 3.5.0b14 which had no version stored then we need to apply new settings if (missing_version) { LOG_DEBUG("Setting MQTT Entity ID format to v3.4 format"); EMSESP::esp8266React.getMqttSettingsService()->update( @@ -1141,15 +1145,26 @@ bool System::check_upgrade(bool factory_settings) { }, "local"); } - } else if (this_version < settings_version) { + // need downgrade LOG_NOTICE("Downgrading to version %d.%d.%d-%s", this_version.major(), this_version.minor(), this_version.patch(), this_version.prerelease().c_str()); } else { // same version, do nothing - return false; + save_version = false; } - return reboot_required; + // if we did a change, set the new version and reboot + if (save_version) { + EMSESP::webSettingsService.update( + [&](WebSettings & settings) { + settings.version = EMSESP_APP_VERSION; + return StateUpdateResult::CHANGED; + }, + "local"); + return true; // need reboot + } + + return false; } // list commands @@ -1201,17 +1216,26 @@ bool System::command_info(const char * value, const int8_t id, JsonObject & outp // System node = output.createNestedObject("System Info"); node["version"] = EMSESP_APP_VERSION; - node["platform"] = EMSESP_PLATFORM; node["uptime"] = uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3); node["uptime (seconds)"] = uuid::get_uptime_sec(); #ifndef EMSESP_STANDALONE + node["platform"] = ARDUINO_VERSION; + + node["sdk"] = ESP.getSdkVersion(); node["free mem"] = getHeapMem(); node["max alloc"] = getMaxAllocMem(); + node["used app"] = EMSESP::system_.appUsed(); // kilobytes node["free app"] = EMSESP::system_.appFree(); // kilobytes -#endif - node["reset reason"] = EMSESP::system_.reset_reason(0) + " / " + EMSESP::system_.reset_reason(1); + node["partition"] = esp_ota_get_running_partition()->label; + + // hash: Helpers::data_to_hex(desc->app_elf_sha256, sizeof(desc->app_elf_sha256)); + const esp_app_desc_t * desc = + esp_ota_get_app_description(); // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/misc_system_api.html#_CPPv414esp_app_desc_t + if (desc != nullptr) { + node["app_build"] = std::string(desc->date) + " " + desc->time; + } + node["build_date"] = std::string(__DATE__) + " " + __TIME__; -#ifndef EMSESP_STANDALONE // Network Status node = output.createNestedObject("Network Info"); if (EMSESP::system_.ethernet_connected()) { @@ -1424,10 +1448,11 @@ bool System::command_info(const char * value, const int8_t id, JsonObject & outp obj["product id"] = emsdevice->product_id(); obj["version"] = emsdevice->version(); obj["entities"] = emsdevice->count_entities(); - char result[300]; + char result[300] = {'\0'}; (void)emsdevice->show_telegram_handlers(result, sizeof(result), EMSdevice::Handlers::RECEIVED); + // don't show handlers if there aren't any if (result[0] != '\0') { - obj["handlers received"] = result; // don't show handlers if there aren't any + obj["handlers received"] = result; } (void)emsdevice->show_telegram_handlers(result, sizeof(result), EMSdevice::Handlers::FETCHED); if (result[0] != '\0') { @@ -1581,4 +1606,4 @@ bool System::ntp_connected() { return ntp_connected_; } -} // namespace emsesp +} // namespace emsesp \ No newline at end of file diff --git a/src/temperaturesensor.cpp b/src/temperaturesensor.cpp index 3f91c463c..31c50599b 100644 --- a/src/temperaturesensor.cpp +++ b/src/temperaturesensor.cpp @@ -544,9 +544,9 @@ void TemperatureSensor::publish_values(const bool force) { config["name"] = name; JsonObject dev = config.createNestedObject("dev"); - dev["name"] = Mqtt::basename(); + dev["name"] = Mqtt::basename() + " Temperature"; JsonArray ids = dev.createNestedArray("ids"); - ids.add(Mqtt::basename()); + ids.add(Mqtt::basename() + "-temperature"); // add "availability" section Mqtt::add_avty_to_doc(stat_t, config.as(), val_cond); diff --git a/src/test/test.cpp b/src/test/test.cpp index 2503eea88..c5e3cf040 100644 --- a/src/test/test.cpp +++ b/src/test/test.cpp @@ -282,8 +282,8 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const run_test("general"); // add sensors - emsesp::EMSESP::analogsensor_.test(); - emsesp::EMSESP::temperaturesensor_.test(); + EMSESP::analogsensor_.test(); + EMSESP::temperaturesensor_.test(); // shell.invoke_command("show devices"); shell.invoke_command("show values"); @@ -306,7 +306,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const run_test("general"); #ifdef EMSESP_STANDALONE - AsyncWebServerRequest request; + PsychicRequest request; request.method(HTTP_GET); request.url("/api/custom"); request.url("/api/custom/boiler_flowtemp"); @@ -321,7 +321,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const run_test("general"); #ifdef EMSESP_STANDALONE - AsyncWebServerRequest request; + PsychicRequest request; request.method(HTTP_GET); request.url("/api/boiler/coldshot"); EMSESP::webAPIService.webAPIService_get(&request); @@ -704,7 +704,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const if (command == "temperature") { shell.printfln("Testing adding Temperature sensor"); - emsesp::EMSESP::temperaturesensor_.test(); + EMSESP::temperaturesensor_.test(); ok = true; } @@ -714,7 +714,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const Mqtt::nested_format(1); // Mqtt::nested_format(0); - emsesp::EMSESP::temperaturesensor_.test(); + EMSESP::temperaturesensor_.test(); shell.invoke_command("show values"); shell.invoke_command("call system publish"); @@ -732,7 +732,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const Mqtt::nested_format(1); // Mqtt::nested_format(0); - emsesp::EMSESP::analogsensor_.test(); + EMSESP::analogsensor_.test(); shell.invoke_command("show values"); // shell.invoke_command("call system publish"); // shell.invoke_command("show mqtt"); @@ -846,9 +846,9 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const run_test("boiler"); run_test("thermostat"); - AsyncWebServerRequest request; - DynamicJsonDocument doc(2000); - JsonVariant json; + PsychicRequest request; + DynamicJsonDocument doc(2000); + JsonVariant json; request.method(HTTP_GET); request.url("/api/boiler/values"); @@ -890,7 +890,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const Mqtt::nested_format(1); run_test("310"); - AsyncWebServerRequest request; + PsychicRequest request; request.method(HTTP_POST); DynamicJsonDocument doc(2000); JsonVariant json; @@ -915,9 +915,9 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const run_test("boiler"); run_test("thermostat"); - AsyncWebServerRequest requestX; - DynamicJsonDocument docX(2000); - JsonVariant jsonX; + PsychicRequest requestX; + DynamicJsonDocument docX(2000); + JsonVariant jsonX; requestX.method(HTTP_GET); @@ -956,19 +956,19 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const /* requestX.url("/api/system"); // check if defaults to info EMSESP::webAPIService.webAPIService_get(&requestX); - emsesp::EMSESP::logger().notice("*"); + EMSESP::logger().notice("*"); requestX.url("/api/system/info"); EMSESP::webAPIService.webAPIService_get(&requestX); - emsesp::EMSESP::logger().notice("*"); + EMSESP::logger().notice("*"); requestX.url("/api/thermostat"); // check if defaults to values EMSESP::webAPIService.webAPIService_get(&requestX); - emsesp::EMSESP::logger().notice("*"); + EMSESP::logger().notice("*"); requestX.url("/api/thermostat/info"); EMSESP::webAPIService.webAPIService_get(&requestX); - emsesp::EMSESP::logger().notice("*"); + EMSESP::logger().notice("*"); requestX.url("/api/thermostat/seltemp"); EMSESP::webAPIService.webAPIService_get(&requestX); @@ -984,7 +984,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const /* requestX.url("/api/temperaturesensor/xxxx"); EMSESP::webAPIService.webAPIService_get(&requestX); - emsesp::EMSESP::logger().notice("****"); + EMSESP::logger().notice("****"); requestX.url("/api/temperaturesensor/info"); EMSESP::webAPIService.webAPIService_get(&requestX); return; @@ -1084,7 +1084,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const EMSESP::mqtt_.incoming("home/cellar/heating/thermostat/mode"); // empty payload // Web API TESTS - AsyncWebServerRequest request; + PsychicRequest request; request.method(HTTP_GET); @@ -1160,7 +1160,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const request.url("/api"); EMSESP::webAPIService.webAPIService_post(&request, json); - emsesp::EMSESP::logger().warning("* these next ones should fail *"); + EMSESP::logger().warning("* these next ones should fail *"); // write value from web - testing hc9/seltemp - should fail! char data8[] = "{\"id\":2,\"devicevalue\":{\"v\":\"55\",\"u\":1,\"n\":\"hc2 selected room temperature\",\"c\":\"hc9/seltemp\"}"; @@ -1714,7 +1714,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const // shell.invoke_command("call mixer wwc2 info"); // test API - AsyncWebServerRequest request; + PsychicRequest request; request.url("/api/mixer"); EMSESP::webAPIService.webAPIService_get(&request); request.url("/api/mixer/hc1/pumpstatus"); diff --git a/src/test/test.h b/src/test/test.h index 1ae7a5a83..583e10638 100644 --- a/src/test/test.h +++ b/src/test/test.h @@ -22,7 +22,7 @@ #define EMSESP_TEST_H #include "emsesp.h" -#include +#include namespace emsesp { diff --git a/src/version.h b/src/version.h index 98c9132ab..986808d84 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define EMSESP_APP_VERSION "3.6.5-dev.4" +#define EMSESP_APP_VERSION "3.6.5-https.1" diff --git a/src/web/WebAPIService.cpp b/src/web/WebAPIService.cpp index 5c340038c..99b125e38 100644 --- a/src/web/WebAPIService.cpp +++ b/src/web/WebAPIService.cpp @@ -1,7 +1,7 @@ /* * EMS-ESP - https://github.com/emsesp/EMS-ESP * Copyright 2020-2023 Paul Derbyshire - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or @@ -25,49 +25,45 @@ namespace emsesp { uint32_t WebAPIService::api_count_ = 0; uint16_t WebAPIService::api_fails_ = 0; -WebAPIService::WebAPIService(AsyncWebServer * server, SecurityManager * securityManager) - : _securityManager(securityManager) - , _apiHandler("/api", std::bind(&WebAPIService::webAPIService_post, this, _1, _2), 256) { // for POSTS, must use 'Content-Type: application/json' in header - server->on("/api", HTTP_GET, std::bind(&WebAPIService::webAPIService_get, this, _1)); // for GETS - server->addHandler(&_apiHandler); +WebAPIService::WebAPIService(PsychicHttpServer * server, SecurityManager * securityManager) + : _server(server) + , _securityManager(securityManager) { +} + +void WebAPIService::registerURI() { + // POST /api/{device}[/{hc|wwc|id}][/{name}] + _server->on(EMSESP_API_SERVICE_PATH, HTTP_POST, [this](PsychicRequest * request, JsonVariant & json) { + // if no json body then treat it as a secure GET + if (!json.is()) { + StaticJsonDocument input_doc; + JsonObject input = input_doc.to(); + return parse(request, input); + } + + // extract values from the json. these will be used as default values + auto && input = json.as(); + return parse(request, input); + }); + + // GET /{device}/{entity} + _server->on(EMSESP_API_SERVICE_PATH, HTTP_GET, [this](PsychicRequest * request) { + StaticJsonDocument input_doc; + JsonObject input = input_doc.to(); // empty input json + return parse(request, input); + }); // for settings - server->on(GET_SETTINGS_PATH, HTTP_GET, securityManager->wrapRequest(std::bind(&WebAPIService::getSettings, this, _1), AuthenticationPredicates::IS_ADMIN)); - server->on(GET_CUSTOMIZATIONS_PATH, - HTTP_GET, - securityManager->wrapRequest(std::bind(&WebAPIService::getCustomizations, this, _1), AuthenticationPredicates::IS_ADMIN)); - server->on(GET_SCHEDULE_PATH, HTTP_GET, securityManager->wrapRequest(std::bind(&WebAPIService::getSchedule, this, _1), AuthenticationPredicates::IS_ADMIN)); - server->on(GET_ENTITIES_PATH, HTTP_GET, securityManager->wrapRequest(std::bind(&WebAPIService::getEntities, this, _1), AuthenticationPredicates::IS_ADMIN)); -} - -// HTTP GET -// GET /{device} -// GET /{device}/{entity} -void WebAPIService::webAPIService_get(AsyncWebServerRequest * request) { - // has no body JSON so create dummy as empty input object - StaticJsonDocument input_doc; - JsonObject input = input_doc.to(); - parse(request, input); -} - -// For HTTP POSTS with an optional JSON body -// HTTP_POST | HTTP_PUT | HTTP_PATCH -// POST /{device}[/{hc|id}][/{name}] -void WebAPIService::webAPIService_post(AsyncWebServerRequest * request, JsonVariant & json) { - // if no body then treat it as a secure GET - if (!json.is()) { - webAPIService_get(request); - return; - } - - // extract values from the json. these will be used as default values - auto && input = json.as(); - parse(request, input); + _server->on(GET_SETTINGS_PATH, HTTP_GET, _securityManager->wrapRequest(std::bind(&WebAPIService::getSettings, this, _1), AuthenticationPredicates::IS_ADMIN)); + _server->on(GET_CUSTOMIZATIONS_PATH, + HTTP_GET, + _securityManager->wrapRequest(std::bind(&WebAPIService::getCustomizations, this, _1), AuthenticationPredicates::IS_ADMIN)); + _server->on(GET_SCHEDULE_PATH, HTTP_GET, _securityManager->wrapRequest(std::bind(&WebAPIService::getSchedule, this, _1), AuthenticationPredicates::IS_ADMIN)); + _server->on(GET_ENTITIES_PATH, HTTP_GET, _securityManager->wrapRequest(std::bind(&WebAPIService::getEntities, this, _1), AuthenticationPredicates::IS_ADMIN)); } // parse the URL looking for query or path parameters // reporting back any errors -void WebAPIService::parse(AsyncWebServerRequest * request, JsonObject & input) { +esp_err_t WebAPIService::parse(PsychicRequest * request, JsonObject & input) { // check if the user has admin privileges (token is included and authorized) bool is_admin = false; EMSESP::webSettingsService.read([&](WebSettings & settings) { @@ -77,47 +73,50 @@ void WebAPIService::parse(AsyncWebServerRequest * request, JsonObject & input) { // check for query parameters first, the old style from v2 // api?device={device}&cmd={name}&data={value}&id={hc} - if (request->url() == "/api") { - // get the device - if (request->hasParam(F_(device))) { - input["device"] = request->getParam(F_(device))->value().c_str(); - } - if (request->hasParam(F_(cmd))) { - input["cmd"] = request->getParam(F_(cmd))->value().c_str(); - } - if (request->hasParam(F_(data))) { - input["data"] = request->getParam(F_(data))->value().c_str(); - } - if (request->hasParam(F_(value))) { - input["value"] = request->getParam(F_(value))->value().c_str(); - } - if (request->hasParam(F_(id))) { - input["id"] = Helpers::atoint(request->getParam(F_(id))->value().c_str()); - } - if (request->hasParam(F_(hc))) { - input["hc"] = Helpers::atoint(request->getParam(F_(hc))->value().c_str()); - } - if (request->hasParam(F_(wwc))) { - input["wwc"] = Helpers::atoint(request->getParam(F_(wwc))->value().c_str()); - } + if (request->hasParam(F_(device))) { + input["device"] = request->getParam(F_(device))->value().c_str(); + } + if (request->hasParam(F_(cmd))) { + input["cmd"] = request->getParam(F_(cmd))->value().c_str(); + } + if (request->hasParam(F_(data))) { + input["data"] = request->getParam(F_(data))->value().c_str(); + } + if (request->hasParam(F_(value))) { + input["value"] = request->getParam(F_(value))->value().c_str(); + } + if (request->hasParam(F_(id))) { + input["id"] = Helpers::atoint(request->getParam(F_(id))->value().c_str()); + } + if (request->hasParam(F_(hc))) { + input["hc"] = Helpers::atoint(request->getParam(F_(hc))->value().c_str()); + } + if (request->hasParam(F_(wwc))) { + input["wwc"] = Helpers::atoint(request->getParam(F_(wwc))->value().c_str()); } // capture current heap memory before allocating the large return buffer - emsesp::EMSESP::system_.refreshHeapMem(); + EMSESP::system_.refreshHeapMem(); // output json buffer - size_t buffer = EMSESP_JSON_SIZE_XXXLARGE; - auto * response = new PrettyAsyncJsonResponse(false, buffer); - while (!response->getSize()) { - delete response; - buffer -= 1024; - response = new PrettyAsyncJsonResponse(false, buffer); - } - JsonObject output = response->getRoot(); + size_t buffer = EMSESP_JSON_SIZE_XXXLARGE; + + PsychicJsonResponse response = PsychicJsonResponse(request, false, buffer); + + // TODO add back memory managegement. Be careful we do need to free()/delete() any object we extend with new() + // while (!response->getSize()) { + // delete response; + // buffer -= 1024; + // response = new PrettyAsyncJsonResponse(false, buffer); + // response = PsychicJsonResponse(request, false, buffer); + // } + + JsonObject output = response.getRoot(); // call command uint8_t return_code = Command::process(request->url().c_str(), is_admin, input, output); + // handle failure if (return_code != CommandRet::OK) { char error[100]; if (output.size()) { @@ -125,44 +124,33 @@ void WebAPIService::parse(AsyncWebServerRequest * request, JsonObject & input) { } else { snprintf(error, sizeof(error), "API failed with error code (%s)", Command::return_code_string(return_code).c_str()); } - emsesp::EMSESP::logger().err(error); + EMSESP::logger().err(error); api_fails_++; + + // FAIL, OK, NOT_FOUND, ERROR, NOT_ALLOWED = 400 (bad request), 200 (OK), 400 (not found), 400 (bad request), 401 (unauthorized) + int ret_codes[6] = {400, 200, 400, 400, 401, 400}; + request->reply(ret_codes[return_code]); // exit with error code } - // if we're returning single values, just sent as plain text + api_count_++; // another successful api call + + // if we're returning single values, just sent as plain text and not json // https://github.com/emsesp/EMS-ESP32/issues/462#issuecomment-1093877210 if (output.containsKey("api_data")) { JsonVariant data = output["api_data"]; - request->send(200, "text/plain; charset=utf-8", data.as()); - api_count_++; - delete response; - return; + return request->reply(200, "text/plain; charset=utf-8", data.as()); } - // send the json that came back from the command call - // FAIL, OK, NOT_FOUND, ERROR, NOT_ALLOWED = 400 (bad request), 200 (OK), 400 (not found), 400 (bad request), 401 (unauthorized) - int ret_codes[6] = {400, 200, 400, 400, 401, 400}; - response->setCode(ret_codes[return_code]); - response->setLength(); - response->setContentType("application/json; charset=utf-8"); - request->send(response); - api_count_++; + // normal return + response.setContentType("application/json; charset=utf-8"); // TODO doesn't seem to work + // response.addHeader("Connection", "close"); -#if defined(EMSESP_STANDALONE) - Serial.print(COLOR_YELLOW); - Serial.print("web response code: "); - Serial.println(ret_codes[return_code]); - if (output.size()) { - serializeJsonPretty(output, Serial); - } - Serial.println(); - Serial.print(COLOR_RESET); -#endif + return response.send(); } -void WebAPIService::getSettings(AsyncWebServerRequest * request) { - auto * response = new AsyncJsonResponse(false, FS_BUFFER_SIZE); - JsonObject root = response->getRoot(); +esp_err_t WebAPIService::getSettings(PsychicRequest * request) { + PsychicJsonResponse response = PsychicJsonResponse(request, false, FS_BUFFER_SIZE); + JsonObject root = response.getRoot(); root["type"] = "settings"; @@ -177,44 +165,39 @@ void WebAPIService::getSettings(AsyncWebServerRequest * request) { System::extractSettings(SECURITY_SETTINGS_FILE, "Security", root); System::extractSettings(EMSESP_SETTINGS_FILE, "Settings", root); - response->setLength(); - request->send(response); + return response.send(); } -void WebAPIService::getCustomizations(AsyncWebServerRequest * request) { - auto * response = new AsyncJsonResponse(false, FS_BUFFER_SIZE); - JsonObject root = response->getRoot(); +esp_err_t WebAPIService::getCustomizations(PsychicRequest * request) { + PsychicJsonResponse response = PsychicJsonResponse(request, false, FS_BUFFER_SIZE); + JsonObject root = response.getRoot(); root["type"] = "customizations"; - System::extractSettings(EMSESP_CUSTOMIZATION_FILE, "Customizations", root); - response->setLength(); - request->send(response); + return response.send(); } -void WebAPIService::getSchedule(AsyncWebServerRequest * request) { - auto * response = new AsyncJsonResponse(false, FS_BUFFER_SIZE); - JsonObject root = response->getRoot(); +esp_err_t WebAPIService::getSchedule(PsychicRequest * request) { + PsychicJsonResponse response = PsychicJsonResponse(request, false, FS_BUFFER_SIZE); + JsonObject root = response.getRoot(); root["type"] = "schedule"; System::extractSettings(EMSESP_SCHEDULER_FILE, "Schedule", root); - response->setLength(); - request->send(response); + return response.send(); } -void WebAPIService::getEntities(AsyncWebServerRequest * request) { - auto * response = new AsyncJsonResponse(false, FS_BUFFER_SIZE); - JsonObject root = response->getRoot(); +esp_err_t WebAPIService::getEntities(PsychicRequest * request) { + PsychicJsonResponse response = PsychicJsonResponse(request, false, FS_BUFFER_SIZE); + JsonObject root = response.getRoot(); root["type"] = "entities"; System::extractSettings(EMSESP_CUSTOMENTITY_FILE, "Entities", root); - response->setLength(); - request->send(response); + return response.send(); } } // namespace emsesp diff --git a/src/web/WebAPIService.h b/src/web/WebAPIService.h index 2570317b5..de9f695f5 100644 --- a/src/web/WebAPIService.h +++ b/src/web/WebAPIService.h @@ -1,7 +1,7 @@ /* * EMS-ESP - https://github.com/emsesp/EMS-ESP * Copyright 2020-2023 Paul Derbyshire - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or @@ -19,7 +19,7 @@ #ifndef WebAPIService_h #define WebAPIService_h -#define EMSESP_API_SERVICE_PATH "/api" +#define EMSESP_API_SERVICE_PATH "/api/?*" #define GET_SETTINGS_PATH "/rest/getSettings" #define GET_CUSTOMIZATIONS_PATH "/rest/getCustomizations" #define GET_SCHEDULE_PATH "/rest/getSchedule" @@ -29,10 +29,9 @@ namespace emsesp { class WebAPIService { public: - WebAPIService(AsyncWebServer * server, SecurityManager * securityManager); + WebAPIService(PsychicHttpServer * server, SecurityManager * securityManager); - void webAPIService_post(AsyncWebServerRequest * request, JsonVariant & json); // for POSTs - void webAPIService_get(AsyncWebServerRequest * request); // for GETs + void registerURI(); static uint32_t api_count() { return api_count_; @@ -43,18 +42,18 @@ class WebAPIService { } private: - SecurityManager * _securityManager; - AsyncCallbackJsonWebHandler _apiHandler; // for POSTs + SecurityManager * _securityManager; + PsychicHttpServer * _server; static uint32_t api_count_; static uint16_t api_fails_; - void parse(AsyncWebServerRequest * request, JsonObject & input); + esp_err_t parse(PsychicRequest * request, JsonObject & input); - void getSettings(AsyncWebServerRequest * request); - void getCustomizations(AsyncWebServerRequest * request); - void getSchedule(AsyncWebServerRequest * request); - void getEntities(AsyncWebServerRequest * request); + esp_err_t getSettings(PsychicRequest * request); + esp_err_t getCustomizations(PsychicRequest * request); + esp_err_t getSchedule(PsychicRequest * request); + esp_err_t getEntities(PsychicRequest * request); }; } // namespace emsesp diff --git a/src/web/WebCustomEntityService.cpp b/src/web/WebCustomEntityService.cpp index f8b18ff80..68c6da4db 100644 --- a/src/web/WebCustomEntityService.cpp +++ b/src/web/WebCustomEntityService.cpp @@ -22,7 +22,7 @@ namespace emsesp { using namespace std::placeholders; // for `_1` etc -WebCustomEntityService::WebCustomEntityService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager) +WebCustomEntityService::WebCustomEntityService(PsychicHttpServer * server, FS * fs, SecurityManager * securityManager) : _httpEndpoint(WebCustomEntity::read, WebCustomEntity::update, this, @@ -33,6 +33,10 @@ WebCustomEntityService::WebCustomEntityService(AsyncWebServer * server, FS * fs, , _fsPersistence(WebCustomEntity::read, WebCustomEntity::update, this, fs, EMSESP_CUSTOMENTITY_FILE, FS_BUFFER_SIZE) { } +void WebCustomEntityService::registerURI() { + _httpEndpoint.registerURI(); +} + // load the settings when the service starts void WebCustomEntityService::begin() { _fsPersistence.readFromFS(); @@ -427,9 +431,9 @@ void WebCustomEntityService::publish(const bool force) { } } JsonObject dev = config.createNestedObject("dev"); - dev["name"] = Mqtt::basename(); + dev["name"] = Mqtt::basename() + " Custom"; JsonArray ids = dev.createNestedArray("ids"); - ids.add(Mqtt::basename()); + ids.add(Mqtt::basename() + "-custom"); // add "availability" section Mqtt::add_avty_to_doc(stat_t, config.as(), val_cond); @@ -469,7 +473,7 @@ uint8_t WebCustomEntityService::has_commands() { return count; } -// send to dashboard, msgpack don't like serialized, use number +// send to dashboard void WebCustomEntityService::generate_value_web(JsonObject & output) { EMSESP::webCustomEntityService.read([&](WebCustomEntity & webEntity) { customEntityItems = &webEntity.customEntityItems; }); diff --git a/src/web/WebCustomEntityService.h b/src/web/WebCustomEntityService.h index 1b18e2073..586bd369b 100644 --- a/src/web/WebCustomEntityService.h +++ b/src/web/WebCustomEntityService.h @@ -50,9 +50,11 @@ class WebCustomEntity { class WebCustomEntityService : public StatefulService { public: - WebCustomEntityService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager); + WebCustomEntityService(PsychicHttpServer * server, FS * fs, SecurityManager * securityManager); void begin(); + void registerURI(); + void publish_single(const CustomEntityItem & entity); void publish(const bool force = false); bool command_setvalue(const char * value, const std::string name); diff --git a/src/web/WebCustomizationService.cpp b/src/web/WebCustomizationService.cpp index d6a8b8e08..7f21f5c40 100644 --- a/src/web/WebCustomizationService.cpp +++ b/src/web/WebCustomizationService.cpp @@ -24,35 +24,38 @@ using namespace std::placeholders; // for `_1` etc bool WebCustomization::_start = true; -WebCustomizationService::WebCustomizationService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager) - : _httpEndpoint(WebCustomization::read, +WebCustomizationService::WebCustomizationService(PsychicHttpServer * server, FS * fs, SecurityManager * securityManager) + : _server(server) + , _securityManager(securityManager) + , _httpEndpoint(WebCustomization::read, WebCustomization::update, this, server, EMSESP_CUSTOMIZATION_SERVICE_PATH, securityManager, AuthenticationPredicates::IS_AUTHENTICATED) - , _fsPersistence(WebCustomization::read, WebCustomization::update, this, fs, EMSESP_CUSTOMIZATION_FILE) - , _masked_entities_handler(CUSTOMIZATION_ENTITIES_PATH, - securityManager->wrapCallback(std::bind(&WebCustomizationService::customization_entities, this, _1, _2), - AuthenticationPredicates::IS_AUTHENTICATED)) { - server->on(DEVICE_ENTITIES_PATH, - HTTP_GET, - securityManager->wrapRequest(std::bind(&WebCustomizationService::device_entities, this, _1), AuthenticationPredicates::IS_AUTHENTICATED)); + , _fsPersistence(WebCustomization::read, WebCustomization::update, this, fs, EMSESP_CUSTOMIZATION_FILE) { +} +void WebCustomizationService::registerURI() { + _httpEndpoint.registerURI(); - server->on(DEVICES_SERVICE_PATH, - HTTP_GET, - securityManager->wrapRequest(std::bind(&WebCustomizationService::devices, this, _1), AuthenticationPredicates::IS_AUTHENTICATED)); + _server->on(CUSTOMIZATION_ENTITIES_PATH, + HTTP_POST, + _securityManager->wrapCallback(std::bind(&WebCustomizationService::customization_entities, this, _1, _2), + AuthenticationPredicates::IS_AUTHENTICATED)); - server->on(RESET_CUSTOMIZATION_SERVICE_PATH, - HTTP_POST, - securityManager->wrapRequest(std::bind(&WebCustomizationService::reset_customization, this, _1), AuthenticationPredicates::IS_ADMIN)); + _server->on(DEVICE_ENTITIES_PATH, + HTTP_GET, + _securityManager->wrapRequest(std::bind(&WebCustomizationService::device_entities, this, _1), AuthenticationPredicates::IS_AUTHENTICATED)); - _masked_entities_handler.setMethod(HTTP_POST); - _masked_entities_handler.setMaxContentLength(2048); - _masked_entities_handler.setMaxJsonBufferSize(2048); - server->addHandler(&_masked_entities_handler); + _server->on(DEVICES_SERVICE_PATH, + HTTP_GET, + _securityManager->wrapRequest(std::bind(&WebCustomizationService::devices, this, _1), AuthenticationPredicates::IS_AUTHENTICATED)); + + _server->on(RESET_CUSTOMIZATION_SERVICE_PATH, + HTTP_POST, + _securityManager->wrapRequest(std::bind(&WebCustomizationService::reset_customization, this, _1), AuthenticationPredicates::IS_ADMIN)); } // this creates the customization file, saving it to the FS @@ -163,24 +166,22 @@ StateUpdateResult WebCustomization::update(JsonObject & root, WebCustomization & } // deletes the customization file -void WebCustomizationService::reset_customization(AsyncWebServerRequest * request) { +esp_err_t WebCustomizationService::reset_customization(PsychicRequest * request) { #ifndef EMSESP_STANDALONE if (LittleFS.remove(EMSESP_CUSTOMIZATION_FILE)) { - AsyncWebServerResponse * response = request->beginResponse(205); // restart needed - request->send(response); EMSESP::system_.restart_requested(true); - return; + return request->reply(205); // restart needed } + // failed - AsyncWebServerResponse * response = request->beginResponse(400); // bad request - request->send(response); + return request->reply(400); // bad request #endif } // send back a list of devices used in the customization web page -void WebCustomizationService::devices(AsyncWebServerRequest * request) { - auto * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_XLARGE); - JsonObject root = response->getRoot(); +esp_err_t WebCustomizationService::devices(PsychicRequest * request) { + PsychicJsonResponse response = PsychicJsonResponse(request, false, EMSESP_JSON_SIZE_XLARGE); + JsonObject root = response.getRoot(); // list is already sorted by device type // controller is ignored since it doesn't have any associated entities @@ -195,51 +196,45 @@ void WebCustomizationService::devices(AsyncWebServerRequest * request) { } } - response->setLength(); - request->send(response); + return response.send(); } // send back list of device entities -void WebCustomizationService::device_entities(AsyncWebServerRequest * request) { +esp_err_t WebCustomizationService::device_entities(PsychicRequest * request) { uint8_t id; if (request->hasParam(F_(id))) { id = Helpers::atoint(request->getParam(F_(id))->value().c_str()); // get id from url - size_t buffer = EMSESP_JSON_SIZE_XXXXLARGE; - auto * response = new MsgpackAsyncJsonResponse(true, buffer); + PsychicJsonResponse response = PsychicJsonResponse(request, true, EMSESP_JSON_SIZE_XXXXLARGE, true); // is array and also msgpack + JsonArray output = response.getRoot(); + + // TODO add back memory managegement. Be careful we do need to free()/delete() any object we extend with new() + // while (!response) { + // delete response; + // buffer -= 1024; + // // response = new MsgpackAsyncJsonResponse(true, buffer); + // } - while (!response) { - delete response; - buffer -= 1024; - response = new MsgpackAsyncJsonResponse(true, buffer); - } for (const auto & emsdevice : EMSESP::emsdevices) { if (emsdevice->unique_id() == id) { #ifndef EMSESP_STANDALONE - JsonArray output = response->getRoot(); emsdevice->generate_values_web_customization(output); #endif -#if defined(EMSESP_DEBUG) - size_t length = response->setLength(); - EMSESP::logger().debug("Customization buffer used: %d", length); -#else - response->setLength(); -#endif - request->send(response); - return; + // request->send(response); + // return; + return response.send(); } } } // invalid, but send OK anyway - AsyncWebServerResponse * response = request->beginResponse(200); - request->send(response); + return request->reply(200); // OK } // takes a list of updated entities with new masks from the web UI // saves it in the customization service // and updates the entity list real-time -void WebCustomizationService::customization_entities(AsyncWebServerRequest * request, JsonVariant & json) { +esp_err_t WebCustomizationService::customization_entities(PsychicRequest * request, JsonVariant & json) { bool need_reboot = false; if (json.is()) { // find the device using the unique_id @@ -261,7 +256,7 @@ void WebCustomizationService::customization_entities(AsyncWebServerRequest * req } else { emsdevice->setCustomizationEntity(id_s); } - // emsesp::EMSESP::logger().info(id.as()); + // EMSESP::logger().info(id.as()); } // add deleted entities from file read([&](WebCustomization & settings) { @@ -324,8 +319,7 @@ void WebCustomizationService::customization_entities(AsyncWebServerRequest * req } } - AsyncWebServerResponse * response = request->beginResponse(need_reboot ? 205 : 200); // reboot or just OK - request->send(response); + return request->reply(need_reboot ? 205 : 200); // reboot or just OK } // load the settings when the service starts diff --git a/src/web/WebCustomizationService.h b/src/web/WebCustomizationService.h index 5044c0005..61d91bf3c 100644 --- a/src/web/WebCustomizationService.h +++ b/src/web/WebCustomizationService.h @@ -80,9 +80,10 @@ class WebCustomization { class WebCustomizationService : public StatefulService { public: - WebCustomizationService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager); + WebCustomizationService(PsychicHttpServer * server, FS * fs, SecurityManager * securityManager); void begin(); + void registerURI(); // make all functions public so we can test in the debug and standalone mode #ifndef EMSESP_STANDALONE @@ -93,14 +94,16 @@ class WebCustomizationService : public StatefulService { FSPersistence _fsPersistence; // GET - void devices(AsyncWebServerRequest * request); - void device_entities(AsyncWebServerRequest * request); + esp_err_t devices(PsychicRequest * request); + esp_err_t device_entities(PsychicRequest * request); // POST - void customization_entities(AsyncWebServerRequest * request, JsonVariant & json); - void reset_customization(AsyncWebServerRequest * request); // command + esp_err_t customization_entities(PsychicRequest * request, JsonVariant & json); + esp_err_t reset_customization(PsychicRequest * request); // is a command - AsyncCallbackJsonWebHandler _masked_entities_handler; + private: + SecurityManager * _securityManager; + PsychicHttpServer * _server; }; } // namespace emsesp diff --git a/src/web/WebDataService.cpp b/src/web/WebDataService.cpp index aaa223b76..e51cdf1db 100644 --- a/src/web/WebDataService.cpp +++ b/src/web/WebDataService.cpp @@ -22,57 +22,56 @@ namespace emsesp { using namespace std::placeholders; // for `_1` etc -WebDataService::WebDataService(AsyncWebServer * server, SecurityManager * securityManager) - : _write_value_handler(WRITE_DEVICE_VALUE_SERVICE_PATH, - securityManager->wrapCallback(std::bind(&WebDataService::write_device_value, this, _1, _2), AuthenticationPredicates::IS_ADMIN)) - , _write_temperature_handler(WRITE_TEMPERATURE_SENSOR_SERVICE_PATH, - securityManager->wrapCallback(std::bind(&WebDataService::write_temperature_sensor, this, _1, _2), - AuthenticationPredicates::IS_ADMIN)) - , _write_analog_handler(WRITE_ANALOG_SENSOR_SERVICE_PATH, - securityManager->wrapCallback(std::bind(&WebDataService::write_analog_sensor, this, _1, _2), AuthenticationPredicates::IS_ADMIN)) { - // GET's - server->on(DEVICE_DATA_SERVICE_PATH, - HTTP_GET, - securityManager->wrapRequest(std::bind(&WebDataService::device_data, this, _1), AuthenticationPredicates::IS_AUTHENTICATED)); - - server->on(CORE_DATA_SERVICE_PATH, - HTTP_GET, - securityManager->wrapRequest(std::bind(&WebDataService::core_data, this, _1), AuthenticationPredicates::IS_AUTHENTICATED)); - - server->on(SENSOR_DATA_SERVICE_PATH, - HTTP_GET, - securityManager->wrapRequest(std::bind(&WebDataService::sensor_data, this, _1), AuthenticationPredicates::IS_AUTHENTICATED)); - - // POST's - server->on(SCAN_DEVICES_SERVICE_PATH, - HTTP_POST, - securityManager->wrapRequest(std::bind(&WebDataService::scan_devices, this, _1), AuthenticationPredicates::IS_ADMIN)); - - _write_value_handler.setMethod(HTTP_POST); - _write_value_handler.setMaxContentLength(256); - server->addHandler(&_write_value_handler); - - _write_temperature_handler.setMethod(HTTP_POST); - _write_temperature_handler.setMaxContentLength(256); - server->addHandler(&_write_temperature_handler); - - _write_analog_handler.setMethod(HTTP_POST); - _write_analog_handler.setMaxContentLength(256); - server->addHandler(&_write_analog_handler); +WebDataService::WebDataService(PsychicHttpServer * server, SecurityManager * securityManager) + : _server(server) + , _securityManager(securityManager) { } +void WebDataService::registerURI() { + // GET's + _server->on(CORE_DATA_SERVICE_PATH, + HTTP_GET, + _securityManager->wrapRequest(std::bind(&WebDataService::core_data, this, _1), AuthenticationPredicates::IS_AUTHENTICATED)); + + _server->on(DEVICE_DATA_SERVICE_PATH, + HTTP_GET, + _securityManager->wrapRequest(std::bind(&WebDataService::device_data, this, _1), AuthenticationPredicates::IS_AUTHENTICATED)); + + _server->on(SENSOR_DATA_SERVICE_PATH, + HTTP_GET, + _securityManager->wrapRequest(std::bind(&WebDataService::sensor_data, this, _1), AuthenticationPredicates::IS_AUTHENTICATED)); + + // POST's + _server->on(SCAN_DEVICES_SERVICE_PATH, + HTTP_POST, + _securityManager->wrapRequest(std::bind(&WebDataService::scan_devices, this, _1), AuthenticationPredicates::IS_ADMIN)); + + _server->on(WRITE_DEVICE_VALUE_SERVICE_PATH, + HTTP_POST, + _securityManager->wrapCallback(std::bind(&WebDataService::write_device_value, this, _1, _2), AuthenticationPredicates::IS_AUTHENTICATED)); + + _server->on(WRITE_TEMPERATURE_SENSOR_SERVICE_PATH, + HTTP_POST, + _securityManager->wrapCallback(std::bind(&WebDataService::write_temperature_sensor, this, _1, _2), AuthenticationPredicates::IS_AUTHENTICATED)); + + _server->on(WRITE_ANALOG_SENSOR_SERVICE_PATH, + HTTP_POST, + _securityManager->wrapCallback(std::bind(&WebDataService::write_analog_sensor, this, _1, _2), AuthenticationPredicates::IS_AUTHENTICATED)); +} + + // scan devices service -void WebDataService::scan_devices(AsyncWebServerRequest * request) { +esp_err_t WebDataService::scan_devices(PsychicRequest * request) { EMSESP::logger().info("Scanning devices..."); EMSESP::scan_devices(); - request->send(200); + return request->reply(200); } // this is used in the dashboard and contains all ems device information // /coreData endpoint -void WebDataService::core_data(AsyncWebServerRequest * request) { - auto * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_XXLARGE); - JsonObject root = response->getRoot(); +esp_err_t WebDataService::core_data(PsychicRequest * request) { + PsychicJsonResponse response = PsychicJsonResponse(request, false, EMSESP_JSON_SIZE_XXLARGE); + JsonObject root = response.getRoot(); // list is already sorted by device type JsonArray devices = root.createNestedArray("devices"); @@ -109,15 +108,14 @@ void WebDataService::core_data(AsyncWebServerRequest * request) { root["connected"] = EMSESP::bus_status() != 2; - response->setLength(); - request->send(response); + return response.send(); } // sensor data - sends back to web // /sensorData endpoint -void WebDataService::sensor_data(AsyncWebServerRequest * request) { - auto * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_XXLARGE); - JsonObject root = response->getRoot(); +esp_err_t WebDataService::sensor_data(PsychicRequest * request) { + PsychicJsonResponse response = PsychicJsonResponse(request, false, EMSESP_JSON_SIZE_XXLARGE); + JsonObject root = response.getRoot(); // temperature sensors JsonArray sensors = root.createNestedArray("ts"); @@ -167,68 +165,55 @@ void WebDataService::sensor_data(AsyncWebServerRequest * request) { root["analog_enabled"] = EMSESP::analog_enabled(); root["platform"] = EMSESP_PLATFORM; - response->setLength(); - request->send(response); + return response.send(); } // The unique_id is the unique record ID from the Web table to identify which device to load // Compresses the JSON using MsgPack https://msgpack.org/index.html -void WebDataService::device_data(AsyncWebServerRequest * request) { +esp_err_t WebDataService::device_data(PsychicRequest * request) { uint8_t id; if (request->hasParam(F_(id))) { id = Helpers::atoint(request->getParam(F_(id))->value().c_str()); // get id from url - size_t buffer = EMSESP_JSON_SIZE_XXXXLARGE; - auto * response = new MsgpackAsyncJsonResponse(false, buffer); + PsychicJsonResponse response = PsychicJsonResponse(request, false, EMSESP_JSON_SIZE_XXXXLARGE, true); // is jsonobject and also msgpack + JsonObject output = response.getRoot(); + // TODO add back memory managegement. Be careful we do need to free()/delete() any object we extend with new() // check size - while (!response) { - delete response; - buffer -= 1024; - response = new MsgpackAsyncJsonResponse(false, buffer); - } + // while (!response) { + // delete response; + // buffer -= 1024; + // response = new MsgpackAsyncJsonResponse(false, buffer); + // } for (const auto & emsdevice : EMSESP::emsdevices) { if (emsdevice->unique_id() == id) { // wait max 2.5 sec for updated data (post_send_delay is 2 sec) - for (uint16_t i = 0; i < (emsesp::TxService::POST_SEND_DELAY + 500) && EMSESP::wait_validate(); i++) { + for (uint16_t i = 0; i < (TxService::POST_SEND_DELAY + 500) && EMSESP::wait_validate(); i++) { delay(1); } EMSESP::wait_validate(0); // reset in case of timeout #ifndef EMSESP_STANDALONE - JsonObject output = response->getRoot(); emsdevice->generate_values_web(output); #endif -#if defined(EMSESP_DEBUG) - size_t length = response->setLength(); - EMSESP::logger().debug("Dashboard buffer used: %d", length); -#else - response->setLength(); -#endif - request->send(response); - return; + return response.send(); } } #ifndef EMSESP_STANDALONE if (id == 99) { - JsonObject output = response->getRoot(); EMSESP::webCustomEntityService.generate_value_web(output); - response->setLength(); - request->send(response); - return; + return response.send(); } #endif } - // invalid - AsyncWebServerResponse * response = request->beginResponse(400); - request->send(response); + return request->reply(400); // Invalid/bad request } // assumes the service has been checked for admin authentication -void WebDataService::write_device_value(AsyncWebServerRequest * request, JsonVariant & json) { +esp_err_t WebDataService::write_device_value(PsychicRequest * request, JsonVariant & json) { if (json.is()) { uint8_t unique_id = json["id"]; // unique ID const char * cmd = json["c"]; // the command @@ -236,9 +221,7 @@ void WebDataService::write_device_value(AsyncWebServerRequest * request, JsonVar // quit on bad values if (strlen(cmd) == 0 || data.isNull()) { - AsyncWebServerResponse * response = request->beginResponse(400); // bad request - request->send(response); - return; + return request->reply(400); // Invalid/bad request } // using the unique ID from the web find the real device type @@ -249,8 +232,8 @@ void WebDataService::write_device_value(AsyncWebServerRequest * request, JsonVar cmd = Command::parse_command_string(cmd, id); // extract hc or wwc // create JSON for output - auto * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_SMALL); - JsonObject output = response->getRoot(); + PsychicJsonResponse response = PsychicJsonResponse(request, false, EMSESP_JSON_SIZE_SMALL); + JsonObject output = response.getRoot(); // the data could be in any format, but we need string // authenticated is always true @@ -277,22 +260,22 @@ void WebDataService::write_device_value(AsyncWebServerRequest * request, JsonVar #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 + return response.send(); } } // special check for custom entities (which have a unique id of 99) if (unique_id == 99) { // parse the command as it could have a hc or wwc prefixed, e.g. hc2/seltemp - int8_t id = -1; - cmd = Command::parse_command_string(cmd, id); - auto * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_SMALL); - JsonObject output = response->getRoot(); - uint8_t return_code = CommandRet::NOT_FOUND; - uint8_t device_type = EMSdevice::DeviceType::CUSTOM; + int8_t id = -1; + cmd = Command::parse_command_string(cmd, id); + + PsychicJsonResponse response = PsychicJsonResponse(request, false, EMSESP_JSON_SIZE_SMALL); + JsonObject output = response.getRoot(); + + uint8_t return_code = CommandRet::NOT_FOUND; + uint8_t device_type = EMSdevice::DeviceType::CUSTOM; if (data.is()) { return_code = Command::call(device_type, cmd, data.as(), true, id, output); } else if (data.is()) { @@ -310,21 +293,18 @@ void WebDataService::write_device_value(AsyncWebServerRequest * request, JsonVar #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 + return response.send(); } } // if we reach here, fail - AsyncWebServerResponse * response = request->beginResponse(400); // bad request - request->send(response); + return request->reply(400); // Invalid/Bad Request } // takes a temperaturesensor name and optional offset from the WebUI and update the customization settings // via the temperaturesensor service -void WebDataService::write_temperature_sensor(AsyncWebServerRequest * request, JsonVariant & json) { +esp_err_t WebDataService::write_temperature_sensor(PsychicRequest * request, JsonVariant & json) { bool ok = false; if (json.is()) { JsonObject sensor = json; @@ -342,12 +322,11 @@ void WebDataService::write_temperature_sensor(AsyncWebServerRequest * request, J ok = EMSESP::temperaturesensor_.update(id, name, offset10); } - AsyncWebServerResponse * response = request->beginResponse(ok ? 200 : 400); // bad request - request->send(response); + return request->reply(ok ? 200 : 400); // bad request); // Invalid/Bad Request } // update the analog record, or create a new one -void WebDataService::write_analog_sensor(AsyncWebServerRequest * request, JsonVariant & json) { +esp_err_t WebDataService::write_analog_sensor(PsychicRequest * request, JsonVariant & json) { bool ok = false; if (json.is()) { JsonObject analog = json; @@ -362,8 +341,7 @@ void WebDataService::write_analog_sensor(AsyncWebServerRequest * request, JsonVa ok = EMSESP::analogsensor_.update(gpio, name, offset, factor, uom, type, deleted); } - AsyncWebServerResponse * response = request->beginResponse(ok ? 200 : 400); // bad request - request->send(response); + return request->reply(ok ? 200 : 400); // bad request); // Invalid/Bad Request } } // namespace emsesp \ No newline at end of file diff --git a/src/web/WebDataService.h b/src/web/WebDataService.h index d85d20988..2fe1d9c99 100644 --- a/src/web/WebDataService.h +++ b/src/web/WebDataService.h @@ -34,25 +34,27 @@ namespace emsesp { class WebDataService { public: - WebDataService(AsyncWebServer * server, SecurityManager * securityManager); + WebDataService(PsychicHttpServer * server, SecurityManager * securityManager); + void registerURI(); // make all functions public so we can test in the debug and standalone mode #ifndef EMSESP_STANDALONE private: #endif + SecurityManager * _securityManager; + PsychicHttpServer * _server; + // GET - void core_data(AsyncWebServerRequest * request); - void sensor_data(AsyncWebServerRequest * request); - void device_data(AsyncWebServerRequest * request); + esp_err_t core_data(PsychicRequest * request); + esp_err_t sensor_data(PsychicRequest * request); + esp_err_t device_data(PsychicRequest * request); // POST - void write_device_value(AsyncWebServerRequest * request, JsonVariant & json); - void write_temperature_sensor(AsyncWebServerRequest * request, JsonVariant & json); - void write_analog_sensor(AsyncWebServerRequest * request, JsonVariant & json); - void scan_devices(AsyncWebServerRequest * request); // command - - AsyncCallbackJsonWebHandler _write_value_handler, _write_temperature_handler, _write_analog_handler; + esp_err_t write_device_value(PsychicRequest * request, JsonVariant & json); + esp_err_t write_temperature_sensor(PsychicRequest * request, JsonVariant & json); + esp_err_t write_analog_sensor(PsychicRequest * request, JsonVariant & json); + esp_err_t scan_devices(PsychicRequest * request); // command }; } // namespace emsesp diff --git a/src/web/WebLogService.cpp b/src/web/WebLogService.cpp index d3fe63729..291eefa40 100644 --- a/src/web/WebLogService.cpp +++ b/src/web/WebLogService.cpp @@ -22,18 +22,66 @@ using namespace std::placeholders; namespace emsesp { -WebLogService::WebLogService(AsyncWebServer * server, SecurityManager * securityManager) - : events_(EVENT_SOURCE_LOG_PATH) - , setValues_(LOG_SETTINGS_PATH, std::bind(&WebLogService::setValues, this, _1, _2), 256) { - events_.setFilter(securityManager->filterRequest(AuthenticationPredicates::IS_ADMIN)); +WebLogService::WebLogService(PsychicHttpServer * server, SecurityManager * securityManager) + // TODO fix event source + : // : _events(EVENT_SOURCE_LOG_PATH) + _server(server) + , _securityManager(securityManager) { +} - server->on(LOG_SETTINGS_PATH, HTTP_GET, std::bind(&WebLogService::getValues, this, _1)); // get settings + +void WebLogService::registerURI() { + // TODO fix event source + /* + _events_.onOpen([](PsychicEventSourceClient * client) { + Serial.printf("[eventsource] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString()); + client->send("Hello user!", NULL, millis(), 1000); + }); + _events_.onClose([](PsychicEventSourceClient * client) { + Serial.printf("[eventsource] connection #%u closed from %s\n", client->socket(), client->remoteIP().toString()); + }); + _server->on(EVENT_SOURCE_LOG_PATH, &events_); + */ + + // post + _server->on(LOG_SETTINGS_PATH, HTTP_POST, [this](PsychicRequest * request, JsonVariant & json) { + auto && body = json.as(); + + uuid::log::Level level = body["level"]; + log_level(level); + + uint8_t max_messages = body["max_messages"]; + maximum_log_messages(max_messages); + + bool comp = body["compact"]; + compact(comp); + + return request->reply(200); // OK + }); + + _events.setFilter(_securityManager->filterRequest(AuthenticationPredicates::IS_ADMIN)); + + // get settings + _server->on(LOG_SETTINGS_PATH, HTTP_GET, [this](PsychicRequest * request) { + PsychicJsonResponse response = PsychicJsonResponse(request, false, EMSESP_JSON_SIZE_SMALL); + JsonObject root = response.getRoot(); + + root["level"] = log_level(); + root["max_messages"] = maximum_log_messages(); + root["compact"] = compact(); + return response.send(); + }); // for bring back the whole log - is a command, hence a POST - server->on(FETCH_LOG_PATH, HTTP_POST, std::bind(&WebLogService::fetchLog, this, _1)); + // send the complete log buffer to the API, not filtering on log level + // done by resetting the pointer + _server->on(FETCH_LOG_PATH, HTTP_POST, [this](PsychicRequest * request) { + log_message_id_tail_ = 0; + return request->reply(200); + }); - server->addHandler(&setValues_); - server->addHandler(&events_); + // TODO this can be removed when ported over + // server->addHandler(&events_); } // start the log service with INFO level @@ -143,7 +191,7 @@ void WebLogService::operator<<(std::shared_ptr message) { } void WebLogService::loop() { - if (!events_.count() || log_messages_.empty()) { + if (!_events.count() || log_messages_.empty()) { return; } @@ -200,47 +248,43 @@ void WebLogService::transmit(const QueuedLogMessage & message) { char * buffer = new char[len + 1]; if (buffer) { serializeJson(jsonDocument, buffer, len + 1); - events_.send(buffer, "message", message.id_); + _events.send(buffer, "message", message.id_); } delete[] buffer; } // send the complete log buffer to the API, not filtering on log level // done by resetting the pointer -void WebLogService::fetchLog(AsyncWebServerRequest * request) { - log_message_id_tail_ = 0; - request->send(200); -} - -// sets the values like level after a POST -void WebLogService::setValues(AsyncWebServerRequest * request, JsonVariant & json) { - if (!json.is()) { - return; - } - - auto && body = json.as(); - - uuid::log::Level level = body["level"]; - log_level(level); - - uint8_t max_messages = body["max_messages"]; - maximum_log_messages(max_messages); - - bool comp = body["compact"]; - compact(comp); - - request->send(200); // OK -} +// esp_err_t WebLogService::fetchLog(PsychicRequest * request) { +// log_message_id_tail_ = 0; +// request->send(200); +// } // return the current value settings after a GET -void WebLogService::getValues(AsyncWebServerRequest * request) { - auto * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_SMALL); - JsonObject root = response->getRoot(); - root["level"] = log_level(); - root["max_messages"] = maximum_log_messages(); - root["compact"] = compact(); - response->setLength(); - request->send(response); -} +// esp_err_t WebLogService::getValues(PsychicRequest * request) { +// auto * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_SMALL); +// JsonObject root = response->getRoot(); +// root["level"] = log_level(); +// root["max_messages"] = maximum_log_messages(); +// root["compact"] = compact(); +// response->setLength(); +// request->send(response); +// } + +// sets the values like level after a POST +// esp_err_t WebLogService::setValues(PsychicRequest * request, JsonVariant & json) { +// auto && body = json.as(); + +// uuid::log::Level level = body["level"]; +// log_level(level); + +// uint8_t max_messages = body["max_messages"]; +// maximum_log_messages(max_messages); + +// bool comp = body["compact"]; +// compact(comp); + +// return request->reply(200); // OK +// } } // namespace emsesp \ No newline at end of file diff --git a/src/web/WebLogService.h b/src/web/WebLogService.h index 0f06bfff2..66913425e 100644 --- a/src/web/WebLogService.h +++ b/src/web/WebLogService.h @@ -1,7 +1,7 @@ /* * EMS-ESP - https://github.com/emsesp/EMS-ESP * Copyright 2020-2023 Paul Derbyshire - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or @@ -30,10 +30,13 @@ class WebLogService : public uuid::log::Handler { static constexpr size_t MAX_LOG_MESSAGES = 50; static constexpr size_t REFRESH_SYNC = 50; - WebLogService(AsyncWebServer * server, SecurityManager * securityManager); + WebLogService(PsychicHttpServer * server, SecurityManager * securityManager); + + void begin(); + void start(); + void loop(); + void registerURI(); - void begin(); - void start(); uuid::log::Level log_level() const; void log_level(uuid::log::Level level); size_t maximum_log_messages() const; @@ -41,12 +44,14 @@ class WebLogService : public uuid::log::Handler { void maximum_log_messages(size_t count); bool compact() const; void compact(bool compact); - void loop(); virtual void operator<<(std::shared_ptr message); private: - AsyncEventSource events_; + SecurityManager * _securityManager; + PsychicHttpServer * _server; + + PsychicEventSource _events; class QueuedLogMessage { public: @@ -58,15 +63,12 @@ class WebLogService : public uuid::log::Handler { const std::shared_ptr content_; // Log message content }; - void transmit(const QueuedLogMessage & message); - void fetchLog(AsyncWebServerRequest * request); - void getValues(AsyncWebServerRequest * request); - + void transmit(const QueuedLogMessage & message); char * messagetime(char * out, const uint64_t t, const size_t bufsize); - void setValues(AsyncWebServerRequest * request, JsonVariant & json); - - AsyncCallbackJsonWebHandler setValues_; // for POSTs + esp_err_t fetchLog(PsychicRequest * request); + esp_err_t getValues(PsychicRequest * request); + esp_err_t setValues(PsychicRequest * request, JsonVariant & json); uint64_t last_transmit_ = 0; // Last transmit time size_t maximum_log_messages_ = MAX_LOG_MESSAGES; // Maximum number of log messages to buffer before they are output diff --git a/src/web/WebSchedulerService.cpp b/src/web/WebSchedulerService.cpp index 91321c996..c6989396d 100644 --- a/src/web/WebSchedulerService.cpp +++ b/src/web/WebSchedulerService.cpp @@ -22,11 +22,15 @@ namespace emsesp { using namespace std::placeholders; // for `_1` etc -WebSchedulerService::WebSchedulerService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager) +WebSchedulerService::WebSchedulerService(PsychicHttpServer * server, FS * fs, SecurityManager * securityManager) : _httpEndpoint(WebScheduler::read, WebScheduler::update, this, server, EMSESP_SCHEDULER_SERVICE_PATH, securityManager, AuthenticationPredicates::IS_AUTHENTICATED) , _fsPersistence(WebScheduler::read, WebScheduler::update, this, fs, EMSESP_SCHEDULER_FILE) { } +void WebSchedulerService::registerURI() { + _httpEndpoint.registerURI(); +} + // load the settings when the service starts void WebSchedulerService::begin() { _fsPersistence.readFromFS(); @@ -288,9 +292,9 @@ void WebSchedulerService::publish(const bool force) { } JsonObject dev = config.createNestedObject("dev"); - dev["name"] = Mqtt::basename(); + dev["name"] = Mqtt::basename() + " Scheduler"; JsonArray ids = dev.createNestedArray("ids"); - ids.add(Mqtt::basename()); + ids.add(Mqtt::basename() + "-scheduler"); // add "availability" section Mqtt::add_avty_to_doc(stat_t, config.as(), val_cond); @@ -352,7 +356,7 @@ bool WebSchedulerService::command(const char * cmd, const char * data) { } else { snprintf(error, sizeof(error), "Scheduled command %s failed with error code (%s)", cmd, Command::return_code_string(return_code).c_str()); } - emsesp::EMSESP::logger().err(error); + EMSESP::logger().err(error); return false; } diff --git a/src/web/WebSchedulerService.h b/src/web/WebSchedulerService.h index ef012fc5a..addabf32f 100644 --- a/src/web/WebSchedulerService.h +++ b/src/web/WebSchedulerService.h @@ -1,7 +1,7 @@ /* * EMS-ESP - https://github.com/emsesp/EMS-ESP * Copyright 2020-2023 Paul Derbyshire - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or @@ -49,10 +49,12 @@ class WebScheduler { class WebSchedulerService : public StatefulService { public: - WebSchedulerService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager); + WebSchedulerService(PsychicHttpServer * server, FS * fs, SecurityManager * securityManager); void begin(); void loop(); + void registerURI(); + void publish_single(const char * name, const bool state); void publish(const bool force = false); bool has_commands(); diff --git a/src/web/WebSettingsService.cpp b/src/web/WebSettingsService.cpp index 793f05f8f..cb68c66c8 100644 --- a/src/web/WebSettingsService.cpp +++ b/src/web/WebSettingsService.cpp @@ -1,7 +1,7 @@ /* * EMS-ESP - https://github.com/emsesp/EMS-ESP * Copyright 2020-2023 Paul Derbyshire - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or @@ -24,17 +24,21 @@ uint8_t WebSettings::flags_ = 0; using namespace std::placeholders; // for `_1` etc -WebSettingsService::WebSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager) - : _httpEndpoint(WebSettings::read, WebSettings::update, this, server, EMSESP_SETTINGS_SERVICE_PATH, securityManager) +WebSettingsService::WebSettingsService(PsychicHttpServer * server, FS * fs, SecurityManager * securityManager) + : _server(server) + , _securityManager(securityManager) + , _httpEndpoint(WebSettings::read, WebSettings::update, this, server, EMSESP_SETTINGS_SERVICE_PATH, securityManager) , _fsPersistence(WebSettings::read, WebSettings::update, this, fs, EMSESP_SETTINGS_FILE) { - // GET - server->on(EMSESP_BOARD_PROFILE_SERVICE_PATH, - HTTP_GET, - securityManager->wrapRequest(std::bind(&WebSettingsService::board_profile, this, _1), AuthenticationPredicates::IS_ADMIN)); - addUpdateHandler([&](const String & originId) { onUpdate(); }, false); } +void WebSettingsService::registerURI() { + _httpEndpoint.registerURI(); + _server->on(EMSESP_BOARD_PROFILE_SERVICE_PATH, + HTTP_GET, + _securityManager->wrapRequest(std::bind(&WebSettingsService::board_profile, this, _1), AuthenticationPredicates::IS_ADMIN)); +} + void WebSettings::read(WebSettings & settings, JsonObject & root) { root["version"] = settings.version; root["locale"] = settings.locale; @@ -91,6 +95,7 @@ StateUpdateResult WebSettings::update(JsonObject & root, WebSettings & settings) if ((String)EMSESP_DEFAULT_BOARD_PROFILE != "default" && EMSESP::nvs_.getString("boot") == "") { EMSESP::nvs_.putString("boot", (const char *)EMSESP_DEFAULT_BOARD_PROFILE); } + /* #if CONFIG_IDF_TARGET_ESP32C3 settings.board_profile = root["board_profile"] | "C3MINI"; @@ -103,19 +108,28 @@ StateUpdateResult WebSettings::update(JsonObject & root, WebSettings & settings) settings.board_profile = root["board_profile"] | EMSESP_DEFAULT_BOARD_PROFILE; #endif */ + if (!System::load_board_profile(data, settings.board_profile.c_str())) { // unknown, check for NVS or scan for ethernet, use default E32/E32V2/S32 settings.board_profile = EMSESP::nvs_.getString("boot"); if (!System::load_board_profile(data, settings.board_profile.c_str())) { #if CONFIG_IDF_TARGET_ESP32 && !defined(EMSESP_STANDALONE) if (settings.board_profile == "") { // empty: new test - if (ETH.begin((eth_phy_type_t)1, 16, 23, 18, ETH_PHY_LAN8720, ETH_CLOCK_GPIO0_IN)) { +#if ESP_ARDUINO_VERSION_MAJOR < 3 + if (ETH.begin(1, 16, 23, 18, ETH_PHY_LAN8720, ETH_CLOCK_GPIO0_IN)) { +#else + if (ETH.begin(ETH_PHY_LAN8720, 1, 23, 18, 16, ETH_CLOCK_GPIO0_IN)) { +#endif EMSESP::nvs_.putString("boot", "E32"); } else { EMSESP::nvs_.putString("boot", "Test"); } } else if (settings.board_profile == "Test") { - if (ETH.begin((eth_phy_type_t)0, 15, 23, 18, ETH_PHY_LAN8720, ETH_CLOCK_GPIO0_OUT)) { +#if ESP_ARDUINO_VERSION_MAJOR < 3 + if (ETH.begin(0, 15, 23, 18, ETH_PHY_LAN8720, ETH_CLOCK_GPIO0_OUT)) { +#else + if (ETH.begin(ETH_PHY_LAN8720, 0, 23, 18, 15, ETH_CLOCK_GPIO0_OUT)) { +#endif EMSESP::nvs_.putString("boot", "E32V2"); } else { EMSESP::nvs_.putString("boot", "S32"); @@ -336,7 +350,7 @@ void WebSettingsService::onUpdate() { } if (WebSettings::has_flags(WebSettings::ChangeFlags::MQTT)) { - emsesp::Mqtt::reset_mqtt(); // reload MQTT, init HA etc + Mqtt::reset_mqtt(); // reload MQTT, init HA etc } WebSettings::reset_flags(); @@ -352,12 +366,12 @@ void WebSettingsService::save() { } // build the json profile to send back -void WebSettingsService::board_profile(AsyncWebServerRequest * request) { +esp_err_t WebSettingsService::board_profile(PsychicRequest * request) { if (request->hasParam("boardProfile")) { std::string board_profile = request->getParam("boardProfile")->value().c_str(); - auto * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_MEDIUM); - JsonObject root = response->getRoot(); + PsychicJsonResponse response = PsychicJsonResponse(request, false, EMSESP_JSON_SIZE_MEDIUM); + JsonObject root = response.getRoot(); std::vector data; // led, dallas, rx, tx, button, phy_type, eth_power, eth_phy_addr, eth_clock_mode (void)System::load_board_profile(data, board_profile); @@ -372,13 +386,10 @@ void WebSettingsService::board_profile(AsyncWebServerRequest * request) { root["eth_phy_addr"] = data[7]; root["eth_clock_mode"] = data[8]; - response->setLength(); - request->send(response); - return; + return response.send(); } - AsyncWebServerResponse * response = request->beginResponse(200); - request->send(response); + return request->reply(200); } } // namespace emsesp diff --git a/src/web/WebSettingsService.h b/src/web/WebSettingsService.h index c887cdf09..1331336af 100644 --- a/src/web/WebSettingsService.h +++ b/src/web/WebSettingsService.h @@ -117,16 +117,20 @@ class WebSettings { class WebSettingsService : public StatefulService { public: - WebSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager); + WebSettingsService(PsychicHttpServer * server, FS * fs, SecurityManager * securityManager); void begin(); void save(); + void registerURI(); private: + SecurityManager * _securityManager; + PsychicHttpServer * _server; + HttpEndpoint _httpEndpoint; FSPersistence _fsPersistence; - void board_profile(AsyncWebServerRequest * request); + esp_err_t board_profile(PsychicRequest * request); void onUpdate(); }; diff --git a/src/web/WebStatusService.cpp b/src/web/WebStatusService.cpp index 59272fc50..12c0d36b7 100644 --- a/src/web/WebStatusService.cpp +++ b/src/web/WebStatusService.cpp @@ -1,7 +1,7 @@ /* * EMS-ESP - https://github.com/emsesp/EMS-ESP * Copyright 2020-2023 Paul Derbyshire - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or @@ -22,14 +22,18 @@ using namespace std::placeholders; // for `_1` etc namespace emsesp { -WebStatusService::WebStatusService(AsyncWebServer * server, SecurityManager * securityManager) { - // rest endpoint for web page - server->on(EMSESP_STATUS_SERVICE_PATH, - HTTP_GET, - securityManager->wrapRequest(std::bind(&WebStatusService::webStatusService, this, _1), AuthenticationPredicates::IS_AUTHENTICATED)); +WebStatusService::WebStatusService(PsychicHttpServer * server, SecurityManager * securityManager) + : _server(server) + , _securityManager(securityManager) { WiFi.onEvent(std::bind(&WebStatusService::WiFiEvent, this, _1, _2)); } +void WebStatusService::registerURI() { + _server->on(EMSESP_STATUS_SERVICE_PATH, + HTTP_GET, + _securityManager->wrapRequest(std::bind(&WebStatusService::webStatusService, this, _1), AuthenticationPredicates::IS_AUTHENTICATED)); +} + // handles both WiFI and Ethernet void WebStatusService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) { #ifndef EMSESP_STANDALONE @@ -114,9 +118,9 @@ void WebStatusService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) { #endif } -void WebStatusService::webStatusService(AsyncWebServerRequest * request) { - auto * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_LARGE); - JsonObject root = response->getRoot(); +esp_err_t WebStatusService::webStatusService(PsychicRequest * request) { + PsychicJsonResponse response = PsychicJsonResponse(request, false, EMSESP_JSON_SIZE_LARGE); + JsonObject root = response.getRoot(); root["status"] = EMSESP::bus_status(); // 0, 1 or 2 root["tx_mode"] = EMSESP::txservice_.tx_mode(); @@ -189,8 +193,7 @@ void WebStatusService::webStatusService(AsyncWebServerRequest * request) { } #endif - response->setLength(); - request->send(response); + return response.send(); } // start the multicast UDP service so EMS-ESP is discoverable via .local diff --git a/src/web/WebStatusService.h b/src/web/WebStatusService.h index c0111a86f..d80cc5038 100644 --- a/src/web/WebStatusService.h +++ b/src/web/WebStatusService.h @@ -1,7 +1,7 @@ /* * EMS-ESP - https://github.com/emsesp/EMS-ESP * Copyright 2020-2023 Paul Derbyshire - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or @@ -27,10 +27,14 @@ namespace emsesp { class WebStatusService { public: - WebStatusService(AsyncWebServer * server, SecurityManager * securityManager); + WebStatusService(PsychicHttpServer * server, SecurityManager * securityManager); + void registerURI(); private: - void webStatusService(AsyncWebServerRequest * request); + SecurityManager * _securityManager; + PsychicHttpServer * _server; + + esp_err_t webStatusService(PsychicRequest * request); void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info); void mDNS_start() const; const char * disconnectReason(uint8_t code);