From 184f1abf47e6516fae3967c729ddecb8355d5799 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 11 Dec 2025 22:32:13 +0000 Subject: [PATCH 1/6] Initial plan From 7836a0d309a13685ef84296c99cdb96466c357e5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 11 Dec 2025 22:42:16 +0000 Subject: [PATCH 2/6] Add custom characteristic command processing to WebSocket server Co-authored-by: doudar <17362216+doudar@users.noreply.github.com> --- include/WebsocketAppender.h | 2 ++ src/WebsocketAppender.cpp | 43 +++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/include/WebsocketAppender.h b/include/WebsocketAppender.h index c893ba56..7663dcef 100644 --- a/include/WebsocketAppender.h +++ b/include/WebsocketAppender.h @@ -26,6 +26,8 @@ class WebSocketAppender : public ILogAppender { uint8_t GetClientsCount(); void AddClient(WebsocketsClient* client); void CheckConnectedClients(); + void HandleIncomingMessages(); + void OnMessageReceived(WebsocketsClient& client, WebsocketsMessage message); WebsocketsServer _webSocketsServer; WebsocketsClient* _clients[maxClients]; diff --git a/src/WebsocketAppender.cpp b/src/WebsocketAppender.cpp index bf715e1d..6f993d25 100644 --- a/src/WebsocketAppender.cpp +++ b/src/WebsocketAppender.cpp @@ -7,6 +7,7 @@ // see: https://github.com/gilmaimon/ArduinoWebsockets #include "WebsocketAppender.h" +#include "BLE_Custom_Characteristic.h" WebSocketAppender::WebSocketAppender() { for (uint8_t index = 0; index < maxClients; index++) { @@ -15,6 +16,7 @@ WebSocketAppender::WebSocketAppender() { } void WebSocketAppender::Initialize() { _webSocketsServer.listen(WebSocketAppender::port); } + void WebSocketAppender::Loop() { // CheckConnectedClients(); if (WiFi.status() == WL_CONNECTED && GetClientsCount() < maxClients) { @@ -26,6 +28,9 @@ void WebSocketAppender::Loop() { WebsocketsClient client = _webSocketsServer.accept(); AddClient(new WebsocketsClient(client)); } + + // Handle incoming messages from connected clients + HandleIncomingMessages(); } void WebSocketAppender::Log(const char* message) { @@ -62,6 +67,10 @@ void WebSocketAppender::AddClient(WebsocketsClient* client) { for (uint8_t index = 0; index < maxClients; index++) { if (_clients[index] == NULL) { _clients[index] = client; + // Set up message callback for this client + client->onMessage([this](WebsocketsClient& client, WebsocketsMessage message) { + this->OnMessageReceived(client, message); + }); return; } } @@ -81,4 +90,38 @@ void WebSocketAppender::CheckConnectedClients() { delete client; } } +} + +void WebSocketAppender::HandleIncomingMessages() { + for (uint8_t index = 0; index < maxClients; index++) { + WebsocketsClient* client = _clients[index]; + if (client == NULL) { + continue; + } + + if (client->available()) { + // Poll for messages - the onMessage callback will be triggered + client->poll(); + } + } +} + +void WebSocketAppender::OnMessageReceived(WebsocketsClient& client, WebsocketsMessage message) { + // Only process binary or text messages + if (!message.isBinary() && !message.isText()) { + return; + } + + // Convert message data to std::string for processing + std::string rxValue; + if (message.isBinary()) { + // Binary data - directly use the raw data + rxValue = std::string(message.c_str(), message.length()); + } else { + // Text data - also use as-is (could be hex-encoded or similar) + rxValue = std::string(message.c_str(), message.length()); + } + + // Process the custom characteristic command using the existing BLE processing logic + BLE_ss2kCustomCharacteristic::process(rxValue); } \ No newline at end of file From 62184716da5659dab071fd427bcd2a0dc79643cd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 11 Dec 2025 22:43:14 +0000 Subject: [PATCH 3/6] Update documentation for WebSocket custom characteristic support Co-authored-by: doudar <17362216+doudar@users.noreply.github.com> --- CustomCharacteristic.md | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/CustomCharacteristic.md b/CustomCharacteristic.md index 3943639e..484435ac 100644 --- a/CustomCharacteristic.md +++ b/CustomCharacteristic.md @@ -1,10 +1,21 @@ Please use and refer to the following notes for use of the custom characteristic: -Custom Characteristic for userConfig Variable manipulation via BLE +Custom Characteristic for userConfig Variable manipulation via BLE and WebSocket SMARTSPIN2K_SERVICE_UUID "77776277-7877-7774-4466-896665500000" SMARTSPIN2K_CHARACTERISTIC_UUID "77776277-7877-7774-4466-896665500001" +## Access Methods + +Custom characteristic commands can be sent through multiple channels: + +1. **BLE (Bluetooth Low Energy)**: Using the SmartSpin2k custom characteristic UUID +2. **DirCon Protocol**: Via TCP on port 8081 (see DirCon documentation) +3. **WebSocket**: Via WebSocket connection on port 8080 (NEW) + +The WebSocket server accepts the same binary command format as BLE. This allows web applications +and other WebSocket clients to control SmartSpin2k settings using the same command structure. + An example follows to read/write 26.3kph to simulatedSpeed: simulatedSpeed is a float and first needs to be converted to int by *10 for transmission, so convert 26.3kph to 263 (multiply by 10) @@ -79,7 +90,31 @@ From BLE_common.h *syncMode will disable the movement of the stepper motor by forcing stepperPosition = targetPosition prior to the motor control. While this mode is enabled, it allows the client to set parameters like incline and shifterPosition without moving the motor from it's current position. Once the parameters are set, this mode should be turned back off and SS2K will resume normal operation. +## Using WebSocket for Custom Characteristic Commands + +The WebSocket server (port 8080) accepts binary messages containing custom characteristic commands +in the same format as BLE. To send a command via WebSocket: + +1. Connect to `ws://:8080` +2. Send a binary WebSocket message with the command bytes +3. The response will be sent via BLE notification (if subscribed via BLE) + +Example JavaScript code: +```javascript +const ws = new WebSocket('ws://192.168.1.100:8080'); +ws.binaryType = 'arraybuffer'; + +ws.onopen = () => { + // Write 263 to simulatedSpeed (0x06) + const command = new Uint8Array([0x02, 0x06, 0x07, 0x01]); + ws.send(command.buffer); +}; +``` + +Note: WebSocket responses are currently sent via BLE notification, not back through the WebSocket. +The WebSocket primarily provides logging output. For bidirectional communication with responses, +use the DirCon protocol (port 8081) instead. This characteristic also notifies when a shift is preformed or the button is pressed. -See code for more references/info in BLE_Server.cpp starting on line 534 \ No newline at end of file +See code for more references/info in BLE_Custom_Characteristic.cpp \ No newline at end of file From d8c48bbd49e9a6d0c80f7d2185a2699d8ea12be3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 11 Dec 2025 23:14:52 +0000 Subject: [PATCH 4/6] Send custom characteristic responses back through WebSocket Co-authored-by: doudar <17362216+doudar@users.noreply.github.com> --- CustomCharacteristic.md | 22 ++++++++++++++++------ include/Main.h | 4 ++++ include/WebsocketAppender.h | 2 ++ src/BLE_Custom_Characteristic.cpp | 5 +++++ src/WebsocketAppender.cpp | 29 +++++++++++++++++++++++++++++ 5 files changed, 56 insertions(+), 6 deletions(-) diff --git a/CustomCharacteristic.md b/CustomCharacteristic.md index 484435ac..b9f8dcf5 100644 --- a/CustomCharacteristic.md +++ b/CustomCharacteristic.md @@ -92,12 +92,12 @@ From BLE_common.h ## Using WebSocket for Custom Characteristic Commands -The WebSocket server (port 8080) accepts binary messages containing custom characteristic commands -in the same format as BLE. To send a command via WebSocket: +The WebSocket server (port 8080) provides full bidirectional communication for custom characteristic +commands in the same format as BLE. To send a command and receive responses via WebSocket: 1. Connect to `ws://:8080` 2. Send a binary WebSocket message with the command bytes -3. The response will be sent via BLE notification (if subscribed via BLE) +3. Receive the response as a binary message back through the same WebSocket connection Example JavaScript code: ```javascript @@ -109,11 +109,21 @@ ws.onopen = () => { const command = new Uint8Array([0x02, 0x06, 0x07, 0x01]); ws.send(command.buffer); }; + +ws.onmessage = (event) => { + if (event.data instanceof ArrayBuffer) { + // Binary response from custom characteristic command + const response = new Uint8Array(event.data); + console.log('Response:', Array.from(response).map(b => '0x' + b.toString(16).padStart(2, '0')).join(', ')); + } else { + // Text message (logging output) + console.log('Log:', event.data); + } +}; ``` -Note: WebSocket responses are currently sent via BLE notification, not back through the WebSocket. -The WebSocket primarily provides logging output. For bidirectional communication with responses, -use the DirCon protocol (port 8081) instead. +Note: Only WebSocket clients that have sent at least one command will receive command responses. +Clients that only listen for logs will not receive custom characteristic responses. This characteristic also notifies when a shift is preformed or the button is pressed. diff --git a/include/Main.h b/include/Main.h index bf1bfeb9..4b795f15 100644 --- a/include/Main.h +++ b/include/Main.h @@ -114,3 +114,7 @@ extern SS2K *ss2k; // Main program variable that stores most everything extern userParameters *userConfig; extern RuntimeParameters *rtConfig; + +// WebSocket appender for logging and custom characteristic commands +class WebSocketAppender; +extern WebSocketAppender webSocketAppender; diff --git a/include/WebsocketAppender.h b/include/WebsocketAppender.h index 7663dcef..8ea08232 100644 --- a/include/WebsocketAppender.h +++ b/include/WebsocketAppender.h @@ -17,6 +17,7 @@ class WebSocketAppender : public ILogAppender { WebSocketAppender(); void Log(const char* message); void Loop(); + void SendResponse(const uint8_t* data, size_t length); private: static const uint16_t port = 8080; @@ -31,4 +32,5 @@ class WebSocketAppender : public ILogAppender { WebsocketsServer _webSocketsServer; WebsocketsClient* _clients[maxClients]; + bool _clientSentCommand[maxClients]; }; diff --git a/src/BLE_Custom_Characteristic.cpp b/src/BLE_Custom_Characteristic.cpp index f4d92dca..38fa345f 100644 --- a/src/BLE_Custom_Characteristic.cpp +++ b/src/BLE_Custom_Characteristic.cpp @@ -87,6 +87,7 @@ This characteristic allows for reading and writing various user configuration pa #include #include #include +#include void BLE_ss2kCustomCharacteristic::setupService(NimBLEServer *pServer) { pSmartSpin2kService = spinBLEServer.pServer->createService(SMARTSPIN2K_SERVICE_UUID); @@ -825,6 +826,8 @@ void BLE_ss2kCustomCharacteristic::process(std::string rxValue) { #endif if (returnString == "") { pCharacteristic->setValue(returnValue, returnLength); + // Send response to WebSocket clients that have sent commands + webSocketAppender.SendResponse(returnValue, returnLength); } else { // Need to send a string instead uint8_t returnChar[returnString.length() + 2]; returnChar[0] = cc_success; @@ -833,6 +836,8 @@ void BLE_ss2kCustomCharacteristic::process(std::string rxValue) { returnChar[i + 2] = returnString[i]; } pCharacteristic->setValue(returnChar, returnString.length() + 2); + // Send response to WebSocket clients that have sent commands + webSocketAppender.SendResponse(returnChar, returnString.length() + 2); } pCharacteristic->indicate(); diff --git a/src/WebsocketAppender.cpp b/src/WebsocketAppender.cpp index 6f993d25..3c065906 100644 --- a/src/WebsocketAppender.cpp +++ b/src/WebsocketAppender.cpp @@ -12,6 +12,7 @@ WebSocketAppender::WebSocketAppender() { for (uint8_t index = 0; index < maxClients; index++) { _clients[index] = NULL; + _clientSentCommand[index] = false; } } @@ -45,6 +46,7 @@ void WebSocketAppender::Log(const char* message) { if (!client->available() || !client->send(message)) { _clients[index] = NULL; + _clientSentCommand[index] = false; // Serial.println("Remove disconnected websocket client from Log()."); client->close(); delete client; @@ -67,6 +69,7 @@ void WebSocketAppender::AddClient(WebsocketsClient* client) { for (uint8_t index = 0; index < maxClients; index++) { if (_clients[index] == NULL) { _clients[index] = client; + _clientSentCommand[index] = false; // Set up message callback for this client client->onMessage([this](WebsocketsClient& client, WebsocketsMessage message) { this->OnMessageReceived(client, message); @@ -86,6 +89,7 @@ void WebSocketAppender::CheckConnectedClients() { if (!client->available()) { // Serial.println("Remove disconnected websocket client."); _clients[index] = NULL; + _clientSentCommand[index] = false; client->close(); delete client; } @@ -112,6 +116,14 @@ void WebSocketAppender::OnMessageReceived(WebsocketsClient& client, WebsocketsMe return; } + // Mark this client as having sent a command so it receives responses + for (uint8_t index = 0; index < maxClients; index++) { + if (_clients[index] == &client) { + _clientSentCommand[index] = true; + break; + } + } + // Convert message data to std::string for processing std::string rxValue; if (message.isBinary()) { @@ -124,4 +136,21 @@ void WebSocketAppender::OnMessageReceived(WebsocketsClient& client, WebsocketsMe // Process the custom characteristic command using the existing BLE processing logic BLE_ss2kCustomCharacteristic::process(rxValue); +} + +void WebSocketAppender::SendResponse(const uint8_t* data, size_t length) { + // Send response to all clients that have previously sent a command + for (uint8_t index = 0; index < maxClients; index++) { + WebsocketsClient* client = _clients[index]; + if (client == NULL || !_clientSentCommand[index]) { + continue; + } + + if (!client->available() || !client->sendBinary((const char*)data, length)) { + _clients[index] = NULL; + _clientSentCommand[index] = false; + client->close(); + delete client; + } + } } \ No newline at end of file From e35366e0aa43f9b629d58a76e40005e9444973d6 Mon Sep 17 00:00:00 2001 From: Anthony Doud Date: Thu, 11 Dec 2025 17:33:48 -0600 Subject: [PATCH 5/6] added include for websocket appender --- src/BLE_Custom_Characteristic.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/BLE_Custom_Characteristic.cpp b/src/BLE_Custom_Characteristic.cpp index 38fa345f..068f9259 100644 --- a/src/BLE_Custom_Characteristic.cpp +++ b/src/BLE_Custom_Characteristic.cpp @@ -88,6 +88,7 @@ This characteristic allows for reading and writing various user configuration pa #include #include #include +#include void BLE_ss2kCustomCharacteristic::setupService(NimBLEServer *pServer) { pSmartSpin2kService = spinBLEServer.pServer->createService(SMARTSPIN2K_SERVICE_UUID); From 5db2639203ad0f8f0c46be813aafa5f2eab9d44c Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Thu, 11 Dec 2025 23:34:24 +0000 Subject: [PATCH 6/6] Update changelog for version 25.12.11 --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09734ba3..2f0708bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +### Changed + +### Hardware + + +## [25.12.11] + +### Added + ### Changed - links card update