Initial commit: Olimex ESP32-C6-EVB HA integration + Arduino sketch
This commit is contained in:
630
esp32_arduino/esp32_arduino.ino
Normal file
630
esp32_arduino/esp32_arduino.ino
Normal file
@@ -0,0 +1,630 @@
|
||||
/**
|
||||
* 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 <WiFi.h>
|
||||
#include <WebServer.h>
|
||||
#include <WiFiClient.h>
|
||||
|
||||
// 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 = "<html><head><title>ESP32-C6 Device</title>";
|
||||
html += "<meta name='viewport' content='width=device-width, initial-scale=1'>";
|
||||
html += "<style>";
|
||||
html += "body{font-family:Arial;margin:20px;background:#f0f0f0}";
|
||||
html += ".card{background:white;padding:20px;margin:10px 0;border-radius:8px;box-shadow:0 2px 4px rgba(0,0,0,0.1)}";
|
||||
html += "h1{color:#333}h2{color:#555;font-size:17px;margin-top:0;margin-bottom:12px}";
|
||||
html += ".grid2{display:grid;grid-template-columns:1fr 1fr;gap:10px}";
|
||||
// Input item with round LED indicator
|
||||
html += ".inp-item{display:flex;align-items:center;gap:12px;padding:10px 14px;background:#f7f7f7;border-radius:8px;border:1px solid #e0e0e0}";
|
||||
html += ".led{width:22px;height:22px;border-radius:50%;flex-shrink:0;transition:background 0.3s,box-shadow 0.3s}";
|
||||
html += ".led-on{background:#4CAF50;box-shadow:0 0 8px #4CAF5099}";
|
||||
html += ".led-off{background:#f44336;box-shadow:0 0 8px #f4433666}";
|
||||
html += ".inp-name{font-weight:bold;font-size:14px;color:#333}";
|
||||
html += ".inp-state{font-size:11px;color:#888;margin-top:2px}";
|
||||
// Relay toggle buttons
|
||||
html += ".relay-btn{width:100%;padding:14px 8px;border:none;border-radius:8px;cursor:pointer;font-size:14px;font-weight:bold;transition:background 0.2s,transform 0.1s}";
|
||||
html += ".relay-btn:active{transform:scale(0.97)}";
|
||||
html += ".relay-on{background:#4CAF50;color:white}";
|
||||
html += ".relay-off{background:#9e9e9e;color:white}";
|
||||
html += ".relay-on:hover{background:#43A047}.relay-off:hover{background:#757575}";
|
||||
// LED control & webhook
|
||||
html += ".card-row{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin:10px 0}"; // two big columns
|
||||
html += ".card-row .card{margin:0}"; // reset per-card margin inside row
|
||||
html += ".btn{padding:8px 16px;margin:4px;border:none;border-radius:4px;cursor:pointer;font-size:14px}";
|
||||
html += ".btn-on{background:#4CAF50;color:white}.btn-off{background:#f44336;color:white}";
|
||||
html += ".wh-ok{background:#c8e6c9;color:#1b5e20;padding:10px;border-radius:4px}";
|
||||
html += ".wh-err{background:#ffcdd2;color:#b71c1c;padding:10px;border-radius:4px}";
|
||||
html += "</style>";
|
||||
html += "<script>";
|
||||
// Relay toggle: read current state from data-state attribute, POST, update DOM
|
||||
html += "function toggleRelay(n){";
|
||||
html += "var b=document.getElementById('r'+n);";
|
||||
html += "var on=b.dataset.state==='1';";
|
||||
html += "fetch((on?'/relay/off':'/relay/on')+'?relay='+n,{method:'POST'})";
|
||||
html += ".then(function(){var ns=!on;b.dataset.state=ns?'1':'0';";
|
||||
html += "b.className='relay-btn '+(ns?'relay-on':'relay-off');";
|
||||
html += "b.textContent='Relay '+n+': '+(ns?'ON':'OFF');})";
|
||||
html += ".catch(function(e){console.error(e);});";
|
||||
html += "}";
|
||||
// Polling: update LED indicators and relay button states
|
||||
html += "function updateStatus(){fetch('/api/status').then(function(r){return r.json();}).then(function(d){";
|
||||
html += "for(var i=1;i<=4;i++){";
|
||||
// input: raw HIGH=not pressed, so pressed = !d['input'+i]
|
||||
html += "var p=!d['input'+i];";
|
||||
html += "document.getElementById('led'+i).className='led '+(p?'led-on':'led-off');";
|
||||
html += "document.getElementById('is'+i).textContent=p?'PRESSED':'NOT PRESSED';";
|
||||
// relay: update toggle button
|
||||
html += "var b=document.getElementById('r'+i);var on=d['relay'+i];";
|
||||
html += "b.dataset.state=on?'1':'0';";
|
||||
html += "b.className='relay-btn '+(on?'relay-on':'relay-off');";
|
||||
html += "b.textContent='Relay '+i+': '+(on?'ON':'OFF');";
|
||||
html += "}";
|
||||
html += "}).catch(function(e){console.error(e);});}";
|
||||
html += "window.addEventListener('load',function(){updateStatus();setInterval(updateStatus,2000);});";
|
||||
html += "</script>";
|
||||
html += "</head><body>";
|
||||
html += "<h1>ESP32-C6 Control Panel</h1>";
|
||||
|
||||
// Device Info
|
||||
html += "<div class='card'><h2>Device Info</h2>";
|
||||
html += "IP: " + WiFi.localIP().toString() + " ";
|
||||
html += "RSSI: " + String(WiFi.RSSI()) + " dBm ";
|
||||
html += "Temp: " + String(temperature, 1) + "°C ";
|
||||
html += "Uptime: " + String(millis() / 1000) + "s</div>";
|
||||
|
||||
// Inputs + Relays side by side in a 2-column row
|
||||
html += "<div class='card-row'>";
|
||||
|
||||
// Inputs — 2-column inner grid, round LED indicator
|
||||
html += "<div class='card'><h2>Inputs</h2><div class='grid2'>";
|
||||
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 += "<div class='inp-item'>";
|
||||
html += "<div class='led " + String(pressed ? "led-on" : "led-off") + "' id='led" + String(i) + "'></div>";
|
||||
html += "<div><div class='inp-name'>Input " + String(i) + "</div>";
|
||||
html += "<div class='inp-state' id='is" + String(i) + "'>" + String(pressed ? "PRESSED" : "NOT PRESSED") + "</div></div>";
|
||||
html += "</div>";
|
||||
}
|
||||
html += "</div></div>"; // close Inputs card
|
||||
|
||||
// Relays — 2-column inner grid, toggle buttons
|
||||
html += "<div class='card'><h2>Relays</h2><div class='grid2'>";
|
||||
bool relayStates[5] = {false, relay1_state, relay2_state, relay3_state, relay4_state};
|
||||
for (int i = 1; i <= 4; i++) {
|
||||
bool on = relayStates[i];
|
||||
html += "<button class='relay-btn " + String(on ? "relay-on" : "relay-off") + "' ";
|
||||
html += "id='r" + String(i) + "' data-state='" + String(on ? "1" : "0") + "' ";
|
||||
html += "onclick='toggleRelay(" + String(i) + ")'>";
|
||||
html += "Relay " + String(i) + ": " + String(on ? "ON" : "OFF");
|
||||
html += "</button>";
|
||||
}
|
||||
html += "</div></div>"; // close Relays card
|
||||
html += "</div>"; // close .card-row
|
||||
|
||||
// LED Control
|
||||
html += "<div class='card'><h2>LED Control</h2>";
|
||||
html += "<button class='btn btn-on' onclick='fetch(\"/led/on\",{method:\"POST\"}).then(()=>location.reload())'>LED ON</button>";
|
||||
html += "<button class='btn btn-off' onclick='fetch(\"/led/off\",{method:\"POST\"}).then(()=>location.reload())'>LED OFF</button>";
|
||||
html += " Status: <strong>" + String(led_state ? "ON" : "OFF") + "</strong></div>";
|
||||
|
||||
// Home Assistant Webhook Status
|
||||
html += "<div class='card'><h2>Home Assistant Webhook</h2>";
|
||||
if (ha_registered && strlen(ha_callback_url) > 0) {
|
||||
html += "<div class='wh-ok'>✓ Connected — " + String(ha_callback_url) + "</div>";
|
||||
} else {
|
||||
html += "<div class='wh-err'>✗ Not registered — waiting for Home Assistant...</div>";
|
||||
}
|
||||
html += "</div>";
|
||||
|
||||
html += "<div class='card'><h2>API Endpoints</h2>";
|
||||
html += "GET /api/status POST /relay/on?relay=1-4 POST /relay/off?relay=1-4<br>";
|
||||
html += "GET /input/status?input=1-4 POST /led/on POST /led/off<br>";
|
||||
html += "POST /register?callback_url=...</div>";
|
||||
|
||||
html += "</body></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);
|
||||
}
|
||||
Reference in New Issue
Block a user