Skip to content

Commit 1622a40

Browse files
authored
Merge pull request #46 from theelims/eventsocket
Release Event Socket as 0.4.0
2 parents fd5bfb0 + 70a9f91 commit 1622a40

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+2086
-1153
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ node_modules
1616
*WWWData.h
1717
lib/framework/WWWData.h
1818
ssl_certs/cacert.pem
19+
/logs

CHANGELOG.md

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,37 +2,52 @@
22

33
All notable changes to this project will be documented in this file.
44

5-
## [WIP] - Work in Progress
5+
## [0.4.0] - 2024-04-21
6+
7+
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.
8+
9+
> [!NOTE]
10+
> The new Event Socket system is likely to change with coming updates.
611
712
### Added
813

914
- 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.
10-
- Added a hook handler to StatefulService. Unlike an UPDATE a hook is called every time a state receives and updated, even if the result is UNCHANGED or ERROR.
11-
- Added missing include for S2 in SystemStatus.cpp (#23)
12-
- 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)
15+
- 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.
16+
- Added missing include for S2 in SystemStatus.cpp [#23](https://github.com/theelims/ESP32-sveltekit/issues/23)
17+
- 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)
18+
- 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)
19+
- TS Types definition in one central place for the frontend.
1320

1421
### Changed
1522

16-
- more generic board definition in platformio.ini (#20)
17-
- refactored MqttPubSub.h into a single class to improve readability
18-
- Moves appName and copyright to `layout.ts` to keep customization in one place (#31)
19-
- Make eventSource use timeout for reconnect (#34)
20-
- Make each toasts disappear after timeout (#35)
23+
- more generic board definition in platformio.ini [#20](https://github.com/theelims/ESP32-sveltekit/pull/20)
24+
- Renamed `MqttPubSub.h` and class to `MqttEndpoint.h` and class.
25+
- refactored MqttEndpoint.h into a single class to improve readability
26+
- Moves appName and copyright to `layout.ts` to keep customization in one place [#31](https://github.com/theelims/ESP32-sveltekit/pull/31)
27+
- Make event source use timeout for reconnect [#34](https://github.com/theelims/ESP32-sveltekit/pull/34)
28+
- Make each toasts disappear after timeout [#35](https://github.com/theelims/ESP32-sveltekit/pull/35)
2129
- Fixed version `platform = espressif32 @ 6.6.0` in platformio.ini
30+
- Analytics data limited to 1000 data points (roughly 33 minutes).
31+
- postcss.config.cjs as ESM module [#24](https://github.com/theelims/ESP32-sveltekit/issues/24)
2232

2333
### Fixed
2434

25-
- Duplicate method in FeatureService (#18)
2635
- Fixed compile error with FLAG `-D SERVE_CONFIG_FILES`
27-
- Fixed typo in telemetry.ts (#38)
28-
- Fixed the development warning: `Loading /rest/features using `window.fetch`. For best results, use the `fetch`that is passed to your`load` function:`
36+
- Fixed typo in telemetry.ts [#38](https://github.com/theelims/ESP32-sveltekit/pull/38)
37+
- Fixed the development warning: `Loading /rest/features using 'window.fetch'. For best results, use the 'fetch' that is passed to your 'load' function:`
2938

3039
### Removed
3140

41+
- Duplicate method in FeatureService [#18](https://github.com/theelims/ESP32-sveltekit/pull/18)
3242
- Duplicate lines in Systems Settings view.
33-
- Removes duplicate begin (#36)
43+
- Removes duplicate begin [#36](https://github.com/theelims/ESP32-sveltekit/pull/36)
44+
- Temporary disabled OTA progress update due to crash with PsychicHttp [#32](https://github.com/theelims/ESP32-sveltekit/issues/32) until a fix is found.
45+
46+
### Known Issues
47+
48+
- 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.
3449

35-
## [0.3.0] - 2023-02-05
50+
## [0.3.0] - 2024-02-05
3651

3752
> [!CAUTION]
3853
> This update has breaking changes!

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ SvelteKit is ideally suited to be served from constrained devices like an ESP32.
2323

2424
### :telephone: Rich Communication Interfaces
2525

26-
Comes with a rich set of communication interfaces to cover most standard needs of an IoT application. Like MQTT client, HTTP RESTful API or 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.
26+
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.
2727

2828
### :file_cabinet: WiFi Provisioning and Management
2929

docs/buildprocess.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,16 @@ build_flags =
141141
-D SERVE_CONFIG_FILES
142142
```
143143

144+
### Serial Info
145+
146+
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.
147+
148+
```ini
149+
build_flags =
150+
...
151+
-D SERIAL_INFO
152+
```
153+
144154
## SSL Root Certificate Store
145155

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

docs/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ SvelteKit is ideally suited to be served from constrained devices like an ESP32.
3131

3232
### :telephone: Rich Communication Interfaces
3333

34-
Comes with a rich set of communication interfaces to cover most standard needs of an IoT application. Like MQTT client, HTTP RESTful API or 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.
34+
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.
3535

3636
### :file_cabinet: WiFi Provisioning and Management
3737

docs/statefulservice.md

Lines changed: 91 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ class LightStateService : public StatefulService<LightState> {
5454
};
5555
```
5656
57+
### Update Handler
58+
5759
You may listen for changes to state by registering an update handler callback. It is possible to remove an update handler later if required.
5860
5961
```cpp
@@ -74,9 +76,11 @@ An "originId" is passed to the update handler which may be used to identify the
7476
| Origin | Description |
7577
| -------------------------- | ----------------------------------------------- |
7678
| http | An update sent over REST (HttpEndpoint) |
77-
| mqtt | An update sent over MQTT (MqttPubSub) |
79+
| mqtt | An update sent over MQTT (MqttEndpoint) |
7880
| websocketserver:{clientId} | An update sent over WebSocket (WebSocketServer) |
7981

82+
### Hook Handler
83+
8084
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.
8185

8286
```cpp
@@ -119,7 +123,7 @@ There are three possible return values for an update function which are as follo
119123
| StateUpdateResult::UNCHANGED | The state was unchanged, propagation should not take place |
120124
| StateUpdateResult::ERROR | There was an error updating the state, propagation should not take place |
121125

122-
### Serialization
126+
### JSON Serialization
123127

124128
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:
125129

@@ -165,7 +169,7 @@ JsonObject jsonObject = jsonDocument.as<JsonObject>();
165169
lightStateService->update(jsonObject, LightState::update, "timer");
166170
```
167171
168-
### Endpoints
172+
### HTTP RESTful Endpoint
169173
170174
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.
171175
@@ -191,7 +195,7 @@ Endpoint security is provided by authentication predicates which are [documented
191195

192196
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.
193197

194-
### Persistence
198+
### File System Persistence
195199

196200
[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.
197201

@@ -209,7 +213,33 @@ class LightStateService : public StatefulService<LightState> {
209213
};
210214
```
211215
212-
### WebSockets
216+
### Event Socket Endpoint
217+
218+
[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.
219+
220+
The code below demonstrates how to extend the LightStateService class to provide an WebSocket:
221+
222+
```cpp
223+
class LightStateService : public StatefulService<LightState> {
224+
public:
225+
LightStateService(EventSocket *socket) :
226+
_eventEndpoint(LightState::read, LightState::update, this, socket, "led") {}
227+
228+
void begin()
229+
{
230+
_eventEndpoint.begin();
231+
}
232+
233+
private:
234+
EventEndpoint<LightState> _eventEndpoint;
235+
};
236+
```
237+
238+
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.
239+
240+
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`.
241+
242+
### WebSocket Server
213243

214244
[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.
215245

@@ -235,11 +265,11 @@ WebSocket security is provided by authentication predicates which are [documente
235265
236266
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.
237267
238-
### MQTT
268+
### MQTT Client
239269
240270
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.
241271
242-
[MqttPubSub.h](https://github.com/theelims/ESP32-sveltekit/blob/main/lib/framework/MqttPubSub.h) allows you to publish and subscribe to synchronize state over a pair of MQTT topics. MqttPubSub automatically pushes changes to the "pub" topic and reads updates from the "sub" topic.
272+
[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.
243273
244274
The code below demonstrates how to extend the LightStateService class to interface with MQTT:
245275
@@ -248,7 +278,7 @@ The code below demonstrates how to extend the LightStateService class to interfa
248278
class LightStateService : public StatefulService<LightState> {
249279
public:
250280
LightStateService(AsyncMqttClient* mqttClient) :
251-
_mqttPubSub(LightState::read,
281+
_mqttEndpoint(LightState::read,
252282
LightState::update,
253283
this,
254284
mqttClient,
@@ -257,18 +287,68 @@ class LightStateService : public StatefulService<LightState> {
257287
}
258288
259289
private:
260-
MqttPubSub<LightState> _mqttPubSub;
290+
MqttEndpoint<LightState> _mqttEndpoint;
261291
};
262292
```
263293

264294
You can re-configure the pub/sub topics at runtime as required:
265295

266296
```cpp
267-
_mqttPubSub.configureBroker("homeassistant/light/desk_lamp/set", "homeassistant/light/desk_lamp/state");
297+
_mqttEndpoint.configureBroker("homeassistant/light/desk_lamp/set", "homeassistant/light/desk_lamp/state");
268298
```
269299

270300
The demo project allows the user to modify the MQTT topics via the UI so they can be changed without re-flashing the firmware.
271301

302+
## Event Socket
303+
304+
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`.
305+
306+
### Emit an Event
307+
308+
The Event Socket provides an overloaded `emit()` 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:
309+
310+
```cpp
311+
void emit(String event, String payload);
312+
void emit(const char *event, const char *payload);
313+
void emit(const char *event, const char *payload, const char *originId, bool onlyToSameOrigin = false);
314+
```
315+
316+
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.
317+
318+
### Receive an Event
319+
320+
A callback or lambda function can be registered to receive an ArduinoJSON object and the originId of the client sending the data:
321+
322+
```cpp
323+
_socket.onEvent("CostumEvent",[&](JsonObject &root, int originId)
324+
{
325+
bool ledState = root["led_on"];
326+
});
327+
```
328+
329+
### Get Notified on Subscriptions
330+
331+
Similarly a callback or lambda function may be registered to get notified when a client subscribes to an event:
332+
333+
```cpp
334+
_socket.onEvent("CostumEvent",[&](const String &originId, bool sync)
335+
{
336+
Serial.println("New Client subscribed: " + originId);
337+
});
338+
```
339+
340+
The boolean parameter provided will always be `true`.
341+
342+
### Push Notifications to All Clients
343+
344+
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
345+
346+
```cpp
347+
esp32sveltekit.getSocket()->pushNotification("Pushed a message!", PUSHINFO);
348+
```
349+
350+
or keep a local pointer to the `EventSocket` instance. It is possible to send `PUSHINFO`, `PUSHWARNING`, `PUSHERROR` and `PUSHSUCCESS` events to all clients.
351+
272352
## Security features
273353
274354
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).
@@ -397,24 +477,6 @@ esp32sveltekit.recoveryMode();
397477

398478
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.
399479

400-
### Push Notifications to All Clients
401-
402-
It is possibly to send push notifications to all clients by using Server Side Events. These will be displayed as toasts an the client side. Either directly call
403-
404-
```cpp
405-
esp32sveltekit.getNotificationEvents()->pushNotification("Pushed a message!", PUSHINFO, millis());
406-
```
407-
408-
or keep a local pointer to the `NotificationEvents` instance. It is possible to send `PUSHINFO`, `PUSHWARNING`, `PUSHERROR` and `PUSHSUCCESS` events to all clients. The HTTP endpoint for this service is at `/events/notifications`.
409-
410-
In addition the raw `send()` function is mapped out as well:
411-
412-
```cpp
413-
esp32sveltekit.getNotificationEvents()->send("Pushed a message!", "event", millis());
414-
```
415-
416-
This allows you to send your own Server-Sent Events without opening a new HTTP connection.
417-
418480
### Power Down with Deep Sleep
419481

420482
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.
@@ -441,7 +503,7 @@ esp32sveltekit.getSleepService()->sleepNow();
441503

442504
### Battery State of Charge
443505

444-
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 Server-sent events and exposes two functions that can be used to update the clients.
506+
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.
445507

446508
```cpp
447509
esp32sveltekit.getBatteryService()->updateSOC(float stateOfCharge); // update state of charge in percent (0 - 100%)

0 commit comments

Comments
 (0)