From be3632bd26426abe567274cec42f561bc81fbdca Mon Sep 17 00:00:00 2001 From: Rune Harlyk Date: Thu, 4 Apr 2024 20:06:17 +0200 Subject: [PATCH 01/13] Adds socket service --- lib/framework/ESP32SvelteKit.cpp | 2 + lib/framework/ESP32SvelteKit.h | 7 ++ lib/framework/Socket.cpp | 130 +++++++++++++++++++++++++++++++ lib/framework/Socket.h | 63 +++++++++++++++ 4 files changed, 202 insertions(+) create mode 100644 lib/framework/Socket.cpp create mode 100644 lib/framework/Socket.h diff --git a/lib/framework/ESP32SvelteKit.cpp b/lib/framework/ESP32SvelteKit.cpp index 257836115..513d8ce44 100644 --- a/lib/framework/ESP32SvelteKit.cpp +++ b/lib/framework/ESP32SvelteKit.cpp @@ -24,6 +24,7 @@ ESP32SvelteKit::ESP32SvelteKit(PsychicHttpServer *server, unsigned int numberEnd _apSettingsService(server, &ESPFS, &_securitySettingsService), _apStatus(server, &_securitySettingsService, &_apSettingsService), _notificationEvents(server), + _socket(server), #if FT_ENABLED(FT_NTP) _ntpSettingsService(server, &ESPFS, &_securitySettingsService), _ntpStatus(server, &_securitySettingsService), @@ -140,6 +141,7 @@ void ESP32SvelteKit::begin() // Start the services _apStatus.begin(); _notificationEvents.begin(); + _socket.begin(); _apSettingsService.begin(); _factoryResetService.begin(); _featureService.begin(); diff --git a/lib/framework/ESP32SvelteKit.h b/lib/framework/ESP32SvelteKit.h index 339d30b7a..c05d9fde5 100644 --- a/lib/framework/ESP32SvelteKit.h +++ b/lib/framework/ESP32SvelteKit.h @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -90,6 +91,11 @@ class ESP32SvelteKit return &_notificationEvents; } + Socket *getSocket() + { + return &_socket; + } + #if FT_ENABLED(FT_SECURITY) StatefulService *getSecuritySettingsService() { @@ -171,6 +177,7 @@ class ESP32SvelteKit APSettingsService _apSettingsService; APStatus _apStatus; NotificationEvents _notificationEvents; + Socket _socket; #if FT_ENABLED(FT_NTP) NTPSettingsService _ntpSettingsService; NTPStatus _ntpStatus; diff --git a/lib/framework/Socket.cpp b/lib/framework/Socket.cpp new file mode 100644 index 000000000..e51b8a50b --- /dev/null +++ b/lib/framework/Socket.cpp @@ -0,0 +1,130 @@ +/** + * 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) 2024 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 + +Socket::Socket(PsychicHttpServer *server) : + _server(server), + _bufferSize(1024) +{ +} + +void Socket::begin() +{ + _socket.onOpen(std::bind(&Socket::onWSOpen, this, std::placeholders::_1)); + _socket.onClose(std::bind(&Socket::onWSClose, this, std::placeholders::_1)); + _socket.onFrame(std::bind(&Socket::onFrame, this, std::placeholders::_1, std::placeholders::_2)); + _server->on(WEB_SOCKET_SERVICE_PATH, &_socket); + + ESP_LOGV("Socket", "Registered socket Source endpoint: %s", WEB_SOCKET_SERVICE_PATH); +} + +void Socket::onWSOpen(PsychicWebSocketClient *client) +{ + ESP_LOGI("WebSocketServer", "ws[%s][%u] connect", client->remoteIP().toString(), client->socket()); +} + +void Socket::onWSClose(PsychicWebSocketClient *client) +{ + for (auto &event_subscriptions : client_subscriptions) { + event_subscriptions.second.remove(client->socket()); + } + ESP_LOGI("WebSocketServer", "ws[%s][%u] disconnect", client->remoteIP().toString(), client->socket()); +} + +esp_err_t Socket::onFrame(PsychicWebSocketRequest *request, httpd_ws_frame *frame) +{ + ESP_LOGV("WebSocketServer", "ws[%s][%u] opcode[%d]", request->client()->remoteIP().toString(), request->client()->socket(), frame->type); + + if (frame->type == HTTPD_WS_TYPE_TEXT) + { + ESP_LOGV("WebSocketServer", "ws[%s][%u] request: %s", request->client()->remoteIP().toString(), request->client()->socket(), (char *)frame->payload); + + DynamicJsonDocument doc = DynamicJsonDocument(_bufferSize); + DeserializationError error = deserializeJson(doc, (char *)frame->payload, frame->len); + + if (!error && doc.is()) + { + String event = doc["event"]; + if (event == "subscribe") { + client_subscriptions[doc["data"]].push_back(request->client()->socket()); + } else if (event == "unsubscribe") { + client_subscriptions[doc["data"]].remove(request->client()->socket()); + } else { + JsonObject jsonObject = doc["data"].as(); + handleCallbacks(event, jsonObject); + } + return ESP_OK; + } + } + return ESP_OK; +} + +void Socket::emit(JsonObject root, String event, size_t dataSize) +{ + if (client_subscriptions[event].size() == 0) return; + DynamicJsonDocument doc(dataSize + 100); + doc["event"] = event; + doc["data"] = root; + String buffer; + serializeJson(doc, buffer); + for (int subscription : client_subscriptions[event]) + { + PsychicWebSocketClient *client = _socket.getClient(subscription); + if (client == NULL) { + client_subscriptions[event].remove(subscription); + continue; + } + ESP_LOGD("WebSocketServer", "Emitting event: %s to %s, Message: %s", event.c_str(), client->remoteIP().toString(), buffer.c_str()); + client->sendMessage(buffer.c_str()); + } +} + +void Socket::emit(String message, String event) +{ + if (client_subscriptions[event].size() == 0) return; + size_t dataSize = message.length() + 1; + DynamicJsonDocument doc(dataSize + 100); + doc["event"] = event; + doc["data"] = message; + String buffer; + serializeJson(doc, buffer); + for (int subscription : client_subscriptions[event]) + { + PsychicWebSocketClient *client = _socket.getClient(subscription); + if (client == NULL) { + client_subscriptions[event].remove(subscription); + continue; + } + ESP_LOGD("WebSocketServer", "Emitting event: %s to %s, Message: %s", event.c_str(), client->remoteIP().toString(), buffer.c_str()); + client->sendMessage(buffer.c_str()); + } +} + +void Socket::handleCallbacks(String event, JsonObject &jsonObject){ + for (auto &callback : event_callbacks[event]) + { + callback(jsonObject); + } +} + +void Socket::on(String event, EventCallback callback) +{ + event_callbacks[event].push_back(callback); + log_d("Socket::on"); +} + +void Socket::broadcast(String message) +{ + _socket.sendAll(message.c_str()); +} diff --git a/lib/framework/Socket.h b/lib/framework/Socket.h new file mode 100644 index 000000000..68f5dcb65 --- /dev/null +++ b/lib/framework/Socket.h @@ -0,0 +1,63 @@ +#ifndef Socket_h +#define Socket_h + +/** + * 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) 2024 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 +#include +#include +#include + +#define WEB_SOCKET_CLIENT_ID_MSG_SIZE 128 + +#define WEB_SOCKET_ORIGIN "wsserver" +#define WEB_SOCKET_ORIGIN_CLIENT_ID_PREFIX "wsserver:" + +#define WEB_SOCKET_SERVICE_PATH "/ws" + +#define SOCKET_CONNECT "connect"; +#define SOCKET_DISCONNECT "disconnect"; +#define SOCKET_ERROR "error"; + +typedef std::function EventCallback; + +class Socket +{ +private: + PsychicHttpServer *_server; + PsychicWebSocketHandler _socket; + std::map> client_subscriptions; + std::map> event_callbacks; + void handleCallbacks(String event, JsonObject &jsonObject); + + size_t _bufferSize; + void onWSOpen(PsychicWebSocketClient *client); + void onWSClose(PsychicWebSocketClient *client); + esp_err_t onFrame(PsychicWebSocketRequest *request, httpd_ws_frame *frame); + +public: + Socket(PsychicHttpServer *server); + + void begin(); + + void on(String event, EventCallback callback); + + void emit(JsonObject root, String event, size_t dataSize); + + void emit(String message, String event); + + void broadcast(String message); +};; + +#endif From cd06e8b7dc08ae076badb97417f490d609ced1cc Mon Sep 17 00:00:00 2001 From: Rune Harlyk Date: Thu, 4 Apr 2024 20:07:14 +0200 Subject: [PATCH 02/13] Adds frontend socket service --- interface/src/lib/stores/socket.ts | 97 ++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 interface/src/lib/stores/socket.ts diff --git a/interface/src/lib/stores/socket.ts b/interface/src/lib/stores/socket.ts new file mode 100644 index 000000000..9a33a0e24 --- /dev/null +++ b/interface/src/lib/stores/socket.ts @@ -0,0 +1,97 @@ +import { writable } from 'svelte/store'; + +function createWebSocket(url: string | URL) { + let listeners = new Map 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; + + function disconnect(reason: SocketEvent, event?: Event) { + ws.close(); + set(false); + clearTimeout(unresponsiveTimeoutId); + clearTimeout(reconnectTimeoutId); + listeners.get(reason)?.forEach((listener) => listener(event)); + reconnectTimeoutId = setTimeout(connect, 1000); + } + + function connect() { + ws = new WebSocket(url); + 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(); + const data = JSON.parse(message.data); + if (data.event) listeners.get(data.event)?.forEach((listener) => listener(data.data)); + listeners.get('message')?.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; + ws.send(JSON.stringify(msg)); + } + + function sendEvent(event: string, data: unknown) { + send({ event, data }); + } + + connect(); + + return { + subscribe, + send, + sendEvent, + on: (event: string, listener: (data: T) => void): (() => void) => { + let eventListeners = listeners.get(event); + if (!eventListeners) { + if (!socketEvents.includes(event as SocketEvent)) { + sendEvent('subscribe', event); + } + eventListeners = new Set(); + listeners.set(event, eventListeners); + } + 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(`ws://${window.location.host}/ws`); From 6ec68200ea81652c79a602229a97fd780cd53429 Mon Sep 17 00:00:00 2001 From: Rune Harlyk Date: Thu, 4 Apr 2024 20:09:07 +0200 Subject: [PATCH 03/13] Updates LightStateService to use socket # Conflicts: # interface/src/routes/demo/Demo.svelte # Conflicts: # interface/src/routes/demo/Demo.svelte # Conflicts: # interface/src/routes/demo/Demo.svelte --- interface/src/routes/demo/Demo.svelte | 33 ++++------------- src/LightStateService.cpp | 53 ++++++++++++++------------- src/LightStateService.h | 7 ++-- src/main.cpp | 11 ++++-- 4 files changed, 46 insertions(+), 58 deletions(-) diff --git a/interface/src/routes/demo/Demo.svelte b/interface/src/routes/demo/Demo.svelte index e1ba44db2..70634f6e0 100644 --- a/interface/src/routes/demo/Demo.svelte +++ b/interface/src/routes/demo/Demo.svelte @@ -8,6 +8,7 @@ import Info from '~icons/tabler/info-circle'; import Save from '~icons/tabler/device-floppy'; import Reload from '~icons/tabler/reload'; + import { socket } from '$lib/stores/socket'; import type { LightState } from '$lib/types/models'; let lightState: LightState = { led_on: false }; @@ -31,34 +32,14 @@ return; } - const ws_token = $page.data.features.security ? '?access_token=' + $user.bearer_token : ''; - - const lightStateSocket = new WebSocket('ws://' + $page.url.host + '/ws/lightState' + ws_token); - - lightStateSocket.onopen = (event) => { - lightStateSocket.send('Hello'); - }; - - lightStateSocket.addEventListener('close', (event) => { - const closeCode = event.code; - const closeReason = event.reason; - console.log('WebSocket closed with code:', closeCode); - console.log('Close reason:', closeReason); - notifications.error('Websocket disconnected', 5000); - }); - - lightStateSocket.onmessage = (event) => { - const message = JSON.parse(event.data); - if (message.type != 'id') { - lightState = message; - } - }; - - onDestroy(() => lightStateSocket.close()); - onMount(() => { + socket.on("led", (data) => { + lightState = data; + }) getLightstate(); }); + + onDestroy(() => socket.off("led")); async function postLightstate() { try { @@ -125,7 +106,7 @@ class="toggle toggle-primary" bind:checked={lightState.led_on} on:change={() => { - lightStateSocket.send(JSON.stringify(lightState)); + socket.sendEvent("led", lightState); }} /> diff --git a/src/LightStateService.cpp b/src/LightStateService.cpp index a11b7c26d..0bac5f57c 100644 --- a/src/LightStateService.cpp +++ b/src/LightStateService.cpp @@ -14,30 +14,24 @@ #include -LightStateService::LightStateService(PsychicHttpServer *server, - SecurityManager *securityManager, - PsychicMqttClient *mqttClient, - LightMqttSettingsService *lightMqttSettingsService) : _httpEndpoint(LightState::read, - LightState::update, - this, - server, - LIGHT_SETTINGS_ENDPOINT_PATH, - securityManager, - AuthenticationPredicates::IS_AUTHENTICATED), - _mqttPubSub(LightState::homeAssistRead, LightState::homeAssistUpdate, this, mqttClient), - _webSocketServer(LightState::read, - LightState::update, - this, - server, - LIGHT_SETTINGS_SOCKET_PATH, - securityManager, - AuthenticationPredicates::IS_AUTHENTICATED), - _mqttClient(mqttClient), - _lightMqttSettingsService(lightMqttSettingsService) -/* _webSocketClient(LightState::read, - LightState::update, - this, - LIGHT_SETTINGS_SOCKET_PATH)*/ +LightStateService::LightStateService( + PsychicHttpServer *server, + Socket *socket, + SecurityManager *securityManager, + PsychicMqttClient *mqttClient, + LightMqttSettingsService *lightMqttSettingsService) : + _httpEndpoint( + LightState::read, + LightState::update, + this, + server, + LIGHT_SETTINGS_ENDPOINT_PATH, + securityManager, + AuthenticationPredicates::IS_AUTHENTICATED), + _mqttPubSub(LightState::homeAssistRead, LightState::homeAssistUpdate, this, mqttClient), + _mqttClient(mqttClient), + _lightMqttSettingsService(lightMqttSettingsService), + _socket(socket) { // configure led to be output pinMode(LED_BUILTIN, OUTPUT); @@ -59,7 +53,8 @@ LightStateService::LightStateService(PsychicHttpServer *server, void LightStateService::begin() { _httpEndpoint.begin(); - _webSocketServer.begin(); + String event = "led"; + _socket->on(event, std::bind(&LightStateService::handleUpdateLightState, this, std::placeholders::_1)); _state.ledOn = DEFAULT_LED_STATE; onConfigUpdated(); } @@ -69,6 +64,13 @@ void LightStateService::onConfigUpdated() digitalWrite(LED_BUILTIN, _state.ledOn ? 1 : 0); } +void LightStateService::handleUpdateLightState(JsonObject &root) +{ + _state.ledOn = root["led_on"]; + onConfigUpdated(); + ESP_LOGI("LightStateService", "Received light state update led_on: %s", _state.ledOn ? "true" : "false"); +} + void LightStateService::registerConfig() { if (!_mqttClient->connected()) @@ -99,3 +101,4 @@ void LightStateService::registerConfig() _mqttPubSub.configureTopics(pubTopic, subTopic); } + diff --git a/src/LightStateService.h b/src/LightStateService.h index 4c7e29799..3a5a05f45 100644 --- a/src/LightStateService.h +++ b/src/LightStateService.h @@ -20,7 +20,7 @@ #include #include #include -// #include +#include #define DEFAULT_LED_STATE false #define OFF_STATE "OFF" @@ -82,6 +82,7 @@ class LightStateService : public StatefulService { public: LightStateService(PsychicHttpServer *server, + Socket *socket, SecurityManager *securityManager, PsychicMqttClient *mqttClient, LightMqttSettingsService *lightMqttSettingsService); @@ -90,13 +91,13 @@ class LightStateService : public StatefulService private: HttpEndpoint _httpEndpoint; MqttPubSub _mqttPubSub; - WebSocketServer _webSocketServer; - // WebSocketClient _webSocketClient; PsychicMqttClient *_mqttClient; LightMqttSettingsService *_lightMqttSettingsService; + Socket *_socket; void registerConfig(); void onConfigUpdated(); + void handleUpdateLightState(JsonObject &root); }; #endif diff --git a/src/main.cpp b/src/main.cpp index 326f3634e..64b67bcde 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -26,10 +26,13 @@ ESP32SvelteKit esp32sveltekit(&server, 120); LightMqttSettingsService lightMqttSettingsService = LightMqttSettingsService(&server, esp32sveltekit.getFS(), esp32sveltekit.getSecurityManager()); -LightStateService lightStateService = LightStateService(&server, - esp32sveltekit.getSecurityManager(), - esp32sveltekit.getMqttClient(), - &lightMqttSettingsService); +LightStateService lightStateService = LightStateService( + &server, + esp32sveltekit.getSocket(), + esp32sveltekit.getSecurityManager(), + esp32sveltekit.getMqttClient(), + &lightMqttSettingsService +); void setup() { From be8fbc5cfb3e3ee87a649ec4c3f8497feec5601a Mon Sep 17 00:00:00 2001 From: Rune Harlyk Date: Thu, 4 Apr 2024 20:10:13 +0200 Subject: [PATCH 04/13] Updates analytics, downloadfirmware and wifisetting # Conflicts: # interface/src/lib/stores/analytics.ts # interface/src/routes/+layout.svelte # interface/src/routes/system/status/SystemStatus.svelte # Conflicts: # interface/src/routes/+layout.svelte # interface/src/routes/system/status/SystemStatus.svelte # Conflicts: # interface/src/routes/+layout.svelte # interface/src/routes/system/status/SystemStatus.svelte --- interface/src/routes/+layout.svelte | 111 ++++++++---------- interface/src/routes/+layout.ts | 2 +- .../routes/system/status/SystemStatus.svelte | 9 +- lib/framework/AnalyticsService.h | 10 +- lib/framework/DownloadFirmwareService.cpp | 35 +++--- lib/framework/DownloadFirmwareService.h | 6 +- lib/framework/ESP32SvelteKit.cpp | 6 +- lib/framework/WiFiSettingsService.cpp | 8 +- lib/framework/WiFiSettingsService.h | 6 +- 9 files changed, 83 insertions(+), 110 deletions(-) diff --git a/interface/src/routes/+layout.svelte b/interface/src/routes/+layout.svelte index bf5778c82..787944cf8 100644 --- a/interface/src/routes/+layout.svelte +++ b/interface/src/routes/+layout.svelte @@ -4,6 +4,7 @@ import { user } from '$lib/stores/user'; import { telemetry } from '$lib/stores/telemetry'; import { analytics } from '$lib/stores/analytics'; + import { socket } from '$lib/stores/socket'; import type { userProfile } from '$lib/stores/user'; import { page } from '$app/stores'; import { Modals, closeModal } from 'svelte-modals'; @@ -22,10 +23,38 @@ if ($user.bearer_token !== '') { await validateUser($user); } - connectToEventSource(); + addEventListeners() }); - onDestroy(() => disconnectEventSource()); + onDestroy(() => { + removeEventListeners() + }); + + const addEventListeners = () => { + socket.on("analytics", handleAnalytics) + socket.on("open", handleOpen) + socket.on("close", handleClose) + socket.on("rssi", handleNetworkStatus) + socket.on("infoToast", handleInfoToast) + socket.on("successToast", handleSuccessToast) + socket.on("warningToast", handleWarningToast) + socket.on("errorToast", handleErrorToast) + socket.on("battery", handleBattery) + socket.on("download_ota", handleOAT) + } + + const removeEventListeners = () => { + socket.off("analytics", handleAnalytics) + socket.off("open", handleOpen) + socket.off("close", handleClose) + socket.off("rssi", handleNetworkStatus) + socket.off("infoToast", handleInfoToast) + socket.off("successToast", handleSuccessToast) + socket.off("warningToast", handleWarningToast) + socket.off("errorToast", handleErrorToast) + socket.off("battery", handleBattery) + socket.off("download_ota", handleOAT) + } async function validateUser(userdata: userProfile) { try { @@ -44,73 +73,27 @@ } } - let menuOpen = false; + const handleClose = () => notifications.error('Connection to device established', 5000); - let eventSourceUrl = '/events'; - let eventSource: EventSource; - let unresponsiveTimeoutId: number; - - function connectToEventSource() { - eventSource = new EventSource(eventSourceUrl); - - eventSource.addEventListener('open', () => { - notifications.success('Connection to device established', 5000); - telemetry.setRSSI('found'); // Update store and flag as server being available again - }); - - eventSource.addEventListener('rssi', (event) => { - telemetry.setRSSI(event.data); - resetUnresponsiveCheck(); - }); - - eventSource.addEventListener('error', (event) => { - reconnectEventSource(); - }); - - eventSource.addEventListener('infoToast', (event) => { - notifications.info(event.data, 5000); - }); - - eventSource.addEventListener('successToast', (event) => { - notifications.success(event.data, 5000); - }); - - eventSource.addEventListener('warningToast', (event) => { - notifications.warning(event.data, 5000); - }); - - eventSource.addEventListener('errorToast', (event) => { - notifications.error(event.data, 5000); - }); - - eventSource.addEventListener('battery', (event) => { - telemetry.setBattery(event.data); - }); - - eventSource.addEventListener('download_ota', (event) => { - telemetry.setDownloadOTA(event.data); - }); - eventSource.addEventListener('analytics', (event) => { - const data = JSON.parse(event.data) as Analytics; - analytics.addData(data); - }); - } + const handleInfoToast = (data: string) => notifications.info(data, 5000) + const handleWarningToast = (data: string) => notifications.warning(data, 5000) + const handleErrorToast = (data: string) => notifications.error(data, 5000) + const handleSuccessToast = (data: string) => notifications.success(data, 5000) - function disconnectEventSource() { - clearTimeout(unresponsiveTimeoutId); - eventSource?.close(); + const handleOpen = () => { + notifications.success('Connection to device established', 5000) + telemetry.setRSSI('found') } + + const handleAnalytics = (data: Analytics) => analytics.addData(data) - function reconnectEventSource() { - notifications.error('Connection to device lost', 5000); - disconnectEventSource(); - connectToEventSource(); - } + const handleNetworkStatus = (data: string) => telemetry.setRSSI(data); - function resetUnresponsiveCheck() { - clearTimeout(unresponsiveTimeoutId); - unresponsiveTimeoutId = setTimeout(() => reconnectEventSource(), 2000); - } + const handleBattery = (data: string) => telemetry.setRSSI(data); + + const handleOAT = (data: string) => telemetry.setDownloadOTA(data); + + let menuOpen = false; diff --git a/interface/src/routes/+layout.ts b/interface/src/routes/+layout.ts index 9de140765..4b1211411 100644 --- a/interface/src/routes/+layout.ts +++ b/interface/src/routes/+layout.ts @@ -4,7 +4,7 @@ import type { LayoutLoad } from './$types'; export const prerender = false; export const ssr = false; -export const load = (async () => { +export const load = (async ({ fetch }) => { const result = await fetch('/rest/features'); const item = await result.json(); return { diff --git a/interface/src/routes/system/status/SystemStatus.svelte b/interface/src/routes/system/status/SystemStatus.svelte index 39ea5315b..71c0c585b 100644 --- a/interface/src/routes/system/status/SystemStatus.svelte +++ b/interface/src/routes/system/status/SystemStatus.svelte @@ -25,6 +25,7 @@ import Stopwatch from '~icons/tabler/24-hours'; import SDK from '~icons/tabler/sdk'; import type { SystemInformation } from '$lib/types/models'; + import { socket } from '$lib/stores/socket'; let systemInformation: SystemInformation; @@ -44,13 +45,11 @@ return systemInformation; } - const interval = setInterval(async () => { - getSystemStatus(); - }, 5000); + onMount(() => socket.on("analytics", handleSystemData)); - onMount(() => getSystemStatus()); + onDestroy(() => socket.off("analytics", handleSystemData)); - onDestroy(() => clearInterval(interval)); + const handleSystemData = (data: Analytics) => systemInformation = { ...systemInformation, ...data }; async function postRestart() { const response = await fetch('/rest/restart', { diff --git a/lib/framework/AnalyticsService.h b/lib/framework/AnalyticsService.h index 915dcf26e..06b6a57d0 100644 --- a/lib/framework/AnalyticsService.h +++ b/lib/framework/AnalyticsService.h @@ -16,7 +16,7 @@ #include #include #include -#include +#include #define MAX_ESP_ANALYTICS_SIZE 1024 #define ANALYTICS_INTERVAL 2000 @@ -24,7 +24,7 @@ class AnalyticsService { public: - AnalyticsService(NotificationEvents *notificationEvents) : _notificationEvents(notificationEvents){}; + AnalyticsService(Socket *socket) : _socket(socket){}; void begin() { @@ -40,7 +40,7 @@ class AnalyticsService }; protected: - NotificationEvents *_notificationEvents; + Socket *_socket; static void _loopImpl(void *_this) { static_cast(_this)->_loop(); } void _loop() @@ -50,7 +50,6 @@ class AnalyticsService while (1) { StaticJsonDocument doc; - String message; doc["uptime"] = millis() / 1000; doc["free_heap"] = ESP.getFreeHeap(); doc["total_heap"] = ESP.getHeapSize(); @@ -60,8 +59,7 @@ class AnalyticsService doc["fs_total"] = ESPFS.totalBytes(); doc["core_temp"] = temperatureRead(); - serializeJson(doc, message); - _notificationEvents->send(message, "analytics", millis()); + _socket->emit(doc.as(), "analytics", MAX_ESP_ANALYTICS_SIZE); vTaskDelayUntil(&xLastWakeTime, ANALYTICS_INTERVAL / portTICK_PERIOD_MS); } diff --git a/lib/framework/DownloadFirmwareService.cpp b/lib/framework/DownloadFirmwareService.cpp index 110522a73..c19359a4b 100644 --- a/lib/framework/DownloadFirmwareService.cpp +++ b/lib/framework/DownloadFirmwareService.cpp @@ -15,28 +15,24 @@ extern const uint8_t rootca_crt_bundle_start[] asm("_binary_src_certs_x509_crt_bundle_bin_start"); -static NotificationEvents *_notificationEvents = nullptr; +static Socket *_socket = nullptr; static int previousProgress = 0; StaticJsonDocument<128> doc; void update_started() { - String output; doc["status"] = "preparing"; - serializeJson(doc, output); - _notificationEvents->send(output, "download_ota", millis()); + _socket->emit(doc.as(), "download_ota", 128); } void update_progress(int currentBytes, int totalBytes) { - String output; doc["status"] = "progress"; int progress = ((currentBytes * 100) / totalBytes); if (progress > previousProgress) { doc["progress"] = progress; - serializeJson(doc, output); - _notificationEvents->send(output, "download_ota", millis()); + _socket->emit(doc.as(), "download_ota", 128); ESP_LOGV("Download OTA", "HTTP update process at %d of %d bytes... (%d %%)", currentBytes, totalBytes, progress); } previousProgress = progress; @@ -44,10 +40,8 @@ void update_progress(int currentBytes, int totalBytes) void update_finished() { - String output; doc["status"] = "finished"; - serializeJson(doc, output); - _notificationEvents->send(output, "download_ota", millis()); + _socket->emit(doc.as(), "download_ota", 128); // delay to allow the event to be sent out vTaskDelay(100 / portTICK_PERIOD_MS); @@ -76,8 +70,7 @@ void updateTask(void *param) doc["status"] = "error"; doc["error"] = httpUpdate.getLastErrorString().c_str(); - serializeJson(doc, output); - _notificationEvents->send(output, "download_ota", millis()); + _socket->emit(doc.as(), "download_ota", 128); ESP_LOGE("Download OTA", "HTTP Update failed with error (%d): %s", httpUpdate.getLastError(), httpUpdate.getLastErrorString().c_str()); #ifdef SERIAL_INFO @@ -88,8 +81,7 @@ void updateTask(void *param) doc["status"] = "error"; doc["error"] = "Update failed, has same firmware version"; - serializeJson(doc, output); - _notificationEvents->send(output, "download_ota", millis()); + _socket->emit(doc.as(), "download_ota", 128); ESP_LOGE("Download OTA", "HTTP Update failed, has same firmware version"); #ifdef SERIAL_INFO @@ -106,10 +98,13 @@ void updateTask(void *param) vTaskDelete(NULL); } -DownloadFirmwareService::DownloadFirmwareService(PsychicHttpServer *server, SecurityManager *securityManager, NotificationEvents *notificationEvents) : _server(server), - _securityManager(securityManager), - _notificationEvents(notificationEvents) - +DownloadFirmwareService::DownloadFirmwareService( + PsychicHttpServer *server, + SecurityManager *securityManager, + Socket *socket) : + _server(server), + _securityManager(securityManager), + _socket(socket) { } @@ -141,9 +136,7 @@ esp_err_t DownloadFirmwareService::downloadUpdate(PsychicRequest *request, JsonV doc["progress"] = 0; doc["error"] = ""; - String output; - serializeJson(doc, output); - _notificationEvents->send(output, "download_ota", millis()); + _socket->emit(doc.as(), "download_ota", 250); if (xTaskCreatePinnedToCore( &updateTask, // Function that should be called diff --git a/lib/framework/DownloadFirmwareService.h b/lib/framework/DownloadFirmwareService.h index 652528a27..17309e46e 100644 --- a/lib/framework/DownloadFirmwareService.h +++ b/lib/framework/DownloadFirmwareService.h @@ -20,7 +20,7 @@ #include #include #include -#include +#include #include #include @@ -32,13 +32,13 @@ class DownloadFirmwareService { public: - DownloadFirmwareService(PsychicHttpServer *server, SecurityManager *securityManager, NotificationEvents *notificationEvents); + DownloadFirmwareService(PsychicHttpServer *server, SecurityManager *securityManager, Socket *socket); void begin(); private: SecurityManager *_securityManager; PsychicHttpServer *_server; - NotificationEvents *_notificationEvents; + Socket *_socket; esp_err_t downloadUpdate(PsychicRequest *request, JsonVariant &json); }; diff --git a/lib/framework/ESP32SvelteKit.cpp b/lib/framework/ESP32SvelteKit.cpp index 513d8ce44..cd3c591c1 100644 --- a/lib/framework/ESP32SvelteKit.cpp +++ b/lib/framework/ESP32SvelteKit.cpp @@ -18,7 +18,7 @@ ESP32SvelteKit::ESP32SvelteKit(PsychicHttpServer *server, unsigned int numberEnd _numberEndpoints(numberEndpoints), _featureService(server), _securitySettingsService(server, &ESPFS), - _wifiSettingsService(server, &ESPFS, &_securitySettingsService, &_notificationEvents), + _wifiSettingsService(server, &ESPFS, &_securitySettingsService, &_socket), _wifiScanner(server, &_securitySettingsService), _wifiStatus(server, &_securitySettingsService), _apSettingsService(server, &ESPFS, &_securitySettingsService), @@ -33,7 +33,7 @@ ESP32SvelteKit::ESP32SvelteKit(PsychicHttpServer *server, unsigned int numberEnd _uploadFirmwareService(server, &_securitySettingsService), #endif #if FT_ENABLED(FT_DOWNLOAD_FIRMWARE) - _downloadFirmwareService(server, &_securitySettingsService, &_notificationEvents), + _downloadFirmwareService(server, &_securitySettingsService, &_socket), #endif #if FT_ENABLED(FT_MQTT) _mqttSettingsService(server, &ESPFS, &_securitySettingsService), @@ -49,7 +49,7 @@ ESP32SvelteKit::ESP32SvelteKit(PsychicHttpServer *server, unsigned int numberEnd _batteryService(&_notificationEvents), #endif #if FT_ENABLED(FT_ANALYTICS) - _analyticsService(&_notificationEvents), + _analyticsService(&_socket), #endif _restartService(server, &_securitySettingsService), _factoryResetService(server, &ESPFS, &_securitySettingsService), diff --git a/lib/framework/WiFiSettingsService.cpp b/lib/framework/WiFiSettingsService.cpp index 97bb5df9a..925ed48ae 100644 --- a/lib/framework/WiFiSettingsService.cpp +++ b/lib/framework/WiFiSettingsService.cpp @@ -14,7 +14,7 @@ #include -WiFiSettingsService::WiFiSettingsService(PsychicHttpServer *server, FS *fs, SecurityManager *securityManager, NotificationEvents *notificationEvents) : _server(server), +WiFiSettingsService::WiFiSettingsService(PsychicHttpServer *server, FS *fs, SecurityManager *securityManager, Socket *socket) : _server(server), _securityManager(securityManager), _httpEndpoint(WiFiSettings::read, WiFiSettings::update, @@ -30,7 +30,7 @@ WiFiSettingsService::WiFiSettingsService(PsychicHttpServer *server, FS *fs, Secu fs, WIFI_SETTINGS_FILE), _lastConnectionAttempt(0), - _notificationEvents(notificationEvents) + _socket(socket) { addUpdateHandler([&](const String &originId) { reconfigureWiFiConnection(); }, @@ -224,11 +224,11 @@ void WiFiSettingsService::updateRSSI() if (WiFi.isConnected()) { String rssi = String(WiFi.RSSI()); - _notificationEvents->send(rssi, "rssi", millis()); + _socket->emit(rssi, "rssi"); } else { - _notificationEvents->send("disconnected", "rssi", millis()); + _socket->emit("disconnected", "rssi"); } } diff --git a/lib/framework/WiFiSettingsService.h b/lib/framework/WiFiSettingsService.h index c946fb094..0b8e84ac2 100644 --- a/lib/framework/WiFiSettingsService.h +++ b/lib/framework/WiFiSettingsService.h @@ -22,7 +22,7 @@ #include #include #include -#include +#include #include #include #include @@ -201,7 +201,7 @@ class WiFiSettings class WiFiSettingsService : public StatefulService { public: - WiFiSettingsService(PsychicHttpServer *server, FS *fs, SecurityManager *securityManager, NotificationEvents *notificationEvents); + WiFiSettingsService(PsychicHttpServer *server, FS *fs, SecurityManager *securityManager, Socket *socket); void initWiFi(); void begin(); @@ -213,7 +213,7 @@ class WiFiSettingsService : public StatefulService SecurityManager *_securityManager; HttpEndpoint _httpEndpoint; FSPersistence _fsPersistence; - NotificationEvents *_notificationEvents; + Socket *_socket; unsigned long _lastConnectionAttempt; unsigned long _lastRssiUpdate; From 39c8c48af769ca86d5880d4849df86069756be72 Mon Sep 17 00:00:00 2001 From: Rune Harlyk Date: Thu, 4 Apr 2024 23:19:22 +0200 Subject: [PATCH 05/13] Removes notificationEvents service in favor of socket --- interface/src/lib/stores/socket.ts | 23 +++++++--- interface/src/routes/+layout.svelte | 2 + lib/framework/BatteryService.cpp | 6 +-- lib/framework/BatteryService.h | 6 +-- lib/framework/ESP32SvelteKit.cpp | 6 +-- lib/framework/ESP32SvelteKit.h | 7 --- lib/framework/NotificationEvents.cpp | 65 ---------------------------- lib/framework/NotificationEvents.h | 45 ------------------- lib/framework/Socket.cpp | 53 +++++++++++++++++++---- lib/framework/Socket.h | 52 ++++++++++++---------- platformio.ini | 2 +- 11 files changed, 101 insertions(+), 166 deletions(-) delete mode 100644 lib/framework/NotificationEvents.cpp delete mode 100644 lib/framework/NotificationEvents.h diff --git a/interface/src/lib/stores/socket.ts b/interface/src/lib/stores/socket.ts index 9a33a0e24..9ee3a6c94 100644 --- a/interface/src/lib/stores/socket.ts +++ b/interface/src/lib/stores/socket.ts @@ -1,6 +1,6 @@ import { writable } from 'svelte/store'; -function createWebSocket(url: string | URL) { +function createWebSocket() { let listeners = new Map void>>(); const { subscribe, set } = writable(false); const socketEvents = ['open', 'close', 'error', 'message', 'unresponsive'] as const; @@ -8,6 +8,12 @@ function createWebSocket(url: string | URL) { let unresponsiveTimeoutId: number; let reconnectTimeoutId: number; let ws: WebSocket; + let socketUrl: string | URL; + + function init(url: string | URL) { + socketUrl = url; + connect(); + } function disconnect(reason: SocketEvent, event?: Event) { ws.close(); @@ -19,7 +25,7 @@ function createWebSocket(url: string | URL) { } function connect() { - ws = new WebSocket(url); + ws = new WebSocket(socketUrl); ws.onopen = (ev) => { set(true); clearTimeout(reconnectTimeoutId); @@ -31,7 +37,13 @@ function createWebSocket(url: string | URL) { }; ws.onmessage = (message) => { resetUnresponsiveCheck(); - const data = JSON.parse(message.data); + let data = message.data; + try { + data = JSON.parse(message.data); + } catch (error) { + console.error('Invalid JSON received', message.data); + return; + } if (data.event) listeners.get(data.event)?.forEach((listener) => listener(data.data)); listeners.get('message')?.forEach((listener) => listener(data)); }; @@ -67,12 +79,11 @@ function createWebSocket(url: string | URL) { send({ event, data }); } - connect(); - return { subscribe, send, sendEvent, + init, on: (event: string, listener: (data: T) => void): (() => void) => { let eventListeners = listeners.get(event); if (!eventListeners) { @@ -94,4 +105,4 @@ function createWebSocket(url: string | URL) { }; } -export const socket = createWebSocket(`ws://${window.location.host}/ws`); +export const socket = createWebSocket(); diff --git a/interface/src/routes/+layout.svelte b/interface/src/routes/+layout.svelte index 787944cf8..c1f133367 100644 --- a/interface/src/routes/+layout.svelte +++ b/interface/src/routes/+layout.svelte @@ -31,6 +31,8 @@ }); const addEventListeners = () => { + const ws_token = $page.data.features.security ? '?access_token=' + $user.bearer_token : '' + socket.init(`ws://${window.location.host}/ws${ws_token}`) socket.on("analytics", handleAnalytics) socket.on("open", handleOpen) socket.on("close", handleClose) diff --git a/lib/framework/BatteryService.cpp b/lib/framework/BatteryService.cpp index d4003cac3..608e4247d 100644 --- a/lib/framework/BatteryService.cpp +++ b/lib/framework/BatteryService.cpp @@ -13,16 +13,14 @@ #include -BatteryService::BatteryService(NotificationEvents *notificationEvents) : _notificationEvents(notificationEvents) +BatteryService::BatteryService(Socket *socket) : _socket(socket) { } void BatteryService::batteryEvent() { StaticJsonDocument<32> doc; - String message; doc["soc"] = _lastSOC; doc["charging"] = _isCharging; - serializeJson(doc, message); - _notificationEvents->send(message, "battery", millis()); + _socket->emit(doc.as(), "battery", 32); } diff --git a/lib/framework/BatteryService.h b/lib/framework/BatteryService.h index ff021a6c3..f3ede7b37 100644 --- a/lib/framework/BatteryService.h +++ b/lib/framework/BatteryService.h @@ -14,12 +14,12 @@ **/ #include -#include +#include class BatteryService { public: - BatteryService(NotificationEvents *notificationEvents); + BatteryService(Socket *socket); void updateSOC(float stateOfCharge) { @@ -35,7 +35,7 @@ class BatteryService private: void batteryEvent(); - NotificationEvents *_notificationEvents; + Socket *_socket; int _lastSOC = 100; boolean _isCharging = false; }; diff --git a/lib/framework/ESP32SvelteKit.cpp b/lib/framework/ESP32SvelteKit.cpp index cd3c591c1..7bc2f763d 100644 --- a/lib/framework/ESP32SvelteKit.cpp +++ b/lib/framework/ESP32SvelteKit.cpp @@ -23,8 +23,7 @@ ESP32SvelteKit::ESP32SvelteKit(PsychicHttpServer *server, unsigned int numberEnd _wifiStatus(server, &_securitySettingsService), _apSettingsService(server, &ESPFS, &_securitySettingsService), _apStatus(server, &_securitySettingsService, &_apSettingsService), - _notificationEvents(server), - _socket(server), + _socket(server, &_securitySettingsService), #if FT_ENABLED(FT_NTP) _ntpSettingsService(server, &ESPFS, &_securitySettingsService), _ntpStatus(server, &_securitySettingsService), @@ -46,7 +45,7 @@ ESP32SvelteKit::ESP32SvelteKit(PsychicHttpServer *server, unsigned int numberEnd _sleepService(server, &_securitySettingsService), #endif #if FT_ENABLED(FT_BATTERY) - _batteryService(&_notificationEvents), + _batteryService(&_socket), #endif #if FT_ENABLED(FT_ANALYTICS) _analyticsService(&_socket), @@ -140,7 +139,6 @@ void ESP32SvelteKit::begin() // Start the services _apStatus.begin(); - _notificationEvents.begin(); _socket.begin(); _apSettingsService.begin(); _factoryResetService.begin(); diff --git a/lib/framework/ESP32SvelteKit.h b/lib/framework/ESP32SvelteKit.h index c05d9fde5..f5ba37b0f 100644 --- a/lib/framework/ESP32SvelteKit.h +++ b/lib/framework/ESP32SvelteKit.h @@ -29,7 +29,6 @@ #include #include #include -#include #include #include #include @@ -86,11 +85,6 @@ class ESP32SvelteKit return &_securitySettingsService; } - NotificationEvents *getNotificationEvents() - { - return &_notificationEvents; - } - Socket *getSocket() { return &_socket; @@ -176,7 +170,6 @@ class ESP32SvelteKit WiFiStatus _wifiStatus; APSettingsService _apSettingsService; APStatus _apStatus; - NotificationEvents _notificationEvents; Socket _socket; #if FT_ENABLED(FT_NTP) NTPSettingsService _ntpSettingsService; diff --git a/lib/framework/NotificationEvents.cpp b/lib/framework/NotificationEvents.cpp deleted file mode 100644 index acdc65661..000000000 --- a/lib/framework/NotificationEvents.cpp +++ /dev/null @@ -1,65 +0,0 @@ -#pragma once - -/** - * 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 - -NotificationEvents::NotificationEvents(PsychicHttpServer *server) : _server(server) -{ -} - -void NotificationEvents::begin() -{ - _eventSource.onOpen([&](PsychicEventSourceClient *client) { // client->send("hello", NULL, millis(), 1000); -#ifdef SERIAL_INFO - Serial.printf("New client connected to Event Source: #%u connected from %s\n", client->socket(), client->remoteIP().toString()); -#endif - }); - _eventSource.onClose([&](PsychicEventSourceClient *client) { // client->send("hello", NULL, millis(), 1000); -#ifdef SERIAL_INFO - Serial.printf("Client closed connection to Event Source: #%u connected from %s\n", client->socket(), client->remoteIP().toString()); -#endif - }); - _server->on(EVENT_NOTIFICATION_SERVICE_PATH, &_eventSource); - - ESP_LOGV("NotificationEvents", "Registered Event Source endpoint: %s", EVENT_NOTIFICATION_SERVICE_PATH); -} - -void NotificationEvents::pushNotification(String message, pushEvent event, int id) -{ - String eventType; - switch (event) - { - case (PUSHERROR): - eventType = "errorToast"; - break; - case (PUSHWARNING): - eventType = "warningToast"; - break; - case (PUSHINFO): - eventType = "infoToast"; - break; - case (PUSHSUCCESS): - eventType = "successToast"; - break; - default: - return; - } - _eventSource.send(message.c_str(), eventType.c_str(), id); -} - -void NotificationEvents::send(String message, String event, int id) -{ - _eventSource.send(message.c_str(), event.c_str(), id); -} diff --git a/lib/framework/NotificationEvents.h b/lib/framework/NotificationEvents.h deleted file mode 100644 index d33193e9c..000000000 --- a/lib/framework/NotificationEvents.h +++ /dev/null @@ -1,45 +0,0 @@ -#pragma once - -/** - * 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 - -#include -#include - -#define EVENT_NOTIFICATION_SERVICE_PATH "/events" - -enum pushEvent -{ - PUSHERROR, - PUSHWARNING, - PUSHINFO, - PUSHSUCCESS -}; - -class NotificationEvents -{ -protected: - PsychicHttpServer *_server; - PsychicEventSource _eventSource; - -public: - NotificationEvents(PsychicHttpServer *server); - - void begin(); - - void pushNotification(String message, pushEvent event, int id = 0); - - void send(String message, String event, int id = 0); -}; diff --git a/lib/framework/Socket.cpp b/lib/framework/Socket.cpp index e51b8a50b..5fdf2c364 100644 --- a/lib/framework/Socket.cpp +++ b/lib/framework/Socket.cpp @@ -13,17 +13,21 @@ #include -Socket::Socket(PsychicHttpServer *server) : +Socket::Socket(PsychicHttpServer *server, SecurityManager *securityManager, AuthenticationPredicate authenticationPredicate) : _server(server), + _securityManager(securityManager), + _authenticationPredicate(authenticationPredicate), _bufferSize(1024) { } void Socket::begin() { + _socket.setFilter(_securityManager->filterRequest(_authenticationPredicate)); _socket.onOpen(std::bind(&Socket::onWSOpen, this, std::placeholders::_1)); _socket.onClose(std::bind(&Socket::onWSClose, this, std::placeholders::_1)); _socket.onFrame(std::bind(&Socket::onFrame, this, std::placeholders::_1, std::placeholders::_2)); + _server->on(EVENT_NOTIFICATION_SERVICE_PATH, &_eventSource); _server->on(WEB_SOCKET_SERVICE_PATH, &_socket); ESP_LOGV("Socket", "Registered socket Source endpoint: %s", WEB_SOCKET_SERVICE_PATH); @@ -76,8 +80,13 @@ void Socket::emit(JsonObject root, String event, size_t dataSize) DynamicJsonDocument doc(dataSize + 100); doc["event"] = event; doc["data"] = root; - String buffer; - serializeJson(doc, buffer); + char buffer[dataSize + 100]; + if (_eventSource.count() > 0) { + serializeJson(root, buffer, sizeof(buffer)); + _eventSource.send(buffer, event.c_str(), millis()); + } + serializeJson(doc, buffer, sizeof(buffer)); + for (int subscription : client_subscriptions[event]) { PsychicWebSocketClient *client = _socket.getClient(subscription); @@ -85,20 +94,23 @@ void Socket::emit(JsonObject root, String event, size_t dataSize) client_subscriptions[event].remove(subscription); continue; } - ESP_LOGD("WebSocketServer", "Emitting event: %s to %s, Message: %s", event.c_str(), client->remoteIP().toString(), buffer.c_str()); - client->sendMessage(buffer.c_str()); + ESP_LOGV("WebSocketServer", "Emitting event: %s to %s, Message: %s", event.c_str(), client->remoteIP().toString(), buffer); + client->sendMessage(buffer); } } void Socket::emit(String message, String event) { + if (_eventSource.count() > 0) { + _eventSource.send(message.c_str(), event.c_str(), millis()); + } if (client_subscriptions[event].size() == 0) return; size_t dataSize = message.length() + 1; DynamicJsonDocument doc(dataSize + 100); doc["event"] = event; doc["data"] = message; - String buffer; - serializeJson(doc, buffer); + char buffer[dataSize + 100]; + serializeJson(doc, buffer, sizeof(buffer)); for (int subscription : client_subscriptions[event]) { PsychicWebSocketClient *client = _socket.getClient(subscription); @@ -106,9 +118,32 @@ void Socket::emit(String message, String event) client_subscriptions[event].remove(subscription); continue; } - ESP_LOGD("WebSocketServer", "Emitting event: %s to %s, Message: %s", event.c_str(), client->remoteIP().toString(), buffer.c_str()); - client->sendMessage(buffer.c_str()); + ESP_LOGV("WebSocketServer", "Emitting event: %s to %s, Message: %s", event.c_str(), client->remoteIP().toString(), buffer); + client->sendMessage(buffer); + } +} + +void Socket::pushNotification(String message, pushEvent event) +{ + String eventType; + switch (event) + { + case (PUSHERROR): + eventType = "errorToast"; + break; + case (PUSHWARNING): + eventType = "warningToast"; + break; + case (PUSHINFO): + eventType = "infoToast"; + break; + case (PUSHSUCCESS): + eventType = "successToast"; + break; + default: + return; } + emit(message, eventType); } void Socket::handleCallbacks(String event, JsonObject &jsonObject){ diff --git a/lib/framework/Socket.h b/lib/framework/Socket.h index 68f5dcb65..da053d42c 100644 --- a/lib/framework/Socket.h +++ b/lib/framework/Socket.h @@ -19,35 +19,26 @@ #include #include -#define WEB_SOCKET_CLIENT_ID_MSG_SIZE 128 - -#define WEB_SOCKET_ORIGIN "wsserver" -#define WEB_SOCKET_ORIGIN_CLIENT_ID_PREFIX "wsserver:" - #define WEB_SOCKET_SERVICE_PATH "/ws" - -#define SOCKET_CONNECT "connect"; -#define SOCKET_DISCONNECT "disconnect"; -#define SOCKET_ERROR "error"; +#define EVENT_NOTIFICATION_SERVICE_PATH "/events" typedef std::function EventCallback; -class Socket +enum pushEvent { -private: - PsychicHttpServer *_server; - PsychicWebSocketHandler _socket; - std::map> client_subscriptions; - std::map> event_callbacks; - void handleCallbacks(String event, JsonObject &jsonObject); - - size_t _bufferSize; - void onWSOpen(PsychicWebSocketClient *client); - void onWSClose(PsychicWebSocketClient *client); - esp_err_t onFrame(PsychicWebSocketRequest *request, httpd_ws_frame *frame); + PUSHERROR, + PUSHWARNING, + PUSHINFO, + PUSHSUCCESS +}; +class Socket +{ public: - Socket(PsychicHttpServer *server); + Socket( + PsychicHttpServer *server, + SecurityManager *_securityManager, + AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_AUTHENTICATED); void begin(); @@ -57,7 +48,24 @@ class Socket void emit(String message, String event); + void pushNotification(String message, pushEvent event); + void broadcast(String message); + +private: + PsychicHttpServer *_server; + PsychicWebSocketHandler _socket; + SecurityManager *_securityManager; + AuthenticationPredicate _authenticationPredicate; + PsychicEventSource _eventSource; + std::map> client_subscriptions; + std::map> event_callbacks; + void handleCallbacks(String event, JsonObject &jsonObject); + + size_t _bufferSize; + void onWSOpen(PsychicWebSocketClient *client); + void onWSClose(PsychicWebSocketClient *client); + esp_err_t onFrame(PsychicWebSocketRequest *request, httpd_ws_frame *frame); };; #endif diff --git a/platformio.ini b/platformio.ini index 1ac47601d..1a1ec8381 100644 --- a/platformio.ini +++ b/platformio.ini @@ -26,7 +26,7 @@ build_flags = -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 ; Uncomment to receive log messages from the ESP Arduino Core - -D CORE_DEBUG_LEVEL=5 + -D CORE_DEBUG_LEVEL=4 ; Move all networking stuff to the protocol core 0 and leave business logic on application core 1 -D ESP32SVELTEKIT_RUNNING_CORE=0 ; Uncomment EMBED_WWW to embed the WWW data in the firmware binary From 57b5d979ead4a4a788c66e0eec8ccdc95499a585 Mon Sep 17 00:00:00 2001 From: Rune Harlyk Date: Fri, 5 Apr 2024 00:06:25 +0200 Subject: [PATCH 06/13] Makes socket connection happen after login --- interface/src/routes/+layout.svelte | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/interface/src/routes/+layout.svelte b/interface/src/routes/+layout.svelte index c1f133367..cb6db92b5 100644 --- a/interface/src/routes/+layout.svelte +++ b/interface/src/routes/+layout.svelte @@ -23,6 +23,12 @@ if ($user.bearer_token !== '') { await validateUser($user); } + user.subscribe((value) => { + if (value.bearer_token !== '') { + const ws_token = $page.data.features.security ? '?access_token=' + $user.bearer_token : '' + socket.init(`ws://${window.location.host}/ws${ws_token}`) + } + }); addEventListeners() }); @@ -31,8 +37,6 @@ }); const addEventListeners = () => { - const ws_token = $page.data.features.security ? '?access_token=' + $user.bearer_token : '' - socket.init(`ws://${window.location.host}/ws${ws_token}`) socket.on("analytics", handleAnalytics) socket.on("open", handleOpen) socket.on("close", handleClose) @@ -74,6 +78,11 @@ console.error('Error:', error); } } + + const handleOpen = () => { + notifications.success('Connection to device established', 5000) + telemetry.setRSSI('found') + } const handleClose = () => notifications.error('Connection to device established', 5000); @@ -82,10 +91,6 @@ const handleErrorToast = (data: string) => notifications.error(data, 5000) const handleSuccessToast = (data: string) => notifications.success(data, 5000) - const handleOpen = () => { - notifications.success('Connection to device established', 5000) - telemetry.setRSSI('found') - } const handleAnalytics = (data: Analytics) => analytics.addData(data) From 659711c045946802cdca43ef35d5be0f4602c64a Mon Sep 17 00:00:00 2001 From: Rune Harlyk Date: Fri, 5 Apr 2024 15:07:35 +0200 Subject: [PATCH 07/13] Formats socket service --- lib/framework/Socket.cpp | 58 +++++++++++++++++++++++++--------------- lib/framework/Socket.h | 16 +++++------ 2 files changed, 44 insertions(+), 30 deletions(-) diff --git a/lib/framework/Socket.cpp b/lib/framework/Socket.cpp index 5fdf2c364..50a6d8407 100644 --- a/lib/framework/Socket.cpp +++ b/lib/framework/Socket.cpp @@ -13,11 +13,10 @@ #include -Socket::Socket(PsychicHttpServer *server, SecurityManager *securityManager, AuthenticationPredicate authenticationPredicate) : - _server(server), - _securityManager(securityManager), - _authenticationPredicate(authenticationPredicate), - _bufferSize(1024) +Socket::Socket(PsychicHttpServer *server, SecurityManager *securityManager, + AuthenticationPredicate authenticationPredicate) + : _server(server), _securityManager(securityManager), _authenticationPredicate(authenticationPredicate), + _bufferSize(1024) { } @@ -40,7 +39,8 @@ void Socket::onWSOpen(PsychicWebSocketClient *client) void Socket::onWSClose(PsychicWebSocketClient *client) { - for (auto &event_subscriptions : client_subscriptions) { + for (auto &event_subscriptions : client_subscriptions) + { event_subscriptions.second.remove(client->socket()); } ESP_LOGI("WebSocketServer", "ws[%s][%u] disconnect", client->remoteIP().toString(), client->socket()); @@ -48,11 +48,13 @@ void Socket::onWSClose(PsychicWebSocketClient *client) esp_err_t Socket::onFrame(PsychicWebSocketRequest *request, httpd_ws_frame *frame) { - ESP_LOGV("WebSocketServer", "ws[%s][%u] opcode[%d]", request->client()->remoteIP().toString(), request->client()->socket(), frame->type); + ESP_LOGV("WebSocketServer", "ws[%s][%u] opcode[%d]", request->client()->remoteIP().toString(), + request->client()->socket(), frame->type); if (frame->type == HTTPD_WS_TYPE_TEXT) { - ESP_LOGV("WebSocketServer", "ws[%s][%u] request: %s", request->client()->remoteIP().toString(), request->client()->socket(), (char *)frame->payload); + ESP_LOGV("WebSocketServer", "ws[%s][%u] request: %s", request->client()->remoteIP().toString(), + request->client()->socket(), (char *)frame->payload); DynamicJsonDocument doc = DynamicJsonDocument(_bufferSize); DeserializationError error = deserializeJson(doc, (char *)frame->payload, frame->len); @@ -60,11 +62,16 @@ esp_err_t Socket::onFrame(PsychicWebSocketRequest *request, httpd_ws_frame *fram if (!error && doc.is()) { String event = doc["event"]; - if (event == "subscribe") { + if (event == "subscribe") + { client_subscriptions[doc["data"]].push_back(request->client()->socket()); - } else if (event == "unsubscribe") { + } + else if (event == "unsubscribe") + { client_subscriptions[doc["data"]].remove(request->client()->socket()); - } else { + } + else + { JsonObject jsonObject = doc["data"].as(); handleCallbacks(event, jsonObject); } @@ -76,35 +83,41 @@ esp_err_t Socket::onFrame(PsychicWebSocketRequest *request, httpd_ws_frame *fram void Socket::emit(JsonObject root, String event, size_t dataSize) { - if (client_subscriptions[event].size() == 0) return; + if (client_subscriptions[event].size() == 0) + return; DynamicJsonDocument doc(dataSize + 100); doc["event"] = event; doc["data"] = root; char buffer[dataSize + 100]; - if (_eventSource.count() > 0) { + if (_eventSource.count() > 0) + { serializeJson(root, buffer, sizeof(buffer)); _eventSource.send(buffer, event.c_str(), millis()); } serializeJson(doc, buffer, sizeof(buffer)); - + for (int subscription : client_subscriptions[event]) { PsychicWebSocketClient *client = _socket.getClient(subscription); - if (client == NULL) { + if (client == NULL) + { client_subscriptions[event].remove(subscription); continue; } - ESP_LOGV("WebSocketServer", "Emitting event: %s to %s, Message: %s", event.c_str(), client->remoteIP().toString(), buffer); + ESP_LOGV("WebSocketServer", "Emitting event: %s to %s, Message: %s", event.c_str(), + client->remoteIP().toString(), buffer); client->sendMessage(buffer); } } void Socket::emit(String message, String event) { - if (_eventSource.count() > 0) { + if (_eventSource.count() > 0) + { _eventSource.send(message.c_str(), event.c_str(), millis()); } - if (client_subscriptions[event].size() == 0) return; + if (client_subscriptions[event].size() == 0) + return; size_t dataSize = message.length() + 1; DynamicJsonDocument doc(dataSize + 100); doc["event"] = event; @@ -114,11 +127,13 @@ void Socket::emit(String message, String event) for (int subscription : client_subscriptions[event]) { PsychicWebSocketClient *client = _socket.getClient(subscription); - if (client == NULL) { + if (client == NULL) + { client_subscriptions[event].remove(subscription); continue; } - ESP_LOGV("WebSocketServer", "Emitting event: %s to %s, Message: %s", event.c_str(), client->remoteIP().toString(), buffer); + ESP_LOGV("WebSocketServer", "Emitting event: %s to %s, Message: %s", event.c_str(), + client->remoteIP().toString(), buffer); client->sendMessage(buffer); } } @@ -146,7 +161,8 @@ void Socket::pushNotification(String message, pushEvent event) emit(message, eventType); } -void Socket::handleCallbacks(String event, JsonObject &jsonObject){ +void Socket::handleCallbacks(String event, JsonObject &jsonObject) +{ for (auto &callback : event_callbacks[event]) { callback(jsonObject); diff --git a/lib/framework/Socket.h b/lib/framework/Socket.h index da053d42c..1ecd6e009 100644 --- a/lib/framework/Socket.h +++ b/lib/framework/Socket.h @@ -14,9 +14,9 @@ * the terms of the LGPL v3 license. See the LICENSE file for details. **/ -#include #include #include +#include #include #define WEB_SOCKET_SERVICE_PATH "/ws" @@ -34,16 +34,14 @@ enum pushEvent class Socket { -public: - Socket( - PsychicHttpServer *server, - SecurityManager *_securityManager, - AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_AUTHENTICATED); + public: + Socket(PsychicHttpServer *server, SecurityManager *_securityManager, + AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_AUTHENTICATED); void begin(); void on(String event, EventCallback callback); - + void emit(JsonObject root, String event, size_t dataSize); void emit(String message, String event); @@ -52,7 +50,7 @@ class Socket void broadcast(String message); -private: + private: PsychicHttpServer *_server; PsychicWebSocketHandler _socket; SecurityManager *_securityManager; @@ -66,6 +64,6 @@ class Socket void onWSOpen(PsychicWebSocketClient *client); void onWSClose(PsychicWebSocketClient *client); esp_err_t onFrame(PsychicWebSocketRequest *request, httpd_ws_frame *frame); -};; +}; #endif From e2452761e7fd6a0d42299cddb64fb356dfac207c Mon Sep 17 00:00:00 2001 From: Rune Harlyk Date: Thu, 4 Apr 2024 21:20:45 +0200 Subject: [PATCH 08/13] Updates lightStateService to use statefull service --- lib/framework/WebSocketServer.h | 140 +++++++------------------------- src/LightStateService.cpp | 20 ++--- src/LightStateService.h | 4 +- 3 files changed, 41 insertions(+), 123 deletions(-) diff --git a/lib/framework/WebSocketServer.h b/lib/framework/WebSocketServer.h index dc7e8e7a3..1f70f6ade 100644 --- a/lib/framework/WebSocketServer.h +++ b/lib/framework/WebSocketServer.h @@ -18,145 +18,61 @@ #include #include #include - -#define WEB_SOCKET_CLIENT_ID_MSG_SIZE 128 - -#define WEB_SOCKET_ORIGIN "wsserver" -#define WEB_SOCKET_ORIGIN_CLIENT_ID_PREFIX "wsserver:" +#include template class WebSocketServer { public: - WebSocketServer(JsonStateReader stateReader, - JsonStateUpdater stateUpdater, - StatefulService *statefulService, - PsychicHttpServer *server, - const char *webSocketPath, - SecurityManager *securityManager, - AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN, - size_t bufferSize = DEFAULT_BUFFER_SIZE) : _stateReader(stateReader), - _stateUpdater(stateUpdater), - _statefulService(statefulService), - _server(server), - _bufferSize(bufferSize), - _webSocketPath(webSocketPath), - _authenticationPredicate(authenticationPredicate), - _securityManager(securityManager) + WebSocketServer( + JsonStateReader stateReader, + JsonStateUpdater stateUpdater, + StatefulService *statefulService, + Socket *socket, + const char *event, + size_t bufferSize = DEFAULT_BUFFER_SIZE + ) : + _stateReader(stateReader), + _stateUpdater(stateUpdater), + _statefulService(statefulService), + _socket(socket), + _bufferSize(bufferSize), + _event(event) { + _socket->on(event, std::bind(&WebSocketServer::updateState, this, std::placeholders::_1)); _statefulService->addUpdateHandler( [&](const String &originId) - { transmitData(nullptr, originId); }, + { syncState(originId); }, false); } - void begin() - { - _webSocket.setFilter(_securityManager->filterRequest(_authenticationPredicate)); - _webSocket.onOpen(std::bind(&WebSocketServer::onWSOpen, - this, - std::placeholders::_1)); - _webSocket.onClose(std::bind(&WebSocketServer::onWSClose, - this, - std::placeholders::_1)); - _webSocket.onFrame(std::bind(&WebSocketServer::onWSFrame, - this, - std::placeholders::_1, - std::placeholders::_2)); - _server->on(_webSocketPath.c_str(), &_webSocket); - - ESP_LOGV("WebSocketServer", "Registered WebSocket handler: %s", _webSocketPath.c_str()); - } - - void onWSOpen(PsychicWebSocketClient *client) - { - - // when a client connects, we transmit it's id and the current payload - transmitId(client); - transmitData(client, WEB_SOCKET_ORIGIN); - ESP_LOGI("WebSocketServer", "ws[%s][%u] connect", client->remoteIP().toString(), client->socket()); - } - - void onWSClose(PsychicWebSocketClient *client) - { - ESP_LOGI("WebSocketServer", "ws[%s][%u] disconnect", client->remoteIP().toString(), client->socket()); - } - - esp_err_t onWSFrame(PsychicWebSocketRequest *request, httpd_ws_frame *frame) - { - ESP_LOGV("WebSocketServer", "ws[%s][%u] opcode[%d]", request->client()->remoteIP().toString(), request->client()->socket(), frame->type); - - if (frame->type == HTTPD_WS_TYPE_TEXT) - { - ESP_LOGV("WebSocketServer", "ws[%s][%u] request: %s", request->client()->remoteIP().toString(), request->client()->socket(), (char *)frame->payload); - - DynamicJsonDocument jsonDocument = DynamicJsonDocument(_bufferSize); - DeserializationError error = deserializeJson(jsonDocument, (char *)frame->payload, frame->len); - - if (!error && jsonDocument.is()) - { - JsonObject jsonObject = jsonDocument.as(); - _statefulService->update(jsonObject, _stateUpdater, clientId(request->client())); - return ESP_OK; - } - } - return ESP_OK; - } - - String clientId(PsychicWebSocketClient *client) - { - return WEB_SOCKET_ORIGIN_CLIENT_ID_PREFIX + String(client->socket()); - } - private: JsonStateReader _stateReader; JsonStateUpdater _stateUpdater; StatefulService *_statefulService; - AuthenticationPredicate _authenticationPredicate; - SecurityManager *_securityManager; - PsychicHttpServer *_server; - PsychicWebSocketHandler _webSocket; - String _webSocketPath; + Socket *_socket; + const char * _event; size_t _bufferSize; - void transmitId(PsychicWebSocketClient *client) + void updateState(JsonObject &root) { - DynamicJsonDocument jsonDocument = DynamicJsonDocument(WEB_SOCKET_CLIENT_ID_MSG_SIZE); - JsonObject root = jsonDocument.to(); - root["type"] = "id"; - root["id"] = clientId(client); + StateUpdateResult outcome = _statefulService->updateWithoutPropagation(root, _stateUpdater); + String originId = "0"; - // serialize the json to a string - String buffer; - serializeJson(jsonDocument, buffer); - client->sendMessage(buffer.c_str()); + if (outcome == StateUpdateResult::CHANGED) + { + _statefulService->callUpdateHandlers(originId); + } } - /** - * Broadcasts the payload to the destination, if provided. Otherwise broadcasts to all clients except the origin, if - * specified. - * - * Original implementation sent clients their own IDs so they could ignore updates they initiated. This approach - * simplifies the client and the server implementation but may not be sufficient for all use-cases. - */ - void transmitData(PsychicWebSocketClient *client, const String &originId) + void syncState(const String &originId) { DynamicJsonDocument jsonDocument = DynamicJsonDocument(_bufferSize); JsonObject root = jsonDocument.to(); - String buffer; _statefulService->read(root, _stateReader); - // serialize the json to a string - serializeJson(jsonDocument, buffer); - if (client) - { - client->sendMessage(buffer.c_str()); - } - else - { - _webSocket.sendAll(buffer.c_str()); - } + _socket->emit(root, _event, _bufferSize); } }; diff --git a/src/LightStateService.cpp b/src/LightStateService.cpp index 0bac5f57c..a6caf7b27 100644 --- a/src/LightStateService.cpp +++ b/src/LightStateService.cpp @@ -27,7 +27,16 @@ LightStateService::LightStateService( server, LIGHT_SETTINGS_ENDPOINT_PATH, securityManager, - AuthenticationPredicates::IS_AUTHENTICATED), + AuthenticationPredicates::IS_AUTHENTICATED + ), + _webSocketServer( + LightState::read, + LightState::update, + this, + socket, + LIGHT_SETTINGS_EVENT, + LIGHT_SETTINGS_MAX_BUFFER_SIZE + ), _mqttPubSub(LightState::homeAssistRead, LightState::homeAssistUpdate, this, mqttClient), _mqttClient(mqttClient), _lightMqttSettingsService(lightMqttSettingsService), @@ -53,8 +62,6 @@ LightStateService::LightStateService( void LightStateService::begin() { _httpEndpoint.begin(); - String event = "led"; - _socket->on(event, std::bind(&LightStateService::handleUpdateLightState, this, std::placeholders::_1)); _state.ledOn = DEFAULT_LED_STATE; onConfigUpdated(); } @@ -64,13 +71,6 @@ void LightStateService::onConfigUpdated() digitalWrite(LED_BUILTIN, _state.ledOn ? 1 : 0); } -void LightStateService::handleUpdateLightState(JsonObject &root) -{ - _state.ledOn = root["led_on"]; - onConfigUpdated(); - ESP_LOGI("LightStateService", "Received light state update led_on: %s", _state.ledOn ? "true" : "false"); -} - void LightStateService::registerConfig() { if (!_mqttClient->connected()) diff --git a/src/LightStateService.h b/src/LightStateService.h index 3a5a05f45..8a677c5bf 100644 --- a/src/LightStateService.h +++ b/src/LightStateService.h @@ -27,7 +27,8 @@ #define ON_STATE "ON" #define LIGHT_SETTINGS_ENDPOINT_PATH "/rest/lightState" -#define LIGHT_SETTINGS_SOCKET_PATH "/ws/lightState" +#define LIGHT_SETTINGS_EVENT "led" +#define LIGHT_SETTINGS_MAX_BUFFER_SIZE 256 class LightState { @@ -90,6 +91,7 @@ class LightStateService : public StatefulService private: HttpEndpoint _httpEndpoint; + WebSocketServer _webSocketServer; MqttPubSub _mqttPubSub; PsychicMqttClient *_mqttClient; LightMqttSettingsService *_lightMqttSettingsService; From e626fc453f8ca0ccc2e22ce9c057610526e07880 Mon Sep 17 00:00:00 2001 From: Rune Harlyk Date: Sun, 7 Apr 2024 19:59:24 +0200 Subject: [PATCH 09/13] Moves staticJsonDoc outside task loop --- lib/framework/AnalyticsService.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/framework/AnalyticsService.h b/lib/framework/AnalyticsService.h index 06b6a57d0..4e0adada3 100644 --- a/lib/framework/AnalyticsService.h +++ b/lib/framework/AnalyticsService.h @@ -47,9 +47,9 @@ class AnalyticsService { TickType_t xLastWakeTime; xLastWakeTime = xTaskGetTickCount(); + StaticJsonDocument doc; while (1) { - StaticJsonDocument doc; doc["uptime"] = millis() / 1000; doc["free_heap"] = ESP.getFreeHeap(); doc["total_heap"] = ESP.getHeapSize(); From 1da3467d519ab822c6002d81ab4cff99095d33f3 Mon Sep 17 00:00:00 2001 From: Rune Harlyk Date: Mon, 8 Apr 2024 13:41:54 +0200 Subject: [PATCH 10/13] Renames SocketService to EventSocket --- lib/framework/AnalyticsService.h | 6 +-- lib/framework/BatteryService.cpp | 2 +- lib/framework/BatteryService.h | 6 +-- lib/framework/DownloadFirmwareService.cpp | 12 ++--- lib/framework/DownloadFirmwareService.h | 6 +-- lib/framework/ESP32SvelteKit.cpp | 2 +- lib/framework/ESP32SvelteKit.h | 6 +-- lib/framework/{Socket.cpp => EventSocket.cpp} | 46 +++++++++---------- lib/framework/{Socket.h => EventSocket.h} | 10 ++-- lib/framework/WebSocketServer.h | 35 +++++--------- lib/framework/WiFiSettingsService.cpp | 35 ++++---------- lib/framework/WiFiSettingsService.h | 6 +-- src/LightStateService.cpp | 35 ++++---------- src/LightStateService.h | 13 ++---- 14 files changed, 81 insertions(+), 139 deletions(-) rename lib/framework/{Socket.cpp => EventSocket.cpp} (76%) rename lib/framework/{Socket.h => EventSocket.h} (85%) diff --git a/lib/framework/AnalyticsService.h b/lib/framework/AnalyticsService.h index 4e0adada3..371416071 100644 --- a/lib/framework/AnalyticsService.h +++ b/lib/framework/AnalyticsService.h @@ -16,7 +16,7 @@ #include #include #include -#include +#include #define MAX_ESP_ANALYTICS_SIZE 1024 #define ANALYTICS_INTERVAL 2000 @@ -24,7 +24,7 @@ class AnalyticsService { public: - AnalyticsService(Socket *socket) : _socket(socket){}; + AnalyticsService(EventSocket *socket) : _socket(socket){}; void begin() { @@ -40,7 +40,7 @@ class AnalyticsService }; protected: - Socket *_socket; + EventSocket *_socket; static void _loopImpl(void *_this) { static_cast(_this)->_loop(); } void _loop() diff --git a/lib/framework/BatteryService.cpp b/lib/framework/BatteryService.cpp index 608e4247d..06160ffcd 100644 --- a/lib/framework/BatteryService.cpp +++ b/lib/framework/BatteryService.cpp @@ -13,7 +13,7 @@ #include -BatteryService::BatteryService(Socket *socket) : _socket(socket) +BatteryService::BatteryService(EventSocket *socket) : _socket(socket) { } diff --git a/lib/framework/BatteryService.h b/lib/framework/BatteryService.h index f3ede7b37..3e4c7d3a2 100644 --- a/lib/framework/BatteryService.h +++ b/lib/framework/BatteryService.h @@ -13,13 +13,13 @@ * the terms of the LGPL v3 license. See the LICENSE file for details. **/ +#include #include -#include class BatteryService { public: - BatteryService(Socket *socket); + BatteryService(EventSocket *socket); void updateSOC(float stateOfCharge) { @@ -35,7 +35,7 @@ class BatteryService private: void batteryEvent(); - Socket *_socket; + EventSocket *_socket; int _lastSOC = 100; boolean _isCharging = false; }; diff --git a/lib/framework/DownloadFirmwareService.cpp b/lib/framework/DownloadFirmwareService.cpp index c19359a4b..7701e7e6b 100644 --- a/lib/framework/DownloadFirmwareService.cpp +++ b/lib/framework/DownloadFirmwareService.cpp @@ -15,7 +15,7 @@ extern const uint8_t rootca_crt_bundle_start[] asm("_binary_src_certs_x509_crt_bundle_bin_start"); -static Socket *_socket = nullptr; +static EventSocket *_socket = nullptr; static int previousProgress = 0; StaticJsonDocument<128> doc; @@ -98,13 +98,9 @@ void updateTask(void *param) vTaskDelete(NULL); } -DownloadFirmwareService::DownloadFirmwareService( - PsychicHttpServer *server, - SecurityManager *securityManager, - Socket *socket) : - _server(server), - _securityManager(securityManager), - _socket(socket) +DownloadFirmwareService::DownloadFirmwareService(PsychicHttpServer *server, SecurityManager *securityManager, + EventSocket *socket) + : _server(server), _securityManager(securityManager), _socket(socket) { } diff --git a/lib/framework/DownloadFirmwareService.h b/lib/framework/DownloadFirmwareService.h index 17309e46e..f06d799c9 100644 --- a/lib/framework/DownloadFirmwareService.h +++ b/lib/framework/DownloadFirmwareService.h @@ -18,9 +18,9 @@ #include #include +#include #include #include -#include #include #include @@ -32,13 +32,13 @@ class DownloadFirmwareService { public: - DownloadFirmwareService(PsychicHttpServer *server, SecurityManager *securityManager, Socket *socket); + DownloadFirmwareService(PsychicHttpServer *server, SecurityManager *securityManager, EventSocket *socket); void begin(); private: SecurityManager *_securityManager; PsychicHttpServer *_server; - Socket *_socket; + EventSocket *_socket; esp_err_t downloadUpdate(PsychicRequest *request, JsonVariant &json); }; diff --git a/lib/framework/ESP32SvelteKit.cpp b/lib/framework/ESP32SvelteKit.cpp index 7bc2f763d..e11b40c04 100644 --- a/lib/framework/ESP32SvelteKit.cpp +++ b/lib/framework/ESP32SvelteKit.cpp @@ -6,7 +6,7 @@ * https://github.com/theelims/ESP32-sveltekit * * Copyright (C) 2018 - 2023 rjwats - * Copyright (C) 2023 theelims + * Copyright (C) 2024 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. diff --git a/lib/framework/ESP32SvelteKit.h b/lib/framework/ESP32SvelteKit.h index f5ba37b0f..6f41932f3 100644 --- a/lib/framework/ESP32SvelteKit.h +++ b/lib/framework/ESP32SvelteKit.h @@ -27,9 +27,9 @@ #include #include #include +#include #include #include -#include #include #include #include @@ -85,7 +85,7 @@ class ESP32SvelteKit return &_securitySettingsService; } - Socket *getSocket() + EventSocket *getSocket() { return &_socket; } @@ -170,7 +170,7 @@ class ESP32SvelteKit WiFiStatus _wifiStatus; APSettingsService _apSettingsService; APStatus _apStatus; - Socket _socket; + EventSocket _socket; #if FT_ENABLED(FT_NTP) NTPSettingsService _ntpSettingsService; NTPStatus _ntpStatus; diff --git a/lib/framework/Socket.cpp b/lib/framework/EventSocket.cpp similarity index 76% rename from lib/framework/Socket.cpp rename to lib/framework/EventSocket.cpp index 50a6d8407..26c77d2b3 100644 --- a/lib/framework/Socket.cpp +++ b/lib/framework/EventSocket.cpp @@ -11,33 +11,33 @@ * the terms of the LGPL v3 license. See the LICENSE file for details. **/ -#include +#include -Socket::Socket(PsychicHttpServer *server, SecurityManager *securityManager, - AuthenticationPredicate authenticationPredicate) +EventSocket::EventSocket(PsychicHttpServer *server, SecurityManager *securityManager, + AuthenticationPredicate authenticationPredicate) : _server(server), _securityManager(securityManager), _authenticationPredicate(authenticationPredicate), _bufferSize(1024) { } -void Socket::begin() +void EventSocket::begin() { _socket.setFilter(_securityManager->filterRequest(_authenticationPredicate)); - _socket.onOpen(std::bind(&Socket::onWSOpen, this, std::placeholders::_1)); - _socket.onClose(std::bind(&Socket::onWSClose, this, std::placeholders::_1)); - _socket.onFrame(std::bind(&Socket::onFrame, this, std::placeholders::_1, std::placeholders::_2)); - _server->on(EVENT_NOTIFICATION_SERVICE_PATH, &_eventSource); - _server->on(WEB_SOCKET_SERVICE_PATH, &_socket); + _socket.onOpen((std::bind(&EventSocket::onWSOpen, this, std::placeholders::_1))); + _socket.onClose(std::bind(&EventSocket::onWSClose, this, std::placeholders::_1)); + _socket.onFrame(std::bind(&EventSocket::onFrame, this, std::placeholders::_1, std::placeholders::_2)); + _server->on(EVENT_SERVICE_PATH, &_eventSource); + _server->on(WS_EVENT_SERVICE_PATH, &_socket); - ESP_LOGV("Socket", "Registered socket Source endpoint: %s", WEB_SOCKET_SERVICE_PATH); + ESP_LOGV("Socket", "Registered socket Source endpoint: %s", EVENT_SERVICE_PATH); } -void Socket::onWSOpen(PsychicWebSocketClient *client) +void EventSocket::onWSOpen(PsychicWebSocketClient *client) { ESP_LOGI("WebSocketServer", "ws[%s][%u] connect", client->remoteIP().toString(), client->socket()); } -void Socket::onWSClose(PsychicWebSocketClient *client) +void EventSocket::onWSClose(PsychicWebSocketClient *client) { for (auto &event_subscriptions : client_subscriptions) { @@ -46,7 +46,7 @@ void Socket::onWSClose(PsychicWebSocketClient *client) ESP_LOGI("WebSocketServer", "ws[%s][%u] disconnect", client->remoteIP().toString(), client->socket()); } -esp_err_t Socket::onFrame(PsychicWebSocketRequest *request, httpd_ws_frame *frame) +esp_err_t EventSocket::onFrame(PsychicWebSocketRequest *request, httpd_ws_frame *frame) { ESP_LOGV("WebSocketServer", "ws[%s][%u] opcode[%d]", request->client()->remoteIP().toString(), request->client()->socket(), frame->type); @@ -81,7 +81,7 @@ esp_err_t Socket::onFrame(PsychicWebSocketRequest *request, httpd_ws_frame *fram return ESP_OK; } -void Socket::emit(JsonObject root, String event, size_t dataSize) +void EventSocket::emit(JsonObject root, String event, size_t dataSize) { if (client_subscriptions[event].size() == 0) return; @@ -98,8 +98,8 @@ void Socket::emit(JsonObject root, String event, size_t dataSize) for (int subscription : client_subscriptions[event]) { - PsychicWebSocketClient *client = _socket.getClient(subscription); - if (client == NULL) + auto *client = _socket.getClient(subscription); + if (!client) { client_subscriptions[event].remove(subscription); continue; @@ -110,7 +110,7 @@ void Socket::emit(JsonObject root, String event, size_t dataSize) } } -void Socket::emit(String message, String event) +void EventSocket::emit(String message, String event) { if (_eventSource.count() > 0) { @@ -126,8 +126,8 @@ void Socket::emit(String message, String event) serializeJson(doc, buffer, sizeof(buffer)); for (int subscription : client_subscriptions[event]) { - PsychicWebSocketClient *client = _socket.getClient(subscription); - if (client == NULL) + auto *client = _socket.getClient(subscription); + if (!client) { client_subscriptions[event].remove(subscription); continue; @@ -138,7 +138,7 @@ void Socket::emit(String message, String event) } } -void Socket::pushNotification(String message, pushEvent event) +void EventSocket::pushNotification(String message, pushEvent event) { String eventType; switch (event) @@ -161,7 +161,7 @@ void Socket::pushNotification(String message, pushEvent event) emit(message, eventType); } -void Socket::handleCallbacks(String event, JsonObject &jsonObject) +void EventSocket::handleCallbacks(String event, JsonObject &jsonObject) { for (auto &callback : event_callbacks[event]) { @@ -169,13 +169,13 @@ void Socket::handleCallbacks(String event, JsonObject &jsonObject) } } -void Socket::on(String event, EventCallback callback) +void EventSocket::on(String event, EventCallback callback) { event_callbacks[event].push_back(callback); log_d("Socket::on"); } -void Socket::broadcast(String message) +void EventSocket::broadcast(String message) { _socket.sendAll(message.c_str()); } diff --git a/lib/framework/Socket.h b/lib/framework/EventSocket.h similarity index 85% rename from lib/framework/Socket.h rename to lib/framework/EventSocket.h index 1ecd6e009..8752b548d 100644 --- a/lib/framework/Socket.h +++ b/lib/framework/EventSocket.h @@ -19,8 +19,8 @@ #include #include -#define WEB_SOCKET_SERVICE_PATH "/ws" -#define EVENT_NOTIFICATION_SERVICE_PATH "/events" +#define EVENT_SERVICE_PATH "/events" +#define WS_EVENT_SERVICE_PATH "/ws" typedef std::function EventCallback; @@ -32,11 +32,11 @@ enum pushEvent PUSHSUCCESS }; -class Socket +class EventSocket { public: - Socket(PsychicHttpServer *server, SecurityManager *_securityManager, - AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_AUTHENTICATED); + EventSocket(PsychicHttpServer *server, SecurityManager *_securityManager, + AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_AUTHENTICATED); void begin(); diff --git a/lib/framework/WebSocketServer.h b/lib/framework/WebSocketServer.h index 1f70f6ade..200de8c40 100644 --- a/lib/framework/WebSocketServer.h +++ b/lib/framework/WebSocketServer.h @@ -15,42 +15,29 @@ * the terms of the LGPL v3 license. See the LICENSE file for details. **/ -#include +#include #include #include -#include +#include template class WebSocketServer { public: - WebSocketServer( - JsonStateReader stateReader, - JsonStateUpdater stateUpdater, - StatefulService *statefulService, - Socket *socket, - const char *event, - size_t bufferSize = DEFAULT_BUFFER_SIZE - ) : - _stateReader(stateReader), - _stateUpdater(stateUpdater), - _statefulService(statefulService), - _socket(socket), - _bufferSize(bufferSize), - _event(event) - { - _socket->on(event, std::bind(&WebSocketServer::updateState, this, std::placeholders::_1)); - _statefulService->addUpdateHandler( - [&](const String &originId) - { syncState(originId); }, - false); - } + WebSocketServer(JsonStateReader stateReader, JsonStateUpdater stateUpdater, StatefulService *statefulService, + EventSocket *socket, const char *event, size_t bufferSize = DEFAULT_BUFFER_SIZE) + : _stateReader(stateReader), _stateUpdater(stateUpdater), _statefulService(statefulService), _socket(socket), + _bufferSize(bufferSize), _event(event) + { + _socket->on(event, std::bind(&WebSocketServer::updateState, this, std::placeholders::_1)); + _statefulService->addUpdateHandler([&](const String &originId) { syncState(originId); }, false); + } private: JsonStateReader _stateReader; JsonStateUpdater _stateUpdater; StatefulService *_statefulService; - Socket *_socket; + EventSocket *_socket; const char * _event; size_t _bufferSize; diff --git a/lib/framework/WiFiSettingsService.cpp b/lib/framework/WiFiSettingsService.cpp index 925ed48ae..d9595989b 100644 --- a/lib/framework/WiFiSettingsService.cpp +++ b/lib/framework/WiFiSettingsService.cpp @@ -14,23 +14,13 @@ #include -WiFiSettingsService::WiFiSettingsService(PsychicHttpServer *server, FS *fs, SecurityManager *securityManager, Socket *socket) : _server(server), - _securityManager(securityManager), - _httpEndpoint(WiFiSettings::read, - WiFiSettings::update, - this, - server, - WIFI_SETTINGS_SERVICE_PATH, - securityManager, - AuthenticationPredicates::IS_ADMIN, - WIFI_SETTINGS_BUFFER_SIZE), - _fsPersistence(WiFiSettings::read, - WiFiSettings::update, - this, - fs, - WIFI_SETTINGS_FILE), - _lastConnectionAttempt(0), - _socket(socket) +WiFiSettingsService::WiFiSettingsService(PsychicHttpServer *server, FS *fs, SecurityManager *securityManager, + EventSocket *socket) + : _server(server), _securityManager(securityManager), + _httpEndpoint(WiFiSettings::read, WiFiSettings::update, this, server, WIFI_SETTINGS_SERVICE_PATH, securityManager, + AuthenticationPredicates::IS_ADMIN, WIFI_SETTINGS_BUFFER_SIZE), + _fsPersistence(WiFiSettings::read, WiFiSettings::update, this, fs, WIFI_SETTINGS_FILE), _lastConnectionAttempt(0), + _socket(socket) { addUpdateHandler([&](const String &originId) { reconfigureWiFiConnection(); }, @@ -220,16 +210,7 @@ void WiFiSettingsService::configureNetwork(wifi_settings_t &network) void WiFiSettingsService::updateRSSI() { - // if WiFi is disconnected send disconnect - if (WiFi.isConnected()) - { - String rssi = String(WiFi.RSSI()); - _socket->emit(rssi, "rssi"); - } - else - { - _socket->emit("disconnected", "rssi"); - } + _socket->emit(WiFi.isConnected() ? String(WiFi.RSSI()) : "disconnected", "rssi"); } void WiFiSettingsService::onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info) diff --git a/lib/framework/WiFiSettingsService.h b/lib/framework/WiFiSettingsService.h index 0b8e84ac2..62dc018bf 100644 --- a/lib/framework/WiFiSettingsService.h +++ b/lib/framework/WiFiSettingsService.h @@ -19,10 +19,10 @@ #include #include #include +#include #include #include #include -#include #include #include #include @@ -201,7 +201,7 @@ class WiFiSettings class WiFiSettingsService : public StatefulService { public: - WiFiSettingsService(PsychicHttpServer *server, FS *fs, SecurityManager *securityManager, Socket *socket); + WiFiSettingsService(PsychicHttpServer *server, FS *fs, SecurityManager *securityManager, EventSocket *socket); void initWiFi(); void begin(); @@ -213,7 +213,7 @@ class WiFiSettingsService : public StatefulService SecurityManager *_securityManager; HttpEndpoint _httpEndpoint; FSPersistence _fsPersistence; - Socket *_socket; + EventSocket *_socket; unsigned long _lastConnectionAttempt; unsigned long _lastRssiUpdate; diff --git a/src/LightStateService.cpp b/src/LightStateService.cpp index a6caf7b27..d2ba5854f 100644 --- a/src/LightStateService.cpp +++ b/src/LightStateService.cpp @@ -14,33 +14,14 @@ #include -LightStateService::LightStateService( - PsychicHttpServer *server, - Socket *socket, - SecurityManager *securityManager, - PsychicMqttClient *mqttClient, - LightMqttSettingsService *lightMqttSettingsService) : - _httpEndpoint( - LightState::read, - LightState::update, - this, - server, - LIGHT_SETTINGS_ENDPOINT_PATH, - securityManager, - AuthenticationPredicates::IS_AUTHENTICATED - ), - _webSocketServer( - LightState::read, - LightState::update, - this, - socket, - LIGHT_SETTINGS_EVENT, - LIGHT_SETTINGS_MAX_BUFFER_SIZE - ), - _mqttPubSub(LightState::homeAssistRead, LightState::homeAssistUpdate, this, mqttClient), - _mqttClient(mqttClient), - _lightMqttSettingsService(lightMqttSettingsService), - _socket(socket) +LightStateService::LightStateService(PsychicHttpServer *server, EventSocket *socket, SecurityManager *securityManager, + PsychicMqttClient *mqttClient, LightMqttSettingsService *lightMqttSettingsService) + : _httpEndpoint(LightState::read, LightState::update, this, server, LIGHT_SETTINGS_ENDPOINT_PATH, securityManager, + AuthenticationPredicates::IS_AUTHENTICATED), + _webSocketServer(LightState::read, LightState::update, this, socket, LIGHT_SETTINGS_EVENT, + LIGHT_SETTINGS_MAX_BUFFER_SIZE), + _mqttPubSub(LightState::homeAssistRead, LightState::homeAssistUpdate, this, mqttClient), _mqttClient(mqttClient), + _lightMqttSettingsService(lightMqttSettingsService), _socket(socket) { // configure led to be output pinMode(LED_BUILTIN, OUTPUT); diff --git a/src/LightStateService.h b/src/LightStateService.h index 8a677c5bf..d4c8daa88 100644 --- a/src/LightStateService.h +++ b/src/LightStateService.h @@ -17,10 +17,10 @@ #include +#include #include #include #include -#include #define DEFAULT_LED_STATE false #define OFF_STATE "OFF" @@ -82,12 +82,9 @@ class LightState class LightStateService : public StatefulService { public: - LightStateService(PsychicHttpServer *server, - Socket *socket, - SecurityManager *securityManager, - PsychicMqttClient *mqttClient, - LightMqttSettingsService *lightMqttSettingsService); - void begin(); + LightStateService(PsychicHttpServer *server, EventSocket *socket, SecurityManager *securityManager, + PsychicMqttClient *mqttClient, LightMqttSettingsService *lightMqttSettingsService); + void begin(); private: HttpEndpoint _httpEndpoint; @@ -95,7 +92,7 @@ class LightStateService : public StatefulService MqttPubSub _mqttPubSub; PsychicMqttClient *_mqttClient; LightMqttSettingsService *_lightMqttSettingsService; - Socket *_socket; + EventSocket *_socket; void registerConfig(); void onConfigUpdated(); From dde1bacc3f2770c36949617ed5db129f55020795 Mon Sep 17 00:00:00 2001 From: Rune Harlyk Date: Mon, 8 Apr 2024 14:01:38 +0200 Subject: [PATCH 11/13] Adds new socket event type: json and binary --- interface/src/lib/stores/socket.ts | 10 ++++++++-- interface/src/routes/+layout.svelte | 3 +++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/interface/src/lib/stores/socket.ts b/interface/src/lib/stores/socket.ts index 9ee3a6c94..ba6e05114 100644 --- a/interface/src/lib/stores/socket.ts +++ b/interface/src/lib/stores/socket.ts @@ -38,14 +38,20 @@ function createWebSocket() { ws.onmessage = (message) => { resetUnresponsiveCheck(); let data = message.data; + + if (data instanceof ArrayBuffer) { + listeners.get('binary')?.forEach((listener) => listener(data)); + return; + } + listeners.get('message')?.forEach((listener) => listener(data)); try { data = JSON.parse(message.data); } catch (error) { - console.error('Invalid JSON received', message.data); + listeners.get('error')?.forEach((listener) => listener(error)); return; } + listeners.get('json')?.forEach((listener) => listener(data)); if (data.event) listeners.get(data.event)?.forEach((listener) => listener(data.data)); - listeners.get('message')?.forEach((listener) => listener(data)); }; ws.onerror = (ev) => disconnect('error', ev); ws.onclose = (ev) => disconnect('close', ev); diff --git a/interface/src/routes/+layout.svelte b/interface/src/routes/+layout.svelte index cb6db92b5..13262443a 100644 --- a/interface/src/routes/+layout.svelte +++ b/interface/src/routes/+layout.svelte @@ -40,6 +40,7 @@ socket.on("analytics", handleAnalytics) socket.on("open", handleOpen) socket.on("close", handleClose) + socket.on("error", handleError) socket.on("rssi", handleNetworkStatus) socket.on("infoToast", handleInfoToast) socket.on("successToast", handleSuccessToast) @@ -86,6 +87,8 @@ const handleClose = () => notifications.error('Connection to device established', 5000); + const handleError = (data: any) => console.error(data) + const handleInfoToast = (data: string) => notifications.info(data, 5000) const handleWarningToast = (data: string) => notifications.warning(data, 5000) const handleErrorToast = (data: string) => notifications.error(data, 5000) From 130e80932320d8bcb951dbad7654509e6bd1db8d Mon Sep 17 00:00:00 2001 From: Rune Harlyk Date: Mon, 15 Apr 2024 21:58:11 +0200 Subject: [PATCH 12/13] Uptimizing socket performance and threadsafty --- interface/src/lib/stores/socket.ts | 5 +- lib/framework/AnalyticsService.h | 8 ++- lib/framework/BatteryService.cpp | 4 +- lib/framework/DownloadFirmwareService.cpp | 22 +++++-- lib/framework/EventSocket.cpp | 73 +++++++---------------- lib/framework/EventSocket.h | 18 +----- lib/framework/WebSocketServer.h | 15 ++--- lib/framework/WiFiSettingsService.cpp | 4 +- src/LightStateService.h | 1 - 9 files changed, 59 insertions(+), 91 deletions(-) diff --git a/interface/src/lib/stores/socket.ts b/interface/src/lib/stores/socket.ts index ba6e05114..693dcaa60 100644 --- a/interface/src/lib/stores/socket.ts +++ b/interface/src/lib/stores/socket.ts @@ -45,13 +45,14 @@ function createWebSocket() { } listeners.get('message')?.forEach((listener) => listener(data)); try { - data = JSON.parse(message.data); + data = JSON.parse(message.data.substring(2)); } catch (error) { listeners.get('error')?.forEach((listener) => listener(error)); return; } listeners.get('json')?.forEach((listener) => listener(data)); - if (data.event) listeners.get(data.event)?.forEach((listener) => listener(data.data)); + const [event, payload] = data; + if (event) listeners.get(event)?.forEach((listener) => listener(payload)); }; ws.onerror = (ev) => disconnect('error', ev); ws.onclose = (ev) => disconnect('close', ev); diff --git a/lib/framework/AnalyticsService.h b/lib/framework/AnalyticsService.h index 371416071..a435065a3 100644 --- a/lib/framework/AnalyticsService.h +++ b/lib/framework/AnalyticsService.h @@ -45,11 +45,12 @@ class AnalyticsService static void _loopImpl(void *_this) { static_cast(_this)->_loop(); } void _loop() { - TickType_t xLastWakeTime; - xLastWakeTime = xTaskGetTickCount(); + TickType_t xLastWakeTime = xTaskGetTickCount(); StaticJsonDocument doc; + char message[MAX_ESP_ANALYTICS_SIZE]; while (1) { + doc.clear(); doc["uptime"] = millis() / 1000; doc["free_heap"] = ESP.getFreeHeap(); doc["total_heap"] = ESP.getHeapSize(); @@ -59,7 +60,8 @@ class AnalyticsService doc["fs_total"] = ESPFS.totalBytes(); doc["core_temp"] = temperatureRead(); - _socket->emit(doc.as(), "analytics", MAX_ESP_ANALYTICS_SIZE); + serializeJson(doc, message); + _socket->emit("analytics", message); vTaskDelayUntil(&xLastWakeTime, ANALYTICS_INTERVAL / portTICK_PERIOD_MS); } diff --git a/lib/framework/BatteryService.cpp b/lib/framework/BatteryService.cpp index 06160ffcd..5a8d19b69 100644 --- a/lib/framework/BatteryService.cpp +++ b/lib/framework/BatteryService.cpp @@ -20,7 +20,9 @@ BatteryService::BatteryService(EventSocket *socket) : _socket(socket) void BatteryService::batteryEvent() { StaticJsonDocument<32> doc; + char message[32]; doc["soc"] = _lastSOC; doc["charging"] = _isCharging; - _socket->emit(doc.as(), "battery", 32); + serializeJson(doc, message); + _socket->emit("battery", message); } diff --git a/lib/framework/DownloadFirmwareService.cpp b/lib/framework/DownloadFirmwareService.cpp index 7701e7e6b..2543735d1 100644 --- a/lib/framework/DownloadFirmwareService.cpp +++ b/lib/framework/DownloadFirmwareService.cpp @@ -21,18 +21,21 @@ StaticJsonDocument<128> doc; void update_started() { + String output; doc["status"] = "preparing"; - _socket->emit(doc.as(), "download_ota", 128); + serializeJson(doc, output); + _socket->emit("download_ota", output.c_str()); } void update_progress(int currentBytes, int totalBytes) { + String output; doc["status"] = "progress"; int progress = ((currentBytes * 100) / totalBytes); if (progress > previousProgress) { doc["progress"] = progress; - _socket->emit(doc.as(), "download_ota", 128); + _socket->emit("download_ota", output.c_str()); ESP_LOGV("Download OTA", "HTTP update process at %d of %d bytes... (%d %%)", currentBytes, totalBytes, progress); } previousProgress = progress; @@ -40,8 +43,10 @@ void update_progress(int currentBytes, int totalBytes) void update_finished() { + String output; doc["status"] = "finished"; - _socket->emit(doc.as(), "download_ota", 128); + serializeJson(doc, output); + _socket->emit("download_ota", output.c_str()); // delay to allow the event to be sent out vTaskDelay(100 / portTICK_PERIOD_MS); @@ -70,7 +75,8 @@ void updateTask(void *param) doc["status"] = "error"; doc["error"] = httpUpdate.getLastErrorString().c_str(); - _socket->emit(doc.as(), "download_ota", 128); + serializeJson(doc, output); + _socket->emit("download_ota", output.c_str()); ESP_LOGE("Download OTA", "HTTP Update failed with error (%d): %s", httpUpdate.getLastError(), httpUpdate.getLastErrorString().c_str()); #ifdef SERIAL_INFO @@ -81,7 +87,8 @@ void updateTask(void *param) doc["status"] = "error"; doc["error"] = "Update failed, has same firmware version"; - _socket->emit(doc.as(), "download_ota", 128); + serializeJson(doc, output); + _socket->emit("download_ota", output.c_str()); ESP_LOGE("Download OTA", "HTTP Update failed, has same firmware version"); #ifdef SERIAL_INFO @@ -132,7 +139,10 @@ esp_err_t DownloadFirmwareService::downloadUpdate(PsychicRequest *request, JsonV doc["progress"] = 0; doc["error"] = ""; - _socket->emit(doc.as(), "download_ota", 250); + String output; + serializeJson(doc, output); + + _socket->emit("download_ota", output.c_str()); if (xTaskCreatePinnedToCore( &updateTask, // Function that should be called diff --git a/lib/framework/EventSocket.cpp b/lib/framework/EventSocket.cpp index 26c77d2b3..7a6800937 100644 --- a/lib/framework/EventSocket.cpp +++ b/lib/framework/EventSocket.cpp @@ -1,18 +1,7 @@ -/** - * 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) 2024 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 +SemaphoreHandle_t clientSubscriptionsMutex = xSemaphoreCreateMutex(); + EventSocket::EventSocket(PsychicHttpServer *server, SecurityManager *securityManager, AuthenticationPredicate authenticationPredicate) : _server(server), _securityManager(securityManager), _authenticationPredicate(authenticationPredicate), @@ -81,61 +70,39 @@ esp_err_t EventSocket::onFrame(PsychicWebSocketRequest *request, httpd_ws_frame return ESP_OK; } -void EventSocket::emit(JsonObject root, String event, size_t dataSize) +void EventSocket::emit(String event, String payload) { - if (client_subscriptions[event].size() == 0) - return; - DynamicJsonDocument doc(dataSize + 100); - doc["event"] = event; - doc["data"] = root; - char buffer[dataSize + 100]; - if (_eventSource.count() > 0) - { - serializeJson(root, buffer, sizeof(buffer)); - _eventSource.send(buffer, event.c_str(), millis()); - } - serializeJson(doc, buffer, sizeof(buffer)); - - for (int subscription : client_subscriptions[event]) - { - auto *client = _socket.getClient(subscription); - if (!client) - { - client_subscriptions[event].remove(subscription); - continue; - } - ESP_LOGV("WebSocketServer", "Emitting event: %s to %s, Message: %s", event.c_str(), - client->remoteIP().toString(), buffer); - client->sendMessage(buffer); - } + emit(event.c_str(), payload.c_str()); } -void EventSocket::emit(String message, String event) +void EventSocket::emit(const char *event, const char *payload) { if (_eventSource.count() > 0) { - _eventSource.send(message.c_str(), event.c_str(), millis()); + _eventSource.send(payload, event, millis()); } - if (client_subscriptions[event].size() == 0) + xSemaphoreTake(clientSubscriptionsMutex, portMAX_DELAY); + auto &subscriptions = client_subscriptions[event]; + if (subscriptions.empty()) + { + xSemaphoreGive(clientSubscriptionsMutex); return; - size_t dataSize = message.length() + 1; - DynamicJsonDocument doc(dataSize + 100); - doc["event"] = event; - doc["data"] = message; - char buffer[dataSize + 100]; - serializeJson(doc, buffer, sizeof(buffer)); + } + String msg = "42[\"" + String(event) + "\"," + String(payload) + "]"; + for (int subscription : client_subscriptions[event]) { auto *client = _socket.getClient(subscription); if (!client) { - client_subscriptions[event].remove(subscription); + subscriptions.remove(subscription); continue; } - ESP_LOGV("WebSocketServer", "Emitting event: %s to %s, Message: %s", event.c_str(), - client->remoteIP().toString(), buffer); - client->sendMessage(buffer); + ESP_LOGV("WebSocketServer", "Emitting event: %s to %s, Message: %s", event, client->remoteIP().toString(), + msg.c_str()); + client->sendMessage(msg.c_str()); } + xSemaphoreGive(clientSubscriptionsMutex); } void EventSocket::pushNotification(String message, pushEvent event) @@ -158,7 +125,7 @@ void EventSocket::pushNotification(String message, pushEvent event) default: return; } - emit(message, eventType); + emit(eventType.c_str(), message.c_str()); } void EventSocket::handleCallbacks(String event, JsonObject &jsonObject) diff --git a/lib/framework/EventSocket.h b/lib/framework/EventSocket.h index 8752b548d..6119d2f2c 100644 --- a/lib/framework/EventSocket.h +++ b/lib/framework/EventSocket.h @@ -1,19 +1,6 @@ #ifndef Socket_h #define Socket_h -/** - * 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) 2024 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 #include #include @@ -42,9 +29,9 @@ class EventSocket void on(String event, EventCallback callback); - void emit(JsonObject root, String event, size_t dataSize); + void emit(String event, String payload); - void emit(String message, String event); + void emit(const char *event, const char *payload); void pushNotification(String message, pushEvent event); @@ -56,6 +43,7 @@ class EventSocket SecurityManager *_securityManager; AuthenticationPredicate _authenticationPredicate; PsychicEventSource _eventSource; + std::map> client_subscriptions; std::map> event_callbacks; void handleCallbacks(String event, JsonObject &jsonObject); diff --git a/lib/framework/WebSocketServer.h b/lib/framework/WebSocketServer.h index 200de8c40..70b585950 100644 --- a/lib/framework/WebSocketServer.h +++ b/lib/framework/WebSocketServer.h @@ -43,23 +43,20 @@ class WebSocketServer void updateState(JsonObject &root) { - StateUpdateResult outcome = _statefulService->updateWithoutPropagation(root, _stateUpdater); - String originId = "0"; - - if (outcome == StateUpdateResult::CHANGED) + if (_statefulService->updateWithoutPropagation(root, _stateUpdater) == StateUpdateResult::CHANGED) { - _statefulService->callUpdateHandlers(originId); + _statefulService->callUpdateHandlers("0"); } } void syncState(const String &originId) { - DynamicJsonDocument jsonDocument = DynamicJsonDocument(_bufferSize); + DynamicJsonDocument jsonDocument{_bufferSize}; JsonObject root = jsonDocument.to(); - + String output; _statefulService->read(root, _stateReader); - - _socket->emit(root, _event, _bufferSize); + serializeJson(root, output); + _socket->emit(_event, output.c_str()); } }; diff --git a/lib/framework/WiFiSettingsService.cpp b/lib/framework/WiFiSettingsService.cpp index d9595989b..110391ba2 100644 --- a/lib/framework/WiFiSettingsService.cpp +++ b/lib/framework/WiFiSettingsService.cpp @@ -210,7 +210,9 @@ void WiFiSettingsService::configureNetwork(wifi_settings_t &network) void WiFiSettingsService::updateRSSI() { - _socket->emit(WiFi.isConnected() ? String(WiFi.RSSI()) : "disconnected", "rssi"); + char buffer[16]; + snprintf(buffer, sizeof(buffer), WiFi.isConnected() ? "%d" : "disconnected", WiFi.RSSI()); + _socket->emit("rssi", buffer); } void WiFiSettingsService::onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info) diff --git a/src/LightStateService.h b/src/LightStateService.h index d4c8daa88..caa49f38f 100644 --- a/src/LightStateService.h +++ b/src/LightStateService.h @@ -96,7 +96,6 @@ class LightStateService : public StatefulService void registerConfig(); void onConfigUpdated(); - void handleUpdateLightState(JsonObject &root); }; #endif From 3788b64864ad5aaf85c242ea93aed3e975945781 Mon Sep 17 00:00:00 2001 From: Rune Harlyk Date: Mon, 15 Apr 2024 22:34:57 +0200 Subject: [PATCH 13/13] Ensures origin is ignore when emitting changes --- lib/framework/EventSocket.cpp | 17 ++++++++++++----- lib/framework/EventSocket.h | 6 ++++-- lib/framework/WebSocketServer.h | 8 ++++---- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/lib/framework/EventSocket.cpp b/lib/framework/EventSocket.cpp index 7a6800937..d2f7b600a 100644 --- a/lib/framework/EventSocket.cpp +++ b/lib/framework/EventSocket.cpp @@ -62,7 +62,7 @@ esp_err_t EventSocket::onFrame(PsychicWebSocketRequest *request, httpd_ws_frame else { JsonObject jsonObject = doc["data"].as(); - handleCallbacks(event, jsonObject); + handleCallbacks(event, jsonObject, request->client()->socket()); } return ESP_OK; } @@ -72,11 +72,17 @@ esp_err_t EventSocket::onFrame(PsychicWebSocketRequest *request, httpd_ws_frame void EventSocket::emit(String event, String payload) { - emit(event.c_str(), payload.c_str()); + emit(event.c_str(), payload.c_str(), ""); } void EventSocket::emit(const char *event, const char *payload) { + emit(event, payload, ""); +} + +void EventSocket::emit(const char *event, const char *payload, const char *originId) +{ + int originSubscriptionId = originId[0] ? atoi(originId) : -1; if (_eventSource.count() > 0) { _eventSource.send(payload, event, millis()); @@ -92,6 +98,8 @@ void EventSocket::emit(const char *event, const char *payload) for (int subscription : client_subscriptions[event]) { + if (subscription == originSubscriptionId) + continue; auto *client = _socket.getClient(subscription); if (!client) { @@ -128,18 +136,17 @@ void EventSocket::pushNotification(String message, pushEvent event) emit(eventType.c_str(), message.c_str()); } -void EventSocket::handleCallbacks(String event, JsonObject &jsonObject) +void EventSocket::handleCallbacks(String event, JsonObject &jsonObject, int originId) { for (auto &callback : event_callbacks[event]) { - callback(jsonObject); + callback(jsonObject, originId); } } void EventSocket::on(String event, EventCallback callback) { event_callbacks[event].push_back(callback); - log_d("Socket::on"); } void EventSocket::broadcast(String message) diff --git a/lib/framework/EventSocket.h b/lib/framework/EventSocket.h index 6119d2f2c..391ec894e 100644 --- a/lib/framework/EventSocket.h +++ b/lib/framework/EventSocket.h @@ -9,7 +9,7 @@ #define EVENT_SERVICE_PATH "/events" #define WS_EVENT_SERVICE_PATH "/ws" -typedef std::function EventCallback; +typedef std::function EventCallback; enum pushEvent { @@ -33,6 +33,8 @@ class EventSocket void emit(const char *event, const char *payload); + void emit(const char *event, const char *payload, const char *originId); + void pushNotification(String message, pushEvent event); void broadcast(String message); @@ -46,7 +48,7 @@ class EventSocket std::map> client_subscriptions; std::map> event_callbacks; - void handleCallbacks(String event, JsonObject &jsonObject); + void handleCallbacks(String event, JsonObject &jsonObject, int originId); size_t _bufferSize; void onWSOpen(PsychicWebSocketClient *client); diff --git a/lib/framework/WebSocketServer.h b/lib/framework/WebSocketServer.h index 70b585950..3e5e22544 100644 --- a/lib/framework/WebSocketServer.h +++ b/lib/framework/WebSocketServer.h @@ -29,7 +29,7 @@ class WebSocketServer : _stateReader(stateReader), _stateUpdater(stateUpdater), _statefulService(statefulService), _socket(socket), _bufferSize(bufferSize), _event(event) { - _socket->on(event, std::bind(&WebSocketServer::updateState, this, std::placeholders::_1)); + _socket->on(event, std::bind(&WebSocketServer::updateState, this, std::placeholders::_1, std::placeholders::_2)); _statefulService->addUpdateHandler([&](const String &originId) { syncState(originId); }, false); } @@ -41,11 +41,11 @@ class WebSocketServer const char * _event; size_t _bufferSize; - void updateState(JsonObject &root) + void updateState(JsonObject &root, int originId) { if (_statefulService->updateWithoutPropagation(root, _stateUpdater) == StateUpdateResult::CHANGED) { - _statefulService->callUpdateHandlers("0"); + _statefulService->callUpdateHandlers(String(originId)); } } @@ -56,7 +56,7 @@ class WebSocketServer String output; _statefulService->read(root, _stateReader); serializeJson(root, output); - _socket->emit(_event, output.c_str()); + _socket->emit(_event, output.c_str(), originId.c_str()); } };