├── .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 | openCollapsible()}>
25 |
30 |
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 |
38 |
41 |
{title}
42 |
43 |
{message}
44 |
45 |
46 | {
49 | modals.close();
50 | }}>{labels?.cancel.label}
52 | {labels?.confirm.label}
57 |
58 |
59 |
60 | {/if}
61 |
--------------------------------------------------------------------------------
/interface/src/lib/components/GithubUpdateDialog.svelte:
--------------------------------------------------------------------------------
1 |
62 |
63 | {#if isOpen}
64 |
70 |
73 |
Updating Firmware
74 |
75 |
76 |
77 | {#if $telemetry.download_ota.status == 'progress'}
78 |
79 | {:else}
80 |
81 | {/if}
82 |
{message}
83 |
84 |
85 |
86 |
87 |
88 |
{
92 | modals.closeAll();
93 | location.reload();
94 | }}
95 | >
96 | Close
98 |
99 |
100 |
101 | {/if}
102 |
--------------------------------------------------------------------------------
/interface/src/lib/components/InfoDialog.svelte:
--------------------------------------------------------------------------------
1 |
25 |
26 | {#if isOpen}
27 |
33 |
36 |
{title}
37 |
38 |
{message}
39 |
40 |
41 | {dismiss.label}
46 |
47 |
48 |
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 |
43 |
--------------------------------------------------------------------------------
/interface/src/lib/components/SettingsCard.svelte:
--------------------------------------------------------------------------------
1 |
21 |
22 | {#if collapsible}
23 |
26 |
29 |
30 | {@render icon?.()}
31 | {@render title?.()}
32 |
33 | {
36 | open = !open;
37 | }}
38 | >
39 |
44 |
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 |
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 |
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 |
62 |
90 |
91 |
92 |
93 |
123 |
--------------------------------------------------------------------------------
/interface/src/routes/statusbar.svelte:
--------------------------------------------------------------------------------
1 |
39 |
40 |
41 |
42 |
43 |
46 | {page.data.title}
47 |
48 |
49 |
50 |
51 |
52 | {#if $telemetry.rssi.disconnected}
53 |
54 | {:else}
55 |
61 | {/if}
62 |
63 |
64 | {#if page.data.features.battery}
65 |
66 |
71 |
72 | {/if}
73 |
74 | {#if page.data.features.sleep}
75 |
80 | {/if}
81 |
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 |
64 |
67 |
{title}
68 |
69 |
114 |
115 |
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 |
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 |
--------------------------------------------------------------------------------