Skip to content

Installing code on ESP32

  • ESP32 DEVKITV1 board
  • Arduino IDE with ESP32 board support installed
  • Libraries:
    • WebSockets (by Markus Sattler)
    • ArduinoJson
    • ESP32Servo
    • Preferences (built-in for ESP32)

You can install these libraries using the Arduino IDE’s Library Manager (Sketch > Include Library > Manage Libraries...).

  1. Open the Arduino IDE and copy the code or download the UnderDeckESP32.ino provided below.
  2. Ensure all required libraries are installed via the Library Manager.
  3. Connect your ESP32 board to your computer via USB.
  4. Select the correct board (ESP32 DEVKITV1) and COM port from the Tools menu.
  5. Upload the code to the ESP32.
Code UnderDeckESP32.ino
#include <WiFi.h>
#include <WebSocketsClient.h>
#include <ArduinoJson.h>
#include <ESP32Servo.h>
#include <Preferences.h>
#include <vector>

// Define the WebSocket server address
const char* websocket_server_host = "wss-global.undernouzen.shop";
const uint16_t websocket_server_port = 443; // Standard port for WSS
const char* websocket_path = "/ESP32UND?";

// Object to store preferences in flash memory
Preferences preferences;

// Objects for communication and control
WebSocketsClient webSocketClient;
Servo myServo;
String current_mac_address;
bool ws_connected = false;

// Dynamic pin variables
int ledPin = -1;
int servoPin = -1;
bool is_servo_attached = false;

// Debug property
int debug_mode = 0; // 0 = disabled, 1 = enabled
const int builtInLed = 2; // Built-in LED of the ESP32 board

// Potentiometer monitoring variables
const int potThreshold = 50; // Minimum change to trigger an event

// Touch sensor monitoring variables
const int touchThreshold = 40; // Minimum value to detect a touch

// Button monitoring variables
unsigned long lastDebounceTime = 0;
const unsigned long debounceDelay = 50;

// Structure to hold sensor information for monitoring
struct MonitoredSensor {
  int pin;
  String type;
  int lastValue;
};

// Vector to hold the list of active sensors
std::vector<MonitoredSensor> activeSensors;

// Function prototypes
void startWiFi(const char* ssid, const char* password);
void startWiFiAP();
void saveWiFiCredentials(const char* ssid, const char* password);
void loadWiFiCredentials(String &ssid, String &password);
void saveDebugMode(int mode);
int loadDebugMode();
String getMacAddressString();
String getMacAddressNoColons();
void webSocketEvent(WStype_t type, uint8_t * payload, size_t length);
void handleCommand(String payload, bool isSerial);
void processSerial();
void connectToWebSocket();
void blinkBuiltInLed(int duration);
void sendSensorValueToWebSocket(int pin, const char* type, int value);
void monitorActiveSensors();
void enableSensorMonitor(int pin, const char* type);
void disableSensorMonitor(int pin, const char* type);

Serial.print("{'action':'starting'}");

void setup() {
  Serial.begin(115200);
  delay(1000);

  // Load debug mode before any conditional serial printing
  debug_mode = loadDebugMode();
  if (debug_mode == 1) {
    Serial.println("Debug mode: ENABLED");
  } else {
    Serial.println("Debug mode: DISABLED");
  }

  // Configure built-in LED
  pinMode(builtInLed, OUTPUT);
  digitalWrite(builtInLed, LOW);

  // Initialize WiFi mode
  WiFi.mode(WIFI_STA); 
  
  // Try to load credentials and connect
  String saved_ssid, saved_password;
  loadWiFiCredentials(saved_ssid, saved_password);

  if (saved_ssid.length() > 0 && saved_password.length() > 0) {
    if (debug_mode == 1) Serial.println("Saved WiFi credentials found. Attempting to connect...");
    startWiFi(saved_ssid.c_str(), saved_password.c_str());
  } else {
    Serial.println("No WiFi credentials saved. Starting in Access Point mode.");
    startWiFiAP();
  }
}

void loop() {
  // Handle WebSocket events
  if (WiFi.status() == WL_CONNECTED) {
    if (current_mac_address.length() > 0 && !ws_connected) {
        connectToWebSocket();
    }
    webSocketClient.loop();
  } else {
    ws_connected = false;
  }

  // Handle commands via Serial
  processSerial();
  
  // Monitor active sensors
  monitorActiveSensors();
  
  // A small delay to avoid overwhelming the CPU
  delay(10);
}

void startWiFi(const char* ssid, const char* password) {
  if (debug_mode == 1) Serial.println("Attempting to connect to WiFi...");
  WiFi.begin(ssid, password);

  int timeout_counter = 0;
  while (WiFi.status() != WL_CONNECTED && timeout_counter < 30) {
    delay(500);
    if (debug_mode == 1) Serial.print(".");
    timeout_counter++;
  }

  if (WiFi.status() == WL_CONNECTED) {
    Serial.println("\nConnected to WiFi network!");
    Serial.print("IP Address: ");
    Serial.println(WiFi.localIP());

    current_mac_address = getMacAddressNoColons(); 
    Serial.print("ESP32 MAC Address (without ':'): ");
    Serial.println(current_mac_address);
  } else {
    Serial.println("\nConnection failed. Starting in Access Point mode.");
    startWiFiAP();
  }
}

void startWiFiAP() {
  Serial.println("Starting Access Point...");
  WiFi.mode(WIFI_AP);
  WiFi.softAP("UnderDeck", "12345678"); 
  Serial.print("Access Point (AP) started with SSID: ");
  Serial.println("UnderDeck");
  Serial.print("AP IP Address: ");
  Serial.println(WiFi.softAPIP());
  
  current_mac_address = getMacAddressNoColons(); 
  if (debug_mode == 1) {
    Serial.print("ESP32 MAC Address (without ':'): ");
    Serial.println(current_mac_address);
  }
}

void saveWiFiCredentials(const char* ssid, const char* password) {
  preferences.begin("wifi_creds", false);
  preferences.putString("ssid", ssid);
  preferences.putString("password", password);
  preferences.end();
  if (debug_mode == 1) Serial.println("WiFi credentials saved to memory.");
}

void loadWiFiCredentials(String &ssid, String &password) {
  preferences.begin("wifi_creds", true);
  ssid = preferences.getString("ssid", "");
  password = preferences.getString("password", "");
  preferences.end();
}

void saveDebugMode(int mode) {
  preferences.begin("system", false);
  preferences.putInt("debug_mode", mode);
  preferences.end();
  if (debug_mode == 1) Serial.println("Debug mode saved.");
}

int loadDebugMode() {
  preferences.begin("system", true);
  int mode = preferences.getInt("debug_mode", 0);
  preferences.end();
  return mode;
}

String getMacAddressString() {
  return WiFi.macAddress();
}

String getMacAddressNoColons() {
  String mac = WiFi.macAddress();
  mac.replace(":", "");
  return mac;
}

void connectToWebSocket() {
  if (ws_connected) {
    webSocketClient.disconnect();
    ws_connected = false;
  }

  if (WiFi.status() == WL_CONNECTED) {
    String url = websocket_path;
    url += current_mac_address;
    if (debug_mode == 1){
      Serial.print("Attempting to connect to wss://");
      Serial.print(websocket_server_host);
      Serial.println(url);
    }

    webSocketClient.beginSSL(websocket_server_host, websocket_server_port, url.c_str());
    webSocketClient.onEvent(webSocketEvent);
    webSocketClient.setReconnectInterval(5000);
    ws_connected = true;
  } else {
    if (debug_mode == 1) Serial.println("Not connected to WiFi, cannot connect to WebSocket.");
  }
}

void processSerial() {
  if (Serial.available()) {
    String command = Serial.readStringUntil('\n');
    command.trim();
    if (command.startsWith("{") && command.endsWith("}")) {
      if (debug_mode == 1) Serial.print("JSON command received via Serial: ");
      if (debug_mode == 1) Serial.println(command);
      blinkBuiltInLed(50);
      handleCommand(command, true);
    }
  }
}

void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) {
  switch (type) {
    case WStype_CONNECTED:
      Serial.println("[WS] Connected to server!");
      ws_connected = true;
      break;
    case WStype_DISCONNECTED:
      Serial.println("[WS] Disconnected from server!");
      ws_connected = false;
      break;
    case WStype_TEXT: {
      String message = String((char*)payload);
      if (debug_mode == 1) Serial.print("[WS] Message received: ");
      if (debug_mode == 1) Serial.println(message);
      blinkBuiltInLed(50);
      handleCommand(message, false);
      break;
    }
    case WStype_BIN:
    case WStype_ERROR:
    case WStype_PONG:
      break;
  }
}

void handleCommand(String payload, bool isSerial) {
  StaticJsonDocument<512> doc; 
  DeserializationError error = deserializeJson(doc, payload);

  if (error) {
    String response_error = "{\"status\":\"error\", \"message\":\"Invalid JSON\"}";
    if (debug_mode == 1) Serial.println(response_error);
    if (!isSerial) {
      webSocketClient.sendTXT(response_error);
    }
    return;
  }
  
  const char* type = doc["type"];
  if (type != nullptr && strcmp(type, "esp32") == 0) {
      const char* code = doc["code"];
      if (code != nullptr && strcmp(code, "off-line") == 0) {
          if (debug_mode == 1) Serial.println("Received 'off-line' message from server. No action needed.");
          return; 
      }
  }

  const char* action = doc["action"];
  String response;
  StaticJsonDocument<128> responseDoc;
  responseDoc["status"] = "success";
  responseDoc["action"] = action;

  if (action == nullptr) {
    responseDoc["status"] = "error";
    responseDoc["message"] = "Action not specified or invalid JSON";
  } else if (strcmp(action, "set_debug") == 0) {
    int new_mode = doc["mode"];
    if (new_mode == 0 || new_mode == 1) {
      debug_mode = new_mode;
      saveDebugMode(new_mode);
      responseDoc["message"] = "Debug mode changed to " + String(new_mode);
    } else {
      responseDoc["status"] = "error";
      responseDoc["message"] = "Invalid debug mode. Use 0 or 1.";
    }
  } else if (strcmp(action, "set_wifi") == 0) {
    const char* ssid = doc["ssid"];
    const char* password = doc["password"];
    if (ssid && password) {
      saveWiFiCredentials(ssid, password);
      responseDoc["message"] = "WiFi credentials saved. Restart to reconnect.";
    } else {
      responseDoc["status"] = "error";
      responseDoc["message"] = "Missing SSID/Password parameters.";
    }
  } else if (strcmp(action, "reboot_wifi") == 0) {
    if (debug_mode == 1) Serial.println("WiFi reboot command received.");
    String saved_ssid, saved_password;
    loadWiFiCredentials(saved_ssid, saved_password);
    if (saved_ssid.length() > 0 && saved_password.length() > 0) {
        WiFi.disconnect(true);
        startWiFi(saved_ssid.c_str(), saved_password.c_str());
    } else {
        WiFi.disconnect(true);
        startWiFiAP();
    }
    responseDoc["message"] = "Attempting to restart WiFi connection.";
  } else if (strcmp(action, "get_mac") == 0) {
    responseDoc["mac_address"] = current_mac_address;
  } else if (strcmp(action, "get_status") == 0) {
    bool isConnected = WiFi.status() == WL_CONNECTED;
    responseDoc["connected"] = isConnected;
    responseDoc["message"] = isConnected ? "Connected to WiFi" : "Not connected to WiFi";
  } else if (strcmp(action, "get_device_info") == 0) {
    responseDoc["hostname"] = WiFi.getHostname();
    responseDoc["mac_address"] = current_mac_address;
  } else if (strcmp(action, "enable_monitor") == 0) {
    int pin = doc["pin"];
    const char* sensorType = doc["type"];
    if (pin >= 0 && sensorType != nullptr) {
      enableSensorMonitor(pin, sensorType);
      responseDoc["message"] = "Enabled monitoring for " + String(sensorType) + " on pin " + String(pin);
    } else {
      responseDoc["status"] = "error";
      responseDoc["message"] = "Invalid parameters for enabling monitor.";
    }
  } else if (strcmp(action, "disable_monitor") == 0) {
    int pin = doc["pin"];
    const char* sensorType = doc["type"];
    if (pin >= 0 && sensorType != nullptr) {
      disableSensorMonitor(pin, sensorType);
      responseDoc["message"] = "Disabled monitoring for " + String(sensorType) + " on pin " + String(pin);
    } else {
      responseDoc["status"] = "error";
      responseDoc["message"] = "Invalid parameters for disabling monitor.";
    }
  } else if (strcmp(action, "read_analog") == 0) {
    const int pin = doc["pin"];
    if (pin >= 0) {
      int sensorValue = analogRead(pin);
      sendSensorValueToWebSocket(pin, "analog", sensorValue);
      responseDoc["message"] = "Analog value read at pin " + String(pin);
    } else {
      responseDoc["status"] = "error";
      responseDoc["message"] = "Invalid pin specified for analog sensor.";
    }
  } else if (strcmp(action, "led") == 0) {
    const int pin = doc["pin"];
    const char* state = doc["state"];
    if (pin != ledPin) {
      pinMode(pin, OUTPUT);
      ledPin = pin;
    }
    if (strcmp(state, "on") == 0) {
      digitalWrite(ledPin, HIGH);
      responseDoc["message"] = "LED turned on at pin " + String(ledPin);
    } else if (strcmp(state, "off") == 0) {
      digitalWrite(ledPin, LOW);
      responseDoc["message"] = "LED turned off at pin " + String(ledPin);
    }
  } else if (strcmp(action, "servo") == 0) {
    const int pin = doc["pin"];
    int position = doc["position"];
    if (pin != servoPin || !is_servo_attached) {
      if (is_servo_attached) {
        myServo.detach();
      }
      myServo.attach(pin);
      servoPin = pin;
      is_servo_attached = true;
    }
    myServo.write(position);
    responseDoc["message"] = "Servo moved to " + String(position) + " degrees at pin " + String(servoPin);
  } else {
    responseDoc["status"] = "error";
    responseDoc["message"] = "Unknown command";
  }

  serializeJson(responseDoc, response);
  if (!isSerial) {
    webSocketClient.sendTXT(response);
    if (debug_mode == 1) Serial.println(response);
  } else {
    Serial.println(response);
  }
}

void blinkBuiltInLed(int duration) {
  digitalWrite(builtInLed, HIGH);
  delay(duration);
  digitalWrite(builtInLed, LOW);
}

void sendSensorValueToWebSocket(int pin, const char* type, int value) {
  if (ws_connected) {
    StaticJsonDocument<128> eventDoc;
    eventDoc["action"] = "sensor_update";
    eventDoc["type"] = type;
    eventDoc["pin"] = pin;
    eventDoc["value"] = value;
    eventDoc["timestamp"] = millis();
    String json_string;
    serializeJson(eventDoc, json_string);
    webSocketClient.sendTXT(json_string);
    Serial.println(json_string);
    blinkBuiltInLed(50);
  } else {
    if (debug_mode == 1) {
      Serial.print("WebSocket not connected. Cannot send ");
      Serial.print(type);
      Serial.println(" sensor value.");
    }
  }
}

void monitorActiveSensors() {
  for (size_t i = 0; i < activeSensors.size(); ++i) {
    MonitoredSensor& sensor = activeSensors[i];
    int currentValue = 0;
    bool valueChanged = false;

    if (sensor.type == "button") {
      int reading = digitalRead(sensor.pin);
      if (reading != sensor.lastValue) {
        lastDebounceTime = millis();
      }
      if ((millis() - lastDebounceTime) > debounceDelay) {
        if (reading != sensor.lastValue) {
          currentValue = reading;
          valueChanged = true;
        }
      }
    } else if (sensor.type == "potentiometer") {
      currentValue = analogRead(sensor.pin);
      if (abs(currentValue - sensor.lastValue) > potThreshold) {
        valueChanged = true;
      }
    } else if (sensor.type == "touch") {
      currentValue = touchRead(sensor.pin);
      if (abs(currentValue - sensor.lastValue) > touchThreshold) {
        valueChanged = true;
      }
    }
    
    if (valueChanged) {
      sensor.lastValue = currentValue;
      sendSensorValueToWebSocket(sensor.pin, sensor.type.c_str(), currentValue);
    }
  }
}

void enableSensorMonitor(int pin, const char* type) {
  // Check if sensor is already being monitored
  for (const auto& sensor : activeSensors) {
    if (sensor.pin == pin && sensor.type == type) {
      if (debug_mode == 1) Serial.println("Sensor is already being monitored.");
      return;
    }
  }

  // Add sensor to the list and configure pin
  MonitoredSensor newSensor;
  newSensor.pin = pin;
  newSensor.type = type;
  
  if (newSensor.type == "button") {
    pinMode(pin, INPUT_PULLUP);
    newSensor.lastValue = digitalRead(pin);
  } else if (newSensor.type == "potentiometer") {
    newSensor.lastValue = analogRead(pin);
  } else if (newSensor.type == "touch") {
    newSensor.lastValue = touchRead(pin);
  }

  activeSensors.push_back(newSensor);
  if (debug_mode == 1) {
    Serial.print("Enabled monitoring for ");
    Serial.print(type);
    Serial.print(" on pin ");
    Serial.println(pin);
  }
}

void disableSensorMonitor(int pin, const char* type) {
  for (size_t i = 0; i < activeSensors.size(); ++i) {
    if (activeSensors[i].pin == pin && activeSensors[i].type == type) {
      activeSensors.erase(activeSensors.begin() + i);
      if (debug_mode == 1) {
        Serial.print("Disabled monitoring for ");
        Serial.print(type);
        Serial.print(" on pin ");
        Serial.println(pin);
      }
      return;
    }
  }
}

Now your ESP32 is ready to be implemented with Under Deck APIs and routes.