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)

Other software:

Real time graph for data logging

While scowering the internet for some hardware info on data logging i came upon a logging program (using chartjs) that seemed interesting at first, but that kept crashing my ESP8266 the moment the webpage was loaded. But as i like a challenge, I set out to find the fault and fix it. In the end it involved altering the entire structure of the program.
Another issue with that program was that it loaded 4 fake values before it started recording some real values. This was obviously done to have some initial values in the graph during the first interval time. There is a better way to do that so I fixed that too. Finally, I made it suitable to be viewed on a tablet or phone as well.

This is the final program: (note, there is a mistake in the html because wordpress cannot display it correctly. Best get the file here.

/*
 * ESP8266 NodeMCU Real Time Graphs Demo
 * Updates and Gets data from webpage without page refresh
 */
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>

#define LED 2  //On board LED

//SSID and Password of your WiFi router
const char* ssid = "YourSSID";
const char* password = "YourPW";

ESP8266WebServer server(80); //Server on port 80
//---------------------
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html>

<head>
  <title>Data logger</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
//the below line is wrong and wordpress cannot show the right way. The link should be in a 'script src=' tag and closed with '/script' html tag
  https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.3/Chart.min.js
  <style>
  canvas{
    -moz-user-select: none;
    -webkit-user-select: none;
    -ms-user-select: none;
  }

  /* Data Table Styling */
  #dataTable {
    font-family: "Trebuchet MS", Arial, Helvetica, sans-serif;
    border-collapse: collapse;
    width: 100%;
  }

  #dataTable td, #dataTable th {
    border: 1px solid #ddd;
    padding: 8px;
  }

  #dataTable tr:nth-child(even){background-color: #f2f2f2;}

  #dataTable tr:hover {background-color: #ddd;}

  #dataTable th {
    padding-top: 12px;
    padding-bottom: 12px;
    text-align: left;
    background-color: #4CAF50;
    color: white;
  }
  </style>
</head>

<body>
    <div style="text-align:center;">Real Time Voltage logging</div>
    <div class="chart-container" position: relative; height:350px; width:100%">
        <canvas id="Chart" width="400" height="400"></canvas>
    </div>
<div>
  <table id="dataTable">
    <tr><th>Time</th><th>ADC Value</th></tr>
  </table>
</div>
<br>
<br>  

<script>
//Graphs visit: https://www.chartjs.org
var values = [];
var timeStamp = [];
function showGraph()
{
    for (i = 0; i < arguments.length; i++) {
      values.push(arguments[i]);    
    }

    var ctx = document.getElementById("Chart").getContext('2d');
    var Chart2 = new Chart(ctx, {
        type: 'line',
        data: {
            labels: timeStamp,  //Bottom Labeling
            datasets: [{
                label: "Voltage",
                fill: false, 
                backgroundColor: 'rgba( 18, 156, 243 , 1)', //Dot marker color
                borderColor: 'rgba( 18, 156, 243 , 1)', //Graph Line Color
                data: values,
            }],
        },
        options: {
            title: {
                    display: true,
                    text: "ADC Voltage"
                },
            maintainAspectRatio: false,
            elements: {
            line: {
                    tension: 0.2 //Smoothening (Curved) of data lines
                }
            },
            scales: {
                    yAxes: [{
                        ticks: {
                            beginAtZero:false
                        }
                    }]
            }
        }
    });

}

//On Page load show graphs
window.onload = function() {
  console.log(new Date().toLocaleTimeString());
  getData();
  showGraph();
};

//Ajax script to get ADC voltage at every 5 Seconds 

setInterval(function() {
  // Call a function repetatively with 5 Second interval
  getData();
}, 5000); 
 
function getData() {
  var xhttp = new XMLHttpRequest();
  xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
     //Push the data in array
  var time = new Date().toLocaleTimeString();
  var ADCValue = this.responseText; 
      values.push(ADCValue);
      timeStamp.push(time);
      showGraph();  //Update Graphs
  //Update Data Table
    var table = document.getElementById("dataTable");
    var row = table.insertRow(1); //Add after headings
    var cell1 = row.insertCell(0);
    var cell2 = row.insertCell(1);
    cell1.innerHTML = time;
    cell2.innerHTML = ADCValue;
    }
  };
  xhttp.open("GET", "readADC", true); //Handle readADC server on ESP8266
  xhttp.send();
}
    
</script>
</body>

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

void handleRoot() {
 server.send(200, "text/html", index_html); //Send web page
  
}

void handleADC() {
 int a = analogRead(A0);
 String adcValue = String(a);
 digitalWrite(LED,!digitalRead(LED)); //Toggle LED on data request ajax
 server.send(200, "text/plane", adcValue); //Send ADC value only to client ajax request
}

void setup(){
  Serial.begin(115200);
  
  WiFi.begin(ssid, password);     //Connect to your WiFi router
  Serial.println("");

  //Onboard LED port Direction output
  pinMode(LED,OUTPUT); 
  
  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  //If connection successful show IP address in serial monitor
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());  //IP address assigned to your ESP

  server.on("/", handleRoot);      
  server.on("/readADC", handleADC); 
  server.begin();                  
  Serial.println("HTTP server started");
}

void loop(){
  server.handleClient();          //Handle client requests
}

The below picture shows what the corrupted line should look like, but best download the file from here.

Coldframe monitoring with an ESP8266 (2)

In an earlier post, I described a Coldframe that was being monitored by an ESP8266. Though I was quite happy with the result, I did learn a few things that I would do different with a new ColdFrame, which I now have built.
I made the following changes:
I ditched the HTU21D sensor as I found it unreliable. I replaced it with a DS18B20. I defaulted on measuring the humidity as it did not add much value as it is usually pretty high.

I replaced the PCF8591 with an ADS1015 because it has a much faster conversion time. The ADS1015 works a bit different from the PCF8591. The PCF is an 8 bit ADC. If you feed it with 3.3Volts then the max readout of 256 is 3.3Volt. If you feed it with 5Volt, a readout of 256 means 5 Volt.
That is not how the ADS1015 works. It is a 12 bit ADC so the max analog reading is 4096, but that does not represent the Vcc. In it’s default state a reading of 4096 means 4.096 Volts, regardless whether the Vcc is 3V3 or 5Volt. The problem I faced with this is that the datasheet is rather unclear on this point. The datasheet claims that in its default state the max reading is 4.096 Volt, but that the LSB is 2mV. However, if the max analog reading is 4096 (the max 12 bit number) and that represents 4.096 Volt, the LSB should be 1mV. The description in the Sparkfun library examples makes it even more obscure as it says:

*at default gain setting of 1 (and 3.3V VCC), 0-2V will read 0-2047.
*anything greater than 2V will read 2047

This seems rather odd and illogical and also contradictory to what they claim in another example. Sure it fits with the ‘2mV/LSB’ but why would it not read any voltage over 2 Volt? A quick test setup shows that with 3V3 as Vcc and as input, the ADC reading is 1655, which would suggest indeed a 2mV/LSB, but that also means that the max reading of 2047 for 2 Volt is not correct and that the reading still can indicate more than 2 Volt. If I set the programmable gain at 2/3, then according to the datasheet is 3mV/LSB. My test setup showed a reading of 1100 for 3v3, which is indeed a 3mV/LSB, but a full 12Bit reading should be 12.288Volt, not 6.144 Volt as the datasheet says. Mind you though that input on the ADC should never exceed the Vcc.

I did away with the fan that could blow air out and replaced it with a fan that would just use for an internal air-stream. I have hesitated to put any control buttons on the interface and maybe go for all automatic. Especially the fan that now has become an internal fan would be best to always leave on as airflow is good for plants under any circumstance.
I have added a fan to blow air through an underground pipe, to get rid of hot air during the day and pull out warm air during the night, and perhaps that is best handled automatic also. I also added a heater, to keep the temperature above zero degrees if necessary, and perhaps that is best left fully automatic as well.
That leaves the lid opener. It functions pretty well in all it’s simplicity, but as the now added heatsink, appears to manage keeping the temperature below 20 degrees centigrade (=68 F), I ditched it.

Sending messages through Signal or Whatsapp

Earlier I described how to send messages from your ESP8266 (or ESP32 for that matter) via the Telegram message app.
Signal is a slightly less known app that is getting more and more popular and it is also possible to send messages from your microcontroller through that app. It is even simpler (in my humble opinion than via Telegram).
Here is what to do:
install the messenger app on your phone and register.
Add the number “+34 603 21 25 97” to your contact list and give it a name (let’s say ‘Signal Bot’)

Yes, the SignaBot has Whatsapp too


Send the following message to that contact:
“I allow callmebot to send me messages”

You should get a reply “New user registered”, followed by an API key

It is now possible to send messages to your phone with https:/api.callmebot.com/signal/send.php?phone=<yourphonenumber>&apikey=xxxxxx&text=This+is+a+test

So suppose your international phonenumber =+31612345678 and your Api=123456, and your message is “Hello World”, then the call will become:

https:/api.callmebot.com/signal/send.php?phone=+31612345678&apikey=123456&text=Hello+World

Sending pictures is possible like this

https://api.callmebot.com/signal/send.php?phone=+31612345678&apikey=123456&image=https://arduinodiy.files.wordpress.com/2021/07/signal.jpg

Currently it is not possible yet to send messages to Signal, but that supposedly is in the making.

Program
A https connection is necessary, which the ESP8266/ ESP32 can do. Unles you plan to send highly classified material, it is easiest to do away with certificates or fingerprint and use ‘client.setInsecure();’
You can download the program here.

Just a few remarks: My first version of the program worked immediately. However, it did send one message, and somehow it would never send another message. Tried several solutions, but none brought it back to life. Then I picked a basic example of Ivan Gorotkov that I adapted to use with setInsecure() rather than with fingerprint.
As Ivan’s core code was virtually akin to my first program, albeit with some checks on the http request….it did not work. Curious about what the request reply was (hoping to find an indication of the problem), I started fiddling a bit with the cut-off character. Normally this was a “\n”, but that is not very helpful if the first character is in fact a return. To my surprise, using another character, suddenly brought the program back to life and it has been working reliable since (albeit that one time a message took a bit long to arrive). Now it should not really matter in what way i read the reply string in order for the program to function properly, but it does, so I suspect there is a bit of a timing issue before the connection is closed. If anybody can give some insight in this i would be glad to hear it.

Whatsapp

The same approach works for Whatsapp as well:

Note that the https request string is slightly different from the one used in Signal. (It is also possible to send messages from your ESP8266 through WhatApp via Twilio and Webhooks)

Telegram & messenger
The ‘callmebot’ supposedly works with telegram and facebook messenger as well.
As far as telegram goes, the approach there is a bit different.
Make sure you have a usename set in Telegram (in ‘settings’. I found this is easiest through a browser)
Authorize callmebot through this link. Or, you can start the bot sending /start to @CallMeBot_txtbot.
The https request to send is:
https://api.callmebot.com/text.php?user=@yourusername&text=This+is+a+test+from+CallMeBot

Then send messages with this call

https://api.callmebot.com/text.php?user=@yourusername&text=This+is+a+test+from+CallMeBot

mind you though that telegram als has its own bot service I refer to that one at the beginning of this article.
As i do not use facebook, I cannot really try that out, but the procedure is described here.

Parametric 3D Box and Lid

Often, when doing a project, it stays i sort of a ‘experimental’ state coz of lack of a proper casing. I designed a universal box and lid in Freecad. It is parameter driven, which means you can just fill in the required measures in a spreadsheet and you are good to go. Mind you that both the lid and the case need to have the parameters set.
You will find a parameter called ‘shim’. That is just to create a tiny bit of space between the lid extrusion and the casing. For a very tight fit set it to ‘0’

The files are available here.

Spreadsheet for the lid
Spreadsheet for the case

Te above will create a box with outer dimensions of 75x55x30 mm. The wall thickness will be 2 mm. two flaps of 5x5mm are added with a screw hole of 3mm (i advise to make the flaps and the hole a bit bigger, say 8xb8 mm and a 4-5mm hole.) It should fit a standard 5×7 cm veroboard.

The casing has 2 holes, one for a 3mm LED and one for cables going in and out. They can b easily removed if not necessary

How to modify the AsyncElegantOTA library…..if you really need to

The AyncElegantOTA library is an easy way to add WebOTA to your projects with well, uh an ‘Elegant’ interface.

A recent discussion an facebook was about ‘how can I modify the AsyncElegantOTA?’

Ofcourse it is not so hard to do that if you know something about JavaScript and/or HTML, but one who dives into the library files will soon see that a large part of it seems to be encrypted, which no doubt is a hurdle for many, but it can be taken.

It might be worthwhile though to ask yourself why you would want to change the AsyncElegantOTA library, why not just use the native ESP8266HTTPUpdateServer library and make any desired modifications in that one (coz let’s admitt it, that one has a boring interface). No need to decode any encrypted files. You will find that library in C:\Users\<name>\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.7.3\libraries\ESP8266HTTPUpdateServer.src. There is a great example of this library being modified to include password protection and to look a bit more jazzy.

But if you really want to tackle the AsyncElegantOTA library here is how to do it:

Go to where you installed the AsyncElegantOTA files and open the elegantWebpage.h file. It looks like this:

The part we are interested in is de PROGMEM content, but we can’t do anything with it as long as it is still encoded. To decode it copy the PROGMEM content in its entity, without the brackets. So in fact just the numbers with the comma’s in between

Then go to the Cyberchef webpage, and paste the PROGMEM content in the input field. We need to do 2 decoding actions on the inputfield.
First: Under operations choose “From Decimal” (under the heading ‘Dataformat’), double click it so it is moved under “Recipe” and pick ‘Comma’ as the delimiter. (if you follow the link I gave, both operations should already be added)

However, before you add the second operation (the ‘Unzip’), we will first do a check: Look in the ‘Output’ header. You will find 3 parameters

If you check ‘length’ in the ‘Output’ heading, you will see that number is equal to the ELEGANT_HTML_SIZE constant that is mentioned in the elegantWebpage.h. if you are using version 2.2.5 of the library, that number most likely is “53715”. Just take a mental note of where/when that number popped up (i.e. ‘after the ‘From decimal conversion of the encoded section’) because we need that mental note later to calculate a new value Output’
The second decoding we need to add is an unzip. So under operations we now choose “Gunzip” (find it under the ‘Compression’ heading), and in the output file an HTML structure should appear.

If you scroll through the HTML, you will see that it has JavaScript embedded in it the HTML code. This in total (the HTML and the JS) is the code you can modify. What you exactly want to modify is up to you.

Once you have modified the HTML /Javascript file, there are several ways you could embed it in the library again; You could leave it uncrypted and put it back in your elegantWebpage.h file as a ‘Raw literal’, like so:

const char ELEGANT_HTML[] PROGMEM = R"---(
<html>
<head>
...
)---";



but then you would need to make a change in the AsyncElegantOTA.h file as well:

The above line would need to be changed to something like:

AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", ELEGANT_HTML);

provided that “ELEGANT_HTML” now is the name you gave to the raw literal. This is not the method I followed though, as I found it simpler to just encode the HTML file again. In order to do this, we go back to the Cyberchef website but now paste the HTML file in the input field. Either remove or pause the two previous operations and now add “Gzip” and “To Decimal”, like so:

If you have done right you now should get a new decimal code in your output field. Copy that and paste it over the previous PROGMEM content in your ‘elegantWebpage.h’ file.

You are now almost done. you need to update the size constant in your elegantWebpage.h file.

const uint32_t ELEGANT_HTML_SIZE = 53715;

Well, remember how I asked you to make a mental note earlier of where you could find the length of the file?

Well we have to find that length for our modified and recrypted file
After you copied your “altered and recrypted to decimal again” file from the output field and pasted it in the elegantWebpage.h file, you now paste it in the input field of the Cyberchef website again. Make sure the only operation you choose is “From Decimal” with ‘Comma’ set as delimiter. Now in the Output field header you will see the new size: use that one to update the ELEGANT_HTML_SIZE constant.

Your modifcations should work now

50 Shades of Blink

Well, not really 50, but I will dive into various ways to make an LED blink, after all, if you can control an LED you can control a relay and if you can control a relay you can control the world. This article is mainly aimed at showing beginners various approaches to a specific functionality, but perhaps there will be some new stuff for the more seasoned user as well, as I will also touch upon assembly language.

Everybody who is using an Arduino started with the blink sketch. if not loaded by themselves, it was probably already loaded on the arduino they got.
In all it’s simplicity it looks like this:

void setup() {
  // initialize digital pin LED_BUILTIN as an output.
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(500);                       // wait for a second
  digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
  delay(500);                       // wait for a second
}

When compiled it uses 924 bytes or 3% of the available storage space. That is a lot for just a blinking LED. Can we do better?
Yes we can. We can use something that is called ‘port manipulation’.

If we look at the pin-out of the Arduino, we see that digital pin13 is also called ‘PB5’. That means that it is controlled by the 5th bit of a register that is called ‘PORT B’. Instead of using statements like ‘digitalWrite(13, HIGH);’ we can directly manipulate the status of that 5th bit.


There is something similar of the ‘pinMode’ command: Input and output for the arduino is controlled by another register that is called the ‘Data Direction Register’. For PORTB (that is the one we need for pin13/PB5) we use ‘DataDirectionRegisterB’ or DDRB and as we want to set it to output, we need to write a ‘1’ in that bit. We could do that by writing DDRB=”0B11111111″, but then we set D8-D13 to output (PB0-PB5) all to output and we (uselessly) write to the bits that control the bits controlling the 16Mhz crystal (PB6-PB7).
So we have to make sure we only write to PB5. There are several ways to do that, but I have chosen to ‘bitwise OR’ the DDRB register. With a bitwise OR, we leave the original content of the register intact and only set the bit HIGH that we want to be HIGH.

The bitwise OR of two bits is 1 if either or both of the input bits is 1, otherwise it is 0. In other words:

    0 | 0 == 0
    0 | 1 == 1
    1 | 0 == 1
    1 | 1 == 1


So suppose the content of the DDRB= 00000000 and we OR that with 00100000, it becomes 00100000. If originally it was 00000100 and we OR it with 00100000, it becomes: 001000100. The sign for the OR command is ‘|’.

So our pinMode(13,1); statement becomes DDRB=DDRB | 0B00100000;

We do something similar setting bit 5 of PORTB HIGH, so our digitalWrite(13,HIGH); becomes PORTB=PORTB | B00100000;

In order to set set bit5 of PORT B low again (the equivalent of digitalWrite(13,LOW);), we cannot just ‘OR’ the PORTB register with ‘0’ as that would have no effect: it leaves all ‘1”s to ‘1’. We could just write a ‘0’ to the register, that would work, but then we set all bits to ‘0’ and we might not want to do that.
For this we use another bitwise operator, the ‘bitwise AND’ (symbol &). The truth table looks like:

    0 & 0 == 0
    0 & 1 == 0
    1 & 0 == 0
    1 & 1 == 1

so suppose our PORTB register has bit 5 and bit 1 HIGH and we only want bit 5 to be set LOW, we have to ‘AND’ the PORTB register with 0B11011111. Therefore digitalWrite(13,LOW); becomes PORTB=PORTB & 0B11011111;
Now there are some ‘easier’ ways to deal with having to count zero’s and one’s. It is obvious that 0B11011111 is the inverse of 0B00100000 and as such it can be written as ~(0B00100000). it still can be ‘simpler’ or at least with less ‘counting’ we can just state that we want a ‘1’ on the position of PB5. That is written as (1<<PB5). So our statement becomes PORTB= PORTB& ~(1<<PORTB5); and that can be written in good C++ fashion like PORTB &=~(1<<PORTB5);
The program then looks like this:

void setup() {
  // initialize digital pin 13 as an output
DDRB = DDRB | 0B00100000 ; //set bit 5 to output
}

void loop() {
PORTB = PORTB | B00100000;
delay(500);
PORTB &= ~(1<<PORTB5);
delay(500);
}

//644 bytes
//gewone blink: 924 bytes (3%)
//assembler blink 488 bytes (1%)

We see that we now saved some memory as we only need 640 bytes

Can we do better? yes we can: the ‘delay(500)’ statement is one of those high order statements that might be consuming more bytes than necessary. With some caveats, we can use the _delay_ms(); function

//depending on your IDE version and setup you may need to include the following system libraries
//#include <avr/io.h>
//#include <avr/delay.h>
void setup() {
  // initialize digital pin 13 as an output
DDRB = DDRB | 0B00100000 ; //set bit 5 to output
}

void loop() {
PORTB = PORTB | B00100000;
_delay_ms(1000);
PORTB &= ~(1<<PORTB5);
_delay_ms(500);
}

Again some savings, we now only need 486 bytes

We still can shave a few (12) bytes off if we give up the possibility to have different on and off times. We do that by just toggling the D13 pin (=PB5), like so:

void setup() {
  DDRB = DDRB | 0B00100000; //set pinMode High for D13
}

void loop() {
 
 PORTB=PORTB^0B00100000;//toggle the value of D13
  
 _delay_ms(1000);// =delay(1000);

}

We now only use 474 bytes

That is quite a saving compared to the original 924 bytes plus, we now have a non blocking blink. The downside of using registers is that your code becomes less portable. What may work on an Atmega328 will likely not work on an Atmega8 or Atmega32 unless you adapt registers.
Can we do better? Going to be hard, but let’s see if we can. How about if we forego C++ and use assembly language?

It is possible to write assembly in the arduino IDE. You can do it in the main ino file with the inline ‘asm’ command, but I prefer to do it in a separate asm file.
For that, open the IDE and create two tabs. One called “blinkasm.ino” (or any other name you prefer) and a second one called “blink.S” (or any other name as long as the extension is a capital “S”). Like this:

Then in the ino file we only have to give some basic instructions to go to the asm file.
We start with

extern "C" {
  void start();
  void blink();
}

This basically says that we define two external procedures, called ‘start’ and ‘blink’.
Then in the startup() we just call ‘start()’ and in the loop we call ‘blink()’
The entire file then looks like this:

extern "C" {
  void start();
  void blink();
}
void setup() {
  start();
}

void loop() {
  blink();
}

we then go to the ‘blink.S’ tab and past the following code:

#define __SFR_OFFSET 0
#include "avr/io.h"

.global start
.global blink

start:
sbi DDRB,5    ;PB5as output
ret

blink:
ldi r20,250 ;  set delay in msec. Max is 255
call delay_n_ms
sbi PORTB,5
ldi r20,250
call delay_n_ms
cbi PORTB,5
ret

delay_n_ms:
ldi 31, 6400>>8    ;here we divide 6400 ovet 2 registers
ldi 30, 6400&255
delaylp:
sbiw r30,1  ;subtract immediate word -> subtract a <63 value from a 16 bit pointer register
brne delaylp
subi r20,1  ;subtract "1" from r20 and store in r20
brne delay_n_ms
ret

It is not my intention to give an entire course in AVR assembly so i will discuss the ASM program very superficially: We see 3 procedures: ‘start’ that you could compare with the ‘setup’ in arduino programming, there is ‘blink’ that is repeatedly called from the ‘loop’ in the ino tab,and there is ‘delay_n_ms’ (delay number of microsecs).

In ‘start’, the only thing we do is to set bit 5 in the DataDirectionregisterB (DDRB) and by now we know that means ‘set D13 as output’

In the blink procedure we load 250 in register 20. register 20 is a common register in the ATMEGA 328 that is available to the user,however it can only contain one byte, so we cannot put more in it than 255. For now we put in 250 and use that as a base for our 500ms delay. We then set bit 5 of PORTB, switching the LED on PB5 (=D13) on. We then call the delay function (explained later). We then load r20 again with 250 again and call the delay function again.
The delay function does the following: it creates a loop that takes 5 cycles. On a 16MHz Arduino 1ms takes 1600 cycles, so if we go through that loop 16000/5 =3200 times we have delayed 1ms and if we do it 6400 times, we have a delay of 2ms (stating the obvious). If we then repeat that entire proces 250 times (being the value in r20) we come to 500ms.

Surprisingly, the ASM code takes up a bit more space than the code in which we bitwise manipulated the registers

It uses 488 bytes as opposed to 486 bytes in the previous program.
I am fairly sure though that it is not the most optimal way to write Assembler code and we might still be able to shave a few bytes off using a proper editor, but we are probably very close to what is possible.

The downside of using Assembly is that your code becomes less portable. What may work on an Atmega328 will likely not work on an Atmega8 or Atmega32.

There is one thing though that all these programs have in common that virtually makes them worthless: They are ‘blocking’ codes. that means that as long as the LED is blinking, it is hard to have the code do anything else, so we have to come up with a non blocking way to blink the LED’s. I know that is cut and dry to anybody who spent a bit of time with the Arduino, but for completeness sake I will discuss it here. After all, novices have to learn somewhere. The only thing you ahve to do to avoid blocking code is to get rid of the delay statements and make the LED blink in another way.We can do that with timers, for which e.g. the ‘micros’ command is very handy.

#define BlinkPeriod 500  // this is in milliseconds

boolean ledState = LOW;

// here we store the last time the LED blinked
unsigned long lastTime = 0;

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  unsigned long now = millis();
  if (now - lastTime >= BlinkPeriod) // this will be true every 500 milliseconds
  {
    lastTime = now;
    if (ledState == LOW)
    {
      ledState = HIGH;
    }
    else
    {
      ledState = LOW;
    }
    digitalWrite(LED_BUILTIN, ledState);
  }

  //can do other stuff here
}

What this program factually does is that it continuously compares the time the loop has been running since the LastTime the LED blinked (now-LastTime) against a preset time (‘BlinkPeriod’) and if that period is surpassed, it ‘toggles’ the LED. Toggling meaning that it just checks what th elast state of the LED was (On or OFF) and then it switches it to the other state.
Interestingly this method is more efficient -using 846 bytes- than the classic blinksketch, eventhough it has improved functionality

Ofcourse we can combine this non blocking code with some of the techniques we learned earlier, like using direct portmanipulation.

Such a sketch would look like this:

#define BlinkPeriod 500  // this is in milliseconds

boolean ledState = LOW;

// here we store the last time the LED blinked
unsigned long lastTime = 0;

void setup() {
  DDRB = DDRB | 0B00100000;
}

void loop() {
  unsigned long now = millis();
  if (now - lastTime >= BlinkPeriod) // this will be true every 500 milliseconds
  {
    lastTime = now;
    if (ledState == LOW)
    {
      ledState = HIGH;
      PORTB = PORTB | B00100000;
    }
    else
    {
      ledState = LOW;
      PORTB=PORTB& ~(1<<PORTB5);
    }
    //digitalWrite(LED_BUILTIN, ledState);
    
  }

  //can do other stuff here
}

Then we get a sketch using only 550 bytes

or we could just toggle D13 again:

#define BlinkPeriod 500  // this is in milliseconds

// here we store the last time the LED blinked
unsigned long lastTime = 0;

void setup() {
  DDRB = DDRB | 0B00100000;
}

void loop() {
  unsigned long now = millis();
  if (now - lastTime >= BlinkPeriod) // this will be true every 500 milliseconds
  {
    lastTime = now;
   PORTB=PORTB^0B00100000;
    
  }

  //can do other stuff here
}

and get another 16 bytes off til 534 bytes:

Classes

What else can we do if we want an LED to blink. Suppose we have a code in which we want to blink one or more LED’s from time to time. Do we have to put in that piece of blink code every time we want to blink the LED? Sure we could define a procedure that takes various parameters such as blinkperiod and LED number, that works, but actually this is a good time to use a ‘class’. A class basically is a collection of commands and variables that belong to eachother. Below I present a class called ‘Blinker’. It allows you to use several different LEDs, all with different on and off periods.

class  Blinker
{
    byte ledNr;
    long OnTime;
    long OffTime;
    boolean lampState;
    unsigned long previousMillis;
  public:
    Blinker(char nr, long on, long off)
    {
      ledNr = nr;
      OnTime = on;
      OffTime = off;
      lampState = true;
      previousMillis = 0;

    }
    void Update()
    {
      unsigned long currentMillis = millis();
      if ((lampState == true) && (currentMillis - previousMillis >= OffTime))
      {
        lampState = false;
        previousMillis = currentMillis;
        digitalWrite(ledNr,HIGH);
      }
      else if ((lampState == false) && (currentMillis - previousMillis >= OnTime))
      {
        lampState = true;
        previousMillis = currentMillis;
        digitalWrite(ledNr,LOW);
      }
    }
    void Stop()
    {
      lampState = false;
    }
};
Blinker led1(13, 1000, 400);
Blinker led2(12, 500,300);

void setup() {
  // put your setup code here, to run once:
}

void loop() {
  // put your main code here, to run repeatedly:
  
  led1.Update();
  led2.Update();
 //here your other code
}



This sketch takes 922 bytes.

How about putting your Arduino to sleep between blinks? the easiest way is perhaps by using the ‘Sleepy‘ command in the Jeelabs library:

#include <JeeLib.h>
int led = 13;
ISR(WDT_vect) { Sleepy::watchdogEvent(); } 

void setup() {
  pinMode(led, OUTPUT);
}

void loop() {
  digitalWrite(led, HIGH);
  Sleepy::loseSomeTime(1000);  
  digitalWrite(led, LOW);
  Sleepy::loseSomeTime(1000); 
}

This Sketch uses 2118 bytes (6%) of program storage space, which is quite a lot.

And ofcourse this program can be combined with the port manipulation as described above.

#include <JeeLib.h>

ISR(WDT_vect) { Sleepy::watchdogEvent(); } 

void setup() {
DDRB = DDRB | 0B00100000; //set pinMode High for D13
}

void loop() {
PORTB=PORTB^0B00100000;//toggle the value of D13
Sleepy::loseSomeTime(1000); 
}

That still is a staggering 1806 bytes though.

Let’s forget that bulky Library and just do the necessary coding by ourselves. I put the Arduino into sleep and use the Watchdogtimer (set at 1 sec) to wake up:

void setup() {
  DDRB = DDRB | 0B00100000; //set pinMode OUTPUT for D13
  
  //SETUP WATCHDOG TIMER
  WDTCSR = (24);
  WDTCSR = (6);//= 0b00000110-> 1 sec (table 10-3 datasheet)
  WDTCSR |= (1<<6);//enable interrupt mode

  //ENABLE SLEEP - this enables the sleep mode
  SMCR |= (1 << 2); //power down mode
  SMCR |= 1;//enable sleep
}

void loop() {
   PORTB=PORTB^0B00100000;//toggle the value of D13 

  //BOD DISABLE (the BOD disable can be taken out, which will save another 10 bytes)
  MCUCR |= (3 << 5); 
  MCUCR = (MCUCR & ~(1 << 5)) | (1 << 6); 
  __asm__  __volatile__("sleep");//in line assembler to go to sleep
}


ISR(WDT_vect){
 //This interrupt routine is essential. Do not leave it out.
//One could put the toggling of the LED in here...but that takes 8 extra bytes
}

Goes to show that using a library is easy, but it can be very power hungry. Admitted, the program looks a bit complicated, but as this post is about various ways of blinking an LED and not about deepsleep, i will just leave it as is and possibly explain it in an article about putting the arduino to sleep.

Timers

void setup(){
cli();
TCCR1A = 0b01<<COM1B0 | 0b00<<WGM10;   // Toggle OC1B on match, CTC mode
TCCR1B = 0b01<<WGM12 | 0b101<<CS10; 
OCR1A=15624;// count 15625 cycles for 1 second
TIMSK1 |= 1<<OCIE1A;//Enable timer compare interrupt
sei();// Enable Global interrupt
//-----------------------
DDRB |= (1<<5); //Set PortB Pin5 as an output

}
void loop(){
//other code
}

ISR(TIMER1_COMPA_vect) //Interrupt Service Routine
{
PORTB ^= (1<<5); //Use xor to toggle the LED
}

So, as one can see, there are various ways to skin a cat when it comes to blinking LEDs. Keep in mind tough that if one uses any ‘delays’ in the rest of the code, that can influence the blink. (using delay generally is a bad habit anyway);

ESP8266 WiFiConnections

This article is probably cut and dry for those who have been working with the ESP8266 for more than a week, but every now and then I do get questions on how to connect an ESP8266 to the internet.

The code below shows how to do that in a combined STA and AP mode. It shows various options, including a static IP address. It includes a simple webserver, just to show the AP and STA connections are made. You can also download the code here.

#include <ESP8266WiFi.h> //for the WiFi connection
#include "ESP8266WebServer.h" //for the Server
ESP8266WebServer server(80);

// Set WiFi credentials
#define WIFI_SSID "YOURSSID"
#define WIFI_PASS "YOURPASSWORD"

// Set AP credentials
#define AP_SSID "YOUR_DESIRED_AP_NAME"
#define AP_PASS "YOUR_DESIRED_AP_PW"

//Extra's. these are optional
#define AP_CHANNEL 1   //choose the channel
#define AP_HIDDEN false //SSID broadcasted or not
#define AP_MAX_CON 4 // max nr of connections to the AP

uint8_t macAddr[6];

//for static IP (all not necessary if you choose DHCP)
// Set IP addresses
IPAddress local_IP(192, 168, 1, 70);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0);
//change the default IP address for the AP (192.168.4.1)
IPAddress AP_IP(192,168,4,5);

void setup() {
  // Setup serial port
  Serial.begin(115200);

  //begin Acces Point
  WiFi.mode(WIFI_AP_STA);
  //WiFi.mode(WIFI_STA);// for just Station connection
  //WiFi.mode(WIFI_AP);//for just AP connection

  //WiFi.softAP(AP_SSID, AP_PASS); //this makes your Acces point connection
  WiFi.softAPConfig(AP_IP, AP_IP, subnet);// this is optional
  WiFi.softAP(AP_SSID, AP_PASS, AP_CHANNEL, AP_HIDDEN, AP_MAX_CON);

  // Begin WiFi
  WiFi.config(local_IP, gateway, subnet);// optional, for  static connection
  WiFi.begin(WIFI_SSID, WIFI_PASS);// this makes your STA connection

  // Loop continuously while WiFi is not connected
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(100);
    Serial.print(".");
  }

  // Connected to WiFi
  WiFi.setAutoReconnect(true); //For automatic reconnect
  WiFi.persistent(true); // ditto
  Serial.println("Connected");
  Serial.print("Network IP address: ");
  Serial.println(WiFi.localIP());
  Serial.print("IP address for network ");
  Serial.print(WIFI_SSID);
  Serial.print(" : ");
  Serial.println(WiFi.localIP());
  Serial.print("IP address for network ");
  Serial.print(AP_SSID);
  Serial.print(" : ");
  Serial.println(WiFi.softAPIP());
  WiFi.softAPmacAddress(macAddr);
  Serial.printf("MAC address = %02x:%02x:%02x:%02x:%02x:%02x\n", macAddr[0], macAddr[1], macAddr[2], macAddr[3], macAddr[4], macAddr[5]);
  Serial.println("");
  //----------------
  server.on("/other", []() {   //Define the handling function for the path

    server.send(200, "text / plain", "Other URL");

  });

  server.on("/", handleRootPath);    //Associate the handler function to the path
  server.begin();                    //Start the server
  Serial.println("Server listening");
}

void loop() {
  server.handleClient();         //Handling of incoming requests
  // put your main code here, to run repeatedly:
  Serial.print("number of clients connected:");
  Serial.println(WiFi.softAPgetStationNum());
delay(3000);
  
}

// for a simple proof of context it would have been enough to just do server.send(200,"text/plain", "Hello World");
// but I added some html to make the content adapt to the size of the viewing device, so it would be properly visible on a phone as well
void handleRootPath() {            //Handler for the rooth path
  server.send(200, "text/html", "<!DOCTYPE HTML><html><head><meta name='viewport' content='width=device-width,initial-scale=1'></head><body>Hello world</body></html>");
}

Also check this post for some strange behaviour of the AccessPointI came across. You may also find this post interesting.

Graphical presentation of Sensordata with ESP8266

In an earlier post I described the use of a graph, showing results of a BME280 and DS18B20 sensor. This was based on an article from siblings Rui anď Sara Santos from randomnerdtutorials, but tailored to my needs. The biggest difference with the original article is that i used a JSON for data transfer in order to display more than one line per graph. The original code as well as my adaptation had some drawbacks, one of them being that after opening the chart, one had to wait the full interval (set to 30 secs) before the first value appeared. That is now fixed (thanks JB): the first value will appear immediately on opening the chart.

As an example I am using an ESP8266-01 that addresses a PCF8591 ADC/DAC. The 4 ADC’s read 4 soil humidity sensors and plot those in a graph. The battery voltage is also plotted. As i use a LiFePO4 battery, there is no need for a regulator and i can simply read the Vcc in order to know the battery voltage. In order to save the soilsensors from electrolysis, i switch them on when i take a measurement and switch them off in between. Rather than using the free gpio1 and gpio3 (Tx and Rx) for that I use the DAC output.

One could argue (and i would agree) that it is quite useless to check soilmoisture every 30 secs where maybe once every 4-6 hrs suffices, but just see it as example of how you could use it say for a slow cooker or your oven or CV.

*These pins hve internal pullup on the ESP01S, but not on the ESP01

You will find the code here.

Another ‘issue’ was that since an external library (highcharts) is called, one needs active connection with internet. This is not always desirable or possible. There are 2 ways around this: download the highcharts library and put it on your own server, or download it and put it in the esp8266 FS and make some small adaptations to your code (solution provided by J. Bredahl). You will find that code here.

Graphical and Table presentation of database info using Googlecharts and PHP

In an earlier post I discussed storing sensor data in a MySQL or MariaDB database (in combo with DeepSleep, which was the focus of the article).
The database part was based on an article by randomnerdtutorials.
Output of the gathered MySQL data was in shape of a table*).

Sometimes though a graph is a more practical way to look at data over a period of time. There are 3 values in the table: temperature, Humidity and Airpressure.
As I wanted one graph, I decided to leave out the Airpressure, as it’s high values  would skew the  Y-axis a bit, so i will be looking at  2 values only: temperature and humidity. The Airpressure is easy to add though.

There are various possibilities of  presenting data in a graph. ‘Highcharts ‘ for example, or jpGraph. This time though I decided for Googlegraphs. Fortunately there is a lot of info available and i found some frameworks at Techjunkgigs that I could rework.

That turned out something like this:

or, with more data:
You will find the program here. There are a few things you need to check. The program expects a database file that is called ‘esp-data’. It checks a table called ‘SensorData’. It looks for fields called ‘value1’, ‘value2’ and ‘reading_time ‘. If you are using different names, you need to adpt tghe program. You also need to insert your database username and password.

You will notice that when time passes by the graph will be condensed more and more.
That is why I added a ‘WHERE’ statement in the query:
$query = “SELECT * from SensorData WHERE id >=1”;

where you can enter a higher ID number (or insert other criteria such as date, or temperatures above a certain value).
However, if you would only be interested in say only the last 100 records, it is better to replace the query by:

$query = "SELECT * FROM(SELECT * from SensorData WHERE id >=1 ORDER BY id DESC LIMIT 100) AS `table` ORDER by id ASC";
_______________

*) The presented table is a bit basic. There are more interactive ways to present data in a table.
espdata-search

You will find the code here.