Skip to content

Commit

Permalink
Deep Sleep Service API
Browse files Browse the repository at this point in the history
  • Loading branch information
theelims committed Jun 29, 2023
1 parent e06d078 commit 1c5f726
Show file tree
Hide file tree
Showing 18 changed files with 253 additions and 15 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ All notable changes to this project will be documented in this file.
- Introduced CHANGELOG.md
- 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

### Changed

Expand Down
2 changes: 1 addition & 1 deletion docs/gettingstarted.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ pip install mkdocs-material
| [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/mkdosc.yaml) | MkDocs project configuration file |
| [mkdocs.yaml](https://github.com/theelims/ESP32-sveltekit/blob/main/mkdocs.yaml) | MkDocs project configuration file |

## Setting up PlatformIO

Expand Down
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,4 @@ The code runs on all variants of the ESP32 chip family. From the plain old ESP32

## 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/LICENSE) for a full text of both licenses.
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.
1 change: 1 addition & 0 deletions docs/restfulapi.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,4 @@ The back end exposes a number of API endpoints which are referenced in the table
| 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 |
| POST | /rest/sleep | `IS_AUTHENTICATED` | none | Puts the device in deep sleep mode |
30 changes: 29 additions & 1 deletion docs/statefulservice.md
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,8 @@ The framework supplies access to various features via getter functions:
| getOTASettingsService() | Configures and manages the Over-The-Air update feature |
| getMqttSettingsService() | Configures and manages the MQTT connection |
| getMqttClient() | Provides direct access to the MQTT client instance |
| getNotificationEvents() | Provides direct access to the MQTT client instance |
| getSleepService() | Provides direct access to the MQTT client instance |
The core features use the [StatefulService.h](https://github.com/theelims/ESP32-sveltekit/blob/main/lib/framework/StatefulService.h) class and can therefore you can change settings or observe changes to settings through the read/update API.
Expand Down Expand Up @@ -364,7 +366,7 @@ from your code. This will erase the complete settings folder, wiping out all set

### Recovery Mode

There is also a recovery mode present which will for the creation of an access point. By calling
There is also a recovery mode present which will force the creation of an access point. By calling

```cpp
esp32sveltekit.recoveryMode();
Expand All @@ -383,7 +385,33 @@ esp32sveltekit.getNotificationEvents()->pushNotification("Pushed a message!", IN
or keep a local pointer to the `NotificationEvents` instance. It is possible to send `INFO`, `WARNING`, `ERROR` and `SUCCESS` events to all clients. The HTTP endpoint for this service is at `/events/notifications`.
In addition the raw `send()` function is mapped out as well:
```cpp
esp32sveltekit.getNotificationEvents()->send("Pushed a message!", "event", millis());
```

This allows you to send your own Server-Sent Events without opening a new HTTP connection.

### 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.

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
```

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();
```
6 changes: 6 additions & 0 deletions factory_settings.ini
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,9 @@ build_flags =

; JWT Secret
-D FACTORY_JWT_SECRET=\"#{random}-#{random}\" ; supports placeholders

; 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


1 change: 1 addition & 0 deletions features.ini
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ build_flags =
-D FT_NTP=1
-D FT_OTA=1
-D FT_UPLOAD_FIRMWARE=1
-D FT_SLEEP=1
38 changes: 37 additions & 1 deletion interface/src/routes/statusbar.svelte
Original file line number Diff line number Diff line change
@@ -1,10 +1,39 @@
<script lang="ts">
import { page } from '$app/stores';
import { telemetry } from '$lib/stores/telemetry';
import { openModal, closeModal } from 'svelte-modals';
import { user } from '$lib/stores/user';
import ConfirmDialog from '$lib/components/ConfirmDialog.svelte';
import CPU from '~icons/tabler/cpu';
import WiFiOff from '~icons/tabler/wifi-off';
import Hamburger from '~icons/tabler/menu-2';
import Power from '~icons/tabler/power';
import Cancel from '~icons/tabler/x';
import RssiIndicator from '$lib/components/RSSIIndicator.svelte';
async function postSleep() {
const response = await fetch('/rest/sleep', {
method: 'POST',
headers: {
Authorization: $page.data.features.security ? 'Bearer ' + $user.bearer_token : 'Basic'
}
});
}
function confirmSleep() {
openModal(ConfirmDialog, {
title: 'Confirm Power Down',
message: 'Are you sure you want to switch off the device?',
labels: {
cancel: { label: 'Abort', icon: Cancel },
confirm: { label: 'Switch Off', icon: Power }
},
onConfirm: () => {
closeModal();
postSleep();
}
});
}
</script>

<div class="navbar bg-base-300 sticky top-0 z-10 h-12 min-h-fit drop-shadow-lg lg:h-16">
Expand All @@ -28,7 +57,14 @@
{#if $telemetry.rssi.disconnected}
<WiFiOff class="h-9 w-9" />
{:else}
<RssiIndicator showDBm={true} rssi_dbm={$telemetry.rssi.rssi} class="h-9 w-9" />
<RssiIndicator showDBm={false} rssi_dbm={$telemetry.rssi.rssi} class="h-9 w-9" />
{/if}
</div>
<div class="flex-none">
{#if $page.data.features.sleep}
<button class="btn btn-square btn-ghost" on:click={confirmSleep}>
<Power class="text-error h-7 w-7" />
</button>
{/if}
</div>
</div>
41 changes: 36 additions & 5 deletions interface/src/routes/system/SystemStatus.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
import { cubicOut } from 'svelte/easing';
import CPU from '~icons/tabler/cpu';
import CPP from '~icons/tabler/brand-cpp';
import Power from '~icons/tabler/power';
import Power from '~icons/tabler/reload';
import Sleep from '~icons/tabler/zzz';
import FactoryReset from '~icons/tabler/refresh-dot';
import Speed from '~icons/tabler/activity';
import Flash from '~icons/tabler/device-sd-card';
Expand Down Expand Up @@ -108,6 +109,30 @@
}
});
}
async function postSleep() {
const response = await fetch('/rest/sleep', {
method: 'POST',
headers: {
Authorization: $page.data.features.security ? 'Bearer ' + $user.bearer_token : 'Basic'
}
});
}
function confirmSleep() {
openModal(ConfirmDialog, {
title: 'Confirm Going to Sleep',
message: 'Are you sure you want to put the device into sleep?',
labels: {
cancel: { label: 'Abort', icon: Cancel },
confirm: { label: 'Sleep', icon: Sleep }
},
onConfirm: () => {
closeModal();
postSleep();
}
});
}
</script>

<SettingsCard collapsible={false}>
Expand Down Expand Up @@ -249,14 +274,20 @@
</div>
{/await}
</div>
{#if !$page.data.features.security || $user.admin}
<div class="mt-4 flex flex-wrap justify-end gap-2">

<div class="mt-4 flex flex-wrap justify-end gap-2">
{#if $page.data.features.sleep}
<button class="btn btn-primary inline-flex items-center" on:click={confirmSleep}
><Sleep class="mr-2 h-5 w-5" /><span>Sleep</span></button
>
{/if}
{#if !$page.data.features.security || $user.admin}
<button class="btn btn-primary inline-flex items-center" on:click={confirmRestart}
><Power class="mr-2 h-5 w-5" /><span>Restart</span></button
>
<button class="btn btn-secondary inline-flex items-center" on:click={confirmReset}
><FactoryReset class="mr-2 h-5 w-5" /><span>Factory Reset</span></button
>
</div>
{/if}
{/if}
</div>
</SettingsCard>
9 changes: 6 additions & 3 deletions lib/framework/ESP32SvelteKit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ ESP32SvelteKit::ESP32SvelteKit(AsyncWebServer *server) : _featureService(server)
#endif
#if FT_ENABLED(FT_SECURITY)
_authenticationService(server, &_securitySettingsService),
#endif
#if FT_ENABLED(FT_SLEEP)
_sleepService(server, &_securitySettingsService),
#endif
_restartService(server, &_securitySettingsService),
_factoryResetService(server, &ESPFS, &_securitySettingsService),
Expand Down Expand Up @@ -110,15 +113,15 @@ void ESP32SvelteKit::begin()
#if FT_ENABLED(FT_NTP)
_ntpSettingsService.begin();
#endif
#if FT_ENABLED(FT_OTA)
_otaSettingsService.begin();
#endif
#if FT_ENABLED(FT_MQTT)
_mqttSettingsService.begin();
#endif
#if FT_ENABLED(FT_SECURITY)
_securitySettingsService.begin();
#endif
#if FT_ENABLED(FT_OTA)
_otaSettingsService.begin();
#endif
}

void ESP32SvelteKit::loop()
Expand Down
11 changes: 11 additions & 0 deletions lib/framework/ESP32SvelteKit.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include <UploadFirmwareService.h>
#include <RestartService.h>
#include <SecuritySettingsService.h>
#include <SleepService.h>
#include <SystemStatus.h>
#include <WiFiScanner.h>
#include <WiFiSettingsService.h>
Expand Down Expand Up @@ -118,6 +119,13 @@ class ESP32SvelteKit
}
#endif

#if FT_ENABLED(FT_SLEEP)
SleepService *getSleepService()
{
return &_sleepService;
}
#endif

void factoryReset()
{
_factoryResetService.factoryReset();
Expand Down Expand Up @@ -158,6 +166,9 @@ class ESP32SvelteKit
#endif
#if FT_ENABLED(FT_SECURITY)
AuthenticationService _authenticationService;
#endif
#if FT_ENABLED(FT_SLEEP)
SleepService _sleepService;
#endif
RestartService _restartService;
FactoryResetService _factoryResetService;
Expand Down
5 changes: 5 additions & 0 deletions lib/framework/Features.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,9 @@
#define FT_UPLOAD_FIRMWARE 0
#endif

// ESP32 sleep states off by default
#ifndef FT_SLEEP
#define FT_SLEEP 0
#endif

#endif
5 changes: 5 additions & 0 deletions lib/framework/FeaturesService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ void FeaturesService::features(AsyncWebServerRequest *request)
root["upload_firmware"] = true;
#else
root["upload_firmware"] = false;
#endif
#if FT_ENABLED(FT_SLEEP)
root["sleep"] = true;
#else
root["sleep"] = false;
#endif
response->setLength();
request->send(response);
Expand Down
1 change: 0 additions & 1 deletion lib/framework/FeaturesService.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@

#include <WiFi.h>
#include <AsyncTCP.h>

#include <ArduinoJson.h>
#include <AsyncJson.h>
#include <ESPAsyncWebServer.h>
Expand Down
3 changes: 2 additions & 1 deletion lib/framework/OTASettingsService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ void OTASettingsService::configureArduinoOTA()
_arduinoOTA->setPort(_state.port);
_arduinoOTA->setPassword(_state.password.c_str());
_arduinoOTA->setMdnsEnabled(false);
MDNS.enableArduino(_state.port, (_state.password.length() > 0));
_arduinoOTA->onStart([]()
{ Serial.println(F("Starting")); });
_arduinoOTA->onEnd([]()
Expand All @@ -76,7 +75,9 @@ void OTASettingsService::configureArduinoOTA()
Serial.println(F("Receive Failed"));
else if (error == OTA_END_ERROR)
Serial.println(F("End Failed")); });

_arduinoOTA->begin();
MDNS.enableArduino(_state.port, (_state.password.length() > 0));
}
}

Expand Down
61 changes: 61 additions & 0 deletions lib/framework/SleepService.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* ESP32 SvelteKit
*
* A simple, secure and extensible framework for IoT projects for ESP32 platforms
* with responsive Sveltekit front-end built with TailwindCSS and DaisyUI.
* https://github.com/theelims/ESP32-sveltekit
*
* Copyright (C) 2023 theelims
*
* All Rights Reserved. This software may be modified and distributed under
* the terms of the LGPL v3 license. See the LICENSE file for details.
**/

#include <SleepService.h>

// Definition of static member variable
void (*SleepService::_callbackSleep)() = nullptr;

SleepService::SleepService(AsyncWebServer *server, SecurityManager *securityManager)
{
server->on(SLEEP_SERVICE_PATH,
HTTP_POST,
securityManager->wrapRequest(std::bind(&SleepService::sleep, this, std::placeholders::_1),
AuthenticationPredicates::IS_AUTHENTICATED));
}

void SleepService::sleep(AsyncWebServerRequest *request)
{
request->onDisconnect(SleepService::sleepNow);
request->send(200);
}

void SleepService::sleepNow()
{
Serial.println("Going into deep sleep now");
// Callback for main code sleep preparation
if (_callbackSleep != nullptr)
{
_callbackSleep();
}
delay(100);

MDNS.end();
delay(100);

WiFi.disconnect(true);
delay(500);

// Prepare ESP for sleep
uint64_t bitmask = (uint64_t)1 << (WAKEUP_PIN_NUMBER);
esp_sleep_enable_ext1_wakeup(bitmask, (esp_sleep_ext1_wakeup_mode_t)WAKEUP_SIGNAL);
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF);

Serial.println("Good by!");

// Just to be sure
delay(100);

// Hibernate
esp_deep_sleep_start();
}
Loading

0 comments on commit 1c5f726

Please sign in to comment.