├── .github └── workflows │ └── ci.yaml ├── .gitignore ├── CHANGELOG.md ├── ESP32-sveltekit.code-workspace ├── LICENSE ├── README.md ├── docs ├── buildprocess.md ├── components.md ├── gettingstarted.md ├── index.md ├── media │ ├── Login_dark.png │ ├── Login_light.png │ ├── PIO-upload.png │ ├── Screenshot_dark.png │ ├── Screenshot_light.png │ ├── Screenshot_mobile.png │ ├── favicon.png │ ├── framework.png │ ├── mkdocs_gh-pages.PNG │ └── svelte-logo.png ├── restfulapi.md ├── statefulservice.md ├── stores.md ├── structure.md └── sveltekit.md ├── factory_settings.ini ├── features.ini ├── interface ├── .eslintignore ├── .eslintrc.cjs ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── postcss.config.js ├── src │ ├── app.css │ ├── app.d.ts │ ├── app.html │ ├── lib │ │ ├── DaisyUiHelper.ts │ │ ├── assets │ │ │ └── logo.png │ │ ├── components │ │ │ ├── BatteryIndicator.svelte │ │ │ ├── Collapsible.svelte │ │ │ ├── ConfirmDialog.svelte │ │ │ ├── GithubUpdateDialog.svelte │ │ │ ├── InfoDialog.svelte │ │ │ ├── InputPassword.svelte │ │ │ ├── RSSIIndicator.svelte │ │ │ ├── SettingsCard.svelte │ │ │ ├── Spinner.svelte │ │ │ ├── UpdateIndicator.svelte │ │ │ └── toasts │ │ │ │ ├── Toast.svelte │ │ │ │ └── notifications.ts │ │ ├── stores │ │ │ ├── analytics.ts │ │ │ ├── battery.ts │ │ │ ├── socket.ts │ │ │ ├── telemetry.ts │ │ │ └── user.ts │ │ └── types │ │ │ └── models.ts │ └── routes │ │ ├── +error.svelte │ │ ├── +layout.svelte │ │ ├── +layout.ts │ │ ├── +page.svelte │ │ ├── connections │ │ ├── +page.ts │ │ ├── mqtt │ │ │ ├── +page.svelte │ │ │ ├── +page.ts │ │ │ ├── MQTT.svelte │ │ │ └── MQTTConfig.svelte │ │ └── ntp │ │ │ ├── +page.svelte │ │ │ ├── +page.ts │ │ │ ├── NTP.svelte │ │ │ └── timezones.ts │ │ ├── demo │ │ ├── +page.svelte │ │ ├── +page.ts │ │ └── Demo.svelte │ │ ├── login.svelte │ │ ├── menu.svelte │ │ ├── statusbar.svelte │ │ ├── system │ │ ├── +page.ts │ │ ├── metrics │ │ │ ├── +page.svelte │ │ │ ├── +page.ts │ │ │ ├── BatteryMetrics.svelte │ │ │ └── SystemMetrics.svelte │ │ ├── status │ │ │ ├── +page.svelte │ │ │ ├── +page.ts │ │ │ └── SystemStatus.svelte │ │ └── update │ │ │ ├── +page.svelte │ │ │ ├── +page.ts │ │ │ ├── GithubFirmwareManager.svelte │ │ │ └── UploadFirmware.svelte │ │ ├── user │ │ ├── +page.svelte │ │ ├── +page.ts │ │ └── EditUser.svelte │ │ └── wifi │ │ ├── +page.ts │ │ ├── ap │ │ ├── +page.svelte │ │ ├── +page.ts │ │ └── Accesspoint.svelte │ │ └── sta │ │ ├── +page.svelte │ │ ├── +page.ts │ │ ├── Scan.svelte │ │ └── Wifi.svelte ├── static │ ├── favicon.png │ └── manifest.json ├── svelte.config.js ├── tailwind.config.cjs ├── tsconfig.json ├── vite-plugin-littlefs.ts └── vite.config.ts ├── lib ├── PsychicHttp │ ├── .gitignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── RELEASE.md │ ├── assets │ │ ├── handler-callbacks.svg │ │ └── request-flow.svg │ ├── library.json │ ├── request flow.drawio │ └── src │ │ ├── ChunkPrinter.cpp │ │ ├── ChunkPrinter.h │ │ ├── PsychicClient.cpp │ │ ├── PsychicClient.h │ │ ├── PsychicCore.h │ │ ├── PsychicEndpoint.cpp │ │ ├── PsychicEndpoint.h │ │ ├── PsychicEventSource.cpp │ │ ├── PsychicEventSource.h │ │ ├── PsychicFileResponse.cpp │ │ ├── PsychicFileResponse.h │ │ ├── PsychicHandler.cpp │ │ ├── PsychicHandler.h │ │ ├── PsychicHttp.h │ │ ├── PsychicHttpServer.cpp │ │ ├── PsychicHttpServer.h │ │ ├── PsychicHttpsServer.cpp │ │ ├── PsychicHttpsServer.h │ │ ├── PsychicJson.cpp │ │ ├── PsychicJson.h │ │ ├── PsychicRequest.cpp │ │ ├── PsychicRequest.h │ │ ├── PsychicResponse.cpp │ │ ├── PsychicResponse.h │ │ ├── PsychicStaticFileHander.cpp │ │ ├── PsychicStaticFileHandler.h │ │ ├── PsychicStreamResponse.cpp │ │ ├── PsychicStreamResponse.h │ │ ├── PsychicUploadHandler.cpp │ │ ├── PsychicUploadHandler.h │ │ ├── PsychicWebHandler.cpp │ │ ├── PsychicWebHandler.h │ │ ├── PsychicWebParameter.h │ │ ├── PsychicWebSocket.cpp │ │ ├── PsychicWebSocket.h │ │ ├── TemplatePrinter.cpp │ │ ├── TemplatePrinter.h │ │ ├── async_worker.cpp │ │ ├── async_worker.h │ │ ├── http_status.cpp │ │ └── http_status.h └── framework │ ├── APSettingsService.cpp │ ├── APSettingsService.h │ ├── APStatus.cpp │ ├── APStatus.h │ ├── AnalyticsService.h │ ├── ArduinoJsonJWT.cpp │ ├── ArduinoJsonJWT.h │ ├── AuthenticationService.cpp │ ├── AuthenticationService.h │ ├── BatteryService.cpp │ ├── BatteryService.h │ ├── CoreDump.cpp │ ├── CoreDump.h │ ├── DownloadFirmwareService.cpp │ ├── DownloadFirmwareService.h │ ├── ESP32SvelteKit.cpp │ ├── ESP32SvelteKit.h │ ├── ESPFS.h │ ├── EventEndpoint.h │ ├── EventSocket.cpp │ ├── EventSocket.h │ ├── FSPersistence.h │ ├── FactoryResetService.cpp │ ├── FactoryResetService.h │ ├── Features.h │ ├── FeaturesService.cpp │ ├── FeaturesService.h │ ├── HttpEndpoint.h │ ├── IPUtils.h │ ├── JsonUtils.h │ ├── LICENSE │ ├── MqttEndpoint.h │ ├── MqttSettingsService.cpp │ ├── MqttSettingsService.h │ ├── MqttStatus.cpp │ ├── MqttStatus.h │ ├── NTPSettingsService.cpp │ ├── NTPSettingsService.h │ ├── NTPStatus.cpp │ ├── NTPStatus.h │ ├── NotificationService.cpp │ ├── NotificationService.h │ ├── RestartService.cpp │ ├── RestartService.h │ ├── SecurityManager.h │ ├── SecuritySettingsService.cpp │ ├── SecuritySettingsService.h │ ├── SettingValue.cpp │ ├── SettingValue.h │ ├── SleepService.cpp │ ├── SleepService.h │ ├── StatefulService.cpp │ ├── StatefulService.h │ ├── SystemStatus.cpp │ ├── SystemStatus.h │ ├── UploadFirmwareService.cpp │ ├── UploadFirmwareService.h │ ├── WWWData.h │ ├── WebSocketClient.bak │ ├── WebSocketServer.h │ ├── WiFiScanner.cpp │ ├── WiFiScanner.h │ ├── WiFiSettingsService.cpp │ ├── WiFiSettingsService.h │ ├── WiFiStatus.cpp │ └── WiFiStatus.h ├── mkdocs.yml ├── platformio.ini ├── scripts ├── build_interface.py ├── generate_cert_bundle.py ├── merge_bin.py ├── rename_fw.py └── save_elf.py ├── src ├── LightMqttSettingsService.cpp ├── LightMqttSettingsService.h ├── LightStateService.cpp ├── LightStateService.h └── main.cpp └── ssl_certs └── DigiCert_Global_Root_CA.pem /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: ci 2 | on: 3 | push: 4 | branches: 5 | - master 6 | - main 7 | permissions: 8 | contents: write 9 | jobs: 10 | deploy: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - uses: actions/setup-python@v4 15 | with: 16 | python-version: 3.x 17 | - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV 18 | - uses: actions/cache@v3 19 | with: 20 | key: mkdocs-material-${{ env.cache_id }} 21 | path: .cache 22 | restore-keys: | 23 | mkdocs-material- 24 | - run: pip install mkdocs-material 25 | - run: mkdocs gh-deploy --force 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .clang_complete 3 | .gcc-flags.json 4 | *Thumbs.db 5 | /data/www 6 | /interface/build 7 | /interface/node_modules 8 | /interface/.eslintcache 9 | .vscode 10 | node_modules 11 | /releases 12 | /src/certs 13 | /temp 14 | /build 15 | /lib/framework/WWWData.h 16 | *WWWData.h 17 | lib/framework/WWWData.h 18 | ssl_certs/cacert.pem 19 | /logs 20 | -------------------------------------------------------------------------------- /ESP32-sveltekit.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "settings": { 8 | "files.associations": { 9 | "*.tcc": "cpp", 10 | "algorithm": "cpp", 11 | "esp32-hal-misc.c": "cpp", 12 | "esp_crt_bundle.h": "c", 13 | "functional": "cpp", 14 | "array": "cpp", 15 | "atomic": "cpp", 16 | "bitset": "cpp", 17 | "cctype": "cpp", 18 | "clocale": "cpp", 19 | "cmath": "cpp", 20 | "cstdarg": "cpp", 21 | "cstddef": "cpp", 22 | "cstdint": "cpp", 23 | "cstdio": "cpp", 24 | "cstdlib": "cpp", 25 | "cstring": "cpp", 26 | "ctime": "cpp", 27 | "cwchar": "cpp", 28 | "cwctype": "cpp", 29 | "deque": "cpp", 30 | "list": "cpp", 31 | "unordered_map": "cpp", 32 | "vector": "cpp", 33 | "exception": "cpp", 34 | "iterator": "cpp", 35 | "map": "cpp", 36 | "memory": "cpp", 37 | "memory_resource": "cpp", 38 | "numeric": "cpp", 39 | "optional": "cpp", 40 | "random": "cpp", 41 | "regex": "cpp", 42 | "string": "cpp", 43 | "string_view": "cpp", 44 | "system_error": "cpp", 45 | "tuple": "cpp", 46 | "type_traits": "cpp", 47 | "utility": "cpp", 48 | "fstream": "cpp", 49 | "initializer_list": "cpp", 50 | "iosfwd": "cpp", 51 | "istream": "cpp", 52 | "limits": "cpp", 53 | "new": "cpp", 54 | "ostream": "cpp", 55 | "sstream": "cpp", 56 | "stdexcept": "cpp", 57 | "streambuf": "cpp", 58 | "cinttypes": "cpp", 59 | "typeinfo": "cpp", 60 | "unordered_set": "cpp", 61 | "iomanip": "cpp" 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /docs/media/Login_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theelims/ESP32-sveltekit/a9e8cd695fa0edecba9806d53b6a5e9c47b9a16f/docs/media/Login_dark.png -------------------------------------------------------------------------------- /docs/media/Login_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theelims/ESP32-sveltekit/a9e8cd695fa0edecba9806d53b6a5e9c47b9a16f/docs/media/Login_light.png -------------------------------------------------------------------------------- /docs/media/PIO-upload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theelims/ESP32-sveltekit/a9e8cd695fa0edecba9806d53b6a5e9c47b9a16f/docs/media/PIO-upload.png -------------------------------------------------------------------------------- /docs/media/Screenshot_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theelims/ESP32-sveltekit/a9e8cd695fa0edecba9806d53b6a5e9c47b9a16f/docs/media/Screenshot_dark.png -------------------------------------------------------------------------------- /docs/media/Screenshot_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theelims/ESP32-sveltekit/a9e8cd695fa0edecba9806d53b6a5e9c47b9a16f/docs/media/Screenshot_light.png -------------------------------------------------------------------------------- /docs/media/Screenshot_mobile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theelims/ESP32-sveltekit/a9e8cd695fa0edecba9806d53b6a5e9c47b9a16f/docs/media/Screenshot_mobile.png -------------------------------------------------------------------------------- /docs/media/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theelims/ESP32-sveltekit/a9e8cd695fa0edecba9806d53b6a5e9c47b9a16f/docs/media/favicon.png -------------------------------------------------------------------------------- /docs/media/framework.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theelims/ESP32-sveltekit/a9e8cd695fa0edecba9806d53b6a5e9c47b9a16f/docs/media/framework.png -------------------------------------------------------------------------------- /docs/media/mkdocs_gh-pages.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theelims/ESP32-sveltekit/a9e8cd695fa0edecba9806d53b6a5e9c47b9a16f/docs/media/mkdocs_gh-pages.PNG -------------------------------------------------------------------------------- /docs/media/svelte-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theelims/ESP32-sveltekit/a9e8cd695fa0edecba9806d53b6a5e9c47b9a16f/docs/media/svelte-logo.png -------------------------------------------------------------------------------- /factory_settings.ini: -------------------------------------------------------------------------------- 1 | ; The indicated settings support placeholder substitution as follows: 2 | ; 3 | ; #{platform} - The microcontroller platform, e.g. "esp32" or "esp32c3" 4 | ; #{unique_id} - A unique identifier derived from the MAC address, e.g. "0b0a859d6816" 5 | ; #{random} - A random number encoded as a hex string, e.g. "55722f94" 6 | 7 | [factory_settings] 8 | build_flags = 9 | ; WiFi settings 10 | -D FACTORY_WIFI_SSID=\"\" 11 | -D FACTORY_WIFI_PASSWORD=\"\" 12 | -D FACTORY_WIFI_HOSTNAME=\"#{platform}-#{unique_id}\" ; supports placeholders 13 | -D FACTORY_WIFI_RSSI_THRESHOLD=-80 ; dBm, -80 is a good value for most applications 14 | 15 | ; Access point settings 16 | -D FACTORY_AP_PROVISION_MODE=AP_MODE_DISCONNECTED 17 | -D FACTORY_AP_SSID=\"ESP32-SvelteKit-#{unique_id}\" ; 1-64 characters, supports placeholders 18 | -D FACTORY_AP_PASSWORD=\"esp-sveltekit\" ; 8-64 characters 19 | -D FACTORY_AP_CHANNEL=1 20 | -D FACTORY_AP_SSID_HIDDEN=false 21 | -D FACTORY_AP_MAX_CLIENTS=4 22 | -D FACTORY_AP_LOCAL_IP=\"192.168.4.1\" 23 | -D FACTORY_AP_GATEWAY_IP=\"192.168.4.1\" 24 | -D FACTORY_AP_SUBNET_MASK=\"255.255.255.0\" 25 | 26 | ; User credentials for admin and guest user 27 | -D FACTORY_ADMIN_USERNAME=\"admin\" 28 | -D FACTORY_ADMIN_PASSWORD=\"admin\" 29 | -D FACTORY_GUEST_USERNAME=\"guest\" 30 | -D FACTORY_GUEST_PASSWORD=\"guest\" 31 | 32 | ; NTP settings 33 | -D FACTORY_NTP_ENABLED=true 34 | -D FACTORY_NTP_TIME_ZONE_LABEL=\"Europe/Berlin\" 35 | -D FACTORY_NTP_TIME_ZONE_FORMAT=\"GMT0BST,M3.5.0/1,M10.5.0\" 36 | -D FACTORY_NTP_SERVER=\"time.google.com\" 37 | 38 | ; MQTT settings 39 | -D FACTORY_MQTT_ENABLED=false 40 | -D FACTORY_MQTT_URI=\"mqtts://mqtt.eclipseprojects.io:8883\" 41 | -D FACTORY_MQTT_USERNAME=\"\" ; supports placeholders 42 | -D FACTORY_MQTT_PASSWORD=\"\" 43 | -D FACTORY_MQTT_CLIENT_ID=\"#{platform}-#{unique_id}\" ; supports placeholders 44 | -D FACTORY_MQTT_KEEP_ALIVE=120 45 | -D FACTORY_MQTT_CLEAN_SESSION=true 46 | 47 | ; JWT Secret 48 | -D FACTORY_JWT_SECRET=\"#{random}-#{random}\" ; supports placeholders 49 | 50 | ; Deep Sleep Configuration 51 | -D WAKEUP_PIN_NUMBER=0 ; pin number to wake up the ESP 52 | -D WAKEUP_SIGNAL=0 ; 1 for wakeup on HIGH, 0 for wakeup on LOW 53 | 54 | 55 | -------------------------------------------------------------------------------- /features.ini: -------------------------------------------------------------------------------- 1 | [features] 2 | build_flags = 3 | -D FT_SECURITY=1 4 | -D FT_MQTT=1 5 | -D FT_NTP=1 6 | -D FT_UPLOAD_FIRMWARE=1 7 | -D FT_DOWNLOAD_FIRMWARE=1 ; requires FT_NTP=1 8 | -D FT_SLEEP=1 9 | -D FT_BATTERY=0 10 | -D FT_ANALYTICS=1 11 | -D FT_COREDUMP=1 12 | -------------------------------------------------------------------------------- /interface/.eslintignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /interface/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'], 5 | plugins: ['svelte3', '@typescript-eslint'], 6 | ignorePatterns: ['*.cjs'], 7 | overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }], 8 | settings: { 9 | 'svelte3/typescript': () => require('typescript') 10 | }, 11 | parserOptions: { 12 | sourceType: 'module', 13 | ecmaVersion: 2020 14 | }, 15 | env: { 16 | browser: true, 17 | es2017: true, 18 | node: true 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /interface/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | vite.config.js.timestamp-* 10 | vite.config.ts.timestamp-* 11 | -------------------------------------------------------------------------------- /interface/.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /interface/.prettierignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /interface/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100, 6 | "plugins": ["prettier-plugin-svelte"], 7 | "pluginSearchDirs": ["."], 8 | "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] 9 | } 10 | -------------------------------------------------------------------------------- /interface/LICENSE: -------------------------------------------------------------------------------- 1 | ESP32-SvelteKit is distributed with two licenses for different sections of the 2 | code. The back end code inherits the GNU LESSER GENERAL PUBLIC LICENSE Version 3 3 | and is therefore distributed said license. The front end code is distributed 4 | under the MIT License. 5 | 6 | MIT License 7 | 8 | Copyright (C) 2023 - 2024 theelims 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | -------------------------------------------------------------------------------- /interface/README.md: -------------------------------------------------------------------------------- 1 | # create-svelte 2 | 3 | Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte). 4 | 5 | ## Creating a project 6 | 7 | If you're seeing this, you've probably already done this step. Congrats! 8 | 9 | ```bash 10 | # create a new project in the current directory 11 | npm create svelte@latest 12 | 13 | # create a new project in my-app 14 | npm create svelte@latest my-app 15 | ``` 16 | 17 | ## Developing 18 | 19 | Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: 20 | 21 | ```bash 22 | npm run dev 23 | 24 | # or start the server and open the app in a new browser tab 25 | npm run dev -- --open 26 | ``` 27 | 28 | ## Building 29 | 30 | To create a production version of your app: 31 | 32 | ```bash 33 | npm run build 34 | ``` 35 | 36 | You can preview the production build with `npm run preview`. 37 | 38 | > To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment. 39 | -------------------------------------------------------------------------------- /interface/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ESP32-Sveltekit Template", 3 | "version": "0.2.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vite dev", 7 | "build": "vite build", 8 | "preview": "vite preview", 9 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 10 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", 11 | "lint": "prettier --plugin-search-dir . --check . && eslint .", 12 | "format": "prettier --plugin-search-dir . --write ." 13 | }, 14 | "devDependencies": { 15 | "@iconify-json/tabler": "^1.1.86", 16 | "@sveltejs/adapter-auto": "^3.0.0", 17 | "@sveltejs/adapter-static": "^3.0.0", 18 | "@sveltejs/kit": "^2.5.27", 19 | "@sveltejs/vite-plugin-svelte": "^4.0.0", 20 | "@types/msgpack-lite": "^0.1.11", 21 | "@typescript-eslint/eslint-plugin": "^6.2.1", 22 | "@typescript-eslint/parser": "^6.2.1", 23 | "autoprefixer": "^10.4.14", 24 | "daisyui": "^4.12.23", 25 | "eslint": "^8.46.0", 26 | "eslint-config-prettier": "^9.0.0", 27 | "eslint-plugin-svelte": "^2.45.1", 28 | "postcss": "^8.4.27", 29 | "prettier": "^3.1.0", 30 | "prettier-plugin-svelte": "^3.2.6", 31 | "prettier-plugin-tailwindcss": "^0.5.4", 32 | "svelte": "^5.0.0", 33 | "svelte-check": "^4.0.0", 34 | "svelte-focus-trap": "^1.2.0", 35 | "tailwindcss": "^3.3.3", 36 | "tslib": "^2.6.1", 37 | "typescript": "^5.5.0", 38 | "unplugin-icons": "^0.18.5", 39 | "vite": "^5.4.4" 40 | }, 41 | "type": "module", 42 | "dependencies": { 43 | "chart.js": "^4.4.0", 44 | "chartjs-adapter-luxon": "^1.3.1", 45 | "compare-versions": "^6.0.0", 46 | "jwt-decode": "^4.0.0", 47 | "luxon": "^3.5.0", 48 | "msgpack-lite": "^0.1.26", 49 | "postcss-remove-classes": "^2.0.0", 50 | "svelte-dnd-list": "^0.1.8", 51 | "svelte-modals": "^2.0.0" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /interface/postcss.config.js: -------------------------------------------------------------------------------- 1 | import tailwindcss from 'tailwindcss'; 2 | import autoprefixer from 'autoprefixer'; 3 | import postcssRemoveClasses from 'postcss-remove-classes'; 4 | 5 | export default { 6 | plugins: [ 7 | tailwindcss(), 8 | autoprefixer() 9 | //postcssRemoveClasses.default('range') 10 | ] 11 | }; 12 | -------------------------------------------------------------------------------- /interface/src/app.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /interface/src/app.d.ts: -------------------------------------------------------------------------------- 1 | // See https://kit.svelte.dev/docs/types#app 2 | // for information about these interfaces 3 | 4 | /// 5 | /// 6 | 7 | declare global { 8 | namespace App { 9 | // interface Error {} 10 | // interface Locals {} 11 | // interface PageData {} 12 | // interface Platform {} 13 | } 14 | } 15 | 16 | export {}; 17 | -------------------------------------------------------------------------------- /interface/src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %sveltekit.head% 8 | 9 | 10 |
%sveltekit.body%
11 | 12 | 13 | -------------------------------------------------------------------------------- /interface/src/lib/DaisyUiHelper.ts: -------------------------------------------------------------------------------- 1 | export function daisyColor(name: string, opacity: number = 100) { 2 | const color = getComputedStyle(document.documentElement).getPropertyValue(name); 3 | return `oklch(${color} / ${Math.min(Math.max(Math.round(opacity), 0), 100)}%)`; 4 | } 5 | -------------------------------------------------------------------------------- /interface/src/lib/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theelims/ESP32-sveltekit/a9e8cd695fa0edecba9806d53b6a5e9c47b9a16f/interface/src/lib/assets/logo.png -------------------------------------------------------------------------------- /interface/src/lib/components/BatteryIndicator.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 |
13 | {#if charging} 14 | 15 | {:else if soc > 75} 16 | 17 | {:else if soc > 55} 18 | 19 | {:else if soc > 30} 20 | 21 | {:else if soc > 5} 22 | 23 | {:else} 24 | 25 | {/if} 26 |
27 | -------------------------------------------------------------------------------- /interface/src/lib/components/Collapsible.svelte: -------------------------------------------------------------------------------- 1 | 17 | 18 |
19 |
20 | 21 | 22 | 23 | 24 | 31 |
32 | {#if open} 33 |
37 | 38 |
39 | {/if} 40 |
41 | -------------------------------------------------------------------------------- /interface/src/lib/components/ConfirmDialog.svelte: -------------------------------------------------------------------------------- 1 | 29 | 30 | {#if isOpen} 31 | {@const SvelteComponent = labels?.confirm.icon} 32 | 60 | {/if} 61 | -------------------------------------------------------------------------------- /interface/src/lib/components/GithubUpdateDialog.svelte: -------------------------------------------------------------------------------- 1 | 62 | 63 | {#if isOpen} 64 | 101 | {/if} 102 | -------------------------------------------------------------------------------- /interface/src/lib/components/InfoDialog.svelte: -------------------------------------------------------------------------------- 1 | 25 | 26 | {#if isOpen} 27 | 49 | {/if} 50 | -------------------------------------------------------------------------------- /interface/src/lib/components/InputPassword.svelte: -------------------------------------------------------------------------------- 1 | 15 | 16 |
17 | 18 |
19 | 20 | (show = false)} 24 | width="40" 25 | height="40" 26 | viewBox="0 0 24 24" 27 | stroke-width="2" 28 | stroke="currentColor" 29 | fill="none" 30 | stroke-linecap="round" 31 | stroke-linejoin="round" 32 | > 33 | 34 | 35 | 38 | 39 | 40 | 41 | 42 | (show = true)} 46 | width="40" 47 | height="40" 48 | viewBox="0 0 24 24" 49 | stroke-width="2" 50 | stroke="currentColor" 51 | fill="none" 52 | stroke-linecap="round" 53 | stroke-linejoin="round" 54 | > 55 | 56 | 57 | 58 | 59 |
60 |
61 | -------------------------------------------------------------------------------- /interface/src/lib/components/RSSIIndicator.svelte: -------------------------------------------------------------------------------- 1 | 15 | 16 |
17 |
18 | {#if showDBm} 19 | 20 | {rssi_dbm} dBm 21 | 22 | {/if} 23 | {#if rssi_dbm >= -55} 24 | 25 | {:else if rssi_dbm >= -75} 26 |
27 | 28 | 29 |
30 | {:else if rssi_dbm >= -85} 31 |
32 | 33 | 34 |
35 | {:else} 36 |
37 | 38 | 39 |
40 | {/if} 41 |
42 |
43 | -------------------------------------------------------------------------------- /interface/src/lib/components/SettingsCard.svelte: -------------------------------------------------------------------------------- 1 | 21 | 22 | {#if collapsible} 23 |
26 |
29 | 30 | {@render icon?.()} 31 | {@render title?.()} 32 | 33 | 45 |
46 | {#if open} 47 |
51 | {@render children?.()} 52 |
53 | {/if} 54 |
55 | {:else} 56 |
59 |
60 | 61 | {@render icon?.()} 62 | {@render title?.()} 63 | 64 |
65 |
66 | {@render children?.()} 67 |
68 |
69 | {/if} 70 | -------------------------------------------------------------------------------- /interface/src/lib/components/Spinner.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 | 7 |

Loading...

8 |
9 | -------------------------------------------------------------------------------- /interface/src/lib/components/toasts/Toast.svelte: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 | {#each $notifications as notification (notification.id)} 27 | {@const SvelteComponent = icon[notification.type]} 28 |
34 | 35 | {notification.message} 36 |
37 | {/each} 38 |
39 | -------------------------------------------------------------------------------- /interface/src/lib/components/toasts/notifications.ts: -------------------------------------------------------------------------------- 1 | import { writable, derived, type Writable } from 'svelte/store'; 2 | 3 | type StateType = 'info' | 'success' | 'warning' | 'error'; 4 | 5 | type State = { 6 | id: string; 7 | type: StateType; 8 | message: string; 9 | }; 10 | 11 | function createNotificationStore() { 12 | const state: State[] = []; 13 | const notifications = writable(state); 14 | const { subscribe } = notifications; 15 | 16 | function send(message: string, type: StateType = 'info', timeout: number) { 17 | const id = generateId(); 18 | setTimeout(() => { 19 | notifications.update((state) => { 20 | return state.filter((n) => n.id !== id); 21 | }); 22 | }, timeout); 23 | notifications.update((state) => { 24 | return [...state, { id, type, message }]; 25 | }); 26 | } 27 | 28 | return { 29 | subscribe, 30 | send, 31 | error: (msg: string, timeout: number) => send(msg, 'error', timeout), 32 | warning: (msg: string, timeout: number) => send(msg, 'warning', timeout), 33 | info: (msg: string, timeout: number) => send(msg, 'info', timeout), 34 | success: (msg: string, timeout: number) => send(msg, 'success', timeout) 35 | }; 36 | } 37 | 38 | function generateId() { 39 | return '_' + Math.random().toString(36).substr(2, 9); 40 | } 41 | 42 | export const notifications = createNotificationStore(); 43 | -------------------------------------------------------------------------------- /interface/src/lib/stores/analytics.ts: -------------------------------------------------------------------------------- 1 | import { type Analytics } from '$lib/types/models'; 2 | import { writable } from 'svelte/store'; 3 | 4 | let analytics_data = { 5 | uptime: [], 6 | free_heap: [], 7 | used_heap: [], 8 | total_heap: [], 9 | min_free_heap: [], 10 | max_alloc_heap: [], 11 | fs_used: [], 12 | fs_total: [], 13 | core_temp: [], 14 | free_psram: [], 15 | used_psram: [], 16 | psram_size: [], 17 | }; 18 | 19 | const maxAnalyticsData = 1000; // roughly 33 Minutes of data at 1 update per 2 seconds 20 | 21 | function createAnalytics() { 22 | const { subscribe, update } = writable(analytics_data); 23 | 24 | return { 25 | subscribe, 26 | addData: (content: Analytics) => { 27 | update((analytics_data) => ({ 28 | ...analytics_data, 29 | uptime: [...analytics_data.uptime, content.uptime].slice(-maxAnalyticsData), 30 | free_heap: [...analytics_data.free_heap, content.free_heap / 1000].slice(-maxAnalyticsData), 31 | used_heap: [...analytics_data.used_heap, content.used_heap / 1000].slice(-maxAnalyticsData), 32 | total_heap: [...analytics_data.total_heap, content.total_heap / 1000].slice( 33 | -maxAnalyticsData 34 | ), 35 | min_free_heap: [...analytics_data.min_free_heap, content.min_free_heap / 1000].slice( 36 | -maxAnalyticsData 37 | ), 38 | max_alloc_heap: [...analytics_data.max_alloc_heap, content.max_alloc_heap / 1000].slice( 39 | -maxAnalyticsData 40 | ), 41 | fs_used: [...analytics_data.fs_used, content.fs_used / 1000].slice(-maxAnalyticsData), 42 | fs_total: [...analytics_data.fs_total, content.fs_total / 1000].slice(-maxAnalyticsData), 43 | core_temp: [...analytics_data.core_temp, content.core_temp].slice(-maxAnalyticsData), 44 | free_psram: [...analytics_data.free_psram, content.free_psram / 1000].slice(-maxAnalyticsData), 45 | used_psram: [...analytics_data.used_psram, content.used_psram / 1000].slice(-maxAnalyticsData), 46 | psram_size: [...analytics_data.psram_size, content.psram_size / 1000].slice(-maxAnalyticsData), 47 | })); 48 | } 49 | }; 50 | } 51 | 52 | export const analytics = createAnalytics(); 53 | -------------------------------------------------------------------------------- /interface/src/lib/stores/battery.ts: -------------------------------------------------------------------------------- 1 | import { type Battery } from '$lib/types/models'; 2 | import { writable } from 'svelte/store'; 3 | 4 | let battery_history = { 5 | soc: [], 6 | charging: [], 7 | timestamp: [] 8 | }; 9 | 10 | const maxAnalyticsData = 3600; // roughly 5 Hours of data at 1 update per 5 seconds 11 | 12 | function createBatteryHistory() { 13 | const { subscribe, update } = writable(battery_history); 14 | 15 | return { 16 | subscribe, 17 | addData: (content: Battery) => { 18 | update((battery_history) => ({ 19 | ...battery_history, 20 | soc: [...battery_history.soc, content.soc].slice(-maxAnalyticsData), 21 | charging: [...battery_history.charging, content.charging ? 1 : 0].slice(-maxAnalyticsData), 22 | timestamp: [...battery_history.timestamp, Date.now()].slice(-maxAnalyticsData) 23 | })); 24 | } 25 | }; 26 | } 27 | 28 | export const batteryHistory = createBatteryHistory(); 29 | -------------------------------------------------------------------------------- /interface/src/lib/stores/telemetry.ts: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | import type { RSSI } from '../types/models'; 3 | import type { Battery } from '../types/models'; 4 | import type { DownloadOTA } from '../types/models'; 5 | 6 | let telemetry_data = { 7 | rssi: { 8 | rssi: 0, 9 | ssid: '', 10 | disconnected: true 11 | }, 12 | battery: { 13 | soc: 100, 14 | charging: false 15 | }, 16 | download_ota: { 17 | status: 'none', 18 | progress: 0, 19 | error: '' 20 | } 21 | }; 22 | 23 | function createTelemetry() { 24 | const { subscribe, set, update } = writable(telemetry_data); 25 | 26 | return { 27 | subscribe, 28 | setRSSI: (data: RSSI) => { 29 | if (!isNaN(Number(data.rssi))) { 30 | update((telemetry_data) => ({ 31 | ...telemetry_data, 32 | rssi: { rssi: Number(data.rssi), ssid: data.ssid, disconnected: false } 33 | })); 34 | } else { 35 | update((telemetry_data) => ({ 36 | ...telemetry_data, 37 | rssi: { rssi: 0, ssid: data.ssid, disconnected: true } 38 | })); 39 | } 40 | }, 41 | setBattery: (data: Battery) => { 42 | update((telemetry_data) => ({ 43 | ...telemetry_data, 44 | battery: { soc: data.soc, charging: data.charging } 45 | })); 46 | }, 47 | setDownloadOTA: (data: DownloadOTA) => { 48 | update((telemetry_data) => ({ 49 | ...telemetry_data, 50 | download_ota: { status: data.status, progress: data.progress, error: data.error } 51 | })); 52 | } 53 | }; 54 | } 55 | 56 | export const telemetry = createTelemetry(); 57 | -------------------------------------------------------------------------------- /interface/src/lib/stores/user.ts: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | import { goto } from '$app/navigation'; 3 | import { jwtDecode } from 'jwt-decode'; 4 | 5 | export type userProfile = { 6 | username: string; 7 | admin: boolean; 8 | bearer_token: string; 9 | }; 10 | 11 | type decodedJWT = { 12 | username: string; 13 | admin: boolean; 14 | }; 15 | 16 | let empty = { 17 | username: '', 18 | admin: false, 19 | bearer_token: '' 20 | }; 21 | 22 | function createStore() { 23 | const { subscribe, set } = writable(empty); 24 | 25 | // retrieve store from sessionStorage / localStorage if available 26 | const userdata = localStorage.getItem('user'); 27 | if (userdata) { 28 | set(JSON.parse(userdata)); 29 | } 30 | 31 | return { 32 | subscribe, 33 | init: (access_token: string) => { 34 | const decoded: decodedJWT = jwtDecode(access_token); 35 | const userdata = { 36 | bearer_token: access_token, 37 | username: decoded.username, 38 | admin: decoded.admin 39 | }; 40 | set(userdata); 41 | // persist store in sessionStorage / localStorage 42 | localStorage.setItem('user', JSON.stringify(userdata)); 43 | }, 44 | invalidate: () => { 45 | console.log('Log out user'); 46 | set(empty); 47 | // remove localStorage "user" 48 | localStorage.removeItem('user'); 49 | // redirect to login page 50 | goto('/'); 51 | } 52 | }; 53 | } 54 | 55 | export const user = createStore(); 56 | -------------------------------------------------------------------------------- /interface/src/lib/types/models.ts: -------------------------------------------------------------------------------- 1 | export type WifiStatus = { 2 | status: number; 3 | local_ip: string; 4 | mac_address: string; 5 | rssi: number; 6 | ssid: string; 7 | bssid: string; 8 | channel: number; 9 | subnet_mask: string; 10 | gateway_ip: string; 11 | dns_ip_1: string; 12 | dns_ip_2?: string; 13 | }; 14 | 15 | export type WifiSettings = { 16 | hostname: string; 17 | connection_mode: number; 18 | wifi_networks: KnownNetworkItem[]; 19 | }; 20 | 21 | export type KnownNetworkItem = { 22 | ssid: string; 23 | password: string; 24 | static_ip_config: boolean; 25 | local_ip?: string; 26 | subnet_mask?: string; 27 | gateway_ip?: string; 28 | dns_ip_1?: string; 29 | dns_ip_2?: string; 30 | }; 31 | 32 | export type NetworkItem = { 33 | rssi: number; 34 | ssid: string; 35 | bssid: string; 36 | channel: number; 37 | encryption_type: number; 38 | }; 39 | 40 | export type ApStatus = { 41 | status: number; 42 | ip_address: string; 43 | mac_address: string; 44 | station_num: number; 45 | }; 46 | 47 | export type ApSettings = { 48 | provision_mode: number; 49 | ssid: string; 50 | password: string; 51 | channel: number; 52 | ssid_hidden: boolean; 53 | max_clients: number; 54 | local_ip: string; 55 | gateway_ip: string; 56 | subnet_mask: string; 57 | }; 58 | 59 | export type LightState = { 60 | led_on: boolean; 61 | }; 62 | 63 | export type BrokerSettings = { 64 | mqtt_path: string; 65 | name: string; 66 | unique_id: string; 67 | }; 68 | 69 | export type NTPStatus = { 70 | status: number; 71 | utc_time: string; 72 | local_time: string; 73 | server: string; 74 | uptime: number; 75 | }; 76 | 77 | export type NTPSettings = { 78 | enabled: boolean; 79 | server: string; 80 | tz_label: string; 81 | tz_format: string; 82 | }; 83 | 84 | export type Analytics = { 85 | max_alloc_heap: number; 86 | psram_size: number; 87 | free_psram: number; 88 | used_psram: number; 89 | free_heap: number; 90 | used_heap: number; 91 | total_heap: number; 92 | min_free_heap: number; 93 | core_temp: number; 94 | fs_total: number; 95 | fs_used: number; 96 | uptime: number; 97 | }; 98 | 99 | export type RSSI = { 100 | rssi: number; 101 | ssid: string; 102 | }; 103 | 104 | export type Battery = { 105 | soc: number; 106 | charging: boolean; 107 | }; 108 | 109 | export type DownloadOTA = { 110 | status: string; 111 | progress: number; 112 | error: string; 113 | }; 114 | 115 | export type StaticSystemInformation = { 116 | esp_platform: string; 117 | firmware_version: string; 118 | cpu_freq_mhz: number; 119 | cpu_type: string; 120 | cpu_rev: number; 121 | cpu_cores: number; 122 | sketch_size: number; 123 | free_sketch_space: number; 124 | sdk_version: string; 125 | arduino_version: string; 126 | flash_chip_size: number; 127 | flash_chip_speed: number; 128 | cpu_reset_reason: string; 129 | }; 130 | 131 | export type SystemInformation = Analytics & StaticSystemInformation; 132 | 133 | export type MQTTStatus = { 134 | enabled: boolean; 135 | connected: boolean; 136 | client_id: string; 137 | last_error: string; 138 | }; 139 | 140 | export type MQTTSettings = { 141 | enabled: boolean; 142 | uri: string; 143 | username: string; 144 | password: string; 145 | client_id: string; 146 | keep_alive: number; 147 | clean_session: boolean; 148 | }; 149 | -------------------------------------------------------------------------------- /interface/src/routes/+error.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 |
11 |
12 |
13 |
{page.status}
14 |
{page.error?.message}
15 |
16 |
17 |

Oops! Something has gone wrong.

18 |
19 |
20 | -------------------------------------------------------------------------------- /interface/src/routes/+layout.ts: -------------------------------------------------------------------------------- 1 | import type { LayoutLoad } from './$types'; 2 | 3 | // This can be false if you're using a fallback (i.e. SPA mode) 4 | export const prerender = false; 5 | export const ssr = false; 6 | 7 | export const load = (async ({ fetch }) => { 8 | const result = await fetch('/rest/features'); 9 | const item = await result.json(); 10 | return { 11 | features: item, 12 | title: 'ESP32-SvelteKit', 13 | github: 'theelims/ESP32-sveltekit', 14 | copyright: '2024 theelims', 15 | appName: 'ESP32 SvelteKit' 16 | }; 17 | }) satisfies LayoutLoad; 18 | -------------------------------------------------------------------------------- /interface/src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
14 |
15 |
Logo
16 |
17 |

Welcome to ESP32-SvelteKit

18 |

19 | A simple, secure and extensible framework for IoT projects for ESP32 platforms with 20 | responsive SvelteKit 26 | front-end built with 27 | TailwindCSS 30 | and 31 | DaisyUI. 34 |

35 | notifications.success('You did it!', 1000)}>Start Demo 40 |
41 |
42 |
43 | -------------------------------------------------------------------------------- /interface/src/routes/connections/+page.ts: -------------------------------------------------------------------------------- 1 | import type { PageLoad } from './$types'; 2 | import { goto } from '$app/navigation'; 3 | 4 | export const load = (async () => { 5 | goto('/'); 6 | return; 7 | }) satisfies PageLoad; 8 | -------------------------------------------------------------------------------- /interface/src/routes/connections/mqtt/+page.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
17 | 18 | 19 |
20 | -------------------------------------------------------------------------------- /interface/src/routes/connections/mqtt/+page.ts: -------------------------------------------------------------------------------- 1 | import type { PageLoad } from './$types'; 2 | 3 | export const load = (async () => { 4 | return { 5 | title: "MQTT" 6 | }; 7 | }) satisfies PageLoad; -------------------------------------------------------------------------------- /interface/src/routes/connections/ntp/+page.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 |
16 | 17 |
18 | -------------------------------------------------------------------------------- /interface/src/routes/connections/ntp/+page.ts: -------------------------------------------------------------------------------- 1 | import type { PageLoad } from './$types'; 2 | 3 | export const load = (async () => { 4 | return { 5 | title: 'NTP' 6 | }; 7 | }) satisfies PageLoad; 8 | -------------------------------------------------------------------------------- /interface/src/routes/demo/+page.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 |
16 | 17 |
18 | -------------------------------------------------------------------------------- /interface/src/routes/demo/+page.ts: -------------------------------------------------------------------------------- 1 | import type { PageLoad } from './$types'; 2 | 3 | export const load = (async ({ fetch }) => { 4 | return { 5 | title: 'Demo App' 6 | }; 7 | }) satisfies PageLoad; 8 | -------------------------------------------------------------------------------- /interface/src/routes/login.svelte: -------------------------------------------------------------------------------- 1 | 52 | 53 |
54 |
61 |
Logo
62 |
63 |

Login

64 |
65 | 68 | 74 | 75 | 78 | 79 | 80 |
81 | 87 |
88 | 89 |
90 |
91 |
92 | 93 | 123 | -------------------------------------------------------------------------------- /interface/src/routes/statusbar.svelte: -------------------------------------------------------------------------------- 1 | 39 | 40 | 82 | -------------------------------------------------------------------------------- /interface/src/routes/system/+page.ts: -------------------------------------------------------------------------------- 1 | import type { PageLoad } from './$types'; 2 | import { goto } from '$app/navigation'; 3 | 4 | export const load = (async () => { 5 | goto('/'); 6 | return; 7 | }) satisfies PageLoad; 8 | -------------------------------------------------------------------------------- /interface/src/routes/system/metrics/+page.svelte: -------------------------------------------------------------------------------- 1 | 19 | 20 |
24 | {#if page.data.features.analytics} 25 | 26 | {/if} 27 | {#if page.data.features.battery} 28 | 29 | {/if} 30 |
31 | -------------------------------------------------------------------------------- /interface/src/routes/system/metrics/+page.ts: -------------------------------------------------------------------------------- 1 | import type { PageLoad } from './$types'; 2 | 3 | export const load = (async () => { 4 | return { title: 'System Metrics' }; 5 | }) satisfies PageLoad; 6 | -------------------------------------------------------------------------------- /interface/src/routes/system/status/+page.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 |
18 | 19 |
20 | -------------------------------------------------------------------------------- /interface/src/routes/system/status/+page.ts: -------------------------------------------------------------------------------- 1 | import type { PageLoad } from './$types'; 2 | 3 | export const load = (async () => { 4 | return { title: 'System Status' }; 5 | }) satisfies PageLoad; 6 | -------------------------------------------------------------------------------- /interface/src/routes/system/update/+page.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 |
19 | {#if page.data.features.download_firmware && (!page.data.features.security || $user.admin)} 20 | 21 | {/if} 22 | 23 | {#if page.data.features.upload_firmware && (!page.data.features.security || $user.admin)} 24 | 25 | {/if} 26 |
27 | -------------------------------------------------------------------------------- /interface/src/routes/system/update/+page.ts: -------------------------------------------------------------------------------- 1 | import type { PageLoad } from './$types'; 2 | 3 | export const load = (async () => { 4 | return { title: 'Firmware Update' }; 5 | }) satisfies PageLoad; 6 | -------------------------------------------------------------------------------- /interface/src/routes/system/update/UploadFirmware.svelte: -------------------------------------------------------------------------------- 1 | 45 | 46 | 47 | {#snippet icon()} 48 | 49 | {/snippet} 50 | {#snippet title()} 51 | Upload Firmware 52 | {/snippet} 53 |
54 | 55 | Uploading a new firmware (.bin) file will replace the existing firmware. You may upload a 57 | (.md5) file first to verify the uploaded firmware. 59 |
60 | 61 | 69 |
70 | -------------------------------------------------------------------------------- /interface/src/routes/user/+page.ts: -------------------------------------------------------------------------------- 1 | import type { PageLoad } from './$types'; 2 | 3 | export const load = (async () => { 4 | return { 5 | title: 'Users' 6 | }; 7 | }) satisfies PageLoad; -------------------------------------------------------------------------------- /interface/src/routes/user/EditUser.svelte: -------------------------------------------------------------------------------- 1 | 57 | 58 | {#if isOpen} 59 | 116 | {/if} 117 | -------------------------------------------------------------------------------- /interface/src/routes/wifi/+page.ts: -------------------------------------------------------------------------------- 1 | import type { PageLoad } from './$types'; 2 | import { goto } from '$app/navigation'; 3 | 4 | export const load = (async () => { 5 | goto('/'); 6 | return; 7 | }) satisfies PageLoad; 8 | -------------------------------------------------------------------------------- /interface/src/routes/wifi/ap/+page.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 |
16 | 17 |
18 | -------------------------------------------------------------------------------- /interface/src/routes/wifi/ap/+page.ts: -------------------------------------------------------------------------------- 1 | import type { PageLoad } from './$types'; 2 | 3 | export const load = (async () => { 4 | return { 5 | title: 'Access Point' 6 | }; 7 | }) satisfies PageLoad; 8 | -------------------------------------------------------------------------------- /interface/src/routes/wifi/sta/+page.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 |
16 | 17 |
18 | -------------------------------------------------------------------------------- /interface/src/routes/wifi/sta/+page.ts: -------------------------------------------------------------------------------- 1 | import type { PageLoad } from './$types'; 2 | 3 | export const load = (async () => { 4 | return { 5 | title: 'WiFi Station' 6 | }; 7 | }) satisfies PageLoad; 8 | -------------------------------------------------------------------------------- /interface/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theelims/ESP32-sveltekit/a9e8cd695fa0edecba9806d53b6a5e9c47b9a16f/interface/static/favicon.png -------------------------------------------------------------------------------- /interface/static/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ESP32 SvelteKit", 3 | "icons": [ 4 | { 5 | "src": "/favicon.png", 6 | "sizes": "48x48 72x72 96x96 128x128 256x256" 7 | } 8 | ], 9 | "start_url": "/", 10 | "display": "fullscreen", 11 | "orientation": "any" 12 | } 13 | -------------------------------------------------------------------------------- /interface/svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-static'; 2 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | const config = { 6 | // Consult https://kit.svelte.dev/docs/integrations#preprocessors 7 | // for more information about preprocessors 8 | preprocess: vitePreprocess(), 9 | 10 | kit: { 11 | adapter: adapter({ 12 | pages: 'build', 13 | assets: 'build', 14 | fallback: 'index.html', 15 | precompress: false, 16 | strict: true 17 | }) 18 | //prerender: { default: true }, 19 | } 20 | }; 21 | 22 | export default config; 23 | -------------------------------------------------------------------------------- /interface/tailwind.config.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ['./src/**/*.{html,js,svelte,ts}'], 4 | theme: { 5 | extend: {} 6 | }, 7 | plugins: [require('daisyui')], 8 | daisyui: { 9 | themes: ['corporate', 'business'], 10 | darkTheme: 'business' 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /interface/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "strict": true 12 | } 13 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias 14 | // 15 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes 16 | // from the referenced tsconfig.json - TypeScript does not merge them in 17 | } 18 | -------------------------------------------------------------------------------- /interface/vite-plugin-littlefs.ts: -------------------------------------------------------------------------------- 1 | import type { UserConfig, Plugin } from 'vite'; 2 | 3 | export default function viteLittleFS(): Plugin[] { 4 | return [ 5 | { 6 | name: 'vite-plugin-littlefs', 7 | enforce: 'post', 8 | apply: 'build', 9 | 10 | async config(config, _configEnv) { 11 | const { assetFileNames, chunkFileNames, entryFileNames } = 12 | config.build?.rollupOptions?.output; 13 | 14 | // Handle Server-build + Client Assets 15 | config.build.rollupOptions.output = { 16 | ...config.build?.rollupOptions?.output, 17 | assetFileNames: assetFileNames.replace('.[hash]', '') 18 | } 19 | 20 | // Handle Client-build 21 | if (config.build?.rollupOptions?.output.chunkFileNames.includes('hash')) { 22 | 23 | config.build.rollupOptions.output = { 24 | ...config.build?.rollupOptions?.output, 25 | chunkFileNames: chunkFileNames.replace('.[hash]', ''), 26 | entryFileNames: entryFileNames.replace('.[hash]', ''), 27 | } 28 | } 29 | } 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /interface/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { sveltekit } from '@sveltejs/kit/vite'; 2 | import type { UserConfig } from 'vite'; 3 | import Icons from 'unplugin-icons/vite'; 4 | import viteLittleFS from './vite-plugin-littlefs'; 5 | 6 | const config: UserConfig = { 7 | plugins: [ 8 | sveltekit(), 9 | Icons({ 10 | compiler: 'svelte' 11 | }), 12 | // Shorten file names for LittleFS 32 char limit 13 | viteLittleFS() 14 | ], 15 | server: { 16 | proxy: { 17 | // Proxying REST: http://localhost:5173/rest/bar -> http://192.168.1.83/rest/bar 18 | '/rest': { 19 | target: 'http://192.168.1.107', 20 | changeOrigin: true 21 | }, 22 | // Proxying websockets ws://localhost:5173/ws -> ws://192.168.1.83/ws 23 | '/ws': { 24 | target: 'ws://192.168.1.107', 25 | changeOrigin: true, 26 | ws: true 27 | } 28 | } 29 | } 30 | }; 31 | 32 | export default config; 33 | -------------------------------------------------------------------------------- /lib/PsychicHttp/.gitignore: -------------------------------------------------------------------------------- 1 | **.vscode 2 | **.pio 3 | **.DS_Store 4 | .pioenvs 5 | .clang_complete 6 | .gcc-flags.json 7 | # Compiled Object files 8 | *.slo 9 | *.lo 10 | *.o 11 | *.obj 12 | # Precompiled Headers 13 | *.gch 14 | *.pch 15 | # Compiled Dynamic libraries 16 | *.so 17 | *.dylib 18 | *.dll 19 | # Fortran module files 20 | *.mod 21 | # Compiled Static libraries 22 | *.lai 23 | *.la 24 | *.a 25 | *.lib 26 | # Executables 27 | *.exe 28 | *.out 29 | *.app 30 | # Visual Studio/VisualMicro stuff 31 | Visual\ Micro 32 | *.sdf 33 | *.opensdf 34 | *.suo 35 | .pioenvs 36 | .piolibdeps 37 | .pio 38 | .vscode/c_cpp_properties.json 39 | .vscode/launch.json 40 | .vscode/settings.json 41 | .vscode/.browse.c_cpp.db* 42 | .vscode/ipch 43 | /psychic-http-loadtest.log 44 | /psychic-websocket-loadtest.log 45 | examples/platformio/lib/PsychicHttp 46 | benchmark/.~lock.comparison.ods# 47 | benchmark/psychic-http-loadtest.log 48 | .$request flow.drawio.bkp 49 | .$request flow.drawio.dtmp 50 | benchmark/package-lock.json 51 | benchmark/node_modules 52 | src/secret.h 53 | benchmark/psychichttp/src/_secret.h 54 | benchmark/psychichttps/src/_secret.h 55 | examples/platformio/src/_secret.h 56 | examples/arduino/src/_secret.h 57 | src/cookie.txt 58 | examples/websockets/lib/PsychicHttp 59 | examples/websockets/src/_secret.h 60 | /build 61 | -------------------------------------------------------------------------------- /lib/PsychicHttp/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v1.2.1 2 | 3 | * Fix bug with missing include preventing the HTTPS server from compiling. 4 | 5 | # v1.2 6 | 7 | * Added TemplatePrinter from https://github.com/Chris--A/PsychicHttp/tree/templatePrint 8 | * Support using as ESP IDF component 9 | * Optional using https server in ESP IDF 10 | * Fixed bug with headers 11 | * Add ESP IDF example + CI script 12 | * Added Arduino Captive Portal example and OTAUpdate from @06GitHub 13 | * HTTPS fix for ESP-IDF v5.0.2+ from @06GitHub 14 | * lots of bugfixes from @mathieucarbou 15 | 16 | Thanks to @Chris--A, @06GitHub, and @dzungpv for your contributions. 17 | 18 | # v1.1 19 | 20 | * Changed the internal structure to support request handlers on endpoints and generic requests that do not match an endpoint 21 | * websockets, uploads, etc should now create an appropriate handler and attach to an endpoint with the server.on() syntax 22 | * Added PsychicClient to abstract away some of the internals of ESP-IDF sockets + add convenience 23 | * onOpen and onClose callbacks have changed as a result 24 | * Added support for EventSource / SSE 25 | * Added support for multipart file uploads 26 | * changed getParam() to return a PsychicWebParameter in line with ESPAsyncWebserver 27 | * Renamed various classes / files: 28 | * PsychicHttpFileResponse -> PsychicFileResponse 29 | * PsychicHttpServerEndpoint -> PsychicEndpoint 30 | * PsychicHttpServerRequest -> PsychicRequest 31 | * PsychicHttpServerResponse -> PsychicResponse 32 | * PsychicHttpWebsocket.h -> PsychicWebSocket.h 33 | * Websocket => WebSocket 34 | * Quite a few bugfixes from the community. Thank you @glennsky, @gb88, @KastanEr, @kstam, and @zekageri -------------------------------------------------------------------------------- /lib/PsychicHttp/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2024 Jeremy Poulter, Zachary Smith, and Mathieu Carbou 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /lib/PsychicHttp/RELEASE.md: -------------------------------------------------------------------------------- 1 | * Update CHANGELOG 2 | * Bump version in library.json 3 | * Bump version in library.properties 4 | * Make new release + tag 5 | * this will get pulled in automatically by Arduino Library Indexer 6 | * run ```pio pkg publish``` to publish to Platform.io -------------------------------------------------------------------------------- /lib/PsychicHttp/library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PsychicHttp", 3 | "version": "1.2.1", 4 | "description": "Arduino style wrapper around ESP-IDF HTTP library. HTTP server with SSL + websockets. Works on esp32 and probably esp8266", 5 | "keywords": "network,http,https,tcp,ssl,tls,websocket,espasyncwebserver", 6 | "repository": 7 | { 8 | "type": "git", 9 | "url": "https://github.com/hoeken/PsychicHttp" 10 | }, 11 | "authors": 12 | [ 13 | { 14 | "name": "Zach Hoeken", 15 | "email": "hoeken@gmail.com", 16 | "maintainer": true 17 | } 18 | ], 19 | "license" : "MIT", 20 | "examples": [ 21 | { 22 | "name": "platformio", 23 | "base": "examples/platformio", 24 | "files": [ 25 | "src/main.cpp" 26 | ] 27 | } 28 | ], 29 | "frameworks": "arduino", 30 | "platforms": "espressif32", 31 | "dependencies": [ 32 | { 33 | "owner": "bblanchon", 34 | "name": "ArduinoJson", 35 | "version": "^7.0.4" 36 | }, 37 | { 38 | "owner": "plageoj", 39 | "name" : "UrlEncode", 40 | "version" : "^1.0.1" 41 | } 42 | ], 43 | "export": { 44 | "include": [ 45 | "examples/platformio", 46 | "src", 47 | "library.json", 48 | "library.properties", 49 | "LICENSE", 50 | "README.md" 51 | ] 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/PsychicHttp/src/ChunkPrinter.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "ChunkPrinter.h" 3 | 4 | ChunkPrinter::ChunkPrinter(PsychicResponse *response, uint8_t *buffer, size_t len) : 5 | _response(response), 6 | _buffer(buffer), 7 | _length(len), 8 | _pos(0) 9 | {} 10 | 11 | ChunkPrinter::~ChunkPrinter() 12 | { 13 | flush(); 14 | } 15 | 16 | size_t ChunkPrinter::write(uint8_t c) 17 | { 18 | esp_err_t err; 19 | 20 | //if we're full, send a chunk 21 | if (_pos == _length) 22 | { 23 | _pos = 0; 24 | err = _response->sendChunk(_buffer, _length); 25 | 26 | if (err != ESP_OK) 27 | return 0; 28 | } 29 | 30 | _buffer[_pos] = c; 31 | _pos++; 32 | return 1; 33 | } 34 | 35 | size_t ChunkPrinter::write(const uint8_t *buffer, size_t size) 36 | { 37 | size_t written = 0; 38 | 39 | while (written < size) 40 | { 41 | size_t space = _length - _pos; 42 | size_t blockSize = std::min(space, size - written); 43 | 44 | memcpy(_buffer + _pos, buffer + written, blockSize); 45 | _pos += blockSize; 46 | 47 | if (_pos == _length) 48 | { 49 | _pos = 0; 50 | 51 | if (_response->sendChunk(_buffer, _length) != ESP_OK) 52 | return written; 53 | } 54 | written += blockSize; //Update if sent correctly. 55 | } 56 | return written; 57 | } 58 | 59 | void ChunkPrinter::flush() 60 | { 61 | if (_pos) 62 | { 63 | _response->sendChunk(_buffer, _pos); 64 | _pos = 0; 65 | } 66 | } 67 | 68 | size_t ChunkPrinter::copyFrom(Stream &stream) 69 | { 70 | size_t count = 0; 71 | 72 | while (stream.available()){ 73 | 74 | if (_pos == _length) 75 | { 76 | _response->sendChunk(_buffer, _length); 77 | _pos = 0; 78 | } 79 | 80 | size_t readBytes = stream.readBytes(_buffer + _pos, _length - _pos); 81 | _pos += readBytes; 82 | count += readBytes; 83 | } 84 | return count; 85 | } -------------------------------------------------------------------------------- /lib/PsychicHttp/src/ChunkPrinter.h: -------------------------------------------------------------------------------- 1 | #ifndef ChunkPrinter_h 2 | #define ChunkPrinter_h 3 | 4 | #include "PsychicResponse.h" 5 | #include 6 | 7 | class ChunkPrinter : public Print 8 | { 9 | private: 10 | PsychicResponse *_response; 11 | uint8_t *_buffer; 12 | size_t _length; 13 | size_t _pos; 14 | 15 | public: 16 | ChunkPrinter(PsychicResponse *response, uint8_t *buffer, size_t len); 17 | ~ChunkPrinter(); 18 | 19 | size_t write(uint8_t c) override; 20 | size_t write(const uint8_t *buffer, size_t size) override; 21 | 22 | size_t copyFrom(Stream &stream); 23 | 24 | void flush() override; 25 | }; 26 | 27 | #endif -------------------------------------------------------------------------------- /lib/PsychicHttp/src/PsychicClient.cpp: -------------------------------------------------------------------------------- 1 | #include "PsychicClient.h" 2 | #include "PsychicHttpServer.h" 3 | #include 4 | 5 | PsychicClient::PsychicClient(httpd_handle_t server, int socket) : 6 | _server(server), 7 | _socket(socket), 8 | _friend(NULL), 9 | isNew(false) 10 | {} 11 | 12 | PsychicClient::~PsychicClient() { 13 | } 14 | 15 | httpd_handle_t PsychicClient::server() { 16 | return _server; 17 | } 18 | 19 | int PsychicClient::socket() { 20 | return _socket; 21 | } 22 | 23 | // I'm not sure this is entirely safe to call. I was having issues with race conditions when highly loaded using this. 24 | esp_err_t PsychicClient::close() 25 | { 26 | esp_err_t err = httpd_sess_trigger_close(_server, _socket); 27 | //PsychicHttpServer::closeCallback(_server, _socket); // call this immediately so the client is taken off the list. 28 | 29 | return err; 30 | } 31 | 32 | IPAddress PsychicClient::localIP() 33 | { 34 | IPAddress address(0,0,0,0); 35 | 36 | char ipstr[INET6_ADDRSTRLEN]; 37 | struct sockaddr_in6 addr; // esp_http_server uses IPv6 addressing 38 | socklen_t addr_size = sizeof(addr); 39 | 40 | if (getsockname(_socket, (struct sockaddr *)&addr, &addr_size) < 0) { 41 | ESP_LOGE(PH_TAG, "Error getting client IP"); 42 | return address; 43 | } 44 | 45 | // Convert to IPv4 string 46 | inet_ntop(AF_INET, &addr.sin6_addr.un.u32_addr[3], ipstr, sizeof(ipstr)); 47 | //ESP_LOGD(PH_TAG, "Client Local IP => %s", ipstr); 48 | address.fromString(ipstr); 49 | 50 | return address; 51 | } 52 | 53 | IPAddress PsychicClient::remoteIP() 54 | { 55 | IPAddress address(0,0,0,0); 56 | 57 | char ipstr[INET6_ADDRSTRLEN]; 58 | struct sockaddr_in6 addr; // esp_http_server uses IPv6 addressing 59 | socklen_t addr_size = sizeof(addr); 60 | 61 | if (getpeername(_socket, (struct sockaddr *)&addr, &addr_size) < 0) { 62 | ESP_LOGE(PH_TAG, "Error getting client IP"); 63 | return address; 64 | } 65 | 66 | // Convert to IPv4 string 67 | inet_ntop(AF_INET, &addr.sin6_addr.un.u32_addr[3], ipstr, sizeof(ipstr)); 68 | //ESP_LOGD(PH_TAG, "Client Remote IP => %s", ipstr); 69 | address.fromString(ipstr); 70 | 71 | return address; 72 | } -------------------------------------------------------------------------------- /lib/PsychicHttp/src/PsychicClient.h: -------------------------------------------------------------------------------- 1 | #ifndef PsychicClient_h 2 | #define PsychicClient_h 3 | 4 | #include "PsychicCore.h" 5 | 6 | /* 7 | * PsychicClient :: Generic wrapper around the ESP-IDF socket 8 | */ 9 | 10 | class PsychicClient { 11 | protected: 12 | httpd_handle_t _server; 13 | int _socket; 14 | 15 | public: 16 | PsychicClient(httpd_handle_t server, int socket); 17 | ~PsychicClient(); 18 | 19 | //no idea if this is the right way to do it or not, but lets see. 20 | //pointer to our derived class (eg. PsychicWebSocketConnection) 21 | void *_friend; 22 | 23 | bool isNew = false; 24 | 25 | bool operator==(PsychicClient& rhs) const { return _socket == rhs.socket(); } 26 | 27 | httpd_handle_t server(); 28 | int socket(); 29 | esp_err_t close(); 30 | 31 | IPAddress localIP(); 32 | IPAddress remoteIP(); 33 | }; 34 | 35 | #endif -------------------------------------------------------------------------------- /lib/PsychicHttp/src/PsychicCore.h: -------------------------------------------------------------------------------- 1 | #ifndef PsychicCore_h 2 | #define PsychicCore_h 3 | 4 | #define PH_TAG "🔮" 5 | 6 | // version numbers 7 | #define PSYCHIC_HTTP_VERSION_MAJOR 1 8 | #define PSYCHIC_HTTP_VERSION_MINOR 1 9 | #define PSYCHIC_HTTP_VERSION_PATCH 0 10 | 11 | #ifndef MAX_COOKIE_SIZE 12 | #define MAX_COOKIE_SIZE 512 13 | #endif 14 | 15 | #ifndef FILE_CHUNK_SIZE 16 | #define FILE_CHUNK_SIZE 8 * 1024 17 | #endif 18 | 19 | #ifndef STREAM_CHUNK_SIZE 20 | #define STREAM_CHUNK_SIZE 1024 21 | #endif 22 | 23 | #ifndef MAX_UPLOAD_SIZE 24 | #define MAX_UPLOAD_SIZE (2048 * 1024) // 2MB 25 | #endif 26 | 27 | #ifndef MAX_REQUEST_BODY_SIZE 28 | #define MAX_REQUEST_BODY_SIZE (16 * 1024) // 16K 29 | #endif 30 | 31 | #ifdef ARDUINO 32 | #include 33 | #endif 34 | 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include "esp_random.h" 40 | #include "MD5Builder.h" 41 | #include 42 | #include "FS.h" 43 | #include 44 | 45 | enum HTTPAuthMethod 46 | { 47 | BASIC_AUTH, 48 | DIGEST_AUTH 49 | }; 50 | 51 | String urlDecode(const char *encoded); 52 | 53 | class PsychicHttpServer; 54 | class PsychicRequest; 55 | class PsychicWebSocketRequest; 56 | class PsychicClient; 57 | 58 | // filter function definition 59 | typedef std::function PsychicRequestFilterFunction; 60 | 61 | // client connect callback 62 | typedef std::function PsychicClientCallback; 63 | 64 | // callback definitions 65 | typedef std::function PsychicHttpRequestCallback; 66 | typedef std::function PsychicJsonRequestCallback; 67 | 68 | struct HTTPHeader 69 | { 70 | char *field; 71 | char *value; 72 | }; 73 | 74 | class DefaultHeaders 75 | { 76 | std::list _headers; 77 | 78 | public: 79 | DefaultHeaders() {} 80 | 81 | void addHeader(const String &field, const String &value) 82 | { 83 | addHeader(field.c_str(), value.c_str()); 84 | } 85 | 86 | void addHeader(const char *field, const char *value) 87 | { 88 | HTTPHeader header; 89 | 90 | // these are just going to stick around forever. 91 | header.field = (char *)malloc(strlen(field) + 1); 92 | header.value = (char *)malloc(strlen(value) + 1); 93 | 94 | strlcpy(header.field, field, strlen(field) + 1); 95 | strlcpy(header.value, value, strlen(value) + 1); 96 | 97 | _headers.push_back(header); 98 | } 99 | 100 | const std::list &getHeaders() { return _headers; } 101 | 102 | // delete the copy constructor, singleton class 103 | DefaultHeaders(DefaultHeaders const &) = delete; 104 | DefaultHeaders &operator=(DefaultHeaders const &) = delete; 105 | 106 | // single static class interface 107 | static DefaultHeaders &Instance() 108 | { 109 | static DefaultHeaders instance; 110 | return instance; 111 | } 112 | }; 113 | 114 | #endif // PsychicCore_h -------------------------------------------------------------------------------- /lib/PsychicHttp/src/PsychicEndpoint.cpp: -------------------------------------------------------------------------------- 1 | #include "PsychicEndpoint.h" 2 | #include "PsychicHttpServer.h" 3 | 4 | PsychicEndpoint::PsychicEndpoint() : 5 | _server(NULL), 6 | _uri(""), 7 | _method(HTTP_GET), 8 | _handler(NULL) 9 | { 10 | } 11 | 12 | PsychicEndpoint::PsychicEndpoint(PsychicHttpServer *server, http_method method, const char * uri) : 13 | _server(server), 14 | _uri(uri), 15 | _method(method), 16 | _handler(NULL) 17 | { 18 | } 19 | 20 | PsychicEndpoint * PsychicEndpoint::setHandler(PsychicHandler *handler) 21 | { 22 | //clean up old / default handler 23 | if (_handler != NULL) 24 | delete _handler; 25 | 26 | //get our new pointer 27 | _handler = handler; 28 | 29 | //keep a pointer to the server 30 | _handler->_server = _server; 31 | 32 | return this; 33 | } 34 | 35 | PsychicHandler * PsychicEndpoint::handler() 36 | { 37 | return _handler; 38 | } 39 | 40 | String PsychicEndpoint::uri() { 41 | return _uri; 42 | } 43 | 44 | esp_err_t PsychicEndpoint::requestCallback(httpd_req_t *req) 45 | { 46 | #ifdef ENABLE_ASYNC 47 | if (is_on_async_worker_thread() == false) { 48 | if (submit_async_req(req, PsychicEndpoint::requestCallback) == ESP_OK) { 49 | return ESP_OK; 50 | } else { 51 | httpd_resp_set_status(req, "503 Busy"); 52 | httpd_resp_sendstr(req, "No workers available. Server busy."); 53 | return ESP_OK; 54 | } 55 | } 56 | #endif 57 | 58 | PsychicEndpoint *self = (PsychicEndpoint *)req->user_ctx; 59 | PsychicHandler *handler = self->handler(); 60 | PsychicRequest request(self->_server, req); 61 | 62 | //make sure we have a handler 63 | if (handler != NULL) 64 | { 65 | if (handler->filter(&request) && handler->canHandle(&request)) 66 | { 67 | //check our credentials 68 | if (handler->needsAuthentication(&request)) 69 | return handler->authenticate(&request); 70 | 71 | //pass it to our handler 72 | return handler->handleRequest(&request); 73 | } 74 | //pass it to our generic handlers 75 | else 76 | return PsychicHttpServer::notFoundHandler(req, HTTPD_500_INTERNAL_SERVER_ERROR); 77 | } 78 | else 79 | return request.reply(500, "text/html", "No handler registered."); 80 | } 81 | 82 | PsychicEndpoint* PsychicEndpoint::setFilter(PsychicRequestFilterFunction fn) { 83 | _handler->setFilter(fn); 84 | return this; 85 | } 86 | 87 | PsychicEndpoint* PsychicEndpoint::setAuthentication(const char *username, const char *password, HTTPAuthMethod method, const char *realm, const char *authFailMsg) { 88 | _handler->setAuthentication(username, password, method, realm, authFailMsg); 89 | return this; 90 | }; -------------------------------------------------------------------------------- /lib/PsychicHttp/src/PsychicEndpoint.h: -------------------------------------------------------------------------------- 1 | #ifndef PsychicEndpoint_h 2 | #define PsychicEndpoint_h 3 | 4 | #include "PsychicCore.h" 5 | 6 | class PsychicHandler; 7 | 8 | #ifdef ENABLE_ASYNC 9 | #include "async_worker.h" 10 | #endif 11 | 12 | class PsychicEndpoint 13 | { 14 | friend PsychicHttpServer; 15 | 16 | private: 17 | PsychicHttpServer *_server; 18 | String _uri; 19 | http_method _method; 20 | PsychicHandler *_handler; 21 | 22 | public: 23 | PsychicEndpoint(); 24 | PsychicEndpoint(PsychicHttpServer *server, http_method method, const char * uri); 25 | 26 | PsychicEndpoint *setHandler(PsychicHandler *handler); 27 | PsychicHandler *handler(); 28 | 29 | PsychicEndpoint* setFilter(PsychicRequestFilterFunction fn); 30 | PsychicEndpoint* setAuthentication(const char *username, const char *password, HTTPAuthMethod method = BASIC_AUTH, const char *realm = "", const char *authFailMsg = ""); 31 | 32 | String uri(); 33 | 34 | static esp_err_t requestCallback(httpd_req_t *req); 35 | }; 36 | 37 | #endif // PsychicEndpoint_h -------------------------------------------------------------------------------- /lib/PsychicHttp/src/PsychicEventSource.h: -------------------------------------------------------------------------------- 1 | /* 2 | Asynchronous WebServer library for Espressif MCUs 3 | 4 | Copyright (c) 2016 Hristo Gochkov. All rights reserved. 5 | 6 | This library is free software; you can redistribute it and/or 7 | modify it under the terms of the GNU Lesser General Public 8 | License as published by the Free Software Foundation; either 9 | version 2.1 of the License, or (at your option) any later version. 10 | 11 | This library is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | Lesser General Public License for more details. 15 | 16 | You should have received a copy of the GNU Lesser General Public 17 | License along with this library; if not, write to the Free Software 18 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 19 | */ 20 | #ifndef PsychicEventSource_H_ 21 | #define PsychicEventSource_H_ 22 | 23 | #include "PsychicCore.h" 24 | #include "PsychicHandler.h" 25 | #include "PsychicClient.h" 26 | #include "PsychicResponse.h" 27 | 28 | class PsychicEventSource; 29 | class PsychicEventSourceResponse; 30 | class PsychicEventSourceClient; 31 | class PsychicResponse; 32 | 33 | typedef std::function PsychicEventSourceClientCallback; 34 | 35 | class PsychicEventSourceClient : public PsychicClient { 36 | friend PsychicEventSource; 37 | 38 | protected: 39 | uint32_t _lastId; 40 | 41 | public: 42 | PsychicEventSourceClient(PsychicClient *client); 43 | ~PsychicEventSourceClient(); 44 | 45 | uint32_t lastId() const { return _lastId; } 46 | void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0); 47 | void sendEvent(const char *event); 48 | }; 49 | 50 | class PsychicEventSource : public PsychicHandler { 51 | private: 52 | PsychicEventSourceClientCallback _onOpen; 53 | PsychicEventSourceClientCallback _onClose; 54 | 55 | public: 56 | PsychicEventSource(); 57 | ~PsychicEventSource(); 58 | 59 | PsychicEventSourceClient * getClient(int socket) override; 60 | PsychicEventSourceClient * getClient(PsychicClient *client) override; 61 | void addClient(PsychicClient *client) override; 62 | void removeClient(PsychicClient *client) override; 63 | void openCallback(PsychicClient *client) override; 64 | void closeCallback(PsychicClient *client) override; 65 | 66 | PsychicEventSource *onOpen(PsychicEventSourceClientCallback fn); 67 | PsychicEventSource *onClose(PsychicEventSourceClientCallback fn); 68 | 69 | esp_err_t handleRequest(PsychicRequest *request) override final; 70 | 71 | void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0); 72 | }; 73 | 74 | class PsychicEventSourceResponse: public PsychicResponse { 75 | public: 76 | PsychicEventSourceResponse(PsychicRequest *request); 77 | virtual esp_err_t send() override; 78 | }; 79 | 80 | String generateEventMessage(const char *message, const char *event, uint32_t id, uint32_t reconnect); 81 | 82 | #endif /* PsychicEventSource_H_ */ -------------------------------------------------------------------------------- /lib/PsychicHttp/src/PsychicFileResponse.h: -------------------------------------------------------------------------------- 1 | #ifndef PsychicFileResponse_h 2 | #define PsychicFileResponse_h 3 | 4 | #include "PsychicCore.h" 5 | #include "PsychicResponse.h" 6 | 7 | class PsychicRequest; 8 | 9 | class PsychicFileResponse: public PsychicResponse 10 | { 11 | using File = fs::File; 12 | using FS = fs::FS; 13 | private: 14 | File _content; 15 | void _setContentType(const String& path); 16 | public: 17 | PsychicFileResponse(PsychicRequest *request, FS &fs, const String& path, const String& contentType=String(), bool download=false); 18 | PsychicFileResponse(PsychicRequest *request, File content, const String& path, const String& contentType=String(), bool download=false); 19 | ~PsychicFileResponse(); 20 | esp_err_t send(); 21 | }; 22 | 23 | #endif // PsychicFileResponse_h -------------------------------------------------------------------------------- /lib/PsychicHttp/src/PsychicHandler.cpp: -------------------------------------------------------------------------------- 1 | #include "PsychicHandler.h" 2 | 3 | PsychicHandler::PsychicHandler() : 4 | _filter(NULL), 5 | _server(NULL), 6 | _username(""), 7 | _password(""), 8 | _method(DIGEST_AUTH), 9 | _realm(""), 10 | _authFailMsg(""), 11 | _subprotocol("") 12 | {} 13 | 14 | PsychicHandler::~PsychicHandler() { 15 | // actual PsychicClient deletion handled by PsychicServer 16 | // for (PsychicClient *client : _clients) 17 | // delete(client); 18 | _clients.clear(); 19 | } 20 | 21 | PsychicHandler* PsychicHandler::setFilter(PsychicRequestFilterFunction fn) { 22 | _filter = fn; 23 | return this; 24 | } 25 | 26 | bool PsychicHandler::filter(PsychicRequest *request){ 27 | return _filter == NULL || _filter(request); 28 | } 29 | 30 | void PsychicHandler::setSubprotocol(const String& subprotocol) { 31 | this->_subprotocol = subprotocol; 32 | } 33 | const char* PsychicHandler::getSubprotocol() const { 34 | return _subprotocol.c_str(); 35 | } 36 | 37 | PsychicHandler* PsychicHandler::setAuthentication(const char *username, const char *password, HTTPAuthMethod method, const char *realm, const char *authFailMsg) { 38 | _username = String(username); 39 | _password = String(password); 40 | _method = method; 41 | _realm = String(realm); 42 | _authFailMsg = String(authFailMsg); 43 | return this; 44 | }; 45 | 46 | bool PsychicHandler::needsAuthentication(PsychicRequest *request) { 47 | return (_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str()); 48 | } 49 | 50 | esp_err_t PsychicHandler::authenticate(PsychicRequest *request) { 51 | return request->requestAuthentication(_method, _realm.c_str(), _authFailMsg.c_str()); 52 | } 53 | 54 | PsychicClient * PsychicHandler::checkForNewClient(PsychicClient *client) 55 | { 56 | PsychicClient *c = PsychicHandler::getClient(client); 57 | if (c == NULL) 58 | { 59 | c = client; 60 | addClient(c); 61 | c->isNew = true; 62 | } 63 | else 64 | c->isNew = false; 65 | 66 | return c; 67 | } 68 | 69 | void PsychicHandler::checkForClosedClient(PsychicClient *client) 70 | { 71 | if (hasClient(client)) 72 | { 73 | closeCallback(client); 74 | removeClient(client); 75 | } 76 | } 77 | 78 | void PsychicHandler::addClient(PsychicClient *client) { 79 | _clients.push_back(client); 80 | } 81 | 82 | void PsychicHandler::removeClient(PsychicClient *client) { 83 | _clients.remove(client); 84 | } 85 | 86 | PsychicClient * PsychicHandler::getClient(int socket) 87 | { 88 | //make sure the server has it too. 89 | if (!_server->hasClient(socket)) 90 | return NULL; 91 | 92 | //what about us? 93 | for (PsychicClient *client : _clients) 94 | if (client->socket() == socket) 95 | return client; 96 | 97 | //nothing found. 98 | return NULL; 99 | } 100 | 101 | PsychicClient * PsychicHandler::getClient(PsychicClient *client) { 102 | return PsychicHandler::getClient(client->socket()); 103 | } 104 | 105 | bool PsychicHandler::hasClient(PsychicClient *socket) { 106 | return PsychicHandler::getClient(socket) != NULL; 107 | } 108 | 109 | const std::list& PsychicHandler::getClientList() { 110 | return _clients; 111 | } -------------------------------------------------------------------------------- /lib/PsychicHttp/src/PsychicHandler.h: -------------------------------------------------------------------------------- 1 | #ifndef PsychicHandler_h 2 | #define PsychicHandler_h 3 | 4 | #include "PsychicCore.h" 5 | #include "PsychicRequest.h" 6 | 7 | class PsychicEndpoint; 8 | class PsychicHttpServer; 9 | 10 | /* 11 | * HANDLER :: Can be attached to any endpoint or as a generic request handler. 12 | */ 13 | 14 | class PsychicHandler { 15 | friend PsychicEndpoint; 16 | 17 | protected: 18 | PsychicRequestFilterFunction _filter; 19 | PsychicHttpServer *_server; 20 | 21 | String _username; 22 | String _password; 23 | HTTPAuthMethod _method; 24 | String _realm; 25 | String _authFailMsg; 26 | 27 | String _subprotocol; 28 | 29 | std::list _clients; 30 | 31 | public: 32 | PsychicHandler(); 33 | virtual ~PsychicHandler(); 34 | 35 | PsychicHandler* setFilter(PsychicRequestFilterFunction fn); 36 | bool filter(PsychicRequest *request); 37 | 38 | PsychicHandler* setAuthentication(const char *username, const char *password, HTTPAuthMethod method = BASIC_AUTH, const char *realm = "", const char *authFailMsg = ""); 39 | bool needsAuthentication(PsychicRequest *request); 40 | esp_err_t authenticate(PsychicRequest *request); 41 | 42 | virtual bool isWebSocket() { return false; }; 43 | 44 | void setSubprotocol(const String& subprotocol); 45 | const char* getSubprotocol() const; 46 | 47 | PsychicClient * checkForNewClient(PsychicClient *client); 48 | void checkForClosedClient(PsychicClient *client); 49 | 50 | virtual void addClient(PsychicClient *client); 51 | virtual void removeClient(PsychicClient *client); 52 | virtual PsychicClient * getClient(int socket); 53 | virtual PsychicClient * getClient(PsychicClient *client); 54 | virtual void openCallback(PsychicClient *client) {}; 55 | virtual void closeCallback(PsychicClient *client) {}; 56 | 57 | bool hasClient(PsychicClient *client); 58 | int count() { return _clients.size(); }; 59 | const std::list& getClientList(); 60 | 61 | //derived classes must implement these functions 62 | virtual bool canHandle(PsychicRequest *request) { return true; }; 63 | virtual esp_err_t handleRequest(PsychicRequest *request) = 0; 64 | }; 65 | 66 | #endif -------------------------------------------------------------------------------- /lib/PsychicHttp/src/PsychicHttp.h: -------------------------------------------------------------------------------- 1 | #ifndef PsychicHttp_h 2 | #define PsychicHttp_h 3 | 4 | //#define ENABLE_ASYNC // This is something added in ESP-IDF 5.1.x where each request can be handled in its own thread 5 | 6 | #include 7 | #include "PsychicHttpServer.h" 8 | #include "PsychicRequest.h" 9 | #include "PsychicResponse.h" 10 | #include "PsychicEndpoint.h" 11 | #include "PsychicHandler.h" 12 | #include "PsychicStaticFileHandler.h" 13 | #include "PsychicFileResponse.h" 14 | #include "PsychicStreamResponse.h" 15 | #include "PsychicUploadHandler.h" 16 | #include "PsychicWebSocket.h" 17 | #include "PsychicEventSource.h" 18 | #include "PsychicJson.h" 19 | 20 | #ifdef ENABLE_ASYNC 21 | #include "async_worker.h" 22 | #endif 23 | 24 | #endif /* PsychicHttp_h */ -------------------------------------------------------------------------------- /lib/PsychicHttp/src/PsychicHttpServer.h: -------------------------------------------------------------------------------- 1 | #ifndef PsychicHttpServer_h 2 | #define PsychicHttpServer_h 3 | 4 | #include "PsychicCore.h" 5 | #include "PsychicClient.h" 6 | #include "PsychicHandler.h" 7 | 8 | class PsychicEndpoint; 9 | class PsychicHandler; 10 | class PsychicStaticFileHandler; 11 | 12 | class PsychicHttpServer 13 | { 14 | protected: 15 | bool _use_ssl = false; 16 | std::list _endpoints; 17 | std::list _handlers; 18 | std::list _clients; 19 | 20 | PsychicClientCallback _onOpen; 21 | PsychicClientCallback _onClose; 22 | 23 | esp_err_t _start(); 24 | virtual esp_err_t _startServer(); 25 | 26 | public: 27 | PsychicHttpServer(); 28 | virtual ~PsychicHttpServer(); 29 | 30 | //esp-idf specific stuff 31 | httpd_handle_t server; 32 | httpd_config_t config; 33 | 34 | //some limits on what we will accept 35 | unsigned long maxUploadSize; 36 | unsigned long maxRequestBodySize; 37 | 38 | PsychicEndpoint *defaultEndpoint; 39 | 40 | static void destroy(void *ctx); 41 | 42 | esp_err_t listen(uint16_t port); 43 | 44 | virtual void stop(); 45 | 46 | PsychicHandler& addHandler(PsychicHandler* handler); 47 | void removeHandler(PsychicHandler* handler); 48 | 49 | void addClient(PsychicClient *client); 50 | void removeClient(PsychicClient *client); 51 | PsychicClient* getClient(int socket); 52 | PsychicClient* getClient(httpd_req_t *req); 53 | bool hasClient(int socket); 54 | int count() { return _clients.size(); }; 55 | const std::list& getClientList(); 56 | 57 | PsychicEndpoint* on(const char* uri); 58 | PsychicEndpoint* on(const char* uri, http_method method); 59 | PsychicEndpoint* on(const char* uri, PsychicHandler *handler); 60 | PsychicEndpoint* on(const char* uri, http_method method, PsychicHandler *handler); 61 | PsychicEndpoint* on(const char* uri, PsychicHttpRequestCallback onRequest); 62 | PsychicEndpoint* on(const char* uri, http_method method, PsychicHttpRequestCallback onRequest); 63 | PsychicEndpoint* on(const char* uri, PsychicJsonRequestCallback onRequest); 64 | PsychicEndpoint* on(const char* uri, http_method method, PsychicJsonRequestCallback onRequest); 65 | 66 | static esp_err_t notFoundHandler(httpd_req_t *req, httpd_err_code_t err); 67 | static esp_err_t defaultNotFoundHandler(PsychicRequest *request); 68 | void onNotFound(PsychicHttpRequestCallback fn); 69 | 70 | void onOpen(PsychicClientCallback handler); 71 | void onClose(PsychicClientCallback handler); 72 | static esp_err_t openCallback(httpd_handle_t hd, int sockfd); 73 | static void closeCallback(httpd_handle_t hd, int sockfd); 74 | 75 | PsychicStaticFileHandler* serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_control = NULL); 76 | }; 77 | 78 | bool ON_STA_FILTER(PsychicRequest *request); 79 | bool ON_AP_FILTER(PsychicRequest *request); 80 | 81 | #endif // PsychicHttpServer_h -------------------------------------------------------------------------------- /lib/PsychicHttp/src/PsychicHttpsServer.cpp: -------------------------------------------------------------------------------- 1 | #include "PsychicHttpsServer.h" 2 | 3 | #ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE 4 | 5 | PsychicHttpsServer::PsychicHttpsServer() : PsychicHttpServer() 6 | { 7 | //for a SSL server 8 | ssl_config = HTTPD_SSL_CONFIG_DEFAULT(); 9 | ssl_config.httpd.open_fn = PsychicHttpServer::openCallback; 10 | ssl_config.httpd.close_fn = PsychicHttpServer::closeCallback; 11 | ssl_config.httpd.uri_match_fn = httpd_uri_match_wildcard; 12 | ssl_config.httpd.global_user_ctx = this; 13 | ssl_config.httpd.global_user_ctx_free_fn = destroy; 14 | ssl_config.httpd.max_uri_handlers = 20; 15 | 16 | // each SSL connection takes about 45kb of heap 17 | // a barebones sketch with PsychicHttp has ~150kb of heap available 18 | // if we set it higher than 2 and use all the connections, we get lots of memory errors. 19 | // not to mention there is no heap left over for the program itself. 20 | ssl_config.httpd.max_open_sockets = 2; 21 | } 22 | 23 | PsychicHttpsServer::~PsychicHttpsServer() {} 24 | 25 | esp_err_t PsychicHttpsServer::listen(uint16_t port, const char *cert, const char *private_key) 26 | { 27 | this->_use_ssl = true; 28 | 29 | this->ssl_config.port_secure = port; 30 | 31 | #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 2) 32 | this->ssl_config.servercert = (uint8_t *)cert; 33 | this->ssl_config.servercert_len = strlen(cert)+1; 34 | #else 35 | this->ssl_config.cacert_pem = (uint8_t *)cert; 36 | this->ssl_config.cacert_len = strlen(cert)+1; 37 | #endif 38 | 39 | this->ssl_config.prvtkey_pem = (uint8_t *)private_key; 40 | this->ssl_config.prvtkey_len = strlen(private_key)+1; 41 | 42 | return this->_start(); 43 | } 44 | 45 | esp_err_t PsychicHttpsServer::_startServer() 46 | { 47 | if (this->_use_ssl) 48 | return httpd_ssl_start(&this->server, &this->ssl_config); 49 | else 50 | return httpd_start(&this->server, &this->config); 51 | } 52 | 53 | void PsychicHttpsServer::stop() 54 | { 55 | if (this->_use_ssl) 56 | httpd_ssl_stop(this->server); 57 | else 58 | httpd_stop(this->server); 59 | } 60 | 61 | #endif // CONFIG_ESP_HTTPS_SERVER_ENABLE -------------------------------------------------------------------------------- /lib/PsychicHttp/src/PsychicHttpsServer.h: -------------------------------------------------------------------------------- 1 | #ifndef PsychicHttpsServer_h 2 | #define PsychicHttpsServer_h 3 | 4 | #include 5 | 6 | #ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE 7 | 8 | #include "PsychicCore.h" 9 | #include "PsychicHttpServer.h" 10 | #include 11 | #if !CONFIG_HTTPD_WS_SUPPORT 12 | #error PsychicHttpsServer cannot be used unless HTTPD_WS_SUPPORT is enabled in esp-http-server component configuration 13 | #endif 14 | 15 | #define PSY_ENABLE_SSL //you can use this define in your code to enable/disable these features 16 | 17 | class PsychicHttpsServer : public PsychicHttpServer 18 | { 19 | protected: 20 | bool _use_ssl = false; 21 | 22 | public: 23 | PsychicHttpsServer(); 24 | ~PsychicHttpsServer(); 25 | 26 | httpd_ssl_config_t ssl_config; 27 | 28 | using PsychicHttpServer::listen; //keep the regular version 29 | esp_err_t listen(uint16_t port, const char *cert, const char *private_key); 30 | 31 | virtual esp_err_t _startServer() override final; 32 | virtual void stop() override final; 33 | }; 34 | 35 | #endif // PsychicHttpsServer_h 36 | 37 | #else 38 | #error ESP-IDF https server support not enabled. 39 | #endif // CONFIG_ESP_HTTPS_SERVER_ENABLE -------------------------------------------------------------------------------- /lib/PsychicHttp/src/PsychicJson.h: -------------------------------------------------------------------------------- 1 | // PsychicJson.h 2 | /* 3 | Async Response to use with ArduinoJson and AsyncWebServer 4 | Written by Andrew Melvin (SticilFace) with help from me-no-dev and BBlanchon. 5 | Ported to PsychicHttp by Zach Hoeken 6 | 7 | */ 8 | #ifndef PSYCHIC_JSON_H_ 9 | #define PSYCHIC_JSON_H_ 10 | 11 | #include "PsychicRequest.h" 12 | #include "PsychicWebHandler.h" 13 | #include "ChunkPrinter.h" 14 | #include 15 | 16 | #if ARDUINOJSON_VERSION_MAJOR == 6 17 | #define ARDUINOJSON_6_COMPATIBILITY 18 | #ifndef DYNAMIC_JSON_DOCUMENT_SIZE 19 | #define DYNAMIC_JSON_DOCUMENT_SIZE 4096 20 | #endif 21 | #endif 22 | 23 | 24 | #ifndef JSON_BUFFER_SIZE 25 | #define JSON_BUFFER_SIZE 4*1024 26 | #endif 27 | 28 | constexpr const char *JSON_MIMETYPE = "application/json"; 29 | 30 | /* 31 | * Json Response 32 | * */ 33 | 34 | class PsychicJsonResponse : public PsychicResponse 35 | { 36 | protected: 37 | #ifdef ARDUINOJSON_5_COMPATIBILITY 38 | DynamicJsonBuffer _jsonBuffer; 39 | #elif ARDUINOJSON_VERSION_MAJOR == 6 40 | DynamicJsonDocument _jsonBuffer; 41 | #else 42 | JsonDocument _jsonBuffer; 43 | #endif 44 | 45 | JsonVariant _root; 46 | size_t _contentLength; 47 | 48 | public: 49 | #ifdef ARDUINOJSON_5_COMPATIBILITY 50 | PsychicJsonResponse(PsychicRequest *request, bool isArray = false); 51 | #elif ARDUINOJSON_VERSION_MAJOR == 6 52 | PsychicJsonResponse(PsychicRequest *request, bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE); 53 | #else 54 | PsychicJsonResponse(PsychicRequest *request, bool isArray = false); 55 | #endif 56 | 57 | ~PsychicJsonResponse() {} 58 | 59 | JsonVariant &getRoot(); 60 | size_t getLength(); 61 | 62 | virtual esp_err_t send() override; 63 | }; 64 | 65 | class PsychicJsonHandler : public PsychicWebHandler 66 | { 67 | protected: 68 | PsychicJsonRequestCallback _onRequest; 69 | #if ARDUINOJSON_VERSION_MAJOR == 6 70 | const size_t _maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE; 71 | #endif 72 | 73 | public: 74 | #ifdef ARDUINOJSON_5_COMPATIBILITY 75 | PsychicJsonHandler(); 76 | PsychicJsonHandler(PsychicJsonRequestCallback onRequest); 77 | #elif ARDUINOJSON_VERSION_MAJOR == 6 78 | PsychicJsonHandler(size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE); 79 | PsychicJsonHandler(PsychicJsonRequestCallback onRequest, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE); 80 | #else 81 | PsychicJsonHandler(); 82 | PsychicJsonHandler(PsychicJsonRequestCallback onRequest); 83 | #endif 84 | 85 | void onRequest(PsychicJsonRequestCallback fn); 86 | virtual esp_err_t handleRequest(PsychicRequest *request) override; 87 | }; 88 | 89 | #endif -------------------------------------------------------------------------------- /lib/PsychicHttp/src/PsychicResponse.h: -------------------------------------------------------------------------------- 1 | #ifndef PsychicResponse_h 2 | #define PsychicResponse_h 3 | 4 | #include "PsychicCore.h" 5 | #include "time.h" 6 | 7 | class PsychicRequest; 8 | 9 | class PsychicResponse 10 | { 11 | protected: 12 | PsychicRequest *_request; 13 | 14 | int _code; 15 | char _status[60]; 16 | std::list _headers; 17 | int64_t _contentLength; 18 | const char * _body; 19 | 20 | public: 21 | PsychicResponse(PsychicRequest *request); 22 | virtual ~PsychicResponse(); 23 | 24 | void setCode(int code); 25 | 26 | void setContentType(const char *contentType); 27 | void setContentLength(int64_t contentLength) { _contentLength = contentLength; } 28 | int64_t getContentLength(int64_t contentLength) { return _contentLength; } 29 | 30 | void addHeader(const char *field, const char *value); 31 | 32 | void setCookie(const char *key, const char *value, unsigned long max_age = 60*60*24*30, const char *extras = ""); 33 | 34 | void setContent(const char *content); 35 | void setContent(const uint8_t *content, size_t len); 36 | 37 | const char * getContent(); 38 | size_t getContentLength(); 39 | 40 | virtual esp_err_t send(); 41 | void sendHeaders(); 42 | esp_err_t sendChunk(uint8_t *chunk, size_t chunksize); 43 | esp_err_t finishChunking(); 44 | }; 45 | 46 | #endif // PsychicResponse_h -------------------------------------------------------------------------------- /lib/PsychicHttp/src/PsychicStaticFileHandler.h: -------------------------------------------------------------------------------- 1 | #ifndef PsychicStaticFileHandler_h 2 | #define PsychicStaticFileHandler_h 3 | 4 | #include "PsychicCore.h" 5 | #include "PsychicWebHandler.h" 6 | #include "PsychicRequest.h" 7 | #include "PsychicResponse.h" 8 | #include "PsychicFileResponse.h" 9 | 10 | class PsychicStaticFileHandler : public PsychicWebHandler { 11 | using File = fs::File; 12 | using FS = fs::FS; 13 | private: 14 | bool _getFile(PsychicRequest *request); 15 | bool _fileExists(const String& path); 16 | uint8_t _countBits(const uint8_t value) const; 17 | protected: 18 | FS _fs; 19 | File _file; 20 | String _filename; 21 | String _uri; 22 | String _path; 23 | String _default_file; 24 | String _cache_control; 25 | String _last_modified; 26 | bool _isDir; 27 | bool _gzipFirst; 28 | uint8_t _gzipStats; 29 | public: 30 | PsychicStaticFileHandler(const char* uri, FS& fs, const char* path, const char* cache_control); 31 | bool canHandle(PsychicRequest *request) override; 32 | esp_err_t handleRequest(PsychicRequest *request) override; 33 | PsychicStaticFileHandler& setIsDir(bool isDir); 34 | PsychicStaticFileHandler& setDefaultFile(const char* filename); 35 | PsychicStaticFileHandler& setCacheControl(const char* cache_control); 36 | PsychicStaticFileHandler& setLastModified(const char* last_modified); 37 | PsychicStaticFileHandler& setLastModified(struct tm* last_modified); 38 | //PsychicStaticFileHandler& setTemplateProcessor(AwsTemplateProcessor newCallback) {_callback = newCallback; return *this;} 39 | }; 40 | 41 | #endif /* PsychicHttp_h */ -------------------------------------------------------------------------------- /lib/PsychicHttp/src/PsychicStreamResponse.cpp: -------------------------------------------------------------------------------- 1 | #include "PsychicStreamResponse.h" 2 | #include "PsychicResponse.h" 3 | #include "PsychicRequest.h" 4 | 5 | PsychicStreamResponse::PsychicStreamResponse(PsychicRequest *request, const String& contentType) 6 | : PsychicResponse(request), _buffer(NULL) { 7 | 8 | setContentType(contentType.c_str()); 9 | addHeader("Content-Disposition", "inline"); 10 | } 11 | 12 | 13 | PsychicStreamResponse::PsychicStreamResponse(PsychicRequest *request, const String& contentType, const String& name) 14 | : PsychicResponse(request), _buffer(NULL) { 15 | 16 | setContentType(contentType.c_str()); 17 | 18 | char buf[26+name.length()]; 19 | snprintf(buf, sizeof (buf), "attachment; filename=\"%s\"", name.c_str()); 20 | addHeader("Content-Disposition", buf); 21 | } 22 | 23 | 24 | PsychicStreamResponse::~PsychicStreamResponse() 25 | { 26 | endSend(); 27 | } 28 | 29 | 30 | esp_err_t PsychicStreamResponse::beginSend() 31 | { 32 | if(_buffer) 33 | return ESP_OK; 34 | 35 | //Buffer to hold ChunkPrinter and stream buffer. Using placement new will keep us at a single allocation. 36 | _buffer = (uint8_t*)malloc(STREAM_CHUNK_SIZE + sizeof(ChunkPrinter)); 37 | 38 | if(!_buffer) 39 | { 40 | /* Respond with 500 Internal Server Error */ 41 | httpd_resp_send_err(_request->request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Unable to allocate memory."); 42 | return ESP_FAIL; 43 | } 44 | 45 | _printer = new (_buffer) ChunkPrinter(this, _buffer + sizeof(ChunkPrinter), STREAM_CHUNK_SIZE); 46 | 47 | sendHeaders(); 48 | return ESP_OK; 49 | } 50 | 51 | 52 | esp_err_t PsychicStreamResponse::endSend() 53 | { 54 | esp_err_t err = ESP_OK; 55 | 56 | if(!_buffer) 57 | err = ESP_FAIL; 58 | else 59 | { 60 | _printer->~ChunkPrinter(); //flushed on destruct 61 | err = finishChunking(); 62 | free(_buffer); 63 | _buffer = NULL; 64 | } 65 | return err; 66 | } 67 | 68 | 69 | void PsychicStreamResponse::flush() 70 | { 71 | if(_buffer) 72 | _printer->flush(); 73 | } 74 | 75 | 76 | size_t PsychicStreamResponse::write(uint8_t data) 77 | { 78 | return _buffer ? _printer->write(data) : 0; 79 | } 80 | 81 | 82 | size_t PsychicStreamResponse::write(const uint8_t *buffer, size_t size) 83 | { 84 | return _buffer ? _printer->write(buffer, size) : 0; 85 | } 86 | 87 | 88 | size_t PsychicStreamResponse::copyFrom(Stream &stream) 89 | { 90 | if(_buffer) 91 | return _printer->copyFrom(stream); 92 | 93 | return 0; 94 | } 95 | -------------------------------------------------------------------------------- /lib/PsychicHttp/src/PsychicStreamResponse.h: -------------------------------------------------------------------------------- 1 | #ifndef PsychicStreamResponse_h 2 | #define PsychicStreamResponse_h 3 | 4 | #include "PsychicCore.h" 5 | #include "PsychicResponse.h" 6 | #include "ChunkPrinter.h" 7 | 8 | class PsychicRequest; 9 | 10 | class PsychicStreamResponse : public PsychicResponse, public Print 11 | { 12 | private: 13 | ChunkPrinter *_printer; 14 | uint8_t *_buffer; 15 | public: 16 | 17 | PsychicStreamResponse(PsychicRequest *request, const String& contentType); 18 | PsychicStreamResponse(PsychicRequest *request, const String& contentType, const String& name); //Download 19 | 20 | ~PsychicStreamResponse(); 21 | 22 | esp_err_t beginSend(); 23 | esp_err_t endSend(); 24 | 25 | void flush() override; 26 | 27 | size_t write(uint8_t data) override; 28 | size_t write(const uint8_t *buffer, size_t size) override; 29 | 30 | size_t copyFrom(Stream &stream); 31 | 32 | using Print::write; 33 | }; 34 | 35 | #endif // PsychicStreamResponse_h 36 | -------------------------------------------------------------------------------- /lib/PsychicHttp/src/PsychicUploadHandler.h: -------------------------------------------------------------------------------- 1 | #ifndef PsychicUploadHandler_h 2 | #define PsychicUploadHandler_h 3 | 4 | #include "PsychicCore.h" 5 | #include "PsychicHttpServer.h" 6 | #include "PsychicRequest.h" 7 | #include "PsychicWebHandler.h" 8 | #include "PsychicWebParameter.h" 9 | 10 | //callback definitions 11 | typedef std::function PsychicUploadCallback; 12 | 13 | /* 14 | * HANDLER :: Can be attached to any endpoint or as a generic request handler. 15 | */ 16 | 17 | class PsychicUploadHandler : public PsychicWebHandler { 18 | protected: 19 | PsychicUploadCallback _uploadCallback; 20 | 21 | PsychicRequest *_request; 22 | 23 | String _temp; 24 | size_t _parsedLength; 25 | uint8_t _multiParseState; 26 | String _boundary; 27 | uint8_t _boundaryPosition; 28 | size_t _itemStartIndex; 29 | size_t _itemSize; 30 | String _itemName; 31 | String _itemFilename; 32 | String _itemType; 33 | String _itemValue; 34 | uint8_t *_itemBuffer; 35 | size_t _itemBufferIndex; 36 | bool _itemIsFile; 37 | 38 | esp_err_t _basicUploadHandler(PsychicRequest *request); 39 | esp_err_t _multipartUploadHandler(PsychicRequest *request); 40 | 41 | void _handleUploadByte(uint8_t data, bool last); 42 | void _parseMultipartPostByte(uint8_t data, bool last); 43 | 44 | public: 45 | PsychicUploadHandler(); 46 | ~PsychicUploadHandler(); 47 | 48 | bool canHandle(PsychicRequest *request) override; 49 | esp_err_t handleRequest(PsychicRequest *request) override; 50 | 51 | PsychicUploadHandler * onUpload(PsychicUploadCallback fn); 52 | }; 53 | 54 | enum { 55 | EXPECT_BOUNDARY, 56 | PARSE_HEADERS, 57 | WAIT_FOR_RETURN1, 58 | EXPECT_FEED1, 59 | EXPECT_DASH1, 60 | EXPECT_DASH2, 61 | BOUNDARY_OR_DATA, 62 | DASH3_OR_RETURN2, 63 | EXPECT_FEED2, 64 | PARSING_FINISHED, 65 | PARSE_ERROR 66 | }; 67 | 68 | #endif // PsychicUploadHandler_h -------------------------------------------------------------------------------- /lib/PsychicHttp/src/PsychicWebHandler.cpp: -------------------------------------------------------------------------------- 1 | #include "PsychicWebHandler.h" 2 | 3 | PsychicWebHandler::PsychicWebHandler() : 4 | PsychicHandler(), 5 | _requestCallback(NULL), 6 | _onOpen(NULL), 7 | _onClose(NULL) 8 | {} 9 | PsychicWebHandler::~PsychicWebHandler() {} 10 | 11 | bool PsychicWebHandler::canHandle(PsychicRequest *request) { 12 | return true; 13 | } 14 | 15 | esp_err_t PsychicWebHandler::handleRequest(PsychicRequest *request) 16 | { 17 | //lookup our client 18 | PsychicClient *client = checkForNewClient(request->client()); 19 | if (client->isNew) 20 | openCallback(client); 21 | 22 | /* Request body cannot be larger than a limit */ 23 | if (request->contentLength() > request->server()->maxRequestBodySize) 24 | { 25 | ESP_LOGE(PH_TAG, "Request body too large : %d bytes", request->contentLength()); 26 | 27 | /* Respond with 400 Bad Request */ 28 | char error[60]; 29 | sprintf(error, "Request body must be less than %lu bytes!", request->server()->maxRequestBodySize); 30 | httpd_resp_send_err(request->request(), HTTPD_400_BAD_REQUEST, error); 31 | 32 | /* Return failure to close underlying connection else the incoming file content will keep the socket busy */ 33 | return ESP_FAIL; 34 | } 35 | 36 | //get our body loaded up. 37 | esp_err_t err = request->loadBody(); 38 | if (err != ESP_OK) 39 | return err; 40 | 41 | //load our params in. 42 | request->loadParams(); 43 | 44 | //okay, pass on to our callback. 45 | if (this->_requestCallback != NULL) 46 | err = this->_requestCallback(request); 47 | 48 | return err; 49 | } 50 | 51 | PsychicWebHandler * PsychicWebHandler::onRequest(PsychicHttpRequestCallback fn) { 52 | _requestCallback = fn; 53 | return this; 54 | } 55 | 56 | void PsychicWebHandler::openCallback(PsychicClient *client) { 57 | if (_onOpen != NULL) 58 | _onOpen(client); 59 | } 60 | 61 | void PsychicWebHandler::closeCallback(PsychicClient *client) { 62 | if (_onClose != NULL) 63 | _onClose(getClient(client)); 64 | } 65 | 66 | PsychicWebHandler * PsychicWebHandler::onOpen(PsychicClientCallback fn) { 67 | _onOpen = fn; 68 | return this; 69 | } 70 | 71 | PsychicWebHandler * PsychicWebHandler::onClose(PsychicClientCallback fn) { 72 | _onClose = fn; 73 | return this; 74 | } -------------------------------------------------------------------------------- /lib/PsychicHttp/src/PsychicWebHandler.h: -------------------------------------------------------------------------------- 1 | #ifndef PsychicWebHandler_h 2 | #define PsychicWebHandler_h 3 | 4 | // #include "PsychicCore.h" 5 | // #include "PsychicHttpServer.h" 6 | // #include "PsychicRequest.h" 7 | #include "PsychicHandler.h" 8 | 9 | /* 10 | * HANDLER :: Can be attached to any endpoint or as a generic request handler. 11 | */ 12 | 13 | class PsychicWebHandler : public PsychicHandler { 14 | protected: 15 | PsychicHttpRequestCallback _requestCallback; 16 | PsychicClientCallback _onOpen; 17 | PsychicClientCallback _onClose; 18 | 19 | public: 20 | PsychicWebHandler(); 21 | ~PsychicWebHandler(); 22 | 23 | virtual bool canHandle(PsychicRequest *request) override; 24 | virtual esp_err_t handleRequest(PsychicRequest *request) override; 25 | PsychicWebHandler * onRequest(PsychicHttpRequestCallback fn); 26 | 27 | virtual void openCallback(PsychicClient *client); 28 | virtual void closeCallback(PsychicClient *client); 29 | 30 | PsychicWebHandler *onOpen(PsychicClientCallback fn); 31 | PsychicWebHandler *onClose(PsychicClientCallback fn); 32 | }; 33 | 34 | #endif -------------------------------------------------------------------------------- /lib/PsychicHttp/src/PsychicWebParameter.h: -------------------------------------------------------------------------------- 1 | #ifndef PsychicWebParameter_h 2 | #define PsychicWebParameter_h 3 | 4 | /* 5 | * PARAMETER :: Chainable object to hold GET/POST and FILE parameters 6 | * */ 7 | 8 | class PsychicWebParameter { 9 | private: 10 | String _name; 11 | String _value; 12 | size_t _size; 13 | bool _isForm; 14 | bool _isFile; 15 | 16 | public: 17 | 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){} 18 | const String& name() const { return _name; } 19 | const String& value() const { return _value; } 20 | size_t size() const { return _size; } 21 | bool isPost() const { return _isForm; } 22 | bool isFile() const { return _isFile; } 23 | }; 24 | 25 | #endif //PsychicWebParameter_h -------------------------------------------------------------------------------- /lib/PsychicHttp/src/PsychicWebSocket.h: -------------------------------------------------------------------------------- 1 | #ifndef PsychicWebSocket_h 2 | #define PsychicWebSocket_h 3 | 4 | #include "PsychicCore.h" 5 | #include "PsychicRequest.h" 6 | 7 | class PsychicWebSocketRequest; 8 | class PsychicWebSocketClient; 9 | 10 | //callback function definitions 11 | typedef std::function PsychicWebSocketClientCallback; 12 | typedef std::function PsychicWebSocketFrameCallback; 13 | 14 | class PsychicWebSocketClient : public PsychicClient 15 | { 16 | public: 17 | PsychicWebSocketClient(PsychicClient *client); 18 | ~PsychicWebSocketClient(); 19 | 20 | esp_err_t sendMessage(httpd_ws_frame_t * ws_pkt); 21 | esp_err_t sendMessage(httpd_ws_type_t op, const void *data, size_t len); 22 | esp_err_t sendMessage(const char *buf); 23 | }; 24 | 25 | class PsychicWebSocketRequest : public PsychicRequest 26 | { 27 | private: 28 | PsychicWebSocketClient _client; 29 | 30 | public: 31 | PsychicWebSocketRequest(PsychicRequest *req); 32 | virtual ~PsychicWebSocketRequest(); 33 | 34 | PsychicWebSocketClient * client() override; 35 | 36 | esp_err_t reply(httpd_ws_frame_t * ws_pkt); 37 | esp_err_t reply(httpd_ws_type_t op, const void *data, size_t len); 38 | esp_err_t reply(const char *buf); 39 | }; 40 | 41 | class PsychicWebSocketHandler : public PsychicHandler { 42 | protected: 43 | PsychicWebSocketClientCallback _onOpen; 44 | PsychicWebSocketFrameCallback _onFrame; 45 | PsychicWebSocketClientCallback _onClose; 46 | 47 | public: 48 | PsychicWebSocketHandler(); 49 | ~PsychicWebSocketHandler(); 50 | 51 | PsychicWebSocketClient * getClient(int socket) override; 52 | PsychicWebSocketClient * getClient(PsychicClient *client) override; 53 | void addClient(PsychicClient *client) override; 54 | void removeClient(PsychicClient *client) override; 55 | void openCallback(PsychicClient *client) override; 56 | void closeCallback(PsychicClient *client) override; 57 | 58 | bool isWebSocket() override final; 59 | esp_err_t handleRequest(PsychicRequest *request) override; 60 | 61 | PsychicWebSocketHandler *onOpen(PsychicWebSocketClientCallback fn); 62 | PsychicWebSocketHandler *onFrame(PsychicWebSocketFrameCallback fn); 63 | PsychicWebSocketHandler *onClose(PsychicWebSocketClientCallback fn); 64 | 65 | void sendAll(httpd_ws_frame_t * ws_pkt); 66 | void sendAll(httpd_ws_type_t op, const void *data, size_t len); 67 | void sendAll(const char *buf); 68 | }; 69 | 70 | #endif // PsychicWebSocket_h -------------------------------------------------------------------------------- /lib/PsychicHttp/src/TemplatePrinter.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************ 2 | 3 | TemplatePrinter Class 4 | 5 | A basic templating engine for a stream of text. 6 | This wraps the Arduino Print interface and writes to any 7 | Print interface. 8 | 9 | Written by Christopher Andrews (https://github.com/Chris--A) 10 | 11 | ************************************************************/ 12 | 13 | #include "TemplatePrinter.h" 14 | 15 | void TemplatePrinter::resetParam(bool flush){ 16 | if(flush && _inParam){ 17 | _stream.write(_delimiter); 18 | 19 | if(_paramPos) 20 | _stream.print(_paramBuffer); 21 | } 22 | 23 | memset(_paramBuffer, 0, sizeof(_paramBuffer)); 24 | _paramPos = 0; 25 | _inParam = false; 26 | } 27 | 28 | 29 | void TemplatePrinter::flush(){ 30 | resetParam(true); 31 | _stream.flush(); 32 | } 33 | 34 | size_t TemplatePrinter::write(uint8_t data){ 35 | 36 | if(data == _delimiter){ 37 | 38 | // End of parameter, send to callback 39 | if(_inParam){ 40 | 41 | // On false, return the parameter place holder as is: not a parameter 42 | // Bug fix: ignore parameters that are zero length. 43 | if(!_paramPos || !_cb(_stream, _paramBuffer)){ 44 | resetParam(true); 45 | _stream.write(data); 46 | }else{ 47 | resetParam(false); 48 | } 49 | 50 | // Start collecting parameter 51 | }else{ 52 | _inParam = true; 53 | } 54 | }else{ 55 | 56 | // Are we collecting 57 | if(_inParam){ 58 | 59 | // Is param still valid 60 | if(isalnum(data) || data == '_'){ 61 | 62 | // Total param len must be 63, 1 for null. 63 | if(_paramPos < sizeof(_paramBuffer) - 1){ 64 | _paramBuffer[_paramPos++] = data; 65 | 66 | // Not a valid param 67 | }else{ 68 | resetParam(true); 69 | } 70 | }else{ 71 | resetParam(true); 72 | _stream.write(data); 73 | } 74 | 75 | // Just output 76 | }else{ 77 | _stream.write(data); 78 | } 79 | } 80 | return 1; 81 | } 82 | 83 | size_t TemplatePrinter::copyFrom(Stream &stream){ 84 | size_t count = 0; 85 | 86 | while(stream.available()) 87 | count += this->write(stream.read()); 88 | 89 | return count; 90 | } 91 | -------------------------------------------------------------------------------- /lib/PsychicHttp/src/TemplatePrinter.h: -------------------------------------------------------------------------------- 1 | #ifndef TemplatePrinter_h 2 | #define TemplatePrinter_h 3 | 4 | #include "PsychicCore.h" 5 | #include 6 | 7 | /************************************************************ 8 | 9 | TemplatePrinter Class 10 | 11 | A basic templating engine for a stream of text. 12 | This wraps the Arduino Print interface and writes to any 13 | Print interface. 14 | 15 | Written by Christopher Andrews (https://github.com/Chris--A) 16 | 17 | ************************************************************/ 18 | 19 | class TemplatePrinter; 20 | 21 | typedef std::function TemplateCallback; 22 | typedef std::function TemplateSourceCallback; 23 | 24 | class TemplatePrinter : public Print{ 25 | private: 26 | bool _inParam; 27 | char _paramBuffer[64]; 28 | uint8_t _paramPos; 29 | Print &_stream; 30 | TemplateCallback _cb; 31 | char _delimiter; 32 | 33 | void resetParam(bool flush); 34 | 35 | public: 36 | using Print::write; 37 | 38 | static void start(Print &stream, TemplateCallback cb, TemplateSourceCallback entry){ 39 | TemplatePrinter printer(stream, cb); 40 | entry(printer); 41 | } 42 | 43 | TemplatePrinter(Print &stream, TemplateCallback cb, const char delimeter = '%') : _stream(stream), _cb(cb), _delimiter(delimeter) { resetParam(false); } 44 | ~TemplatePrinter(){ flush(); } 45 | 46 | void flush() override; 47 | size_t write(uint8_t data) override; 48 | size_t copyFrom(Stream &stream); 49 | }; 50 | 51 | #endif 52 | -------------------------------------------------------------------------------- /lib/PsychicHttp/src/async_worker.h: -------------------------------------------------------------------------------- 1 | #ifndef async_worker_h 2 | #define async_worker_h 3 | 4 | #include "PsychicCore.h" 5 | #include "freertos/FreeRTOS.h" 6 | #include "freertos/semphr.h" 7 | 8 | #define ASYNC_WORKER_TASK_PRIORITY 5 9 | #define ASYNC_WORKER_TASK_STACK_SIZE (4*1024) 10 | #define ASYNC_WORKER_COUNT 8 11 | 12 | // Async requests are queued here while they wait to be processed by the workers 13 | static QueueHandle_t async_req_queue; 14 | 15 | // Track the number of free workers at any given time 16 | static SemaphoreHandle_t worker_ready_count; 17 | 18 | // Each worker has its own thread 19 | static TaskHandle_t worker_handles[ASYNC_WORKER_COUNT]; 20 | 21 | typedef esp_err_t (*httpd_req_handler_t)(httpd_req_t *req); 22 | 23 | typedef struct { 24 | httpd_req_t* req; 25 | httpd_req_handler_t handler; 26 | } httpd_async_req_t; 27 | 28 | bool is_on_async_worker_thread(void); 29 | esp_err_t submit_async_req(httpd_req_t *req, httpd_req_handler_t handler); 30 | void async_req_worker_task(void *p); 31 | void start_async_req_workers(void); 32 | 33 | esp_err_t httpd_req_async_handler_begin(httpd_req_t *r, httpd_req_t **out); 34 | esp_err_t httpd_req_async_handler_complete(httpd_req_t *r); 35 | 36 | #endif //async_worker_h -------------------------------------------------------------------------------- /lib/PsychicHttp/src/http_status.h: -------------------------------------------------------------------------------- 1 | #ifndef MICRO_HTTP_STATUS_H 2 | #define MICRO_HTTP_STATUS_H 3 | 4 | #include 5 | 6 | bool http_informational(int code); 7 | bool http_success(int code); 8 | bool http_redirection(int code); 9 | bool http_client_error(int code); 10 | bool http_server_error(int code); 11 | bool http_failure(int code); 12 | const char *http_status_group(int code); 13 | const char *http_status_reason(int code); 14 | 15 | #endif // MICRO_HTTP_STATUS_H -------------------------------------------------------------------------------- /lib/framework/APStatus.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * ESP32 SvelteKit 3 | * 4 | * A simple, secure and extensible framework for IoT projects for ESP32 platforms 5 | * with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. 6 | * https://github.com/theelims/ESP32-sveltekit 7 | * 8 | * Copyright (C) 2018 - 2023 rjwats 9 | * Copyright (C) 2023 - 2024 theelims 10 | * 11 | * All Rights Reserved. This software may be modified and distributed under 12 | * the terms of the LGPL v3 license. See the LICENSE file for details. 13 | **/ 14 | 15 | #include 16 | 17 | APStatus::APStatus(PsychicHttpServer *server, 18 | SecurityManager *securityManager, 19 | APSettingsService *apSettingsService) : _server(server), 20 | _securityManager(securityManager), 21 | _apSettingsService(apSettingsService) 22 | { 23 | } 24 | void APStatus::begin() 25 | { 26 | _server->on(AP_STATUS_SERVICE_PATH, 27 | HTTP_GET, 28 | _securityManager->wrapRequest(std::bind(&APStatus::apStatus, this, std::placeholders::_1), 29 | AuthenticationPredicates::IS_AUTHENTICATED)); 30 | 31 | ESP_LOGV(SVK_TAG, "Registered GET endpoint: %s", AP_STATUS_SERVICE_PATH); 32 | } 33 | 34 | esp_err_t APStatus::apStatus(PsychicRequest *request) 35 | { 36 | PsychicJsonResponse response = PsychicJsonResponse(request, false); 37 | JsonObject root = response.getRoot(); 38 | 39 | root["status"] = _apSettingsService->getAPNetworkStatus(); 40 | root["ip_address"] = WiFi.softAPIP().toString(); 41 | root["mac_address"] = WiFi.softAPmacAddress(); 42 | root["station_num"] = WiFi.softAPgetStationNum(); 43 | 44 | return response.send(); 45 | } 46 | 47 | bool APStatus::isActive() 48 | { 49 | return _apSettingsService->getAPNetworkStatus() == APNetworkStatus::ACTIVE ? true : false; 50 | } 51 | -------------------------------------------------------------------------------- /lib/framework/APStatus.h: -------------------------------------------------------------------------------- 1 | #ifndef APStatus_h 2 | #define APStatus_h 3 | 4 | /** 5 | * ESP32 SvelteKit 6 | * 7 | * A simple, secure and extensible framework for IoT projects for ESP32 platforms 8 | * with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. 9 | * https://github.com/theelims/ESP32-sveltekit 10 | * 11 | * Copyright (C) 2018 - 2023 rjwats 12 | * Copyright (C) 2023 - 2024 theelims 13 | * 14 | * All Rights Reserved. This software may be modified and distributed under 15 | * the terms of the LGPL v3 license. See the LICENSE file for details. 16 | **/ 17 | 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #define AP_STATUS_SERVICE_PATH "/rest/apStatus" 27 | 28 | class APStatus 29 | { 30 | public: 31 | APStatus(PsychicHttpServer *server, SecurityManager *securityManager, APSettingsService *apSettingsService); 32 | 33 | void begin(); 34 | 35 | bool isActive(); 36 | 37 | private: 38 | PsychicHttpServer *_server; 39 | SecurityManager *_securityManager; 40 | APSettingsService *_apSettingsService; 41 | esp_err_t apStatus(PsychicRequest *request); 42 | }; 43 | 44 | #endif // end APStatus_h 45 | -------------------------------------------------------------------------------- /lib/framework/AnalyticsService.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /** 4 | * ESP32 SvelteKit 5 | * 6 | * A simple, secure and extensible framework for IoT projects for ESP32 platforms 7 | * with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. 8 | * https://github.com/theelims/ESP32-sveltekit 9 | * 10 | * Copyright (C) 2023 - 2024 theelims 11 | * 12 | * All Rights Reserved. This software may be modified and distributed under 13 | * the terms of the LGPL v3 license. See the LICENSE file for details. 14 | **/ 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #define MAX_ESP_ANALYTICS_SIZE 1024 22 | #define EVENT_ANALYTICS "analytics" 23 | #define ANALYTICS_INTERVAL 2000 24 | 25 | class AnalyticsService 26 | { 27 | public: 28 | AnalyticsService(EventSocket *socket) : _socket(socket) {}; 29 | 30 | void begin() 31 | { 32 | _socket->registerEvent(EVENT_ANALYTICS); 33 | } 34 | 35 | void loop() 36 | { 37 | if (millis() - lastMillis > ANALYTICS_INTERVAL) 38 | { 39 | lastMillis = millis(); 40 | JsonDocument doc; 41 | doc["uptime"] = millis() / 1000; 42 | doc["free_heap"] = ESP.getFreeHeap(); 43 | doc["used_heap"] = ESP.getHeapSize() - ESP.getFreeHeap(); 44 | doc["total_heap"] = ESP.getHeapSize(); 45 | doc["min_free_heap"] = ESP.getMinFreeHeap(); 46 | doc["max_alloc_heap"] = ESP.getMaxAllocHeap(); 47 | doc["fs_used"] = ESPFS.usedBytes(); 48 | doc["fs_total"] = ESPFS.totalBytes(); 49 | doc["core_temp"] = temperatureRead(); 50 | if (psramFound()) { 51 | doc["free_psram"] = ESP.getFreePsram(); 52 | doc["used_psram"] = ESP.getPsramSize() - ESP.getFreePsram(); 53 | doc["psram_size"] = ESP.getPsramSize(); 54 | } 55 | 56 | JsonObject jsonObject = doc.as(); 57 | _socket->emitEvent(EVENT_ANALYTICS, jsonObject); 58 | } 59 | }; 60 | 61 | protected: 62 | EventSocket *_socket; 63 | 64 | unsigned long lastMillis = 0; 65 | }; 66 | -------------------------------------------------------------------------------- /lib/framework/ArduinoJsonJWT.h: -------------------------------------------------------------------------------- 1 | #ifndef ArduinoJsonJWT_H 2 | #define ArduinoJsonJWT_H 3 | 4 | /** 5 | * ESP32 SvelteKit 6 | * 7 | * A simple, secure and extensible framework for IoT projects for ESP32 platforms 8 | * with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. 9 | * https://github.com/theelims/ESP32-sveltekit 10 | * 11 | * Copyright (C) 2018 - 2023 rjwats 12 | * Copyright (C) 2023 - 2024 theelims 13 | * 14 | * All Rights Reserved. This software may be modified and distributed under 15 | * the terms of the LGPL v3 license. See the LICENSE file for details. 16 | **/ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | class ArduinoJsonJWT 25 | { 26 | private: 27 | String _secret; 28 | 29 | const String JWT_HEADER = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"; 30 | const int JWT_HEADER_SIZE = JWT_HEADER.length(); 31 | 32 | String sign(String &value); 33 | 34 | static String encode(const char *cstr, int len); 35 | static String decode(String value); 36 | 37 | public: 38 | ArduinoJsonJWT(String secret); 39 | 40 | void setSecret(String secret); 41 | String getSecret(); 42 | 43 | String buildJWT(JsonObject &payload); 44 | void parseJWT(String jwt, JsonDocument &jsonDocument); 45 | }; 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /lib/framework/AuthenticationService.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * ESP32 SvelteKit 3 | * 4 | * A simple, secure and extensible framework for IoT projects for ESP32 platforms 5 | * with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. 6 | * https://github.com/theelims/ESP32-sveltekit 7 | * 8 | * Copyright (C) 2018 - 2023 rjwats 9 | * Copyright (C) 2023 - 2024 theelims 10 | * 11 | * All Rights Reserved. This software may be modified and distributed under 12 | * the terms of the LGPL v3 license. See the LICENSE file for details. 13 | **/ 14 | 15 | #include 16 | 17 | #if FT_ENABLED(FT_SECURITY) 18 | 19 | AuthenticationService::AuthenticationService(PsychicHttpServer *server, SecurityManager *securityManager) : _server(server), 20 | _securityManager(securityManager) 21 | { 22 | } 23 | 24 | void AuthenticationService::begin() 25 | { 26 | // Signs in a user if the username and password match. Provides a JWT to be used in the Authorization header in subsequent requests 27 | _server->on(SIGN_IN_PATH, HTTP_POST, [this](PsychicRequest *request, JsonVariant &json) 28 | { 29 | if (json.is()) { 30 | String username = json["username"]; 31 | String password = json["password"]; 32 | Authentication authentication = _securityManager->authenticate(username, password); 33 | if (authentication.authenticated) { 34 | PsychicJsonResponse response = PsychicJsonResponse(request, false); 35 | JsonObject root = response.getRoot(); 36 | root["access_token"] = _securityManager->generateJWT(authentication.user); 37 | return response.send(); 38 | } 39 | } 40 | return request->reply(401); }); 41 | 42 | ESP_LOGV(SVK_TAG, "Registered POST endpoint: %s", SIGN_IN_PATH); 43 | 44 | // Verifies that the request supplied a valid JWT 45 | _server->on(VERIFY_AUTHORIZATION_PATH, HTTP_GET, [this](PsychicRequest *request) 46 | { 47 | Authentication authentication = _securityManager->authenticateRequest(request); 48 | return request->reply(authentication.authenticated ? 200 : 401); }); 49 | 50 | ESP_LOGV(SVK_TAG, "Registered GET endpoint: %s", VERIFY_AUTHORIZATION_PATH); 51 | } 52 | 53 | #endif // end FT_ENABLED(FT_SECURITY) 54 | -------------------------------------------------------------------------------- /lib/framework/AuthenticationService.h: -------------------------------------------------------------------------------- 1 | #ifndef AuthenticationService_H_ 2 | #define AuthenticationService_H_ 3 | 4 | /** 5 | * ESP32 SvelteKit 6 | * 7 | * A simple, secure and extensible framework for IoT projects for ESP32 platforms 8 | * with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. 9 | * https://github.com/theelims/ESP32-sveltekit 10 | * 11 | * Copyright (C) 2018 - 2023 rjwats 12 | * Copyright (C) 2023 - 2024 theelims 13 | * 14 | * All Rights Reserved. This software may be modified and distributed under 15 | * the terms of the LGPL v3 license. See the LICENSE file for details. 16 | **/ 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | #define VERIFY_AUTHORIZATION_PATH "/rest/verifyAuthorization" 23 | #define SIGN_IN_PATH "/rest/signIn" 24 | 25 | #if FT_ENABLED(FT_SECURITY) 26 | 27 | class AuthenticationService 28 | { 29 | public: 30 | AuthenticationService(PsychicHttpServer *server, SecurityManager *securityManager); 31 | 32 | void begin(); 33 | 34 | private: 35 | SecurityManager *_securityManager; 36 | PsychicHttpServer *_server; 37 | }; 38 | 39 | #endif // end FT_ENABLED(FT_SECURITY) 40 | #endif // end SecurityManager_h 41 | -------------------------------------------------------------------------------- /lib/framework/BatteryService.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * ESP32 SvelteKit 3 | * 4 | * A simple, secure and extensible framework for IoT projects for ESP32 platforms 5 | * with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. 6 | * https://github.com/theelims/ESP32-sveltekit 7 | * 8 | * Copyright (C) 2023 - 2024 theelims 9 | * 10 | * All Rights Reserved. This software may be modified and distributed under 11 | * the terms of the LGPL v3 license. See the LICENSE file for details. 12 | **/ 13 | 14 | #include 15 | 16 | BatteryService::BatteryService(EventSocket *socket) : _socket(socket) 17 | { 18 | } 19 | 20 | void BatteryService::updateSOC(float stateOfCharge) 21 | { 22 | _lastSOC = (int)round(stateOfCharge); 23 | batteryEvent(); 24 | } 25 | 26 | void BatteryService::setCharging(boolean isCharging) 27 | { 28 | _isCharging = isCharging; 29 | batteryEvent(); 30 | } 31 | 32 | boolean BatteryService::isCharging() 33 | { 34 | return _isCharging; 35 | } 36 | 37 | int BatteryService::getSOC() 38 | { 39 | return _lastSOC; 40 | } 41 | 42 | void BatteryService::begin() 43 | { 44 | _socket->registerEvent(EVENT_BATTERY); 45 | } 46 | 47 | void BatteryService::batteryEvent() 48 | { 49 | JsonDocument doc; 50 | doc["soc"] = _lastSOC; 51 | doc["charging"] = _isCharging; 52 | JsonObject jsonObject = doc.as(); 53 | _socket->emitEvent(EVENT_BATTERY, jsonObject); 54 | } 55 | -------------------------------------------------------------------------------- /lib/framework/BatteryService.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /** 4 | * ESP32 SvelteKit 5 | * 6 | * A simple, secure and extensible framework for IoT projects for ESP32 platforms 7 | * with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. 8 | * https://github.com/theelims/ESP32-sveltekit 9 | * 10 | * Copyright (C) 2023 - 2024 theelims 11 | * 12 | * All Rights Reserved. This software may be modified and distributed under 13 | * the terms of the LGPL v3 license. See the LICENSE file for details. 14 | **/ 15 | 16 | #include 17 | #include 18 | 19 | #define EVENT_BATTERY "battery" 20 | 21 | class BatteryService 22 | { 23 | public: 24 | BatteryService(EventSocket *socket); 25 | 26 | void begin(); 27 | 28 | void updateSOC(float stateOfCharge); 29 | 30 | void setCharging(boolean isCharging); 31 | 32 | boolean isCharging(); 33 | 34 | int getSOC(); 35 | 36 | private: 37 | EventSocket *_socket; 38 | int _lastSOC = 100; 39 | boolean _isCharging = false; 40 | 41 | void batteryEvent(); 42 | }; 43 | -------------------------------------------------------------------------------- /lib/framework/CoreDump.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * ESP32 SvelteKit 3 | * 4 | * A simple, secure and extensible framework for IoT projects for ESP32 platforms 5 | * with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. 6 | * https://github.com/theelims/ESP32-sveltekit 7 | * 8 | * Copyright (C) 2018 - 2023 rjwats 9 | * Copyright (C) 2023 - 2024 theelims 10 | * 11 | * All Rights Reserved. This software may be modified and distributed under 12 | * the terms of the LGPL v3 license. See the LICENSE file for details. 13 | **/ 14 | 15 | #include 16 | #include 17 | 18 | #include "esp_core_dump.h" 19 | #include "esp_partition.h" 20 | #include "esp_flash.h" 21 | 22 | #define MIN(a, b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a < _b ? _a : _b; }) 23 | 24 | CoreDump::CoreDump(PsychicHttpServer *server, 25 | SecurityManager *securityManager) : _server(server), 26 | _securityManager(securityManager) 27 | { 28 | } 29 | 30 | void CoreDump::begin() 31 | { 32 | _server->on(CORE_DUMP_SERVICE_PATH, 33 | HTTP_GET, 34 | _securityManager->wrapRequest(std::bind(&CoreDump::coreDump, this, std::placeholders::_1), 35 | AuthenticationPredicates::IS_AUTHENTICATED)); 36 | 37 | ESP_LOGV("CoreDump", "Registered GET endpoint: %s", CORE_DUMP_SERVICE_PATH); 38 | } 39 | 40 | esp_err_t CoreDump::coreDump(PsychicRequest *request) 41 | { 42 | size_t coredump_addr; 43 | size_t coredump_size; 44 | esp_err_t err = esp_core_dump_image_get(&coredump_addr, &coredump_size); 45 | if (err != ESP_OK) 46 | { 47 | request->reply(500, "application/json", "{\"status\":\"error\",\"message\":\"core dump not available\"}"); 48 | return err; 49 | } 50 | size_t const chunk_len = 3 * 16; // must be multiple of 3 51 | size_t const b64_len = chunk_len / 3 * 4 + 4; 52 | uint8_t *const chunk = (uint8_t *)malloc(chunk_len); 53 | char *const b64 = (char *)malloc(b64_len); 54 | assert(chunk && b64); 55 | 56 | /*if (write_cfg->start) { 57 | if ((err = write_cfg->start(write_cfg->priv)) != ESP_OK) { 58 | return err; 59 | } 60 | }*/ 61 | 62 | ESP_LOGI(SVK_TAG, "Coredump is %u bytes", coredump_size); 63 | httpd_resp_set_status(request->request(), "200 OK"); 64 | PsychicResponse response(request); 65 | response.setCode(200); 66 | response.setContentType("text/plain"); 67 | response.sendHeaders(); 68 | for (size_t offset = 0; offset < coredump_size; offset += chunk_len) 69 | { 70 | uint const read_len = MIN(chunk_len, coredump_size - offset); 71 | if (esp_flash_read(esp_flash_default_chip, chunk, coredump_addr + offset, read_len)) 72 | { 73 | ESP_LOGE(SVK_TAG, "Coredump read failed"); 74 | break; 75 | } 76 | err = response.sendChunk(chunk, read_len); 77 | if (err != ESP_OK) 78 | { 79 | break; 80 | } 81 | } 82 | free(chunk); 83 | free(b64); 84 | 85 | err = response.finishChunking(); 86 | 87 | /*uint32_t sec_num = coredump_size / SPI_FLASH_SEC_SIZE; 88 | if (coredump_size % SPI_FLASH_SEC_SIZE) { 89 | sec_num++; 90 | } 91 | err = esp_flash_erase_region(esp_flash_default_chip, coredump_addr, sec_num * SPI_FLASH_SEC_SIZE); 92 | if (err != ESP_OK) { 93 | ESP_LOGE(SVK_TAG, "Failed to erase coredump (%d)!", err); 94 | }*/ 95 | return err; 96 | } -------------------------------------------------------------------------------- /lib/framework/CoreDump.h: -------------------------------------------------------------------------------- 1 | #ifndef CoreDump_h 2 | #define CoreDump_h 3 | 4 | /** 5 | * ESP32 SvelteKit 6 | * 7 | * A simple, secure and extensible framework for IoT projects for ESP32 platforms 8 | * with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. 9 | * https://github.com/theelims/ESP32-sveltekit 10 | * 11 | * Copyright (C) 2018 - 2023 rjwats 12 | * Copyright (C) 2023 - 2024 theelims 13 | * 14 | * All Rights Reserved. This software may be modified and distributed under 15 | * the terms of the LGPL v3 license. See the LICENSE file for details. 16 | **/ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #define CORE_DUMP_SERVICE_PATH "/rest/coreDump" 25 | 26 | class CoreDump 27 | { 28 | public: 29 | CoreDump(PsychicHttpServer *server, SecurityManager *securityManager); 30 | 31 | void begin(); 32 | 33 | private: 34 | PsychicHttpServer *_server; 35 | SecurityManager *_securityManager; 36 | esp_err_t coreDump(PsychicRequest *request); 37 | }; 38 | 39 | #endif // end CoreDump_h -------------------------------------------------------------------------------- /lib/framework/DownloadFirmwareService.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /** 4 | * ESP32 SvelteKit 5 | * 6 | * A simple, secure and extensible framework for IoT projects for ESP32 platforms 7 | * with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. 8 | * https://github.com/theelims/ESP32-sveltekit 9 | * 10 | * Copyright (C) 2018 - 2023 rjwats 11 | * Copyright (C) 2023 - 2024 theelims 12 | * 13 | * All Rights Reserved. This software may be modified and distributed under 14 | * the terms of the LGPL v3 license. See the LICENSE file for details. 15 | **/ 16 | 17 | #include 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include 26 | #include 27 | // #include 28 | 29 | #define GITHUB_FIRMWARE_PATH "/rest/downloadUpdate" 30 | #define EVENT_DOWNLOAD_OTA "otastatus" 31 | #define OTA_TASK_STACK_SIZE 9216 32 | 33 | class DownloadFirmwareService 34 | { 35 | public: 36 | DownloadFirmwareService(PsychicHttpServer *server, SecurityManager *securityManager, EventSocket *socket); 37 | 38 | void begin(); 39 | 40 | private: 41 | SecurityManager *_securityManager; 42 | PsychicHttpServer *_server; 43 | EventSocket *_socket; 44 | esp_err_t downloadUpdate(PsychicRequest *request, JsonVariant &json); 45 | }; 46 | -------------------------------------------------------------------------------- /lib/framework/ESPFS.h: -------------------------------------------------------------------------------- 1 | #ifndef ESPFS_H_ 2 | #define ESPFS_H_ 3 | 4 | /** 5 | * ESP32 SvelteKit 6 | * 7 | * A simple, secure and extensible framework for IoT projects for ESP32 platforms 8 | * with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. 9 | * https://github.com/theelims/ESP32-sveltekit 10 | * 11 | * Copyright (C) 2018 - 2023 rjwats 12 | * Copyright (C) 2023 - 2024 theelims 13 | * 14 | * All Rights Reserved. This software may be modified and distributed under 15 | * the terms of the LGPL v3 license. See the LICENSE file for details. 16 | **/ 17 | 18 | #include 19 | #define ESPFS LittleFS 20 | 21 | #endif -------------------------------------------------------------------------------- /lib/framework/EventEndpoint.h: -------------------------------------------------------------------------------- 1 | #ifndef EventEndpoint_h 2 | #define EventEndpoint_h 3 | 4 | /** 5 | * ESP32 SvelteKit 6 | * 7 | * A simple, secure and extensible framework for IoT projects for ESP32 platforms 8 | * with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. 9 | * https://github.com/theelims/ESP32-sveltekit 10 | * 11 | * Copyright (C) 2018 - 2023 rjwats 12 | * Copyright (C) 2023 - 2024 theelims 13 | * 14 | * All Rights Reserved. This software may be modified and distributed under 15 | * the terms of the LGPL v3 license. See the LICENSE file for details. 16 | **/ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | template 24 | class EventEndpoint 25 | { 26 | public: 27 | EventEndpoint(JsonStateReader stateReader, 28 | JsonStateUpdater stateUpdater, 29 | StatefulService *statefulService, 30 | EventSocket *socket, const char *event) : _stateReader(stateReader), 31 | _stateUpdater(stateUpdater), 32 | _statefulService(statefulService), 33 | _socket(socket), 34 | _event(event) 35 | { 36 | _statefulService->addUpdateHandler([&](const String &originId) 37 | { syncState(originId); }, 38 | false); 39 | } 40 | 41 | void begin() 42 | { 43 | _socket->registerEvent(_event); 44 | _socket->onEvent(_event, std::bind(&EventEndpoint::updateState, this, std::placeholders::_1, std::placeholders::_2)); 45 | _socket->onSubscribe(_event, [&](const String &originId) 46 | { syncState(originId, true); }); 47 | } 48 | 49 | private: 50 | JsonStateReader _stateReader; 51 | JsonStateUpdater _stateUpdater; 52 | StatefulService *_statefulService; 53 | EventSocket *_socket; 54 | const char *_event; 55 | 56 | void updateState(JsonObject &root, int originId) 57 | { 58 | _statefulService->update(root, _stateUpdater, String(originId)); 59 | } 60 | 61 | void syncState(const String &originId, bool sync = false) 62 | { 63 | JsonDocument jsonDocument; 64 | JsonObject root = jsonDocument.to(); 65 | _statefulService->read(root, _stateReader); 66 | JsonObject jsonObject = jsonDocument.as(); 67 | _socket->emitEvent(_event, jsonObject, originId.c_str(), sync); 68 | } 69 | }; 70 | 71 | #endif 72 | -------------------------------------------------------------------------------- /lib/framework/EventSocket.h: -------------------------------------------------------------------------------- 1 | #ifndef Socket_h 2 | #define Socket_h 3 | 4 | /** 5 | * ESP32 SvelteKit 6 | * 7 | * A simple, secure and extensible framework for IoT projects for ESP32 platforms 8 | * with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. 9 | * https://github.com/theelims/ESP32-sveltekit 10 | * 11 | * Copyright (C) 2018 - 2023 rjwats 12 | * Copyright (C) 2023 - 2024 theelims 13 | * 14 | * All Rights Reserved. This software may be modified and distributed under 15 | * the terms of the LGPL v3 license. See the LICENSE file for details. 16 | **/ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #define EVENT_SERVICE_PATH "/ws/events" 26 | 27 | typedef std::function EventCallback; 28 | typedef std::function SubscribeCallback; 29 | 30 | class EventSocket 31 | { 32 | public: 33 | EventSocket(PsychicHttpServer *server, SecurityManager *_securityManager, AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_AUTHENTICATED); 34 | 35 | void begin(); 36 | 37 | void registerEvent(String event); 38 | 39 | void onEvent(String event, EventCallback callback); 40 | 41 | void onSubscribe(String event, SubscribeCallback callback); 42 | 43 | void emitEvent(String event, JsonObject &jsonObject, const char *originId = "", bool onlyToSameOrigin = false); 44 | // if onlyToSameOrigin == true, the message will be sent to the originId only, otherwise it will be broadcasted to all clients except the originId 45 | 46 | unsigned int getConnectedClients(); 47 | 48 | private: 49 | PsychicHttpServer *_server; 50 | PsychicWebSocketHandler _socket; 51 | SecurityManager *_securityManager; 52 | AuthenticationPredicate _authenticationPredicate; 53 | 54 | std::vector events; 55 | std::map> client_subscriptions; 56 | std::map> event_callbacks; 57 | std::map> subscribe_callbacks; 58 | void handleEventCallbacks(String event, JsonObject &jsonObject, int originId); 59 | void handleSubscribeCallbacks(String event, const String &originId); 60 | 61 | bool isEventValid(String event); 62 | 63 | void onWSOpen(PsychicWebSocketClient *client); 64 | void onWSClose(PsychicWebSocketClient *client); 65 | esp_err_t onFrame(PsychicWebSocketRequest *request, httpd_ws_frame *frame); 66 | }; 67 | 68 | #endif 69 | -------------------------------------------------------------------------------- /lib/framework/FactoryResetService.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * ESP32 SvelteKit 3 | * 4 | * A simple, secure and extensible framework for IoT projects for ESP32 platforms 5 | * with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. 6 | * https://github.com/theelims/ESP32-sveltekit 7 | * 8 | * Copyright (C) 2018 - 2023 rjwats 9 | * Copyright (C) 2023 - 2024 theelims 10 | * 11 | * All Rights Reserved. This software may be modified and distributed under 12 | * the terms of the LGPL v3 license. See the LICENSE file for details. 13 | **/ 14 | 15 | #include 16 | 17 | using namespace std::placeholders; 18 | 19 | FactoryResetService::FactoryResetService(PsychicHttpServer *server, 20 | FS *fs, 21 | SecurityManager *securityManager) : _server(server), 22 | fs(fs), 23 | _securityManager(securityManager) 24 | { 25 | } 26 | 27 | void FactoryResetService::begin() 28 | { 29 | _server->on(FACTORY_RESET_SERVICE_PATH, 30 | HTTP_POST, 31 | _securityManager->wrapRequest(std::bind(&FactoryResetService::handleRequest, this, _1), AuthenticationPredicates::IS_ADMIN)); 32 | 33 | ESP_LOGV(SVK_TAG, "Registered POST endpoint: %s", FACTORY_RESET_SERVICE_PATH); 34 | } 35 | 36 | esp_err_t FactoryResetService::handleRequest(PsychicRequest *request) 37 | { 38 | request->reply(200); 39 | factoryReset(); 40 | 41 | return ESP_OK; 42 | } 43 | 44 | /** 45 | * Delete function assumes that all files are stored flat, within the config directory. 46 | */ 47 | void FactoryResetService::factoryReset() 48 | { 49 | File root = fs->open(FS_CONFIG_DIRECTORY); 50 | File file; 51 | while (file = root.openNextFile()) 52 | { 53 | String path = file.path(); 54 | file.close(); 55 | fs->remove(path); 56 | } 57 | RestartService::restartNow(); 58 | } 59 | -------------------------------------------------------------------------------- /lib/framework/FactoryResetService.h: -------------------------------------------------------------------------------- 1 | #ifndef FactoryResetService_h 2 | #define FactoryResetService_h 3 | 4 | /** 5 | * ESP32 SvelteKit 6 | * 7 | * A simple, secure and extensible framework for IoT projects for ESP32 platforms 8 | * with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. 9 | * https://github.com/theelims/ESP32-sveltekit 10 | * 11 | * Copyright (C) 2018 - 2023 rjwats 12 | * Copyright (C) 2023 - 2024 theelims 13 | * 14 | * All Rights Reserved. This software may be modified and distributed under 15 | * the terms of the LGPL v3 license. See the LICENSE file for details. 16 | **/ 17 | 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #define FS_CONFIG_DIRECTORY "/config" 26 | #define FACTORY_RESET_SERVICE_PATH "/rest/factoryReset" 27 | 28 | class FactoryResetService 29 | { 30 | FS *fs; 31 | 32 | public: 33 | FactoryResetService(PsychicHttpServer *server, FS *fs, SecurityManager *securityManager); 34 | 35 | void begin(); 36 | void factoryReset(); 37 | 38 | private: 39 | PsychicHttpServer *_server; 40 | SecurityManager *_securityManager; 41 | esp_err_t handleRequest(PsychicRequest *request); 42 | }; 43 | 44 | #endif // end FactoryResetService_h 45 | -------------------------------------------------------------------------------- /lib/framework/Features.h: -------------------------------------------------------------------------------- 1 | #ifndef Features_h 2 | #define Features_h 3 | 4 | /** 5 | * ESP32 SvelteKit 6 | * 7 | * A simple, secure and extensible framework for IoT projects for ESP32 platforms 8 | * with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. 9 | * https://github.com/theelims/ESP32-sveltekit 10 | * 11 | * Copyright (C) 2018 - 2023 rjwats 12 | * Copyright (C) 2023 - 2024 theelims 13 | * 14 | * All Rights Reserved. This software may be modified and distributed under 15 | * the terms of the LGPL v3 license. See the LICENSE file for details. 16 | **/ 17 | 18 | #define FT_ENABLED(feature) feature 19 | 20 | // security feature on by default 21 | #ifndef FT_SECURITY 22 | #define FT_SECURITY 1 23 | #endif 24 | 25 | // mqtt feature on by default 26 | #ifndef FT_MQTT 27 | #define FT_MQTT 1 28 | #endif 29 | 30 | // ntp feature on by default 31 | #ifndef FT_NTP 32 | #define FT_NTP 1 33 | #endif 34 | 35 | // upload firmware feature off by default 36 | #ifndef FT_UPLOAD_FIRMWARE 37 | #define FT_UPLOAD_FIRMWARE 0 38 | #endif 39 | 40 | // download firmware feature off by default 41 | #ifndef FT_DOWNLOAD_FIRMWARE 42 | #define FT_DOWNLOAD_FIRMWARE 0 43 | #endif 44 | 45 | // ESP32 sleep states off by default 46 | #ifndef FT_SLEEP 47 | #define FT_SLEEP 0 48 | #endif 49 | 50 | // ESP32 battery state off by default 51 | #ifndef FT_BATTERY 52 | #define FT_BATTERY 0 53 | #endif 54 | 55 | // ESP32 analytics on by default 56 | #ifndef FT_ANALYTICS 57 | #define FT_ANALYTICS 1 58 | #endif 59 | 60 | // Use JSON for events. Default, use MessagePack for events 61 | #ifndef EVENT_USE_JSON 62 | #define EVENT_USE_JSON 0 63 | #endif 64 | 65 | // Endpoint for Core Dump, off by default 66 | #ifndef FT_COREDUMP 67 | #define FT_COREDUMP 0 68 | #endif 69 | 70 | #endif 71 | -------------------------------------------------------------------------------- /lib/framework/FeaturesService.h: -------------------------------------------------------------------------------- 1 | #ifndef FeaturesService_h 2 | #define FeaturesService_h 3 | 4 | /** 5 | * ESP32 SvelteKit 6 | * 7 | * A simple, secure and extensible framework for IoT projects for ESP32 platforms 8 | * with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. 9 | * https://github.com/theelims/ESP32-sveltekit 10 | * 11 | * Copyright (C) 2018 - 2023 rjwats 12 | * Copyright (C) 2023 - 2024 theelims 13 | * 14 | * All Rights Reserved. This software may be modified and distributed under 15 | * the terms of the LGPL v3 license. See the LICENSE file for details. 16 | **/ 17 | 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #define FEATURES_SERVICE_PATH "/rest/features" 27 | #define FEATURES_SERVICE_EVENT "features" 28 | 29 | typedef struct 30 | { 31 | String feature; 32 | bool enabled; 33 | } UserFeature; 34 | 35 | class FeaturesService 36 | { 37 | public: 38 | FeaturesService(PsychicHttpServer *server, EventSocket *socket); 39 | 40 | void begin(); 41 | 42 | void addFeature(String feature, bool enabled); 43 | 44 | private: 45 | PsychicHttpServer *_server; 46 | EventSocket *_socket; 47 | std::vector userFeatures; 48 | 49 | void createJSON(JsonObject &root); 50 | }; 51 | 52 | #endif 53 | -------------------------------------------------------------------------------- /lib/framework/IPUtils.h: -------------------------------------------------------------------------------- 1 | #ifndef IPUtils_h 2 | #define IPUtils_h 3 | 4 | /** 5 | * ESP32 SvelteKit 6 | * 7 | * A simple, secure and extensible framework for IoT projects for ESP32 platforms 8 | * with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. 9 | * https://github.com/theelims/ESP32-sveltekit 10 | * 11 | * Copyright (C) 2018 - 2023 rjwats 12 | * Copyright (C) 2023 - 2024 theelims 13 | * 14 | * All Rights Reserved. This software may be modified and distributed under 15 | * the terms of the LGPL v3 license. See the LICENSE file for details. 16 | **/ 17 | 18 | #include 19 | 20 | const IPAddress IP_NOT_SET = IPAddress(INADDR_NONE); 21 | 22 | class IPUtils 23 | { 24 | public: 25 | static bool isSet(const IPAddress &ip) 26 | { 27 | return ip != IP_NOT_SET; 28 | } 29 | static bool isNotSet(const IPAddress &ip) 30 | { 31 | return ip == IP_NOT_SET; 32 | } 33 | }; 34 | 35 | #endif // end IPUtils_h 36 | -------------------------------------------------------------------------------- /lib/framework/JsonUtils.h: -------------------------------------------------------------------------------- 1 | #ifndef JsonUtils_h 2 | #define JsonUtils_h 3 | 4 | /** 5 | * ESP32 SvelteKit 6 | * 7 | * A simple, secure and extensible framework for IoT projects for ESP32 platforms 8 | * with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. 9 | * https://github.com/theelims/ESP32-sveltekit 10 | * 11 | * Copyright (C) 2018 - 2023 rjwats 12 | * Copyright (C) 2023 - 2024 theelims 13 | * 14 | * All Rights Reserved. This software may be modified and distributed under 15 | * the terms of the LGPL v3 license. See the LICENSE file for details. 16 | **/ 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | class JsonUtils 23 | { 24 | public: 25 | static void readIPStr(JsonObject &root, const String &key, IPAddress &ip, const String &def) 26 | { 27 | IPAddress defaultIp = {}; 28 | if (!defaultIp.fromString(def)) 29 | { 30 | defaultIp = INADDR_NONE; 31 | } 32 | readIP(root, key, ip, defaultIp); 33 | } 34 | 35 | static void readIP(JsonObject &root, const String &key, IPAddress &ip, const IPAddress &defaultIp = INADDR_NONE) 36 | { 37 | if (!root[key].is() || !ip.fromString(root[key].as())) 38 | { 39 | ip = defaultIp; 40 | } 41 | } 42 | 43 | static void writeIP(JsonObject &root, const String &key, const IPAddress &ip) 44 | { 45 | if (IPUtils::isSet(ip)) 46 | { 47 | root[key] = ip.toString(); 48 | } 49 | } 50 | }; 51 | 52 | #endif // end JsonUtils 53 | -------------------------------------------------------------------------------- /lib/framework/MqttStatus.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * ESP32 SvelteKit 3 | * 4 | * A simple, secure and extensible framework for IoT projects for ESP32 platforms 5 | * with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. 6 | * https://github.com/theelims/ESP32-sveltekit 7 | * 8 | * Copyright (C) 2018 - 2023 rjwats 9 | * Copyright (C) 2023 - 2024 theelims 10 | * 11 | * All Rights Reserved. This software may be modified and distributed under 12 | * the terms of the LGPL v3 license. See the LICENSE file for details. 13 | **/ 14 | 15 | #include 16 | 17 | MqttStatus::MqttStatus(PsychicHttpServer *server, 18 | MqttSettingsService *mqttSettingsService, 19 | SecurityManager *securityManager) : _server(server), 20 | _securityManager(securityManager), 21 | _mqttSettingsService(mqttSettingsService) 22 | { 23 | } 24 | 25 | void MqttStatus::begin() 26 | { 27 | _server->on(MQTT_STATUS_SERVICE_PATH, 28 | HTTP_GET, 29 | _securityManager->wrapRequest(std::bind(&MqttStatus::mqttStatus, this, std::placeholders::_1), 30 | AuthenticationPredicates::IS_AUTHENTICATED)); 31 | 32 | ESP_LOGV(SVK_TAG, "Registered GET endpoint: %s", MQTT_STATUS_SERVICE_PATH); 33 | } 34 | 35 | esp_err_t MqttStatus::mqttStatus(PsychicRequest *request) 36 | { 37 | PsychicJsonResponse response = PsychicJsonResponse(request, false); 38 | JsonObject root = response.getRoot(); 39 | 40 | root["enabled"] = _mqttSettingsService->isEnabled(); 41 | root["connected"] = _mqttSettingsService->isConnected(); 42 | root["client_id"] = _mqttSettingsService->getClientId(); 43 | root["last_error"] = _mqttSettingsService->getLastError(); 44 | 45 | return response.send(); 46 | } 47 | 48 | bool MqttStatus::isConnected() 49 | { 50 | return _mqttSettingsService->isConnected(); 51 | } 52 | -------------------------------------------------------------------------------- /lib/framework/MqttStatus.h: -------------------------------------------------------------------------------- 1 | #ifndef MqttStatus_h 2 | #define MqttStatus_h 3 | 4 | /** 5 | * ESP32 SvelteKit 6 | * 7 | * A simple, secure and extensible framework for IoT projects for ESP32 platforms 8 | * with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. 9 | * https://github.com/theelims/ESP32-sveltekit 10 | * 11 | * Copyright (C) 2018 - 2023 rjwats 12 | * Copyright (C) 2023 - 2024 theelims 13 | * 14 | * All Rights Reserved. This software may be modified and distributed under 15 | * the terms of the LGPL v3 license. See the LICENSE file for details. 16 | **/ 17 | 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #define MQTT_STATUS_SERVICE_PATH "/rest/mqttStatus" 26 | 27 | class MqttStatus 28 | { 29 | public: 30 | MqttStatus(PsychicHttpServer *server, MqttSettingsService *mqttSettingsService, SecurityManager *securityManager); 31 | 32 | void begin(); 33 | 34 | bool isConnected(); 35 | 36 | private: 37 | PsychicHttpServer *_server; 38 | SecurityManager *_securityManager; 39 | MqttSettingsService *_mqttSettingsService; 40 | 41 | esp_err_t mqttStatus(PsychicRequest *request); 42 | }; 43 | 44 | #endif // end MqttStatus_h 45 | -------------------------------------------------------------------------------- /lib/framework/NTPSettingsService.h: -------------------------------------------------------------------------------- 1 | #ifndef NTPSettingsService_h 2 | #define NTPSettingsService_h 3 | 4 | /** 5 | * ESP32 SvelteKit 6 | * 7 | * A simple, secure and extensible framework for IoT projects for ESP32 platforms 8 | * with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. 9 | * https://github.com/theelims/ESP32-sveltekit 10 | * 11 | * Copyright (C) 2018 - 2023 rjwats 12 | * Copyright (C) 2023 - 2024 theelims 13 | * 14 | * All Rights Reserved. This software may be modified and distributed under 15 | * the terms of the LGPL v3 license. See the LICENSE file for details. 16 | **/ 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | #include 23 | #include 24 | 25 | #ifdef CONFIG_LWIP_TCPIP_CORE_LOCKING 26 | #include "lwip/priv/tcpip_priv.h" 27 | #endif 28 | 29 | #ifndef FACTORY_NTP_ENABLED 30 | #define FACTORY_NTP_ENABLED true 31 | #endif 32 | 33 | #ifndef FACTORY_NTP_TIME_ZONE_LABEL 34 | #define FACTORY_NTP_TIME_ZONE_LABEL "Europe/London" 35 | #endif 36 | 37 | #ifndef FACTORY_NTP_TIME_ZONE_FORMAT 38 | #define FACTORY_NTP_TIME_ZONE_FORMAT "GMT0BST,M3.5.0/1,M10.5.0" 39 | #endif 40 | 41 | #ifndef FACTORY_NTP_SERVER 42 | #define FACTORY_NTP_SERVER "time.google.com" 43 | #endif 44 | 45 | #define NTP_SETTINGS_FILE "/config/ntpSettings.json" 46 | #define NTP_SETTINGS_SERVICE_PATH "/rest/ntpSettings" 47 | 48 | #define TIME_PATH "/rest/time" 49 | 50 | class NTPSettings 51 | { 52 | public: 53 | bool enabled; 54 | String tzLabel; 55 | String tzFormat; 56 | String server; 57 | 58 | static void read(NTPSettings &settings, JsonObject &root) 59 | { 60 | root["enabled"] = settings.enabled; 61 | root["server"] = settings.server; 62 | root["tz_label"] = settings.tzLabel; 63 | root["tz_format"] = settings.tzFormat; 64 | } 65 | 66 | static StateUpdateResult update(JsonObject &root, NTPSettings &settings) 67 | { 68 | settings.enabled = root["enabled"] | FACTORY_NTP_ENABLED; 69 | settings.server = root["server"] | FACTORY_NTP_SERVER; 70 | settings.tzLabel = root["tz_label"] | FACTORY_NTP_TIME_ZONE_LABEL; 71 | settings.tzFormat = root["tz_format"] | FACTORY_NTP_TIME_ZONE_FORMAT; 72 | return StateUpdateResult::CHANGED; 73 | } 74 | }; 75 | 76 | class NTPSettingsService : public StatefulService 77 | { 78 | public: 79 | NTPSettingsService(PsychicHttpServer *server, FS *fs, SecurityManager *securityManager); 80 | 81 | void begin(); 82 | 83 | private: 84 | PsychicHttpServer *_server; 85 | SecurityManager *_securityManager; 86 | HttpEndpoint _httpEndpoint; 87 | FSPersistence _fsPersistence; 88 | 89 | void onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info); 90 | void onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info); 91 | void configureNTP(); 92 | esp_err_t configureTime(PsychicRequest *request, JsonVariant &json); 93 | }; 94 | 95 | #endif // end NTPSettingsService_h 96 | -------------------------------------------------------------------------------- /lib/framework/NTPStatus.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * ESP32 SvelteKit 3 | * 4 | * A simple, secure and extensible framework for IoT projects for ESP32 platforms 5 | * with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. 6 | * https://github.com/theelims/ESP32-sveltekit 7 | * 8 | * Copyright (C) 2018 - 2023 rjwats 9 | * Copyright (C) 2023 - 2024 theelims 10 | * 11 | * All Rights Reserved. This software may be modified and distributed under 12 | * the terms of the LGPL v3 license. See the LICENSE file for details. 13 | **/ 14 | 15 | #include 16 | 17 | NTPStatus::NTPStatus(PsychicHttpServer *server, SecurityManager *securityManager) : _server(server), 18 | _securityManager(securityManager) 19 | { 20 | } 21 | 22 | void NTPStatus::begin() 23 | { 24 | _server->on(NTP_STATUS_SERVICE_PATH, 25 | HTTP_GET, 26 | _securityManager->wrapRequest(std::bind(&NTPStatus::ntpStatus, this, std::placeholders::_1), 27 | AuthenticationPredicates::IS_AUTHENTICATED)); 28 | 29 | ESP_LOGV(SVK_TAG, "Registered GET endpoint: %s", NTP_STATUS_SERVICE_PATH); 30 | } 31 | 32 | /* 33 | * Formats the time using the format provided. 34 | * 35 | * Uses a 25 byte buffer, large enough to fit an ISO time string with offset. 36 | */ 37 | String formatTime(tm *time, const char *format) 38 | { 39 | char time_string[25]; 40 | strftime(time_string, 25, format, time); 41 | return String(time_string); 42 | } 43 | 44 | String toUTCTimeString(tm *time) 45 | { 46 | return formatTime(time, "%FT%TZ"); 47 | } 48 | 49 | String toLocalTimeString(tm *time) 50 | { 51 | return formatTime(time, "%FT%T"); 52 | } 53 | 54 | esp_err_t NTPStatus::ntpStatus(PsychicRequest *request) 55 | { 56 | PsychicJsonResponse response = PsychicJsonResponse(request, false); 57 | JsonObject root = response.getRoot(); 58 | 59 | // grab the current instant in unix seconds 60 | time_t now = time(nullptr); 61 | 62 | // only provide enabled/disabled status for now 63 | root["status"] = sntp_enabled() ? 1 : 0; 64 | 65 | // the current time in UTC 66 | root["utc_time"] = toUTCTimeString(gmtime(&now)); 67 | 68 | // local time with offset 69 | root["local_time"] = toLocalTimeString(localtime(&now)); 70 | 71 | // the sntp server name 72 | root["server"] = sntp_getservername(0); 73 | 74 | // device uptime in seconds 75 | root["uptime"] = millis() / 1000; 76 | 77 | return response.send(); 78 | } 79 | -------------------------------------------------------------------------------- /lib/framework/NTPStatus.h: -------------------------------------------------------------------------------- 1 | #ifndef NTPStatus_h 2 | #define NTPStatus_h 3 | 4 | /** 5 | * ESP32 SvelteKit 6 | * 7 | * A simple, secure and extensible framework for IoT projects for ESP32 platforms 8 | * with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. 9 | * https://github.com/theelims/ESP32-sveltekit 10 | * 11 | * Copyright (C) 2018 - 2023 rjwats 12 | * Copyright (C) 2023 - 2024 theelims 13 | * 14 | * All Rights Reserved. This software may be modified and distributed under 15 | * the terms of the LGPL v3 license. See the LICENSE file for details. 16 | **/ 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | #define NTP_STATUS_SERVICE_PATH "/rest/ntpStatus" 27 | 28 | class NTPStatus 29 | { 30 | public: 31 | NTPStatus(PsychicHttpServer *server, SecurityManager *securityManager); 32 | 33 | void begin(); 34 | 35 | private: 36 | PsychicHttpServer *_server; 37 | SecurityManager *_securityManager; 38 | esp_err_t ntpStatus(PsychicRequest *request); 39 | }; 40 | 41 | #endif // end NTPStatus_h 42 | -------------------------------------------------------------------------------- /lib/framework/NotificationService.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // array translating pushType into strings 4 | const char *pushTypeStrings[] = {"error", "warning", "info", "success"}; 5 | 6 | NotificationService::NotificationService(EventSocket *eventSocket) : _eventSocket(eventSocket) 7 | { 8 | } 9 | 10 | void NotificationService::begin() 11 | { 12 | _eventSocket->registerEvent(NOTIFICATION_EVENT); 13 | } 14 | 15 | void NotificationService::pushNotification(String message, pushType event) 16 | { 17 | JsonDocument doc; 18 | doc["type"] = pushTypeStrings[event]; 19 | doc["message"] = message; 20 | JsonObject jsonObject = doc.as(); 21 | _eventSocket->emitEvent(NOTIFICATION_EVENT, jsonObject); 22 | } 23 | -------------------------------------------------------------------------------- /lib/framework/NotificationService.h: -------------------------------------------------------------------------------- 1 | #ifndef NotificationService_h 2 | #define NotificationService_h 3 | 4 | /** 5 | * ESP32 SvelteKit 6 | * 7 | * A simple, secure and extensible framework for IoT projects for ESP32 platforms 8 | * with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. 9 | * https://github.com/theelims/ESP32-sveltekit 10 | * 11 | * Copyright (C) 2018 - 2023 rjwats 12 | * Copyright (C) 2023 - 2024 theelims 13 | * 14 | * All Rights Reserved. This software may be modified and distributed under 15 | * the terms of the LGPL v3 license. See the LICENSE file for details. 16 | **/ 17 | 18 | #include 19 | 20 | #define NOTIFICATION_EVENT "notification" 21 | 22 | enum pushType 23 | { 24 | PUSHERROR, 25 | PUSHWARNING, 26 | PUSHINFO, 27 | PUSHSUCCESS 28 | }; 29 | 30 | class NotificationService 31 | { 32 | public: 33 | NotificationService(EventSocket *eventSocket); 34 | 35 | void begin(); 36 | 37 | void pushNotification(String message, pushType event); 38 | 39 | private: 40 | EventSocket *_eventSocket; 41 | }; 42 | 43 | #endif // NotificationService_h -------------------------------------------------------------------------------- /lib/framework/RestartService.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * ESP32 SvelteKit 3 | * 4 | * A simple, secure and extensible framework for IoT projects for ESP32 platforms 5 | * with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. 6 | * https://github.com/theelims/ESP32-sveltekit 7 | * 8 | * Copyright (C) 2018 - 2023 rjwats 9 | * Copyright (C) 2023 - 2024 theelims 10 | * 11 | * All Rights Reserved. This software may be modified and distributed under 12 | * the terms of the LGPL v3 license. See the LICENSE file for details. 13 | **/ 14 | 15 | #include 16 | 17 | RestartService::RestartService(PsychicHttpServer *server, SecurityManager *securityManager) : _server(server), 18 | _securityManager(securityManager) 19 | { 20 | } 21 | 22 | void RestartService::begin() 23 | { 24 | _server->on(RESTART_SERVICE_PATH, 25 | HTTP_POST, 26 | _securityManager->wrapRequest(std::bind(&RestartService::restart, this, std::placeholders::_1), 27 | AuthenticationPredicates::IS_ADMIN)); 28 | 29 | ESP_LOGV(SVK_TAG, "Registered POST endpoint: %s", RESTART_SERVICE_PATH); 30 | } 31 | 32 | esp_err_t RestartService::restart(PsychicRequest *request) 33 | { 34 | request->reply(200); 35 | restartNow(); 36 | return ESP_OK; 37 | } 38 | -------------------------------------------------------------------------------- /lib/framework/RestartService.h: -------------------------------------------------------------------------------- 1 | #ifndef RestartService_h 2 | #define RestartService_h 3 | 4 | /** 5 | * ESP32 SvelteKit 6 | * 7 | * A simple, secure and extensible framework for IoT projects for ESP32 platforms 8 | * with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. 9 | * https://github.com/theelims/ESP32-sveltekit 10 | * 11 | * Copyright (C) 2018 - 2023 rjwats 12 | * Copyright (C) 2023 - 2024 theelims 13 | * 14 | * All Rights Reserved. This software may be modified and distributed under 15 | * the terms of the LGPL v3 license. See the LICENSE file for details. 16 | **/ 17 | 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #define RESTART_SERVICE_PATH "/rest/restart" 25 | 26 | class RestartService 27 | { 28 | public: 29 | RestartService(PsychicHttpServer *server, SecurityManager *securityManager); 30 | 31 | void begin(); 32 | 33 | static void restartNow() 34 | { 35 | xTaskCreate( 36 | [](void *pvParams) { 37 | delay(250); 38 | MDNS.end(); 39 | delay(100); 40 | WiFi.disconnect(true); 41 | delay(500); 42 | ESP.restart(); 43 | }, 44 | "Restart task", 4096, nullptr, 10, nullptr); 45 | } 46 | 47 | private: 48 | PsychicHttpServer *_server; 49 | SecurityManager *_securityManager; 50 | esp_err_t restart(PsychicRequest *request); 51 | }; 52 | 53 | #endif // end RestartService_h 54 | -------------------------------------------------------------------------------- /lib/framework/SecurityManager.h: -------------------------------------------------------------------------------- 1 | #ifndef SecurityManager_h 2 | #define SecurityManager_h 3 | 4 | /** 5 | * ESP32 SvelteKit 6 | * 7 | * A simple, secure and extensible framework for IoT projects for ESP32 platforms 8 | * with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. 9 | * https://github.com/theelims/ESP32-sveltekit 10 | * 11 | * Copyright (C) 2018 - 2023 rjwats 12 | * Copyright (C) 2023 - 2024 theelims 13 | * 14 | * All Rights Reserved. This software may be modified and distributed under 15 | * the terms of the LGPL v3 license. See the LICENSE file for details. 16 | **/ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #define SVK_TAG "🐼" 24 | 25 | #define ACCESS_TOKEN_PARAMATER "access_token" 26 | 27 | #define AUTHORIZATION_HEADER "Authorization" 28 | #define AUTHORIZATION_HEADER_PREFIX "Bearer " 29 | #define AUTHORIZATION_HEADER_PREFIX_LEN 7 30 | 31 | class User 32 | { 33 | public: 34 | String username; 35 | String password; 36 | bool admin; 37 | 38 | public: 39 | User(String username, String password, bool admin) : username(username), password(password), admin(admin) 40 | { 41 | } 42 | }; 43 | 44 | class Authentication 45 | { 46 | public: 47 | User *user; 48 | boolean authenticated; 49 | 50 | public: 51 | Authentication(User &user) : user(new User(user)), authenticated(true) 52 | { 53 | } 54 | Authentication() : user(nullptr), authenticated(false) 55 | { 56 | } 57 | ~Authentication() 58 | { 59 | delete (user); 60 | } 61 | }; 62 | 63 | typedef std::function AuthenticationPredicate; 64 | 65 | class AuthenticationPredicates 66 | { 67 | public: 68 | static bool NONE_REQUIRED(Authentication &authentication) 69 | { 70 | return true; 71 | }; 72 | static bool IS_AUTHENTICATED(Authentication &authentication) 73 | { 74 | return authentication.authenticated; 75 | }; 76 | static bool IS_ADMIN(Authentication &authentication) 77 | { 78 | return authentication.authenticated && authentication.user->admin; 79 | }; 80 | }; 81 | 82 | class SecurityManager 83 | { 84 | public: 85 | #if FT_ENABLED(FT_SECURITY) 86 | /* 87 | * Authenticate, returning the user if found 88 | */ 89 | virtual Authentication authenticate(const String &username, const String &password) = 0; 90 | 91 | /* 92 | * Generate a JWT for the user provided 93 | */ 94 | virtual String generateJWT(User *user) = 0; 95 | 96 | #endif 97 | 98 | /* 99 | * Check the request header for the Authorization token 100 | */ 101 | virtual Authentication authenticateRequest(PsychicRequest *request) = 0; 102 | 103 | /** 104 | * Filter a request with the provided predicate, only returning true if the predicate matches. 105 | */ 106 | virtual PsychicRequestFilterFunction filterRequest(AuthenticationPredicate predicate) = 0; 107 | 108 | /** 109 | * Wrap the provided request to provide validation against an AuthenticationPredicate. 110 | */ 111 | virtual PsychicHttpRequestCallback wrapRequest(PsychicHttpRequestCallback onRequest, AuthenticationPredicate predicate) = 0; 112 | 113 | /** 114 | * Wrap the provided json request callback to provide validation against an AuthenticationPredicate. 115 | */ 116 | virtual PsychicJsonRequestCallback wrapCallback(PsychicJsonRequestCallback onRequest, AuthenticationPredicate predicate) = 0; 117 | }; 118 | 119 | #endif // end SecurityManager_h 120 | -------------------------------------------------------------------------------- /lib/framework/SettingValue.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * ESP32 SvelteKit 3 | * 4 | * A simple, secure and extensible framework for IoT projects for ESP32 platforms 5 | * with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. 6 | * https://github.com/theelims/ESP32-sveltekit 7 | * 8 | * Copyright (C) 2018 - 2023 rjwats 9 | * Copyright (C) 2023 - 2024 theelims 10 | * 11 | * All Rights Reserved. This software may be modified and distributed under 12 | * the terms of the LGPL v3 license. See the LICENSE file for details. 13 | **/ 14 | 15 | #include 16 | 17 | namespace SettingValue 18 | { 19 | const String PLATFORM = "esp32"; 20 | 21 | /** 22 | * Returns a new string after replacing each instance of the pattern with a value generated by calling the provided 23 | * callback. 24 | */ 25 | String replaceEach(String value, String pattern, String (*generateReplacement)()) 26 | { 27 | while (true) 28 | { 29 | int index = value.indexOf(pattern); 30 | if (index == -1) 31 | { 32 | break; 33 | } 34 | value = value.substring(0, index) + generateReplacement() + value.substring(index + pattern.length()); 35 | } 36 | return value; 37 | } 38 | 39 | /** 40 | * Generates a random number, encoded as a hex string. 41 | */ 42 | String getRandom() 43 | { 44 | return String(random(2147483647), HEX); 45 | } 46 | 47 | /** 48 | * Uses the station's MAC address to create a unique id for each device. 49 | */ 50 | String getUniqueId() 51 | { 52 | uint8_t mac[6]; 53 | esp_read_mac(mac, ESP_MAC_WIFI_STA); 54 | char macStr[13] = {0}; 55 | sprintf(macStr, "%02x%02x%02x%02x%02x%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); 56 | return String(macStr); 57 | } 58 | 59 | String format(String value) 60 | { 61 | value = replaceEach(value, "#{random}", getRandom); 62 | value.replace("#{unique_id}", getUniqueId()); 63 | value.replace("#{platform}", PLATFORM); 64 | return value; 65 | } 66 | 67 | }; // end namespace SettingValue 68 | -------------------------------------------------------------------------------- /lib/framework/SettingValue.h: -------------------------------------------------------------------------------- 1 | #ifndef SettingValue_h 2 | #define SettingValue_h 3 | 4 | /** 5 | * ESP32 SvelteKit 6 | * 7 | * A simple, secure and extensible framework for IoT projects for ESP32 platforms 8 | * with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. 9 | * https://github.com/theelims/ESP32-sveltekit 10 | * 11 | * Copyright (C) 2018 - 2023 rjwats 12 | * Copyright (C) 2023 - 2024 theelims 13 | * 14 | * All Rights Reserved. This software may be modified and distributed under 15 | * the terms of the LGPL v3 license. See the LICENSE file for details. 16 | **/ 17 | 18 | #include 19 | 20 | #if ESP_ARDUINO_VERSION_MAJOR == 3 21 | #include 22 | #endif 23 | 24 | namespace SettingValue 25 | { 26 | String format(String value); 27 | }; 28 | 29 | #endif // end SettingValue 30 | -------------------------------------------------------------------------------- /lib/framework/SleepService.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /** 4 | * ESP32 SvelteKit 5 | * 6 | * A simple, secure and extensible framework for IoT projects for ESP32 platforms 7 | * with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. 8 | * https://github.com/theelims/ESP32-sveltekit 9 | * 10 | * Copyright (C) 2023 - 2024 theelims 11 | * 12 | * All Rights Reserved. This software may be modified and distributed under 13 | * the terms of the LGPL v3 license. See the LICENSE file for details. 14 | **/ 15 | 16 | #include 17 | #include 18 | 19 | #include 20 | #include 21 | #include "driver/rtc_io.h" 22 | 23 | #define SLEEP_SERVICE_PATH "/rest/sleep" 24 | 25 | #ifndef WAKEUP_PIN_NUMBER 26 | #define WAKEUP_PIN_NUMBER 0 27 | #endif 28 | 29 | #ifndef WAKEUP_SIGNAL 30 | #define WAKEUP_SIGNAL 0 31 | #endif 32 | 33 | enum class pinTermination 34 | { 35 | FLOATING, 36 | PULL_UP, 37 | PULL_DOWN 38 | }; 39 | 40 | // typdef for sleep service callback 41 | typedef std::function sleepCallback; 42 | 43 | class SleepService 44 | { 45 | public: 46 | SleepService(PsychicHttpServer *server, SecurityManager *securityManager); 47 | 48 | void begin(); 49 | 50 | static void sleepNow(); 51 | 52 | void attachOnSleepCallback(sleepCallback callbackSleep) 53 | { 54 | _callbackSleep = callbackSleep; 55 | } 56 | 57 | void setWakeUpPin(int pin, bool level, pinTermination termination = pinTermination::FLOATING); 58 | 59 | private: 60 | PsychicHttpServer *_server; 61 | SecurityManager *_securityManager; 62 | esp_err_t sleep(PsychicRequest *request); 63 | 64 | protected: 65 | static sleepCallback _callbackSleep; 66 | }; 67 | -------------------------------------------------------------------------------- /lib/framework/StatefulService.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * ESP32 SvelteKit 3 | * 4 | * A simple, secure and extensible framework for IoT projects for ESP32 platforms 5 | * with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. 6 | * https://github.com/theelims/ESP32-sveltekit 7 | * 8 | * Copyright (C) 2018 - 2023 rjwats 9 | * Copyright (C) 2023 - 2024 theelims 10 | * 11 | * All Rights Reserved. This software may be modified and distributed under 12 | * the terms of the LGPL v3 license. See the LICENSE file for details. 13 | **/ 14 | 15 | #include 16 | 17 | update_handler_id_t StateUpdateHandlerInfo::currentUpdatedHandlerId = 0; 18 | hook_handler_id_t StateHookHandlerInfo::currentHookHandlerId = 0; 19 | -------------------------------------------------------------------------------- /lib/framework/SystemStatus.h: -------------------------------------------------------------------------------- 1 | #ifndef SystemStatus_h 2 | #define SystemStatus_h 3 | 4 | /** 5 | * ESP32 SvelteKit 6 | * 7 | * A simple, secure and extensible framework for IoT projects for ESP32 platforms 8 | * with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. 9 | * https://github.com/theelims/ESP32-sveltekit 10 | * 11 | * Copyright (C) 2018 - 2023 rjwats 12 | * Copyright (C) 2023 - 2024 theelims 13 | * 14 | * All Rights Reserved. This software may be modified and distributed under 15 | * the terms of the LGPL v3 license. See the LICENSE file for details. 16 | **/ 17 | 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #define SYSTEM_STATUS_SERVICE_PATH "/rest/systemStatus" 26 | 27 | class SystemStatus 28 | { 29 | public: 30 | SystemStatus(PsychicHttpServer *server, SecurityManager *securityManager); 31 | 32 | void begin(); 33 | 34 | private: 35 | PsychicHttpServer *_server; 36 | SecurityManager *_securityManager; 37 | esp_err_t systemStatus(PsychicRequest *request); 38 | }; 39 | 40 | #endif // end SystemStatus_h 41 | -------------------------------------------------------------------------------- /lib/framework/UploadFirmwareService.h: -------------------------------------------------------------------------------- 1 | #ifndef UploadFirmwareService_h 2 | #define UploadFirmwareService_h 3 | 4 | /** 5 | * ESP32 SvelteKit 6 | * 7 | * A simple, secure and extensible framework for IoT projects for ESP32 platforms 8 | * with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. 9 | * https://github.com/theelims/ESP32-sveltekit 10 | * 11 | * Copyright (C) 2018 - 2023 rjwats 12 | * Copyright (C) 2023 - 2024 theelims 13 | * 14 | * All Rights Reserved. This software may be modified and distributed under 15 | * the terms of the LGPL v3 license. See the LICENSE file for details. 16 | **/ 17 | 18 | #include 19 | 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #define UPLOAD_FIRMWARE_PATH "/rest/uploadFirmware" 28 | 29 | enum FileType 30 | { 31 | ft_none = 0, 32 | ft_firmware = 1, 33 | ft_md5 = 2 34 | }; 35 | 36 | class UploadFirmwareService 37 | { 38 | public: 39 | UploadFirmwareService(PsychicHttpServer *server, SecurityManager *securityManager); 40 | 41 | void begin(); 42 | 43 | private: 44 | PsychicHttpServer *_server; 45 | SecurityManager *_securityManager; 46 | 47 | esp_err_t handleUpload(PsychicRequest *request, 48 | const String &filename, 49 | uint64_t index, 50 | uint8_t *data, 51 | size_t len, 52 | bool final); 53 | esp_err_t uploadComplete(PsychicRequest *request); 54 | esp_err_t handleError(PsychicRequest *request, int code); 55 | esp_err_t handleEarlyDisconnect(); 56 | }; 57 | 58 | #endif // end UploadFirmwareService_h 59 | -------------------------------------------------------------------------------- /lib/framework/WiFiScanner.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * ESP32 SvelteKit 3 | * 4 | * A simple, secure and extensible framework for IoT projects for ESP32 platforms 5 | * with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. 6 | * https://github.com/theelims/ESP32-sveltekit 7 | * 8 | * Copyright (C) 2018 - 2023 rjwats 9 | * Copyright (C) 2023 - 2024 theelims 10 | * 11 | * All Rights Reserved. This software may be modified and distributed under 12 | * the terms of the LGPL v3 license. See the LICENSE file for details. 13 | **/ 14 | 15 | #include 16 | 17 | WiFiScanner::WiFiScanner(PsychicHttpServer *server, 18 | SecurityManager *securityManager) : _server(server), 19 | _securityManager(securityManager) 20 | { 21 | } 22 | 23 | void WiFiScanner::begin() 24 | { 25 | _server->on(SCAN_NETWORKS_SERVICE_PATH, 26 | HTTP_GET, 27 | _securityManager->wrapRequest(std::bind(&WiFiScanner::scanNetworks, this, std::placeholders::_1), 28 | AuthenticationPredicates::IS_ADMIN)); 29 | 30 | ESP_LOGV(SVK_TAG, "Registered GET endpoint: %s", SCAN_NETWORKS_SERVICE_PATH); 31 | 32 | _server->on(LIST_NETWORKS_SERVICE_PATH, 33 | HTTP_GET, 34 | _securityManager->wrapRequest(std::bind(&WiFiScanner::listNetworks, this, std::placeholders::_1), 35 | AuthenticationPredicates::IS_ADMIN)); 36 | 37 | ESP_LOGV(SVK_TAG, "Registered GET endpoint: %s", LIST_NETWORKS_SERVICE_PATH); 38 | } 39 | 40 | esp_err_t WiFiScanner::scanNetworks(PsychicRequest *request) 41 | { 42 | if (WiFi.scanComplete() != -1) 43 | { 44 | WiFi.scanDelete(); 45 | WiFi.scanNetworks(true); 46 | } 47 | return request->reply(202); 48 | } 49 | 50 | esp_err_t WiFiScanner::listNetworks(PsychicRequest *request) 51 | { 52 | int numNetworks = WiFi.scanComplete(); 53 | if (numNetworks > -1) 54 | { 55 | PsychicJsonResponse response = PsychicJsonResponse(request, false); 56 | JsonObject root = response.getRoot(); 57 | JsonArray networks = root["networks"].to(); 58 | for (int i = 0; i < numNetworks; i++) 59 | { 60 | JsonObject network = networks.add(); 61 | network["rssi"] = WiFi.RSSI(i); 62 | network["ssid"] = WiFi.SSID(i); 63 | network["bssid"] = WiFi.BSSIDstr(i); 64 | network["channel"] = WiFi.channel(i); 65 | network["encryption_type"] = (uint8_t)WiFi.encryptionType(i); 66 | } 67 | 68 | return response.send(); 69 | } 70 | else if (numNetworks == -1) 71 | { 72 | return request->reply(202); 73 | } 74 | else 75 | { 76 | return scanNetworks(request); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /lib/framework/WiFiScanner.h: -------------------------------------------------------------------------------- 1 | #ifndef WiFiScanner_h 2 | #define WiFiScanner_h 3 | 4 | /** 5 | * ESP32 SvelteKit 6 | * 7 | * A simple, secure and extensible framework for IoT projects for ESP32 platforms 8 | * with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. 9 | * https://github.com/theelims/ESP32-sveltekit 10 | * 11 | * Copyright (C) 2018 - 2023 rjwats 12 | * Copyright (C) 2023 - 2024 theelims 13 | * 14 | * All Rights Reserved. This software may be modified and distributed under 15 | * the terms of the LGPL v3 license. See the LICENSE file for details. 16 | **/ 17 | 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #define SCAN_NETWORKS_SERVICE_PATH "/rest/scanNetworks" 25 | #define LIST_NETWORKS_SERVICE_PATH "/rest/listNetworks" 26 | 27 | class WiFiScanner 28 | { 29 | public: 30 | WiFiScanner(PsychicHttpServer *server, SecurityManager *securityManager); 31 | 32 | void begin(); 33 | 34 | private: 35 | PsychicHttpServer *_server; 36 | SecurityManager *_securityManager; 37 | 38 | esp_err_t scanNetworks(PsychicRequest *request); 39 | esp_err_t listNetworks(PsychicRequest *request); 40 | }; 41 | 42 | #endif // end WiFiScanner_h 43 | -------------------------------------------------------------------------------- /lib/framework/WiFiStatus.h: -------------------------------------------------------------------------------- 1 | #ifndef WiFiStatus_h 2 | #define WiFiStatus_h 3 | 4 | /** 5 | * ESP32 SvelteKit 6 | * 7 | * A simple, secure and extensible framework for IoT projects for ESP32 platforms 8 | * with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. 9 | * https://github.com/theelims/ESP32-sveltekit 10 | * 11 | * Copyright (C) 2018 - 2023 rjwats 12 | * Copyright (C) 2023 - 2024 theelims 13 | * 14 | * All Rights Reserved. This software may be modified and distributed under 15 | * the terms of the LGPL v3 license. See the LICENSE file for details. 16 | **/ 17 | 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #define WIFI_STATUS_SERVICE_PATH "/rest/wifiStatus" 26 | 27 | class WiFiStatus 28 | { 29 | public: 30 | WiFiStatus(PsychicHttpServer *server, SecurityManager *securityManager); 31 | 32 | void begin(); 33 | 34 | bool isConnected(); 35 | 36 | private: 37 | PsychicHttpServer *_server; 38 | SecurityManager *_securityManager; 39 | 40 | // static functions for logging WiFi events to the UART 41 | static void onStationModeConnected(WiFiEvent_t event, WiFiEventInfo_t info); 42 | static void onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info); 43 | static void onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info); 44 | esp_err_t wifiStatus(PsychicRequest *request); 45 | }; 46 | 47 | #endif // end WiFiStatus_h 48 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: ESP32 SvelteKit 2 | 3 | nav: 4 | - Home: index.md 5 | - "Build Tools": 6 | - gettingstarted.md 7 | - buildprocess.md 8 | - "Front End": 9 | - sveltekit.md 10 | - structure.md 11 | - stores.md 12 | - components.md 13 | - "Back End": 14 | - statefulservice.md 15 | - restfulapi.md 16 | 17 | site_author: elims 18 | site_description: >- 19 | A simple, secure and extensible framework for IoT projects on ESP32 platforms with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. 20 | 21 | # Repository 22 | repo_name: theelims/ESP32-sveltekit 23 | repo_url: https://github.com/theelims/ESP32-sveltekit 24 | 25 | theme: 26 | name: material 27 | logo: media/svelte-logo.png 28 | favicon: media/favicon.png 29 | icon: 30 | repo: fontawesome/brands/github 31 | palette: 32 | # Palette toggle for light mode 33 | - media: "(prefers-color-scheme: light)" 34 | scheme: default 35 | toggle: 36 | icon: material/weather-night 37 | name: Switch to dark mode 38 | primary: blue 39 | accent: blue 40 | 41 | # Palette toggle for dark mode 42 | - media: "(prefers-color-scheme: dark)" 43 | scheme: slate 44 | toggle: 45 | icon: material/weather-sunny 46 | name: Switch to light mode 47 | primary: indigo 48 | accent: indigo 49 | 50 | features: 51 | - navigation.instant 52 | - navigation.tracking 53 | - navigation.tabs 54 | - navigation.tabs.sticky 55 | - navigation.sections 56 | - navigation.expand 57 | - toc.follow 58 | - toc.integrate 59 | - navigation.top 60 | - content.code.copy 61 | 62 | markdown_extensions: 63 | - attr_list 64 | - md_in_html 65 | - tables 66 | - admonition 67 | - pymdownx.details 68 | - pymdownx.superfences: 69 | custom_fences: 70 | - name: mermaid 71 | class: mermaid 72 | format: !!python/name:pymdownx.superfences.fence_code_format 73 | - pymdownx.emoji: 74 | emoji_index: !!python/name:materialx.emoji.twemoji 75 | emoji_generator: !!python/name:materialx.emoji.to_svg 76 | 77 | extra: 78 | social: 79 | - icon: fontawesome/brands/github 80 | link: https://github.com/theelims/ 81 | consent: 82 | title: Cookie consent 83 | description: >- 84 | We use cookies to recognize your repeated visits and preferences, as well 85 | as to measure the effectiveness of our documentation and whether users 86 | find what they're searching for. With your consent, you're helping us to 87 | make our documentation better. 88 | actions: 89 | - accept 90 | - reject 91 | 92 | plugins: 93 | - search: 94 | separator: '[\s\-,:!=\[\]()"/]+|(?!\b)(?=[A-Z][a-z])|\.(?!\d)|&[lg]t;' 95 | 96 | copyright: | 97 | Copyright © 2024 by elims - 98 | Change cookie settings 99 | -------------------------------------------------------------------------------- /scripts/merge_bin.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | Import("env") 4 | 5 | APP_BIN = "$BUILD_DIR/${PROGNAME}.bin" 6 | OUTPUT_DIR = "build{}merged{}".format(os.path.sep, os.path.sep) 7 | 8 | BOARD_CONFIG = env.BoardConfig() 9 | 10 | def readFlag(flag): 11 | buildFlags = env.ParseFlags(env["BUILD_FLAGS"]) 12 | # print(buildFlags.get("CPPDEFINES")) 13 | for define in buildFlags.get("CPPDEFINES"): 14 | if (define == flag or (isinstance(define, list) and define[0] == flag)): 15 | # print("Found "+flag+" = "+define[1]) 16 | # strip quotes ("") from define[1] 17 | cleanedFlag = re.sub(r'^"|"$', '', define[1]) 18 | return cleanedFlag 19 | return None 20 | 21 | 22 | def merge_bin(source, target, env): 23 | 24 | # check if output directories exist and create if necessary 25 | if not os.path.isdir("build"): 26 | os.mkdir("build") 27 | 28 | if not os.path.isdir(OUTPUT_DIR): 29 | os.mkdir(OUTPUT_DIR) 30 | 31 | MERGED_BIN = "$PROJECT_DIR{}{}{}_{}_{}.bin".format(os.path.sep, OUTPUT_DIR, readFlag("APP_NAME"), env.get('PIOENV'), readFlag("APP_VERSION").replace(".", "-")) 32 | 33 | # The list contains all extra images (bootloader, partitions, eboot) and 34 | # the final application binary 35 | flash_images = env.Flatten(env.get("FLASH_EXTRA_IMAGES", [])) + ["$ESP32_APP_OFFSET", APP_BIN] 36 | flash_size = env.BoardConfig().get("upload.flash_size", "4MB") 37 | flash_freq = env.BoardConfig().get("build.f_flash", "40000000L") 38 | flash_freq = str(flash_freq).replace("L", "") 39 | flash_freq = str(int(int(flash_freq) / 1000000)) + "m" 40 | flash_mode = env.BoardConfig().get("build.flash_mode", "dio") 41 | memory_type = env.BoardConfig().get("build.arduino.memory_type", "qio_qspi") 42 | 43 | if flash_mode == "qio" or flash_mode == "qout": 44 | flash_mode = "dio" 45 | if memory_type == "opi_opi" or memory_type == "opi_qspi": 46 | flash_mode = "dout" 47 | 48 | # Run esptool to merge images into a single binary 49 | env.Execute( 50 | " ".join( 51 | [ 52 | "$PYTHONEXE", 53 | "$OBJCOPY", 54 | "--chip", 55 | BOARD_CONFIG.get("build.mcu", "esp32"), 56 | "merge_bin", 57 | "-o", 58 | MERGED_BIN, 59 | "--flash_mode", 60 | flash_mode, 61 | "--flash_freq", 62 | flash_freq, 63 | "--flash_size", 64 | flash_size 65 | ] 66 | + flash_images 67 | ) 68 | ) 69 | 70 | # Add a post action that runs esptoolpy to merge available flash images 71 | env.AddPostAction(APP_BIN , merge_bin) 72 | -------------------------------------------------------------------------------- /scripts/rename_fw.py: -------------------------------------------------------------------------------- 1 | """ 2 | EMS-ESP - https://github.com/emsesp/EMS-ESP 3 | Copyright 2020-2023 Paul Derbyshire 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | You should have received a copy of the GNU General Public License 14 | along with this program. If not, see . 15 | 16 | """ 17 | 18 | import shutil 19 | import re 20 | import os 21 | Import("env") 22 | import hashlib 23 | 24 | 25 | OUTPUT_DIR = "build{}release{}".format(os.path.sep, os.path.sep) 26 | 27 | def readFlag(flag): 28 | buildFlags = env.ParseFlags(env["BUILD_FLAGS"]) 29 | # print(buildFlags.get("CPPDEFINES")) 30 | for define in buildFlags.get("CPPDEFINES"): 31 | if (define == flag or (isinstance(define, list) and define[0] == flag)): 32 | # print("Found "+flag+" = "+define[1]) 33 | # strip quotes ("") from define[1] 34 | cleanedFlag = re.sub(r'^"|"$', '', define[1]) 35 | return cleanedFlag 36 | return None 37 | 38 | 39 | def bin_copy(source, target, env): 40 | 41 | # get the build info 42 | app_version = readFlag("APP_VERSION") 43 | app_name = readFlag("APP_NAME") 44 | build_target = env.get('PIOENV') 45 | 46 | # print information's 47 | print("App Version: " + app_version) 48 | print("App Name: " + app_name) 49 | print("Build Target: " + build_target) 50 | 51 | # convert . to - so Windows doesn't complain 52 | variant = app_name + "_" + build_target + "_" + app_version.replace(".", "-") 53 | 54 | # check if output directories exist and create if necessary 55 | if not os.path.isdir(OUTPUT_DIR): 56 | os.mkdir(OUTPUT_DIR) 57 | 58 | # create string with location and file names based on variant 59 | bin_file = "{}{}.bin".format(OUTPUT_DIR, variant) 60 | md5_file = "{}{}.md5".format(OUTPUT_DIR, variant) 61 | 62 | # check if new target files exist and remove if necessary 63 | for f in [bin_file]: 64 | if os.path.isfile(f): 65 | os.remove(f) 66 | 67 | # check if new target files exist and remove if necessary 68 | for f in [md5_file]: 69 | if os.path.isfile(f): 70 | os.remove(f) 71 | 72 | print("Renaming file to "+bin_file) 73 | 74 | # copy firmware.bin to firmware/.bin 75 | shutil.copy(str(target[0]), bin_file) 76 | 77 | with open(bin_file,"rb") as f: 78 | result = hashlib.md5(f.read()) 79 | print("Calculating MD5: "+result.hexdigest()) 80 | file1 = open(md5_file, 'w') 81 | file1.write(result.hexdigest()) 82 | file1.close() 83 | 84 | env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", [bin_copy]) 85 | env.AddPostAction("$BUILD_DIR/${PROGNAME}.md5", [bin_copy]) 86 | -------------------------------------------------------------------------------- /scripts/save_elf.py: -------------------------------------------------------------------------------- 1 | import shutil 2 | import re 3 | import os 4 | Import("env") 5 | import hashlib 6 | 7 | 8 | OUTPUT_DIR = "build{}elf{}".format(os.path.sep,os.path.sep) 9 | 10 | def elf_copy(source, target, env): 11 | # check if output directories exist and create if necessary 12 | if not os.path.isdir("build"): 13 | os.mkdir("build") 14 | 15 | if not os.path.isdir(OUTPUT_DIR): 16 | os.mkdir(OUTPUT_DIR) 17 | 18 | with open(str(target[0]),"rb") as f: 19 | result = hashlib.sha256(f.read()) 20 | 21 | # create string with location and file names based on variant 22 | elf_file = "{}{}.elf".format(OUTPUT_DIR, result.hexdigest()) 23 | 24 | # check if new target files exist and remove if necessary 25 | for f in [elf_file]: 26 | if os.path.isfile(f): 27 | os.remove(f) 28 | 29 | print("Saving ELF file to "+elf_file) 30 | 31 | # copy firmware.bin to firmware/.bin 32 | shutil.copy(str(target[0]), elf_file) 33 | 34 | env.AddPostAction("$BUILD_DIR/${PROGNAME}.elf", [elf_copy]) -------------------------------------------------------------------------------- /src/LightMqttSettingsService.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * ESP32 SvelteKit 3 | * 4 | * A simple, secure and extensible framework for IoT projects for ESP32 platforms 5 | * with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. 6 | * https://github.com/theelims/ESP32-sveltekit 7 | * 8 | * Copyright (C) 2018 - 2023 rjwats 9 | * Copyright (C) 2023 - 2024 theelims 10 | * 11 | * All Rights Reserved. This software may be modified and distributed under 12 | * the terms of the LGPL v3 license. See the LICENSE file for details. 13 | **/ 14 | 15 | #include 16 | 17 | LightMqttSettingsService::LightMqttSettingsService(PsychicHttpServer *server, 18 | ESP32SvelteKit *sveltekit) : _httpEndpoint(LightMqttSettings::read, 19 | LightMqttSettings::update, 20 | this, 21 | server, 22 | LIGHT_BROKER_SETTINGS_PATH, 23 | sveltekit->getSecurityManager(), 24 | AuthenticationPredicates::IS_AUTHENTICATED), 25 | _fsPersistence(LightMqttSettings::read, 26 | LightMqttSettings::update, 27 | this, 28 | sveltekit->getFS(), 29 | LIGHT_BROKER_SETTINGS_FILE) 30 | { 31 | } 32 | 33 | void LightMqttSettingsService::begin() 34 | { 35 | _httpEndpoint.begin(); 36 | _fsPersistence.readFromFS(); 37 | } 38 | -------------------------------------------------------------------------------- /src/LightMqttSettingsService.h: -------------------------------------------------------------------------------- 1 | #ifndef LightMqttSettingsService_h 2 | #define LightMqttSettingsService_h 3 | 4 | /** 5 | * ESP32 SvelteKit 6 | * 7 | * A simple, secure and extensible framework for IoT projects for ESP32 platforms 8 | * with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. 9 | * https://github.com/theelims/ESP32-sveltekit 10 | * 11 | * Copyright (C) 2018 - 2023 rjwats 12 | * Copyright (C) 2023 - 2024 theelims 13 | * 14 | * All Rights Reserved. This software may be modified and distributed under 15 | * the terms of the LGPL v3 license. See the LICENSE file for details. 16 | **/ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #define LIGHT_BROKER_SETTINGS_FILE "/config/brokerSettings.json" 24 | #define LIGHT_BROKER_SETTINGS_PATH "/rest/brokerSettings" 25 | 26 | class LightMqttSettings 27 | { 28 | public: 29 | String mqttPath; 30 | String name; 31 | String uniqueId; 32 | 33 | static void read(LightMqttSettings &settings, JsonObject &root) 34 | { 35 | root["mqtt_path"] = settings.mqttPath; 36 | root["name"] = settings.name; 37 | root["unique_id"] = settings.uniqueId; 38 | } 39 | 40 | static StateUpdateResult update(JsonObject &root, LightMqttSettings &settings) 41 | { 42 | settings.mqttPath = root["mqtt_path"] | SettingValue::format("homeassistant/light/#{unique_id}"); 43 | settings.name = root["name"] | SettingValue::format("light-#{unique_id}"); 44 | settings.uniqueId = root["unique_id"] | SettingValue::format("light-#{unique_id}"); 45 | return StateUpdateResult::CHANGED; 46 | } 47 | }; 48 | 49 | class LightMqttSettingsService : public StatefulService 50 | { 51 | public: 52 | LightMqttSettingsService(PsychicHttpServer *server, ESP32SvelteKit *sveltekit); 53 | void begin(); 54 | 55 | private: 56 | HttpEndpoint _httpEndpoint; 57 | FSPersistence _fsPersistence; 58 | }; 59 | 60 | #endif // end LightMqttSettingsService_h 61 | -------------------------------------------------------------------------------- /src/LightStateService.h: -------------------------------------------------------------------------------- 1 | #ifndef LightStateService_h 2 | #define LightStateService_h 3 | 4 | /** 5 | * ESP32 SvelteKit 6 | * 7 | * A simple, secure and extensible framework for IoT projects for ESP32 platforms 8 | * with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. 9 | * https://github.com/theelims/ESP32-sveltekit 10 | * 11 | * Copyright (C) 2018 - 2023 rjwats 12 | * Copyright (C) 2023 - 2024 theelims 13 | * 14 | * All Rights Reserved. This software may be modified and distributed under 15 | * the terms of the LGPL v3 license. See the LICENSE file for details. 16 | **/ 17 | 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #define DEFAULT_LED_STATE false 28 | #define OFF_STATE "OFF" 29 | #define ON_STATE "ON" 30 | 31 | #define LIGHT_SETTINGS_ENDPOINT_PATH "/rest/lightState" 32 | #define LIGHT_SETTINGS_SOCKET_PATH "/ws/lightState" 33 | #define LIGHT_SETTINGS_EVENT "led" 34 | 35 | class LightState 36 | { 37 | public: 38 | bool ledOn; 39 | 40 | static void read(LightState &settings, JsonObject &root) 41 | { 42 | root["led_on"] = settings.ledOn; 43 | } 44 | 45 | static StateUpdateResult update(JsonObject &root, LightState &lightState) 46 | { 47 | boolean newState = root["led_on"] | DEFAULT_LED_STATE; 48 | if (lightState.ledOn != newState) 49 | { 50 | lightState.ledOn = newState; 51 | return StateUpdateResult::CHANGED; 52 | } 53 | return StateUpdateResult::UNCHANGED; 54 | } 55 | 56 | static void homeAssistRead(LightState &settings, JsonObject &root) 57 | { 58 | root["state"] = settings.ledOn ? ON_STATE : OFF_STATE; 59 | } 60 | 61 | static StateUpdateResult homeAssistUpdate(JsonObject &root, LightState &lightState) 62 | { 63 | String state = root["state"]; 64 | // parse new led state 65 | boolean newState = false; 66 | if (state.equals(ON_STATE)) 67 | { 68 | newState = true; 69 | } 70 | else if (!state.equals(OFF_STATE)) 71 | { 72 | return StateUpdateResult::ERROR; 73 | } 74 | // change the new state, if required 75 | if (lightState.ledOn != newState) 76 | { 77 | lightState.ledOn = newState; 78 | return StateUpdateResult::CHANGED; 79 | } 80 | return StateUpdateResult::UNCHANGED; 81 | } 82 | }; 83 | 84 | class LightStateService : public StatefulService 85 | { 86 | public: 87 | LightStateService(PsychicHttpServer *server, 88 | ESP32SvelteKit *sveltekit, 89 | LightMqttSettingsService *lightMqttSettingsService); 90 | 91 | void begin(); 92 | 93 | private: 94 | HttpEndpoint _httpEndpoint; 95 | EventEndpoint _eventEndpoint; 96 | MqttEndpoint _mqttEndpoint; 97 | WebSocketServer _webSocketServer; 98 | PsychicMqttClient *_mqttClient; 99 | LightMqttSettingsService *_lightMqttSettingsService; 100 | 101 | void registerConfig(); 102 | void onConfigUpdated(); 103 | }; 104 | 105 | #endif 106 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * ESP32 SvelteKit 3 | * 4 | * A simple, secure and extensible framework for IoT projects for ESP32 platforms 5 | * with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. 6 | * https://github.com/theelims/ESP32-sveltekit 7 | * 8 | * Copyright (C) 2018 - 2023 rjwats 9 | * Copyright (C) 2023 - 2024 theelims 10 | * 11 | * All Rights Reserved. This software may be modified and distributed under 12 | * the terms of the LGPL v3 license. See the LICENSE file for details. 13 | **/ 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #define SERIAL_BAUD_RATE 115200 21 | 22 | PsychicHttpServer server; 23 | 24 | ESP32SvelteKit esp32sveltekit(&server, 120); 25 | 26 | LightMqttSettingsService lightMqttSettingsService = LightMqttSettingsService(&server, 27 | &esp32sveltekit); 28 | 29 | LightStateService lightStateService = LightStateService(&server, 30 | &esp32sveltekit, 31 | &lightMqttSettingsService); 32 | 33 | void setup() 34 | { 35 | // start serial and filesystem 36 | Serial.begin(SERIAL_BAUD_RATE); 37 | 38 | // start ESP32-SvelteKit 39 | esp32sveltekit.begin(); 40 | 41 | // load the initial light settings 42 | lightStateService.begin(); 43 | // start the light service 44 | lightMqttSettingsService.begin(); 45 | } 46 | 47 | void loop() 48 | { 49 | // Delete Arduino loop task, as it is not needed in this example 50 | vTaskDelete(NULL); 51 | } 52 | -------------------------------------------------------------------------------- /ssl_certs/DigiCert_Global_Root_CA.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh 3 | MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 4 | d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD 5 | QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT 6 | MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j 7 | b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG 8 | 9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB 9 | CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 10 | nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt 11 | 43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P 12 | T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 13 | gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO 14 | BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR 15 | TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw 16 | DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr 17 | hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg 18 | 06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF 19 | PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls 20 | YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk 21 | CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= 22 | -----END CERTIFICATE----- 23 | --------------------------------------------------------------------------------