#include #include // or #include for ESP32 #include // or #include for ESP32 #include #include #include // Node metadata structure struct Node { String name; IPAddress ip; unsigned long lastSeen; String status; }; // Service structure struct Service { String name; String endpoint; void (*callback)(); }; // MemberList and ServiceRegistry Node memberList[10]; // Example size Service serviceRegistry[10]; AsyncWebServer server(80); AsyncWebSocket ws("/ws"); void onServiceCall() { // Placeholder for service callback } // Helper to update node status String getNodeStatus(unsigned long lastSeen) { unsigned long now = millis(); if (now - lastSeen < 10000) return "active"; if (now - lastSeen < 60000) return "inactive"; return "dead"; } // Discover other nodes via mDNS void discoverNodes() { int n = MDNS.queryService("_ws", "tcp"); if (n == 0) { Serial.println("No mDNS services found"); return; } for (int i = 0; i < n && i < 10; i++) { IPAddress ip = MDNS.answerIP(i); String name = MDNS.answerHostname(i); if (ip == INADDR_NONE || name.isEmpty()) continue; // Skip invalid entries Serial.printf("Discovered node: %s (%s)\n", name.c_str(), ip.toString().c_str()); // Add/update node in memberList bool found = false; for (int j = 0; j < 10; j++) { if (memberList[j].ip == ip) { memberList[j].lastSeen = millis(); String oldStatus = memberList[j].status; memberList[j].status = getNodeStatus(memberList[j].lastSeen); found = true; if (oldStatus != memberList[j].status) { Serial.printf("Node %s (%s) status changed: %s -> %s\n", memberList[j].name.c_str(), memberList[j].ip.toString().c_str(), oldStatus.c_str(), memberList[j].status.c_str()); } break; } } if (!found) { for (int j = 0; j < 10; j++) { if (memberList[j].name == "") { memberList[j].name = name; memberList[j].ip = ip; memberList[j].lastSeen = millis(); memberList[j].status = "active"; Serial.printf("Discovered new node: %s (%s)\n", name.c_str(), ip.toString().c_str()); break; } } } } } // Broadcast event to all nodes void broadcastEvent(const String& event) { DynamicJsonDocument doc(256); doc["type"] = "event"; doc["data"] = event; String msg; serializeJson(doc, msg); ws.textAll(msg); } // Handle incoming WebSocket events void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) { if (type == WS_EVT_DATA) { String msg = String((char*)data); DynamicJsonDocument doc(256); DeserializationError err = deserializeJson(doc, msg); if (!err) { String type = doc["type"]; if (type == "event") { // Handle pub/sub event Serial.println("Received event: " + doc["data"].as()); } else if (type == "service_call") { // Handle service call String endpoint = doc["endpoint"]; for (int i = 0; i < 10; i++) { if (serviceRegistry[i].endpoint == endpoint && serviceRegistry[i].callback) { serviceRegistry[i].callback(); break; } } } } } } extern "C" { #include } void onNewClient(System_Event_t *event) { if (event->event == EVENT_SOFTAPMODE_STACONNECTED) { uint8_t *mac = event->event_info.sta_connected.mac; Serial.printf("New WiFi client connected: MAC %02x:%02x:%02x:%02x:%02x:%02x\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); } } String getMacHostname() { uint8_t mac[6]; WiFi.macAddress(mac); char hostname[32]; snprintf(hostname, sizeof(hostname), "esp-%02x%02x%02x%02x%02x%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); return String(hostname); } void setup() { Serial.begin(115200); delay(100); String myHostname = getMacHostname(); WiFi.hostname(myHostname); WiFi.mode(WIFI_STA); WiFi.begin("contraption", "sprocket"); Serial.printf("Attempting to connect to AP 'contraption' as %s...\n", myHostname.c_str()); unsigned long startAttemptTime = millis(); bool connected = false; while (millis() - startAttemptTime < 10000) { // try for 10 seconds if (WiFi.status() == WL_CONNECTED) { connected = true; break; } delay(500); Serial.print("."); } if (connected) { Serial.printf("\nConnected to AP 'contraption' as %s.\n", myHostname.c_str()); } else { Serial.println("\nFailed to connect. Starting AP mode."); WiFi.mode(WIFI_AP); WiFi.softAP("contraption", "sprocket"); WiFi.hostname(myHostname.c_str()); Serial.printf("Access Point 'contraption' started with password 'sprocket' and hostname %s.\n", myHostname.c_str()); wifi_set_event_handler_cb(onNewClient); } // mDNS setup if (MDNS.begin(myHostname)) { Serial.println("mDNS responder started"); // Register WebSocket service on mDNS MDNS.addService("_ws", "tcp", 80); // 80 is the default WebSocket port Serial.println("mDNS WebSocket service '_ws._tcp' registered on port 80"); } // WebSocket server setup ws.onEvent(onWsEvent); server.addHandler(&ws); server.begin(); // Register example service serviceRegistry[0] = {"status", "/api/status", onServiceCall}; } void loop() { // Periodically discover nodes static unsigned long lastDiscovery = 0; if (millis() - lastDiscovery > 5000) { discoverNodes(); lastDiscovery = millis(); } // Update node statuses for (int i = 0; i < 10; i++) { if (memberList[i].name != "") { String oldStatus = memberList[i].status; memberList[i].status = getNodeStatus(memberList[i].lastSeen); if (oldStatus != memberList[i].status) { Serial.printf("Node %s (%s) status changed: %s -> %s\n", memberList[i].name.c_str(), memberList[i].ip.toString().c_str(), oldStatus.c_str(), memberList[i].status.c_str()); } } } // No need for webSocket.loop() with ESPAsyncWebServer // Node discovery, memberlist update, service registry logic to be added }