/** * ESP32-C6 Home Assistant Integration * Arduino IDE Project * * Board: ESP32C6 Dev Module * Flash Size: 4MB * USB CDC On Boot: Enabled (REQUIRED for serial output!) * * Provides REST API for Home Assistant integration */ // version 1.5 Initial release #include #include #include // WiFi credentials const char* ssid = "BUON GUSTO PARTER"; const char* password = "arleta13"; // Web server on port 80 WebServer server(80); // GPIO pins - Olimex ESP32-C6-EVB board configuration const int LED_PIN = 8; // Onboard LED const int BUT_PIN = 9; // Onboard button const int RELAY_1_PIN = 10; // Relay 1 const int RELAY_2_PIN = 11; // Relay 2 const int RELAY_3_PIN = 22; // Relay 3 const int RELAY_4_PIN = 23; // Relay 4 const int DIN1_PIN = 1; // Digital Input 1 const int DIN2_PIN = 2; // Digital Input 2 const int DIN3_PIN = 3; // Digital Input 3 const int DIN4_PIN = 15; // Digital Input 4 // State tracking bool relay1_state = false; bool relay2_state = false; bool relay3_state = false; bool relay4_state = false; bool led_state = false; // Input state tracking - for change detection bool input1_state = true; // HIGH when not pressed (pull-up) bool input2_state = true; bool input3_state = true; bool input4_state = true; bool last_input1_state = true; bool last_input2_state = true; bool last_input3_state = true; bool last_input4_state = true; unsigned long last_input_check = 0; // Home Assistant callback configuration char ha_callback_url[256] = ""; // URL to POST input events to bool ha_registered = false; // Temperature simulation float temperature = 25.0; void setup() { // Initialize USB CDC serial Serial.begin(115200); delay(2000); // Give time for USB CDC to initialize // Wait for serial port to be ready (up to 5 seconds) for (int i = 0; i < 10 && !Serial; i++) { delay(500); } Serial.println("\n\n================================="); Serial.println("ESP32-C6 Home Assistant Device"); Serial.println("Arduino Framework"); Serial.println("================================="); // Initialize GPIO - Outputs pinMode(LED_PIN, OUTPUT); pinMode(RELAY_1_PIN, OUTPUT); pinMode(RELAY_2_PIN, OUTPUT); pinMode(RELAY_3_PIN, OUTPUT); pinMode(RELAY_4_PIN, OUTPUT); // Initialize GPIO - Inputs with pull-up pinMode(BUT_PIN, INPUT_PULLUP); pinMode(DIN1_PIN, INPUT_PULLUP); pinMode(DIN2_PIN, INPUT_PULLUP); pinMode(DIN3_PIN, INPUT_PULLUP); pinMode(DIN4_PIN, INPUT_PULLUP); // Set all outputs to LOW digitalWrite(LED_PIN, LOW); digitalWrite(RELAY_1_PIN, LOW); digitalWrite(RELAY_2_PIN, LOW); digitalWrite(RELAY_3_PIN, LOW); digitalWrite(RELAY_4_PIN, LOW); Serial.println("GPIO initialized"); // Configure WiFi Serial.println("\n--- WiFi Configuration ---"); WiFi.mode(WIFI_STA); WiFi.setAutoReconnect(true); // Set static IP (prevents DHCP IP changes) IPAddress staticIP(192, 168, 0, 181); IPAddress gateway(192, 168, 0, 1); IPAddress subnet(255, 255, 255, 0); WiFi.config(staticIP, gateway, subnet); // Connect to WiFi Serial.print("Connecting to WiFi: "); Serial.println(ssid); WiFi.begin(ssid, password); int attempts = 0; while (WiFi.status() != WL_CONNECTED && attempts < 40) { // 40 * 500ms = 20 seconds delay(500); Serial.print("."); attempts++; } Serial.println(""); // New line after dots // Check WiFi status int wifiStatus = WiFi.status(); if (wifiStatus == WL_CONNECTED) { Serial.println("\n✓ WiFi connected!"); Serial.print("IP address: "); Serial.println(WiFi.localIP()); Serial.print("RSSI: "); Serial.print(WiFi.RSSI()); Serial.println(" dBm"); Serial.print("MAC: "); Serial.println(WiFi.macAddress()); } else { Serial.println("\n✗ WiFi connection failed!"); Serial.print("WiFi Status Code: "); Serial.println(wifiStatus); // Status codes: 0=IDLE, 1=NO_SSID, 2=SCAN_COMPLETE, 3=CONNECTED, 4=CONNECT_FAILED, 5=CONNECTION_LOST, 6=DISCONNECTED switch(wifiStatus) { case WL_NO_SSID_AVAIL: Serial.println("ERROR: SSID not found! Check network name."); break; case WL_CONNECT_FAILED: Serial.println("ERROR: Connection failed! Check password."); break; case WL_CONNECTION_LOST: Serial.println("ERROR: Connection lost."); break; case WL_DISCONNECTED: Serial.println("ERROR: Disconnected from network."); break; default: Serial.println("ERROR: Unknown WiFi error."); } Serial.println("Continuing anyway to allow API access..."); // Scan and show available networks scanWiFiNetworks(); } // Setup API endpoints server.on("/", handleRoot); server.on("/api/status", HTTP_GET, handleStatus); // Relay endpoints server.on("/relay/on", HTTP_POST, handleRelayOn); server.on("/relay/off", HTTP_POST, handleRelayOff); server.on("/relay/status", HTTP_GET, handleRelayStatus); // Input endpoints server.on("/input/status", HTTP_GET, handleInputStatus); // Home Assistant webhook registration server.on("/register", HTTP_POST, handleRegister); // LED endpoints server.on("/led/on", HTTP_POST, handleLEDOn); server.on("/led/off", HTTP_POST, handleLEDOff); server.onNotFound(handleNotFound); // Start server server.begin(); Serial.println("\n✓ HTTP server started on port 80"); Serial.println("\n================================="); Serial.println("Ready! Try these endpoints:"); Serial.print(" http://"); Serial.print(WiFi.localIP()); Serial.println("/api/status"); Serial.println("=================================\n"); } void loop() { server.handleClient(); // Simulate temperature reading temperature = 25.0 + (random(-20, 20) / 10.0); // Check for input state changes every 50ms if (millis() - last_input_check > 50) { last_input_check = millis(); checkInputChanges(); } delay(10); } // ============================================ // WiFi Debug Function // ============================================ void scanWiFiNetworks() { Serial.println("\n--- Available WiFi Networks ---"); int n = WiFi.scanNetworks(); if (n == 0) { Serial.println("No networks found!"); } else { Serial.print("Found "); Serial.print(n); Serial.println(" networks:"); for (int i = 0; i < n; i++) { Serial.print(i + 1); Serial.print(". "); Serial.print(WiFi.SSID(i)); Serial.print(" ("); Serial.print(WiFi.RSSI(i)); Serial.print(" dBm) "); Serial.println(WiFi.encryptionType(i) == WIFI_AUTH_OPEN ? "Open" : "Secured"); } } Serial.println("-------------------------------\n"); } // ============================================ // API Handlers // ============================================ void handleRoot() { // Read all inputs first to get current state input1_state = digitalRead(DIN1_PIN); input2_state = digitalRead(DIN2_PIN); input3_state = digitalRead(DIN3_PIN); input4_state = digitalRead(DIN4_PIN); String html = "ESP32-C6 Device"; html += ""; html += ""; html += ""; html += ""; html += "

ESP32-C6 Control Panel

"; // Device Info html += "

Device Info

"; html += "IP: " + WiFi.localIP().toString() + "   "; html += "RSSI: " + String(WiFi.RSSI()) + " dBm   "; html += "Temp: " + String(temperature, 1) + "°C   "; html += "Uptime: " + String(millis() / 1000) + "s
"; // Inputs + Relays side by side in a 2-column row html += "
"; // Inputs — 2-column inner grid, round LED indicator html += "

Inputs

"; bool inputStates[5] = {false, input1_state, input2_state, input3_state, input4_state}; for (int i = 1; i <= 4; i++) { bool pressed = !inputStates[i]; // pull-up: LOW=pressed html += "
"; html += "
"; html += "
Input " + String(i) + "
"; html += "
" + String(pressed ? "PRESSED" : "NOT PRESSED") + "
"; html += "
"; } html += "
"; // close Inputs card // Relays — 2-column inner grid, toggle buttons html += "

Relays

"; bool relayStates[5] = {false, relay1_state, relay2_state, relay3_state, relay4_state}; for (int i = 1; i <= 4; i++) { bool on = relayStates[i]; html += ""; } html += "
"; // close Relays card html += "
"; // close .card-row // LED Control html += "

LED Control

"; html += ""; html += ""; html += "   Status: " + String(led_state ? "ON" : "OFF") + "
"; // Home Assistant Webhook Status html += "

Home Assistant Webhook

"; if (ha_registered && strlen(ha_callback_url) > 0) { html += "
✓ Connected — " + String(ha_callback_url) + "
"; } else { html += "
✗ Not registered — waiting for Home Assistant...
"; } html += "
"; html += "

API Endpoints

"; html += "GET /api/status   POST /relay/on?relay=1-4   POST /relay/off?relay=1-4
"; html += "GET /input/status?input=1-4   POST /led/on   POST /led/off
"; html += "POST /register?callback_url=...
"; html += ""; server.send(200, "text/html", html); } // Status request monitoring static unsigned long last_status_log = 0; static int status_request_count = 0; void handleStatus() { status_request_count++; // Read fresh input states input1_state = digitalRead(DIN1_PIN); input2_state = digitalRead(DIN2_PIN); input3_state = digitalRead(DIN3_PIN); input4_state = digitalRead(DIN4_PIN); String json = "{"; json += "\"input1\":" + String(input1_state ? "true" : "false") + ","; json += "\"input2\":" + String(input2_state ? "true" : "false") + ","; json += "\"input3\":" + String(input3_state ? "true" : "false") + ","; json += "\"input4\":" + String(input4_state ? "true" : "false") + ","; json += "\"relay1\":" + String(relay1_state ? "true" : "false") + ","; json += "\"relay2\":" + String(relay2_state ? "true" : "false") + ","; json += "\"relay3\":" + String(relay3_state ? "true" : "false") + ","; json += "\"relay4\":" + String(relay4_state ? "true" : "false") + ","; json += "\"led\":" + String(led_state ? "true" : "false"); json += "}"; server.send(200, "application/json", json); // Log status every 10 seconds if (millis() - last_status_log > 10000) { last_status_log = millis(); Serial.printf("API: %d requests/10sec, Free heap: %d bytes, Uptime: %lus\n", status_request_count, ESP.getFreeHeap(), millis() / 1000); status_request_count = 0; } } void handleRelayOn() { if (!server.hasArg("relay")) { server.send(400, "application/json", "{\"error\":\"Missing relay parameter\"}"); return; } int relay_num = server.arg("relay").toInt(); int pin = -1; bool *state_ptr = nullptr; switch(relay_num) { case 1: pin = RELAY_1_PIN; state_ptr = &relay1_state; break; case 2: pin = RELAY_2_PIN; state_ptr = &relay2_state; break; case 3: pin = RELAY_3_PIN; state_ptr = &relay3_state; break; case 4: pin = RELAY_4_PIN; state_ptr = &relay4_state; break; default: server.send(400, "application/json", "{\"error\":\"Invalid relay number\"}"); return; } digitalWrite(pin, HIGH); *state_ptr = true; server.send(200, "application/json", "{\"status\":\"ok\",\"state\":true}"); Serial.printf("Relay %d ON\n", relay_num); } void handleRelayOff() { if (!server.hasArg("relay")) { server.send(400, "application/json", "{\"error\":\"Missing relay parameter\"}"); return; } int relay_num = server.arg("relay").toInt(); int pin = -1; bool *state_ptr = nullptr; switch(relay_num) { case 1: pin = RELAY_1_PIN; state_ptr = &relay1_state; break; case 2: pin = RELAY_2_PIN; state_ptr = &relay2_state; break; case 3: pin = RELAY_3_PIN; state_ptr = &relay3_state; break; case 4: pin = RELAY_4_PIN; state_ptr = &relay4_state; break; default: server.send(400, "application/json", "{\"error\":\"Invalid relay number\"}"); return; } digitalWrite(pin, LOW); *state_ptr = false; server.send(200, "application/json", "{\"status\":\"ok\",\"state\":false}"); Serial.printf("Relay %d OFF\n", relay_num); } void handleRelayStatus() { if (!server.hasArg("relay")) { server.send(400, "application/json", "{\"error\":\"Missing relay parameter\"}"); return; } int relay_num = server.arg("relay").toInt(); bool state = false; switch(relay_num) { case 1: state = relay1_state; break; case 2: state = relay2_state; break; case 3: state = relay3_state; break; case 4: state = relay4_state; break; default: server.send(400, "application/json", "{\"error\":\"Invalid relay number\"}"); return; } String json = "{\"state\":" + String(state ? "true" : "false") + "}"; server.send(200, "application/json", json); } void handleInputStatus() { if (!server.hasArg("input")) { server.send(400, "application/json", "{\"error\":\"Missing input parameter\"}"); return; } int input_num = server.arg("input").toInt(); int pin = -1; switch(input_num) { case 1: pin = DIN1_PIN; break; case 2: pin = DIN2_PIN; break; case 3: pin = DIN3_PIN; break; case 4: pin = DIN4_PIN; break; default: server.send(400, "application/json", "{\"error\":\"Invalid input number\"}"); return; } int level = digitalRead(pin); String json = "{\"state\":" + String(level ? "true" : "false") + "}"; server.send(200, "application/json", json); Serial.printf("Input %d status: %d\n", input_num, level); } void handleLEDOn() { digitalWrite(LED_PIN, LOW); // LED is active-low led_state = true; server.send(200, "application/json", "{\"status\":\"ok\"}"); Serial.println("LED ON"); } void handleLEDOff() { digitalWrite(LED_PIN, HIGH); // LED is active-low led_state = false; server.send(200, "application/json", "{\"status\":\"ok\"}"); Serial.println("LED OFF"); } void handleNotFound() { String message = "404: Not Found\n\n"; message += "URI: " + server.uri() + "\n"; message += "Method: " + String((server.method() == HTTP_GET) ? "GET" : "POST") + "\n"; server.send(404, "text/plain", message); } // ============================================ // Home Assistant Integration // ============================================ void handleRegister() { if (!server.hasArg("callback_url")) { server.send(400, "application/json", "{\"error\":\"Missing callback_url parameter\"}"); return; } String url = server.arg("callback_url"); if (url.length() > 255) { server.send(400, "application/json", "{\"error\":\"URL too long (max 255 chars)\"}"); return; } url.toCharArray(ha_callback_url, 256); ha_registered = true; Serial.printf("Home Assistant webhook registered: %s\n", ha_callback_url); server.send(200, "application/json", "{\"status\":\"ok\",\"message\":\"Webhook registered\"}"); } void checkInputChanges() { if (!ha_registered) return; // Only check if HA is registered // Read all input states bool curr_input1 = digitalRead(DIN1_PIN); bool curr_input2 = digitalRead(DIN2_PIN); bool curr_input3 = digitalRead(DIN3_PIN); bool curr_input4 = digitalRead(DIN4_PIN); // Check for changes and POST events if (curr_input1 != last_input1_state) { last_input1_state = curr_input1; postInputEvent(1, curr_input1); } if (curr_input2 != last_input2_state) { last_input2_state = curr_input2; postInputEvent(2, curr_input2); } if (curr_input3 != last_input3_state) { last_input3_state = curr_input3; postInputEvent(3, curr_input3); } if (curr_input4 != last_input4_state) { last_input4_state = curr_input4; postInputEvent(4, curr_input4); } } void postInputEvent(int input_num, bool state) { if (!ha_registered || strlen(ha_callback_url) == 0) { return; // Not registered, skip } // Invert state because inputs use pull-up (HIGH=not pressed, LOW=pressed) bool pressed = !state; String event_type = pressed ? "input_on" : "input_off"; Serial.printf("Input %d event: %s (raw_state=%d) - POSTing to HA\n", input_num, event_type.c_str(), state); // Parse callback URL (format: http://host:port/path) String url_str = String(ha_callback_url); // Extract host and path int protocol_end = url_str.indexOf("://"); if (protocol_end < 0) return; int host_start = protocol_end + 3; int port_separator = url_str.indexOf(":", host_start); int path_start = url_str.indexOf("/", host_start); if (path_start < 0) path_start = url_str.length(); if (port_separator < 0 || port_separator > path_start) port_separator = -1; String host = url_str.substring(host_start, (port_separator >= 0) ? port_separator : path_start); int port = 80; // Default HTTP port if (port_separator >= 0) { int colon_port_end = url_str.indexOf("/", port_separator); if (colon_port_end < 0) colon_port_end = url_str.length(); String port_str = url_str.substring(port_separator + 1, colon_port_end); port = port_str.toInt(); } String path = url_str.substring(path_start); // Create JSON payload String json = "{\"input\":" + String(input_num) + ",\"state\":" + (pressed ? "true" : "false") + "}"; // Connect to Home Assistant and POST WiFiClient client; if (!client.connect(host.c_str(), port)) { Serial.printf("Failed to connect to %s:%d\n", host.c_str(), port); return; } // Send HTTP POST request client.println("POST " + path + " HTTP/1.1"); client.println("Host: " + host); client.println("Content-Type: application/json"); client.println("Content-Length: " + String(json.length())); client.println("Connection: close"); client.println(); client.print(json); // Wait for response delay(100); // Read response while (client.available()) { char c = client.read(); // Just discard the response for now } client.stop(); Serial.printf("Input %d event posted successfully\n", input_num); }