Webserver for SonOff basic, adressing Red LED, including sensor function

Webserver output

Sure there is Tasmota, ESPhome, Espruino or ESPEasy, but here is a simple webserver that controls the relay, the Green LED and also the Red LED.
On the WiFi version of the SonOff Basic, the Red LED is not connected (it is for the 433Mhz version), but it can be easily utilized by soldering a wire (See here and here how it is done). I wanted to pick picked gpio4 to control the LED, but with my ageing eyes gpio 14 was easier.

The server software is for a large part taken from randommerdtutorials (check them out of you already have not done so) and altered to my needs. I have added OTA, but as the SONOFF only has 1 Mb of memory so it might become a thigh squeeze. Understand that there might not be that much use in being able to switch on the LED’s, I am just showing how to do it. You may well want to use those LED’s as a signal function for something else.

I have added a readout of the voltage on the vcc pin. This might be quite futile for a mains fed esp8266, but it illustrates how to add sensor data. If with better eyes you could manage to switch the red LED from gpio4, you have gpio14 easily available for for instance for a DS18B20 temperature sensor. Also, gpio0 is easily accessible (it attaches to the push button), and realise that Tx and Rx can be used as gpio pins as well. (text continues below the program listing). If you decide to solder on the SonOff PCB: be very careful: the tracks are delicate. Don’t pull them off.

/*********
Sonoffbasic webserver
 Uses a big piece of code from
 https://RandomNerdTutorials.com/esp8266-relay-module-ac-web-server/

  GPIO0  Button (inverted)
  GPIO1  TX pin (C1 Pin 2)
  GPIO3  RX pin (C1 Pin 3)
  GPIO12 Relay
  GPIO13 Green LED (inverted)
  GPIO14 Optional Sensor (C1 Pin 5)
*********/

// Import required libraries
#include "ESP8266WiFi.h"
#include "ESPAsyncWebServer.h"
//---------------for OTA----------------------
#include <ESP8266mDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>
//---------------------------------------------
ADC_MODE(ADC_VCC);
int vcc;
double dvcc;


// Set to true to define Relay as Normally Open (NO)
#define RELAY_NO    true

// Set number of relays
#define NUM_RELAYS  3
// Assign each GPIO to a relay
int relayGPIOs[NUM_RELAYS] = { 12, 13, 14};
String devices[NUM_RELAYS] = {"Switch", "GreenLed","RedLed"};

// Replace with your network credentials
const char* ssid = "YourSSID";
const char* password = "YourPW";

const char* PARAM_INPUT_1 = "relay";
const char* PARAM_INPUT_2 = "state";

// Create AsyncWebServer object on port 80
AsyncWebServer server(80);
AsyncEventSource events("/events");// voor SSE

signed long lastTime = -10000;
unsigned long timerDelay = 10000;  // send readings timer: 10sec

const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href='https://use.fontawesome.com/releases/v5.7.2/css/all.css' integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
    <link rel="icon" href="data:,">

  <style>
    html {font-family: Arial; display: inline-block; text-align: center;}
    h2 {font-size: 3.0rem;}
    p {font-size: 1.0rem;}
    .solid {border-style: solid;border-padding: 1em;}
    .center {
      margin-left: auto;
      margin-right: auto;
      }
table{
  border: 1px solid;
  }
    sensor-head {font-size: 12px; font-style: italic;font-weight: bold; vertical-align:middle;}
    sensor-labels {font-size: 6px;vertical-align:middle;padding-bottom: 10px;}
    body {max-width: 600px; margin:0px auto; padding-bottom: 25px;}
    .switch {position: relative; display: inline-block; width: 89px; height: 34px} 
    .switch input {display: none}
    .slider {position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; border-radius: 34px}
    .slider:before {position: absolute; content: ""; height: 26px; width: 26px; left: 4px; bottom: 4px; background-color: #fff; -webkit-transition: .4s; transition: .4s; border-radius: 68px}
    input:checked+.slider {background-color: #2196F3}
    input:checked+.slider:before {-webkit-transform: translateX(52px); -ms-transform: translateX(52px); transform: translateX(52px)}
  </style>
</head>
<body>
  <h2>SonOff Basic</h2>
  %BUTTONPLACEHOLDER%
<script>function toggleCheckbox(element) {
  var xhr = new XMLHttpRequest();
  if(element.checked){ xhr.open("GET", "/update?relay="+element.id+"&state=1", true); }
  else { xhr.open("GET", "/update?relay="+element.id+"&state=0", true); }
  xhr.send();
}</script>
<p>
<table class='center' id='solid'><tr>
<th><i class="fa-thin fa-bolt" style="color:#ff9e8a;"></i></td> <td><span class="sensor-labels">Vcc</span></td>
    <td><span id="vcc">%VCC%</span></td>
    <td><sup class="units">V</sup></td></tr>
</table>
</p>
 <!-- code voor event -->
  <script>
if (!!window.EventSource) {
 var source = new EventSource('/events');

 source.addEventListener('open', function(e) {
  console.log("Events Connected");
 }, false);
 source.addEventListener('error', function(e) {
  if (e.target.readyState != EventSource.OPEN) {
    console.log("Events Disconnected");
  }
 }, false);

 source.addEventListener('message', function(e) {
  console.log("message", e.data);
 }, false);

source.addEventListener('vcc', function(e) {
  console.log("vcc", e.data);
  document.getElementById("vcc").innerHTML = e.data;
 }, false);

}
</script>
 

</body>
</html>
)rawliteral";

// Replaces placeholder with button section in your web page
String processor(const String& var){
  //Serial.println(var);
  if(var == "BUTTONPLACEHOLDER"){
    String buttons ="";
    for(int i=1; i<=NUM_RELAYS; i++){
      String relayStateValue = relayState(i);
      buttons+= "<h4>"+devices[i-1]+" - Output #" + String(i) + " - GPIO " + relayGPIOs[i-1] + "</h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"" + String(i) + "\" "+ relayStateValue +"><span class=\"slider\"></span></label>";
    }
    return buttons;
  }
  
 if (var=="VCC"){
      vcc = ESP.getVcc();
      dvcc = 1*(float)vcc / 1024;
      return String(dvcc, 3);
      }

  return String();
}

String relayState(int numRelay){
  if(RELAY_NO){
    if(digitalRead(relayGPIOs[numRelay-1])){
      return "";
    }
    else {
      return "checked";
    }
  }
  else {
    if(digitalRead(relayGPIOs[numRelay-1])){
      return "checked";
    }
    else {
      return "";
    }
  }
  return "";
}

void setup(){
  // Serial port for debugging purposes
  Serial.begin(115200);

  // Set all relays to off when the program starts - if set to Normally Open (NO), the relay is off when you set the relay to HIGH
  for(int i=1; i<=NUM_RELAYS; i++){
    pinMode(relayGPIOs[i-1], OUTPUT);
    if(RELAY_NO){
      digitalWrite(relayGPIOs[i-1], HIGH);
    }
    else{
      digitalWrite(relayGPIOs[i-1], LOW);
    }
  }
  
  // Connect to Wi-Fi
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi..");
  }

  // Print ESP8266 Local IP Address
  Serial.println(WiFi.localIP());

  // Route for root / web page
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/html", index_html, processor);
  });


 // Handle Web Server Events
events.onConnect([](AsyncEventSourceClient *client){
  if(client->lastId()){
    // //Serial.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId());
  }
  // send event with message "hello!", id current millis
  // and set reconnect delay to 1 secondtemperature_koudebak = sensors.getTempC(ProbeBinnen);
  client->send("hello!", NULL, millis(), 10000);
});
server.addHandler(&events);

  // Send a GET request to <ESP_IP>/update?relay=<inputMessage>&state=<inputMessage2>
  server.on("/update", HTTP_GET, [] (AsyncWebServerRequest *request) {
    String inputMessage;
    String inputParam;
    String inputMessage2;
    String inputParam2;
    // GET input1 value on <ESP_IP>/update?relay=<inputMessage>
    if (request->hasParam(PARAM_INPUT_1) & request->hasParam(PARAM_INPUT_2)) {
      inputMessage = request->getParam(PARAM_INPUT_1)->value();
      inputParam = PARAM_INPUT_1;
      inputMessage2 = request->getParam(PARAM_INPUT_2)->value();
      inputParam2 = PARAM_INPUT_2;
      if(RELAY_NO){
        Serial.print("NO ");
        digitalWrite(relayGPIOs[inputMessage.toInt()-1], !inputMessage2.toInt());
      }
      else{
        Serial.print("NC ");
        digitalWrite(relayGPIOs[inputMessage.toInt()-1], inputMessage2.toInt());
      }
    }
    else {
      inputMessage = "No message sent";
      inputParam = "none";
    }
    Serial.println(inputMessage + inputMessage2);
    request->send(200, "text/plain", "OK");
  });
  // Start server
  server.begin();
  //----------------------------OTA--------------------
  
  ArduinoOTA.setHostname("Sonoff");

  // No authentication by default
 //  ArduinoOTA.setPassword("admin");

  ArduinoOTA.onStart([]() {
    String type;
    if (ArduinoOTA.getCommand() == U_FLASH) {
      type = "sketch";
    } else { // U_FS
      type = "filesystem";
    }

    // NOTE: if updating FS this would be the place to unmount FS using FS.end()
    Serial.println("Start updating " + type);
  });
  ArduinoOTA.onEnd([]() {
    Serial.println("\nEnd");
  });
  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
    Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
  });
  ArduinoOTA.onError([](ota_error_t error) {
    Serial.printf("Error[%u]: ", error);
    if (error == OTA_AUTH_ERROR) {
      Serial.println("Auth Failed");
    } else if (error == OTA_BEGIN_ERROR) {
      Serial.println("Begin Failed");
    } else if (error == OTA_CONNECT_ERROR) {
      Serial.println("Connect Failed");
    } else if (error == OTA_RECEIVE_ERROR) {
      Serial.println("Receive Failed");
    } else if (error == OTA_END_ERROR) {
      Serial.println("End Failed");
    }
  });
  ArduinoOTA.begin();
  Serial.println("Ready");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
  
}
  
void loop() {
 ArduinoOTA.handle();
if ((millis() - lastTime) > timerDelay) {
  sendEvents();
  lastTime = millis();
}
}

void sendEvents()
{
vcc = ESP.getVcc();
dvcc =1*(float)vcc / 1024;
events.send(String(dvcc, 3).c_str(),"vcc",millis());
}

Rather than checking for millis() in the loop() to determine the passed time, I could have uses a ‘setInterval()’ function in JavaScript, but in this case just a quick check in the loop was easier

As said, I added a read out of the voltage to illustrate how you could add a sensor. We can spice up that readout for instance by making it change color at a certain level. Suppose we want the voltage readout to turn red when it rises above 3.3Volt and back to black when it drops back to 3.3volt or less.
that is quite easy. First, keep in mind that the voltage readout is sent to the server at two instances: initially when the webpage is built and then at every 10 secs (which is the chosen length of the interval).
First we will change the section where the initial build up of the page is taking place: for this locate the section that begins with if var=="VCC){.
Amend that entire section as follows:

 if (var=="VCC"){
      vcc = ESP.getVcc();
      dvcc = 1*(float)vcc / 1024;
      if (dvcc>3.3){
         return "<span style='color:red'>"+String(dvcc, 3)+"</span>";
             }
      if (dvcc<=3.3){
         return String(dvcc, 3);
             }
      }
  return String();
}

That should deal with the initial buildup of the page. Where you to upload this, you would see the voltage readout in red (if it were above 3.3 Volt), but it would turn back to black after 10 secs.

To fix that, go to the end of the program to the “void sendEvent()” function.
amend this as follows:

void sendEvents()
{
vcc = ESP.getVcc();
dvcc =1*(float)vcc / 1024;
if(dvcc>3.3){
  formatdvcc="<span style='color:red'>"+String(dvcc,3)+"</span>";}
if (dvcc<=3.3){
  formatdvcc=String(dvcc,3);
  }
events.send(formatdvcc.c_str(),"vcc",millis());
}

One final thing to do is to go to the declaration section of the program and add: String formatdvcc;.

In case you wonder why I do “dvcc =1*(float)vcc / 1024;” rather than just “dvcc =(float)vcc / 1024;“, that is because the ADC of the ESP8266 is not very accurate and you may need to add a specific multiplication factor to let it 3V3 actually show as 3V3. I have chosen ‘1” for that. Given that my readout is commonly 3.425 Volt, it should probably have been 0.963 rather than 1, but it is not so important. Obviously it is also possible to attach some warning function this way (a buzzer, a flashing LED).

If you like the box around the Vcc value to have a bit more rounded corners in order to be less of a contrast with the round slider buttons, change the table section in style section as follows:

table{
border: 1px solid;
border-radius: 10px;
}

Other software:

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: