Exporting your (Firebase) WebApp HTML table to CSV

Exporting the Firebase readings
Table data ready for import

The WebApp showing the measuring points in the Firebase database database that I presented in a previous article and which was in fact built on a project by randomnerdtutorials, includes a table of the collected datapoints that can be shown, hidden and also deleted.

Printing (just roughly)
In case you want to keep this data, there are several ways to print it (and keep as PDF)

You could for instance add a javascript routine like this:

 function downloadPDFWithBrowserPrint() {
        window.print();}
      
      let btp=document.getElementById("btp");
      btp.addEventListener('click',event=>{downloadPDFWithBrowserPrint();
      });

and add a button in your HTML like this:

<button id="btp">Print</button>

That will let you print everything that is on your screen or send it to a PDF. But as that includes the buttons an gauges etc) that is not very elegant. There is a better option:

Printing better

Go to your ‘button section’ in the index .html file and add the following button:

<button onclick="PrintTable();">Pretty print</button>

Then we need some javascript. We can add that in the index.js file, or in a separate js file, but for ease of use I will just add it as a script towards the end of the index.html file.

<script type="text/javascript">
  function PrintTable() {
      var printWindow = window.open('', '', 'height=200,width=400');
      printWindow.document.write('<html><head><title>Table Contents</title></head>');
      //Print the DIV contents i.e. the HTML Table.
      printWindow.document.write('<body>');
        printWindow.document.write('<table style="width:100%">')
      var divContents = document.getElementById("readings-table").innerHTML;
      printWindow.document.write(divContents);
        printWindow.document.write('</table>')
      printWindow.document.write('</body>');

      printWindow.document.write('</html>');
      printWindow.document.close();
      printWindow.print();
  }
</script>

The way this code works is as follows: First a new browser window is opened. The code then writes the necessary html tags, up til and including <table> It then usessetElementsById to read the contents of the html element that is identified with “readings-table”. That happens to be the full content of the table that is shown with the ‘View data’ and ‘More results’ buttons. It then finishes the required html and calls for the print function.
There is one drawback in this method. Eventhough the headers are defined with a <th> tag that should print the heading on every page, that does not happen here because the onscreen table is continuous and the to be printed table follows that format.

Exporting to CSV
It is perhaps wiser to export the data to say a CSV that can be read by Excel or LibreOffice.

To keep things tidy, I created a new js file in the scripts folder and called it ‘print.js’

To have acces to this file, add http://scripts/print.js to the index.html file. Do this in the section “INCLUDE JS FILES”.

While you are in the index.html file, add the following button:

<button onclick="exportData()"> 
    Export list
</button>

This creates a button that calls a procedure ‘exportData()’. That procedure is put in the ‘print.js’ file created earlier. It looks like this:

function exportData(){
    // Get the HTML table using that id in the <table> tag
    var table = document.getElementById("readings-table");
 
    // Declare array variable 'rows'
    var rows =[];
 
      //loop through the rows
    for(var i=0,row; row = table.rows[i];i++){
        column1 = row.cells[0].innerText;
        column2 = row.cells[1].innerText;
        column3 = row.cells[2].innerText;
        column4 = row.cells[3].innerText;
  
 
    // add records to the array
        rows.push(
            [
                column1,
                column2,
                column3,
                column4,               
            ]
        );
 
        }
        csvContent = "data:text/csv;charset=utf-8,";
         // set the field and record delimiters
        rows.forEach(function(rowArray){
            row = rowArray.join(",");
            csvContent += row + "\r\n";
        });
 
        /* create a DOM node (<a>) and set the needed attributes
        var encodedUri = encodeURI(csvContent);
        var link = document.createElement("a");
        link.setAttribute("href", encodedUri);
        link.setAttribute("download", "FirebaseData.csv");
        document.body.appendChild(link);
        link.click();
}

This function will only download the parts of the table that are made visible through the ‘View all data’ button. It is best to place the ‘Export button’ within the id=”table-container” tag section (meaning in between the tag and the endtag), so it only will be visible when you make the table visible.

Firebase datalogging from Anywhere

(following a randomnerdtutorials article)

Recently, Randomnerdtutorials published a string of articles on using Firebase to control or monitor your ESP8266 or ESP32 from basically anywhere in the world. These articles are very very worthwhile reading as they describe in detail making a Firebase hosted WebApp that allows you to do the above mentioned control. (another good resource is on arduino.cc).

Their  gauge tutorial shows a pretty nice graphic front end, that is a good basis to add and expand on, as in fact they recommend in their tutorial, be it as an exercise or for a practical project.

Now I presume all die hard programmers already made their own projects, so for the not so die hard programmers, I will just explain the content of the various files a bit

So let’s have a look at the files: The most important files for the application are the index.html and index.js file.
If you want anything to appear on your screen, it has to go into your html file. What appears there depends on your index.js file (and how it appears depends on your css file)

The html and js files are pretty well structured (as is always the case with randomnerdtutorials). The index.js file for example, starts with some general functions to convert epoch time and plot values on a chart, defines some DOM (Document Object Module) elements, checks if the user is logged in and if yes, has a charts section (wanting to know how many points you want), a checkbox section, a cards section, a gauges section, a delete data section. a set up table section and if you are not logged in, it goes to login. All those sections are pretty well defined and commented, so it should not be too hard to put your own code in there as well.

So in fact the index.js file is a big If…then statement:
if logged in…then do a bunch of stuff, if not logged in, go to login.

The index.html file is equally structured, starts with the normal html stuff, then the various database things (domain/url/API/UID etc.).
It includes some libraries (highcharts for the graphs, fontawesome for  some nifty icons and the Mikhus Gauges library). For the very purist developers….you could do without the gauges library, as highcharts can do gauges as well.

After that it is pretty well ordered again with clearly marked sections for graphs, gauges, cards etc. The gauges and charts themselves are handled in seperate js files. I should say though that it IS possible to download the highcharts library and use it locally. That may make sense for applications that do not use internet, but in this case it does not add much advantage.

I will not go in detail in the code of every change made, that would be too tedious, but the first one was easy and was just to ‘get into it’

Log in screen

Adding code for a tickbox to show the password was probably the simplest of all

The original code in the index.html file looks like this looks like this:
and we alter it like so:

Now we could have put that function ‘myFunction’  in the index.js file, but it is just as easy to put it right where it is used.

Text cards

Then it is time to handle the ‘cards’  (where the sensor readings are shown in text). I wanted to add  a reading in Fahrenheit, a pressure reading in mmHg and while I was at it add the dewpoint and  a soil moisture reading
firebase_display_cards

That was all pretty straightforward by adding text to the cards and adding proper javascript to the js file. For those with little experience in html and JavaScript: throughout the html file you will see sections of code that have an ‘id’  like: <canvas id="gauge-humidity">. The javascript code  uses those identifiers to inject text in the html file, so it appears on your screen (yes, I know, very basic stuff, but not every beginner knows that), so in my code you will later see that I for instance have javascript calculate the fahrenheit temperature and then inject the result in a section of html that is identified by '<span id="fahrenheit">'

Gauges

The original WebApp did not have a gauge for pressure, so I added one and while I was at it, added some extra gauges as well.
firebase-display_charts

For that I had to add the gauge definitions to the ‘gauge-definitions.js file. Not so hard, basically copy paste from what is already there and change the parameters. and then ofcourse the regular  additions in the index.html file and index.js file. In future I might change the thermometer gauge coz I find it a bit too rough and bulky.

Switches

I also wanted to add some switches. The original gauges app would only read data, but I wanted it to be able to switch some stuff on or off and I wanted some icons to change as well depending on the the switch being off or on like so:
firebase_buttons_on

and so:

Firebase_buttons_off

This was the biggest addition as it required a lot of new structure to be added to the files. What it does is that the switches set a 1 or 0 in the database (that is to be  handled by an ESP ofcourse to actually switch a light or device on or off) and update their status (=the ON/OFF text in their card), depending on that status they define a path to 1 of two images, that is then ‘injected’ into the html file.

My html file for  a specific button looks like this:

<!--CARD FOR GPIO 12-->
<div class="card">
<p class="card-title"><img id='img-w' width="25"> WaterPump</p>
<p>
<button class="button-on" id="btn1On">ON</button>
<button class="button-off" id="btn1Off">OFF</button>
</p>
<p class="state">State:<span id="state1"></span></p>
</div>

The various ‘id’-s  will all be updated by JS code.

firebase_switchcode

In that code the state is pulled from the database: if it is ‘1’ the text “ON” is injected into the HTML file (the ‘id’  for that was defined higher up in the js file as ‘state1’). It then defines a path to the ‘pump-on’ picture and injects that into the ‘img-w’  section in the html file.
Initially I wanted to use fantawesome.com icons, but their selection of free icons is limited and for some reason some of the free icons just did not show up, so I decided it was best to just make/modify some icons myself and store those in the WebApp

Level Switches

The next thing I wanted to add was the state of some level switches, including some icons that change with the state, so it would like this:

firebase_level_switches1

and this:

firebase_levelswitches2

That may seem hard, but in fact it was not so bad as basically they are the same as the switches before with regard to reading a specific state from the database.

A word on the database

The database looks like this:

FirebaseRTDB

It is akin to the database from the original article with the difference that I added the ‘moisture’ field to the readings and added  ‘inputs’ (for my level switches) and ‘outputs’ (for my switches). Ofcourse I could have added my level switches to the ‘readings’ records, but as i am not really interested whether my  pond was full 2 weeks ago, I store the switch values in records that are to be overwritten by the most up to date value.

I suggest you read part 1 of the gauge tutorial (Check here for ESP32) at Randomnerdtutorials if you have not done so already. Set up firebase according to their description and run the ESP8266/ESP32 code in order to create the necessary database.

To be clear, in this article I focused solely on the interaction between the WebApp and the Firebase RTDB. How you get the wanted data (sensors and buttons) in the database is a different matter and greatly depends on what project you are building. It could come from one ESP8266/32 or from various ESP’s. The randomnerdtutorials website has a number of firebase projects with ESP’s writing data to and receiving data from the firebase RTDB. Start with the ESP8266/ESP32 codes as mentioned in the previous paragraph and you already covered most of it.

You will find the files here.
I will not include the pictures, as your project may have no need for those specific pictures and i am also not entirely sure if I can distribute them.

Update: if you want to store or print your table data: look here on how to do that.

 

Conditional images in a webpage

full-emptySometimes in a web application you want different pictures to appear, depending on a condition. For many die hard programmers that will be a piece of cake, but for may others it isn’t.
Suppose you have an application that monitors if the level in your water tank is full or not completely full yet (say with a float switch) and you want a picture to appear that either shows a full tank, or a not so full tank. Your sensor sends either a ‘0’ or a ‘1’, depending on whether the switch is closed or open.
Say you get that state either via a json or a GET or a PUT. Just as example I presume it comes in via a json and it’s name is ‘level’

You start with some HTML that looks like this:

HTML

<!--Container for picture -->
<div>
Tank is <span id="tank"></span>
<p><img id='img' width="50"></p>
</div>

You then add some Javascript that looks like this:
JavaScript

var l=jsonData.level
if (l==1){
document.getElementById("tank").innerHTML="full";
var imageshown="/images/full.png"
}else{
document.getElementById("tank").innerHTML="not full";
var imageshown="/images/empty.png"
}
document.getElementById('img').src=imageshown

This is how it works:
The HTML  basically says “Thank is …..” and then goes to show  a picture with the ‘img’ tag. It is just that  some parts still need to be filled in . The parts that need to fill in we have given an ‘id’  by which they can be recognized. Those id’s we have called ‘tank’ and ‘img’, but they could have had any other name.

Then  in the Javascript we make use of the DOM programming interface. DOM stands for Document Object Model, it is a programming API for HTML and XML documents. It defines the logical structure of documents and the way a document is accessed and manipulated.

We make use of ‘document.getElementById’ that is a method of accessing element in HTML. In this case, we use the id ‘tank’, that we have defined before in the HTML.
We then get or control the content of that object we just have identified, with ‘innerHTML’. In our case it means that we  replace the identified element with either the text ‘full’  or ‘not full’.
Also, depending on the state of the ‘l’ variable, we define the ‘imageshown’ variable with the path of either the ‘full’  image, or the ’empty’  image. We then use the ‘document.getElementById’ method again to identify  the ‘img’ object in the HTML and use innerHTML to replace it with the path to the  appropriate image.

Obviously, one can add  html tags to the replacement content: for instance:

document.getElementById('vijver').innerHTML="<span style='color:red'><i>not full</i></span>";

will  make the text appear in red italic.

Mind you that depending on where you put the javascript, you may need to put <script> </script> tags around it (e.g. when it is directly in your HTML).

ESP8266 and MySQL with Grafana

MySQL data in Grafana

In a previous article I described how to use InfluxDB to collect data from an ESP8266 and present that in Grafana or Chronograf. Grafana can also be used to present data that has been collected in a MySQL or MariaDB database. If you want your data as a graph, make sure that the data is stored as a number (albeit an int or a double or a float). If it is stored as a string, Grafana can only put it in a Table.

Suppose one has an SQL database called ‘esp-data’ that has a Table called ‘Sensor’ with the following structure:

MySQL datastructure

That can be presented in Grafana as a graph (or a table), but we need to prepare a few things: It is best to make a user that has only ‘Select’ priviliges. That is because the SQL statements we use later in Grafana could influence your data when a mistake is made. In order to create a new user go into MySQL, type
mysql -u yourusername -p

then: CREATE USER 'grafana' identified BY 'GRAFANA'
followed by: GRANT SELECT ON * TO 'grafana'
This created a usr called ‘grafana’ with password ‘GRAFANA’.

You may also need to configure your MySQL for remote access, but for now we leave it as is. I will come back to that later.

Next thing to do is to go to your grafana interface. You will find that at the computer it is installed on, at port 3000. So type ‘localhost:3000‘ in your browser. When it opens up, go to ‘datasources’. after logging in, go to the cogwheel in the left column and choose ‘Data sources’. Then pick ‘MySQL’ and fill in the details below. Ofcourse you can give the datasource another name than I did and/or choose another timezone. Then click “test and Save” and if everything goes well, you should have the connection made. (note: the image shows ‘password configured’ as i already entered the password. You will still need to enter the ‘GRAFANA’ password)

Grafana datasource

If however you get an error, there can be a number of possibilities. You will have to check the logfile at /var/log/grafana/grafana.log to see what exactly went wrong. What usually goes wrong is that you didn’t create the right user, that your timezone was an unknown format (remove it to make sure), or that MySQL was not started.
What can also cause a problem is when your MySQL doesn’t accept remote connections. That can be fixed:
In your console type:

sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf

In that file, look for this line:

bind-address = 127.0.0.1

Change it to:

bind-address = 0.0.0.0

Save and close the file. Restart the MySQL service with:

sudo systemctl restart mysql


When you have the connection established goto the ‘+‘ sign in the left colomn, choose: ‘Create’ and ‘ Dashboard’ and then ‘add new panel’.
Fill that in like so:

That will give you you a graph akin to this one, provided there is data in the time frame you have selected

The time frame to look at can be selected at the top of the screen:

Do not forget to save your dashboard!!

In this article I presumed you already had a MySQL database that was being filled by an ESP8266. If you do not, my advice is to not use MySQL/MariaDB but to pick InfluxDB, but if you insist on using MySQL, randomnerdtutorials has an excellent article on how to set up a MySQL database and fill it with an ESP8266. I also presumed you already had grafana running, if not, refer to the article linked at the top of this post

ESP8266 with influxDB and Grafana or Chronograf

Grafana
Chronograf

In an earlier post I referred to the use of InfluxDB and Grafana to store and present data. In this post I will explain how to do this and i will also add Chronograf, another graphics package.
There is a recent post of randomnerdtutorials on Influxdb as well, but where Rudy and Sarah discuss the use of a Cloud server (with only 30 days data retention in their free plan). For those interested, it might be good to know that recently InfluxDB allows filling a database through MQTT messages. I though will be using a local install of InfluxdB and will connect it to Grafana. As i am using Linux, that will be the install I will be discussing as that is the one I can test.

Install InfluxDB (Ubuntu)
One can use the Software manager for that, but I am not sure which version that will install (could be 1.6). I will do a manual install of version 1.8 (there is version 2, but I still prefer 1.8). (for other OS’s check here)

Type the following commando’s in your Command line

sudo curl -sL https://repos.influxdata.com/influxdb.key | sudo apt-key add -
sudo echo "deb https://repos.influxdata.com/ubuntu bionic stable" | sudo tee /etc/apt/sources.list.d/influxdb.list
sudo apt update
sudo apt install influxdb

Once influxdb is done installing, check the status

sudo systemctl status influxdb

In your case it will probably not say ‘active’ but more likely ‘dead’, as it is not running yet.
The next command will start it and make sure it is reloaded every time you start your system

sudo systemctl enable --now influxdb

Should you prefer a manual start, one can do that with

sudo systemctl start influxdb

Configure InfluxDB

type:

sudo nano /etc/influxdb/influxdb.conf

That will open the config file. Go to the [http] section and uncomment ‘enable = true’ and uncomment the bind-address

Save a the file and leave the editor with Ctrl-X and type ‘Y’ on the question ‘save file?’

Now restart influxdb with

sudo systemctl stop influxdb && sudo systemctl start influxdb

or with:

sudo systemctl restart influxdb

Now we need to create an admin account. as example I will call that account ‘john’ and as password I will use “JOHN”

curl -XPOST "http://localhost:8086/query" --data-urlencode "q=CREATE USER john WITH PASSWORD 'JOHN' WITH ALL PRIVILEGES"

once this is done, you can access influxDB with

influx -username 'john' -password 'JOHN'

Now create a database called ‘garden’ and check its creation with ‘show databases, as shown below

Now it is time to add some data. Make sure to use the right database with the command use garden

InfluxDB creates its fields ‘on the fly’, but the database needs a specific structure, like this:

<measurement>[,<tag-key>=<tag-value>...] <field-key>=<field-value>[,<field2-key>=<field2-value>...] [unix-nano-timestamp]

As the influxDB records are all time based, a timestamp is automatically added to the records. The tag keys are optional, the ‘measurement’ is not
Anyway we will call the measurement ‘beds’ and add a tag-key ‘bedname’. Then we add 4 fields called ‘moisture’,’temperature’,’licht’ and ‘druk’ (the latter two being dutch words for ‘light’ and ‘pressure’, you may rename them).
INSERT beds,bedname=bed1 moisture=590,temperature=24.5,licht=1400,druk=1013 and then show the record with the ‘select’ command

Add a couple of more records this way

Mind you that InfluxDB stores numbers as floats. If you specifically want to store them as integers, you have to add the suffix ‘i’, your command line should look like this:

INSERT beds,bedname=bed1 moisture=590i,temperature=24.5,licht=1400i,druk=1013i

Once you have sent a value that created a float, you cannot send integers to that field anymore.

As filling a database with manual commando’s is no fun, we will let the ESP8266 do that. For simplicity i will not read out any real sensors, but use random values, but you can easily replace those by a sensor of your choice:

#include <esp8266wifi.h>
#include <esp8266wifimulti.h>
#include <influxdb.h>
#define INFLUXDB_HOST "192.168.2.9"   //Enter IP of your device running Influx Database
#define WIFI_SSID "yourssid"     //Enter SSID of your WIFI Access Point
#define WIFI_PASS "yourPW"      //Enter Password of your WIFI Access Point
ESP8266WiFiMulti WiFiMulti;
Influxdb influx(INFLUXDB_HOST);
void setup() {
  Serial.begin(115200);
  WiFiMulti.addAP(WIFI_SSID, WIFI_PASS);
  Serial.print("Connecting to WIFI");
  while (WiFiMulti.run() != WL_CONNECTED) {
    Serial.print(".");
    delay(100);
  }
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
  influx.setDb("garden");
  Serial.println("Setup Complete.");
}
void loop() {
  InfluxData row("beds");
  row.addTag("bedname", "bed1");
  row.addValue("moisture", random(500,800));
  row.addValue("temperature", random(10, 40));
  row.addValue("licht", random(1100,1400));
  row.addValue("druk", random(980, 1023));
  influx.write(row);
  delay(20000);
}

(download here). After a while that will give:

If you wish you could add a line likerow.addValue("rssi", WiFi.RSSI()); which will add the WiFi signal strength.


Should you want to delete 1 or more records, do that with the ‘DROP SERIES command, like so:
DROP SERIES FROM beds WHERE bedname='bed1'
In InfluxDB only tags or measurements can be part of this WHERE clause, fieldnames cannot. You can do all sorts of selections on the dataset, try for instance: select temperature from beds where time > now() - 5m.

That concludes the InfluxDB section, but let me just add a few words: This is version 1.8. Should you point your browser to localhost:8086, you will find a 404 error. In version 2 you supposedly will find a user interface.

Grafana (on Ubuntu)

Grafana can be installed via the software manager in Ubuntu, but if you want more flexibility, check this link. or this one (for various operating systems).
(Update: per June 7,2022 the newest version is 8.5.5. )
Install like this:

sudo apt-get install -y adduser libfontconfig1
wget https://dl.grafana.com/enterprise/release/grafana-enterprise_8.5.5_amd64.deb
sudo dpkg -i grafana-enterprise_8.5.5_amd64.deb

You can check your current version by going to any dashboard and go to settings:

Grafana version

After installation of Grafana, start it and go to http://localhost:3000.
The grafana user interface opens

In the left navigation of the Grafana UI. At the left hand side is a column with icons: hover over the gear icon to expand the Configuration section.
Click Data Sources.
Click Add data source.
Select InfluxDB from the list of available data sources.
On the Data Source configuration page, enter a Name for your InfluxDB data source.
Under Query Language, select either “InfluxQL” or “Flux”. I picked InfluxQL. I understand (but am not sure, that with InfluxDB version 2, picking Flux is the better option.

Under HTTP, you most likely will find ‘URL http://localhost:8086&#8217;.
Leave that for now, but we may have to revisit it.

Under Auth, select Basic auth

Under Basic Auth Details, enter:

Under InfluxDB Details, enter the following:

  • User:-> john
  • Password:->JOHN

Under InfluxDB Details

  • Database: your database name -> garden
  • User: your InfluxDB username: ->john
  • Password: your InfluxDB password:-> JOHN
  • HTTP Method: Select GET or POST:->POST

Once you are sure you filled out everything correct click “Save &Test

Should you get an error saying it could not connect and you are sure you filled out everything correctly, go back to the URL field and change ‘http://localhost:8086&#8217; into ‘http://ipnr-where-you-installed-InfluxDB:8086&#8217; and try again. Pretty good chance your connection now will work.

Now go to the ‘+’ sign in the left hand column and Create- Dashboard and then ‘Pick an Empty Panel’

As we have 4 fields that we are interested in (temperature, moisture, light and pressure, we will be adding 4 fields. Under ‘Data source‘ pick the connection we created earlier (InfluxDB). Under ‘FROMSelect measurment‘ you should get the option ‘beds’ (which is the dataset we have been creating. Pick that one.

Under SELECT field, pick the field you want. I picked ‘Druk’ (=pressure). Under ALIAS type ‘Airpressure’ You should end up with this

Now add 3 more series in which you choose the other 3 channels and give them an appropriate ‘ALIAS’. Once your program has collected a number of data points, it should look something like this:

That is the basic setup of InfluxDB with grafana with an ESP8266.

Adding a BME280

So let’s now add a real sensor, like the BME280. That only requires a slight alteration of the program:

#include <ESP8266WiFi.h>
#include <ESP8266WiFiMulti.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#include <InfluxDb.h>

#define INFLUXDB_HOST "host_ip_nr"   //Enter IP of device running Influx Database
#define WIFI_SSID "Your SSID"              //Enter SSID of your WIFI Access Point
#define WIFI_PASS "Your Password"          //Enter Password of your WIFI Access Point

ESP8266WiFiMulti WiFiMulti;
Influxdb influx(INFLUXDB_HOST);

//BME280
Adafruit_BME280 bme; // I2C
float temperature;
float humidity;
float pressure;

// Initialize BME280
void initBME(){
  if (!bme.begin(0x76)) {
    Serial.println("No valid BME280 sensor found");
    while (1);
  }
}

void setup() {
  Serial.begin(115200);
  WiFiMulti.addAP(WIFI_SSID, WIFI_PASS);
  Serial.print("Connecting to WIFI");
  while (WiFiMulti.run() != WL_CONNECTED) {
    Serial.print(".");
    delay(100);
  }
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());

  influx.setDb("garden");
  initBME();//call BME280 initiation routine
  Serial.println("Setup Complete.");
}

int recordnr = 0;

void loop() {
  temperature = bme.readTemperature();
  humidity = bme.readHumidity();
  pressure = bme.readPressure()/100.0F;

  loopCount++;
  InfluxData row("beds");
  
  row.addTag("Sensor", "BME280");
  row.addTag("RecordNr", String(recordno));
  row.addValue("temperature", temperature);
  row.addValue("humidity", humidity);
  row.addValue("pressure",pressure);
  row.addValue("rssi", WiFi.RSSI());
  influx.write(row);
  
  Serial.print(String(recordno));
  Serial.println(" written to local InfluxDB instance");
  
  Serial.println();
  delay(60000);
}

A quick check of the database shows it is filling up:

Then we add 3 queries to our Grafana dashboard, like so:

repeat this for the pressure and temperature. Don’t forget to set proper names for the sensor readings under ‘ALIAS’ in the query.

And now the dashboard will look like this:

If you only see dots instead of lines, go to the properties at the right side of your screen and set the following parameters

As you can see, the temperature and humidity are a bit pushed together because of the high value of the pressure readings. It is possible to only view a subset of the graph by clicking on the legends you want to see. Use shift-click for more than one line.

It is also possible to define mare than 1 and even more than 2 Y axis, each with their own scale:

Grafana offers a lot more possibilities and once you reached this point, it is best to just play around with the various options presented.
Don’t forget to save your dashboard.

Chronograf (on Ubuntu)

Chronograf is another package that can be used to present InfluxDB data, it is in fact from the same manufacturer. Installation is as follows:

wget https://dl.influxdata.com/chronograf/releases/chronograf_1.9.4_amd64.deb
sudo dpkg -i chronograf_1.9.4_amd64.deb

Then start the chronograf instance with:

chronograf

if you then go to localhost:8888 you will get:

Klik ‘Get started’ and configure your connection

Then add a connection. Choose ‘InfluxDB’

Subsequently you can add your fields, which will fill out the Query automatically

Under the ‘visualization button you can modify the properties of the graph.
Then use the green button to ‘send’ your query to the dashboard. The dashboard will open and at first you may be daunted by the many text windows telling you you need to install a plug-in.

Fear not as these all relate to functions/queries one CAN add (but doesn’t need to). Just scroll to the bottom and you will find the graphs you are looking for.

Ofcourse it is possible to combine queries in one graph, like so:

One can delete all the before mentioned text windows, which is a bit tedious. It is apparently possible to avoid these popping up by disabling the “_internal” database in a no doubt deeply hidden config file.

Summary
InfluxDB is a very versatile database that is different from and dare i say more flexible than databases as MySQL or MariaDB. Grafana and Chronograf are two extremely versatile graphic packages that can be used to present data from a number of sources, including influxDB. On a whole I found Grafana slightly easier to use and producing nicer graphs, but that is no doubt because i have more experience with Grafana than with Chronograf.

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:

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. So I set out to find the fault and fix it. In the end it involved altering the entire structure of the program. (Edit: their program will work correctly if in the ‘void handleRoot()’ routine, they had changed String s = MAIN_page; to be String s = FPSTR(MAIN_page);.
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.

Ofcourse choosing Influx and Grafana to store and show data is also a good choice.

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