-
Notifications
You must be signed in to change notification settings - Fork 571
Open
Description
So it's quite weird. When the web socket server starts. It reboots the arduino. Can this library work with ethernet?
I am using;
Arduino Nano ESP32 with Headers [ABX00083] - ESP32-S3, USB-C, Wi-Fi, Bluetooth, HID Support, MicroPython Compatible
https://www.amazon.co.uk/dp/B0C947BHK5?ref=ppx_yo2ov_dt_b_fed_asin_title
and
AZDelivery ENC28J60 Ethernet Shield LAN Network Module compatible with Arduino including E-Book!
https://www.amazon.co.uk/dp/B07D8SV85Q?ref=ppx_yo2ov_dt_b_fed_asin_title
#include <EthernetENC.h>
#include <WebSocketsServer.h> // Links2004 WebSocket library
#include <ArduinoJson.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define LED_PIN 46
#define OLED_RESET -1
#define I2C_SDA 5
#define I2C_SCL 4
// ENC28J60 pins for SPI communication
#define CS_PIN 7
#define MISO_PIN 12
#define MOSI_PIN 11
#define SCK_PIN 13
// Network configuration
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(192, 168, 0, 177);
IPAddress gateway(192, 168, 0, 1);
IPAddress subnet(255, 255, 255, 0);
WebSocketsServer webSocket = WebSocketsServer(80);
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
bool displayAvailable = false;
#define MAX_CLIENTS 10
#define MAX_ROOMS 5
#define MAX_LOG_LINES 5
#define MAX_PLAYERS_PER_ROOM 4
// Message queue structure
#define MAX_QUEUE_SIZE 10
struct QueuedMessage {
String message;
bool used;
};
struct VirtualLockbox {
bool isLocked;
int deviceCount;
bool isInitialized;
};
struct Room {
String roomId;
VirtualLockbox lockbox;
bool isActive;
};
struct MessageQueue {
QueuedMessage messages[MAX_QUEUE_SIZE];
int head;
int tail;
};
MessageQueue messageQueues[MAX_CLIENTS];
Room rooms[MAX_ROOMS];
// Circular buffer for log messages
String logLines[MAX_LOG_LINES];
int currentLogLine = 0;
struct WebSocketClient {
uint8_t num; // WebSocket client number
String clientId;
String roomId;
bool isHost;
bool isExternal;
bool isPlayer;
String label;
};
WebSocketClient wsClients[MAX_CLIENTS];
uint8_t clientIds[MAX_ROOMS][MAX_CLIENTS];
uint8_t clientCount[MAX_ROOMS] = {0};
uint8_t playerCount[MAX_ROOMS] = {0};
// Queue management functions
void initQueue(int clientIndex) {
messageQueues[clientIndex].head = 0;
messageQueues[clientIndex].tail = 0;
for (int i = 0; i < MAX_QUEUE_SIZE; i++) {
messageQueues[clientIndex].messages[i].used = false;
}
}
void queueMessage(int clientIndex, const String& message) {
MessageQueue& queue = messageQueues[clientIndex];
int nextTail = (queue.tail + 1) % MAX_QUEUE_SIZE;
if (nextTail != queue.head) {
queue.messages[queue.tail].message = message;
queue.messages[queue.tail].used = true;
queue.tail = nextTail;
}
}
void setLockboxLED(bool locked) {
digitalWrite(LED_PIN, !locked ? HIGH : LOW);
addLog(String("LED ") + (locked ? "ON" : "OFF"));
}
void sendQueuedMessages(uint8_t num) {
int clientIndex = -1;
for (int i = 0; i < MAX_CLIENTS; i++) {
if (wsClients[i].num == num) {
clientIndex = i;
break;
}
}
if (clientIndex != -1) {
MessageQueue& queue = messageQueues[clientIndex];
while (queue.head != queue.tail) {
if (queue.messages[queue.head].used) {
webSocket.sendTXT(num, queue.messages[queue.head].message);
queue.messages[queue.head].used = false;
}
queue.head = (queue.head + 1) % MAX_QUEUE_SIZE;
}
}
}
void addLog(const String& message) {
Serial.println(message);
logLines[currentLogLine] = message;
currentLogLine = (currentLogLine + 1) % MAX_LOG_LINES;
if (displayAvailable) {
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(0, 0);
display.print("IP: ");
display.println(Ethernet.localIP());
display.drawLine(0, 9, SCREEN_WIDTH - 1, 9, WHITE);
int y = 11;
for (int i = 0; i < MAX_LOG_LINES; i++) {
int index = (currentLogLine - 1 - i + MAX_LOG_LINES) % MAX_LOG_LINES;
if (!logLines[index].isEmpty()) {
display.setCursor(0, y);
String truncated = logLines[index];
if (truncated.length() > 21) {
truncated = truncated.substring(0, 18) + "...";
}
display.println(truncated);
y += 10;
}
}
display.display();
}
}
int findRoomIndex(const String& roomId) {
for (int i = 0; i < MAX_ROOMS; i++) {
if (clientCount[i] > 0 && wsClients[clientIds[i][0]].roomId == roomId) {
return i;
}
}
for (int i = 0; i < MAX_ROOMS; i++) {
if (clientCount[i] == 0) {
return i;
}
}
return -1;
}
void broadcastToRoom(uint8_t sender, String& message) { // Changed to non-const reference
String roomId;
for (int i = 0; i < MAX_CLIENTS; i++) {
if (wsClients[i].num == sender) {
roomId = wsClients[i].roomId;
break;
}
}
if (!roomId.isEmpty()) {
int roomIndex = findRoomIndex(roomId);
if (roomIndex != -1) {
for (int i = 0; i < clientCount[roomIndex]; i++) {
uint8_t clientNum = wsClients[clientIds[roomIndex][i]].num;
webSocket.sendTXT(clientNum, message);
}
}
}
}
void initializeRoomLockbox(const String& roomId) {
int roomIndex = findRoomIndex(roomId);
if (roomIndex != -1) {
rooms[roomIndex].roomId = roomId;
rooms[roomIndex].isActive = true;
rooms[roomIndex].lockbox = {false, 0, true};
DynamicJsonDocument doc(256);
doc["type"] = "lockbox_init";
String message;
serializeJson(doc, message);
for (int i = 0; i < clientCount[roomIndex]; i++) {
uint8_t clientNum = wsClients[clientIds[roomIndex][i]].num;
webSocket.sendTXT(clientNum, message);
}
addLog("Room " + roomId + " created with virtual lockbox");
}
}
void cleanupRoomLockbox(const String& roomId) {
int roomIndex = findRoomIndex(roomId);
if (roomIndex != -1) {
rooms[roomIndex].isActive = false;
rooms[roomIndex].lockbox = {false, 0, false};
setLockboxLED(false);
addLog("Room " + roomId + " destroyed, lockbox cleaned up");
}
}
void handleLockboxMessage(uint8_t num, const String& type, const JsonObject& data) {
String roomId;
for (int i = 0; i < MAX_CLIENTS; i++) {
if (wsClients[i].num == num) {
roomId = wsClients[i].roomId;
break;
}
}
int roomIndex = findRoomIndex(roomId);
if (roomIndex == -1 || !rooms[roomIndex].isActive) return;
VirtualLockbox& lockbox = rooms[roomIndex].lockbox;
if (type == "lockbox_init") {
lockbox.deviceCount = 0;
lockbox.isLocked = false;
lockbox.isInitialized = true;
setLockboxLED(false);
DynamicJsonDocument doc(256);
doc["type"] = "lockbox_init";
String message;
serializeJson(doc, message);
broadcastToRoom(num, message);
addLog("Lockbox initialized in room: " + roomId);
}
else if (type == "lockbox_device_added") {
if (!lockbox.isInitialized) return;
lockbox.deviceCount++;
DynamicJsonDocument doc(256);
doc["type"] = "lockbox_device_added";
doc["data"]["deviceCount"] = lockbox.deviceCount;
String message;
serializeJson(doc, message);
broadcastToRoom(num, message);
addLog("Device added to lockbox in room " + roomId + ": " + String(lockbox.deviceCount));
if (lockbox.deviceCount >= 4 && !lockbox.isLocked) {
lockbox.isLocked = true;
setLockboxLED(true);
DynamicJsonDocument lockDoc(256);
lockDoc["type"] = "devices_locked";
lockDoc["data"]["locked"] = true;
String lockMessage;
serializeJson(lockDoc, lockMessage);
broadcastToRoom(num, lockMessage);
addLog("Lockbox locked in room: " + roomId);
}
}
else if (type == "unlock_devices") {
if (!lockbox.isInitialized) return;
lockbox.isLocked = false;
setLockboxLED(false);
DynamicJsonDocument doc(256);
doc["type"] = "unlock_devices";
String message;
serializeJson(doc, message);
broadcastToRoom(num, message);
addLog("Lockbox unlocked in room: " + roomId);
}
}
void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) {
switch(type) {
case WStype_DISCONNECTED: {
Serial.printf("[WebSocket] Client #%u Disconnected\n", num);
String clientId;
String roomId;
bool wasHost = false;
bool wasPlayer = false;
int clientIndex = -1;
// Find the disconnected client's information
for (int i = 0; i < MAX_CLIENTS; i++) {
if (wsClients[i].num == num) {
clientId = wsClients[i].clientId;
roomId = wsClients[i].roomId;
wasHost = wsClients[i].isHost;
wasPlayer = wsClients[i].isPlayer;
clientIndex = i;
break;
}
}
if (clientIndex != -1) {
int roomIndex = findRoomIndex(roomId);
if (roomIndex != -1) {
// Update player count if necessary
if (wasPlayer) {
playerCount[roomIndex]--;
}
// Remove client from room's client list
for (int j = 0; j < clientCount[roomIndex]; j++) {
if (clientIds[roomIndex][j] == clientIndex) {
// Shift remaining clients
for (int k = j; k < clientCount[roomIndex] - 1; k++) {
clientIds[roomIndex][k] = clientIds[roomIndex][k + 1];
}
clientCount[roomIndex]--;
break;
}
}
// If room is empty, clean up
if (clientCount[roomIndex] == 0) {
cleanupRoomLockbox(roomId);
playerCount[roomIndex] = 0;
addLog("Room " + roomId + " reset");
} else {
// Notify remaining clients about disconnection
DynamicJsonDocument doc(256);
doc["type"] = "user_disconnected";
doc["data"]["clientId"] = clientId;
doc["data"]["isPlayer"] = wasPlayer;
doc["data"]["wasHost"] = wasHost;
doc["data"]["playerCount"] = playerCount[roomIndex];
String message;
serializeJson(doc, message);
for (int j = 0; j < clientCount[roomIndex]; j++) {
uint8_t targetNum = wsClients[clientIds[roomIndex][j]].num;
webSocket.sendTXT(targetNum, message);
}
// Reassign host if necessary
if (wasHost) {
reassignHost(roomId);
}
}
}
// Clear client data
wsClients[clientIndex] = {0, "", "", false, false, false, ""};
initQueue(clientIndex);
addLog("Client disconnected: " + clientId);
}
break;
}
case WStype_CONNECTED: {
Serial.printf("[WebSocket] Client #%u Connected\n", num);
String url = String((char*)payload);
String clientId = "";
String roomId = "";
bool isPlayer = false;
String label = "";
// Extract parameters from connection URL
int clientIdStart = url.indexOf("clientId=");
int roomIdStart = url.indexOf("roomId=");
int isPlayerStart = url.indexOf("isPlayer=");
int labelStart = url.indexOf("label=");
if (clientIdStart != -1) {
clientIdStart += 9; // Length of "clientId="
int clientIdEnd = url.indexOf('&', clientIdStart);
if (clientIdEnd == -1) clientIdEnd = url.length();
clientId = url.substring(clientIdStart, clientIdEnd);
}
if (roomIdStart != -1) {
roomIdStart += 7; // Length of "roomId="
int roomIdEnd = url.indexOf('&', roomIdStart);
if (roomIdEnd == -1) roomIdEnd = url.length();
roomId = url.substring(roomIdStart, roomIdEnd);
}
if (isPlayerStart != -1) {
isPlayerStart += 9; // Length of "isPlayer="
int isPlayerEnd = url.indexOf('&', isPlayerStart);
if (isPlayerEnd == -1) isPlayerEnd = url.length();
String isPlayerStr = url.substring(isPlayerStart, isPlayerEnd);
isPlayer = (isPlayerStr == "true");
}
if (labelStart != -1) {
labelStart += 6; // Length of "label="
int labelEnd = url.indexOf('&', labelStart);
if (labelEnd == -1) labelEnd = url.length();
label = url.substring(labelStart, labelEnd);
}
if (clientId.isEmpty()) {
webSocket.disconnect(num);
return;
}
// Check for reconnection
bool isReconnection = false;
int existingIndex = -1;
for (int i = 0; i < MAX_CLIENTS; i++) {
if (wsClients[i].num != 0 && wsClients[i].clientId == clientId) {
existingIndex = i;
isReconnection = true;
break;
}
}
// Handle reconnection
if (isReconnection) {
addLog("Client reconnecting: " + clientId);
wsClients[existingIndex] = {0, "", "", false, false, false, ""};
}
// Find empty slot for new connection
int clientIndex = -1;
for (int i = 0; i < MAX_CLIENTS; i++) {
if (wsClients[i].num == 0) {
clientIndex = i;
break;
}
}
if (clientIndex != -1) {
int roomIndex = findRoomIndex(roomId);
if (roomIndex == -1) {
roomIndex = findRoomIndex("");
}
if (roomIndex != -1) {
if (clientCount[roomIndex] == 0) {
initializeRoomLockbox(roomId);
}
// Check player limit
if (isPlayer && playerCount[roomIndex] >= MAX_PLAYERS_PER_ROOM) {
webSocket.disconnect(num);
addLog("Room full - connection rejected");
return;
}
bool isHost = (isPlayer && playerCount[roomIndex] == 0);
wsClients[clientIndex] = {num, clientId, roomId, isHost, false, isPlayer, label};
clientIds[roomIndex][clientCount[roomIndex]++] = clientIndex;
if (isPlayer) {
playerCount[roomIndex]++;
addLog("Player count in room " + roomId + ": " + String(playerCount[roomIndex]));
}
initQueue(clientIndex);
sendQueuedMessages(num);
// Notify room about new connection
DynamicJsonDocument doc(256);
doc["type"] = "user_entered";
doc["data"]["clientId"] = clientId;
doc["data"]["isPlayer"] = isPlayer;
doc["data"]["playerCount"] = playerCount[roomIndex];
String message;
serializeJson(doc, message);
for (int i = 0; i < clientCount[roomIndex]; i++) {
uint8_t targetNum = wsClients[clientIds[roomIndex][i]].num;
if (targetNum != num) { // Don't send to the new client
webSocket.sendTXT(targetNum, message);
}
}
if (isHost) {
notifyHostAssigned(roomId, clientId);
}
addLog(String(isPlayer ? "Player" : "Spectator") +
" connected: " + clientId +
(isReconnection ? " (reconnected)" : ""));
} else {
addLog("No room available");
webSocket.disconnect(num);
}
} else {
addLog("Max clients reached");
webSocket.disconnect(num);
}
break;
}
case WStype_TEXT: {
Serial.printf("[WebSocket] Received text from #%u\n", num);
String message = String((char*)payload);
DynamicJsonDocument doc(1024);
DeserializationError error = deserializeJson(doc, message);
if (error) {
addLog("JSON parse failed");
return;
}
String type = doc["type"];
// Handle update_socket_client_id message
if (type == "update_socket_client_id") {
String oldClientId = doc["data"]["oldClientId"];
String newClientId = doc["data"]["newClientId"];
for (int i = 0; i < MAX_CLIENTS; i++) {
if (wsClients[i].num == num && wsClients[i].clientId == oldClientId) {
wsClients[i].clientId = newClientId;
addLog("Updated client ID: " + oldClientId + " -> " + newClientId);
break;
}
}
return;
}
// Handle lockbox messages
if (type == "lockbox_init" || type == "lockbox_device_added" || type == "unlock_devices") {
handleLockboxMessage(num, type, doc["data"]);
return;
}
// Broadcast other messages to room
broadcastToRoom(num, message);
break;
}
case WStype_BIN:
case WStype_ERROR:
case WStype_FRAGMENT_TEXT_START:
case WStype_FRAGMENT_BIN_START:
case WStype_FRAGMENT:
case WStype_FRAGMENT_FIN:
default:
break;
}
}
void notifyHostAssigned(const String& roomId, const String& clientId) {
DynamicJsonDocument doc(256);
doc["type"] = "host_assigned";
doc["data"]["isHost"] = true;
doc["data"]["clientId"] = clientId;
String message;
serializeJson(doc, message);
addLog("Host: " + clientId);
int roomIndex = findRoomIndex(roomId);
if (roomIndex != -1) {
for (int i = 0; i < clientCount[roomIndex]; i++) {
uint8_t clientNum = wsClients[clientIds[roomIndex][i]].num;
webSocket.sendTXT(clientNum, message);
}
}
}
void reassignHost(const String& roomId) {
int roomIndex = findRoomIndex(roomId);
if (roomIndex == -1 || clientCount[roomIndex] == 0) {
return;
}
// Find first non-host, non-external client
for (int i = 0; i < clientCount[roomIndex]; i++) {
uint8_t id = clientIds[roomIndex][i];
if (!wsClients[id].isHost && !wsClients[id].isExternal) {
wsClients[id].isHost = true;
notifyHostAssigned(roomId, wsClients[id].clientId);
addLog("New host assigned: " + wsClients[id].clientId);
break;
}
}
}
bool parseUrlParams(const String& url, String& clientId, String& roomId, bool& isPlayer) {
// Example URL format: /ws?clientId=123&roomId=456&isPlayer=true
int clientIdStart = url.indexOf("clientId=");
int roomIdStart = url.indexOf("roomId=");
int isPlayerStart = url.indexOf("isPlayer=");
if (clientIdStart == -1 || roomIdStart == -1) {
return false;
}
clientIdStart += 9; // Length of "clientId="
int clientIdEnd = url.indexOf('&', clientIdStart);
if (clientIdEnd == -1) clientIdEnd = url.length();
clientId = url.substring(clientIdStart, clientIdEnd);
roomIdStart += 7; // Length of "roomId="
int roomIdEnd = url.indexOf('&', roomIdStart);
if (roomIdEnd == -1) roomIdEnd = url.length();
roomId = url.substring(roomIdStart, roomIdEnd);
if (isPlayerStart != -1) {
isPlayerStart += 9; // Length of "isPlayer="
int isPlayerEnd = url.indexOf('&', isPlayerStart);
if (isPlayerEnd == -1) isPlayerEnd = url.length();
String isPlayerStr = url.substring(isPlayerStart, isPlayerEnd);
isPlayer = (isPlayerStr == "true");
} else {
isPlayer = false;
}
return true;
}
bool initializeDisplay() {
Serial.println("Attempting to initialize OLED display...");
Wire.begin(I2C_SDA, I2C_SCL);
Wire.beginTransmission(0x3C);
if (Wire.endTransmission() != 0) {
Serial.println("Display not found on I2C bus");
return false;
}
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println("SSD1306 allocation failed");
return false;
}
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(0,0);
display.println("Initializing...");
display.display();
Serial.println("OLED display initialized successfully");
return true;
}
bool initializeEthernet() {
Serial.println("Initializing Ethernet...");
SPI.begin(SCK_PIN, MISO_PIN, MOSI_PIN, CS_PIN);
Ethernet.init(CS_PIN);
if (Ethernet.begin(mac)) {
Serial.print("DHCP assigned IP: ");
Serial.println(Ethernet.localIP());
return true;
} else {
Serial.println("DHCP failed, trying static IP...");
Ethernet.begin(mac, ip, gateway, subnet);
if (Ethernet.hardwareStatus() == EthernetNoHardware) {
Serial.println("Ethernet shield not found");
return false;
}
if (Ethernet.linkStatus() == LinkOFF) {
Serial.println("Ethernet cable is not connected");
return false;
}
Serial.print("Static IP: ");
Serial.println(Ethernet.localIP());
return true;
}
}
void setup() {
Serial.begin(115200);
Serial.println("Starting up...");
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, HIGH);
// Initialize rooms
for (int i = 0; i < MAX_ROOMS; i++) {
rooms[i].isActive = false;
rooms[i].lockbox = {false, 0, false};
}
// Initialize client array
for (int i = 0; i < MAX_CLIENTS; i++) {
wsClients[i] = {0, "", "", false, false, false, ""};
initQueue(i);
}
// Initialize display
displayAvailable = initializeDisplay();
if (!displayAvailable) {
Serial.println("WARNING: Display initialization failed - continuing without display");
}
// Initialize Ethernet with verification
if (!initializeEthernet()) {
Serial.println("Failed to initialize Ethernet");
digitalWrite(LED_PIN, LOW);
return;
}
// Wait for Ethernet to be fully ready
delay(1000);
// Verify Ethernet status before starting WebSocket
if (Ethernet.linkStatus() == LinkON && Ethernet.localIP()) {
Serial.println("Ethernet is ready. Starting WebSocket server...");
// Initialize WebSocket server
webSocket.begin();
webSocket.onEvent(webSocketEvent);
addLog("WebSocket Server Ready");
addLog("IP: " + Ethernet.localIP().toString());
digitalWrite(LED_PIN, HIGH); // Indicate successful setup
} else {
Serial.println("Ethernet not ready. Cannot start WebSocket server.");
digitalWrite(LED_PIN, LOW);
}
}
// Modified loop with debug prints and state tracking
void loop() {
static unsigned long lastDebugPrint = 0;
static bool wsActive = false;
static int loopCount = 0;
// Basic loop counter for debugging
loopCount++;
// Print debug info every 5 seconds
if (millis() - lastDebugPrint >= 5000) {
Serial.printf("Loop count: %d, Ethernet status: %s, IP: %s\n",
loopCount,
Ethernet.linkStatus() == LinkON ? "ON" : "OFF",
Ethernet.localIP().toString().c_str());
lastDebugPrint = millis();
loopCount = 0;
}
// Check Ethernet status
if (Ethernet.linkStatus() == LinkON && Ethernet.localIP()) {
if (!wsActive) {
Serial.println("Ethernet is connected, starting WebSocket server");
webSocket.begin();
webSocket.onEvent(webSocketEvent);
wsActive = true;
}
// Only run WebSocket loop if we're properly connected
webSocket.loop();
} else {
if (wsActive) {
Serial.println("Ethernet disconnected, stopping WebSocket server");
wsActive = false;
}
}
// Maintain Ethernet connection
Ethernet.maintain();
// Monitor display connection
static unsigned long lastDisplayCheck = 0;
if (millis() - lastDisplayCheck >= 30000) { // Check every 30 seconds
lastDisplayCheck = millis();
if (!displayAvailable) {
Wire.beginTransmission(0x3C);
if (Wire.endTransmission() == 0) {
displayAvailable = initializeDisplay();
if (displayAvailable) {
addLog("Display connected");
}
}
} else {
Wire.beginTransmission(0x3C);
if (Wire.endTransmission() != 0) {
displayAvailable = false;
Serial.println("Display disconnected");
}
}
}
// Small delay to prevent tight looping
delay(1);
}
Metadata
Metadata
Assignees
Labels
No labels