commit 40ef749621528317d2e55fa4f1394300f195b1f0 Author: Skye Date: Sat Jun 6 13:52:56 2026 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7578e1a --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.theia/ diff --git a/software.ino b/software.ino new file mode 100644 index 0000000..80ac14a --- /dev/null +++ b/software.ino @@ -0,0 +1,237 @@ +#include "esp_task_wdt.h" // watchdogs +#include // WiFi connection +#include +#include +#include "driver/gpio.h" +#include + +/////////////////////////////////////////////////////////////////////////////// +/// Watchdogs Settings + +#define WDT_TIMEOUT 60000 +#define WDT_CONFIG_FREERTOS_NUMBER_OF_CORES 1 // If one core doesn't work, try 2 + +esp_task_wdt_config_t twdt_config = { + .timeout_ms = WDT_TIMEOUT, + .idle_core_mask = (1 << WDT_CONFIG_FREERTOS_NUMBER_OF_CORES) - 1, // Bitmask of all cores + .trigger_panic = true, +}; // Bugfixes for hardware watchdog on arduino-esp32 3.x + +/////////////////////////////////////////////////////////////////////////////// +/// WiFi Settings + +//#define WIFI_SSID "FlannelFlat1" +//#define WIFI_PASSWORD "CosySquares4Life:3" + +#define WIFI_SSID "Hacklab" +#define WIFI_PASSWORD "piranhas" +#define WIFI_HOSTNAME "MediaServerSwitch" + +/////////////////////////////////////////////////////////////////////////////// +/// HTTP Server Setup + +AsyncWebServer server(80); + +/////////////////////////////////////////////////////////////////////////////// +/// Pin Setup + +#define PIN_POWER_RELAY 23 +#define PIN_RESTART_RELAY 19 +#define PIN_STATUS_LED 35 +#define PIN_POWER_BTN 27 + +const char* WiFiCodeToString(int code) { + const char* status; + switch (code) { + case 0: status = "IDLE STATUS"; break; + case 1: status = "NO SSID AVAILABLE"; break; + case 2: status = "SCAN COMPLETE"; break; + case 3: status = "CONNECTED"; break; + case 4: status = "CONNECT FAILED"; break; + case 5: status = "CONNECTION L\sOST"; break; + case 6: status = "DISCONNECTED"; break; + case 255: status = "NO SHIELD"; break; + default: status = "UNKNOWN"; break; + } + + return status; +} + +void setup() { + Serial.begin(9600); + + /////////////////////////////////////////////////////////////////////////// + /// Watchdog Setup + Serial.print("Configuring WDT... "); + esp_task_wdt_deinit(); // WDT is enabled by default, deinit + esp_task_wdt_init(&twdt_config); // Enable panic so ESP32 restarts + esp_task_wdt_add(NULL); // Add current thread to WDT watch + Serial.println("Success"); + + /////////////////////////////////////////////////////////////////////////// + /// Pin Setup + + pinMode(PIN_POWER_RELAY, OUTPUT); // Power Button + pinMode(PIN_RESTART_RELAY, OUTPUT); // Restart Button + pinMode(PIN_STATUS_LED, INPUT); // Status LED + pinMode(PIN_POWER_BTN, INPUT); // Power Button Passthrough + + /////////////////////////////////////////////////////////////////////////// + /// WiFi Setup + + esp_task_wdt_reset(); // Feed the watchdog + + Serial.print("Setting up WiFi... "); + WiFi.mode(WIFI_STA); + WiFi.setHostname(WIFI_HOSTNAME); + MDNS.begin(WIFI_HOSTNAME); + WiFi.begin(WIFI_SSID, WIFI_PASSWORD); + Serial.print("Connecting to Network... "); + + unsigned short count = 0; + unsigned long last = millis(); + while (WiFi.status() != WL_CONNECTED) { + if (count > 60) { + Serial.println("Failed to connect, restarting"); + ESP.restart(); + } else if (millis() - last > 1000) { + Serial.print("."); + last = millis(); + count++; + } + } + + WiFi.persistent(false); + + Serial.print(" Connected with IP: "); + Serial.print(WiFi.localIP()); + Serial.print(" and Hostname: "); + Serial.println(WIFI_HOSTNAME); + + esp_task_wdt_reset(); // Feed the watchdog + + int ch = WiFi.channel(); + bool is5GHz = (ch >= 36); + Serial.printf("Channel: %d (%s GHz), RSSI: %d dBm\n", ch, is5GHz ? "5" : "2.4", WiFi.RSSI()); + + /////////////////////////////////////////////////////////////////////////// + /// Web Server + + server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { + request->send(200, "text/plain", + "/ping Pong\n" + "/status Get ESP and PC status\n" + "/power Press the power button\n" + "/power-long Hold down the power button (for force stops)\n" + "/restart-pc Press the restart button on the PC\n" + "/restart-ESP Shut down the server and restart the whole ESP\n"); + }); + + server.on("/ping", HTTP_GET, [](AsyncWebServerRequest *request) { + request->send(200, "text/plain", "pong"); + }); + + server.on("/status", HTTP_GET, [ch, is5GHz](AsyncWebServerRequest *request) { + char* result; + asprintf(&result, "IP: %s, Hostname: %s, Status: %s, Channel: %d (%s GHz), RSSI: %d dBm\n", WiFi.localIP().toString().c_str(), WIFI_HOSTNAME, WiFiCodeToString(WiFi.status()), ch, is5GHz ? "5" : "2.4", WiFi.RSSI()); + request->send(200, "text/plain", result); + free(result); + }); + + server.on("/power", HTTP_POST, [](AsyncWebServerRequest *request) { + digitalWrite(PIN_POWER_RELAY, HIGH); + delay(500); + digitalWrite(PIN_POWER_RELAY, LOW); + request->send(200, "text/plain", "200 OK"); + }); + + server.on("/power-long", HTTP_POST, [](AsyncWebServerRequest *request) { + digitalWrite(PIN_POWER_RELAY, HIGH); + delay(5000); + digitalWrite(PIN_POWER_RELAY, LOW); + request->send(200, "text/plain", "200 OK"); + }); + + server.on("/restart-pc", HTTP_POST, [](AsyncWebServerRequest *request) { + digitalWrite(PIN_RESTART_RELAY, HIGH); + delay(1000); + digitalWrite(PIN_RESTART_RELAY, LOW); + request->send(200, "text/plain", "200 OK"); + }); + + server.on("/restart-ESP", HTTP_POST, [](AsyncWebServerRequest *request) { + request->send(202, "text/plain", "202 Accepted. Good Night."); + delay(500); + server.end(); + ESP.restart(); + }); + + server.on("/activate", HTTP_POST, [](AsyncWebServerRequest *request) { + if (!request->hasParam("pin", true) || !request->hasParam("duration", true)) { + request->send(400, "text/plain", "400 Bad Request. Missing 'pin' or 'duration' parameter"); + return; + } + + int pin = request->getParam("pin", true)->value().toInt(); + long duration = strtol(request->getParam("duration", true)->value().c_str(), NULL, 10); + + if (!GPIO_IS_VALID_GPIO((gpio_num_t)pin)) { + request->send(400, "text/plain", "Invalid pin for this ESP32"); + return; + } + + if (duration <= 0 || duration > 3600000) { + request->send(400, "text/plain", "400 Bad Request. Duration must be between 1ms and 3600000ms"); + return; + } + + gpio_io_config_t* pin_mode; + gpio_get_io_config((gpio_num_t)pin, pin_mode); + if (pin_mode->oe == true) { + if (pin_mode->ie != true) { + request->send(400, "text/plain", "Pin is configured as input"); + return; + } + + // Configure pin as output + gpio_config_t config = {}; + config.pin_bit_mask = (1ULL << pin); + config.mode = GPIO_MODE_OUTPUT; + config.pull_up_en = GPIO_PULLUP_DISABLE; + config.pull_down_en = GPIO_PULLDOWN_DISABLE; + config.intr_type = GPIO_INTR_DISABLE; + + if (gpio_config(&config) != ESP_OK) { + request->send(400, "text/plain", "Bad Request. Cannot configure pin as output"); + return; + } + } + + gpio_set_level((gpio_num_t)pin, HIGH); + delay(duration); + gpio_set_level((gpio_num_t)pin, LOW); + delay(10); // allow change to propagate + + // unconfigure pin + gpio_reset_pin((gpio_num_t)pin); + + request->send(200, "text/plain", "OK"); + }); + + server.begin(); +} + +unsigned long last = millis(); + +void loop() { + if (millis() - last > 10000) { + Serial.print("Heartbeat. WiFi Status: "); + Serial.println(WiFiCodeToString(WiFi.status())); + esp_task_wdt_reset(); // Feed the watchdog + last = millis(); + } + if (WiFi.status() != WL_CONNECTED) { // you can use WiFi.setAutoReconnect(true); but i rather this for custom behaviour. + Serial.println("Disconnected from network, restarting."); + ESP.restart(); + } +} \ No newline at end of file