Storing Arduino sensor data on a (local) server using POST or GET and PHP and presenting it in a graph

In an earlier article, I described how to store your sensor data in GoogleSheet I also described how to make graphical presentations of data with the Highcharts library (much based on an article of Sarah&Rui Santos of Randomnerdstutorials, but extended by me).

That method was especially suited for an ESP8266 or ESP32 as they can hold and process a lot of data in memory.

So what if we use a processor with less memory, say an Arduino with an ethernet connector? Well, there are some possibilities for that too. It involves storing data on a (local) server. Yes that could be in say MySQL/MariaDB, but for much purposes a simple CSV file is enough. We will do everything in simple PHP with the help of the jpGraph library

So for now I will focus on the serverside: how to  store the data and how to show it nicely in a graph. In a subsequent article I will talk about the client side: the Arduino with an (old) ENC28J60 module.

For now let’s assume we have a local server, most likely a Raspberry with PHP and Apache installed.

The directory where we will store the PHP files as well as the data, is the ‘root’ website directory which is /var/www/html

POST-ing data
<?php 
define("LOG_FILE", "./data.csv"); 
$temp=$_POST['temp'];
$temp2=$_POST['temp2']; 
$file_handler = fopen(LOG_FILE, "a+") or die("Unable to open file");
 #fwrite($file_handler, time() . "," . $temp . "\n");
 fwrite($file_handler, time() . "," . $temp . "," . $temp2 . "\n");
 fflush($file_handler);
 echo "OK";
 ?>

Let’s call the script “POST-data.php”

GET-ing data
<?php

define("PASSWORD", "raspianTemp");
define("LOG_FILE", "./data.csv");

if(!isset($_GET["temp"])) die("NO - no value present");
if(!isset($_GET["pwd"])) die("NO - no password present");

$pwd = $_GET["pwd"];
if($pwd != PASSWORD) die("NO - wrong password");

$temp = $_GET["temp"];
if(!is_numeric($temp)) die("NO - not a number");
echo $temp;

$temp2= $_GET["temp2"];
#if(!is_numeric($temp)) die("NO - not a number");

$file_handler = fopen(LOG_FILE, "a+") or die("Unable to open file");
#fwrite($file_handler, time() . "," . $temp . "\n");
fwrite($file_handler, time() . "," . $temp . "," . $temp2 . "\n");
fflush($file_handler);
echo "OK";

?>

The GET script looks a bit more complicated than the POST script, but that’s simply because it has more checks: on a password and the entered values. That is because the GET method can also be used through an URLsuch as:

http://192.168.xx.yyy/saveTemp.php?pwd=raspianTemp&temp=12.5&temp2=9
But you can opt to leave them out or use them in the POST method as well.

Let’s call this script “GET-data.php”

Oh, darn. ‘privileges’
set the /var/www/html directory to ‘775’ (with chmod) and make sure the apache user is owner of the data.csv file. If  you let the php scripts create the file, then the apache user is the owner. If you happen to have created the file yrself, need to change ownership with:
sudo chown www-data data.csv

jpGraph

jpGraph is a graphic library meant for php. It can be downloaded here. for now we will install it in the /var/www/html folder.
Download the tar file and put it in the /var/www/html folder.
Currently jpgraph-4.2.10.tar.gz is the latest version.
‘Install’ it by unpacking it and then rename the created directory as follows:

tar xzf jpgraph-4.2.10.tar.gz
sudo mv jpgraph-4.2.10 jpgraph

 

Once that is all done we can show the data

Showing the data
<?php

define("LOG_FILE", "./data.csv");
require_once('./jpgraph/src/jpgraph.php');
require_once('./jpgraph/src/jpgraph_line.php');

$times = array();
$values1 = array();
$values2=array();
$file_lines = file(LOG_FILE, FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES);

//Get the data
foreach($file_lines as $line_num => $line_value) {
$line_elements = explode(",", $line_value);
#$times[] = date("H:i:s", $line_elements[0]);
$times[] = date("d-m-y H:i", $line_elements[0]);
$values1[] = $line_elements[1];
$values2[] = $line_elements[2];
}
// setup the graph
$graph = new Graph(1100, 550);
$graph->SetFrame(false);
#$graph->SetScale('intint');
$graph->SetScale('textlin');
$graph->SetShadow($aShowShadow=true,$aShadowWidth=5,$aShadowColor=array(102,102,102));
$graph->ygrid->SetFill(true,'#EFEFEF@0.5','#BBCCFF@0.5');
$graph->ygrid->Show();
$graph->xgrid->Show();

#$graph->img->SetAntiAliasing(false);
//li,re,bo/on
$graph->SetMargin(50,30,36,90);
$graph->SetMarginColor('white');

//Setup Title and axis
$graph->title->Set("Temperatures");
$graph->xaxis->SetLabelAngle('50');
$graph->xaxis->SetTickLabels($times);
$graph->xaxis->SetLabelSide(SIDE_BOTTOM);

$graph->yaxis->scale->SetAutoMin(0);
$graph->yaxis->title->Set("&deg;C");

$graph->ygrid->SetFill($aFlg=true, $aColor1='white', $aColor2='gray9');
// Create the first line
$lineplot = new LinePlot($values1);
$lineplot->SetLegend('Living Room');
#https://jpgraph.net/features/src/show-example.php?target=new_line1.php
$graph->Add($lineplot);
$lineplot->SetColor("green");

// Create the second line
$p2 = new LinePlot($values2);
$graph->Add($p2);
$p2->SetColor("#FF1493");
$p2->SetLegend('Bedroom');

$graph->legend->SetFrameWeight(1);
// Finally send the graph to the browser
$graph->Stroke();
?>

The above methods can come in handy for every kind of data generated by a data monitoring system, but as said, I wanted to make some arduino/ENC28J60/EtherCard lib examples. The POST method was fairly simple (done) but the GET method caused some headaches.

Currently, if you have stored a lot of data, the graph becomes too condensed. That can be remedied by for example only showing the last 10 or 20 lines. I will see if i can add that.

(The idea to use jGraph came from Luca Dentella, who has done much work with the ENC26J60)

Using a MAX6675 temperature sensor without a library

When you are using a sensor, or other type of peripheral, it is always handy to use one of the available libraries. Sometimes though there is no library, or the instructions for the peripheral are simple enough to not use a library, or the library gives a lot of overhead.
In an earlier article, I showed how the (excellent) datasheet of the good old BMP180 offers a blueprint on using the sensor without library on an attiny85.

Purely as a learning moment, I will now do the same for an SPI sensor, the MAX6675 temperature sensor on an ESP8266. Yes I know, there is a library available, but consider this just as an exercise in dealing with SPI peripherals.
The MAX6675 datasheet states the following:

Force CS low and apply a clock signal at SCK to read the results at SO. Forcing CS low immediately stops any conversion process. Initiate a new conversion process by forcing CS high. Force CS low to output the first bit on the SO pin. A complete serial interface read requires 16 clock cycles.Read the 16 output bits on the falling edge of the clock.

The first bit, D15, is a dummy sign bit and is always zero. Bits D14–D3 contain the converted temperature in the order of MSB to LSB. Bit D2 is normally low and goes high when the thermocouple input is open. D1 is low to provide a device ID for the MAX6675 and bit D0 is three-state.

There is another piece of information hidden in the Datasheet, saying that the 12 Bit output is in steps of 0.25 degrees.
Another piece of info that would come in handy, but that is void in the datasheet is the signal timings. Yes, there are some timing diagrams in the datasheet, but they mention no numbers, so we have to just do some trial and error. The speed of the SPI-bus itself is half of the CPU processor speed so in the ESP8266 it runs at 40Mhz. We willl just leave it at that.

In the table of the SO output register we see that apart from MSB (bit15) we also do not really need the 3 LSB’s, but we will read them anyway and deal with them. Bit 2 does have some function as it indicates whether the Sensor is connected or not.
Finally, we have to multiply the 12 bit value we found with 0.25 because  the raw value found gives the number of quarter degrees.

So in order to get the temperature we need to do the following things:

  • Begin with CS low to stop any conversion. To start fresh so to say
  • Then make CS high, to start the conversion
  • Pull CS low to output the bits to MISO on falling edge of clock.
  • Cycle the clock to read all the 16 incoming bits on the MISO pin
  • Ignore the MSB (bit 15, remember, we count from 0)
  • Discard the 3 LSB’s by a right shift 3
  • Multiply the found value with 0.25 (or divide by four).

In Software that goes like this:
Define the pins and variables we will use

#define cs D8 //cs
#define clk D5 //sck
#define miso D6 // so
//define variables
int v = 0;
float tempC;//for temperature in degrees Celsius
float tempF;//for temperature in degrees Fahrenheit

In the setup, we define the pinModes and the begin states of the pins

void setup() {
Serial.begin(115200);
pinMode(cs, OUTPUT);
pinMode(clk, OUTPUT);
pinMode(miso, INPUT);
digitalWrite(cs, HIGH);
digitalWrite(clk, LOW);
}

In the loop, we  call a procedure that reads and returns the MISO bits

void loop() {
v = spiRead();
if (v == -1) {
  Serial.print("Sensor not found");
}
else {
  tempC = v * 0.25; //Celsius (read on,the 0.25 will become clear)
  tempF= tempC * 9.0/5.0 + 32;//Fahrenheit
  Serial.print(tempC);
}
delay(500);
}

Then in the spiRead procedure, we read the  the first MSB (bit15) and discard it.  Then in a loop we read the remaining 15 bits (bit 0-bit 14) and add them to a variable, we then discard bit 0,1 and 2, so we have a 12 bit number left. This number than still needs to be multiplied by 0.25 to give the result in degrees celsius

int spiRead() 
{
int value = 0;
digitalWrite(cs,LOW);
delay(2);
digitalWrite(cs,HIGH);
delay(200);

//Deal with bit 15 first
digitalWrite(cs,LOW);//Pull CS low to start conversion
digitalWrite(clk,HIGH);
delay(1);
digitalWrite(clk,LOW);// bit is read on downflank

//Read bits 14-0 from MAX6675 and store in 'rawTmp'
for (int i=14; i>=0; i--) 
{
  digitalWrite(clk,HIGH);
  rawTmp += digitalRead(miso) << i;   digitalWrite(clk,LOW); } // if bit D2 is HIGH no sensor connected   if ((rawTmp & 0x04) == 0x04) return -1; //remaining 14 bits read, now discard bits 0,1 and 2 //we do that with a shift right three places return rawTmp >> 3;

The section

// if bit D2 is HIGH no sensor connected
  if ((rawTmp & 0x04) == 0x04) return -1;

needs a bit of explaining. Once we read all 15 bytes, we can check bit 2 to see if that is set or not, to see if the sensor is detached or connected. the hex value 0x04 is equal to 0b00000100, so no matter what the value of our 15 bit raw temperature is, if we ‘AND’ it with 0b0b00000100, only bit number 2 will matter. If that bit is set, the result is 0b00000100 again, which is 0x04. So if we AND the raw temperature with 0x04 and the result is 0x04, we know that bit was HIGH, meaning the sensor was not connected.

Once that is done, we remove the  3 unnecessary bits (2,1 and 0) by the rightshift with 3.

It will probably be clear that if  we would have defined the command:

for (int i=14; i>=0; i--)

as

for (int i=14; i>=2; i--)

we would only need to do a rightshift 1 like so

return rawTmp >> 1;

As we have to multiply the rawTmp value with 0.25, or divide it by 4. You might be enticed to do that with  a bitshift because after all, division by 4 is a rightshift with 2.
So, in theory it is possible to combine:
v=rawTmp >>3
tempC=v*0.25

as :
v=rawTmp >>5
Yet that is not a good idea.
Suppose we find the value “101001000” (=328)
with a rightshift 3 that becomes “101001” (=41)
divided by 4 that is 10.25 degrees.
However, if we would have combined it in a rightshift 5, we would have gotten “1010” which is 10.

Once it is all done we can convert the degrees Celsius to degrees Fahrenheit by the known formula: *9/5 +32
Download the code here.
In a next article, I’ll hook up the MAX6675 to an ESP8266-01 and will make a monitoring system for a fireplace or cooktop

A cheap 1 channel relaymodule for ESP8266-01. Modifications revisited

In an earlier post, I discussed 2 popular 1 channel ESP8266-01 relay boards that are widely available on  ebay and chinese webstores. One of these boards -the simplest- seems to cause a lot of problems with the unsuspecting buyer, as it doesn’t work.
So, it might be good to address the problems around this board again, in a more practical session.

Be aware that this circuit is intended for the ESP8266-01S, but it can be made to work with the regular ESP8266-01.

The main reason why this board  does not work, is because the designer of  the PCB was drunk, or high on drugs when he designed it, or maybe he just didn’t understand the workings of the ESP8266-01.
His failure becomes immediately clear when we look at the circuit:
The designer used GPIO0 to trigger the relay pin.
There is nothing wrong with that, but as GPIO0 needs to be pulled high to let the ESP8266 start in UART mode (i.e. a normal start). The consequence is that the relay is activated on start-up. “No problem” must the designer have thought, “I just add a 2k resistor to pull it down”. Sure, that works, but the ESP8266 then just will not boot anymore.
So, we need to remove that 2k resistor that is indicated in the circuit above. On your PCB it is indicated as R2 and labelled with ‘202’.

A simple nudge with a hot soldering iron suffices, but make sure you remove the right one.

The next issue is that there is no connection between the Chipselect (EN or CH_PD) and the Vcc. So that needs to be fixed. Ideally that can be done with a 10k resistor, but it is most practical to just make the wire bridge as indicated in the circuit above, like this:

If you have an ESP8266-01S, then you are set, the board should work.

If you have a classic 512kRAM ESP8266-01, then you need to do one more thing: you need to add 10k pullup resistors between GPIO0 and 3V3 and between GPIO2 and 3V3.
When you have done this, the module will also work with an ESP8266-01.

Now  you have your 01 or 01S working  but, the relay will be activated on startup. That can be handled in several ways:
Add a “digitalWrite(0,HIGH);” instruction to the beginning of your program….or….
Use the NC contact to make your connection….or….
reroute the track to pin 0, to another pin (GPIO 1 or GPIO3) and give that pin a pulldown resistor.

If you want to control the board via MQTT, this program might be of interest to you. If you want to use a webpage on your phone, a simple Webserver will do.

Just one more thing about removing the 2k resistor. You may have come across people that do not mention removing the 2k resistor. They solder a 3k3 resistor between 3V3 and GPIO0. Well, yes that works. Why? Well effectively they make the pull up resistor on GPIO0  about 2k5 (the 10k in combo with the 3k3). So that gives a voltage divider of 2k5 over 2k, which makes 1.46 Volt out of  3.3V, which is high enough to be seen as HIGH by gpio0. That is indeed a less elegant method, but it works.

Now if you happen to have the  other relay board, the one with the STC15F104-SOP8 micro controller in addition to the ESP8266, and you dont get that working, begin with checking if the CH_PD/ EN pin is indeed pulled up to 3V3.

MQTT -HTTP-OTA program template

I provide a template that can be used as a start for your own MQTT program on an ESP8266. It connects either through DHCP or via a static LAN IP.
It implements basic MQTT functions (subscribe and publish) and I have added a hoist of functions/MQTT messages that you might find useful.
For instance:

  • IPnr
  • MACnr
  • ChipId
  • Uptime
  • Heartbeat
  • TimeDate (internet), including the ‘Day of Year’
  • RSSI
  • Filename
  • A number of memory and core functions
  • HTTP-OTA Download here.

The WebOTA is quite basic, upon sending an update command via MQTT, the program seeks a bin file on a server that you need to specify. You can get a bin file from your program by pressing Alt-Ctrl-S in the Arduino-IDE.

The name the OTA expects -and what is generated by Alt-Ctrl-S – is based on the filename plus the board, followed by ‘bin’. So in this case that is “Template.ino.generic.bin”. If you compile it for a Wemos D1, that file will become “Template.ino.d1_mini.bin”

Sending ESP8266 sensordata to GoogleSheet

If you want to store sensor data coming from your ESP8266 (or ESP32) project ‘in the cloud’, then there is a hoist of options. One option is what most people will already have available, namely GoogleSheet.
There are a number of tutorials on how to set that up, and i will just describe how I did it here, as part of a larger project I will publish later.

What we will do is:

  • Set up a new Google Sheet
  • Add a script to that sheet that places data in the sheet
  • Make a function for the ESP8266 that calls that script and hands it the data we want stored.

During the course of creating the GoogleSheet and the GoogleScript, we will need to take note of two numbers: The “sheet ID” and the “script ID”. The latter is also referred to as the GAS_ID
The GAS_ID usually slooks something like: “AKfycbxp……………………”
We will put the GAS_ID in the ESP code calling the script we created.
We will put the sheet ID in the GoogleScript, so the script knows in what sheet to place it.

Creating the Google sheet and acquiring the sheet ID
Go to your google drive on “drive.google.com”
Select New-GoogleSheets
A new sheet will becreated, give it a meaningful name. If you then look at the URL that will look something like:
https://docs.google.com/spreadsheets/d/1UML3C_P1H…&#8230;..hqCxos/edit#gid=0
The part between “/d/” and “/edit$gid-0” is the sheet ID. Copy that somewhere.

Creating the Google script and acquiring the GAS_ID
In your new GoogleSheet, go to “Tools-Script Editor”
Give the script the same name as you gave your Sheet

Paste the following code:

function doGet(e) { 
  Logger.log( JSON.stringify(e) );  // view parameters
  var result = 'Ok'; // assume success
  if (e.parameter == 'undefined') {
    result = 'No Parameters';
  }
  else {
    var sheet_id = '197ZGbUbbFmKtV-TPEtxYCXVRxt7YB00HQukSo-QwX14'; 		// Spreadsheet ID
    var sheet = SpreadsheetApp.openById(sheet_id).getActiveSheet();		// get Active sheet //getSheetByName('naam') voor ander blad
    var newRow = sheet.getLastRow() + 1;						
    var rowData = [];
    var sheet2=SpreadsheetApp.openById(sheet_id).getSheetByName('Sheet2');
    var newRow2=sheet2.getLastRow() + 1;
    var rowData2 = [];
    rowData[0] = new Date();    // Timestamp in column A
    rowData2[0] = new Date();
    for (var param in e.parameter) {
      Logger.log('In for loop, param=' + param);
      var value = stripQuotes(e.parameter[param]);
      Logger.log(param + ':' + e.parameter[param]);
      switch (param) {
        case 'temperature': //Parameter
          rowData[1] = value; //Value in column B
          result = 'Written on Column B';
          rowData2[1]= value;
          break;
        case 'humidity': //Parameter
          rowData[2] = value; //Value in column C
          result += ', Written on column 3';
          break;
        case 'moisture' :
          rowData[3]=value;
          result += ', Written in Column D';
          break;
        default:
          result = "unsupported parameter";
      }
    }
    Logger.log(JSON.stringify(rowData));
    // Write new row below
    var newRange = sheet.getRange(newRow, 1, 1, rowData.length);
    var newRange2= sheet2.getRange(newRow2,1,1, rowData.length)
    newRange.setValues([rowData]);
    newRange2.setValues([rowData]);
  }
  // Return result of operation
  return ContentService.createTextOutput(result);
}
/**
* Remove leading and trailing single or double quotes
*/
function stripQuotes( value ) {
  return value.replace(/^["']|['"]$/g, "");
}

Change the spreadsheet ID into your spreadsheet ID

Next go to Publish-Deploy as Web App

Change the acces type to ‘anyone, even anonymous’ and deploy

After deployment, you will be shown a url. Copy that as it contains the GAS_ID


The URL will look like https://script.google.com/macros/s/AKfycbxPrTJNtUgXxqMX&#8230;
The Script ID  (GAS_ID) is AKfycbxPrTJNtUgXxqMX…
This is the ID we will use in the ESP8266 code.
Before you leave the Google Sheet, go back to the Sheet itself and make sure there is a sheet1 and a sheet2.

This is not an absolute necessity, but it allows me to show how you can write to different sheets in your main sheet. In your neck of the woods those might be called “Sheet1” and “Sheet2”

Setting “less secure apps”
A final thing you need to do is to tell Google it should allow your applications (in this case, a connection coming from your ESP). To do that go to the less secure aps setting, and switch that ON

Switch to “ON”

The ESP8266 code

String GAS_ID = "";//getactivespreadsheetID
const char* fingerprint = "46 B2 C3 44 9C 59 09 8B 01 B6 F8 BD 4C FB 00 74 91 2F EF F6";
const char* host = "script.google.com";
const int httpsPort = 443;

void sendData(int x, int y, byte z)
{
  Serial.print("connecting to ");
  Serial.println(host);
  gsclient.setInsecure();
  if (!gsclient.connect("script.google.com", httpsPort)) {
    Serial.println("connection failed");
    return;
  }

  if (gsclient.verify(fingerprint, host)) {
    Serial.println("certificate matches");
  } else {
    Serial.println("certificate doesn't match");
  }
  String string_x     =  String(x, DEC);
  String string_y     =  String(y, DEC);
  String string_z     =  String(z, DEC);
Serial.println(string_x);
Serial.println(string_y);
Serial.println(string_z);
  String url = "/macros/s/" + GAS_ID + "/exec?temperature=" + string_x + "&humidity=" + string_y + "&moisture=" + string_z;
  Serial.print("requesting URL: ");
  Serial.println(url);

  gsclient.print(String("GET ") + url + " HTTP/1.1\r\n" +
                 "Host: " + host + "\r\n" +
                 "User-Agent: BuildFailureDetectorESP8266\r\n" +
                 "Connection: close\r\n\r\n");

  Serial.println("request sent");

  while (gsclient.connected()) {
    String line = gsclient.readStringUntil('\n');
    if (line == "\r") {
      Serial.println("headers received");
      break;
    }
  }
  String line = gsclient.readStringUntil('\n');
  Serial.println(line);
  if (line.startsWith("{\"state\":\"success\"")) {
    Serial.println("esp8266/Arduino CI successfull!");
  } else {
    Serial.println("esp8266/Arduino CI has failed");
  }
  Serial.println("reply was:");
  Serial.println("closing connection");
}

This function takes 3 arguments that will be placed in  the Google sheet.
Back to the Script

The Lines:

var sheet_id = '1DdQ-dc6bpsP7EtvtvBHvl-cZai1qUC6QfNtaxTnh6w0'; 	
var sheet = SpreadsheetApp.openById(sheet_id).getActiveSheet();

go to the proper sheet and open the sheet that is active.
The lines:

var newRow = sheet.getLastRow() + 1;						
    var rowData = [];
    var sheet2=SpreadsheetApp.openById(sheet_id).getSheetByName('Sheet2');
    var newRow2=sheet2.getLastRow() + 1;
    var rowData2 = [];

Create a new Row in the first sheet and create a new Row in the second sheet. Beware ‘Sheet2’ is the NAME ofyor second sheet. if you have given it a different name, that shoud be reflected here
The lines:

rowData[0] = new Date();    // Timestamp in column A
rowData2[0] = new Date(); //Timestamp Sheet2 column A

insert a timestamp in the first column of both sheets
The lines:

for (var param in e.parameter) {
      Logger.log('In for loop, param=' + param);
      var value = stripQuotes(e.parameter[param]);
      Logger.log(param + ':' + e.parameter[param]);
      switch (param) {
        case 'temperature': //Parameter
          rowData[1] = value; //Value in column B
          result = 'Written on Column B';
          rowData2[1]= value;
          break;
        case 'humidity': //Parameter
          rowData[2] = value; //Value in column C
          result += ', Written on column 3';
          break;
        case 'moisture' :
          rowData[3]=value;
          result += ', Written in Column D';
          break;
        default:
          result = "unsupported parameter";
      }

insert the 3 parameters that are received in the first sheet and the second sheet. The sole reason I create two sheets that get the same data is to demonstrate how to use two sheets.
The example is only logging 3 values, but when looking at the code it should be quite obvious on how to add more data.
One warning: when you edit the script, the previous versions remain, when done, you need to select the right version of the script to use:

An ESP8266 with Static IP, sending DHT11 data via MQTT, going into DeepSleep

Sometimes, all you need is a quick program that needs to send some sensor data from a location that you don’t want or can’t put a power cable to.
Battery power, sending an ESP8266 into deepsleep for most of the time can be the answer.

The program here does just that. For reasons of speed it uses a static IP set up.
Dont forget to connect GPIO16 to the RST pin on your ESP8266.  A bare ESP8266 is your best choice. If you are using an ESP8266-01, you will need to have good eyes to solder a wire between pin 16 and the RST pin.

You can download the full program here.

MX1508 vs L9110S vs TB6612 vs L293D Motordriver board

Three popular, cheap motordriver boards that are available on chinese webstores are the MX1508, L9110S and TB6612.

MX1508

At Aliexpress and Banggood, the MX1508 is listed as a “Dual Channel L298N DC Motor Driver Board PWM Speed Dual H Bridge Stepper Module”. However it is an MX1508.

This board can drive two motors independently. It is an H-bridge configuration for each motor and so can drive the motors in either direction. It is rated at 1.5A on each motor with a peak of 2.5A.
The module itself is not really breadboard friendly. The pin spacing is 0.1″ but the various connectors are at aberrant spacing.

The MX1508 from Shenzhen Guanghui Electronics Co., Ltd. has a working voltage of 1.8-5Volt, but can drive motors with an operating voltage of 2-9.6 Volt. The module however, has only 1 Voltage input, that supposedly can take 2-10 Volt. The digital part of the MX1508 however is fed through a 220 ohm resistor with the resulting voltage being capped by what I presume to be a zenerdiode of 5 Volt. With most hobby motors being either 6 or 12 Volt, 10 Volt may not be the most practical voltage.
When analyzing the PCB, one odd thing becomes clear: Pin 4 (VDD1), the input for the operating voltage of Motor A, seems to be not connected to anything. Yet the module works. Connecting it seems the right way to go though. It is very well possible that it is an unintentional mistake in the PCB.

The MX1508 has no fly back/free-wheeling diodes present, though they are advised by the chip manufacturer. As a result, there might be big spikes on the powerline when you are reversing the direction, which could lead to problems with other devices connected. Connect the motor like in the figure to the right. Controlling a DC motor with an H-bridge such as the MX1508 is in fact quite simple. Yet there is a library available.

The only (currently) available datasheet is in chinese. It advises to put a 100nF over each motor output, something that is not done on the module PCB. The MX1508 is also sold (e.g. by LCSC) under the alias TC1508.

There is also an MX1208 with a current of 1.3A (2A peak). These are only available as a bare chip, not as a module.

Although the chinese webstores market the MX1508 as a replacement for the L298N motordriver, that is with its 46 Volts and 4 amp total, quite a strech.

The L9110S

So, if 10 Volt motorvoltage is not enough, there is the L9119S from Asic. The available module has in fact two L9110 chips, as they each control only one motor. The module can control 2 motors that according to the datasheet can be 2.5-12V with a maximal continuous current of 800mA , with a peak of 1.5~2 Amp.
The labeling on the input pins can be a bit confusing, but it is like this:

  1. B-IA: Motor B Input A
  2. B-IB: Motor B Input B
  3. GND: ground
  4. VCC: 2.5V-12V DC
  5. A-IA: Motor A Input A
  6. A-IB: Motor B Input B

The L9110 chips each have 2Vcc and Ground pins, but there is no indication that these should get different voltages (like one for the chip and one for the motor) and on the module PCB all grounds are connected and all Vcc’s are connected. There is however a small point of concern in using the module with a non-TTL level like 12 Volt: The inputs are pulled up via 10k resistors to the Vcc. That means that if you use 12 Volt DC for the motor and a 3v3 microprocessor to control the motor, on a LOW level there will be possibly flowing 1.2 mA into the uC pin. That does not need to be a major problem, but it is something that needs to be taken into account. If it is a problem, a simple optocoupler circuit can be put in between. Though motors are easy to control with an H-bridge such as this one, there are several libraries for the L9110S.

TB6612

The TB6612 from Toshiba is another relatively cheap board available at chinese webstores. It can drive 2 DC motors (or one stepper) with 1.2A per channel (3A peak). It runs at 2.7V-5V logic (2.7-5.5V according to some sources). The motor voltage is separate from the 2.7-5.5 Volt logic voltage. Good for motor voltages from 4.5V up to 13.5V. The H-bridge is built with FETs and it comes with built in kick-back diodes.
The control of the TB6612 is a bit different compared to other motor drivers. Where usually the speed of a motor in an H-bridge is controlled by a PWM signal on one of the 2 control pins, the TB6612 has a seperate PWM pin, next to the 2 control pins. It can be pulled up if not used. So you set the direction with 2 pins and the speed with a 3rd pin. This makes it imho a bit easier to use with steppermotors. It also has a standby pin to rapidly disable both motors. Connect to ground to disable, pull-up for normal operation. On the ‘Chinese’ boards, it is usually left floating. The adafruit module has it pulled up to 5V via a 10k resistor.
Though DC motors are easy to control with an H-bridge such as this one, there is a library for the TB6612.

L293D
The L293D for a long time has been a workhorse among the motordrivers. The module displayed here is available cheapley and works well. The 5 pins at the bottom do not have a silkscreen label, but their functionis clarified in the picture. The Vcc is connected to pin16 (Vcc1). That pin is the 5V supply for internal logic translation. That is kinda odd as the other pins on that header are dealing with motor connections. The ‘Vin’ on the screw connector is attached to pin8 (Vcc2) which is for the 4.5-36V motor voltage. On this particular modele the Enable pins are pulled HIGH with jumpers. This particular module does not seem to follow the cooling PCB recommendations made in the datasheet, so an additional heatsink may be necessary.

Though the first 3 modules are clearly no competition for the L298, how do they compare to the L293?

MX1508 L9110 TB6612 L293
Motor Voltage 2-10V 2.5-12V 4.5-13.5 4.5-36V
Logic Voltage 1.8-5V 2.5-12V 2.7-5.5 5V
Current 1.5A 800mA 1.2A 1A (0.6 for 293D)
PeakCurrent 2.5A 1.5-2A 3A 2A (1.2 for 293D)

Other popular motordriver boards -especially in 3D printers- are the A4988 and DRV8825. You will find a very good discussion of those two in this comparison.

LSC smart sensors, switches and lamps

Dutch thrift store ‘Action’ had a promotion of TuyaSmart compatible IoT devices for quite low prices. The devices were:

  • Smart switches
  • Smart doorsensor
  • Smart movement sensor
  • Smart alarm
  • Smart lamps and led strips
  • Remote control

I found the efficiency (as in lumen per Watt) of the lamps a bit low, with exception of one 800 lumen lamp, and the ceiling lamp was not really my taste so I didnt bother with them. The remote control seemed somehow obscure, as it seemed it could only control one device. The other devices though were quite promising so I bought those.

Though LSC has it’s own app, I already had the TuyaSmart app, so I decided to use that one. The LSC app is a spitting image of the Tuya app, but it is not recognized by e.g. Google Home, whereas Tuya is.

The EU type switches are quite compact, but you may still ha e trouble putting 2 right next to each other in a double outlet. The setup is very easy and if you use the ‘scan’ option in the tuya app, not much can go wrong.

The door sensor and the alarm are also easy to set up. The doorsensor is coincell operated and the two parts can be stuck to the door and door frame. In the Tuya app, actions can be attached to the doorsensor to e.g. switch on a light, or sound an alarm. The sensor sends notifications to your phone as well.

That leaves the motion sensor and somehow that was a different story. Connecting to wifi was simple, but after that, nothing g happened. No amount of movement seemed to be noticed. As another sensor had the same behaviour, I had not much choice but to return them.

Yet…..I kept wondering. Just couldn’t imagine two sensors were not working, especially as no one else seemed to return them while they were selling like hot cakes.

So….I bought another one…..same result. Various reinstalls, even in the LSC app, showed no effect, so I left it, frustrated, deciding to return it after the weekend.

And then….out of the blue, it started to work, and quite well I should add. The sensor does ha e a bit of a refractory period, meaning that after it senses movement and sends a notification, it will not sense new movement for a few minutes. People have reported this to be about 5 min, but in my experience it is more like 3 minutes and occasionally even 1 minute.

TuyaSmart

The app seems to have one slight shortcoming though: When a movement is registered and shown at the bottom, the app only shows  “intruder” for a short time and goes back to its ‘No intruder’ status quickly. But that is only a cosmetic short coming.Commercial software or Flashing?

The Tuya app, working with the software in the devices does what it does and it is quite good software. Although I do not believe that ‘the chinese’ are particularly interested in when I switch my lights on, there are many good reasons to put your own software on an IoT device: Not being dependent on 3rd party servers that might go out of business, availability on LAN rather than only WAN, no deluge of different (possibly shady) apps on your phone.
The devices I had all appeared to have an ESP8266 under the hood and could be opened.

LSC motion sensor

 

Getting I2C regulated 0-10Volt out of an Arduino or ESP8266

The max Output of an Arduino or ESP8266 is roughly 5, respectively 3.3 Volt. The Arduino has the possibility to output a varying voltage on its Analog output pins, whereas the ESP8266 can emulate a varying voltage on a PWM addressed pin, with the help of a low pass filter (basically an RC filter [4k7/10uF]). There are instances where it would be handy/necessary to have a 10 Volt regulated output: Some dimmable lights require a control signal of 0-10Volt as do some commercial Power-motor controllers.
It is not hard to get such a signal from the mentioned microcontrollers, by amplifying the output.

The circuit shows such an amplifier. It is a simple Opamp non-inverting amplifier. When the Op Amp receives an input on the non-inverting output,  it will raise or lower it’s output until the level on the inverting output is matched.
As we are using a voltage divider to feedback the output voltage, we can make the opamp to drive the output higher than the input on the non-inverting pin.
The output voltage is thus defined as:
Vo=Vin x (1+(Rtop/Rbottom)), in which Rtop is R1+the top fraction of  P2 and Rbottom is R2+the bottom fraction of P2.
the amplification thus can vary between 1+(10/20) and 1+(20/10) or: between 1.5 and 3.
Ideally, with the variable resistor in the exact middle, the amplification equals 2 and the Output -when driven by the Arduino- should be 10 Volt, however, the Arduino may not output exactly 5 Volt, in which case the output can be trimmed with the variable resistor.
As the LM358 is not a rail to rail opamp, the Vcc needs to be higher than the required output. As the datasheet is a bit unclear on this, I have chosen 12 Volt, which turns out to work fine.
When using the ESP8266, the maximum output of the circuit will be 3×2.2=9.9Volt. In practice however the output on the ESP8266 pins will probably not reach 3v3, also, making an analog output with PWM and a low passfilter is not ideal.

Enter, the MCP4725
The ‘problems’ mentioned above, can largely be remedied by using  a proper DAC (Digital Analog Converter). The MCP4725 is a cheap, easy to use DAC that is available on break out boards at various chinese webshops.
The MCP4725 is a 12 bit, rail-to-rail DAC, which means that if you feed it with 5 or 3V3, that will come out of it (well…almost). It also has on board EEPROM for storage of setup, but for this build that is not really necessary. There is a library available in the Arduino IDE library manager. The Vcc pin requires an appropriate bypass capacitor of about 0.1 µF (ceramic) to ground. An additional 10 µF capacitor (tantalum) in parallel is also recommended to further attenuate high frequency noise present in application boards.

As the MCP4725 is a 12 bit DAC, the range goes from 0-4095. Writing 4095 to the MCP 4725 to the DAC therefore would give the max output. A program for the MCP4725 thus would look like something like this:

#include <Wire.h>
#include <Adafruit_MCP4725.h>
Adafruit_MCP4725 dac;

void setup(void) {
dac.begin(0x60); // This may vary between 0x60 and 0x65 for your board
}

void loop() {
// 1V Out from MC4725 -> 2V Out from LM358
dac.setVoltage(819, false); // false meaning "do not write to EEPROM"
delay(2000);
// 2V Out from MC4725 -> 4V Out from LM358
dac.setVoltage(1638, false);
delay(2000);
// 2.5V Out from MC4725 -> 5V out from LM358
dac.setVoltage(2043, false);
delay(2000);
// 3V Out from MC4725 -> 6V Out from LM358
dac.setVoltage(2457, false);
delay(2000);
// 4V Out from MC4725 -> 8V Out from LM358
dac.setVoltage(3276, false);
delay(2000);
// 5V Out from MC4725 -> 10V Out from LM358
dac.setVoltage(4095, false);
delay(2000);
}

The max output current of the LM358 is a bit obscure from the datasheet, it is probably around 20-40mA which is sufficient for most control signals. If you need more current (maybe you directly want to drive a motor), consider the LM675 that has a max output of 3 Amps. Be careful though as the LM675 can easily oscillate at amplification <10. Oscillation will lead to output going to (almost) rail even at no input and the chip warming up). Use decoupling capacitors – preferably a ceramic (100nF) and tantalum (10uF) type in parallel- and keep the leads short. A a small capacitor (on the order of 50 pF to 500 pF) across the circuit input, helps preventing the input leads to function as antenna.
A better choice might be the OPA544T, though that only has a 2 amp output, but is more stable at low amplifications. It is also more expensive.

Another way to get more current is with a power transistor, such as a BD137 (1A) or even a good old 2N3055/TIP3055 (15A).  However,the hFE starts to play an important role in this circuit. take for instance the BD137 that has a worst case hFE of 40. When it needs to deliver 1A, it will require  25mA as its base current. That is on the edge of what an LM358 seems to be able to deliver. If we add a BC547 that has a hFE of 110-800, a much smaller current will be required from the 358.
Other possible transistors in this range are 2SD882 (2Amp hFE 60), BD135, BD137, BD139 (1.5 A, hFE 40)
In case of the 2N3055, that has a worst case hFE of 20. If 15A is required, a base current of 750mA is required. Adding a BC547 would bring that down substantially, but it can’t deliver 750mA. A 2N2222 could, but it’s hFE is only around 30 at that current so you would need a BC547 AND a 2N2222  AND a 2N3055


In this light a TIP120 or TIP122  is a better choice as those already are powerdarlingtons with an hFE of about 1000 and a max current of 5A

Connecting an ESP8266 to the outside World

I admit that this is a subject that is really really basic and no doubt explained at many places. Yet, I still see the question posed time after time at various fora and sadly the replies are often not on the ball. Things like “You need REST API”, “Just use Blynk”, “Just use IFTTT” or “Why would you want to do that”, surely will not help the struggling novice on their way.
So, rather than having to repeat my explanation again and again, it is better, I just write an article, explaining it…..for the novices amongst us.

Finding 2 IP addresses
OK, so you have an ESP8266 that reads a sensor and you want to be able to get the reading when you are away from home, using internet. It is quite simple, but you need to know 2 distinct IP addresses.
1- you need to know the IP address that connects your computer to the internet. You can easily find that on websites like “whatismyip.com“. It is also called ‘Public IP’ or ‘WAN IP’
2- you need to know the IP address that your ESP8266 has in your local network. You need to check the DHCP table in your router, but many ESP8266 programs actually print that number as well in the serial port. These numbers almost always looks like “192.168.x.yyy”. You can also use a phone app like ‘port authority’ to find the number for you.

Port forwarding
So what happens when you are on your holiday address in Reykjavik or Kabul and you want to know what your sensor back home says? You open a browser, type in the IP address you found under “1”. well that connects you via the internet to your router back home. However, your router when it gets that call, doesn’t have a clue what to do with it, let alone that it knows it should forward that call to the ESP8266 that is connected to it, because you didn’t tell it what to do with it.

So when you get back home you have to instruct your router to forward the calls to your ESP8266 and that is done via “IP forwarding” or “Port forwarding”.
What makes that a bit complicated is that it is done differently in various routers, so you would have to check your specific router for it. Look for “IP forwarding” or “Port forwarding” and in some routers it is called ‘virtual server’.
When you found that you usually have to fill out 2 things. The port that is being forwarded and the IP address it has to be forwarded to. For the port you choose “80” and for the IP address to forward to, you use the local ip address, you found under point “2”.It is the address that started with “192.168.”
If it doesnt allow you to forward a specific port, but it does allow you to forward a service, choose ‘http’ for that service.
If you really cant find the port forwarding, but You happen to find something that is called “DMZ” choose that to forward to the ip address of your ESP8266 (the address starting with “192.168”) that is less elegant, but it will work.

Setting up a Webserver
So you are back on holiday and you want to check again. You fill out the internet IP number of your home system (the number you found under “1” and yes, this time the call comes in to your router and that one knows exactly what to do,so it forwards the call to your ESP8266. The ESP duly receives the call and……….has no idea what to do with it because chances are you didnt tell it what to so with it.
Your ESP8266 needs a program that presents the data in “Webpage”. That is not hard, there are plenty of programs who do that. These programs usually are called “Webservers”. Plenty of those available. (Look in your Arduino IDE examples or check e.g. randomnerdstutorial.)

It works!!!! for now: MAC and Address reservation
So, you now have programmed your ESP8266 with a webserver program that presents the sensor data on its own little webpage.
In order to test it when you are at home you open your browser and fill out the Local IP number (that you found under “2” starting with 192.168) and yes, you see the webpage. To make sure you also fill out the address you found under “1” (thus the address that your system has on the worldwide interweb) and great it all works. You go back on holiday and check from the other side of the world and great still works. Mission accomplished!!!

…..or is it. Suddenly it doesn’t work anymore. What on earth is wrong????? Well chances are that you have restarted your ESP8266 a few times and suddenly it has received a different IP address in your Local network. Say it once had 192.168.1.103, but now you find out it has 192.168.1.110. Your router however still sends the incoming call to the first IP number because that is what you told it to do. Now it is hardly feasible that you constantly check if the local IP is still the same and if it isnt that you keep changing your forward instructions.
Fortunately, most routers allow you to reserve a specific IP number in your network, for a specific device, and this is how you do it:
When you earlier found the IP number of your ESP8266 (the one starting with 192.168), you probably also found its MAC number. A MAC number (or addres rather) looks something like: “5D:CF:7F:AC:61:66”
Now you need to look in your router that is called “MAC binding” or “Address reservation”. The object here is that you give the MAC number, as well as the IP number (192.168.xx.yyy) that should always be reserved just for the device with that specific MAC stands for “media access control”.

Yes, works again
OK you got that done,so now whenever you go to the internet address of your system back home, the call comes into your router, the router knows what internal address to send it to, that address is always the same, and the device on that address knows what to do: it presents (“serves”) a webpage to the computer half across the world behind which you are sitting. That webpage can present data, but it may just as well receive commands from you, e.g. to turn a lamp on or off. Realuse though that anybody who knows your public IP address can also check that webpage. That might not be so bad if it just presents temperature, but it might give a problem if strangers can switch a lamp off and on in your home. So once you set it all up, you may consider making a password  protected webpage.

Let me compare the situation with making a phonecall to a hotel: you need the hotel’s phonenumber (compare to the internet IP) and you need to know what room someone is in (the Local IP). The person in the room needs to know what to do with your questions (serving a webpage). That all works well as long as the person is in the same room (the address reservation: when Mr MAC comes to the hotel, he always gets room 192.168.1.103).

A final touch: DNS providers
So, everything works now………..but what if there is a rezoning and the hotel’s phonenumber changes???? Yes, you could look up the new number, but it is hardly practical.
But what does that mean in internet terms? Well, most of us get a so called ‘Dynamic IP address’ from our provider. Usually those addresses stay the same for a long time, but when you happen to restart your router you could get a different internet IP address. Sure you could check your internet address regularly, but that is only possible when you are at home. What to do when you are away from home????
The solution is a (free) DNS provider.
DNS providers provide you with an internet name, that you can use rather than an IP number. The essence though is that you get to put a tiny bit of software on your computer, that regularly checks your internet address and if that changes, it tells your DNS provider what the new number is. So you only have to provide a domain name to your browser and the DNS provider then knows the most up to date IP number to connect to.
A popular freeDNS provider for instance was “noip.com”, but there are more.