Showing preview only (859K chars total). Download the full file or copy to clipboard to get everything.
Repository: theelims/ESP32-sveltekit
Branch: main
Commit: 81827a2af941
Files: 231
Total size: 797.0 KB
Directory structure:
gitextract_e2fr6h29/
├── .github/
│ └── workflows/
│ └── ci.yaml
├── .gitignore
├── CHANGELOG.md
├── ESP32-sveltekit.code-workspace
├── LICENSE
├── README.md
├── docs/
│ ├── buildprocess.md
│ ├── components.md
│ ├── gettingstarted.md
│ ├── index.md
│ ├── restfulapi.md
│ ├── statefulservice.md
│ ├── stores.md
│ ├── structure.md
│ └── sveltekit.md
├── factory_settings.ini
├── features.ini
├── interface/
│ ├── .eslintignore
│ ├── .eslintrc.cjs
│ ├── .gitignore
│ ├── .npmrc
│ ├── .prettierignore
│ ├── .prettierrc
│ ├── LICENSE
│ ├── package.json
│ ├── src/
│ │ ├── app.css
│ │ ├── app.d.ts
│ │ ├── app.html
│ │ ├── lib/
│ │ │ ├── DaisyUiHelper.ts
│ │ │ ├── components/
│ │ │ │ ├── BatteryIndicator.svelte
│ │ │ │ ├── Collapsible.svelte
│ │ │ │ ├── ConfirmDialog.svelte
│ │ │ │ ├── DraggableList.svelte
│ │ │ │ ├── FirmwareUpdateDialog.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
│ │ ├── ethernet/
│ │ │ ├── +page.svelte
│ │ │ ├── +page.ts
│ │ │ └── Ethernet.svelte
│ │ ├── login.svelte
│ │ ├── menu.svelte
│ │ ├── statusbar.svelte
│ │ ├── system/
│ │ │ ├── +page.ts
│ │ │ ├── coredump/
│ │ │ │ ├── +page.svelte
│ │ │ │ └── +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
│ │ ├── EditNetwork.svelte
│ │ ├── Scan.svelte
│ │ └── Wifi.svelte
│ ├── static/
│ │ └── manifest.json
│ ├── svelte.config.js
│ ├── tsconfig.json
│ ├── vite-plugin-littlefs.ts
│ └── vite.config.ts
├── lib/
│ ├── PsychicHttp/
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── RELEASE.md
│ │ ├── 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
│ │ ├── 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
│ ├── EthernetSettingsService.cpp
│ ├── EthernetSettingsService.h
│ ├── EthernetStatus.cpp
│ ├── EthernetStatus.h
│ ├── EventEndpoint.h
│ ├── EventSocket.cpp
│ ├── EventSocket.h
│ ├── FSPersistence.h
│ ├── FactoryResetService.cpp
│ ├── FactoryResetService.h
│ ├── Features.h
│ ├── FeaturesService.cpp
│ ├── FeaturesService.h
│ ├── FirmwareUpdateEvents.h
│ ├── HttpEndpoint.h
│ ├── IPUtils.h
│ ├── JsonUtils.h
│ ├── LICENSE
│ ├── MqttEndpoint.cpp
│ ├── 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
│ ├── 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
│ ├── prebuild_utils.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
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/ci.yaml
================================================
name: ci
on:
push:
branches:
- master
- main
permissions:
contents: write
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: 3.x
- run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV
- uses: actions/cache@v3
with:
key: mkdocs-material-${{ env.cache_id }}
path: .cache
restore-keys: |
mkdocs-material-
- run: pip install mkdocs-material
- run: mkdocs gh-deploy --force
================================================
FILE: .gitignore
================================================
.pio
.clang_complete
.gcc-flags.json
*Thumbs.db
/data/www
/interface/build
/interface/node_modules
/interface/.eslintcache
.vscode
node_modules
/releases
/src/certs
/temp
/build
/lib/framework/WWWData.h
*WWWData.h
lib/framework/WWWData.h
ssl_certs/cacert.pem
/logs
.aid
/scripts/__pycache__
.DS_Store
*.bak
================================================
FILE: CHANGELOG.md
================================================
# Changelog
All notable changes to this project will be documented in this file.
## [WIP] - Next Release
### Added
- Add originID to StateUpdateResult update [#110](https://github.com/theelims/ESP32-sveltekit/pull/110)
- Add originID to StateUpdateResult update [#110](https://github.com/theelims/ESP32-sveltekit/pull/110)
- Ethernet Support [#113](https://github.com/theelims/ESP32-sveltekit/pull/113)
### Changed
- Changed the width of the confirm dialog.
- SvelteKit bundling as single files to reduce heap consumption.
- Rework of firmware upload [#107](https://github.com/theelims/ESP32-sveltekit/pull/107)
### Fixes
- WiFi reconnection issues [#109](https://github.com/theelims/ESP32-sveltekit/issues/109)
- Blurred toast notifications [#114](https://github.com/theelims/ESP32-sveltekit/issues/114)
## [0.6.0] - 2025-11-03
> [!CAUTION]
> This update has breaking changes!
### Added
- Added `GEMINI.md` to store notes about the repository for the Gemini CLI. We are now using the Gemini CLI for development.
- Added a build script to create a merged firmware file to use with [ESP Web Tools](https://esphome.github.io/esp-web-tools/)
- Added compatibility with ESP32-C6
- Added getIP() function to WiFiSettingsService.
- Added Arduino Log Colors
- Possibility to add a loop callback to ESP32-Sveltekit to leverage its loop threat. Meant to include custom services so no separate task is needed for them.
- Change wake-up pin in SleepService during runtime. It is also possible to use the internal pull-up or pull-down resistors now.
- Get current connection status from ESP32-SvelteKit. Useful for status LED or displays.
- Battery history graph to gauge battery consumption and device life.
- Add a status topic (`online` or `offline`) to the MQTT client. It retains its message and sends `offline` as last will and testament, signalling all subscribers when it goes missing.
- FeatureService sends updates through the event system.
- WiFiSettingsService can set the WiFi station mode to offline, without deleting the list of networks.
- Expands menu on selected subitem [#77](https://github.com/theelims/ESP32-sveltekit/pull/77)
- Refactor System Status and Metrics, added PSRAM [#79](https://github.com/theelims/ESP32-sveltekit/pull/79)
- Add /rest/coreDump endpoint [#87](https://github.com/theelims/ESP32-sveltekit/pull/87) & [#94](https://github.com/theelims/ESP32-sveltekit/pull/94)
- Rate limiting for MQTT publish messages. Can be configured as factory setting or at runtime. `0` will disable the rate limiting.
- Added [discord](https://discord.gg/MTn9mVUG5n) invite to readme.md and docs.
- Created DraggableList component based on svelte-dnd-action (used in WiFi Settings) [#100](https://github.com/theelims/ESP32-sveltekit/pull/100)
- Extended Collapsible and SettingsCard components to support a dirty status [#100](https://github.com/theelims/ESP32-sveltekit/pull/100)
- Enhanced ConfirmDialog, InfoDialog and Toast components to support HTML content [#100](https://github.com/theelims/ESP32-sveltekit/pull/100)
- Added connection check to WebSocket store (socket.ts) and allow secure WebSocket connections [#100](https://github.com/theelims/ESP32-sveltekit/pull/100)
- Added a delayed reconnect function to WifiSettingsService.cpp to allow the last POST request from frontend (providing new network settings) to be properly responded to. In the previous implementation, the POST request was never responded to by the backend, as the connection was closed immediately upon receiving the request in the backend. This resulted in the user receiving no feedback about whether the settings update was successful, only a timeout that suggested something had gone wrong. [#100](https://github.com/theelims/ESP32-sveltekit/pull/100)
- Added scripts/prebuild_utils.py that allows other build scripts to be executed depending on the type of the executed task (e.g. I don't want to have the interface built or the certificate bundle created on clean tasks) [#100](https://github.com/theelims/ESP32-sveltekit/pull/100)
- Added build flag `-D TELEPLOT_TASKS` to plot task heap high water mark with teleplot. You can include this in your tasks as well:
```cpp
#ifdef TELEPLOT_TASKS
static int lastTime = 0;
if (millis() - lastTime > 1000)
{
lastTime = millis();
Serial.printf(">ESP32SveltekitTask:%i:%i\n", millis(), uxTaskGetStackHighWaterMark(NULL));
}
#endif
```
### Changed
- Lightstate example uses simpler, less explicit constructor
- MQTT library updated
- Analytics task was refactored into a loop() function which is called by the ESP32-sveltekit main task.
- Updated PsychicHttp to v1.2.1 incl. patches.
- Updated to DaisyUI 5 and Tailwind CSS 4
- Updated Svelte 5 --> see [Svelte 5 Migration Guide](https://svelte.dev/docs/svelte/v5-migration-guide)
- Changed platform to [PIO Arduino](https://github.com/pioarduino/platform-espressif32) using Arduino 3 Core. Also upgrades ESP-IDF to v5.
- ESPD_LOGx: replace first argument with TAG and define TAG as 🐼 [#85](https://github.com/theelims/ESP32-sveltekit/pull/85)
- Replace rtc_get_reset_reason(0) with esp_reset_reason() [#86](https://github.com/theelims/ESP32-sveltekit/pull/86)
- Default build_interface.py script to npm, if no lock file is found.
- Replaced `svelte-dnd-list` with `svelte-dnd-action` as `svelte-dnd-list` creates build warnings and appears to no longer be maintained, while svelte-dnd-action is under active community development. [#100](https://github.com/theelims/ESP32-sveltekit/pull/100)
- Made Spinner component more flexible (to allow other texts than "Loading..." or no text at all) [#100](https://github.com/theelims/ESP32-sveltekit/pull/100)
- Reworked WiFi Settings (Station) dialog: added edit dialog for networks, rearranged UI components, used new DraggableList component [#100](https://github.com/theelims/ESP32-sveltekit/pull/100)
### Fixed
- Ensure thread safety for client subscriptions [#58](https://github.com/theelims/ESP32-sveltekit/pull/58)
- Deferred websocket event connection to after user validation & login [#72](https://github.com/theelims/ESP32-sveltekit/pull/72)
- Wrong return type battery service
- Wrong return types in various getService functions.
- Add file.close in fileHandler handleRequest [#73](https://github.com/theelims/ESP32-sveltekit/pull/73)
- Fixed bug in WiFiSettingsService preventing discovery of networks other than the first
- Fixed mixup pull up and pull down when configuring wake up pin in SleepService.cpp
- Wifi: Multiple edits bug resolved [#79](https://github.com/theelims/ESP32-sveltekit/pull/79)
- Fixed broken link to Adafruit SSL Cert Store [#93](https://github.com/theelims/ESP32-sveltekit/issues/93)
- Fixed JSON creation in WiFiSettingsService.h [#91](https://github.com/theelims/ESP32-sveltekit/pull/91)
- Fixed preprocessor warning: usage of #ifdef with OR operator [#100](https://github.com/theelims/ESP32-sveltekit/pull/100)
- Fixed preprocessor warning: redefinition of ESP_PLATFORM [#100](https://github.com/theelims/ESP32-sveltekit/pull/100)
- Fixed deprecated usage of merge-bin and its parameters in scripts/merge_bin.py [#100](https://github.com/theelims/ESP32-sveltekit/pull/100)
- Fixed Download OTA. Issues with certificate validation might remain, but build flag `-D DOWNLOAD_OTA_SKIP_CERT_VERIFY` allows to circumvent issue by sacrificing certificate validation.
### Removed
- Removed async workers in PsychicHttp, as these were not used, but caused linker errors.
### Depreciate
- Support for ESP Arduino 2 and ESP-IDF v4 will depreciate some time in the future. Try to migrate to the current Arduino 3 / ESP-IDF v5 based branch.
### Migration Guide
#### PIO Arduino & ESP-IDF 5
The firmware is based on the community maintained fork [PIO Arduino](https://github.com/pioarduino/platform-espressif32) of Arduino 3 for ESP32. Which is based on ESP-IDF 5. Please make sure all your dependencies and application code is compatible with ESP-IDF 5.
#### Frontend
SvelteKit was updated to v2 and Svelte to v5. Please check the migration guides for [SvelteKit 2](https://svelte.dev/docs/kit/migrating-to-sveltekit-2) and the [Svelte 5 Migration Guide](https://svelte.dev/docs/svelte/v5-migration-guide) for the changes required on your frontend code.
To migrate your frontend run
```
npm install --force
npx sv migrate svelte-5
```
Also DaisyUI and Tailwind CSS have been updated to their last major versions. Run the official Tailwind upgrade tool:
```
npx @tailwindcss/upgrade
```
This will migrate some of your svelte files to the new naming convention of Tailwind. For DaisyUI follow this [guide](https://daisyui.com/docs/upgrade/#changes-from-v4). Likely you'll need to redo all forms, as the components behave differently. Forms will need the `fieldset` class. Inputs will need an additional `w-full` to have the same behavior as before. And [labels](https://daisyui.com/components/label/) have a different syntax, too.
The themes are to be found in `app.css` now. Add them back if they had been changed from the default.
### Acknowledgment
Many thanks to @runeharlyk, @ewowi, @hmbacher, and @stamp who contributed significantly to this new release.
## [0.5.0] - 2024-05-06
Changes the Event Socket System to use a clearer message structure and MessagePack. Brings breaking changes to the `EventSocket.h` API.
Updated daisyUI to v4. This has changes in the colors and switches to OKLCH. Also button groups and input groups have been depreciated in favor of join. This might require changes to custom parts of the code. Please double check all websites if the still have the desired looks.
Updates ArduinoJSON from v6 to v7 to increase the available free heap. If you make use of ArduinoJSON, changes might be required.
### Added
- Debug buildflag to switch between MessagePack and JSON for event messages.
- Show SSID of the current WiFi Station as tooltip of the RSSI icon.
### Changed
- Moved MQTT types to models.ts as well. [#49](https://github.com/theelims/ESP32-sveltekit/pull/49)
- Updated daisyUI to 4.10.2 [#48](https://github.com/theelims/ESP32-sveltekit/pull/48)
- Fixed spelling error in models.ts
- Changed ArduinoJson from v6 to v7 increasing the free heap by ~40kb
- Split NotificationService out of EventSocket into own class
- Changed API of EventSocket.h. Now uses `void emitEvent(String event, JsonObject &jsonObject, const char *originId = "", bool onlyToSameOrigin = false);`.
- Changed event socket message format to MessagePack
### Fixed
- Fixes to WiFi.svelte and models.ts to fix type errors and visibility rights.
- Fixes bug in highlighting the menu when navigating with the browser (back/forward)
- Made WiFi connection routine more robust by using BSSID. Ensures that the STA truly connects to the strongest hotspot, even if several hotspots are in reach.
### Removed
- Removed duplicate in ESP32SvelteKit.cpp [#47](https://github.com/theelims/ESP32-sveltekit/pull/47) and WiFi.svelte [#50](https://github.com/theelims/ESP32-sveltekit/pull/50)
### Acknowledgment
Many thanks to @runeharlyk who contributed significantly to the new event socket system and fixed many smaller issues with the front-end.
## [0.4.0] - 2024-04-21
This upgrade might require one minor change as `MqttPubSub.h` and its class had been renamed to `MqttEndpoint.h` and `MqttEndoint` respectively. However, it is strongly advised, that you change all existing WebSocketServer endpoints to the new event socket system.
> [!NOTE]
> The new Event Socket system is likely to change with coming updates.
### Added
- Added build flag `-D SERIAL_INFO` to platformio.ini to enable / disable all `Serial.print()` statements. On some boards with native USB those Serial prints have been reported to block and make the server unresponsive.
- Added a hook handler to StatefulService. Unlike an UPDATE a hook is called every time a state receives an updated, even if the result is UNCHANGED or ERROR.
- Added missing include for S2 in SystemStatus.cpp [#23](https://github.com/theelims/ESP32-sveltekit/issues/23)
- Added awareness of front end build script for all 3 major JS package managers. The script will auto-identify the package manager by the lock-file. [#40](https://github.com/theelims/ESP32-sveltekit/pull/40)
- Added a new event socket to bundle the websocket server and the notifications events. This saves on open sockets and allows for concurrent visitors of the internal website. The normal websocket server endpoint remains as an option, should a pure websocket connection be desired. An EventEndpoint was added to use this with Stateful Services. [#29](https://github.com/theelims/ESP32-sveltekit/issues/29) and [#43](https://github.com/theelims/ESP32-sveltekit/pull/43)
- TS Types definition in one central place for the frontend.
### Changed
- more generic board definition in platformio.ini [#20](https://github.com/theelims/ESP32-sveltekit/pull/20)
- Renamed `MqttPubSub.h` and class to `MqttEndpoint.h` and class.
- refactored MqttEndpoint.h into a single class to improve readability
- Moves appName and copyright to `layout.ts` to keep customization in one place [#31](https://github.com/theelims/ESP32-sveltekit/pull/31)
- Make event source use timeout for reconnect [#34](https://github.com/theelims/ESP32-sveltekit/pull/34)
- Make each toasts disappear after timeout [#35](https://github.com/theelims/ESP32-sveltekit/pull/35)
- Fixed version `platform = espressif32 @ 6.6.0` in platformio.ini
- Analytics data limited to 1000 data points (roughly 33 minutes).
- postcss.config.cjs as ESM module [#24](https://github.com/theelims/ESP32-sveltekit/issues/24)
### Fixed
- Fixed compile error with FLAG `-D SERVE_CONFIG_FILES`
- Fixed typo in telemetry.ts [#38](https://github.com/theelims/ESP32-sveltekit/pull/38)
- Fixed the development warning: `Loading /rest/features using 'window.fetch'. For best results, use the 'fetch' that is passed to your 'load' function:`
### Removed
- Duplicate method in FeatureService [#18](https://github.com/theelims/ESP32-sveltekit/pull/18)
- Duplicate lines in Systems Settings view.
- Removes duplicate begin [#36](https://github.com/theelims/ESP32-sveltekit/pull/36)
- Temporary disabled OTA progress update due to crash with PsychicHttp [#32](https://github.com/theelims/ESP32-sveltekit/issues/32) until a fix is found.
### Known Issues
- On ESP32-C3 the security features should be disabled in features.ini: `-D FT_SECURITY=0`. If enabled the ESP32-C3 becomes extremely sluggish with frequent connection drops.
## [0.3.0] - 2024-02-05
> [!CAUTION]
> This update has breaking changes!
This is a major change getting rid of all ESPAsyncTCP and ESPAsyncWebserver dependencies. Despite their popularity they are plagued with countless bugs, since years unmaintained, not SSL capable and simply not suitable for a production build. Although several attempts exist to fix the most pressing bugs even these libraries lead to frequent crashes. This new version replaces them with ESP-IDF based components. [PsychicHttp](https://github.com/hoeken/PsychicHttp) and [PsychicMqttClient](https://github.com/theelims/PsychicMqttClient) both wrap the ESP-IDF components in a familiar wrapper for easy porting of the code base. However, this will break existing code and will require some effort on your codebase. In return the stability is improved greatly and the RAM usage more friendly. Now e.g. running Bluetooth in parallel becomes possible.
### Added
- Added postscript to platform.io build process to copy, rename and calculate MD5 checksum of \*.bin file. These files are ready for uploading to the Github Release page.
- Added more information to SystemStatus API
- Added generateToken API for security settings
- Added Multi-WiFi capability. Add up to five WiFi configurations and connect to either strongest network (default), or by priority.
- Added InfoDialog as a simpler version of the ConfirmDialog for a simple notification modal.
- Added Adafruit certificate repository as the default choice for the X509 certificate bundle.
### Changed
- Better route protection for user page with deep link.
- Changed build_interface.py script to check for modified files in the interface sources before re-building the interface. Saves some time on the compilation process.
- Upload firmware binary allows uploading of MD5 checksum file in advance to verify downloaded firmware package.
- GithubFirmwareManager checks against PIO build_target in filename to support Github OTA for binaries build for various targets. You should rename your old release \*.bin files on the Github release pages for backward compatibility.
- Changed MQTT Client to an ESP-IDF backed one which supports SSL/TLS X509 root CA bundles and transport over WS.
- Changed the `PROGMEM_WWW` flag to `EMBED_WWW` as there is technically speaking no PROGMEM on ESP32's.
- Updated dependencies to the latest version. Except SvelteKit.
### Fixed
- Fixed reactivity of System Status page.
### Removed
- Removed support for Arduino ESP OTA.
- HttpEndpoints and Websocket Server without a securityManager are no longer possible.
### Migrate from ESPAsyncWebServer to PsychicHttp
#### Migrate `main.cpp`
Change the server and ESPSvelteKit instances to PsychicHttpServer and give the ESP32SvelteKit constructor the number of http endpoints of your project.
```
PsychicHttpServer server;
ESP32SvelteKit esp32sveltekit(&server, 120);
```
Remove `server.begin();` in `void setup()`. This is handled by ESP32SvelteKit now.
#### Migrate `platformio.ini`
Remove the following `build_flags`:
```ini
; Increase queue size of SSE and WS
-D SSE_MAX_QUEUED_MESSAGES=64
-D WS_MAX_QUEUED_MESSAGES=64
-D CONFIG_ASYNC_TCP_RUNNING_CORE=0
-D NO_GLOBAL_ARDUINOOTA
-D PROGMEM_WWW
```
Add the following `build_flags` and adjust to your app, if needed:
```ini
-D BUILD_TARGET=\"$PIOENV\"
-D APP_NAME=\"ESP32-Sveltekit\" ; Must only contain characters from [a-zA-Z0-9-_] as this is converted into a filename
-D APP_VERSION=\"0.3.0\" ; semver compatible version string
-D EMBED_WWW
```
Remove the lib dependency `esphome/AsyncTCP-esphome @ ^2.0.0` and add `https://github.com/theelims/PsychicMqttClient.git`
Consider adjusting `board_ssl_cert_source = adafruit`, so that the new MQTT client has universal SSL/TLS support with a wide range of CA root certificates.
#### Migrate `factory_settings.ini`
The new MQTT client has slightly renamed factory settings:
```ini
; MQTT settings
-D FACTORY_MQTT_ENABLED=false
-D FACTORY_MQTT_URI=\"mqtts://mqtt.eclipseprojects.io:8883\"
-D FACTORY_MQTT_USERNAME=\"\" ; supports placeholders
-D FACTORY_MQTT_PASSWORD=\"\"
-D FACTORY_MQTT_CLIENT_ID=\"#{platform}-#{unique_id}\" ; supports placeholders
-D FACTORY_MQTT_KEEP_ALIVE=120
-D FACTORY_MQTT_CLEAN_SESSION=true
```
Max Topic Length is no longer needed.
#### Custom Stateful Services
Adapt the class constructor (`(PsychicHttpServer *server, ...`) to PsychicHttpServer.
Due to the loading sequence HttpEndoint and WebsocketServer both have gotten a `begin()` function to register their http endpoints with the server. This must be called in your stateful services' own `begin()` function:
```cpp
void LightStateService::begin()
{
_httpEndpoint.begin();
_webSocketServer.begin();
_state.ledOn = DEFAULT_LED_STATE;
onConfigUpdated();
}
```
## [0.2.2] - 2023-10-08
### Added
- Status reports reset-reason & uptime.
- AnalyticsService to draw graphs about heap usage and other time dependent values
- Added ping to WebSocket Server
- Use telemetry store with RSSI messages to gauge health of connection. Automatic reconnect for SSE and WS.
- Added user supplied features to FeatureService
- Compiler flag to let it serve the config JSONs for debug purposes
- Hard fork of ESPAsyncWebserver as it is unmaintained to fix bugs and add features
### Changed
- Changed JSON format for websocket server and removed "payload" property. JSON is the same as for MQTT or HTTP now.
- Changed features.ini to default `FT_SLEEP=0`
- Updated dependencies to latest version.
## [0.2.1] - 2023-09-11
### Fixed
- Fixed the boot loop issue for Arduino 6.4.0
## [0.2.0] - 2023-08-03
### Added
- Introduced CHANGELOG.md
- Added core temperature to the system status API
- Added mDNS / Bonjour / zeroConf for better discoverability in local network
- Added recovery mode which forces AP to spin up regardless from its settings
- Added push notification service to show notification toasts on all clients
- Added SSE to update RSSI in status bar on client
- Added firmware version to System Status API
- Added sleep service to send ESP32 into deep sleep. Wake-up with button using EXT1
- Added battery service to show battery state of charge in the status bar. Uses SSE.
- Added download firmware manager to pull firmware binaries e.g. from github release pages
- modified generate_cert_bundle.py from Espressif included into the build process to automatically create SSL Root CA Bundle
### Changed
- Improved system status with more meaningful presentation of available data
- Improved layout on small screens
- Increased queue size for SSE and WS to 64 instead of 32
- ESP32-SvelteKit loop()-function is its own task now
- ArduinoOTA handle runs in own task now
- AsyncTCP tasks run on Core 0 to move all networking related stuff to Core 0 and free up Core 1 for business logic
- Compiler flag on which core ESP32-sveltekit tasks should run
- Renamed WebSocketRxTx.h to WebSocketServer.h to create a distinction between WS Client and WS Server interfaces
- Made code of LightStateExample slightly more verbose
- getServer() returning a pointer to the AsyncWebServer instance.
- Updated frontend dependencies and packages to newest version.
### Depreciated
- ArduinoOTA feature is set to depreciate. It is unstable with mDNS causing some reset loops until it finally settles.
### Removed
- `FT_PROJECT` feature flag removed.
## [0.1.0] - 2023-05-18
This is the initial release of ESP32-sveltekit. With this it is feature complete to [rjwats/esp8266-react](https://github.com/rjwats/esp8266-react), where it forked from.
### Added
- Added copyright notes
### Changed
- Renaming into ESP32-sveltekit
- Small changes to reflect the slightly different file structure of sveltekit
- Build process for sveltekit
### Removed
- Dropping support for ESP8266
================================================
FILE: ESP32-sveltekit.code-workspace
================================================
{
"folders": [
{
"path": "."
}
],
"settings": {
"files.associations": {
"*.tcc": "cpp",
"algorithm": "cpp",
"esp32-hal-misc.c": "cpp",
"esp_crt_bundle.h": "c",
"functional": "cpp",
"array": "cpp",
"atomic": "cpp",
"bitset": "cpp",
"cctype": "cpp",
"clocale": "cpp",
"cmath": "cpp",
"cstdarg": "cpp",
"cstddef": "cpp",
"cstdint": "cpp",
"cstdio": "cpp",
"cstdlib": "cpp",
"cstring": "cpp",
"ctime": "cpp",
"cwchar": "cpp",
"cwctype": "cpp",
"deque": "cpp",
"list": "cpp",
"unordered_map": "cpp",
"vector": "cpp",
"exception": "cpp",
"iterator": "cpp",
"map": "cpp",
"memory": "cpp",
"memory_resource": "cpp",
"numeric": "cpp",
"optional": "cpp",
"random": "cpp",
"regex": "cpp",
"string": "cpp",
"string_view": "cpp",
"system_error": "cpp",
"tuple": "cpp",
"type_traits": "cpp",
"utility": "cpp",
"fstream": "cpp",
"initializer_list": "cpp",
"iosfwd": "cpp",
"istream": "cpp",
"limits": "cpp",
"new": "cpp",
"ostream": "cpp",
"sstream": "cpp",
"stdexcept": "cpp",
"streambuf": "cpp",
"cinttypes": "cpp",
"typeinfo": "cpp",
"unordered_set": "cpp",
"iomanip": "cpp"
}
}
}
================================================
FILE: LICENSE
================================================
ESP32 SvelteKit is distributed with two licenses for different sections of the
code. The back end code inherits the GNU LESSER GENERAL PUBLIC LICENSE Version 3
and is therefore distributed with said license. The front end code is distributed
under the MIT License.
MIT License
Copyright (C) 2023 - 2024 theelims
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:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
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.
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.
================================================
FILE: README.md
================================================
# ESP32 SvelteKit - Create Amazing IoT Projects
<div style="flex">
<img src="/docs/media/Screenshot_light.png" style="height:320px">
<img src="/docs/media/Screenshot_mobile.png" style="height:320px">
</div>
A simple and extensible framework for ESP32 based IoT projects with a feature-rich, beautiful, and responsive front-end build with [Sveltekit](https://kit.svelte.dev/), [TailwindCSS](https://tailwindcss.com/) and [DaisyUI](https://daisyui.com/). This is a project template to get you started in no time backed by a powerful back end service, an amazing front end served from the ESP32 and an easy to use build chain to get everything going.
It was forked from the fabulous [rjwats/esp8266-react](https://github.com/rjwats/esp8266-react) project, from where it inherited the mighty back end services.
> **Tip**: This template repository is not meant to be used stand alone. If you're just looking for a WiFi manager there are plenty of options available. This is a starting point when you need a rich web UI.
## Features
### :butterfly: Beautiful UI powered by DaisyUI and TailwindCSS
Beautiful, responsive UI which works equally well on desktop and on mobile. Gently animated for a snappy and modern feeling without ever being obtrusive or in the way. Easy theming with DaisyUI and media-queries to respect the users wish for a light or dark theme.
### :t-rex: Low Memory Footprint and Easy Customization by Courtesy of SvelteKit
SvelteKit is ideally suited to be served from constrained devices like an ESP32. It's unique approach leads to very slim files. No bloatware like other popular JS frameworks. Not only the low memory footprint make it ideal but the developer experience is also outstanding letting you customize the front end with ease. Adapt and add functionality as you need it. The back end has you covered as well.
### :telephone: Rich Communication Interfaces
Comes with a rich set of communication interfaces to cover most standard needs of an IoT application. Like MQTT client, HTTP RESTful API, a WebSocket based Event Socket and a classic Websocket Server. All communication channels are stateful and fully synchronized. Changes propagate and are communicated to all other participants. The states can be persisted on the file system as well. For accurate time keeping time can by synchronized over NTP.
### :file_cabinet: WiFi Provisioning and Management
Naturally ESP32 SvelteKit comes with rich features to manage all your WiFi needs. From pulling up an access point for provisioning or as fall back, to fully manage your WiFi networks. Scan for available networks and connect to them. Advanced configuration options like static IP are on board as well.
### :old_key: Secured API and User Management
Manage different user of your app with two authorization levels. An administrator and a guest user. Authenticate their API calls with a JWT token. Manage the user's profile from the admin interface. Use at own risk, as it is neither secure without the ability to use TLS/SSL encryption on the ESP32 server, nor very convenient, as only an admin can change passwords.
### :airplane: OTA Upgrade Service
The framework can provide two different channels for Over-the-Air updates. Either by uploading a \*.bin file from the web interface. Or by pulling a firmware image from an update server. This is implemented with the github release page as an example. It is even possible to have different build environments at the same time and the Github OTA process pulls the correct binary.
### :building_construction: Automated Build Chain
The automated build chain takes out the pain and tears of getting all the bits and pieces play nice together. The repository contains a PlatformIO project at its heart. A SvelteKit project for the frontend code and a mkdocs project for the documentation go alongside. The PlatformIO build tools not only build the SvelteKit frontend with Vite, but also ensure that the build results are gzipped and find their way into the flash memory of the ESP32. You have two choices to serve the frontend either from the flash partition, or embedded into the firmware binary. The latter is much more friendly if your frontend code should be distributed OTA as well, leaving all configuration files intact.
### :icecream: Compatible with all ESP32 Flavours
The code runs on many variants of the ESP32 chip family. From the plain old ESP32, the ESP32-S3 and ESP32-C3. Other ESP32 variants might work, but haven't been tested. Sorry, no support for the older ESP8266. Go with one of the ESP32's instead.
## Visit the Project Site
[https://theelims.github.io/ESP32-sveltekit/](https://theelims.github.io/ESP32-sveltekit/)
## Join our [Discord](https://discord.gg/MTn9mVUG5n)
## Libraries Used
- [SvelteKit](https://kit.svelte.dev/)
- [Tailwind CSS](https://tailwindcss.com/)
- [DaisyUI](https://daisyui.com/)
- [tabler ICONS](https://tabler-icons.io/)
- [unplugin-icons](https://github.com/antfu/unplugin-icons)
- [svelte-modals](https://svelte-modals.mattjennings.io/)
- [svelte-dnd-action](https://github.com/isaacHagoel/svelte-dnd-action)
- [ArduinoJson](https://github.com/bblanchon/ArduinoJson)
- [PsychicHttp](https://github.com/hoeken/PsychicHttp)
- [PsychicMqttClient](https://github.com/theelims/PsychicMqttClient)
## Licensing
ESP32 SvelteKit is distributed with two licenses for different sections of the code. The back end code inherits the GNU LESSER GENERAL PUBLIC LICENSE Version 3 and is therefore distributed with said license. The front end code is distributed under the MIT License. See the [LICENSE](LICENSE) for a full text of both licenses.
================================================
FILE: docs/buildprocess.md
================================================
# Build Process
The build process is controlled by [platformio.ini](https://github.com/theelims/ESP32-sveltekit/platformio.ini) and automates the build of the front end website with Vite as well as the binary compilation for the ESP32 firmware. Whenever PlatformIO is building a new binary it will call the python script [build_interface.py](https://github.com/theelims/ESP32-sveltekit/scripts/build_interface.py) to action. It will check the frontend files for changes. If necessary it will start the Vite build and gzip the resulting files either to the `data/` directory or embed them into a header file. In case the WWW files go into a LITTLEFS partition a file system image for the flash is created for the default build environment and upload to the ESP32.
## Changing the JS package manager
This project uses NPM as the default package manager. However, many users might have different preferences and like to use YARN or PNPM instead. Just switch the interface to one of the other package managers. The build script identify the package manager by the presence of its lock-file and start the vite build process accordingly.
## Serving from Flash or Embedding into the Binary
The front end website can be served either from the LITTLEFS partition of the flash, or embedded into the firmware binary (default). Later has the advantage that only one binary needs to be distributed easing the OTA process. Further more this is desirable if you like to preserve the settings stored in the LITTLEFS partition, or have other files there that need to survive a firmware update. To serve from the LITTLEFS partition instead please comment the following build flag out:
```ini
build_flags =
...
-D EMBED_WWW
```
### Partitioning
If you choose to embed the frontend it becomes part of the firmware binary (default). As many ESP32 modules only come with 4MB built-in flash this results in the binary being too large for the reserved flash. Therefor a partition scheme with a larger section for the executable code is selected. However, this limits the LITTLEFS partition to 200kb. There are a great number of [default partition tables](https://github.com/espressif/arduino-esp32/tree/master/tools/partitions) for Arduino-ESP32 to choose from. If you have 8MB or 16MB flash this would be your first choice. If you don't need OTA you can choose a partition scheme without OTA.
Should you want to deploy the frontend from the flash's LITTLEFS partition on a 4MB chip you need to comment out the following two lines. Otherwise the 200kb will not be large enough to host the front end code.
```ini
board_build.partitions = min_spiffs.csv
```
## Selecting Features
Many of the framework's built in features may be enabled or disabled as required at compile time. This can help save sketch space and memory if your project does not require the full suite of features. The access point and WiFi management features are "core features" and are always enabled. Feature selection may be controlled with the build flags defined in [features.ini](https://github.com/theelims/ESP32-sveltekit/blob/main/features.ini).
Customize the settings as you see fit. A value of 0 will disable the specified feature:
```ini
-D FT_SECURITY=1
-D FT_MQTT=1
-D FT_NTP=1
-D FT_UPLOAD_FIRMWARE=1
-D FT_DOWNLOAD_FIRMWARE=1
-D FT_SLEEP=1
-D FT_BATTERY=1
-D FT_ETHERNET=1
```
| Flag | Description |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| FT_SECURITY | Controls whether the [security features](statefulservice.md#security-features) are enabled. Disabling this means you won't need to authenticate to access the device and all authentication predicates will be bypassed. |
| FT_MQTT | Controls whether the MQTT features are enabled. Disable this if your project does not require MQTT support. |
| FT_NTP | Controls whether network time protocol synchronization features are enabled. Disable this if your project does not require accurate time. |
| FT_UPLOAD_FIRMWARE | Controls whether the manual upload firmware feature is enabled. Disable this if you won't be manually uploading firmware. |
| FT_DOWNLOAD_FIRMWARE | Controls whether the firmware download feature is enabled. Disable this if you won't firmware pulled from a server. |
| FT_SLEEP | Controls whether the deep sleep feature is enabled. Disable this if your device is not battery operated or you don't need to place it in deep sleep to save energy. |
| FT_BATTERY | Controls whether the battery state of charge shall be reported to the clients. Disable this if your device is not battery operated. |
| FT_ETHERNET | Controls whether an ethernet interface will be used. Disable this if your device has no ethernet interface connected. |
In addition custom features might be added or removed at runtime. See [Custom Features](statefulservice.md#custom-features) on how to use this in your application.
## Factory Settings
The framework has built-in factory settings which act as default values for the various configurable services where settings are not saved on the file system. These settings can be overridden using the build flags defined in [factory_settings.ini](https://github.com/theelims/ESP32-sveltekit/blob/main/factory_settings.ini). All strings entered here must be escaped, especially special characters.
Customize the settings as you see fit, for example you might configure your home WiFi network as the factory default:
```ini
-D FACTORY_WIFI_SSID=\"My\ Awesome\ WiFi\ Network\"
-D FACTORY_WIFI_PASSWORD=\"secret\"
-D FACTORY_WIFI_HOSTNAME=\"awesome_light_controller\"
```
### Default access point settings
By default, the factory settings configure the device to bring up an access point on start up which can be used to configure the device:
- SSID: ESP32-Sveltekit
- Password: esp-sveltekit
### Security settings and user credentials
By default, the factory settings configure two user accounts with the following credentials:
| Username | Password |
| -------- | -------- |
| admin | admin |
| guest | guest |
It is recommended that you change the user credentials from their defaults to better protect your device. You can do this in the user interface, or by modifying [factory_settings.ini](https://github.com/theelims/ESP32-sveltekit/blob/main/factory_settings.ini) as mentioned above.
### Customizing the factory time zone setting
Changing factory time zone setting is a common requirement. This requires a little effort because the time zone name and POSIX format are stored as separate values for the moment. The time zone names and POSIX formats are contained in the UI code in [timezones.ts](https://github.com/theelims/ESP32-sveltekit/blob/main/interface/src/routes/connections/timezones.ts). Take the appropriate pair of values from there, for example, for Los Angeles you would use:
```ini
-D FACTORY_NTP_TIME_ZONE_LABEL=\"America/Los_Angeles\"
-D FACTORY_NTP_TIME_ZONE_FORMAT=\"PST8PDT,M3.2.0,M11.1.0\"
```
### Placeholder substitution
Various settings support placeholder substitution, indicated by comments in [factory_settings.ini](https://github.com/theelims/ESP32-sveltekit/blob/main/factory_settings.ini). This can be particularly useful where settings need to be unique, such as the Access Point SSID or MQTT client id. The following placeholders are supported:
| Placeholder | Substituted value |
| ------------ | --------------------------------------------------------------------- |
| #{platform} | The microcontroller platform, e.g. "esp32" or "esp32c3" |
| #{unique_id} | A unique identifier derived from the MAC address, e.g. "0b0a859d6816" |
| #{random} | A random number encoded as a hex string, e.g. "55722f94" |
## Other Build Flags
### Cross-Origin Resource Sharing
If you need to enable Cross-Origin Resource Sharing (CORS) on the ESP32 server just uncomment the following build flags:
```ini
build_flags =
...
; Uncomment to configure Cross-Origin Resource Sharing
-D ENABLE_CORS
-D CORS_ORIGIN=\"*\"
```
This will add the `Access-Control-Allow-Origin` and `Access-Control-Allow-Credentials` headers to any request made.
### ESP32 `CORE_DEBUG_LEVEL`
The ESP32 Arduino Core and many other libraries use the ESP Logging tools. To enable these debug and error messages from deep inside your libraries uncomment the following build flag.
```ini
build_flags =
...
-D CORE_DEBUG_LEVEL=5
```
It accepts values from 5 (Verbose) to 1 (Errors) for different information depths to be logged on the serial terminal. If commented out there won't be debug messages from the core libraries. For a production build you should comment this out.
### Serve Config Files
By enabling this build flag the ESP32 will serve all config files stored on the LittleFS flash partition under `http:\\[IP]\config\[filename].json`. This can be helpful to troubleshoot problems. However, it is strongly advised to disable this for production builds.
```ini
build_flags =
...
-D SERVE_CONFIG_FILES
```
### Serial Info
In some circumstances it might be beneficial to not print any information on the serial consol (Serial1 or USB CDC). By commenting out the following build flag ESP32-Sveltekit will not print any information on the serial console.
```ini
build_flags =
...
-D SERIAL_INFO
```
## SSL Root Certificate Store
Some features like firmware download or the MQTT client require a SSL connection. For that the SSL Root CA certificate must be known to the ESP32. The build system contains a python script derived from Espressif ESP-IDF building a certificate store containing one or more certificates. In order to create the store you must uncomment the three lines below in `platformio.ini`.
```ini
extra_scripts =
pre:scripts/generate_cert_bundle.py
board_build.embed_files = src/certs/x509_crt_bundle.bin
board_ssl_cert_source = adafruit
```
The script will download a public certificate store from Mozilla (`board_ssl_cert_source = mozilla`) or a repository curated by Adafruit (`board_ssl_cert_source = adafruit`) or (`board_ssl_cert_source = adafruit-full`), builds a binary containing all certs and embeds this into the firmware. This will add ~65kb to the firmware image. Should you only need a few known certificates you can place their `*.pem` or `*.der` files in the [ssl_certs](https://github.com/theelims/ESP32-sveltekit/blob/main/ssl_certs) folder and change `board_ssl_cert_source = folder`. Then only these certificates will be included in the store. This is especially useful, if you only need to connect to know servers and need to shave some kb off the firmware image:
!!! info
To enable SSL the feature `FT_NTP=1` must be enabled as well.
!!! bug
At the moment there is a bug with the certificate bundle when using the firmware download e.g. from Github. By using the build flag `-D DOWNLOAD_OTA_SKIP_CERT_VERIFY` you may skip certificate validation to keep OTA working. Only OTA seems affected, not MQTT. Keep in mind, that this voids the main security feature of SSL and allows man-in-the-middle attacks.
## Vite and LittleFS 32 Character Limit
The static files for the website are build using vite. By default vite adds a unique hash value to all filenames for improved caching performance. However, LittleFS on the ESP32 is limited to filenames with 32 characters. This restricts the number of characters available for the user to name svelte files. To give a little bit more headroom a vite-plugin removes all hash values, as they offer no benefit on an ESP32. However, have the 32 character limit in mind when naming files. Excessively long names may still cause some issues when building the LittleFS binary.
## Merged Firmware File for Web Flasher
The PIO build system calls a script `merge_bin.py` to create a merged firmware binary ready to be used with [ESP Web Tools](https://esphome.github.io/esp-web-tools/). The file is located under the PIO build folder. Typically `build/merged/{APP_NAME}_{$PIOENV}_{APP_VERSION}.bin`.
================================================
FILE: docs/components.md
================================================
# Components
The project includes a number of components to create the user interface. Even though DaisyUI has a huge set of components, it is often beneficial to recreate them as a Svelte component. This offers a much better integration into the Svelte way of doing things, is less troublesome with animations and results in a overall better user experience.
## Collapsible
A collapsible container to hide / show content by clicking on the arrow button.
```ts
import Collapsible from "$lib/components/Collapsible.svelte";
```
It exports a closed / open state with `export open` which you can use to determine the mounting behavior of the component.
### Slots
The component has two slots. A named slot `title` for the collapsible title and the main slot for the content that can be hidden or shown.
```
<Collapsible open={false} class="shadow-lg" on:closed={doSomething}>
<span slot="title">Title</span>
...
</Collapsible>
```
The `class` attribute may be used as normal to style the container. By default there is no special styling like background or shadows to accentuate the container element.
### Events
The collapsible component dispatches two events. `on:closed` when the collapsible is closed and `on:opened` when it is opened. You can bind to them as to any other event.
## InputPassword
This is an input field specifically for passwords. It comes with an "eye"-button on the right border to toggle the visibility of the password. It natively blends into the style from DaisyUI.
```ts
import InputPassword from "$lib/components/InputPassword.svelte";
```
You may use it like any other form element:
```
<InputPassword id="pwd" bind:value={password} />
```
## RSSIIndicator
This shows the popular WiFi strength indicator icon with differently highlighted circles depending on the received signal strength (RSSI) of the WiFi signal. In addition it can display the signal strength in raw "dBm" as an indicator badge.
```ts
import RssiIndicator from "$lib/components/RSSIIndicator.svelte";
```
Just use and style as you please. It doesn't have any slots or events.
```
<RssiIndicator showDBm={true} rssi_dbm={-85} class="text-base-content h-10 w-10" />
```
Two exports control the behavior of the component. `rssi_dbm` accepts a negative number of the raw RSSI in dBm and is used to determine how many circles of reception should be shown. An optional boolean `showDBm` (defaults to `false`) shows the indicator badge with the dBm value.
## Settings Card
A Settings Card is in many ways similar to a [collapsible](#collapsible). However, it is styled and is the main element of many settings menus. It also accepts an icon in a dedicate slot and unlike collapsible has no events.
```ts
import SettingsCard from "$lib/components/SettingsCard.svelte";
```
### Slots
Three slots are available. Besides the main slot for the content there is a named slot for the `title` and s second one for the `icon`.
```
<SettingsCard collapsible={true} open={false}>
<Icon slot="icon" class="lex-shrink-0 mr-2 h-6 w-6 self-end" />
<span slot="title">Title</span>
...
</SettingsCard>
```
The component exports two properties to determine its behavior. `collapsible` is a boolean describing wether the component should behave like a collapsible in the first place. `open` is a boolean as well and if set true shows the full content of the body on mount.
## Spinner
A small component showing an animated spinner which can be used while waiting for data.
```ts
import Spinner from "$lib/components/Spinner.svelte";
```
No slots, no events, no properties. Just use `<Spinner/>` whenever something is loading.
## Toast Notifications
Toast notifications are implemented as a writable store and are easy to use from any script section. They are an easy way to feedback to the user. To use them just import the notifications store
```ts
import { notifications } from "$lib/components/toasts/notifications";
```
and call one of the 4 toast methods:
| Method | Description |
| -------------------------------------------------- | --------------------------------------------------- |
| `notification.error(msg:string, timeout:number)` | :octicons-x-circle-16: Shows an error message |
| `notification.warning(msg:string, timeout:number)` | :octicons-alert-16: Shows a warning message |
| `notification.info(msg:string, timeout:number)` | :octicons-info-16: Shows an info message |
| `notification.success(msg:string, timeout:number)` | :octicons-check-circle-16: Shows as success message |
Each method takes an `msg`-string as an argument, which will be shown as the message body. It accepts HTML to enrich your toasts, if you should desire to do so. The `timeout` argument specifies how many milliseconds the toast notification shall be shown to the user.
## Github Update Dialog
This is a modal showing the update progress, possible error messages and makes a full page refresh 5 seconds after the OTA was successful.
## Update Indicator
The update indicator is a small widget shown in the upper right corner of the status bar. It indicates the availability of a newer firmware release then the current one. Upon pressing the icon it will automatically update the firmware to the latest release. By default this works through the Github Latest Release API. This must be customized should you use a different update server. Have a look at the [source file](https://github.com/theelims/ESP32-sveltekit/blob/main/interface/src/lib/components/GithubUpdateDialog.svelte) to see what portions to update.
## Info Dialog
Shows a modal on the UI which must be deliberately dismissed. It features a `title` and a `message` property. The dismiss button can be customized via the `dismiss` property with a label and an icon. `onDismiss` call back must close the modal and can be used to do something when closing the info dialog.
```ts
import InfoDialog from "$lib/components/InfoDialog.svelte";
modals.open(InfoDialog, {
title: 'You have a new Info',
message:
'Something really important happened that justifies showing you a modal which must be clicked away.',
dismiss: { label: 'OK', icon: Check },
onDismiss: () => modals.close();
});
```
This modal is based on [svelte-modals](https://svelte-modals.mattjennings.io/) where you can find further information.
## Confirm Dialog
Shows a confirm modal on the UI which must be confirmed to proceed, or can be canceled. It features a `title` and a `message` property. The `confirm` and `cancel` buttons can be customized via the `labels` property with a label and an icon. `onConfirm` call back must close the modal and can be used to trigger further actions.
```ts
import ConfirmDialog from "$lib/components/ConfirmDialog.svelte";
modals.open(ConfirmDialog, {
title: "Confirm what you are doing",
message: "Are you sure you want to proceed? This could break stuff!",
labels: {
cancel: { label: "Abort", icon: Cancel },
confirm: { label: "Confirm", icon: Check },
},
onConfirm: () => modals.close(),
});
```
This modal is based on [svelte-modals](https://svelte-modals.mattjennings.io/) where you can find further information.
================================================
FILE: docs/gettingstarted.md
================================================
# Getting Started
## Prerequisites
This project has quite a complicated build chain to prepare the frontend code for the ESP32. You will need to install some tools to make this all work, starting with a powerful code editor.
### Softwares to Install
Please install the following software, if you haven't already:
- [VSCode](https://code.visualstudio.com/) - IDE for development
- [Node.js](https://nodejs.org) - For building the interface with npm
### VSCode Plugins and Setups
Please install the following mandatory VSCode Plugins:
- [PlatformIO](https://platformio.org/) - Embedded development platform
- [Prettier](https://prettier.io/) - Automated code formatter
- Svelte for VS Code - Makes working with Svelte much easier
- Svelte Intellisense - Another Svelte tool
- Tailwind CSS Intellisense - Makes working with Tailwind CSS much easier
- [Prettier plugin for Tailwind CSS](https://github.com/tailwindlabs/prettier-plugin-tailwindcss) - Automatically sorts the Tailwind classes into their recommended order
Lastly, if you want to make use of Materials for MkDocs as your documentation engine, install [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/) by typing the following into the VSCode terminal:
```bash
pip install mkdocs-material
```
!!! tip
You might need to run this as administrator, if you getting an error message.
### Project Structure
| Resource | Description |
| -------------------------------------------------------------------------------------- | ---------------------------------------------------------------- |
| [.github/](https://github.com/theelims/ESP32-sveltekit/blob/main/.github) | Github CI pipeline to deploy MkDocs to gh-pages |
| [docs/](https://github.com/theelims/ESP32-sveltekit/blob/main/docs) | MkDocs documentation files |
| [interface/](https://github.com/theelims/ESP32-sveltekit/blob/main/interface) | SvelteKit based front end |
| [lib/framework/](https://github.com/theelims/ESP32-sveltekit/blob/main/lib/framework) | C++ back end for the ESP32 device |
| [src/](https://github.com/theelims/ESP32-sveltekit/blob/main/src) | The main.cpp and demo project to get you started |
| [scripts/](https://github.com/theelims/ESP32-sveltekit/tree/main/scripts) | Scripts that build the interface as part of the platformio build |
| [platformio.ini](https://github.com/theelims/ESP32-sveltekit/blob/main/platformio.ini) | PlatformIO project configuration file |
| [mkdocs.yaml](https://github.com/theelims/ESP32-sveltekit/blob/main/mkdocs.yaml) | MkDocs project configuration file |
## Setting up PlatformIO
### Setup Build Target
!!! danger "Do not use the PlatformIO UI for editing platformio.ini"
It is tempting to use the PlatformIO user interface to add dependencies or parameters to platformio.ini. However, doing so will remove all "irrelevant" information like comments from the file. Please edit the file directly in the editor.
[platformio.ini](https://github.com/theelims/ESP32-sveltekit/blob/main/platformio.ini) is the central file controlling the whole build process. It comes pre-configure with a few boards which have different ESP32 chips. It needs to be adapted to the board you want to program.
```ini
[platformio]
...
default_envs = esp32-s3-devkitc-1
...
[env:adafruit_feather_esp32_v2]
board = adafruit_feather_esp32_v2
board_build.mcu = esp32
[env:lolin_c3_mini]
board = lolin_c3_mini
board_build.mcu = esp32c3
[env:esp32-s3-devkitc-1]
board = esp32-s3-devkitc-1
board_build.mcu = esp32s3
```
If your board is not listed in the platformio.ini you may look in the [official board list](https://docs.platformio.org/en/latest/boards/index.html#espressif-32) for supported boards and add their information accordingly. Either delete the obsolete `[env:...]` sections, or change your board as `default_envs = ...`.
!!! info "Default setup is for an ESP32-S3-DevKitC/M board"
The projects platformio.ini defaults for an ESP32-S3-DevKitC/M board by Espressif connected to the UART USB port. If you use an other board and the projects shows an undesired behavior it is likely that some parts do not match with pin definitions.
### Build & Upload Process
After you've changed [platformio.ini](https://github.com/theelims/ESP32-sveltekit/blob/main/platformio.ini) to suit your board you can upload the sample code to your board. This will download all ESP32 libraries and execute `node install` to install all node packages as well. Select your board's environment under the PlatformIO tab and hit `Upload and Monitor`.

The first build process will take a while. After a couple of minutes you can see the ESP32 outputting information on the terminal. Some of the python scripts might need to install additional packages. In that case the first build process will fail. Just run it a second time.
!!! tip "Use several terminals in parallel"
VSCode allows you to have more then one terminal running at the same time. You can dedicate one terminal to the serial monitor, while having the development server running in an other terminal.
## Setting up SvelteKit
### Setup Proxy for Development
To ease the frontend development you can deploy the back end code on an ESP32 board and pass the websocket and REST API calls through the development server's proxy.
The [vite.config.ts](https://github.com/theelims/ESP32-sveltekit/blob/main/interface/vite.config.ts) file defines the location of the services which the development server will proxy. This is defined by the "target" property, which will need to be changed to the the IP address or hostname of the device running the firmware. Change this for both, "http://" and "ws://".
```ts
proxy: {
// Proxying REST: http://localhost:5173/rest/bar -> http://192.168.1.83/rest/bar
'/rest': {
target: 'http://192.168.1.83',
changeOrigin: true,
},
// Proxying websockets ws://localhost:5173/ws -> ws://192.168.1.83/ws
'/ws': {
target: 'ws://192.168.1.83',
changeOrigin: true,
ws: true,
},
},
```
!!! tip
You must restart the development server for changes of the proxy location to come into effect.
### Development Server
The interface comes with Vite as a development server. It allows hot module reloading reflecting code changes to the front end instantly in your browser. Open a new terminal session and execute the following commands:
```bash
cd interface
npm run dev
```
Follow the link to access the front end in your browser.
## Setup Material for mkdocs
Material for MkDocs allows you to create great technical documentation pages just from markup. If you don't want to use it just delete the `.github` and `docs` folder, as well as `mkdocs.yaml`.
Otherwise initiate the github CI pipeline by committing and pushing to your repository once. This triggers the automatic build. After a few minutes a new branch `gh-pages` containing the static website with your documentation should appear. To deploy it go to your github repository go under settings and complete the following steps.

### Development Server
MkDocs comes with a build-in development server which supports hot reload as well. Open a new terminal session in VSCode and type
```
mkdocs serve
```
================================================
FILE: docs/index.md
================================================
---
hide:
- navigation
- toc
---
# ESP32 SvelteKit - Create Amazing IoT Projects
<div style="flex">
<img src="media/Screenshot_light.png" style="height:480px">
<img src="media/Screenshot_mobile.png" style="height:480px">
</div>
A simple and extensible framework for ESP32 based IoT projects with a feature-rich, beautiful, and responsive front-end build with [Sveltekit](https://kit.svelte.dev/), [TailwindCSS](https://tailwindcss.com/) and [DaisyUI](https://daisyui.com/). This is a project template to get you started in no time backed by a powerful back end service, an amazing front end served from the ESP32 and an easy to use build chain to get everything going.
It was forked from the fabulous [rjwats/esp8266-react](https://github.com/rjwats/esp8266-react) project, from where it inherited the mighty back end services.
!!! info
This template repository is not meant to be used stand alone. If you're just looking for a WiFi manager there are plenty of options available. This is a starting point when you need a rich web UI.
## Features
### :butterfly: Beautiful UI powered by DaisyUI and TailwindCSS
Beautiful, responsive UI which works equally well on desktop and on mobile. Gently animated for a snappy and modern feeling without ever being obtrusive or in the way. Easy theming with DaisyUI and media-queries to respect the users wish for a light or dark theme.
### :simple-svelte: Low Memory Footprint and Easy Customization by Courtesy of SvelteKit
SvelteKit is ideally suited to be served from constrained devices like an ESP32. It's unique approach leads to very slim files. No bloatware like other popular JS frameworks. Not only the low memory footprint make it ideal but the developer experience is also outstanding letting you customize the front end with ease. Adapt and add functionality as you need it. The back end has you covered as well.
### :telephone: Rich Communication Interfaces
Comes with a rich set of communication interfaces to cover most standard needs of an IoT application. Like MQTT client, HTTP RESTful API, a WebSocket based Event Socket and a classic Websocket Server. All communication channels are stateful and fully synchronized. Changes propagate and are communicated to all other participants. The states can be persisted on the file system as well. For accurate time keeping time can by synchronized over NTP.
### :file_cabinet: WiFi Provisioning and Management
Naturally ESP32 SvelteKit comes with rich features to manage all your WiFi needs. From pulling up an access point for provisioning or as fall back, to fully manage your WiFi networks. Scan for available networks and connect to them. Advanced configuration options like static IP are on board as well.
### :people_with_bunny_ears_partying: Secured API and User Management
Manage different user of your app with two authorization levels. An administrator and a guest user. Authenticate their API calls with a JWT token. Manage the user's profile from the admin interface. Use at own risk, as it is neither secure without the ability to use TLS/SSL encryption on the ESP32 server, nor very convenient, as only an admin can change passwords.
### :airplane: OTA Upgrade Service
The framework can provide two different channels for Over-the-Air updates. Either by uploading a \*.bin file from the web interface. Or by pulling a firmware image from an update server. This is implemented with the github release page as an example. It is even possible to have different build environments at the same time and the Github OTA process pulls the correct binary.
### :construction_site: Automated Build Chain
The automated build chain takes out the pain and tears of getting all the bits and pieces play nice together. The repository contains a PlatformIO project at its heart. A SvelteKit project for the frontend code and a mkdocs project for the documentation go alongside. The PlatformIO build tools not only build the SvelteKit frontend with Vite, but also ensure that the build results are gzipped and find their way into the flash memory of the ESP32. You have two choices to serve the frontend either from the flash partition, or embedded into the firmware binary. The latter is much more friendly if your frontend code should be distributed OTA as well, leaving all configuration files intact.
### :fontawesome-solid-microchip: Compatible with all ESP32 Flavours
The code runs on all variants of the ESP32 chip family. From the plain old ESP32, the ESP32-S3 and ESP32-C3. Other ESP32 variants might work, but haven't been tested. Sorry, no support for the older ESP8266. Go with one of the ESP32's instead.
[Let's get started!](gettingstarted.md)
## License
ESP32 SvelteKit is distributed with two licenses for different sections of the code. The back end code inherits the GNU LESSER GENERAL PUBLIC LICENSE Version 3 and is therefore distributed with said license. The front end code is distributed under the MIT License. See the [LICENSE](https://github.com/theelims/ESP32-sveltekit/blob/main/LICENSE) for a full text of both licenses.
================================================
FILE: docs/restfulapi.md
================================================
# RESTful API
The back end exposes a number of API endpoints which are referenced in the table below.
| Method | Request URL | Authentication | POST JSON Body | Info |
| ------ | --------------------------------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
| GET | /rest/features | `NONE_REQUIRED` | none | Tells the client which features of the UI should be use |
| GET | /rest/mqttStatus | `IS_AUTHENTICATED` | none | Current MQTT connection status |
| GET | /rest/mqttSettings | `IS_ADMIN` | none | Currently used MQTT settings |
| POST | /rest/mqttSettings | `IS_ADMIN` | `{"enabled":false,"uri":"mqtt://192.168.1.12:1883","username":"","password":"","client_id":"esp32-f412fa4495f8","keep_alive":120,"clean_session":true,"message_interval_ms":0}` | Update MQTT settings with new parameters |
| GET | /rest/ntpStatus | `IS_AUTHENTICATED` | none | Current NTP connection status |
| GET | /rest/ntpSettings | `IS_ADMIN` | none | Current NTP settings |
| POST | /rest/ntpSettings | `IS_ADMIN` | `{"enabled": true,"server": "time.google.com","tz_label": "Europe/London","tz_format": "GMT0BST,M3.5.0/1,M10.5.0"}` | Update the NTP settings |
| GET | /rest/apStatus | `IS_AUTHENTICATED` | none | Current AP status and client information |
| GET | /rest/apSettings | `IS_ADMIN` | none | Current AP settings |
| POST | /rest/apSettings | `IS_ADMIN` | `{"provision_mode": 1,"ssid": "ESP32-SvelteKit-e89f6d20372c","password": "esp-sveltekit","channel": 1,"ssid_hidden": false,"max_clients": 4,"local_ip": "192.168.4.1","gateway_ip": "192.168.4.1","subnet_mask": "255.255.255.0"}` | Update AP settings |
| GET | /rest/wifiStatus | `IS_AUTHENTICATED` | none | Current status of the wifi client connection |
| GET | /rest/scanNetworks | `IS_ADMIN` | none | Async Scan for Networks in Range |
| GET | /rest/listNetworks | `IS_ADMIN` | none | List networks in range after successful scanning. Otherwise triggers scanning. |
| GET | /rest/wifiSettings | `IS_ADMIN` | none | Current WiFi settings |
| POST | /rest/wifiSettings | `IS_ADMIN` | `{"hostname":"esp32-f412fa4495f8","connection_mode":1,"wifi_networks":[{"ssid":"YourSSID","password":"YourPassword","static_ip_config":false}]}` | Update WiFi settings and credentials |
| GET | /rest/systemStatus | `IS_AUTHENTICATED` | none | Get system information about the ESP. |
| POST | /rest/restart | `IS_ADMIN` | none | Restart the ESP32 |
| POST | /rest/factoryReset | `IS_ADMIN` | none | Reset the ESP32 and all settings to their default values |
| POST | /rest/uploadFirmware | `IS_ADMIN` | none | File upload of firmware.bin |
| POST | /rest/signIn | `NONE_REQUIRED` | `{"password": "admin","username": "admin"}` | Signs a user in and returns access token |
| GET | /rest/securitySettings | `IS_ADMIN` | none | retrieves all user information and roles |
| POST | /rest/securitySettings | `IS_ADMIN` | `{"jwt_secret": "734cb5bb-5597b722", "users": [{"username": "admin", "password": "admin", "admin": true}, {"username": "guest", "password": "guest", "admin": false, }]}` | retrieves all user information and roles |
| GET | /rest/verifyAuthorization | `NONE_REQUIRED` | none | Verifies the content of the auth bearer token |
| GET | /rest/generateToken?username={username} | `IS_ADMIN` | `{"token": "734cb5bb-5597b722"}` | Generates a new JWT token for the user from username |
| POST | /rest/sleep | `IS_AUTHENTICATED` | none | Puts the device in deep sleep mode |
| POST | /rest/downloadUpdate | `IS_ADMIN` | `{"download_url": "https://github.com/theelims/ESP32-sveltekit/releases/download/v0.1.0/firmware_esp32s3.bin"}` | Download link for OTA. This requires a valid SSL certificate and will follow redirects. |
| GET | /rest/coreDump | `IS_AUTHENTICATED` | Text | Core dump of the last crash. |
================================================
FILE: docs/statefulservice.md
================================================
# Developing with the Framework
The back end is a set of REST endpoints hosted by a [PsychicHttp](https://github.com/hoeken/PsychicHttp) instance. The ['lib/framework'](https://github.com/theelims/ESP32-sveltekit/blob/main/lib/framework) directory contains the majority of the back end code. The framework contains a number of useful utility classes which you can use when extending it. The project also comes with a demo project to give you some help getting started.
The framework's source is split up by feature, for example [WiFiScanner.h](https://github.com/theelims/ESP32-sveltekit/blob/main/lib/framework/WiFiScanner.h) implements the end points for scanning for available networks where as [WiFiSettingsService.h](https://github.com/theelims/ESP32-sveltekit/blob/main/lib/framework/WiFiSettingsService.h) handles configuring the WiFi settings and managing the WiFi connection.
## Initializing the framework
The ['src/main.cpp'](https://github.com/theelims/ESP32-sveltekit/blob/main/src/main.cpp) file constructs the web server and initializes the framework. You can add endpoints to the server here to support your IoT project. The main loop is also accessible so you can run your own code easily.
The following code creates the web server and esp32sveltekit framework:
```cpp
PsychicHttpServer server;
ESP32SvelteKit esp32sveltekit(&server, 120);
```
ESP32SvelteKit is instantiated with a reference to the server and a number of HTTP endpoints. The underlying ESP-IDF HTTP Server statically allocates memory for each endpoint and needs to know how many there are. Best is to inspect your WWWData.h file for the number of Endpoints from SvelteKit (currently 60), the framework itself has 37 endpoints, and Lighstate Demo has 7 endpoints. Each `_server.on()` counts as an endpoint. Don't forget to add a couple of spare, just in case. Each HttpEndpoint adds 2 endpoints, if CORS is enabled it adds an other endpoint for the CORS preflight request.
Now in the `setup()` function the initialization is performed:
```cpp
void setup() {
// start serial and filesystem
Serial.begin(SERIAL_BAUD_RATE);
// start the framework and demo project
esp32sveltekit.begin();
}
```
`server.begin()` is called by ESP32-SvelteKit, as the start-up sequence is crucial.
## Stateful Service
The framework promotes a modular design and exposes features you may re-use to speed up the development of your project. Where possible it is recommended that you use the features the frameworks supplies. These are documented in this section and a comprehensive example is provided by the demo project.
The following diagram visualizes how the framework's modular components fit together, each feature is described in detail below.

The [StatefulService.h](https://github.com/theelims/ESP32-sveltekit/blob/main/lib/framework/StatefulService.h) class is responsible for managing state. It has an API which allows other code to update or respond to updates in the state it manages. You can define a data class to hold state, then build a StatefulService class to manage it. After that you may attach HTTP endpoints, WebSockets or MQTT topics to the StatefulService instance to provide commonly required features.
Here is a simple example of a state class and a StatefulService to manage it:
```cpp
class LightState {
public:
bool on = false;
uint8_t brightness = 255;
};
class LightStateService : public StatefulService<LightState> {
};
```
### Update Handler
You may listen for changes to state by registering an update handler callback. It is possible to remove an update handler later if required.
```cpp
// register an update handler
update_handler_id_t myUpdateHandler = lightStateService.addUpdateHandler(
[&](const String& originId) {
Serial.print("The light's state has been updated by: ");
Serial.println(originId);
}
);
// remove the update handler
lightStateService.removeUpdateHandler(myUpdateHandler);
```
An "originId" is passed to the update handler which may be used to identify the origin of an update. The default origin values the framework provides are:
| Origin | Description |
| -------------------------- | ----------------------------------------------- |
| http | An update sent over REST (HttpEndpoint) |
| mqtt | An update sent over MQTT (MqttEndpoint) |
| websocketserver:{clientId} | An update sent over WebSocket (WebSocketServer) |
### Hook Handler
Sometimes if can be desired to hook into every update of an state, even if the StateUpdateResult is `StateUpdateResult::UNCHANGED` and the update handler isn't called. In such cases you can use the hook handler. Similarly it can be removed later.
```cpp
// register an update handler
hook_handler_id_t myHookHandler = lightStateService.addHookHandler(
[&](const String& originId, StateUpdateResult &result) {
Serial.printf("The light's state has been updated by: %s with result %d\n", originId, result);
}
);
// remove the update handler
lightStateService.removeHookHandler(myHookHandler);
```
### Read & Update State
StatefulService exposes a read function which you may use to safely read the state. This function takes care of protecting against parallel access to the state in multi-core environments such as the ESP32.
```cpp
lightStateService.read([&](LightState& state) {
digitalWrite(LED_PIN, state.on ? HIGH : LOW); // apply the state update to the LED_PIN
});
```
StatefulService also exposes an update function which allows the caller to update the state with a callback. This function automatically calls the registered update handlers if the state has been changed. The example below changes the state of the light (turns it on) using the arbitrary origin "timer" and returns the "CHANGED" state update result, indicating that a change was made:
```cpp
lightStateService.update([&](LightState& state) {
if (state.on) {
return StateUpdateResult::UNCHANGED; // lights were already on, return UNCHANGED
}
state.on = true; // turn on the lights
return StateUpdateResult::CHANGED; // notify StatefulService by returning CHANGED
}, "timer");
```
There are three possible return values for an update function which are as follows:
| Origin | Description |
| ---------------------------- | ------------------------------------------------------------------------ |
| StateUpdateResult::CHANGED | The update changed the state, propagation should take place if required |
| StateUpdateResult::UNCHANGED | The state was unchanged, propagation should not take place |
| StateUpdateResult::ERROR | There was an error updating the state, propagation should not take place |
### JSON Serialization
When reading or updating state from an external source (HTTP, WebSockets, or MQTT for example) the state must be marshalled into a serializable form (JSON). SettingsService provides two callback patterns which facilitate this internally:
| Callback | Signature | Purpose |
| ---------------- | ------------------------------------------------------- | --------------------------------------------------------------------------------- |
| JsonStateReader | void read(T& settings, JsonObject& root) | Reading the state object into a JsonObject |
| JsonStateUpdater | StateUpdateResult update(JsonObject& root, T& settings) | Updating the state from a JsonObject, returning the appropriate StateUpdateResult |
The static functions below can be used to facilitate the serialization/deserialization of the light state:
```cpp
class LightState {
public:
bool on = false;
uint8_t brightness = 255;
static void read(LightState& state, JsonObject& root) {
root["on"] = state.on;
root["brightness"] = state.brightness;
}
static StateUpdateResult update(JsonObject& root, LightState& state) {
state.on = root["on"] | false;
state.brightness = root["brightness"] | 255;
return StateUpdateResult::CHANGED;
}
};
```
For convenience, the StatefulService class provides overloads of its `update` and `read` functions which utilize these functions.
Read the state to a JsonObject using a serializer:
```cpp
JsonObject jsonObject = jsonDocument.to<JsonObject>();
lightStateService->read(jsonObject, LightState::read);
```
Update the state from a JsonObject using a deserializer:
```cpp
JsonObject jsonObject = jsonDocument.as<JsonObject>();
lightStateService->update(jsonObject, LightState::update, "timer");
```
### HTTP RESTful Endpoint
The framework provides an [HttpEndpoint.h](https://github.com/theelims/ESP32-sveltekit/blob/main/lib/framework/HttpEndpoint.h) class which may be used to register GET and POST handlers to read and update the state over HTTP. You may construct an HttpEndpoint as a part of the StatefulService or separately if you prefer.
The code below demonstrates how to extend the LightStateService class to provide an endpoint:
```cpp
class LightStateService : public StatefulService<LightState> {
public:
LightStateService(PsychicHttpServer* server, ESP32SvelteKit *sveltekit) :
_httpEndpoint(LightState::read, LightState::update, this, server, "/rest/lightState", sveltekit->getSecurityManager(),AuthenticationPredicates::IS_AUTHENTICATED) {
}
void begin(); {
_httpEndpoint.begin();
}
private:
HttpEndpoint<LightState> _httpEndpoint;
};
```
Endpoint security is provided by authentication predicates which are [documented below](#security-features). The SecurityManager and authentication predicate must be provided, even if no secure endpoint is required. The placeholder project shows how endpoints can be secured.
To register the HTTP endpoints with the web server the function `_httpEndpoint.begin()` must be called in the custom StatefulService Class' own `void begin()` function.
### File System Persistence
[FSPersistence.h](https://github.com/theelims/ESP32-sveltekit/blob/main/lib/framework/FSPersistence.h) allows you to save state to the filesystem. FSPersistence automatically writes changes to the file system when state is updated. This feature can be disabled by calling `disableUpdateHandler()` if manual control of persistence is required.
The code below demonstrates how to extend the LightStateService class to provide persistence:
```cpp
class LightStateService : public StatefulService<LightState> {
public:
LightStateService(ESP32SvelteKit *sveltekit) :
_fsPersistence(LightState::read, LightState::update, this, sveltekit->getFS(), "/config/lightState.json") {
}
private:
FSPersistence<LightState> _fsPersistence;
};
```
### Event Socket Endpoint
[EventEndpoint.h](https://github.com/theelims/ESP32-sveltekit/blob/main/lib/framework/EventEndpoint.h) wraps the [Event Socket](#event-socket) into an endpoint compatible with a stateful service. The client may subscribe and unsubscribe to this event to receive updates or push updates to the ESP32. The current state is synchronized upon subscription.
The code below demonstrates how to extend the LightStateService class to provide an WebSocket:
```cpp
class LightStateService : public StatefulService<LightState> {
public:
LightStateService(ESP32SvelteKit *sveltekit) :
_eventEndpoint(LightState::read, LightState::update, this, sveltekit->getSocket(), "led") {}
void begin()
{
_eventEndpoint.begin();
}
private:
EventEndpoint<LightState> _eventEndpoint;
};
```
To register the event endpoint with the event socket the function `_eventEndpoint.begin()` must be called in the custom StatefulService Class' own `void begin()` function.
Since all events run through one websocket connection it is not possible to use the [securityManager](#security-features) to limit access to individual events. The security defaults to `AuthenticationPredicates::IS_AUTHENTICATED`.
### WebSocket Server
[WebSocketServer.h](https://github.com/theelims/ESP32-sveltekit/blob/main/lib/framework/WebSocketServer.h) allows you to read and update state over a WebSocket connection. WebSocketServer automatically pushes changes to all connected clients when state is updated.
The code below demonstrates how to extend the LightStateService class to provide an WebSocket:
```cpp
class LightStateService : public StatefulService<LightState> {
public:
LightStateService(PsychicHttpServer* server, ESP32SvelteKit *sveltekit) :
_webSocket(LightState::read, LightState::update, this, server, "/ws/lightState", sveltekit->getSecurityManager(), AuthenticationPredicates::IS_AUTHENTICATED), {
}
void begin() {
_webSocketServer.begin();
}
private:
WebSocketServer<LightState> _webSocketServer;
};
```
WebSocket security is provided by authentication predicates which are [documented below](#security-features). The SecurityManager and authentication predicate must be provided, even if no secure endpoint is required. The placeholder project shows how endpoints can be secured.
To register the WS endpoint with the web server the function `_webSocketServer.begin()` must be called in the custom StatefulService Class' own `void begin()` function.
### MQTT Client
The framework includes an MQTT client which can be configured via the UI. MQTT requirements will differ from project to project so the framework exposes the client for you to use as you see fit. The framework does however provide a utility to interface StatefulService to a pair of pub/sub (state/set) topics. This utility can be used to synchronize state with software such as Home Assistant.
[MqttEndpoint.h](https://github.com/theelims/ESP32-sveltekit/blob/main/lib/framework/MqttEndpoint.h) allows you to publish and subscribe to synchronize state over a pair of MQTT topics. MqttEndpoint automatically pushes changes to the "pub" topic and reads updates from the "sub" topic.
The code below demonstrates how to extend the LightStateService class to interface with MQTT:
```cpp
class LightStateService : public StatefulService<LightState> {
public:
LightStateService(ESP32SvelteKit *sveltekit) :
_mqttEndpoint(LightState::read,
LightState::update,
this,
sveltekit->getMqttClient(),
"homeassistant/light/my_light/set",
"homeassistant/light/my_light/state") {
}
private:
MqttEndpoint<LightState> _mqttEndpoint;
};
```
You can re-configure the pub/sub topics at runtime as required:
```cpp
_mqttEndpoint.configureBroker("homeassistant/light/desk_lamp/set", "homeassistant/light/desk_lamp/state");
```
The demo project allows the user to modify the MQTT topics via the UI so they can be changed without re-flashing the firmware.
## Event Socket
Beside RESTful HTTP Endpoints the Event Socket System provides a convenient communication path between the client and the ESP32. It uses a single WebSocket connection to synchronize state and to push realtime data to the client. The client needs to subscribe to the topics he is interested. Only clients who have an active subscription will receive data. Every authenticated client may make use of this system as the security settings are set to `AuthenticationPredicates::IS_AUTHENTICATED`.
### Message Format
The event messages exchanged between the ESP32 and its clients consists of an "event" head and the "data" payload. For the LightState example a message looks like this in JSON representation:
```JSON
{
"event": "led",
"data": {
"led_on": true
}
}
```
To save on bandwidth the event message is encoded as binary [MessagePack](https://msgpack.org/) instead of a JSON.
To subscribe the client has to send the following message (as MessagePack):
```JSON
{
"event": "subscribe",
"data": "analytics"
}
```
### Emit an Event
The Event Socket provides an `emitEvent()` function to push data to all subscribed clients. This is used by various esp32sveltekit classes to push real time data to the client. First an event must be registered with the Event Socket by calling `_socket.registerEvent("CustomEvent");`. Only then clients may subscribe to this custom event and you're entitled to emit event data:
```cpp
void emitEvent(String event, JsonObject &jsonObject, const char *originId = "", bool onlyToSameOrigin = false);
```
The latter function allowing a selection of the recipient. If `onlyToSameOrigin = false` the payload is distributed to all subscribed clients, except the `originId`. If `onlyToSameOrigin = true` only the client with `originId` will receive the payload. This is used by the [EventEndpoint](#event-socket-endpoint) to sync the initial state when a new client subscribes.
### Receive an Event
A callback or lambda function can be registered to receive an ArduinoJSON object and the originId of the client sending the data:
```cpp
_socket.onEvent("CostumEvent",[&](JsonObject &root, int originId)
{
bool ledState = root["led_on"];
});
```
### Get Notified on Subscriptions
Similarly a callback or lambda function may be registered to get notified when a client subscribes to an event:
```cpp
_socket.onSubscribe("CostumEvent",[&](const String &originId)
{
Serial.println("New Client subscribed: " + originId);
});
```
The boolean parameter provided will always be `true`.
### Push Notifications to All Clients
It is possibly to send push notifications to all clients by using the Event Socket. These will be displayed as toasts an the client side. Either directly call
```cpp
esp32sveltekit.getNotificationService()->pushNotification("Pushed a message!", PUSHINFO);
```
or keep a local pointer to the `EventSocket` instance. It is possible to send `PUSHINFO`, `PUSHWARNING`, `PUSHERROR` and `PUSHSUCCESS` events to all clients.
## Security features
The framework has security features to prevent unauthorized use of the device. This is driven by [SecurityManager.h](https://github.com/theelims/ESP32-sveltekit/blob/main/lib/framework/SecurityManager.h).
On successful authentication, the /rest/signIn endpoint issues a [JSON Web Token (JWT)](https://jwt.io/) which is then sent using Bearer Authentication. For this add an `Authorization`-Header to the request with the Content `Bearer {JWT-Secret}`. The framework come with built-in predicates for verifying a users access privileges. The built in AuthenticationPredicates can be found in [SecurityManager.h](https://github.com/theelims/ESP32-sveltekit/blob/main/lib/framework/SecurityManager.h) and are as follows:
| Predicate | Description |
| ---------------- | --------------------------------------------- |
| NONE_REQUIRED | No authentication is required. |
| IS_AUTHENTICATED | Any authenticated principal is permitted. |
| IS_ADMIN | The authenticated principal must be an admin. |
You can use the security manager to wrap any request handler function with an authentication predicate:
```cpp
server->on("/rest/someService", HTTP_GET,
_securityManager->wrapRequest(std::bind(&SomeService::someService, this, std::placeholders::_1), AuthenticationPredicates::IS_AUTHENTICATED)
);
```
In case of a websocket connection the JWT token is supplied as a search parameter in the URL when establishing the connection:
```
/ws/lightState?access_token={JWT Token}
```
## Placeholder substitution
Various settings support placeholder substitution, indicated by comments in [factory_settings.ini](https://github.com/theelims/ESP32-sveltekit/blob/main/factory_settings.ini). This can be particularly useful where settings need to be unique, such as the Access Point SSID or MQTT client id. Strings must be properly escaped in the ini-file. The following placeholders are supported:
| Placeholder | Substituted value |
| ------------ | --------------------------------------------------------------------- |
| #{platform} | The microcontroller platform, e.g. "esp32" or "esp32c3" |
| #{unique_id} | A unique identifier derived from the MAC address, e.g. "0b0a859d6816" |
| #{random} | A random number encoded as a hex string, e.g. "55722f94" |
You may use SettingValue::format in your own code if you require the use of these placeholders. This is demonstrated in the demo project:
```cpp
static StateUpdateResult update(JsonObject& root, LightMqttSettings& settings) {
settings.mqttPath = root["mqtt_path"] | SettingValue::format("homeassistant/light/#{unique_id}");
settings.name = root["name"] | SettingValue::format("light-#{unique_id}");
settings.uniqueId = root["unique_id"] | SettingValue::format("light-#{unique_id}");
return StateUpdateResult::CHANGED;
}
```
## Accessing settings and services
The framework supplies access to various features via getter functions:
| SettingsService | Description |
| ---------------------------- | -------------------------------------------------- |
| getFS() | The filesystem used by the framework |
| getSecurityManager() | The security manager - detailed above |
| getSecuritySettingsService() | Configures the users and other security settings |
| getWiFiSettingsService() | Configures and manages the WiFi network connection |
| getAPSettingsService() | Configures and manages the Access Point |
| getNTPSettingsService() | Configures and manages the network time |
| getMqttSettingsService() | Configures and manages the MQTT connection |
| getMqttClient() | Provides direct access to the MQTT client instance |
| getNotificationEvents() | Lets you send push notifications to all clients |
| getSleepService() | Send the ESP32 into deep sleep |
| getBatteryService() | Update battery information on the client |
The core features use the [StatefulService.h](https://github.com/theelims/ESP32-sveltekit/blob/main/lib/framework/StatefulService.h) class and therefore you can change settings or observe changes to settings through the read/update API.
Inspect the current WiFi settings:
```cpp
esp32sveltekit.getWiFiSettingsService()->read([&](WiFiSettings& wifiSettings) {
Serial.print("The ssid is:");
Serial.println(wifiSettings.ssid);
});
```
Configure the WiFi SSID and password manually:
```cpp
esp32sveltekit.getWiFiSettingsService()->update([&](WiFiSettings& wifiSettings) {
wifiSettings.ssid = "MyNetworkSSID";
wifiSettings.password = "MySuperSecretPassword";
return StateUpdateResult::CHANGED;
}, "myapp");
```
Observe changes to the WiFiSettings:
```cpp
esp32sveltekit.getWiFiSettingsService()->addUpdateHandler(
[&](const String& originId) {
Serial.println("The WiFi Settings were updated!");
}
);
```
## Other functions provided
### MDNS Instance Name
ESP32 SvelteKit uses mDNS / Bonjour to advertise its services into the local network. You can set the mDNS instance name property by calling
```cpp
esp32sveltekit.setMDNSAppName("ESP32 SvelteKit Demo App");
```
making the entry a little bit more verbose. This must be called before `esp32sveltekit.begin();`. If you want to advertise further services just include `#include <ESPmNDS.h>` and use `MDNS.addService()` regularly.
### Use ESP32-SvelteKit loop() Function
Under some circumstances custom services might want to do something periodically. One solution would be to use a dedicated task or RTOS timer for this. Or you can leverage the ESP32-SvelteKit loop-function and have it executed as a callback every 20ms.
```cpp
esp32sveltekit.addLoopFunction(callback)
```
### Factory Reset
A factory reset can not only be evoked from the API, but also by calling
```cpp
esp32sveltekit.factoryReset();
```
from your code. This will erase the complete settings folder, wiping out all settings. This can be a last fall back mode if somebody has forgotten his credentials.
### Recovery Mode
There is also a recovery mode present which will force the creation of an access point. By calling
```cpp
esp32sveltekit.recoveryMode();
```
will force a start of the AP regardless of the AP settings. It will not change the the AP settings. To exit the recovery mode restart the device or change the AP settings in the UI.
### Power Down with Deep Sleep
This API service can place the ESP32 in the lowest power deep sleep mode consuming only a few µA. It uses the EXT1 wakeup source, so the ESP32 can be woken up with a button or from a peripherals interrupt. Consult the [ESP-IDF Api Reference](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/sleep_modes.html#_CPPv428esp_sleep_enable_ext1_wakeup8uint64_t28esp_sleep_ext1_wakeup_mode_t) which GPIOs can be used for this. The RTC will also be powered down, so an external pull-up or pull-down resistor is required. It is not possible to persist variable state through the deep sleep. To optimize the deep sleep power consumption it is advisable to use the callback function to put pins with external pull-up's or pull-down's in a special isolated state to prevent current leakage. Please consult the [ESP-IDF Api Reference](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/sleep_modes.html#configuring-ios-deep-sleep-only) for this.
The settings wakeup pin definition and the signal polarity need to be defined in [factory_settings.ini](https://github.com/theelims/ESP32-sveltekit/blob/main/factory_settings.ini):
```ini
; Deep Sleep Configuration
-D WAKEUP_PIN_NUMBER=38 ; pin number to wake up the ESP
-D WAKEUP_SIGNAL=0 ; 1 for wakeup on HIGH, 0 for wakeup on LOW
```
In addition it is possible to change this as well at runtime by calling:
```cpp
esp32sveltekit.getSleepService()->setWakeUpPin(int pin, bool level, pinTermination termination = pinTermination::FLOATING);
```
With this function it is also possible to configure the internal pull-up or pull-down resistor for this RTC pin. Albeit this might increase the deep sleep current slightly.
A callback function can be attached and triggers when the ESP32 is requested to go into deep sleep. This allows you to safely deal with the power down event. Like persisting software state by writing to the flash, tiding up or notify a remote server about the immanent disappearance.
```cpp
esp32sveltekit.getSleepService()->attachOnSleepCallback();
```
Also the code can initiate the power down deep sleep sequence by calling:
```cpp
esp32sveltekit.getSleepService()->sleepNow();
```
### Battery State of Charge
A small helper class let's you update the battery icon in the status bar. This is useful if you have a battery operated IoT device. It must be enabled in [features.ini](https://github.com/theelims/ESP32-sveltekit/blob/main/features.ini). It uses the [Event Socket](#event-socket) and exposes two functions that can be used to update the clients.
```cpp
esp32sveltekit.getBatteryService()->updateSOC(float stateOfCharge); // update state of charge in percent (0 - 100%)
esp32sveltekit.getBatteryService()->setCharging(boolean isCharging); // notify the client that the device is charging
```
### ESP32-SvelteKit Connection Status
Especially for a cases like a colored status LED it can be useful to have a quick indication of the connection status. By calling:
```cpp
ConnectionStatus status = esp32sveltekit.getConnectionStatus();
```
the current connection status can be accessed. The following stats are available:
| Status | Description |
| ------------- | ------------------------------------------------------------------------------- |
| OFFLINE | Device is completely offline |
| AP | Access Point is available, but no client is connected |
| AP_CONNECTED | Access Point is used and at least 1 client is connected |
| STA | Device connected to a WiFi Station |
| STA_CONNECTED | Device connected to a WiFi Station and at least 1 client is connected |
| STA_MQTT | Device connected to a WiFi Station and the device is connected to a MQTT server |
### Custom Features
You may use the compile time feature service also to enable or disable custom features at runtime and thus control the frontend. A custom feature can only be added during initializing the ESP32 and ESP32-SvelteKit. The frontend queries the features only when first loading the page. Thus the frontend must be refreshed for the changes to become effective.
```cpp
esp32sveltekit.getFeatureService()->addFeature("custom_feature", true); // or false to disable it
```
## OTA Firmware Updates
ESP32-SvelteKit offers two different ways to roll out firmware updates to field devices. If the frontend should be updated as well it is necessary to embed it into the firmware binary by activating `-D EMBED_WWW`.
### Firmware Upload
Enabling `FT_UPLOAD_FIRMWARE=1` in [features.ini](https://github.com/theelims/ESP32-sveltekit/blob/main/features.ini) creates a REST endpoint that one can post a firmware binary to. The frontend has a file drop zone to upload a new firmware binary from the browser.
### Firmware Download from Update Server
By enabling `FT_DOWNLOAD_FIRMWARE=1` in [features.ini](https://github.com/theelims/ESP32-sveltekit/blob/main/features.ini) one can POST a link to a firmware binary which is downloaded for the OTA process. This feature requires SSL and is thus dependent on `FT_NTP=1`. The Frontend contains an implementation which uses GitHub's Releases section as the update server. By specifying a firmware version in [platformio.ini](https://github.com/theelims/ESP32-sveltekit/blob/main/platformio.ini) one can make use of semantic versioning to determine the correct firmware:
```ini
-D BUILD_TARGET="$PIOENV"
-D APP_NAME=\"ESP32-Sveltekit\" ; Must only contain characters from [a-zA-Z0-9-_] as this is converted into a filename
-D APP_VERSION=\"0.3.0\" ; semver compatible version string
```
A build script copies the firmware binary files for all build environment to `build/firmware`. It renames them into `{APP_NAME}_{$PIOENV}_{APP_VERSION}.bin`. It also creates a MD5 checksum file for verification during the OTA process. These files can be used as attachment on the GitHub release pages.
!!! info
This feature could be unstable on single-core members of the ESP32 family.
#### Custom Update Server
If Github is not desired as the update server this can be easily modified to any other custom server. The REST API will accept any valid HTTPS-Link. However, SSL is mandatory and may require a different Root CA Certificate then Github to validate correctly.
Follow the instructions here how to change the [SSL CA Certificate](buildprocess.md#ssl-root-certificate-for-download-ota).
If you use a custom update server you must also adapt the [frontend](structure.md#custom-update-server) code to suit your needs.
================================================
FILE: docs/stores.md
================================================
# Stores
## User
The user store holds the current users credentials, if the security features are enabled. Just import it as you would use with any svelte store:
```ts
import { user } from "$lib/stores/user";
```
You can subscribe to it like to any other store with `$user` and it has the following properties:
| Property | Type | Description |
| -------------------- | --------- | ------------------------------------------------- |
| `$user.bearer_token` | `String` | The JWT token to authorize a user at the back end |
| `$user.username` | `String` | Username of the current user |
| `$user.admin` | `Boolean` | `true` if the current user has admin privileges |
In addition to the properties it provides two methods for initializing the user credentials and to invalidate them. `user.init()` takes a valid JWT toke as an argument and extracts the user privileges and username from it. `user.invalidate()` invalidates the user credentials and redirects to the login pages
!!! warning "User credentials are stored in the browsers local storage"
The user credentials including the JWT token are stored in the browsers local storage. Any javascript executed on the browser can access this making it extremely vulnerable to XSS attacks. Also the HTTP connection between ESP32 and front end is not encrypted making it possible for everyone to read the JWT token in the same network. Fixing these severe security issues is on the todo list for upcoming releases.
## Event Socket
The [Event Socket System](statefulservice.md#event-socket) is conveniently provided as a Svelte store. Import the store, subscribe to the data interested with `socket.on`. To unsubscribe simply call `socket.off`. Data can be sent to the ESP32 by calling `socket.sendEvent`
```ts
import { socket } from "$lib/stores/socket";
let lightState: LightState = { led_on: false };
onMount(() => {
socket.on<LightState>("led", (data) => {
lightState = data;
});
});
onDestroy(() => socket.off("led"));
socket.sendEvent("led", lightState);
```
Subscribing to an invalid event will only create a warning in the ESP_LOG on the serial console of the ESP32.
## Telemetry
The telemetry store can be used to update telemetry data like RSSI via the [Event Socket](statefulservice.md#event-socket) system.
```ts
import { telemetry } from "$lib/stores/telemetry";
```
It exposes the following properties you can subscribe to:
| Property | Type | Description |
| ---------------------------------- | --------- | ------------------------------------------- |
| `$telemetry.rssi.rssi` | `Number` | The RSSI signal strength of the WiFi in dBm |
| `$telemetry.rssi.ssid` | `String` | Name of the connected WiFi station |
| `$telemetry.rssi.connected` | `Boolean` | Connection status of the WiFi |
| `$telemetry.battery.soc` | `Number` | Battery state of charge |
| `$telemetry.battery.charging` | `Boolean` | Is battery connected to charger |
| `$telemetry.download_ota.status` | `String` | Status of OTA |
| `$telemetry.download_ota.progress` | `Number` | Progress of OTA |
| `$telemetry.download_ota.error` | `String` | Error Message of OTA |
| `$telemetry.ethernet.connected` | `Boolean` | Connection status of the ethernet interface |
## Analytics
The analytics store holds a log of heap and other debug information via the [Event Socket](statefulservice.md#event-socket) system.
```ts
import { analytics } from "$lib/stores/analytics";
```
It exposes an array of the following properties you can subscribe to:
| Property | Type | Description |
| --------------------------- | -------- | ---------------------------------------------- |
| `$analytics.uptime` | `Number` | Uptime of the chip in seconds since last reset |
| `$analytics.free_heap` | `Number` | Current free heap |
| `$analytics.min_free_heap` | `Number` | Minimum free heap that has been |
| `$analytics.max_alloc_heap` | `Number` | Biggest continues free chunk of heap |
| `$analytics.fs_used` | `Number` | Bytes used on the file system |
| `$analytics.fs_total` | `Number` | Total bytes of the file system |
| `$analytics.core_temp` | `Number` | Core temperature (on some chips) |
By default there is one data point every 2 seconds. It holds 1000 data points worth roughly 33 Minutes of data.
================================================
FILE: docs/structure.md
================================================
# Customizing the Front End
The actual code for the front end is located under [interface/src/](https://github.com/theelims/ESP32-sveltekit/tree/main/interface/src) and divided into the "routes" folder and a "lib" folder for assets, stores and components.
| Resource | Description |
| ------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
| [routes/](https://github.com/theelims/ESP32-sveltekit/tree/main/interface/src/routes/) | Root of the routing system |
| [routes/connections/](https://github.com/theelims/ESP32-sveltekit/blob/main/interface/src/routes/connections) | Setting and status pages for MQTT, NTP, etc. |
| [routes/demo/](https://github.com/theelims/ESP32-sveltekit/blob/main/interface/src/routes/demo/) | The lightstate demo |
| [routes/system/](https://github.com/theelims/ESP32-sveltekit/blob/main/interface/src/routes/system/) | Status page for ESP32 and OTA settings |
| [routes/user/](https://github.com/theelims/ESP32-sveltekit/blob/main/interface/src/routes/user/) | Edit and add users and change passwords |
| [routes/wifi/](https://github.com/theelims/ESP32-sveltekit/blob/main/interface/src/routes/wifi/) | Status and settings for WiFi station and AP |
| [lib/](https://github.com/theelims/ESP32-sveltekit/blob/main/interface/src/lib/) | Library folder for stores, components and assets |
| [lib/assets/](https://github.com/theelims/ESP32-sveltekit/blob/main/interface/src/lib/assets/) | Assets like pictures |
| [lib/components/](https://github.com/theelims/ESP32-sveltekit/blob/main/interface/src/lib/components/) | Reusable components like modals, password input or collapsible |
| [lib/stores](https://github.com/theelims/ESP32-sveltekit/blob/main/interface/src/lib/stores/) | Svelte stores for common access to data |
## Features
The back end provides a JSON which features of the back end are enabled by the [feature selection](buildprocess.md#selecting-features). It is fetched with the page load and made available in the `pages`-store and can be accessed on any site with `page.data.features`. It is used to hide any disabled setting element.
## Delete `demo/` Project
The light state demo project is included by default to demonstrate the use of the backend and front end. It demonstrates the use of the MQTT-API, websocket API and REST API to switch on the build in LED of the board. [routes/connections/mqtt/MQTTConfig.svelte](https://github.com/theelims/ESP32-sveltekit/blob/main/interface/src/routes/connections/mqtt/MQTTConfig.svelte) is also part of the 'demo/' Project. You can reuse this to set your own MQTT topics, or delete it. Do not forget to adjust `+page.svelte` as well. Use it as an example how to create your own custom API and access it from the front end. It can be deleted safely after it has been [removed from the menu](#adapt-the-menu) as well.
## Create your root `+page.svelte`
The root page of the front end is located under [routes/+page.svelte](https://github.com/theelims/ESP32-sveltekit/tree/main/interface/src/routes/+page.svelte). This should be the central place of your app and can be accessed at any time by pressing the logo and app name in the side menu. Just override it to suit your needs.
## Customize the Main Menu
The main menu is located in [routes/menu.svelte](https://github.com/theelims/ESP32-sveltekit/tree/main/interface/src/routes/menu.svelte) as a svelte component and defines the main menu including a menu footer.
### Menu Footer
The main menu comes with a small footer to add your copyright notice plus links to github and your discord server where users can find help. The `active`-flag is used to disable an element in the UI. Most of these global parameters are set in the [routes/+layout.ts](https://github.com/theelims/ESP32-sveltekit/tree/main/interface/src/routes/+layout.ts).
```ts
const discord = { href: ".", active: false };
```
### Menu Structure
The menu consists of an array of menu items. These are defined as follows:
```ts
{
title: 'Demo App',
icon: Control,
href: '/demo',
feature: page.data.features.project,
},
```
- Where `title` refers to the page title. It must be identical to `page.data.title` as defined in the `+page.ts` in any of your routes. If they do not match the corresponding menu item is not highlighted on first page load or a page refresh. A minimum `+page.ts` looks like this:
```ts
import type { PageLoad } from "./$types";
export const load = (async ({ fetch }) => {
return {
title: "Demo App",
};
}) satisfies PageLoad;
```
- `icon` must be an icon component giving the menu items icon.
- `href` is the link to the route the menu item refers to.
- `feature` takes a bool and should be set to `true`. It is used by the [feature selector](#features) to hide a menu entry of it is not present on the back end.
## Advanced Customizations
On the root level there are two more files which you can customize to your needs.
### Login Page
`login.svelte` is a component showing the login screen, when the security features are enabled. By default it shows the app's logo and the login prompt. Change it as you need it.
### Status Bar
`statusbar.svelte` contains the top menu bar which you can customize to show state information about your app and IoT device. By default it shows the active menu title and the hamburger icon on small screens.
## Github Firmware Update
If the feature `FT_DOWNLOAD_FIRMWARE` is enabled, ESP32 SvelteKit pulls the Github Release section through the Github API for firmware updates once per hour. Also the firmware update menu shows all available firmware releases allowing the user to up- and downgrade has they please. If you're using the Github releases section you must first tell the frontend your correct path to your github repository as described [here](sveltekit.md#changing-the-app-name).
Also you must make use of couple build flags in [platformio.ini](https://github.com/theelims/ESP32-sveltekit/blob/main/platformio.ini):
```ini
-D BUILD_TARGET=\"$PIOENV\"
-D APP_NAME=\"ESP32-Sveltekit\" ; Must only contain characters from [a-zA-Z0-9-_] as this is converted into a filename
-D APP_VERSION=\"0.3.0\" ; semver compatible version string
```
Out of these flags the [rename_fw.py](https://github.com/theelims/ESP32-sveltekit/blob/main/scripts/rename_fw.py) script will copy and rename the firmware binary to `/build/firmware/{APP_NAME}_{$PIOENV}_{APP_VERSION}.bin`. In addition it will also create a corresponding MD5 checksum file. These files are ready to be uploaded to the Github release page without any further changes. The frontend searches for the firmware binary which matches the build environment and uses this as the update link. This allows you to serve different build targets (e.g. different boards) from the same release page.
### Custom Update Server
The frontend and backend code can be easily adjusted to suit a custom update server. For the backend the changes are described [here](statefulservice.md#custom-update-server). On the frontend only two files must be adapted and changed to switch to a custom update server: [/lib/components/UpdateIndicator.svelte](https://github.com/theelims/ESP32-sveltekit/blob/main/interface/src/lib/components/UpdateIndicator.svelte) and [/routes/system/update/GithubFirmwareManager.svelte](https://github.com/theelims/ESP32-sveltekit/blob/main/interface/src/routes/system/update/GithubFirmwareManager.svelte).
!!! info
The update server must provide the firmware download through SSL encryption.
================================================
FILE: docs/sveltekit.md
================================================
# Getting Started with SvelteKit
SvelteKits unique approach makes it perfect suitable for constraint server. It builds very small files shipping only the minimum required amount of java script. This keeps the memory footprint low so that rich applications can be build with just the 4MB flash of many ESP32 chips.
However, since SvelteKit is designed with server-side rendering first, there are some catches and pitfalls one must avoid. Especially as nearly all tutorials found on SvelteKit heavily make use of the combined front and back end features.
## Limitations of `adapter-static`
To build a website that can be served from an ESP32 `adapter-static` is used. This means no server functions can be used. The front end is build as a Single-Page Application (SPA) instead. However, SvelteKit will pre-render sites at build time, even if SSR and pre-rendering are disabled. This leads to some restrictions that must be taken into consideration:
- You can't use any server-side logic like `+page.server.ts`, `+layout.server.ts` or `+server.ts` files in your project.
- The load function in `+page.ts` gets executed on the server and the client. If you try to access browser resources in the load function this will fail. Use a more traditional way like fetching the data in the `+page.svelte` with the `onMount(() => {})` callback.
## Customizing and Theming
### Changing the App Name
[+layout.ts](https://github.com/theelims/ESP32-sveltekit/blob/main/interface/src/routes/%2Blayout.ts) bundles a few globally customizable properties like github repository, app name and copyright notice:
```js
export const load = (async () => {
const result = await fetch('/rest/features');
const item = await result.json();
return {
features: item,
title: 'ESP32-SvelteKit',
github: 'theelims/ESP32-sveltekit',
copyright: '2024 theelims',
appName: 'ESP32 SvelteKit'
};
}) satisfies LayoutLoad;
```
In [menu.svelte](https://github.com/theelims/ESP32-sveltekit/blob/main/interface/src/routes/menu.svelte) there is additionally the possibility to add a discord invite, which is disabled by default.
```js
const discord = { href: ".", active: false };
```
There is also a manifest file which contains the app name to use when adding the app to a mobile device, so you may wish to also edit [interface/static/manifest.json](https://github.com/theelims/ESP32-sveltekit/blob/main/interface/static/manifest.json):
```json
{
"name": "ESP32 SvelteKit",
"icons": [
{
"src": "/favicon.png",
"sizes": "48x48 72x72 96x96 128x128 256x256"
}
],
"start_url": "/",
"display": "fullscreen",
"orientation": "any"
}
```
### Changing the App Icon and Favicon
You can replace the apps favicon which is located at [interface/static/favicon.png](https://github.com/theelims/ESP32-sveltekit/blob/main/interface/static/favicon.png) with one of your preference. A 256 x 256 PNG is recommended for best compatibility.
Also the Svelte Logo can be replaced with your own. It is located under [interface/src/lib/assets/logo.png](https://github.com/theelims/ESP32-sveltekit/blob/main/interface/src/lib/assets/logo.png).
### Daisy UI Themes
The overall theme of the front end is defined by [DaisyUI](https://daisyui.com/docs/themes/) and can be easily changed according to their documentation. Either by selecting one of the standard themes of DaisyUI, or creating your own. By default the `corporate` and `business` for dark are defined in [app.css](https://github.com/theelims/ESP32-sveltekit/blob/main/interface/src/app.css):
```js
@plugin "daisyui" {
themes: corporate --default, business --prefersdark;
}
```
#### Opinionated use of Shadows
The front end makes some use of colored shadows with the `shadow-primary` and `shadow-secondary` DaisyUI classes. Just use the search and replace function to change this to a more neutral look, if you find the color too much.
#### Color Scheme Helper
Some JS modules do not accept DaisyUI/TailwindCSS color class names. A small helper function can be imported and used to convert any CSS variable name for a DaisyUI color into OKCHL. That way modules like e.g. Charts.js can be styled in the current color scheme in a responsive manner.
```js
import { daisyColor } from "$lib/DaisyUiHelper";
borderColor: daisyColor('--color-primary'),
backgroundColor: daisyColor('--color-primary', 50),
```
## TS Types Definition
All types used throughout the front end are exported from [models.ts](https://github.com/theelims/ESP32-sveltekit/blob/main/interface/src/lib/types/models.ts). It is a convenient location to add your custom types once you expand the front end.
================================================
FILE: factory_settings.ini
================================================
; The indicated settings support placeholder substitution as follows:
;
; #{platform} - The microcontroller platform, e.g. "esp32" or "esp32c3"
; #{unique_id} - A unique identifier derived from the MAC address, e.g. "0b0a859d6816"
; #{random} - A random number encoded as a hex string, e.g. "55722f94"
[factory_settings]
build_flags =
; WiFi settings
-D FACTORY_WIFI_SSID=\"\"
-D FACTORY_WIFI_PASSWORD=\"\"
-D FACTORY_WIFI_HOSTNAME=\"#{platform}-#{unique_id}\" ; supports placeholders
-D FACTORY_WIFI_RSSI_THRESHOLD=-80 ; dBm, -80 is a good value for most applications
; Access point settings
-D FACTORY_AP_PROVISION_MODE=AP_MODE_DISCONNECTED
-D FACTORY_AP_SSID=\"ESP32-SvelteKit-#{unique_id}\" ; 1-64 characters, supports placeholders
-D FACTORY_AP_PASSWORD=\"esp-sveltekit\" ; 8-64 characters
-D FACTORY_AP_CHANNEL=1
-D FACTORY_AP_SSID_HIDDEN=false
-D FACTORY_AP_MAX_CLIENTS=4
-D FACTORY_AP_LOCAL_IP=\"192.168.4.1\"
-D FACTORY_AP_GATEWAY_IP=\"192.168.4.1\"
-D FACTORY_AP_SUBNET_MASK=\"255.255.255.0\"
; User credentials for admin and guest user
-D FACTORY_ADMIN_USERNAME=\"admin\"
-D FACTORY_ADMIN_PASSWORD=\"admin\"
-D FACTORY_GUEST_USERNAME=\"guest\"
-D FACTORY_GUEST_PASSWORD=\"guest\"
; NTP settings
-D FACTORY_NTP_ENABLED=true
-D FACTORY_NTP_TIME_ZONE_LABEL=\"Europe/Berlin\"
-D FACTORY_NTP_TIME_ZONE_FORMAT=\"GMT0BST,M3.5.0/1,M10.5.0\"
-D FACTORY_NTP_SERVER=\"time.google.com\"
; MQTT settings
-D FACTORY_MQTT_ENABLED=false
-D FACTORY_MQTT_URI=\"mqtts://broker.hivemq.com:8883\"
-D FACTORY_MQTT_USERNAME=\"\" ; supports placeholders
-D FACTORY_MQTT_PASSWORD=\"\"
-D FACTORY_MQTT_CLIENT_ID=\"#{platform}-#{unique_id}\" ; supports placeholders
-D FACTORY_MQTT_KEEP_ALIVE=120
-D FACTORY_MQTT_CLEAN_SESSION=true
-D FACTORY_MQTT_STATUS_TOPIC=\"esp32sveltekit/#{unique_id}/status\" ; supports placeholders
-D FACTORY_MQTT_MIN_MESSAGE_INTERVAL_MS=0
; JWT Secret
-D FACTORY_JWT_SECRET=\"#{random}-#{random}\" ; supports placeholders
; Deep Sleep Configuration
-D WAKEUP_PIN_NUMBER=0 ; pin number to wake up the ESP
-D WAKEUP_SIGNAL=0 ; 1 for wakeup on HIGH, 0 for wakeup on LOW
================================================
FILE: features.ini
================================================
[features]
build_flags =
-D FT_SECURITY=1
-D FT_MQTT=1
-D FT_NTP=1
-D FT_UPLOAD_FIRMWARE=1
-D FT_DOWNLOAD_FIRMWARE=1 ; requires FT_NTP=1
-D FT_SLEEP=1
-D FT_BATTERY=0
-D FT_ANALYTICS=1
-D FT_COREDUMP=1
; -D FT_ETHERNET=1 ; ethernet feature should be enabled in the board config as not every board supports ethernet
================================================
FILE: interface/.eslintignore
================================================
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock
================================================
FILE: interface/.eslintrc.cjs
================================================
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'],
plugins: ['svelte3', '@typescript-eslint'],
ignorePatterns: ['*.cjs'],
overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }],
settings: {
'svelte3/typescript': () => require('typescript')
},
parserOptions: {
sourceType: 'module',
ecmaVersion: 2020
},
env: {
browser: true,
es2017: true,
node: true
}
};
================================================
FILE: interface/.gitignore
================================================
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
================================================
FILE: interface/.npmrc
================================================
engine-strict=true
================================================
FILE: interface/.prettierignore
================================================
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock
================================================
FILE: interface/.prettierrc
================================================
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte"],
"pluginSearchDirs": ["."],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
}
================================================
FILE: interface/LICENSE
================================================
ESP32-SvelteKit is distributed with two licenses for different sections of the
code. The back end code inherits the GNU LESSER GENERAL PUBLIC LICENSE Version 3
and is therefore distributed said license. The front end code is distributed
under the MIT License.
MIT License
Copyright (C) 2023 - 2024 theelims
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:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
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.
================================================
FILE: interface/package.json
================================================
{
"name": "ESP32-Sveltekit Template",
"version": "0.2.0",
"private": true,
"scripts": {
"dev": "vite dev --host",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --plugin-search-dir . --check . && eslint .",
"format": "prettier --plugin-search-dir . --write ."
},
"devDependencies": {
"@iconify-json/tabler": "^1.2.19",
"@sveltejs/adapter-static": "^3.0.8",
"@sveltejs/kit": "^2.22.3",
"@sveltejs/vite-plugin-svelte": "^4.0.4",
"@tailwindcss/vite": "^4.1.11",
"@types/msgpack-lite": "^0.1.11",
"@typescript-eslint/eslint-plugin": "^8.36.0",
"@typescript-eslint/parser": "^8.36.0",
"daisyui": "^5.0.46",
"eslint": "^9.30.1",
"eslint-config-prettier": "^10.1.5",
"eslint-plugin-svelte": "^3.10.1",
"prettier": "^3.6.2",
"prettier-plugin-svelte": "^3.4.0",
"prettier-plugin-tailwindcss": "^0.6.14",
"svelte": "^5.35.5",
"svelte-check": "^4.2.2",
"svelte-focus-trap": "^1.2.0",
"tailwindcss": "^4.1.11",
"terser": "^5.44.0",
"tslib": "^2.8.1",
"typescript": "^5.8.3",
"unplugin-icons": "^22.1.0",
"vite": "^5.4.19"
},
"type": "module",
"dependencies": {
"chart.js": "^4.5.0",
"chartjs-adapter-luxon": "^1.3.1",
"compare-versions": "^6.1.1",
"jwt-decode": "^4.0.0",
"luxon": "^3.7.1",
"msgpack-lite": "^0.1.26",
"svelte-dnd-action": "^0.9.65",
"svelte-modals": "^2.0.1"
}
}
================================================
FILE: interface/src/app.css
================================================
@import "tailwindcss";
@plugin "daisyui" {
themes: corporate --default, business --prefersdark;
}
/*
The default border color has changed to `currentcolor` in Tailwind CSS v4,
so we've added these compatibility styles to make sure everything still
looks the same as it did with Tailwind CSS v3.
If we ever want to remove these styles, we need to add an explicit border
color utility to any element that depends on these defaults.
*/
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-gray-200, currentcolor);
}
}
================================================
FILE: interface/src/app.d.ts
================================================
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
/// <reference types="@sveltejs/kit" />
/// <reference types="unplugin-icons/types/svelte" />
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface Platform {}
}
}
export {};
================================================
FILE: interface/src/app.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>
================================================
FILE: interface/src/lib/DaisyUiHelper.ts
================================================
export function daisyColor(name: string, opacity: number = 100) {
const color = getComputedStyle(document.documentElement).getPropertyValue(name);
// console.debug(`daisyColor: name=${name}, color=${color}, opacity=${opacity}`);
// console.debug(`${color}`);
// add transparency to the color if opacity is less than 100
if (opacity < 100) {
// Convert opacity to a percentage
const alpha = Math.min(Math.max(Math.round(opacity), 0), 100) / 100;
// Remove any existing alpha value and trailing ')' from the oklch color
const oklchColor = color.replace(/(\/\s*\d+(\.\d+)?\))|\)$/, '').trim();
// Append the new alpha value
// console.debug(`oklchColor: ${oklchColor} / ${alpha})`);
return `${oklchColor} / ${alpha})`;
}
return `${color}`; // / ${Math.min(Math.max(Math.round(opacity), 0), 100)}%)`;
}
================================================
FILE: interface/src/lib/components/BatteryIndicator.svelte
================================================
<script lang="ts">
import Battery0 from '~icons/tabler/battery';
import Battery25 from '~icons/tabler/battery-1';
import Battery50 from '~icons/tabler/battery-2';
import Battery75 from '~icons/tabler/battery-3';
import Battery100 from '~icons/tabler/battery-4';
import BatteryCharging from '~icons/tabler/battery-charging-2';
let { charging = false, soc = 100, class: className = '' } = $props();
</script>
<div class="tooltip tooltip-bottom" data-tip="{soc} %">
{#if charging}
<BatteryCharging class="{className} -rotate-90 animate-pulse" />
{:else if soc > 75}
<Battery100 class="{className} -rotate-90" />
{:else if soc > 55}
<Battery75 class="{className} -rotate-90" />
{:else if soc > 30}
<Battery50 class="{className} -rotate-90" />
{:else if soc > 5}
<Battery25 class="{className} -rotate-90" />
{:else}
<Battery0 class="{className} text-error -rotate-90 animate-pulse" />
{/if}
</div>
================================================
FILE: interface/src/lib/components/Collapsible.svelte
================================================
<script lang="ts">
import { slide } from 'svelte/transition';
import { cubicOut } from 'svelte/easing';
import Down from '~icons/tabler/chevron-down';
import Alert from '~icons/tabler/alert-hexagon';
interface Props {
open?: boolean;
opened?: any;
closed?: any;
collapsible?: boolean;
icon?: import('svelte').Snippet;
title?: import('svelte').Snippet;
children?: import('svelte').Snippet;
class?: string;
isDirty?: boolean;
}
let {
open = $bindable(false),
opened,
closed,
icon,
title,
children,
class: className = '',
isDirty = false
}: Props = $props();
function openCollapsible() {
open = !open;
if (open) {
if (opened) opened();
} else {
if (closed) closed();
}
}
</script>
<div class="{className} relative grid w-full max-w-2xl self-center overflow-hidden">
{#if isDirty}
<div class="absolute left-0 top-0 w-1.5 h-full bg-red-300"></div>
{/if}
<div class="min-h-16 flex w-full items-center justify-between space-x-3 p-4 text-xl font-medium">
<span class="inline-flex items-center">
{@render icon?.()}
{@render title?.()}
{#if isDirty}
<div data-tip="There are unsaved changes." class="tooltip tooltip-right tooltip-error">
<Alert class="text-error lex-shrink-0 ml-2 h-6 w-6 self-end cursor-help" />
</div>
{/if}
</span>
<button class="btn btn-circle btn-ghost btn-sm" onclick={() => openCollapsible()}>
<Down
class="text-base-content h-auto w-6 transition-transform duration-300 ease-in-out {open
? 'rotate-180'
: ''}"
/>
</button>
</div>
{#if open}
<div
class="flex flex-col gap-2 p-4 pt-0"
transition:slide|local={{ duration: 300, easing: cubicOut }}
>
{@render children?.()}
</div>
{/if}
</div>
================================================
FILE: interface/src/lib/components/ConfirmDialog.svelte
================================================
<script lang="ts">
import { modals } from 'svelte-modals';
import { focusTrap } from 'svelte-focus-trap';
import { fly } from 'svelte/transition';
import Cancel from '~icons/tabler/x';
import Check from '~icons/tabler/check';
// provided by <Modals />
interface Props {
isOpen?: boolean;
title: string;
message: string;
onConfirm: any;
labels?: any;
}
let {
isOpen,
title,
message,
onConfirm,
labels = {
cancel: { label: 'Cancel', icon: Cancel },
confirm: { label: 'OK', icon: Check }
}
}: Props = $props();
</script>
{#if isOpen}
{@const SvelteComponent = labels?.confirm.icon}
<div
role="dialog"
class="pointer-events-none fixed inset-0 z-50 flex items-center justify-center p-4"
transition:fly={{ y: 50 }}
use:focusTrap
>
<div
class="rounded-box bg-base-100 shadow-secondary/30 pointer-events-auto flex w-full max-w-xs sm:max-w-sm md:max-w-md flex-col justify-between p-4 shadow-lg overflow-hidden"
>
<h2 class="text-base-content text-start text-2xl font-bold break-words">{title}</h2>
<div class="divider my-2"></div>
<p class="text-base-content mb-1 text-start break-words whitespace-normal">{@html message}</p>
<div class="divider my-2"></div>
<div class="flex justify-end gap-2">
<button
class="btn btn-primary inline-flex items-center"
onclick={() => {
modals.close();
}}><labels.cancel.icon class="h-5 w-5" /><span>{labels?.cancel.label}</span></button
>
<button
class="btn btn-warning text-warning-content inline-flex items-center"
onclick={onConfirm}
><SvelteComponent class="h-5 w-5" /><span>{labels?.confirm.label}</span></button
>
</div>
</div>
</div>
{/if}
================================================
FILE: interface/src/lib/components/DraggableList.svelte
================================================
<script lang="ts">
import { dndzone } from 'svelte-dnd-action';
import type { Snippet } from 'svelte';
interface Props {
items: any[];
onReorder?: (reorderedItems: any[]) => void;
flipDurationMs?: number;
dragDisabled?: boolean;
class?: string;
children: Snippet<[{ item: any; index: number; originalItem: any }]>;
}
let {
items,
onReorder = () => {},
flipDurationMs = 200,
dragDisabled = false,
class: className = '',
children
}: Props = $props();
// Create a state array with IDs for drag-and-drop functionality
let itemsWithIds: any[] = $state([]);
// Update the drag-and-drop array whenever items change
$effect(() => {
itemsWithIds = items.map((item, index) => ({
...item,
id: item.id || `dnd-item-${index}-${Date.now()}` // Generate unique ID with timestamp
}));
});
function handleSort(e: any) {
// Update the visual drag-and-drop array immediately
itemsWithIds = e.detail.items;
}
function handleFinalizeSort(e: any) {
// Remove only temporary IDs, preserve original device IDs
const reorderedItems = e.detail.items.map((item: any) => {
// If this is a temporary ID we added (string starting with 'dnd-item-'), remove it
if (typeof item.id === 'string' && item.id.startsWith('dnd-item-')) {
const { id, ...itemWithoutTempId } = item;
return itemWithoutTempId;
}
// Otherwise, keep the item as-is (preserving original numeric IDs)
return item;
});
// Call the parent's reorder handler
onReorder(reorderedItems);
}
</script>
<section
use:dndzone={{
items: itemsWithIds,
flipDurationMs,
dropTargetStyle: {}, // This is to actively clear default styles
dropTargetClasses: ['dragzone-outline'], // This applies custom styling
dragDisabled
}}
onconsider={handleSort}
onfinalize={handleFinalizeSort}
class={className}
>
{#each itemsWithIds as item, index (item.id)}
{@render children({ item, index, originalItem: items[index] })}
{/each}
</section>
<style>
@reference "$src/app.css";
:global(.dragzone-outline) {
@apply outline-solid outline-2 outline-(--color-primary);
}
:global(#dnd-action-dragged-el) {
@apply outline-solid outline-2 outline-current;
}
</style>
================================================
FILE: interface/src/lib/components/FirmwareUpdateDialog.svelte
================================================
<script lang="ts">
import { modals, onBeforeClose } from 'svelte-modals';
import { focusTrap } from 'svelte-focus-trap';
import { fly } from 'svelte/transition';
import { telemetry } from '$lib/stores/telemetry';
import Cancel from '~icons/tabler/x';
import Check from '~icons/tabler/check';
import AlertCircle from '~icons/tabler/alert-circle';
import Refresh from '~icons/tabler/refresh';
import Loader from '~icons/tabler/loader-2';
interface Props {
isOpen: boolean;
title?: string;
}
let { isOpen, title = 'Updating Firmware' }: Props = $props();
// Use telemetry store for all status information
let currentStatus = $derived($telemetry.ota_status.status);
let currentProgress = $derived($telemetry.ota_status.progress);
let currentError = $derived($telemetry.ota_status.error);
let updating = $derived(
currentStatus === 'preparing' || currentStatus === 'progress' || currentStatus === 'none'
);
let displayMessage = $derived.by(() => {
if (currentStatus === 'error') return currentError || 'Update failed.';
if (currentStatus === 'finished') return 'Update finished successfully.';
if (currentStatus === 'progress') return 'Updating...';
if (currentStatus === 'preparing') return 'Preparing...';
return 'Waiting for update...';
});
const RELOAD_COUNTDOWN_SECONDS = 10;
let timerId: number | undefined = $state();
let countdown: number = $state(RELOAD_COUNTDOWN_SECONDS);
function hardReload() {
// Hard reload with cache bust to load new firmware UI
window.location.href = window.location.href.split('?')[0] + '?t=' + Date.now();
}
$effect(() => {
if (currentStatus === 'finished') {
countdown = RELOAD_COUNTDOWN_SECONDS;
// Single timer: countdown and reload when reaching zero
timerId = setInterval(() => {
countdown--;
if (countdown <= 0) {
clearInterval(timerId);
modals.closeAll();
hardReload();
}
}, 1000) as unknown as number;
return () => {
if (timerId) clearInterval(timerId);
};
}
});
onBeforeClose(() => {
if (updating) {
return false; // Prevent closing during update
}
// Reset status when closing error dialog to allow fresh start
if (currentStatus === 'error') {
telemetry.setOTAStatus({ status: 'none', progress: 0, error: '' });
}
return true;
});
</script>
{#if isOpen}
<div
role="dialog"
class="pointer-events-none fixed inset-0 z-50 flex items-center justify-center backdrop-blur-sm"
transition:fly={{ y: 50, duration: 300 }}
use:focusTrap
>
<div
class="bg-base-100 shadow-2xl rounded-2xl pointer-events-auto flex max-h-full w-full max-w-md flex-col border border-base-300"
>
<!-- Header -->
<div class="flex items-center justify-between p-6 pb-4">
<h2 class="text-base-content text-xl font-bold">{title}</h2>
</div>
<!-- Progress Content -->
<div class="flex flex-col items-center justify-center px-6 py-8 space-y-6">
{#if currentStatus === 'progress' || currentStatus === 'preparing' || currentStatus === 'none'}
<!-- Radial Progress or Indeterminate Spinner -->
<div class="relative">
{#if currentStatus === 'progress' && currentProgress > 0}
<div
class="radial-progress text-primary"
style="--value:{currentProgress}; --size:10rem; --thickness: 0.5rem;"
role="progressbar"
>
<span class="text-3xl font-bold">{currentProgress}%</span>
</div>
{:else}
<!-- Indeterminate spinner matching radial size -->
<div class="flex items-center justify-center w-40 h-40">
<Loader class="text-primary h-16 w-16 animate-spin stroke-2" />
</div>
{/if}
</div>
{:else if currentStatus === 'finished'}
<!-- Success Icon -->
<div class="flex items-center justify-center w-24 h-24 rounded-full bg-success/10">
<Check class="h-16 w-16 text-success" />
</div>
{:else if currentStatus === 'error'}
<!-- Error Icon -->
<div class="flex items-center justify-center w-24 h-24 rounded-full bg-error/10">
<AlertCircle class="h-16 w-16 text-error" />
</div>
{/if}
<!-- Status Message -->
<p
class="text-center text-lg {currentStatus === 'error'
? 'text-error'
: 'text-base-content/70'}"
>
{displayMessage}
</p>
{#if currentStatus === 'finished'}
<p class="text-sm text-base-content/50 text-center">
Page will reload automatically in {countdown}
{countdown === 1 ? 'second' : 'seconds'}...
</p>
{/if}
</div>
<!-- Footer -->
<div class="flex justify-end gap-2 p-6 pt-4 border-t border-base-300">
<button
class="btn btn-sm {currentStatus === 'finished' ? 'btn-primary' : currentStatus === 'error' ? 'btn-error' : 'btn-ghost'}"
disabled={updating}
onclick={() => {
if (timerId) clearInterval(timerId);
if (currentStatus === 'finished') {
modals.closeAll();
hardReload();
} else {
modals.closeAll();
}
}}
>
{#if currentStatus === 'finished'}
<Refresh class="h-4 w-4" />
Refresh Now
{:else}
<Cancel class="h-4 w-4" />
Cancel
{/if}
</button>
</div>
</div>
</div>
{/if}
================================================
FILE: interface/src/lib/components/InfoDialog.svelte
================================================
<script lang="ts">
import { modals } from 'svelte-modals';
import { focusTrap } from 'svelte-focus-trap';
import { fly } from 'svelte/transition';
import Check from '~icons/tabler/check';
// provided by <Modals />
interface Props {
isOpen: boolean;
title: string;
message: string;
onDismiss: any;
dismiss?: any;
}
const {
isOpen,
title,
message,
onDismiss,
dismiss = { label: 'Dismiss', icon: Check }
}: Props = $props();
</script>
{#if isOpen}
<div
role="dialog"
class="pointer-events-none fixed inset-0 z-50 flex items-center justify-center"
transition:fly={{ y: 50 }}
use:focusTrap
>
<div
class="rounded-box bg-base-100 shadow-secondary/30 pointer-events-auto flex min-w-fit max-w-md flex-col justify-between p-4 shadow-lg"
>
<h2 class="text-base-content text-start text-2xl font-bold">{title}</h2>
<div class="divider my-2"></div>
<p class="text-base-content mb-1 text-start">{@html message}</p>
<div class="divider my-2"></div>
<div class="flex justify-end gap-2">
<button
class="btn btn-warning text-warning-content inline-flex items-center"
onclick={onDismiss}
><dismiss.icon class="mr-2 h-5 w-5" /><span>{dismiss.label}</span></button
>
</div>
</div>
</div>
{/if}
================================================
FILE: interface/src/lib/components/InputPassword.svelte
================================================
<script lang="ts">
let show = $state(false);
let type = $derived(show ? 'text' : 'password');
interface Props {
value?: string;
id?: string;
}
let { value = $bindable('') as string, id = '' as string }: Props = $props();
function handleInput(e: any) {
value = e.target.value;
}
</script>
<div class="relative">
<input {type} class="input input-bordered w-full" {value} oninput={handleInput} {id} />
<div class="absolute inset-y-0 right-0 flex items-center pr-1">
<!-- svelte-ignore a11y_click_events_have_key_events -->
<svg
xmlns="http://www.w3.org/2000/svg"
class="text-base-content/50 h-6 {show ? 'block' : 'hidden'}"
onclick={() => (show = false)}
role="button"
aria-label="Hide password"
tabindex="0"
width="40"
height="40"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M10.585 10.587a2 2 0 0 0 2.829 2.828" />
<path
d="M16.681 16.673a8.717 8.717 0 0 1 -4.681 1.327c-3.6 0 -6.6 -2 -9 -6c1.272 -2.12 2.712 -3.678 4.32 -4.674m2.86 -1.146a9.055 9.055 0 0 1 1.82 -.18c3.6 0 6.6 2 9 6c-.666 1.11 -1.379 2.067 -2.138 2.87"
/>
<path d="M3 3l18 18" />
</svg>
<!-- svelte-ignore a11y_click_events_have_key_events -->
<svg
xmlns="http://www.w3.org/2000/svg"
class="text-base-content/50 h-6 {show ? 'hidden' : 'block'}"
onclick={() => (show = true)}
role="button"
aria-label="Show password"
tabindex="0"
width="40"
height="40"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M10 12a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" />
<path d="M21 12c-2.4 4 -5.4 6 -9 6c-3.6 0 -6.6 -2 -9 -6c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6" />
</svg>
</div>
</div>
================================================
FILE: interface/src/lib/components/RSSIIndicator.svelte
================================================
<script lang="ts">
import WiFi from '~icons/tabler/wifi';
import WiFi0 from '~icons/tabler/wifi-0';
import WiFi1 from '~icons/tabler/wifi-1';
import WiFi2 from '~icons/tabler/wifi-2';
let { showDBm = false, rssi_dbm = 0, ssid = '', class: className = '' } = $props();
$effect(() => {
if (ssid === '') {
ssid = 'Unknown';
}
});
</script>
<div class="indicator">
<div class="tooltip tooltip-left" data-tip={ssid}>
{#if showDBm}
<span class="indicator-item indicator-start badge badge-accent badge-outline badge-xs">
{rssi_dbm} dBm
</span>
{/if}
{#if rssi_dbm >= -55}
<WiFi class={className} />
{:else if rssi_dbm >= -75}
<div class="{className} relative">
<WiFi class="absolute inset-0 h-full w-full opacity-30" />
<WiFi2 class="absolute inset-0 h-full w-full" />
</div>
{:else if rssi_dbm >= -85}
<div class="{className} relative">
<WiFi class="absolute inset-0 h-full w-full opacity-30" />
<WiFi1 class="absolute inset-0 h-full w-full" />
</div>
{:else}
<div class="{className} relative">
<WiFi class="absolute inset-0 h-full w-full opacity-30" />
<WiFi0 class="absolute inset-0 h-full w-full" />
</div>
{/if}
</div>
</div>
================================================
FILE: interface/src/lib/components/SettingsCard.svelte
================================================
<script lang="ts">
import { slide } from 'svelte/transition';
import { cubicOut } from 'svelte/easing';
import Down from '~icons/tabler/chevron-down';
import Alert from '~icons/tabler/alert-hexagon';
interface Props {
open?: boolean;
collapsible?: boolean;
icon?: import('svelte').Snippet;
title?: import('svelte').Snippet;
children?: import('svelte').Snippet;
maxwidth?: string;
isDirty?: boolean;
}
let {
open = $bindable(true),
collapsible = true,
icon,
title,
children,
maxwidth = 'max-w-2xl',
isDirty = false
}: Props = $props();
</script>
{#if collapsible}
<div
class="bg-base-200 rounded-box shadow-primary/50 relative grid w-full {maxwidth} self-center overflow-hidden shadow-lg m-10"
>
{#if isDirty}
<div class="absolute left-0 top-0 w-1.5 h-full bg-red-300"></div>
{/if}
<div
class="min-h-16 flex w-full items-center justify-between space-x-3 p-4 text-xl font-medium"
>
<span class="inline-flex items-center">
{@render icon?.()}
{@render title?.()}
{#if isDirty}
<div data-tip="There are unsaved changes." class="tooltip tooltip-right tooltip-error">
<Alert class="text-error lex-shrink-0 ml-2 h-6 w-6 self-end cursor-help" />
</div>
{/if}
</span>
<button
class="btn btn-circle btn-ghost btn-sm"
onclick={() => {
open = !open;
}}
>
<Down
class="text-base-content h-auto w-6 transition-transform duration-300 ease-in-out {open
? 'rotate-180'
: ''}"
/>
</button>
</div>
{#if open}
<div
class="flex flex-col gap-2 p-4 pt-0"
transition:slide|local={{ duration: 300, easing: cubicOut }}
>
{@render children?.()}
</div>
{/if}
</div>
{:else}
<div
class="bg-base-200 rounded-box shadow-primary/50 relative grid w-full {maxwidth} self-center overflow-hidden shadow-lg m-10"
>
{#if isDirty}
<div class="absolute left-0 top-0 w-1.5 h-full bg-red-300"></div>
{/if}
<div class="min-h-16 w-full p-4 text-xl font-medium">
<span class="inline-flex items-center">
{@render icon?.()}
{@render title?.()}
{#if isDirty}
<div data-tip="There are unsaved changes." class="tooltip tooltip-right tooltip-error">
<Alert class="text-error lex-shrink-0 ml-2 h-6 w-6 self-end cursor-help" />
</div>
{/if}
</span>
</div>
<div class="flex flex-col gap-2 p-4 pt-0">
{@render children?.()}
</div>
</div>
{/if}
================================================
FILE: interface/src/lib/components/Spinner.svelte
================================================
<script lang="ts">
import Loader from '~icons/tabler/loader-2';
let { text = "Loading..."} = $props();
</script>
<div class="flex w-full flex-col items-center justify-center p-6">
<Loader class="text-primary h-14 w-auto animate-spin stroke-2" />
<p class="text-xl">{text}</p>
</div>
================================================
FILE: interface/src/lib/components/UpdateIndicator.svelte
================================================
<script lang="ts">
import { page } from '$app/state';
import { modals } from 'svelte-modals';
import type { ModalComponent } from 'svelte-modals';
import { user } from '$lib/stores/user';
import { notifications } from '$lib/components/toasts/notifications';
import ConfirmDialog from '$lib/components/ConfirmDialog.svelte';
import Firmware from '~icons/tabler/refresh-alert';
import Cancel from '~icons/tabler/x';
import CloudDown from '~icons/tabler/cloud-download';
import FirmwareUpdateDialog from '$lib/components/FirmwareUpdateDialog.svelte';
import { compareVersions } from 'compare-versions';
import { onMount } from 'svelte';
interface Props {
update?: boolean;
}
let { update = $bindable(false) }: Props = $props();
let firmwareVersion: string = $state('');
let firmwareDownloadLink: string;
async function getGithubAPI() {
const githubUrl = `https://api.github.com/repos/${page.data.github}/releases/latest`;
try {
const response = await fetch(githubUrl, {
method: 'GET',
headers: {
accept: 'application/vnd.github+json',
'X-GitHub-Api-Version': '2022-11-28'
}
});
if (response.status !== 200) {
notifications.error('Failed to fetch latest release from GitHub.', 5000);
throw new Error(`Failed to fetch latest release from ${githubUrl}`);
}
const results = await response.json();
update = false;
firmwareVersion = '';
if (compareVersions(results.tag_name, page.data.features.firmware_version) === 1) {
// iterate over assets and find the correct one
for (let i = 0; i < results.assets.length; i++) {
// check if the asset is of type *.bin
if (
results.assets[i].name.includes('.bin') &&
results.assets[i].name.includes(page.data.features.firmware_built_target)
) {
update = true;
firmwareVersion = results.tag_name;
firmwareDownloadLink = results.assets[i].browser_download_url;
notifications.info('Firmware update available.', 5000);
}
}
}
} catch (error) {
console.warn(error);
}
}
async function postGithubDownload(url: string) {
try {
const apiResponse = await fetch('/rest/downloadUpdate', {
method: 'POST',
headers: {
Authorization: page.data.features.security ? 'Bearer ' + $user.bearer_token : 'Basic',
'Content-Type': 'application/json'
},
body: JSON.stringify({ download_url: url })
});
} catch (error) {
console.error('Error:', error);
}
}
onMount(() => {
if (page.data.features.download_firmware && (!page.data.features.security || $user.admin)) {
getGithubAPI();
const interval = setInterval(
async () => {
getGithubAPI();
},
60 * 60 * 1000
); // once per hour
}
});
function confirmGithubUpdate(url: string) {
modals.open(ConfirmDialog as unknown as ModalComponent<any>, {
title: 'Confirm flashing new firmware to the device',
message: 'Are you sure you want to overwrite the existing firmware with a new one?',
labels: {
cancel: { label: 'Abort', icon: Cancel },
confirm: { label: 'Update', icon: CloudDown }
},
onConfirm: () => {
postGithubDownload(url);
modals.open(FirmwareUpdateDialog, {
title: 'Downloading Firmware'
});
}
});
}
</script>
{#if update}
<button
class="btn btn-square btn-ghost h-9 w-9"
onclick={() => confirmGithubUpdate(firmwareDownloadLink)}
>
<span
class="indicator-item indicator-top indicator-center badge badge-info badge-xs top-2 scale-75 lg:top-1"
>{firmwareVersion}</span
>
<Firmware class="h-7 w-7" />
</button>
{/if}
================================================
FILE: interface/src/lib/components/toasts/Toast.svelte
================================================
<script>
import { flip } from 'svelte/animate';
import { fly } from 'svelte/transition';
import { notifications } from '$lib/components/toasts/notifications';
import error from '~icons/tabler/circle-x';
import success from '~icons/tabler/circle-check';
import warning from '~icons/tabler/alert-triangle';
import info from '~icons/tabler/info-circle';
/** @type {{theme?: any, icon?: any}} */
let {
theme = {
error: 'alert-error',
success: 'alert-success',
warning: 'alert-warning',
info: 'alert-info'
},
icon = {
error: error,
success: success,
warning: warning,
info: info
}
} = $props();
</script>
<div class="toast toast-end z-[100] mr-4">
{#each $notifications as notification (notification.id)}
{@const SvelteComponent = icon[notification.type]}
<div
animate:flip={{ duration: 400 }}
class="alert animate-none {theme[notification.type]}"
in:fly={{ y: 100, duration: 400 }}
out:fly={{ x: 100, duration: 400 }}
>
<SvelteComponent class="h-6 w-6 shrink-0" />
<span>{@html notification.message}</span>
</div>
{/each}
</div>
================================================
FILE: interface/src/lib/components/toasts/notifications.ts
================================================
import { writable, derived, type Writable } from 'svelte/store';
type StateType = 'info' | 'success' | 'warning' | 'error';
type State = {
id: string;
type: StateType;
message: string;
};
function createNotificationStore() {
const state: State[] = [];
const notifications = writable(state);
const { subscribe } = notifications;
function send(message: string, type: StateType = 'info', timeout: number) {
const id = generateId();
setTimeout(() => {
notifications.update((state) => {
return state.filter((n) => n.id !== id);
});
}, timeout);
notifications.update((state) => {
return [...state, { id, type, message }];
});
}
return {
subscribe,
send,
error: (msg: string, timeout: number) => send(msg, 'error', timeout),
warning: (msg: string, timeout: number) => send(msg, 'warning', timeout),
info: (msg: string, timeout: number) => send(msg, 'info', timeout),
success: (msg: string, timeout: number) => send(msg, 'success', timeout)
};
}
function generateId() {
return '_' + Math.random().toString(36).substr(2, 9);
}
export const notifications = createNotificationStore();
================================================
FILE: interface/src/lib/stores/analytics.ts
================================================
import { type Analytics } from '$lib/types/models';
import { writable } from 'svelte/store';
let analytics_data = {
uptime: <number[]>[],
free_heap: <number[]>[],
used_heap: <number[]>[],
total_heap: <number[]>[],
min_free_heap: <number[]>[],
max_alloc_heap: <number[]>[],
fs_used: <number[]>[],
fs_total: <number[]>[],
core_temp: <number[]>[],
free_psram: <number[]>[],
used_psram: <number[]>[],
psram_size: <number[]>[],
};
const maxAnalyticsData = 1000; // roughly 33 Minutes of data at 1 update per 2 seconds
function createAnalytics() {
const { subscribe, update } = writable(analytics_data);
return {
subscribe,
addData: (content: Analytics) => {
update((analytics_data) => ({
...analytics_data,
uptime: [...analytics_data.uptime, content.uptime].slice(-maxAnalyticsData),
free_heap: [...analytics_data.free_heap, content.free_heap / 1000].slice(-maxAnalyticsData),
used_heap: [...analytics_data.used_heap, content.used_heap / 1000].slice(-maxAnalyticsData),
total_heap: [...analytics_data.total_heap, content.total_heap / 1000].slice(
-maxAnalyticsData
),
min_free_heap: [...analytics_data.min_free_heap, content.min_free_heap / 1000].slice(
-maxAnalyticsData
),
max_alloc_heap: [...analytics_data.max_alloc_heap, content.max_alloc_heap / 1000].slice(
-maxAnalyticsData
),
fs_used: [...analytics_data.fs_used, content.fs_used / 1000].slice(-maxAnalyticsData),
fs_total: [...analytics_data.fs_total, content.fs_total / 1000].slice(-maxAnalyticsData),
core_temp: [...analytics_data.core_temp, content.core_temp].slice(-maxAnalyticsData),
free_psram: [...analytics_data.free_psram, content.free_psram / 1000].slice(-maxAnalyticsData),
used_psram: [...analytics_data.used_psram, content.used_psram / 1000].slice(-maxAnalyticsData),
psram_size: [...analytics_data.psram_size, content.psram_size / 1000].slice(-maxAnalyticsData),
}));
}
};
}
export const analytics = createAnalytics();
================================================
FILE: interface/src/lib/stores/battery.ts
================================================
import { type Battery } from '$lib/types/models';
import { writable } from 'svelte/store';
let battery_history = {
soc: <number[]>[],
charging: <number[]>[],
timestamp: <number[]>[]
};
const maxAnalyticsData = 3600; // roughly 5 Hours of data at 1 update per 5 seconds
function createBatteryHistory() {
const { subscribe, update } = writable(battery_history);
return {
subscribe,
addData: (content: Battery) => {
update((battery_history) => ({
...battery_history,
soc: [...battery_history.soc, content.soc].slice(-maxAnalyticsData),
charging: [...battery_history.charging, content.charging ? 1 : 0].slice(-maxAnalyticsData),
timestamp: [...battery_history.timestamp, Date.now()].slice(-maxAnalyticsData)
}));
}
};
}
export const batteryHistory = createBatteryHistory();
================================================
FILE: interface/src/lib/stores/socket.ts
================================================
import { writable } from 'svelte/store';
import msgpack from 'msgpack-lite';
function createWebSocket() {
let listeners = new Map<string, Set<(data?: unknown) => void>>();
const { subscribe, set } = writable(false);
const socketEvents = ['open', 'close', 'error', 'message', 'unresponsive'] as const;
type SocketEvent = (typeof socketEvents)[number];
let unresponsiveTimeoutId: number;
let reconnectTimeoutId: number;
let ws: WebSocket;
let socketUrl: string | URL;
let event_use_json = false;
function init(url: string | URL, use_json: boolean = false) {
socketUrl = url;
event_use_json = use_json;
connect();
}
function disconnect(reason: SocketEvent, event?: Event) {
//console.log('disconnect', reason, event);
ws.close();
set(false);
clearTimeout(unresponsiveTimeoutId);
clearTimeout(reconnectTimeoutId);
listeners.get(reason)?.forEach((listener) => listener(event));
reconnectTimeoutId = setTimeout(connect, 1000);
}
function connect() {
//console.log('connect');
ws = new WebSocket(socketUrl);
ws.binaryType = 'arraybuffer';
ws.onopen = (ev) => {
set(true);
clearTimeout(reconnectTimeoutId);
listeners.get('open')?.forEach((listener) => listener(ev));
for (const event of listeners.keys()) {
if (socketEvents.includes(event as SocketEvent)) continue;
sendEvent('subscribe', event);
}
};
ws.onmessage = (message) => {
resetUnresponsiveCheck();
let payload = message.data;
const binary = payload instanceof ArrayBuffer;
listeners.get(binary ? 'binary' : 'message')?.forEach((listener) => listener(payload));
try {
payload = binary ? msgpack.decode(new Uint8Array(payload)) : JSON.parse(payload);
} catch (error) {
listeners.get('error')?.forEach((listener) => listener(error));
return;
}
listeners.get('json')?.forEach((listener) => listener(payload));
const { event, data } = payload;
if (event) listeners.get(event)?.forEach((listener) => listener(data));
};
ws.onerror = (ev) => disconnect('error', ev);
ws.onclose = (ev) => disconnect('close', ev);
}
function unsubscribe(event: string, listener?: (data: any) => void) {
let eventListeners = listeners.get(event);
if (!eventListeners) return;
if (!eventListeners.size) {
sendEvent('unsubscribe', event);
}
if (listener) {
eventListeners?.delete(listener);
} else {
listeners.delete(event);
}
}
function resetUnresponsiveCheck() {
clearTimeout(unresponsiveTimeoutId);
unresponsiveTimeoutId = setTimeout(() => disconnect('unresponsive'), 2000);
}
function send(msg: unknown) {
if (!ws || ws.readyState !== WebSocket.OPEN) return;
if (event_use_json) {
ws.send(JSON.stringify(msg));
} else {
ws.send(msgpack.encode(msg));
}
}
function sendEvent(event: string, data: unknown) {
send({ event, data });
}
return {
subscribe,
send,
sendEvent,
init,
on: <T>(event: string, listener: (data: T) => void): (() => void) => {
let eventListeners = listeners.get(event);
if (!eventListeners) {
eventListeners = new Set();
listeners.set(event, eventListeners);
// Only send subscription if WebSocket is open and it's not a socket event
if (!socketEvents.includes(event as SocketEvent) &&
ws && ws.readyState === WebSocket.OPEN) {
sendEvent('subscribe', event);
}
}
eventListeners.add(listener as (data: any) => void);
return () => {
unsubscribe(event, listener);
};
},
off: (event: string, listener?: (data: any) => void) => {
unsubscribe(event, listener);
}
};
}
export const socket = createWebSocket();
================================================
FILE: interface/src/lib/stores/telemetry.ts
================================================
import { writable } from 'svelte/store';
import type { RSSI } from '../types/models';
import type { Battery } from '../types/models';
import type { OTAStatus } from '../types/models';
import type { Ethernet } from '../types/models';
let telemetry_data = {
rssi: {
rssi: 0,
ssid: '',
disconnected: true
},
battery: {
soc: 100,
charging: false
},
ota_status: {
status: 'none',
progress: 0,
bytes_written: 0,
total_bytes: 0,
error: ''
},
ethernet: {
connected: false
}
};
function createTelemetry() {
const { subscribe, set, update } = writable(telemetry_data);
return {
subscribe,
setRSSI: (data: RSSI) => {
if (!isNaN(Number(data.rssi))) {
update((telemetry_data) => ({
...telemetry_data,
rssi: { rssi: Number(data.rssi), ssid: data.ssid, disconnected: false }
}));
} else {
update((telemetry_data) => ({
...telemetry_data,
rssi: { rssi: 0, ssid: data.ssid, disconnected: true }
}));
}
},
setBattery: (data: Battery) => {
update((telemetry_data) => ({
...telemetry_data,
battery: { soc: data.soc, charging: data.charging }
}));
},
setOTAStatus: (data: OTAStatus) => {
update((telemetry_data) => ({
...telemetry_data,
ota_status: {
status: data.status,
progress: data.progress,
bytes_written: data.bytes_written ?? 0,
total_bytes: data.total_bytes ?? 0,
error: data.error
}
}));
},
setEthernet: (data: Ethernet) => {
update((telemetry_data) => ({
...telemetry_data,
ethernet: { connected: data.connected }
}));
}
};
}
export const telemetry = createTelemetry();
================================================
FILE: interface/src/lib/stores/user.ts
================================================
import { writable } from 'svelte/store';
import { goto } from '$app/navigation';
import { jwtDecode } from 'jwt-decode';
export type userProfile = {
username: string;
admin: boolean;
bearer_token: string;
};
type decodedJWT = {
username: string;
admin: boolean;
};
let empty = {
username: '',
admin: false,
bearer_token: ''
};
function createStore() {
const { subscribe, set } = writable(empty);
// retrieve store from sessionStorage / localStorage if available
const userdata = localStorage.getItem('user');
if (userdata) {
set(JSON.parse(userdata));
}
return {
subscribe,
init: (access_token: string) => {
const decoded: decodedJWT = jwtDecode(access_token);
const userdata = {
bearer_token: access_token,
username: decoded.username,
admin: decoded.admin
};
set(userdata);
// persist store in sessionStorage / localStorage
localStorage.setItem('user', JSON.stringify(userdata));
},
invalidate: () => {
console.log('Log out user');
set(empty);
// remove localStorage "user"
localStorage.removeItem('user');
// redirect to login page
goto('/');
}
};
}
export const user = createStore();
================================================
FILE: interface/src/lib/types/models.ts
================================================
export type WifiStatus = {
status: number;
local_ip: string;
mac_address: string;
rssi: number;
ssid: string;
bssid: string;
channel: number;
subnet_mask: string;
gateway_ip: string;
dns_ip_1: string;
dns_ip_2?: string;
};
export type WifiSettings = {
hostname: string;
connection_mode: number;
wifi_networks: KnownNetworkItem[];
};
export type KnownNetworkItem = {
ssid: string;
password: string;
static_ip_config: boolean;
local_ip?: string;
subnet_mask?: string;
gateway_ip?: string;
dns_ip_1?: string;
dns_ip_2?: string;
};
export type NetworkItem = {
rssi: number;
ssid: string;
bssid: string;
channel: number;
encryption_type: number;
};
export type ApStatus = {
status: number;
ip_address: string;
mac_address: string;
station_num: number;
};
export type ApSettings = {
provision_mode: number;
ssid: string;
password: string;
channel: number;
ssid_hidden: boolean;
max_clients: number;
local_ip: string;
gateway_ip: string;
subnet_mask: string;
};
export type LightState = {
led_on: boolean;
};
export type BrokerSettings = {
mqtt_path: string;
name: string;
unique_id: string;
status_topic: string;
};
export type NTPStatus = {
status: number;
utc_time: string;
local_time: string;
server: string;
uptime: number;
};
export type NTPSettings = {
enabled: boolean;
server: string;
tz_label: string;
tz_format: string;
};
export type Analytics = {
max_alloc_heap: number;
psram_size: number;
free_psram: number;
used_psram: number;
free_heap: number;
used_heap: number;
total_heap: number;
min_free_heap: number;
core_temp: number;
fs_total: number;
fs_used: number;
uptime: number;
};
export type RSSI = {
rssi: number;
ssid: string;
};
export type Battery = {
soc: number;
charging: boolean;
};
export type OTAStatus = {
status: 'none' | 'preparing' | 'progress' | 'finished' | 'error';
progress: number;
bytes_written?: number;
total_bytes?: number;
error: string;
};
export type StaticSystemInformation = {
esp_platform: string;
firmware_version: string;
cpu_freq_mhz: number;
cpu_type: string;
cpu_rev: number;
cpu_cores: number;
sketch_size: number;
free_sketch_space: number;
sdk_version: string;
arduino_version: string;
flash_chip_size: number;
flash_chip_speed: number;
cpu_reset_reason: string;
};
export type SystemInformation = Analytics & StaticSystemInformation;
export type MQTTStatus = {
enabled: boolean;
connected: boolean;
client_id: string;
last_error: string;
};
export type MQTTSettings = {
enabled: boolean;
uri: string;
username: string;
password: string;
client_id: string;
keep_alive: number;
clean_session: boolean;
message_interval_ms: number;
};
export type Ethernet = {
connected: boolean;
};
export type EthernetStatus = {
connected: boolean;
local_ip: string;
mac_address: string;
subnet_mask: string;
gateway_ip?: string;
dns_ip_1?: string;
dns_ip_2?: string;
link_speed?: number;
};
export type EthernetSettings = {
hostname: string;
static_ip_config: boolean;
local_ip?: string;
subnet_mask?: string;
gateway_ip?: string;
dns_ip_1?: string;
dns_ip_2?: string;
};
================================================
FILE: interface/src/routes/+error.svelte
================================================
<script lang="ts">
import { goto } from '$app/navigation';
import { page } from '$app/state';
if (page.status == 404) {
goto('/');
}
</script>
<div class="min-w-screen flex h-screen">
<div class="mx-auto flex w-8/12 max-w-lg flex-col justify-center self-center p-2 align-middle">
<div class="flex flex-wrap items-end justify-start">
<div class="text-secondary pr-2 text-7xl font-bold">{page.status}</div>
<div class="text-base-content text-5xl font-semibold">{page.error?.message}</div>
</div>
<div class="divider"></div>
<p class="text-xl">Oops! Something has gone wrong.</p>
</div>
</div>
================================================
FILE: interface/src/routes/+layout.svelte
================================================
<script lang="ts">
import type { LayoutData } from './$types';
import { onDestroy, onMount } from 'svelte';
import { user } from '$lib/stores/user';
import { telemetry } from '$lib/stores/telemetry';
import { analytics } from '$lib/stores/analytics';
import { batteryHistory } from '$lib/stores/battery';
import { socket } from '$lib/stores/socket';
import type { userProfile } from '$lib/stores/user';
import { page } from '$app/state';
import { Modals, modals } from 'svelte-modals';
import Toast from '$lib/components/toasts/Toast.svelte';
import { notifications } from '$lib/components/toasts/notifications';
import { fade } from 'svelte/transition';
import '../app.css';
import Menu from './menu.svelte';
import Statusbar from './statusbar.svelte';
import Login from './login.svelte';
import type { Analytics } from '$lib/types/models';
import type { RSSI } from '$lib/types/models';
import type { Battery } from '$lib/types/models';
import type { OTAStatus } from '$lib/types/models';
import type { Ethernet } from '$lib/types/models';
interface Props {
data: LayoutData;
children?: import('svelte').Snippet;
}
let { data, children }: Props = $props();
onMount(async () => {
if ($user.bearer_token !== '') {
await validateUser($user);
}
if (!(page.data.features.security && $user.bearer_token === '')) {
initSocket();
}
});
const initSocket = () => {
const ws_token = page.data.features.security ? '?access_token=' + $user.bearer_token : '';
const ws_protocol = window.location.protocol === 'https:' ? 'wss' : 'ws';
socket.init(
`${ws_protocol}://${window.location.host}/ws/events${ws_token}`,
page.data.features.event_use_json
);
addEventListeners();
};
onDestroy(() => {
removeEventListeners();
});
const addEventListeners = () => {
socket.on('open', handleOpen);
socket.on('close', handleClose);
socket.on('error', handleError);
socket.on('rssi', handleNetworkStatus);
socket.on('notification', handleNotification);
if (page.data.features.analytics) socket.on('analytics', handleAnalytics);
if (page.data.features.battery) socket.on('battery', handleBattery);
if (page.data.features.download_firmware) socket.on('otastatus', handleOTA);
if (page.data.features.ethernet) socket.on('ethernet', handleEthernet);
};
const removeEventListeners = () => {
socket.off('analytics', handleAnalytics);
socket.off('open', handleOpen);
socket.off('close', handleClose);
socket.off('rssi', handleNetworkStatus);
socket.off('notification', handleNotification);
socket.off('battery', handleBattery);
socket.off('otastatus', handleOTA);
socket.off('ethernet', handleEthernet);
};
async function validateUser(userdata: userProfile) {
try {
const response = await fetch('/rest/verifyAuthorization', {
method: 'GET',
headers: {
Authorization: 'Bearer ' + userdata.bearer_token,
'Content-Type': 'application/json'
}
});
if (response.status !== 200) {
user.invalidate();
}
} catch (error) {
console.error('Error:', error);
}
}
const handleOpen = () => {
notifications.success('Connection to device established', 5000);
};
const handleClose = () => {
notifications.error('Connection to device lost', 5000);
telemetry.setRSSI({ rssi: 0, ssid: '' });
};
const handleError = (data: any) => console.error(data);
const handleNotification = (data: any) => {
switch (data.type) {
case 'info':
notifications.info(data.message, 5000);
break;
case 'warning':
notifications.warning(data.message, 5000);
break;
case 'error':
notifications.error(data.message, 5000);
break;
case 'success':
notifications.success(data.message, 5000);
break;
default:
break;
}
};
const handleAnalytics = (data: Analytics) => analytics.addData(data);
const handleNetworkStatus = (data: RSSI) => telemetry.setRSSI(data);
const handleBattery = (data: Battery) => {
telemetry.setBattery(data);
batteryHistory.addData(data);
};
const handleOTA = (data: OTAStatus) => {
telemetry.setOTAStatus(data);
};
const handleEthernet = (data: Ethernet) => {
telemetry.setEthernet(data);
};
let menuOpen = $state(false);
</script>
<svelte:head>
<title>{page.data.title}</title>
</svelte:head>
{#if page.data.features.security && $user.bearer_token === ''}
<Login signIn={initSocket} />
{:else}
<div class="drawer lg:drawer-open">
<input id="main-menu" type="checkbox" class="drawer-toggle" bind:checked={menuOpen} />
<div class="drawer-content flex flex-col">
<!-- Status bar content here -->
<Statusbar />
<!-- Main page content here -->
{@render children?.()}
</div>
<!-- Side Navigation -->
<div class="drawer-side z-30 shadow-lg">
<label for="main-menu" class="drawer-overlay"></label>
<Menu
closeMenu={() => {
menuOpen = false;
}}
/>
</div>
</div>
{/if}
<Modals>
<!-- svelte-ignore a11y_click_events_have_key_events -->
{#snippet backdrop({ close })}
<div
class="fixed inset-0 z-40 max-h-full max-w-full bg-black/20 backdrop-blur-sm"
transition:fade|global
onclick={() => close()}
role="button"
tabindex="0"
aria-label="Close modal"
></div>
{/snippet}
</Modals>
<Toast />
================================================
FILE: interface/src/routes/+layout.ts
================================================
import type { LayoutLoad } from './$types';
// This can be false if you're using a fallback (i.e. SPA mode)
export const prerender = false;
export const ssr = false;
export const load = (async ({ fetch }) => {
const result = await fetch('/rest/features');
const item = await result.json();
return {
features: item,
title: 'ESP32-SvelteKit',
github: 'theelims/ESP32-sveltekit',
copyright: '2025 theelims',
appName: 'ESP32 SvelteKit'
};
}) satisfies LayoutLoad;
================================================
FILE: interface/src/routes/+page.svelte
================================================
<script lang="ts">
import type { PageData } from './$types';
import logo from '$lib/assets/logo.png';
import { notifications } from '$lib/components/toasts/notifications';
interface Props {
data: PageData;
}
let { data }: Props = $props();
</script>
<div class="hero bg-base-100 h-screen">
<div class="card md:card-side bg-base-200 shadow-primary shadow-2xl">
<figure class="bg-base-200"><img src={logo} alt="Logo" class="h-auto w-64" /></figure>
<div class="card-body w-80">
<h2 class="card-title text-center text-2xl">Welcome to ESP32-SvelteKit</h2>
<p class="py-6 text-center">
A simple, secure and extensible framework for IoT projects for ESP32 platforms with
responsive <a
href="https://kit.svelte.dev/"
class="link"
target="_blank"
rel="noopener noreferrer">SvelteKit</a
>
front-end built with
<a href="https://tailwindcss.com/" class="link" target="_blank" rel="noopener noreferrer"
>TailwindCSS</a
>
and
<a href="https://daisyui.com/" class="link" target="_blank" rel="noopener noreferrer"
>DaisyUI</a
>.
</p>
<a
class="btn btn-primary"
href="/demo"
onclick={() => notifications.success('You did it!', 1000)}>Start Demo</a
>
</div>
</div>
</div>
================================================
FILE: interface/src/routes/connections/+page.ts
================================================
import type { PageLoad } from './$types';
import { goto } from '$app/navigation';
export const load = (async () => {
goto('/');
return;
}) satisfies PageLoad;
================================================
FILE: interface/src/routes/connections/mqtt/+page.svelte
================================================
<script lang="ts">
import type { PageData } from '../$types';
import MQTT from './MQTT.svelte';
import MqttConfig from './MQTTConfig.svelte';
interface Props {
data: PageData;
}
let { data }: Props = $props();
</script>
<div
class="mx-0 my-1 flex flex-col space-y-4
sm:mx-8 sm:my-8"
>
<MQTT />
<MqttConfig />
</div>
================================================
FILE: interface/src/routes/connections/mqtt/+page.ts
================================================
import type { PageLoad } from './$types';
export const load = (async () => {
return {
title: "MQTT"
};
}) satisfies PageLoad;
================================================
FILE: interface/src/routes/connections/mqtt/MQTT.svelte
================================================
<script lang="ts">
import { onMount, onDestroy } from 'svelte';
import { slide } from 'svelte/transition';
import { cubicOut } from 'svelte/easing';
import InputPassword from '$lib/components/InputPassword.svelte';
import SettingsCard from '$lib/components/SettingsCard.svelte';
import { user } from '$lib/stores/user';
import { page } from '$app/state';
import { notifications } from '$lib/components/toasts/notifications';
import Spinner from '$lib/components/Spinner.svelte';
import Collapsible from '$lib/components/Collapsible.svelte';
import MQTT from '~icons/tabler/topology-star-3';
import Client from '~icons/tabler/robot';
import type { MQTTSettings, MQTTStatus } from '$lib/types/models';
let mqttSettings: MQTTSettings = $state();
let mqttStatus: MQTTStatus = $state();
let formField: any = $state();
async function getMQTTStatus() {
try {
const response = await fetch('/rest/mqttStatus', {
method: 'GET',
headers: {
Authorization: page.data.features.security ? 'Bearer ' + $user.bearer_token : 'Basic',
'Content-Type': 'application/json'
}
});
mqttStatus = await response.json();
} catch (error) {
console.error('Error:', error);
}
return mqttStatus;
}
async function getMQTTSettings() {
try {
const response = await fetch('/rest/mqttSettings', {
method: 'GET',
headers: {
Authorization: page.data.features.security ? 'Bearer ' + $user.bearer_token : 'Basic',
'Content-Type': 'application/json'
}
});
mqttSettings = await response.json();
} catch (error) {
console.error('Error:', error);
}
return mqttSettings;
}
const interval = setInterval(async () => {
getMQTTStatus();
}, 5000);
onDestroy(() => clearInterval(interval));
onMount(() => {
if (!page.data.features.security || $user.admin) {
getMQTTSettings();
}
});
let formErrors = $state({
host: false,
port: false,
keep_alive: false,
topic_length: false,
rate_limit: false
});
async function postMQTTSettings(data: MQTTSettings) {
try {
const response = await fetch('/rest/mqttSettings', {
method: 'POST',
headers: {
Authorization: page.data.features.security ? 'Bearer ' + $user.bearer_token : 'Basic',
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
if (response.status == 200) {
notifications.success('MQTT settings updated.', 3000);
mqttSettings = await response.json();
} else {
notifications.error('User not authorized.', 3000);
}
} catch (error) {
console.error('Error:', error);
}
return;
}
function handleSubmitMQTT() {
let valid = true;
// Validate Server URI
const regexExpURL =
/^(mqtt|mqtts|ws|wss):\/\/((?:[a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+|(?:\d{1,3}\.){3}\d{1,3})(?::(\d{1,5}))?$/;
if (!regexExpURL.test(mqttSettings.uri)) {
valid = false;
formErrors.host = true;
} else {
formErrors.host = false;
}
// Validate if port is a number and within the right range
let keepalive = Number(mqttSettings.keep_alive);
if (1 <= keepalive && keepalive <= 600) {
formErrors.keep_alive = false;
} else {
formErrors.keep_alive = true;
valid = false;
}
// Validate it rate limit is a number and within the right range
let ratelimit = Number(mqttSettings.message_interval_ms);
if (0 <= ratelimit && ratelimit <= 1000) {
formErrors.rate_limit = false;
} else {
formErrors.rate_limit = true;
valid = false;
}
// Submit JSON to REST API
if (valid) {
postMQTTSettings(mqttSettings);
//alert('Form Valid');
}
}
function preventDefault(fn) {
return function (event) {
event.preventDefault();
fn.call(this, event);
};
}
</script>
<SettingsCard collapsible={false}>
{#snippet icon()}
<MQTT class="lex-shrink-0 mr-2 h-6 w-6 self-end" />
{/snippet}
{#snippet title()}
<span>MQTT</span>
{/snippet}
<div class="w-full">
{#await getMQTTStatus()}
<Spinner />
{:then nothing}
<div
class="flex w-full flex-col space-y-1"
transition:slide|local={{ duration: 300, easing: cubicOut }}
>
<div class="rounded-box bg-base-100 flex items-center space-x-3 px-4 py-2">
<div
class="mask mask-hexagon h-auto w-10 {mqttStatus.connected === true
? 'bg-success'
: 'bg-error'}"
>
<MQTT
class="h-auto w-full scale-75 {mqttStatus.connected === true
? 'text-success-content'
: 'text-error-content'}"
/>
</div>
<div>
<div class="font-bold">Status</div>
<div class="text-sm opacity-75">
{#if mqttStatus.connected}
Connected
{:else if !mqttStatus.enabled}
MQTT Disabled
{:else}
{mqttStatus.last_error}
{/if}
</div>
</div>
</div>
<div class="rounded-box bg-base-100 flex items-center space-x-3 px-4 py-2">
<div class="mask mask-hexagon bg-primary h-auto w-10">
<Client class="text-primary-content h-auto w-full scale-75" />
</div>
<div>
<div class="font-bold">Client ID</div>
<div class="text-sm opacity-75">
{mqttStatus.client_id}
</div>
</div>
</div>
</div>
{/await}
</div>
{#if !page.data.features.security || $user.admin}
<Collapsible open={false} class="shadow-lg" icon={null} opened={() => {}} closed={() => {}}>
{#snippet title()}
<span>Change MQTT Settings</span>
{/snippet}
<form
onsubmit={preventDefault(handleSubmitMQTT)}
novalidate
bind:this={formField}
class="fieldset"
>
<div class="gr
gitextract_e2fr6h29/
├── .github/
│ └── workflows/
│ └── ci.yaml
├── .gitignore
├── CHANGELOG.md
├── ESP32-sveltekit.code-workspace
├── LICENSE
├── README.md
├── docs/
│ ├── buildprocess.md
│ ├── components.md
│ ├── gettingstarted.md
│ ├── index.md
│ ├── restfulapi.md
│ ├── statefulservice.md
│ ├── stores.md
│ ├── structure.md
│ └── sveltekit.md
├── factory_settings.ini
├── features.ini
├── interface/
│ ├── .eslintignore
│ ├── .eslintrc.cjs
│ ├── .gitignore
│ ├── .npmrc
│ ├── .prettierignore
│ ├── .prettierrc
│ ├── LICENSE
│ ├── package.json
│ ├── src/
│ │ ├── app.css
│ │ ├── app.d.ts
│ │ ├── app.html
│ │ ├── lib/
│ │ │ ├── DaisyUiHelper.ts
│ │ │ ├── components/
│ │ │ │ ├── BatteryIndicator.svelte
│ │ │ │ ├── Collapsible.svelte
│ │ │ │ ├── ConfirmDialog.svelte
│ │ │ │ ├── DraggableList.svelte
│ │ │ │ ├── FirmwareUpdateDialog.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
│ │ ├── ethernet/
│ │ │ ├── +page.svelte
│ │ │ ├── +page.ts
│ │ │ └── Ethernet.svelte
│ │ ├── login.svelte
│ │ ├── menu.svelte
│ │ ├── statusbar.svelte
│ │ ├── system/
│ │ │ ├── +page.ts
│ │ │ ├── coredump/
│ │ │ │ ├── +page.svelte
│ │ │ │ └── +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
│ │ ├── EditNetwork.svelte
│ │ ├── Scan.svelte
│ │ └── Wifi.svelte
│ ├── static/
│ │ └── manifest.json
│ ├── svelte.config.js
│ ├── tsconfig.json
│ ├── vite-plugin-littlefs.ts
│ └── vite.config.ts
├── lib/
│ ├── PsychicHttp/
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── RELEASE.md
│ │ ├── 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
│ │ ├── 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
│ ├── EthernetSettingsService.cpp
│ ├── EthernetSettingsService.h
│ ├── EthernetStatus.cpp
│ ├── EthernetStatus.h
│ ├── EventEndpoint.h
│ ├── EventSocket.cpp
│ ├── EventSocket.h
│ ├── FSPersistence.h
│ ├── FactoryResetService.cpp
│ ├── FactoryResetService.h
│ ├── Features.h
│ ├── FeaturesService.cpp
│ ├── FeaturesService.h
│ ├── FirmwareUpdateEvents.h
│ ├── HttpEndpoint.h
│ ├── IPUtils.h
│ ├── JsonUtils.h
│ ├── LICENSE
│ ├── MqttEndpoint.cpp
│ ├── 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
│ ├── 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
│ ├── prebuild_utils.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
SYMBOL INDEX (370 symbols across 112 files)
FILE: interface/src/lib/DaisyUiHelper.ts
function daisyColor (line 1) | function daisyColor(name: string, opacity: number = 100) {
FILE: interface/src/lib/components/toasts/notifications.ts
type StateType (line 3) | type StateType = 'info' | 'success' | 'warning' | 'error';
type State (line 5) | type State = {
function createNotificationStore (line 11) | function createNotificationStore() {
function generateId (line 38) | function generateId() {
FILE: interface/src/lib/stores/analytics.ts
function createAnalytics (line 21) | function createAnalytics() {
FILE: interface/src/lib/stores/battery.ts
function createBatteryHistory (line 12) | function createBatteryHistory() {
FILE: interface/src/lib/stores/socket.ts
function createWebSocket (line 4) | function createWebSocket() {
FILE: interface/src/lib/stores/telemetry.ts
function createTelemetry (line 29) | function createTelemetry() {
FILE: interface/src/lib/stores/user.ts
type userProfile (line 5) | type userProfile = {
type decodedJWT (line 11) | type decodedJWT = {
function createStore (line 22) | function createStore() {
FILE: interface/src/lib/types/models.ts
type WifiStatus (line 1) | type WifiStatus = {
type WifiSettings (line 15) | type WifiSettings = {
type KnownNetworkItem (line 21) | type KnownNetworkItem = {
type NetworkItem (line 32) | type NetworkItem = {
type ApStatus (line 40) | type ApStatus = {
type ApSettings (line 47) | type ApSettings = {
type LightState (line 59) | type LightState = {
type BrokerSettings (line 63) | type BrokerSettings = {
type NTPStatus (line 70) | type NTPStatus = {
type NTPSettings (line 78) | type NTPSettings = {
type Analytics (line 85) | type Analytics = {
type RSSI (line 100) | type RSSI = {
type Battery (line 105) | type Battery = {
type OTAStatus (line 110) | type OTAStatus = {
type StaticSystemInformation (line 118) | type StaticSystemInformation = {
type SystemInformation (line 134) | type SystemInformation = Analytics & StaticSystemInformation;
type MQTTStatus (line 136) | type MQTTStatus = {
type MQTTSettings (line 143) | type MQTTSettings = {
type Ethernet (line 155) | type Ethernet = {
type EthernetStatus (line 159) | type EthernetStatus = {
type EthernetSettings (line 170) | type EthernetSettings = {
FILE: interface/src/routes/connections/ntp/timezones.ts
type TimeZones (line 1) | type TimeZones = {
constant TIME_ZONES (line 5) | const TIME_ZONES: TimeZones = {
FILE: interface/vite-plugin-littlefs.ts
function viteLittleFS (line 3) | function viteLittleFS(): Plugin[] {
FILE: interface/vite.config.ts
method manualChunks (line 37) | manualChunks(id) {
FILE: lib/PsychicHttp/src/ChunkPrinter.h
function class (line 7) | class ChunkPrinter : public Print
FILE: lib/PsychicHttp/src/PsychicClient.cpp
function httpd_handle_t (line 15) | httpd_handle_t PsychicClient::server() {
function esp_err_t (line 24) | esp_err_t PsychicClient::close()
function IPAddress (line 32) | IPAddress PsychicClient::localIP()
function IPAddress (line 53) | IPAddress PsychicClient::remoteIP()
FILE: lib/PsychicHttp/src/PsychicClient.h
function class (line 10) | class PsychicClient {
FILE: lib/PsychicHttp/src/PsychicCore.h
type HTTPAuthMethod (line 45) | enum HTTPAuthMethod
type std (line 59) | typedef std::function<bool(PsychicRequest *request)> PsychicRequestFilte...
type std (line 62) | typedef std::function<void(PsychicClient *client)> PsychicClientCallback;
type std (line 65) | typedef std::function<esp_err_t(PsychicRequest *request)> PsychicHttpReq...
type std (line 66) | typedef std::function<esp_err_t(PsychicRequest *request, JsonVariant &js...
type HTTPHeader (line 68) | struct HTTPHeader
function class (line 74) | class DefaultHeaders
FILE: lib/PsychicHttp/src/PsychicEndpoint.cpp
function PsychicEndpoint (line 20) | PsychicEndpoint * PsychicEndpoint::setHandler(PsychicHandler *handler)
function PsychicHandler (line 35) | PsychicHandler * PsychicEndpoint::handler()
function String (line 40) | String PsychicEndpoint::uri() {
function esp_err_t (line 44) | esp_err_t PsychicEndpoint::requestCallback(httpd_req_t *req)
function PsychicEndpoint (line 70) | PsychicEndpoint* PsychicEndpoint::setFilter(PsychicRequestFilterFunction...
function PsychicEndpoint (line 75) | PsychicEndpoint* PsychicEndpoint::setAuthentication(const char *username...
FILE: lib/PsychicHttp/src/PsychicEndpoint.h
function class (line 8) | class PsychicEndpoint
FILE: lib/PsychicHttp/src/PsychicEventSource.cpp
function PsychicEventSourceClient (line 36) | PsychicEventSourceClient * PsychicEventSource::getClient(int socket)
function PsychicEventSourceClient (line 46) | PsychicEventSourceClient * PsychicEventSource::getClient(PsychicClient *...
function esp_err_t (line 50) | esp_err_t PsychicEventSource::handleRequest(PsychicRequest *request)
function PsychicEventSource (line 74) | PsychicEventSource * PsychicEventSource::onOpen(PsychicEventSourceClient...
function PsychicEventSource (line 79) | PsychicEventSource * PsychicEventSource::onClose(PsychicEventSourceClien...
function esp_err_t (line 162) | esp_err_t PsychicEventSourceResponse::send() {
function String (line 196) | String generateEventMessage(const char *message, const char *event, uint...
FILE: lib/PsychicHttp/src/PsychicEventSource.h
type std (line 33) | typedef std::function<void(PsychicEventSourceClient *client)> PsychicEve...
function class (line 35) | class PsychicEventSourceClient : public PsychicClient {
function class (line 50) | class PsychicEventSource : public PsychicHandler {
function class (line 74) | class PsychicEventSourceResponse: public PsychicResponse {
FILE: lib/PsychicHttp/src/PsychicFileResponse.cpp
function esp_err_t (line 98) | esp_err_t PsychicFileResponse::send()
FILE: lib/PsychicHttp/src/PsychicFileResponse.h
function class (line 9) | class PsychicFileResponse: public PsychicResponse
FILE: lib/PsychicHttp/src/PsychicHandler.cpp
function PsychicHandler (line 21) | PsychicHandler* PsychicHandler::setFilter(PsychicRequestFilterFunction f...
function PsychicHandler (line 37) | PsychicHandler* PsychicHandler::setAuthentication(const char *username, ...
function esp_err_t (line 50) | esp_err_t PsychicHandler::authenticate(PsychicRequest *request) {
function PsychicClient (line 54) | PsychicClient * PsychicHandler::checkForNewClient(PsychicClient *client)
function PsychicClient (line 86) | PsychicClient * PsychicHandler::getClient(int socket)
function PsychicClient (line 101) | PsychicClient * PsychicHandler::getClient(PsychicClient *client) {
FILE: lib/PsychicHttp/src/PsychicHandler.h
function class (line 14) | class PsychicHandler {
FILE: lib/PsychicHttp/src/PsychicHttpServer.cpp
function esp_err_t (line 52) | esp_err_t PsychicHttpServer::listen(uint16_t port)
function esp_err_t (line 60) | esp_err_t PsychicHttpServer::_start()
function esp_err_t (line 80) | esp_err_t PsychicHttpServer::_startServer() {
function PsychicHandler (line 89) | PsychicHandler& PsychicHttpServer::addHandler(PsychicHandler* handler){
function PsychicEndpoint (line 98) | PsychicEndpoint* PsychicHttpServer::on(const char* uri) {
function PsychicEndpoint (line 102) | PsychicEndpoint* PsychicHttpServer::on(const char* uri, http_method method)
function PsychicEndpoint (line 109) | PsychicEndpoint* PsychicHttpServer::on(const char* uri, PsychicHandler *...
function PsychicEndpoint (line 114) | PsychicEndpoint* PsychicHttpServer::on(const char* uri, http_method meth...
function PsychicEndpoint (line 143) | PsychicEndpoint* PsychicHttpServer::on(const char* uri, PsychicHttpReque...
function PsychicEndpoint (line 148) | PsychicEndpoint* PsychicHttpServer::on(const char* uri, http_method meth...
function PsychicEndpoint (line 157) | PsychicEndpoint* PsychicHttpServer::on(const char* uri, PsychicJsonReque...
function PsychicEndpoint (line 162) | PsychicEndpoint* PsychicHttpServer::on(const char* uri, http_method meth...
function esp_err_t (line 179) | esp_err_t PsychicHttpServer::notFoundHandler(httpd_req_t *req, httpd_err...
function esp_err_t (line 207) | esp_err_t PsychicHttpServer::defaultNotFoundHandler(PsychicRequest *requ...
function esp_err_t (line 218) | esp_err_t PsychicHttpServer::openCallback(httpd_handle_t hd, int sockfd)
function PsychicStaticFileHandler (line 275) | PsychicStaticFileHandler* PsychicHttpServer::serveStatic(const char* uri...
function PsychicClient (line 292) | PsychicClient * PsychicHttpServer::getClient(int socket) {
function PsychicClient (line 300) | PsychicClient * PsychicHttpServer::getClient(httpd_req_t *req) {
function ON_STA_FILTER (line 312) | bool ON_STA_FILTER(PsychicRequest *request) {
function ON_AP_FILTER (line 316) | bool ON_AP_FILTER(PsychicRequest *request) {
function String (line 320) | String urlDecode(const char* encoded)
FILE: lib/PsychicHttp/src/PsychicHttpServer.h
function class (line 12) | class PsychicHttpServer
FILE: lib/PsychicHttp/src/PsychicHttpsServer.cpp
function esp_err_t (line 25) | esp_err_t PsychicHttpsServer::listen(uint16_t port, const char *cert, co...
function esp_err_t (line 45) | esp_err_t PsychicHttpsServer::_startServer()
FILE: lib/PsychicHttp/src/PsychicHttpsServer.h
function class (line 17) | class PsychicHttpsServer : public PsychicHttpServer
FILE: lib/PsychicHttp/src/PsychicJson.cpp
function JsonVariant (line 25) | JsonVariant &PsychicJsonResponse::getRoot() { return _root; }
function esp_err_t (line 32) | esp_err_t PsychicJsonResponse::send()
function esp_err_t (line 106) | esp_err_t PsychicJsonHandler::handleRequest(PsychicRequest *request)
FILE: lib/PsychicHttp/src/PsychicJson.h
function class (line 34) | class PsychicJsonResponse : public PsychicResponse
function class (line 65) | class PsychicJsonHandler : public PsychicWebHandler
FILE: lib/PsychicHttp/src/PsychicRequest.cpp
function PsychicHttpServer (line 52) | PsychicHttpServer *PsychicRequest::server()
function httpd_req_t (line 57) | httpd_req_t *PsychicRequest::request()
function PsychicClient (line 62) | PsychicClient *PsychicRequest::client()
function String (line 67) | const String PsychicRequest::getFilename()
function ContentDisposition (line 94) | const ContentDisposition PsychicRequest::getContentDisposition()
function esp_err_t (line 127) | esp_err_t PsychicRequest::loadBody()
function http_method (line 167) | http_method PsychicRequest::method()
function String (line 172) | const String PsychicRequest::methodStr()
function String (line 177) | const String PsychicRequest::path()
function String (line 186) | const String &PsychicRequest::uri()
function String (line 191) | const String &PsychicRequest::query()
function String (line 201) | const String PsychicRequest::header(const char *name)
function String (line 221) | const String PsychicRequest::host()
function String (line 226) | const String PsychicRequest::contentType()
function String (line 236) | const String &PsychicRequest::body()
function esp_err_t (line 248) | esp_err_t PsychicRequest::redirect(const char *url)
function String (line 272) | const String PsychicRequest::getCookie(const char *key)
function PsychicWebParameter (line 325) | PsychicWebParameter *PsychicRequest::addParam(const String &name, const ...
function PsychicWebParameter (line 333) | PsychicWebParameter *PsychicRequest::addParam(PsychicWebParameter *param)
function PsychicWebParameter (line 345) | PsychicWebParameter *PsychicRequest::getParam(const char *key)
function String (line 359) | const String PsychicRequest::getSessionKey(const String &key)
function String (line 373) | static const String md5str(const String &in)
function String (line 498) | const String PsychicRequest::_extractParam(const String &authReq, const ...
function String (line 506) | const String PsychicRequest::_getRandomHexString()
function esp_err_t (line 517) | esp_err_t PsychicRequest::requestAuthentication(HTTPAuthMethod mode, con...
function esp_err_t (line 552) | esp_err_t PsychicRequest::reply(int code)
function esp_err_t (line 563) | esp_err_t PsychicRequest::reply(const char *content)
function esp_err_t (line 574) | esp_err_t PsychicRequest::reply(int code, const char *contentType, const...
FILE: lib/PsychicHttp/src/PsychicRequest.h
type std (line 10) | typedef std::map<String, String> SessionData;
type Disposition (line 12) | enum Disposition { NONE, INLINE, ATTACHMENT, FORM_DATA}
type ContentDisposition (line 14) | struct ContentDisposition {
function class (line 20) | class PsychicRequest {
FILE: lib/PsychicHttp/src/PsychicResponse.cpp
type tm (line 54) | struct tm
function esp_err_t (line 100) | esp_err_t PsychicResponse::send()
function esp_err_t (line 140) | esp_err_t PsychicResponse::sendChunk(uint8_t *chunk, size_t chunksize)
function esp_err_t (line 158) | esp_err_t PsychicResponse::finishChunking()
FILE: lib/PsychicHttp/src/PsychicResponse.h
function class (line 9) | class PsychicResponse
FILE: lib/PsychicHttp/src/PsychicStaticFileHander.cpp
function PsychicStaticFileHandler (line 32) | PsychicStaticFileHandler &PsychicStaticFileHandler::setIsDir(bool isDir)
function PsychicStaticFileHandler (line 38) | PsychicStaticFileHandler &PsychicStaticFileHandler::setDefaultFile(const...
function PsychicStaticFileHandler (line 44) | PsychicStaticFileHandler &PsychicStaticFileHandler::setCacheControl(cons...
function PsychicStaticFileHandler (line 50) | PsychicStaticFileHandler &PsychicStaticFileHandler::setLastModified(cons...
function PsychicStaticFileHandler (line 56) | PsychicStaticFileHandler &PsychicStaticFileHandler::setLastModified(stru...
function esp_err_t (line 158) | esp_err_t PsychicStaticFileHandler::handleRequest(PsychicRequest *request)
FILE: lib/PsychicHttp/src/PsychicStaticFileHandler.h
function class (line 10) | class PsychicStaticFileHandler : public PsychicWebHandler {
FILE: lib/PsychicHttp/src/PsychicStreamResponse.cpp
function esp_err_t (line 30) | esp_err_t PsychicStreamResponse::beginSend()
function esp_err_t (line 52) | esp_err_t PsychicStreamResponse::endSend()
FILE: lib/PsychicHttp/src/PsychicUploadHandler.cpp
function esp_err_t (line 13) | esp_err_t PsychicUploadHandler::handleRequest(PsychicRequest *request)
function esp_err_t (line 64) | esp_err_t PsychicUploadHandler::_basicUploadHandler(PsychicRequest *requ...
function esp_err_t (line 122) | esp_err_t PsychicUploadHandler::_multipartUploadHandler(PsychicRequest *...
function PsychicUploadHandler (line 183) | PsychicUploadHandler *PsychicUploadHandler::onUpload(PsychicUploadCallba...
FILE: lib/PsychicHttp/src/PsychicUploadHandler.h
type std (line 11) | typedef std::function<esp_err_t(PsychicRequest *request, const String& f...
function class (line 17) | class PsychicUploadHandler : public PsychicWebHandler {
FILE: lib/PsychicHttp/src/PsychicWebHandler.cpp
function esp_err_t (line 15) | esp_err_t PsychicWebHandler::handleRequest(PsychicRequest *request)
function PsychicWebHandler (line 51) | PsychicWebHandler * PsychicWebHandler::onRequest(PsychicHttpRequestCallb...
function PsychicWebHandler (line 66) | PsychicWebHandler * PsychicWebHandler::onOpen(PsychicClientCallback fn) {
function PsychicWebHandler (line 71) | PsychicWebHandler * PsychicWebHandler::onClose(PsychicClientCallback fn) {
FILE: lib/PsychicHttp/src/PsychicWebHandler.h
function class (line 13) | class PsychicWebHandler : public PsychicHandler {
FILE: lib/PsychicHttp/src/PsychicWebParameter.h
function class (line 8) | class PsychicWebParameter {
FILE: lib/PsychicHttp/src/PsychicWebSocket.cpp
function PsychicWebSocketClient (line 17) | PsychicWebSocketClient * PsychicWebSocketRequest::client() {
function esp_err_t (line 21) | esp_err_t PsychicWebSocketRequest::reply(httpd_ws_frame_t * ws_pkt)
function esp_err_t (line 26) | esp_err_t PsychicWebSocketRequest::reply(httpd_ws_type_t op, const void ...
function esp_err_t (line 38) | esp_err_t PsychicWebSocketRequest::reply(const char *buf)
function esp_err_t (line 55) | esp_err_t PsychicWebSocketClient::sendMessage(httpd_ws_frame_t * ws_pkt)
function esp_err_t (line 60) | esp_err_t PsychicWebSocketClient::sendMessage(httpd_ws_type_t op, const ...
function esp_err_t (line 72) | esp_err_t PsychicWebSocketClient::sendMessage(const char *buf)
function PsychicWebSocketClient (line 88) | PsychicWebSocketClient * PsychicWebSocketHandler::getClient(int socket)
function PsychicWebSocketClient (line 102) | PsychicWebSocketClient * PsychicWebSocketHandler::getClient(PsychicClien...
function esp_err_t (line 141) | esp_err_t PsychicWebSocketHandler::handleRequest(PsychicRequest *request)
function PsychicWebSocketHandler (line 212) | PsychicWebSocketHandler * PsychicWebSocketHandler::onOpen(PsychicWebSock...
function PsychicWebSocketHandler (line 217) | PsychicWebSocketHandler * PsychicWebSocketHandler::onFrame(PsychicWebSoc...
function PsychicWebSocketHandler (line 222) | PsychicWebSocketHandler * PsychicWebSocketHandler::onClose(PsychicWebSoc...
FILE: lib/PsychicHttp/src/PsychicWebSocket.h
type std (line 11) | typedef std::function<void(PsychicWebSocketClient *client)> PsychicWebSo...
type std (line 12) | typedef std::function<esp_err_t(PsychicWebSocketRequest *request, httpd_...
function class (line 14) | class PsychicWebSocketClient : public PsychicClient
function class (line 25) | class PsychicWebSocketRequest : public PsychicRequest
function class (line 41) | class PsychicWebSocketHandler : public PsychicHandler {
FILE: lib/PsychicHttp/src/TemplatePrinter.h
type std (line 21) | typedef std::function<bool(Print &output, const char *parameter)> Templa...
type std (line 22) | typedef std::function<void(TemplatePrinter &printer)> TemplateSourceCall...
function class (line 24) | class TemplatePrinter : public Print{
FILE: lib/PsychicHttp/src/http_status.cpp
function http_informational (line 3) | bool http_informational(int code)
function http_success (line 8) | bool http_success(int code)
function http_redirection (line 13) | bool http_redirection(int code)
function http_client_error (line 18) | bool http_client_error(int code)
function http_server_error (line 23) | bool http_server_error(int code)
function http_failure (line 28) | bool http_failure(int code)
FILE: lib/framework/APSettingsService.cpp
function APNetworkStatus (line 134) | APNetworkStatus APSettingsService::getAPNetworkStatus()
FILE: lib/framework/APSettingsService.h
type APNetworkStatus (line 73) | enum APNetworkStatus
function class (line 80) | class APSettings
function class (line 146) | class APSettingsService : public StatefulService<APSettings>
FILE: lib/framework/APStatus.cpp
function esp_err_t (line 34) | esp_err_t APStatus::apStatus(PsychicRequest *request)
FILE: lib/framework/APStatus.h
function class (line 28) | class APStatus
FILE: lib/framework/AnalyticsService.h
function class (line 25) | class AnalyticsService
FILE: lib/framework/ArduinoJsonJWT.cpp
function String (line 26) | String ArduinoJsonJWT::getSecret()
function String (line 38) | String ArduinoJsonJWT::sign(String &payload)
function String (line 54) | String ArduinoJsonJWT::buildJWT(JsonObject &payload)
function String (line 108) | String ArduinoJsonJWT::encode(const char *cstr, int inputLen)
function String (line 143) | String ArduinoJsonJWT::decode(String value)
FILE: lib/framework/ArduinoJsonJWT.h
function class (line 24) | class ArduinoJsonJWT
FILE: lib/framework/AuthenticationService.h
function class (line 27) | class AuthenticationService
FILE: lib/framework/BatteryService.cpp
function boolean (line 32) | boolean BatteryService::isCharging()
FILE: lib/framework/BatteryService.h
function class (line 21) | class BatteryService
FILE: lib/framework/CoreDump.cpp
function esp_err_t (line 40) | esp_err_t CoreDump::coreDump(PsychicRequest *request)
FILE: lib/framework/CoreDump.h
function class (line 26) | class CoreDump
FILE: lib/framework/DownloadFirmwareService.cpp
function update_started (line 63) | void update_started()
function update_progress (line 78) | void update_progress(int currentBytes, int totalBytes)
function update_finished (line 94) | void update_finished()
function updateTask (line 110) | void updateTask(void *param)
function esp_err_t (line 212) | esp_err_t DownloadFirmwareService::downloadUpdate(PsychicRequest *reques...
FILE: lib/framework/DownloadFirmwareService.h
function class (line 32) | class DownloadFirmwareService
FILE: lib/framework/ESP32SvelteKit.h
type std (line 76) | typedef std::function<void()> loopCallback;
type class (line 79) | enum class
function class (line 89) | class ESP32SvelteKit
FILE: lib/framework/EthernetSettingsService.cpp
function String (line 59) | String EthernetSettingsService::getHostname()
function String (line 64) | String EthernetSettingsService::getIP()
FILE: lib/framework/EthernetSettingsService.h
type ethernet_settings_t (line 44) | typedef struct
function class (line 55) | class EthernetSettings
function class (line 104) | class EthernetSettingsService : public StatefulService<EthernetSettings>
FILE: lib/framework/EthernetStatus.cpp
function esp_err_t (line 64) | esp_err_t EthernetStatus::ethernetStatus(PsychicRequest *request)
FILE: lib/framework/EthernetStatus.h
function class (line 30) | class EthernetStatus
FILE: lib/framework/EventEndpoint.h
function begin (line 41) | void begin()
function updateState (line 56) | void updateState(JsonObject &root, int originId)
FILE: lib/framework/EventSocket.h
type std (line 27) | typedef std::function<void(JsonObject &root, int originId)> EventCallback;
type std (line 28) | typedef std::function<void(const String &originId)> SubscribeCallback;
function class (line 30) | class EventSocket
FILE: lib/framework/FSPersistence.h
function readFromFS (line 39) | void readFromFS()
function writeToFS (line 64) | bool writeToFS()
function disableUpdateHandler (line 89) | void disableUpdateHandler()
function enableUpdateHandler (line 98) | void enableUpdateHandler()
function mkdirs (line 117) | void mkdirs()
FILE: lib/framework/FactoryResetService.cpp
function esp_err_t (line 36) | esp_err_t FactoryResetService::handleRequest(PsychicRequest *request)
FILE: lib/framework/FactoryResetService.h
function class (line 28) | class FactoryResetService
FILE: lib/framework/FeaturesService.h
type UserFeature (line 29) | typedef struct
function class (line 35) | class FeaturesService
FILE: lib/framework/HttpEndpoint.h
function begin (line 46) | void begin()
FILE: lib/framework/IPUtils.h
function class (line 22) | class IPUtils
FILE: lib/framework/JsonUtils.h
function class (line 22) | class JsonUtils
function readIP (line 35) | static void readIP(JsonObject &root, const String &key, IPAddress &ip, c...
function writeIP (line 43) | static void writeIP(JsonObject &root, const String &key, const IPAddress...
FILE: lib/framework/MqttEndpoint.h
function class (line 26) | class MqttCommitHandler
function configureTopics (line 116) | void configureTopics(const String &pubTopic, const String &subTopic)
function setSubTopic (line 122) | void setSubTopic(const String &subTopic)
function setPubTopic (line 137) | void setPubTopic(const String &pubTopic)
function setRetain (line 143) | void setRetain(const bool retain)
function commit (line 149) | void commit() override
function publish (line 172) | void publish()
function PsychicMqttClient (line 181) | PsychicMqttClient *getMqttClient()
function onMqttMessage (line 197) | void onMqttMessage(char *topic,
function onConnect (line 219) | void onConnect()
function subscribe (line 225) | void subscribe()
FILE: lib/framework/MqttSettingsService.cpp
function PsychicMqttClient (line 119) | PsychicMqttClient *MqttSettingsService::getMqttClient()
function String (line 124) | String MqttSettingsService::getLastError()
function String (line 235) | String MqttSettingsService::getStatusTopic()
FILE: lib/framework/MqttSettingsService.h
function class (line 75) | class MqttSettings
function class (line 123) | class MqttSettingsService : public StatefulService<MqttSettings>
FILE: lib/framework/MqttStatus.cpp
function esp_err_t (line 35) | esp_err_t MqttStatus::mqttStatus(PsychicRequest *request)
FILE: lib/framework/MqttStatus.h
function class (line 27) | class MqttStatus
FILE: lib/framework/NTPSettingsService.cpp
function esp_err_t (line 113) | esp_err_t NTPSettingsService::configureTime(PsychicRequest *request, Jso...
FILE: lib/framework/NTPSettingsService.h
function class (line 50) | class NTPSettings
function class (line 76) | class NTPSettingsService : public StatefulService<NTPSettings>
FILE: lib/framework/NTPStatus.cpp
function String (line 37) | String formatTime(tm *time, const char *format)
function String (line 44) | String toUTCTimeString(tm *time)
function String (line 49) | String toLocalTimeString(tm *time)
function esp_err_t (line 54) | esp_err_t NTPStatus::ntpStatus(PsychicRequest *request)
FILE: lib/framework/NTPStatus.h
function class (line 28) | class NTPStatus
FILE: lib/framework/NotificationService.h
type pushType (line 22) | enum pushType
function class (line 30) | class NotificationService
FILE: lib/framework/RestartService.cpp
function esp_err_t (line 32) | esp_err_t RestartService::restart(PsychicRequest *request)
FILE: lib/framework/RestartService.h
function class (line 26) | class RestartService
FILE: lib/framework/SecurityManager.h
function class (line 44) | class Authentication
type std (line 63) | typedef std::function<boolean(Authentication &authentication)> Authentic...
function class (line 65) | class AuthenticationPredicates
function class (line 82) | class SecurityManager
FILE: lib/framework/SecuritySettingsService.cpp
function Authentication (line 43) | Authentication SecuritySettingsService::authenticateRequest(PsychicReque...
function Authentication (line 70) | Authentication SecuritySettingsService::authenticateJWT(String &jwt)
function Authentication (line 89) | Authentication SecuritySettingsService::authenticate(const String &usern...
function populateJWTPayload (line 101) | inline void populateJWTPayload(JsonObject &payload, User *user)
function boolean (line 107) | boolean SecuritySettingsService::validatePayload(JsonObject &parsedPaylo...
function String (line 115) | String SecuritySettingsService::generateJWT(User *user)
function PsychicRequestFilterFunction (line 123) | PsychicRequestFilterFunction SecuritySettingsService::filterRequest(Auth...
function PsychicHttpRequestCallback (line 149) | PsychicHttpRequestCallback SecuritySettingsService::wrapRequest(PsychicH...
function PsychicJsonRequestCallback (line 162) | PsychicJsonRequestCallback SecuritySettingsService::wrapCallback(Psychic...
function esp_err_t (line 175) | esp_err_t SecuritySettingsService::generateToken(PsychicRequest *request)
function PsychicRequestFilterFunction (line 202) | PsychicRequestFilterFunction SecuritySettingsService::filterRequest(Auth...
function Authentication (line 212) | Authentication SecuritySettingsService::authenticateRequest(PsychicReque...
function PsychicHttpRequestCallback (line 218) | PsychicHttpRequestCallback SecuritySettingsService::wrapRequest(PsychicH...
function PsychicJsonRequestCallback (line 224) | PsychicJsonRequestCallback SecuritySettingsService::wrapCallback(Psychic...
FILE: lib/framework/SettingValue.cpp
type SettingValue (line 17) | namespace SettingValue
function String (line 25) | String replaceEach(String value, String pattern, String (*generateRepl...
function String (line 42) | String getRandom()
function String (line 50) | String getUniqueId()
function String (line 59) | String format(String value)
FILE: lib/framework/SettingValue.h
function namespace (line 24) | namespace SettingValue
FILE: lib/framework/SleepService.cpp
function esp_err_t (line 50) | esp_err_t SleepService::sleep(PsychicRequest *request)
FILE: lib/framework/SleepService.h
function pinTermination (line 34) | enum class pinTermination
FILE: lib/framework/StatefulService.h
function StateUpdateResult (line 26) | enum class StateUpdateResult
function removeHookHandler (line 108) | void removeHookHandler(hook_handler_id_t id)
function StateUpdateResult (line 123) | StateUpdateResult update(std::function<StateUpdateResult(T &)> stateUpda...
function StateUpdateResult (line 136) | StateUpdateResult updateWithoutPropagation(std::function<StateUpdateResu...
function StateUpdateResult (line 144) | StateUpdateResult update(JsonObject &jsonObject, JsonStateUpdater<T> sta...
function StateUpdateResult (line 157) | StateUpdateResult updateWithoutPropagation(JsonObject &jsonObject, JsonS...
function read (line 165) | void read(std::function<void(T &)> stateReader)
function read (line 172) | void read(JsonObject &jsonObject, JsonStateReader<T> stateReader)
FILE: lib/framework/SystemStatus.cpp
function String (line 45) | String verbosePrintResetReason(int reason)
function esp_err_t (line 131) | esp_err_t SystemStatus::systemStatus(PsychicRequest *request)
FILE: lib/framework/SystemStatus.h
function class (line 27) | class SystemStatus
FILE: lib/framework/UploadFirmwareService.cpp
function esp_err_t (line 112) | esp_err_t UploadFirmwareService::handleUpload(PsychicRequest *request,
function esp_err_t (line 257) | esp_err_t UploadFirmwareService::uploadComplete(PsychicRequest *request)
function esp_err_t (line 331) | esp_err_t UploadFirmwareService::handleError(PsychicRequest *request, in...
function esp_err_t (line 379) | esp_err_t UploadFirmwareService::handleEarlyDisconnect()
FILE: lib/framework/UploadFirmwareService.h
type FileType (line 49) | enum FileType
function class (line 62) | class UploadFirmwareService
FILE: lib/framework/WebSocketServer.h
function begin (line 49) | void begin()
function onWSOpen (line 67) | void onWSOpen(PsychicWebSocketClient *client)
function onWSClose (line 76) | void onWSClose(PsychicWebSocketClient *client)
function esp_err_t (line 81) | esp_err_t onWSFrame(PsychicWebSocketRequest *request, httpd_ws_frame *fr...
function String (line 102) | String clientId(PsychicWebSocketClient *client)
function transmitId (line 117) | void transmitId(PsychicWebSocketClient *client)
function transmitData (line 137) | void transmitData(PsychicWebSocketClient *client, const String &originId)
FILE: lib/framework/WiFiScanner.cpp
function esp_err_t (line 40) | esp_err_t WiFiScanner::scanNetworks(PsychicRequest *request)
function esp_err_t (line 50) | esp_err_t WiFiScanner::listNetworks(PsychicRequest *request)
FILE: lib/framework/WiFiScanner.h
function class (line 27) | class WiFiScanner
FILE: lib/framework/WiFiSettingsService.cpp
function String (line 131) | String WiFiSettingsService::getHostname()
function String (line 136) | String WiFiSettingsService::getIP()
FILE: lib/framework/WiFiSettingsService.h
type wifi_settings_t (line 57) | typedef struct
type class (line 72) | enum class
function class (line 79) | class WiFiSettings
function class (line 212) | class WiFiSettingsService : public StatefulService<WiFiSettings>
FILE: lib/framework/WiFiStatus.cpp
function esp_err_t (line 64) | esp_err_t WiFiStatus::wifiStatus(PsychicRequest *request)
FILE: lib/framework/WiFiStatus.h
function class (line 27) | class WiFiStatus
FILE: scripts/build_interface.py
function find_latest_timestamp_for_app (line 47) | def find_latest_timestamp_for_app():
function should_regenerate_output_file (line 53) | def should_regenerate_output_file():
function gzip_file (line 66) | def gzip_file(file):
function flag_exists (line 73) | def flag_exists(flag):
function get_package_manager (line 80) | def get_package_manager():
function build_webapp (line 89) | def build_webapp():
function embed_webapp (line 98) | def embed_webapp():
function build_progmem (line 106) | def build_progmem():
function add_app_to_filesystem (line 156) | def add_app_to_filesystem():
FILE: scripts/generate_cert_bundle.py
function download_cacert_file (line 51) | def download_cacert_file(source):
function status (line 77) | def status(msg):
function critical (line 83) | def critical(msg):
class CertificateBundle (line 90) | class CertificateBundle:
method __init__ (line 91) | def __init__(self):
method add_from_path (line 98) | def add_from_path(self, crts_path):
method add_from_file (line 107) | def add_from_file(self, file_path):
method add_from_pem (line 129) | def add_from_pem(self, crt_str):
method add_from_der (line 161) | def add_from_der(self, crt_str):
method create_bundle (line 165) | def create_bundle(self):
class InputError (line 189) | class InputError(RuntimeError):
method __init__ (line 190) | def __init__(self, e):
function main (line 194) | def main():
FILE: scripts/merge_bin.py
function readFlag (line 10) | def readFlag(flag):
function merge_bin (line 22) | def merge_bin(source, target, env):
FILE: scripts/prebuild_utils.py
function is_build_task (line 10) | def is_build_task(required_tasks):
FILE: scripts/rename_fw.py
function readFlag (line 27) | def readFlag(flag):
function bin_copy (line 39) | def bin_copy(source, target, env):
FILE: scripts/save_elf.py
function elf_copy (line 10) | def elf_copy(source, target, env):
FILE: src/LightMqttSettingsService.h
function class (line 33) | class LightMqttSettings
function class (line 59) | class LightMqttSettingsService : public StatefulService<LightMqttSettings>
FILE: src/LightStateService.h
function class (line 35) | class LightState
function class (line 84) | class LightStateService : public StatefulService<LightState>
FILE: src/main.cpp
function setup (line 33) | void setup()
function loop (line 47) | void loop()
Condensed preview — 231 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (888K chars).
[
{
"path": ".github/workflows/ci.yaml",
"chars": 581,
"preview": "name: ci\non:\n push:\n branches:\n - master\n - main\npermissions:\n contents: write\njobs:\n deploy:\n runs-o"
},
{
"path": ".gitignore",
"chars": 307,
"preview": ".pio\n.clang_complete\n.gcc-flags.json\n*Thumbs.db\n/data/www\n/interface/build\n/interface/node_modules\n/interface/.eslintcac"
},
{
"path": "CHANGELOG.md",
"chars": 22406,
"preview": "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\n## [WIP] - Next Release\n\n### Added\n\n-"
},
{
"path": "ESP32-sveltekit.code-workspace",
"chars": 1417,
"preview": "{\n \"folders\": [\n {\n \"path\": \".\"\n }\n ],\n \"settings\": {\n \"files.associations\": {\n \"*.tcc\": \"cpp\",\n "
},
{
"path": "LICENSE",
"chars": 8826,
"preview": "ESP32 SvelteKit is distributed with two licenses for different sections of the\ncode. The back end code inherits the GNU "
},
{
"path": "README.md",
"chars": 5638,
"preview": "# ESP32 SvelteKit - Create Amazing IoT Projects\n\n<div style=\"flex\">\n<img src=\"/docs/media/Screenshot_light.png\" style=\"h"
},
{
"path": "docs/buildprocess.md",
"chars": 13122,
"preview": "# Build Process\n\nThe build process is controlled by [platformio.ini](https://github.com/theelims/ESP32-sveltekit/platfor"
},
{
"path": "docs/components.md",
"chars": 7250,
"preview": "# Components\n\nThe project includes a number of components to create the user interface. Even though DaisyUI has a huge s"
},
{
"path": "docs/gettingstarted.md",
"chars": 7688,
"preview": "# Getting Started\n\n## Prerequisites\n\nThis project has quite a complicated build chain to prepare the frontend code for t"
},
{
"path": "docs/index.md",
"chars": 5082,
"preview": "---\nhide:\n - navigation\n - toc\n---\n\n# ESP32 SvelteKit - Create Amazing IoT Projects\n\n<div style=\"flex\">\n<img src=\"medi"
},
{
"path": "docs/restfulapi.md",
"chars": 11501,
"preview": "# RESTful API\n\nThe back end exposes a number of API endpoints which are referenced in the table below.\n\n| Method | Reque"
},
{
"path": "docs/statefulservice.md",
"chars": 31381,
"preview": "# Developing with the Framework\n\nThe back end is a set of REST endpoints hosted by a [PsychicHttp](https://github.com/ho"
},
{
"path": "docs/stores.md",
"chars": 4794,
"preview": "# Stores\n\n## User\n\nThe user store holds the current users credentials, if the security features are enabled. Just import"
},
{
"path": "docs/structure.md",
"chars": 8143,
"preview": "# Customizing the Front End\n\nThe actual code for the front end is located under [interface/src/](https://github.com/thee"
},
{
"path": "docs/sveltekit.md",
"chars": 4637,
"preview": "# Getting Started with SvelteKit\n\nSvelteKits unique approach makes it perfect suitable for constraint server. It builds "
},
{
"path": "factory_settings.ini",
"chars": 2170,
"preview": "; The indicated settings support placeholder substitution as follows:\n;\n; #{platform} - The microcontroller platform, e"
},
{
"path": "features.ini",
"chars": 335,
"preview": "[features]\nbuild_flags = \n -D FT_SECURITY=1\n -D FT_MQTT=1\n -D FT_NTP=1\n -D FT_UPLOAD_FIRMWARE=1\n -D FT_DOWNLOAD_FIR"
},
{
"path": "interface/.eslintignore",
"chars": 160,
"preview": ".DS_Store\nnode_modules\n/build\n/.svelte-kit\n/package\n.env\n.env.*\n!.env.example\n\n# Ignore files for PNPM, NPM and YARN\npnp"
},
{
"path": "interface/.eslintrc.cjs",
"chars": 494,
"preview": "module.exports = {\n\troot: true,\n\tparser: '@typescript-eslint/parser',\n\textends: ['eslint:recommended', 'plugin:@typescri"
},
{
"path": "interface/.gitignore",
"chars": 132,
"preview": ".DS_Store\nnode_modules\n/build\n/.svelte-kit\n/package\n.env\n.env.*\n!.env.example\nvite.config.js.timestamp-*\nvite.config.ts."
},
{
"path": "interface/.npmrc",
"chars": 19,
"preview": "engine-strict=true\n"
},
{
"path": "interface/.prettierignore",
"chars": 160,
"preview": ".DS_Store\nnode_modules\n/build\n/.svelte-kit\n/package\n.env\n.env.*\n!.env.example\n\n# Ignore files for PNPM, NPM and YARN\npnp"
},
{
"path": "interface/.prettierrc",
"chars": 233,
"preview": "{\n\t\"useTabs\": true,\n\t\"singleQuote\": true,\n\t\"trailingComma\": \"none\",\n\t\"printWidth\": 100,\n\t\"plugins\": [\"prettier-plugin-sv"
},
{
"path": "interface/LICENSE",
"chars": 1333,
"preview": "ESP32-SvelteKit is distributed with two licenses for different sections of the\ncode. The back end code inherits the GNU "
},
{
"path": "interface/package.json",
"chars": 1533,
"preview": "{\n\t\"name\": \"ESP32-Sveltekit Template\",\n\t\"version\": \"0.2.0\",\n\t\"private\": true,\n\t\"scripts\": {\n\t\t\"dev\": \"vite dev --host\",\n"
},
{
"path": "interface/src/app.css",
"chars": 593,
"preview": "@import \"tailwindcss\";\n@plugin \"daisyui\" {\n themes: corporate --default, business --prefersdark;\n}\n\n/*\n The default "
},
{
"path": "interface/src/app.d.ts",
"chars": 337,
"preview": "// See https://kit.svelte.dev/docs/types#app\n// for information about these interfaces\n\n/// <reference types=\"@sveltejs/"
},
{
"path": "interface/src/app.html",
"chars": 329,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n\t<head>\n\t\t<meta charset=\"utf-8\" />\n\t\t<link rel=\"icon\" href=\"%sveltekit.assets%/favicon."
},
{
"path": "interface/src/lib/DaisyUiHelper.ts",
"chars": 822,
"preview": "export function daisyColor(name: string, opacity: number = 100) {\n\tconst color = getComputedStyle(document.documentEleme"
},
{
"path": "interface/src/lib/components/BatteryIndicator.svelte",
"chars": 921,
"preview": "<script lang=\"ts\">\n\timport Battery0 from '~icons/tabler/battery';\n\timport Battery25 from '~icons/tabler/battery-1';\n\timp"
},
{
"path": "interface/src/lib/components/Collapsible.svelte",
"chars": 1741,
"preview": "<script lang=\"ts\">\n\timport { slide } from 'svelte/transition';\n\timport { cubicOut } from 'svelte/easing';\n\timport Down f"
},
{
"path": "interface/src/lib/components/ConfirmDialog.svelte",
"chars": 1705,
"preview": "<script lang=\"ts\">\n\timport { modals } from 'svelte-modals';\n\timport { focusTrap } from 'svelte-focus-trap';\n\timport { fl"
},
{
"path": "interface/src/lib/components/DraggableList.svelte",
"chars": 2192,
"preview": "<script lang=\"ts\">\n\timport { dndzone } from 'svelte-dnd-action';\n\timport type { Snippet } from 'svelte';\n\n\tinterface Pro"
},
{
"path": "interface/src/lib/components/FirmwareUpdateDialog.svelte",
"chars": 5208,
"preview": "<script lang=\"ts\">\n\timport { modals, onBeforeClose } from 'svelte-modals';\n\timport { focusTrap } from 'svelte-focus-trap"
},
{
"path": "interface/src/lib/components/InfoDialog.svelte",
"chars": 1266,
"preview": "<script lang=\"ts\">\n\timport { modals } from 'svelte-modals';\n\timport { focusTrap } from 'svelte-focus-trap';\n\timport { fl"
},
{
"path": "interface/src/lib/components/InputPassword.svelte",
"chars": 1944,
"preview": "<script lang=\"ts\">\n\tlet show = $state(false);\n\tlet type = $derived(show ? 'text' : 'password');\n\n\tinterface Props {\n\t\tva"
},
{
"path": "interface/src/lib/components/RSSIIndicator.svelte",
"chars": 1212,
"preview": "<script lang=\"ts\">\n\timport WiFi from '~icons/tabler/wifi';\n\timport WiFi0 from '~icons/tabler/wifi-0';\n\timport WiFi1 from"
},
{
"path": "interface/src/lib/components/SettingsCard.svelte",
"chars": 2423,
"preview": "<script lang=\"ts\">\n\timport { slide } from 'svelte/transition';\n\timport { cubicOut } from 'svelte/easing';\n\timport Down f"
},
{
"path": "interface/src/lib/components/Spinner.svelte",
"chars": 290,
"preview": "<script lang=\"ts\">\n\timport Loader from '~icons/tabler/loader-2';\n\n\tlet { text = \"Loading...\"} = $props();\n\n</script>\n\n<d"
},
{
"path": "interface/src/lib/components/UpdateIndicator.svelte",
"chars": 3583,
"preview": "<script lang=\"ts\">\n\timport { page } from '$app/state';\n\timport { modals } from 'svelte-modals';\n\timport type { ModalComp"
},
{
"path": "interface/src/lib/components/toasts/Toast.svelte",
"chars": 1097,
"preview": "<script>\n\timport { flip } from 'svelte/animate';\n\timport { fly } from 'svelte/transition';\n\timport { notifications } fro"
},
{
"path": "interface/src/lib/components/toasts/notifications.ts",
"chars": 1127,
"preview": "import { writable, derived, type Writable } from 'svelte/store';\n\ntype StateType = 'info' | 'success' | 'warning' | 'err"
},
{
"path": "interface/src/lib/stores/analytics.ts",
"chars": 1998,
"preview": "import { type Analytics } from '$lib/types/models';\nimport { writable } from 'svelte/store';\n\nlet analytics_data = {\n\tup"
},
{
"path": "interface/src/lib/stores/battery.ts",
"chars": 818,
"preview": "import { type Battery } from '$lib/types/models';\nimport { writable } from 'svelte/store';\n\nlet battery_history = {\n "
},
{
"path": "interface/src/lib/stores/socket.ts",
"chars": 3609,
"preview": "import { writable } from 'svelte/store';\nimport msgpack from 'msgpack-lite';\n\nfunction createWebSocket() {\n\tlet listener"
},
{
"path": "interface/src/lib/stores/telemetry.ts",
"chars": 1635,
"preview": "import { writable } from 'svelte/store';\nimport type { RSSI } from '../types/models';\nimport type { Battery } from '../t"
},
{
"path": "interface/src/lib/stores/user.ts",
"chars": 1169,
"preview": "import { writable } from 'svelte/store';\nimport { goto } from '$app/navigation';\nimport { jwtDecode } from 'jwt-decode';"
},
{
"path": "interface/src/lib/types/models.ts",
"chars": 3141,
"preview": "export type WifiStatus = {\n\tstatus: number;\n\tlocal_ip: string;\n\tmac_address: string;\n\trssi: number;\n\tssid: string;\n\tbssi"
},
{
"path": "interface/src/routes/+error.svelte",
"chars": 615,
"preview": "<script lang=\"ts\">\n\timport { goto } from '$app/navigation';\n\timport { page } from '$app/state';\n\n\tif (page.status == 404"
},
{
"path": "interface/src/routes/+layout.svelte",
"chars": 5251,
"preview": "<script lang=\"ts\">\n\timport type { LayoutData } from './$types';\n\timport { onDestroy, onMount } from 'svelte';\n\timport { "
},
{
"path": "interface/src/routes/+layout.ts",
"chars": 476,
"preview": "import type { LayoutLoad } from './$types';\n\n// This can be false if you're using a fallback (i.e. SPA mode)\nexport cons"
},
{
"path": "interface/src/routes/+page.svelte",
"chars": 1267,
"preview": "<script lang=\"ts\">\n\timport type { PageData } from './$types';\n\timport logo from '$lib/assets/logo.png';\n\timport { notifi"
},
{
"path": "interface/src/routes/connections/+page.ts",
"chars": 162,
"preview": "import type { PageLoad } from './$types';\nimport { goto } from '$app/navigation';\n\nexport const load = (async () => {\n\tg"
},
{
"path": "interface/src/routes/connections/mqtt/+page.svelte",
"chars": 335,
"preview": "<script lang=\"ts\">\n\timport type { PageData } from '../$types';\n\timport MQTT from './MQTT.svelte';\n\timport MqttConfig fro"
},
{
"path": "interface/src/routes/connections/mqtt/+page.ts",
"chars": 142,
"preview": "import type { PageLoad } from './$types';\n\nexport const load = (async () => {\n return {\n title: \"MQTT\"\n };\n"
},
{
"path": "interface/src/routes/connections/mqtt/MQTT.svelte",
"chars": 9090,
"preview": "<script lang=\"ts\">\n\timport { onMount, onDestroy } from 'svelte';\n\timport { slide } from 'svelte/transition';\n\timport { c"
},
{
"path": "interface/src/routes/connections/mqtt/MQTTConfig.svelte",
"chars": 6031,
"preview": "<script lang=\"ts\">\n\timport { slide } from 'svelte/transition';\n\timport { cubicOut } from 'svelte/easing';\n\timport Settin"
},
{
"path": "interface/src/routes/connections/ntp/+page.svelte",
"chars": 269,
"preview": "<script lang=\"ts\">\n\timport type { PageData } from '../$types';\n\timport NTP from './NTP.svelte';\n\n\tinterface Props {\n\t\tda"
},
{
"path": "interface/src/routes/connections/ntp/+page.ts",
"chars": 130,
"preview": "import type { PageLoad } from './$types';\n\nexport const load = (async () => {\n\treturn {\n\t\ttitle: 'NTP'\n\t};\n}) satisfies "
},
{
"path": "interface/src/routes/connections/ntp/NTP.svelte",
"chars": 8358,
"preview": "<script lang=\"ts\">\n\timport { onMount, onDestroy } from 'svelte';\n\timport { slide } from 'svelte/transition';\n\timport { c"
},
{
"path": "interface/src/routes/connections/ntp/timezones.ts",
"chars": 17690,
"preview": "export type TimeZones = {\n [name: string]: string\n };\n \nexport const TIME_ZONES: TimeZones = {\n \"Africa/Abidjan\""
},
{
"path": "interface/src/routes/demo/+page.svelte",
"chars": 272,
"preview": "<script lang=\"ts\">\n\timport type { PageData } from '../$types';\n\timport Demo from './Demo.svelte';\n\n\tinterface Props {\n\t\t"
},
{
"path": "interface/src/routes/demo/+page.ts",
"chars": 144,
"preview": "import type { PageLoad } from './$types';\n\nexport const load = (async ({ fetch }) => {\n\treturn {\n\t\ttitle: 'Demo App'\n\t};"
},
{
"path": "interface/src/routes/demo/Demo.svelte",
"chars": 3685,
"preview": "<script lang=\"ts\">\n\timport { onMount, onDestroy } from 'svelte';\n\timport { user } from '$lib/stores/user';\n\timport { pag"
},
{
"path": "interface/src/routes/ethernet/+page.svelte",
"chars": 283,
"preview": "<script lang=\"ts\">\n\timport type { PageData } from './$types';\n\timport Ethernet from './Ethernet.svelte';\n\n\tinterface Pro"
},
{
"path": "interface/src/routes/ethernet/+page.ts",
"chars": 135,
"preview": "import type { PageLoad } from './$types';\n\nexport const load = (async () => {\n\treturn {\n\t\ttitle: 'Ethernet'\n\t};\n}) satis"
},
{
"path": "interface/src/routes/ethernet/Ethernet.svelte",
"chars": 15858,
"preview": "<script lang=\"ts\">\n\timport { onDestroy, onMount } from 'svelte';\n\timport { socket } from '$lib/stores/socket';\n\timport {"
},
{
"path": "interface/src/routes/login.svelte",
"chars": 3083,
"preview": "<script lang=\"ts\">\n\timport logo from '$lib/assets/logo.png';\n\timport InputPassword from '$lib/components/InputPassword.s"
},
{
"path": "interface/src/routes/menu.svelte",
"chars": 6317,
"preview": "<script lang=\"ts\">\n\timport logo from '$lib/assets/logo.png';\n\timport Github from '~icons/tabler/brand-github';\n\timport D"
},
{
"path": "interface/src/routes/statusbar.svelte",
"chars": 2719,
"preview": "<script lang=\"ts\">\n\timport { page } from '$app/state';\n\timport { telemetry } from '$lib/stores/telemetry';\n\timport { mod"
},
{
"path": "interface/src/routes/system/+page.ts",
"chars": 162,
"preview": "import type { PageLoad } from './$types';\nimport { goto } from '$app/navigation';\n\nexport const load = (async () => {\n\tg"
},
{
"path": "interface/src/routes/system/coredump/+page.svelte",
"chars": 2034,
"preview": "<script lang=\"ts\">\n\timport { onMount } from 'svelte';\n\timport { page } from '$app/state';\n\timport { user } from '$lib/st"
},
{
"path": "interface/src/routes/system/coredump/+page.ts",
"chars": 133,
"preview": "import type { PageLoad } from './$types';\n\nexport const load = (async () => {\n\treturn { title: 'Core Dump' };\n}) satisfi"
},
{
"path": "interface/src/routes/system/metrics/+page.svelte",
"chars": 664,
"preview": "<script lang=\"ts\">\n\timport type { PageData } from './$types';\n\timport SystemMetrics from './SystemMetrics.svelte';\n\timpo"
},
{
"path": "interface/src/routes/system/metrics/+page.ts",
"chars": 138,
"preview": "import type { PageLoad } from './$types';\n\nexport const load = (async () => {\n\treturn { title: 'System Metrics' };\n}) sa"
},
{
"path": "interface/src/routes/system/metrics/BatteryMetrics.svelte",
"chars": 3903,
"preview": "<script lang=\"ts\">\n\timport { onDestroy, onMount } from 'svelte';\n\timport { page } from '$app/state';\n\timport SettingsCar"
},
{
"path": "interface/src/routes/system/metrics/SystemMetrics.svelte",
"chars": 9639,
"preview": "<script lang=\"ts\">\n\timport { onMount } from 'svelte';\n\timport SettingsCard from '$lib/components/SettingsCard.svelte';\n\t"
},
{
"path": "interface/src/routes/system/status/+page.svelte",
"chars": 373,
"preview": "<script lang=\"ts\">\n\timport type { PageData } from './$types';\n\timport SystemStatus from './SystemStatus.svelte';\n\timport"
},
{
"path": "interface/src/routes/system/status/+page.ts",
"chars": 137,
"preview": "import type { PageLoad } from './$types';\n\nexport const load = (async () => {\n\treturn { title: 'System Status' };\n}) sat"
},
{
"path": "interface/src/routes/system/status/SystemStatus.svelte",
"chars": 12381,
"preview": "<script lang=\"ts\">\n\timport { onDestroy, onMount } from 'svelte';\n\timport { modals } from 'svelte-modals';\n\timport { user"
},
{
"path": "interface/src/routes/system/update/+page.svelte",
"chars": 676,
"preview": "<script lang=\"ts\">\n\timport type { PageData } from './$types';\n\timport UploadFirmware from './UploadFirmware.svelte';\n\tim"
},
{
"path": "interface/src/routes/system/update/+page.ts",
"chars": 139,
"preview": "import type { PageLoad } from './$types';\n\nexport const load = (async () => {\n\treturn { title: 'Firmware Update' };\n}) s"
},
{
"path": "interface/src/routes/system/update/GithubFirmwareManager.svelte",
"chars": 5699,
"preview": "<script lang=\"ts\">\n\timport { user } from '$lib/stores/user';\n\timport { page } from '$app/state';\n\timport { modals } from"
},
{
"path": "interface/src/routes/system/update/UploadFirmware.svelte",
"chars": 7730,
"preview": "<script lang=\"ts\">\n\timport { modals } from 'svelte-modals';\n\timport type { ModalComponent } from 'svelte-modals';\n\timpor"
},
{
"path": "interface/src/routes/user/+page.svelte",
"chars": 6589,
"preview": "<script lang=\"ts\">\n\timport type { PageData } from './$types';\n\timport { onMount } from 'svelte';\n\timport { goto } from '"
},
{
"path": "interface/src/routes/user/+page.ts",
"chars": 143,
"preview": "import type { PageLoad } from './$types';\n\nexport const load = (async () => {\n return {\n title: 'Users'\n };"
},
{
"path": "interface/src/routes/user/EditUser.svelte",
"chars": 3139,
"preview": "<script lang=\"ts\">\n\timport { onMount, onDestroy } from 'svelte';\n\timport { modals } from 'svelte-modals';\n\timport { fly "
},
{
"path": "interface/src/routes/wifi/+page.ts",
"chars": 162,
"preview": "import type { PageLoad } from './$types';\nimport { goto } from '$app/navigation';\n\nexport const load = (async () => {\n\tg"
},
{
"path": "interface/src/routes/wifi/ap/+page.svelte",
"chars": 292,
"preview": "<script lang=\"ts\">\n\timport type { PageData } from './$types';\n\timport Accesspoint from './Accesspoint.svelte';\n\n\tinterfa"
},
{
"path": "interface/src/routes/wifi/ap/+page.ts",
"chars": 139,
"preview": "import type { PageLoad } from './$types';\n\nexport const load = (async () => {\n\treturn {\n\t\ttitle: 'Access Point'\n\t};\n}) s"
},
{
"path": "interface/src/routes/wifi/ap/Accesspoint.svelte",
"chars": 11615,
"preview": "<script lang=\"ts\">\n\timport { onMount, onDestroy } from 'svelte';\n\timport { slide } from 'svelte/transition';\n\timport { c"
},
{
"path": "interface/src/routes/wifi/sta/+page.svelte",
"chars": 271,
"preview": "<script lang=\"ts\">\n\timport type { PageData } from './$types';\n\timport Wifi from './Wifi.svelte';\n\n\tinterface Props {\n\t\td"
},
{
"path": "interface/src/routes/wifi/sta/+page.ts",
"chars": 139,
"preview": "import type { PageLoad } from './$types';\n\nexport const load = (async () => {\n\treturn {\n\t\ttitle: 'WiFi Station'\n\t};\n}) s"
},
{
"path": "interface/src/routes/wifi/sta/EditNetwork.svelte",
"chars": 9186,
"preview": "<script lang=\"ts\">\n\timport { modals } from 'svelte-modals';\n\timport { fly } from 'svelte/transition';\n\timport { slide } "
},
{
"path": "interface/src/routes/wifi/sta/Scan.svelte",
"chars": 4426,
"preview": "<script lang=\"ts\">\n\timport { modals } from 'svelte-modals';\n\timport { focusTrap } from 'svelte-focus-trap';\n\timport { fl"
},
{
"path": "interface/src/routes/wifi/sta/Wifi.svelte",
"chars": 16974,
"preview": "<script lang=\"ts\">\n\timport { onDestroy, onMount } from 'svelte';\n\timport { socket } from '$lib/stores/socket';\n\timport {"
},
{
"path": "interface/static/manifest.json",
"chars": 197,
"preview": "{\n\t\"name\": \"ESP32 SvelteKit\",\n\t\"icons\": [\n\t\t{\n\t\t\t\"src\": \"/favicon.png\",\n\t\t\t\"sizes\": \"48x48 72x72 96x96 128x128 256x256\"\n"
},
{
"path": "interface/svelte.config.js",
"chars": 587,
"preview": "import adapter from '@sveltejs/adapter-static';\nimport { vitePreprocess } from '@sveltejs/vite-plugin-svelte';\n\n/** @typ"
},
{
"path": "interface/tsconfig.json",
"chars": 532,
"preview": "{\n\t\"extends\": \"./.svelte-kit/tsconfig.json\",\n\t\"compilerOptions\": {\n\t\t\"allowJs\": true,\n\t\t\"checkJs\": true,\n\t\t\"esModuleInte"
},
{
"path": "interface/vite-plugin-littlefs.ts",
"chars": 940,
"preview": "import type { UserConfig, Plugin } from 'vite';\n\nexport default function viteLittleFS(): Plugin[] {\n\treturn [\n\t\t{\n\t\t\tnam"
},
{
"path": "interface/vite.config.ts",
"chars": 990,
"preview": "import { sveltekit } from '@sveltejs/kit/vite';\nimport type { UserConfig } from 'vite';\nimport Icons from 'unplugin-icon"
},
{
"path": "lib/PsychicHttp/.gitignore",
"chars": 1025,
"preview": "**.vscode\n**.pio\n**.DS_Store\n.pioenvs\n.clang_complete\n.gcc-flags.json\n# Compiled Object files\n*.slo\n*.lo\n*.o\n*.obj\n# Pre"
},
{
"path": "lib/PsychicHttp/CHANGELOG.md",
"chars": 1548,
"preview": "# v1.2.1\n\n* Fix bug with missing include preventing the HTTPS server from compiling.\n\n# v1.2\n\n* Added TemplatePrinter fr"
},
{
"path": "lib/PsychicHttp/LICENSE",
"chars": 1092,
"preview": "Copyright (c) 2024 Jeremy Poulter, Zachary Smith, and Mathieu Carbou\n\nPermission is hereby granted, free of charge, to a"
},
{
"path": "lib/PsychicHttp/README.md",
"chars": 36005,
"preview": "# PsychicHttp - HTTP on your ESP 🧙🔮\n\nPsychicHttp is a webserver library for ESP32 + Arduino framework which uses the [ES"
},
{
"path": "lib/PsychicHttp/RELEASE.md",
"chars": 233,
"preview": "* Update CHANGELOG\n* Bump version in library.json\n* Bump version in library.properties\n* Make new release + tag\n\t* this "
},
{
"path": "lib/PsychicHttp/library.json",
"chars": 1093,
"preview": "{\n \"name\": \"PsychicHttp\",\n \"version\": \"1.2.1\",\n \"description\": \"Arduino style wrapper around ESP-IDF HTTP library. HT"
},
{
"path": "lib/PsychicHttp/request flow.drawio",
"chars": 4513,
"preview": "<mxfile host=\"Electron\" modified=\"2023-12-09T15:02:09.427Z\" agent=\"5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/5"
},
{
"path": "lib/PsychicHttp/src/ChunkPrinter.cpp",
"chars": 1483,
"preview": "\n#include \"ChunkPrinter.h\"\n\nChunkPrinter::ChunkPrinter(PsychicResponse *response, uint8_t *buffer, size_t len) :\n _resp"
},
{
"path": "lib/PsychicHttp/src/ChunkPrinter.h",
"chars": 520,
"preview": "#ifndef ChunkPrinter_h\n#define ChunkPrinter_h\n\n#include \"PsychicResponse.h\"\n#include <Print.h>\n\nclass ChunkPrinter : pub"
},
{
"path": "lib/PsychicHttp/src/PsychicClient.cpp",
"chars": 1856,
"preview": "#include \"PsychicClient.h\"\n#include \"PsychicHttpServer.h\"\n#include <lwip/sockets.h>\n\nPsychicClient::PsychicClient(httpd_"
},
{
"path": "lib/PsychicHttp/src/PsychicClient.h",
"chars": 709,
"preview": "#ifndef PsychicClient_h\n#define PsychicClient_h\n\n#include \"PsychicCore.h\"\n\n/*\n* PsychicClient :: Generic wrapper around "
},
{
"path": "lib/PsychicHttp/src/PsychicCore.h",
"chars": 2540,
"preview": "#ifndef PsychicCore_h\n#define PsychicCore_h\n\n#define PH_TAG \"🔮\"\n\n// version numbers\n#define PSYCHIC_HTTP_VERSION_MAJOR 1"
},
{
"path": "lib/PsychicHttp/src/PsychicEndpoint.cpp",
"chars": 1909,
"preview": "#include \"PsychicEndpoint.h\"\n#include \"PsychicHttpServer.h\"\n\nPsychicEndpoint::PsychicEndpoint() :\n _server(NULL),\n _ur"
},
{
"path": "lib/PsychicHttp/src/PsychicEndpoint.h",
"chars": 827,
"preview": "#ifndef PsychicEndpoint_h\n#define PsychicEndpoint_h\n\n#include \"PsychicCore.h\"\n\nclass PsychicHandler;\n\nclass PsychicEndpo"
},
{
"path": "lib/PsychicHttp/src/PsychicEventSource.cpp",
"chars": 5862,
"preview": "/*\n Asynchronous WebServer library for Espressif MCUs\n\n Copyright (c) 2016 Hristo Gochkov. All rights reserved.\n\n Thi"
},
{
"path": "lib/PsychicHttp/src/PsychicEventSource.h",
"chars": 2824,
"preview": "/*\n Asynchronous WebServer library for Espressif MCUs\n\n Copyright (c) 2016 Hristo Gochkov. All rights reserved.\n\n Thi"
},
{
"path": "lib/PsychicHttp/src/PsychicFileResponse.cpp",
"chars": 4904,
"preview": "#include \"PsychicFileResponse.h\"\n#include \"PsychicResponse.h\"\n#include \"PsychicRequest.h\"\n\n\nPsychicFileResponse::Psychic"
},
{
"path": "lib/PsychicHttp/src/PsychicFileResponse.h",
"chars": 684,
"preview": "#ifndef PsychicFileResponse_h\n#define PsychicFileResponse_h\n\n#include \"PsychicCore.h\"\n#include \"PsychicResponse.h\"\n\nclas"
},
{
"path": "lib/PsychicHttp/src/PsychicHandler.cpp",
"chars": 2691,
"preview": "#include \"PsychicHandler.h\"\n\nPsychicHandler::PsychicHandler() :\n _filter(NULL),\n _server(NULL),\n _username(\"\"),\n _pa"
},
{
"path": "lib/PsychicHttp/src/PsychicHandler.h",
"chars": 1951,
"preview": "#ifndef PsychicHandler_h\n#define PsychicHandler_h\n\n#include \"PsychicCore.h\"\n#include \"PsychicRequest.h\"\n\nclass PsychicEn"
},
{
"path": "lib/PsychicHttp/src/PsychicHttp.h",
"chars": 469,
"preview": "#ifndef PsychicHttp_h\n#define PsychicHttp_h\n\n#include <http_status.h>\n#include \"PsychicHttpServer.h\"\n#include \"PsychicRe"
},
{
"path": "lib/PsychicHttp/src/PsychicHttpServer.cpp",
"chars": 9188,
"preview": "#include \"PsychicHttpServer.h\"\n#include \"PsychicEndpoint.h\"\n#include \"PsychicHandler.h\"\n#include \"PsychicWebHandler.h\"\n#"
},
{
"path": "lib/PsychicHttp/src/PsychicHttpServer.h",
"chars": 2662,
"preview": "#ifndef PsychicHttpServer_h\n#define PsychicHttpServer_h\n\n#include \"PsychicCore.h\"\n#include \"PsychicClient.h\"\n#include \"P"
},
{
"path": "lib/PsychicHttp/src/PsychicHttpsServer.cpp",
"chars": 1841,
"preview": "#include \"PsychicHttpsServer.h\"\n\n#ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE\n\nPsychicHttpsServer::PsychicHttpsServer() : Psych"
},
{
"path": "lib/PsychicHttp/src/PsychicHttpsServer.h",
"chars": 1028,
"preview": "#ifndef PsychicHttpsServer_h\n#define PsychicHttpsServer_h\n\n#include <sdkconfig.h>\n\n#ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE"
},
{
"path": "lib/PsychicHttp/src/PsychicJson.cpp",
"chars": 3373,
"preview": "#include \"PsychicJson.h\"\n\n#ifdef ARDUINOJSON_6_COMPATIBILITY\n PsychicJsonResponse::PsychicJsonResponse(PsychicRequest *"
},
{
"path": "lib/PsychicHttp/src/PsychicJson.h",
"chars": 2392,
"preview": "// PsychicJson.h\n/*\n Async Response to use with ArduinoJson and AsyncWebServer\n Written by Andrew Melvin (SticilFace) "
},
{
"path": "lib/PsychicHttp/src/PsychicRequest.cpp",
"chars": 16115,
"preview": "#include \"PsychicRequest.h\"\n#include \"http_status.h\"\n#include \"PsychicHttpServer.h\"\n\nPsychicRequest::PsychicRequest(Psyc"
},
{
"path": "lib/PsychicHttp/src/PsychicRequest.h",
"chars": 3367,
"preview": "#ifndef PsychicRequest_h\n#define PsychicRequest_h\n\n#include \"PsychicCore.h\"\n#include \"PsychicHttpServer.h\"\n#include \"Psy"
},
{
"path": "lib/PsychicHttp/src/PsychicResponse.cpp",
"chars": 4369,
"preview": "#include \"PsychicResponse.h\"\n#include \"PsychicRequest.h\"\n#include <http_status.h>\n\nPsychicResponse::PsychicResponse(Psyc"
},
{
"path": "lib/PsychicHttp/src/PsychicResponse.h",
"chars": 1164,
"preview": "#ifndef PsychicResponse_h\n#define PsychicResponse_h\n\n#include \"PsychicCore.h\"\n#include \"time.h\"\n\nclass PsychicRequest;\n\n"
},
{
"path": "lib/PsychicHttp/src/PsychicStaticFileHander.cpp",
"chars": 5908,
"preview": "#include \"PsychicStaticFileHandler.h\"\n\n/*************************************/\n/* PsychicStaticFileHandler */\n/"
},
{
"path": "lib/PsychicHttp/src/PsychicStaticFileHandler.h",
"chars": 1438,
"preview": "#ifndef PsychicStaticFileHandler_h\n#define PsychicStaticFileHandler_h\n\n#include \"PsychicCore.h\"\n#include \"PsychicWebHand"
},
{
"path": "lib/PsychicHttp/src/PsychicStreamResponse.cpp",
"chars": 2053,
"preview": "#include \"PsychicStreamResponse.h\"\n#include \"PsychicResponse.h\"\n#include \"PsychicRequest.h\"\n\nPsychicStreamResponse::Psyc"
},
{
"path": "lib/PsychicHttp/src/PsychicStreamResponse.h",
"chars": 827,
"preview": "#ifndef PsychicStreamResponse_h\n#define PsychicStreamResponse_h\n\n#include \"PsychicCore.h\"\n#include \"PsychicResponse.h\"\n#"
},
{
"path": "lib/PsychicHttp/src/PsychicUploadHandler.cpp",
"chars": 15141,
"preview": "#include \"PsychicUploadHandler.h\"\n\nPsychicUploadHandler::PsychicUploadHandler() : PsychicWebHandler(), _temp(), _parsedL"
},
{
"path": "lib/PsychicHttp/src/PsychicUploadHandler.h",
"chars": 1701,
"preview": "#ifndef PsychicUploadHandler_h\n#define PsychicUploadHandler_h\n\n#include \"PsychicCore.h\"\n#include \"PsychicHttpServer.h\"\n#"
},
{
"path": "lib/PsychicHttp/src/PsychicWebHandler.cpp",
"chars": 1939,
"preview": "#include \"PsychicWebHandler.h\"\n\nPsychicWebHandler::PsychicWebHandler() : \n PsychicHandler(),\n _requestCallback(NULL),\n"
},
{
"path": "lib/PsychicHttp/src/PsychicWebHandler.h",
"chars": 945,
"preview": "#ifndef PsychicWebHandler_h\n#define PsychicWebHandler_h\n\n// #include \"PsychicCore.h\"\n// #include \"PsychicHttpServer.h\"\n/"
},
{
"path": "lib/PsychicHttp/src/PsychicWebParameter.h",
"chars": 733,
"preview": "#ifndef PsychicWebParameter_h\n#define PsychicWebParameter_h\n\n/*\n * PARAMETER :: Chainable object to hold GET/POST and FI"
},
{
"path": "lib/PsychicHttp/src/PsychicWebSocket.cpp",
"chars": 6700,
"preview": "#include \"PsychicWebSocket.h\"\n\n/*************************************/\n/* PsychicWebSocketRequest */\n/************"
},
{
"path": "lib/PsychicHttp/src/PsychicWebSocket.h",
"chars": 2292,
"preview": "#ifndef PsychicWebSocket_h\n#define PsychicWebSocket_h\n\n#include \"PsychicCore.h\"\n#include \"PsychicRequest.h\"\n\nclass Psych"
},
{
"path": "lib/PsychicHttp/src/TemplatePrinter.cpp",
"chars": 1934,
"preview": " /************************************************************\n \n\tTemplatePrinter Class\n\t\n\tA basic templating engine f"
},
{
"path": "lib/PsychicHttp/src/TemplatePrinter.h",
"chars": 1428,
"preview": "#ifndef TemplatePrinter_h\n #define TemplatePrinter_h\n\n #include \"PsychicCore.h\"\n #include <Print.h>\n \n /***********"
},
{
"path": "lib/PsychicHttp/src/http_status.cpp",
"chars": 4175,
"preview": "#include \"http_status.h\"\n\nbool http_informational(int code)\n{\n return code >= 100 && code < 200;\n}\n\nbool http_success"
},
{
"path": "lib/PsychicHttp/src/http_status.h",
"chars": 386,
"preview": "#ifndef MICRO_HTTP_STATUS_H\n#define MICRO_HTTP_STATUS_H\n\n#include <stdbool.h>\n\nbool http_informational(int code);\nbool h"
},
{
"path": "lib/framework/APSettingsService.cpp",
"chars": 4531,
"preview": "/**\n * ESP32 SvelteKit\n *\n * A simple, secure and extensible framework for IoT projects for ESP32 platforms\n * wit"
},
{
"path": "lib/framework/APSettingsService.h",
"chars": 5065,
"preview": "#ifndef APSettingsConfig_h\n#define APSettingsConfig_h\n\n/**\n * ESP32 SvelteKit\n *\n * A simple, secure and extensible "
},
{
"path": "lib/framework/APStatus.cpp",
"chars": 1793,
"preview": "/**\n * ESP32 SvelteKit\n *\n * A simple, secure and extensible framework for IoT projects for ESP32 platforms\n * wit"
},
{
"path": "lib/framework/APStatus.h",
"chars": 1101,
"preview": "#ifndef APStatus_h\n#define APStatus_h\n\n/**\n * ESP32 SvelteKit\n *\n * A simple, secure and extensible framework for Io"
},
{
"path": "lib/framework/AnalyticsService.h",
"chars": 1918,
"preview": "#pragma once\n\n/**\n * ESP32 SvelteKit\n *\n * A simple, secure and extensible framework for IoT projects for ESP32 plat"
},
{
"path": "lib/framework/ArduinoJsonJWT.cpp",
"chars": 4326,
"preview": "/**\n * ESP32 SvelteKit\n *\n * A simple, secure and extensible framework for IoT projects for ESP32 platforms\n * wit"
},
{
"path": "lib/framework/ArduinoJsonJWT.h",
"chars": 1177,
"preview": "#ifndef ArduinoJsonJWT_H\n#define ArduinoJsonJWT_H\n\n/**\n * ESP32 SvelteKit\n *\n * A simple, secure and extensible fram"
},
{
"path": "lib/framework/AuthenticationService.cpp",
"chars": 2226,
"preview": "/**\n * ESP32 SvelteKit\n *\n * A simple, secure and extensible framework for IoT projects for ESP32 platforms\n * wit"
},
{
"path": "lib/framework/AuthenticationService.h",
"chars": 1051,
"preview": "#ifndef AuthenticationService_H_\n#define AuthenticationService_H_\n\n/**\n * ESP32 SvelteKit\n *\n * A simple, secure and"
},
{
"path": "lib/framework/BatteryService.cpp",
"chars": 1195,
"preview": "/**\n * ESP32 SvelteKit\n *\n * A simple, secure and extensible framework for IoT projects for ESP32 platforms\n * wit"
},
{
"path": "lib/framework/BatteryService.h",
"chars": 881,
"preview": "#pragma once\n\n/**\n * ESP32 SvelteKit\n *\n * A simple, secure and extensible framework for IoT projects for ESP32 plat"
},
{
"path": "lib/framework/CoreDump.cpp",
"chars": 3173,
"preview": "/**\n * ESP32 SvelteKit\n *\n * A simple, secure and extensible framework for IoT projects for ESP32 platforms\n * wit"
},
{
"path": "lib/framework/CoreDump.h",
"chars": 961,
"preview": "#ifndef CoreDump_h\n#define CoreDump_h\n\n/**\n * ESP32 SvelteKit\n *\n * A simple, secure and extensible framework for Io"
},
{
"path": "lib/framework/DownloadFirmwareService.cpp",
"chars": 10453,
"preview": "/**\n * ESP32 SvelteKit\n *\n * A simple, secure and extensible framework for IoT projects for ESP32 platforms\n * wit"
},
{
"path": "lib/framework/DownloadFirmwareService.h",
"chars": 1167,
"preview": "#pragma once\n\n/**\n * ESP32 SvelteKit\n *\n * A simple, secure and extensible framework for IoT projects for ESP32 plat"
},
{
"path": "lib/framework/ESP32SvelteKit.cpp",
"chars": 12506,
"preview": "/**\n * ESP32 SvelteKit\n *\n * A simple, secure and extensible framework for IoT projects for ESP32 platforms\n * wit"
},
{
"path": "lib/framework/ESP32SvelteKit.h",
"chars": 5829,
"preview": "#ifndef ESP32SvelteKit_h\n#define ESP32SvelteKit_h\n\n/**\n * ESP32 SvelteKit\n *\n * A simple, secure and extensible fram"
},
{
"path": "lib/framework/ESPFS.h",
"chars": 566,
"preview": "#ifndef ESPFS_H_\n#define ESPFS_H_\n\n/**\n * ESP32 SvelteKit\n *\n * A simple, secure and extensible framework for IoT pr"
},
{
"path": "lib/framework/EthernetSettingsService.cpp",
"chars": 3833,
"preview": "/**\n * ESP32 SvelteKit\n *\n * A simple, secure and extensible framework for IoT projects for ESP32 platforms\n * wit"
},
{
"path": "lib/framework/EthernetSettingsService.h",
"chars": 4580,
"preview": "#ifndef EthernetSettingsService_h\n#define EthernetSettingsService_h\n\n/**\n * ESP32 SvelteKit\n *\n * A simple, secure a"
},
{
"path": "lib/framework/EthernetStatus.cpp",
"chars": 3179,
"preview": "/**\n * ESP32 SvelteKit\n *\n * A simple, secure and extensible framework for IoT projects for ESP32 platforms\n * wit"
},
{
"path": "lib/framework/EthernetStatus.h",
"chars": 1453,
"preview": "#ifndef EthernetStatus_h\n#define EthernetStatus_h\n\n/**\n * ESP32 SvelteKit\n *\n * A simple, secure and extensible fram"
},
{
"path": "lib/framework/EventEndpoint.h",
"chars": 2473,
"preview": "#ifndef EventEndpoint_h\n#define EventEndpoint_h\n\n/**\n * ESP32 SvelteKit\n *\n * A simple, secure and extensible framew"
},
{
"path": "lib/framework/EventSocket.cpp",
"chars": 7731,
"preview": "#include <EventSocket.h>\n\nSemaphoreHandle_t clientSubscriptionsMutex = xSemaphoreCreateMutex();\n\nEventSocket::EventSocke"
},
{
"path": "lib/framework/EventSocket.h",
"chars": 2314,
"preview": "#ifndef Socket_h\n#define Socket_h\n\n/**\n * ESP32 SvelteKit\n *\n * A simple, secure and extensible framework for IoT pr"
},
{
"path": "lib/framework/FSPersistence.h",
"chars": 4515,
"preview": "#ifndef FSPersistence_h\n#define FSPersistence_h\n\n/**\n * ESP32 SvelteKit\n *\n * A simple, secure and extensible framew"
},
{
"path": "lib/framework/FactoryResetService.cpp",
"chars": 1808,
"preview": "/**\n * ESP32 SvelteKit\n *\n * A simple, secure and extensible framework for IoT projects for ESP32 platforms\n * wit"
},
{
"path": "lib/framework/FactoryResetService.h",
"chars": 1115,
"preview": "#ifndef FactoryResetService_h\n#define FactoryResetService_h\n\n/**\n * ESP32 SvelteKit\n *\n * A simple, secure and exten"
},
{
"path": "lib/framework/Features.h",
"chars": 1550,
"preview": "#ifndef Features_h\n#define Features_h\n\n/**\n * ESP32 SvelteKit\n *\n * A simple, secure and extensible framework for Io"
},
{
"path": "lib/framework/FeaturesService.cpp",
"chars": 3821,
"preview": "/**\n * ESP32 SvelteKit\n *\n * A simple, secure and extensible framework for IoT projects for ESP32 platforms\n * wit"
},
{
"path": "lib/framework/FeaturesService.h",
"chars": 1162,
"preview": "#ifndef FeaturesService_h\n#define FeaturesService_h\n\n/**\n * ESP32 SvelteKit\n *\n * A simple, secure and extensible fr"
},
{
"path": "lib/framework/FirmwareUpdateEvents.h",
"chars": 1731,
"preview": "#ifndef FirmwareUpdateEvents_h\n#define FirmwareUpdateEvents_h\n\n/**\n * ESP32 SvelteKit\n *\n * A simple, secure and ext"
},
{
"path": "lib/framework/HttpEndpoint.h",
"chars": 4600,
"preview": "#ifndef HttpEndpoint_h\n#define HttpEndpoint_h\n\n#include <functional>\n\n#include <PsychicHttp.h>\n\n#include <SecurityManage"
},
{
"path": "lib/framework/IPUtils.h",
"chars": 825,
"preview": "#ifndef IPUtils_h\n#define IPUtils_h\n\n/**\n * ESP32 SvelteKit\n *\n * A simple, secure and extensible framework for IoT "
},
{
"path": "lib/framework/JsonUtils.h",
"chars": 1367,
"preview": "#ifndef JsonUtils_h\n#define JsonUtils_h\n\n/**\n * ESP32 SvelteKit\n *\n * A simple, secure and extensible framework for "
},
{
"path": "lib/framework/LICENSE",
"chars": 7748,
"preview": "ESP32-SvelteKit is distributed with two licenses for different sections of the\ncode. The back end code inherits the GNU "
},
{
"path": "lib/framework/MqttEndpoint.cpp",
"chars": 770,
"preview": "/**\n * ESP32 SvelteKit\n *\n * A simple, secure and extensible framework for IoT projects for ESP32 platforms\n * wit"
},
{
"path": "lib/framework/MqttEndpoint.h",
"chars": 6853,
"preview": "#ifndef MqttEndpoint_h\n#define MqttEndpoint_h\n\n/**\n * ESP32 SvelteKit\n *\n * A simple, secure and extensible framewor"
},
{
"path": "lib/framework/MqttSettingsService.cpp",
"chars": 8378,
"preview": "/**\n * ESP32 SvelteKit\n *\n * A simple, secure and extensible framework for IoT projects for ESP32 platforms\n * wit"
},
{
"path": "lib/framework/MqttSettingsService.h",
"chars": 5004,
"preview": "#ifndef MqttSettingsService_h\n#define MqttSettingsService_h\n\n/**\n * ESP32 SvelteKit\n *\n * A simple, secure and exten"
},
{
"path": "lib/framework/MqttStatus.cpp",
"chars": 1809,
"preview": "/**\n * ESP32 SvelteKit\n *\n * A simple, secure and extensible framework for IoT projects for ESP32 platforms\n * wit"
},
{
"path": "lib/framework/MqttStatus.h",
"chars": 1108,
"preview": "#ifndef MqttStatus_h\n#define MqttStatus_h\n\n/**\n * ESP32 SvelteKit\n *\n * A simple, secure and extensible framework fo"
},
{
"path": "lib/framework/NTPSettingsService.cpp",
"chars": 4357,
"preview": "/**\n * ESP32 SvelteKit\n *\n * A simple, secure and extensible framework for IoT projects for ESP32 platforms\n * wit"
},
{
"path": "lib/framework/NTPSettingsService.h",
"chars": 2668,
"preview": "#ifndef NTPSettingsService_h\n#define NTPSettingsService_h\n\n/**\n * ESP32 SvelteKit\n *\n * A simple, secure and extensi"
},
{
"path": "lib/framework/NTPStatus.cpp",
"chars": 2254,
"preview": "/**\n * ESP32 SvelteKit\n *\n * A simple, secure and extensible framework for IoT projects for ESP32 platforms\n * wit"
},
{
"path": "lib/framework/NTPStatus.h",
"chars": 998,
"preview": "#ifndef NTPStatus_h\n#define NTPStatus_h\n\n/**\n * ESP32 SvelteKit\n *\n * A simple, secure and extensible framework for "
},
{
"path": "lib/framework/NotificationService.cpp",
"chars": 627,
"preview": "#include <NotificationService.h>\n\n// array translating pushType into strings\nconst char *pushTypeStrings[] = {\"error\", \""
},
{
"path": "lib/framework/NotificationService.h",
"chars": 933,
"preview": "#ifndef NotificationService_h\n#define NotificationService_h\n\n/**\n * ESP32 SvelteKit\n *\n * A simple, secure and exten"
},
{
"path": "lib/framework/RestartService.cpp",
"chars": 1250,
"preview": "/**\n * ESP32 SvelteKit\n *\n * A simple, secure and extensible framework for IoT projects for ESP32 platforms\n * wit"
},
{
"path": "lib/framework/RestartService.h",
"chars": 1142,
"preview": "#ifndef RestartService_h\n#define RestartService_h\n\n/**\n * ESP32 SvelteKit\n *\n * A simple, secure and extensible fram"
},
{
"path": "lib/framework/SecurityManager.h",
"chars": 3062,
"preview": "#ifndef SecurityManager_h\n#define SecurityManager_h\n\n/**\n * ESP32 SvelteKit\n *\n * A simple, secure and extensible fr"
},
{
"path": "lib/framework/SecuritySettingsService.cpp",
"chars": 7908,
"preview": "/**\n * ESP32 SvelteKit\n *\n * A simple, secure and extensible framework for IoT projects for ESP32 platforms\n * wit"
},
{
"path": "lib/framework/SecuritySettingsService.h",
"chars": 4598,
"preview": "#ifndef SecuritySettingsService_h\n#define SecuritySettingsService_h\n\n/**\n * ESP32 SvelteKit\n *\n * A simple, secure a"
}
]
// ... and 31 more files (download for full content)
About this extraction
This page contains the full source code of the theelims/ESP32-sveltekit GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 231 files (797.0 KB), approximately 211.7k tokens, and a symbol index with 370 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.