text.skipToContent text.skipToNavigation

Wi-Fi Environmental Datalogger

Another handy Arduino based tool to add to your workbench, the Environmental Datalogger measures temperature, humidity and light levels and logs it to an SD card with the time of the reading. The data is saved as a .csv file, which will open straight into the Excel spreadsheet program to allow it to be easily graphed and analysed. It is also hosts a minimal web-server, which allows the log files to be downloaded over Wi-Fi. Other features include:

  • - Diagnostic LED to show error status
  • - Card activity LED so you can that it's logging
  • - Years of data storage possible

And being Arduino based, you can customise it to suit whatever data you want to log. There's a little bit of soldering required, so it's not quite plug and play.


Wi-Fi Environmental Datalogger.zip

Shopping List:


The first step is to complete the physical build. Nearly all the connections are made on the prototyping area of the Datalogging Shield, then the Wi-Fi shield is plugged over the top Because all the connections to the Uno are made through the Datalogging Shield, there are no direct Uno connections listed, and they can be assumed to be the same as the Datalogging Shield:

Datalogging ShieldTemperature and Humidity SensorWi-Fi ShieldLight SensorFunction
L1-D4Diagnostic LED
L2-D3Card Use LED
5V(middle pin)5VVCCPower
A0OUTAnalog signal from light sensor
D8SData Signal
D0(RX)TXData from Wi-Fi Shield
D1(TX)RXData to Wi-Fi Shield

We've made the connections using short offcuts of the jumper leads- blue for the ground, orange for the 5V and white for the signals from the sensors, which have been wired to a small three pin piece of the header strip (it breaks easily at the small notches between the pins). This makes it easier to change out the sensor modules if you want to log a different type of data. The connections between L1-D4 and L2-D3 are made with small pieces of wire too.

Notice how we've kept the connections clear of the white box which outlines the ISP header- this can make it easier to do some upgrades in the future.

The final step is to connect the sensors to the board via the jumper leads and then attach the shield to the Uno board, and connect the Wi-Fi shield to the top of the Datalogging Shield. Don't forget to put the card in the socket too.


The Wi-Fi Environmental Datalogger sketch uses a number of libraries, but most of these are included in recent builds of the Arduino IDE. The only one you might need to download is the library for the Real-Time Clock chip on the Datalogging shield, and is linked here. If you've previously done one of the clock projects, you might already have this library installed.

Before uploading the code, turn the small white switches on the Wi-Fi shield to the 'off' position (closest to 1 2). This is because the Wi-Fi shield uses the serial port, and can interfere with the serial port during programming. Open the sketch and find these lines near line 37:

#define SSIDNAME "Arduino"
#define SSIDPWD "Arduino!"

You should change these to the name and password of the Wi-Fi network you'll be using. Then upload the code, making sure you have 'Uno' selected as the board type. Note that because of the way this sketch uses SPI and I2C, only the Uno will work for this project. After the code is successfully uploaded, put the white switches back to the on position and press the reset button on the Uno- this ensures that the Wi-Fi shield receives all the commands it needs to work.

Most of the hard work is done by the libraries- the sketch uses the libraries to check that the card and real-time clock are working and to get the time from the real-time clock and write the data to the card.

Insert the card into the slot and check that the LED's only flash briefly. If they keep flashing, you might have a card or RTC error. You can check which error it is by counting the flashes:

1 FlashCard not detected
2 FlashesRTC not detected
3 FlashesRTC not running
4 FlashesCannot open file
5 FlashesCannot write to card (probably card is full).

If the time is not correct or you have an RTC error, then you might need to temporarily load the ds1307 example sketch from File>Examples>RTClib, and use that to check the RTC and set the time. If you get an SD card error, check that the card is formatted to FAT or FAT32 and has enough space available.

After the sketch starts, it should start logging data every minute, and you should also see the L2 LED flash briefly every minute- this is when the card is being written too, and you should avoid turning the Datalogger off at this time, as that will cause card errors. If you want to check the data, wait until the LED is off, and then power off the Datalogger, and read the card with a computer.

Wi-Fi Access:

The Datalogger serves a very basic web page to allow you to download either the entire log file (which could take a while, as the Wi-Fi shield can only send about 500 bytes/second), a sample of every 60th data point, or the most recent 2kB of data. There is also a status line, where the status code is the same as the number of flashes the LED would give.

To access the webpage, you'll need to know its IP address on the network. We use an app called NetworkScanner on Android to find network devices. Alternatively, most routers should show the MAC address of connected devices in their DHCP client list- look for a MAC address that starts with "18:", and type the corresponding IP address into your browser:

You can click on any of the links to download the corresponding data file. The Uno is very busy trying to keep up with Datalogging and serving pages, so don't be surprised if it's slow or takes a few tries to download the data. Remember too that while the Datalogger is serving a file, it can't log data, so you might miss some samples while downloading the complete data file.

Using the Data:

If you have Excel installed, you can double-click the DATA.CSV file, and you should get something like the following:

You can widen the columns, and improve the Time and Date column by changing the format of column A:

The data can be graphed by selecting column A and one of the other columns, and inserting a chart:

If you save it now, none of the formatting or charts can be saved under the .csv file format, and the Datalogger won't be able to read the .xls format, so it's best to make a copy of the data and edit that.


The Datalogger is very configurable- the logging interval can be set by the #define LOGINTERVAL command near the start of the sketch- this is measured in seconds, and the fastest rate is about one sample per second. Even at this rate, it should not run out of space for many years. With the default settings, we are getting about 35kB per day at one sample per minute, which equates to about 25 bytes per sample.

The Wi-Fi webserver is set to give every 60th sample in the 'Sparse' file- this equates to once per hour, and can be changed in this line:

  if(strmatch("SPARSE.CSV",fname)){sendcsvsparse(60);crcount=0;}                           //serve file with every nth sample

The 'Recent' file option can be changed in the line straight after, where the 2000 is the approximate number of bytes that get delivered:

  if(strmatch("RECENT.CSV",fname)){sendcsvrecent(2000);crcount=0;}                         //serve header, and approximately last n (will typically be slightly more)

The web pages that get served up are defined in the #define HTTP lines near the start of the sketch, and can of course be customized too.

The default light sensor is simply having the raw analog signal (0-1023) logged, so this should suit any analog sensor, and it's possible to process the data afterwards in Excel if you have an equation you can use. Alternatively, you could convert the value in the sketch if you want to be able to immediately export the data. If you've used the header pins on the edge of the board it's easy to connect most of the other duinotech analog sensors. For example, add the Soil Moisture Sensor to keep track of when the garden might need watering, or the Ultraviolet Sensor Module to see how much sun the plants are getting.

The temperature and humidity are in degrees Centigrade and percent Humidity, because this is what the sensor reports directly. If you have another sensor you'd like to use, you'd need to import the code to read that sensor into the getvalue() function, unless it is a simple analog or digital type. If you need to log more different sensor values, then they can be added to logtocard() function, and will need to have the headers edited in the addheaders() function so that their names are reported correctly.

The default Wi-Fi setting are for the Datalogger to connect to an existing Wi-Fi hotspot. If you'd like to set up the Datalogger to create its own hotspot, replace the following lines in the wifiinit() function:

  WIFIcmd("AT+CWQAP",ok,5000);                                                      //exit any AP's
  WIFIcmd("AT+CWJAP=\""  SSIDNAME  "\",\"" SSIDPWD  "\"","WIFI GOT IP\r\n",10000);  //join AP
  WIFIcmd("ATE0",ok,1000);                                                          //turn echo off
  WIFIcmd("AT+CWMODE=1",ok,2000);                                                   //station mode only


  WIFIcmd("AT+CWMODE=2",ok,2000);                                                   //AP mode only   WIFIcmd("AT+CWSAP=\""  SSIDNAME  "\",\"" SSIDPWD  "\",11,3,4",ok,10000);          //set up AP     WIFIcmd("ATE0",ok,1000);                                                          //turn echo off

The next thing we would do with the Datalogger is make it portable- perhaps put it in an enclosure and run it from batteries (via the DC jack) or a USB battery pack (via the USB port). Then it can be left somewhere to run independently.


Note that you will need to install the RTClib library as well.

//Environmental Datalogger- Temperature, humidity and light logged with time to SD card
//outputs CSV data that can be opened by Excel, time is stored as serial number accurate to ~1second
//WIFI webserver to dynamically download data

//Needed for SD card
//Uses hardware SPI on D10,D11,D12,D13
//LOGINTERVAL is in seconds- takes about 1 second to do a log
#include <SPI.h>
#include <SD.h>
const int chipSelect = 10;
File fhandle;
#define LOGINTERVAL 60
#define LIGHTPIN A0

//Needed for RTC
//Uses SCL and SDA aka A4 and A5
#include <Wire.h>
#include "RTClib.h"

RTC_DS1307 rtc;

//LED pins- LED1 is diagnostic, LED2 is card in use (flickers during write)
#define LED1 4
#define LED2 3
long tmout;   //keeps track of logging interval
int temp,hum,light;   //variables to log
int status=0;     //to display status info on webpage

//for DHT11 interface
#define DHT11PIN 8
byte DHTtemp,DHThum;
int DHTstatus=0;

//for WIFI, including SSID name and password and most HTML pages
#define WIFI Serial
#define SSIDNAME "Arduino"
#define SSIDPWD "Arduino!"
#define HTTPINDEX "HTTP/1.x 200 OK\r\nContent-Type: text/html\r\nConnection: close\r\n\r\n<h2>WIFI DATALOGGER</h2><br>Choose a link:<br><a href=\"DATA.CSV\">Download DATA.CSV</a><br><a href=\"SPARSE.CSV\">Sparse Data file</a><br><a href=\"RECENT.CSV\">Most recent data</a><br>"
#define HTTP404 "HTTP/1.x 404 Not Found\r\nContent-Type: text/html\r\nConnection: close\r\n\r\n<h3>File not found</h3><br><a href=\"index.htm\">Back to index...</a><br>"
#define HTTPCSV "HTTP/1.x 200 OK\r\nContent-Type: text/csv\r\nConnection: close\r\n\r\n"
#define STATUSTITLE "<br>Status (zero is no error):"

// sketch needs ~450 bytes local space to not crash
#define PKTSIZE 257
#define SBUFSIZE 20
char getreq[SBUFSIZE]="";   //for GET request
char fname[SBUFSIZE]="";    //filename
int cxn=-1;                 //connection number
char pktbuf[PKTSIZE]="";    //to consolidate data for sending
char ok[]="OK\r\n";         //OK response is very common

void setup() {
  WIFI.begin(115200);   //start serial port for WIFI
  wifiinit();           //send starting settings- find AP, connect and set to station mode only, start server
  DHTsetup();           //start temp/humidity sensor
  digitalWrite(LED2,HIGH);      //turn on LED to show card in use
  if(!SD.begin(chipSelect)){    //SD card not responding
    errorflash(1);              //flash error code for card not found
  if (!rtc.begin()) {           //rtc not responding
    errorflash(2);              //flash error code for RTC not found
  if (!rtc.isrunning()) {       //rtc not running- use ds1307 example to load current time
    errorflash(3);              //flash error code for RTC not running
  fhandle = SD.open(FILENAME, FILE_WRITE);
  if(!fhandle){                 //if file able to be opened
    errorflash(4);              //flash error code for file not opened
  unsigned int s;
  s=fhandle.size();             //get file size
  if(!s){                       //if file is empty, add column headers
  fhandle.close();              //close file so data is saved
  logtocard();                  //log some data immediately

void loop() {
  if(millis()>tmout+LOGINTERVAL*1000L){   //if it's been more than logging interval
    getvalue();                           //fetch data to log
    logtocard();                          //log it
    tmout=tmout+LOGINTERVAL*1000L;        //add interval, so interval is precise

void checkwifi(){
  while(WIFI.available()){                    //check if any data from WIFI
    int d;
    if((d>47)&&(d<58)&&(cxn<0)){cxn=d-48;}    //connection number, could come from IPD or CONNECT
    if(d==':'){digitalWrite(LED2,HIGH);dorequest();digitalWrite(LED2,LOW);}                  //: means end of IPD data, content follows. LED on while busy

void getvalue(){                  //put subroutine for getting data to log here
  DHTtemp=0;                      //zero values to detect errors
  light=analogRead(LIGHTPIN);     //get light sensor data
  DHTstatus=DHT11();              //DHTstatus=0 if error
  temp=DHTtemp;                   //DHT11() loads temp into DHTtemp      
  hum=DHThum;                     //DHT11() loads humidity into DHThum

void errorflash(int n){           //non-recoverable error, flash code on LED1
  status=n;                       //set status for webpage
  while(1){                       //do until reset
    for(int i=0;i<n;i++){         //flash n times
      checkwifi();                //wifi services still available during fault
    delay(1000);                  //pause and repeat

void addheaders(){      //put the column headers you would like here, if you don't want headers, comment out the line below
  fhandle.println(F("\"Time and Date\",\"Temperature\",\"Humidity\",\"Light\""));

void logtocard(){
  unsigned long timestamp;
  DateTime now = rtc.now();                   //capture time
  digitalWrite(LED2,HIGH);                    //turn on LED to show card in use
  delay(200);                                 //a bit of warning that card is being accessed, can be reduced if faster sampling needed
  fhandle = SD.open(FILENAME, FILE_WRITE);
  fhandle.print(timestamp/86400L+25569L);     //write timestamp to card, integer part, converted to Excel time serial number with resolution of ~1 second
  fhandle.print(".");                         //decimal point
  timestamp=timestamp%86400;                  //fractions of a day only
  fhandle.write(((timestamp/8640)%10)+'0');   //print decimal parts
  timestamp=((timestamp%864)*125)/108;        //simplified 1000/864
  if(DHTstatus){fhandle.print(temp);}         //put data if valid otherwise blank (will be blank cell in Excel)
  if(DHTstatus){fhandle.print(hum);}          //put data if valid otherwise blank
  fhandle.print(light);                       //put data (can't validate analog input)
  if(!fhandle.println()){                     //if we can't write data, there's a problem with card (probably full)
    fhandle.close();                          //close file to save the data we have
    errorflash(5);                            //error code
  fhandle.close();                            //close file so data is saved
  digitalWrite(LED2,LOW);                     //turn off LED card to show card closed

void DHTsetup(){                    //set pin to output, set high for idle state

int DHT11(){                    //returns 1 on ok, 0 on fail (eg checksum, data not received)
  unsigned int n[83];           //to store bit times
  byte p=0;                     //pointer to current bit
  unsigned long t;              //time
  byte old=0;
  byte newd;
  for(int i=0;i<83;i++){n[i]=0;}
  digitalWrite(DHT11PIN,LOW);   //start signal
  while((micros()<t)&&(p<83)){    //read bits
  pinMode(DHT11PIN,OUTPUT);      //reset for next cycle
  if(p!=83){return 0;}           //not a valid datastream
  byte data[5]={0,0,0,0,0};
  for(int i=0;i<40;i++){         //store data in array
  byte k=0;     //checksum
  for(int i=0;i<4;i++){k=k+data[i];}
  if((k^data[4])){return 0;}      //checksum error
  DHTtemp=data[2];                //temperature
  DHThum=data[0];                 //humidity
  return 1;                       //data valid

void dorequest(){
  long t;                              //timeout
  int p=0;                             //pointer to getreq position
  int f=1;                             //flag to tell if first line or not
  int crcount=0;                       //if we get two CR/LF in a row, request is complete
  getreq[0]=0;                         //clear string  
  t=millis()+1000;                     //wait up to a second for data, shouldn't take more than 125ms for 1460 byte MTU
  while((millis()<t)&&(crcount<2)){    //drop out if <CR><LF><CR><LF> seen or timeout
      int d;
      if(d>31){                        //if an ASCII character
        crcount=0;                     //clear CR count
        if(f==1){                      //on first line
          getreq[p]=d;                 //add to GET buffer
      if(d==13){                       //if CR found increase CR count
  if((getreq[0]!='G')||(getreq[1]!='E')||(getreq[2]!='T')||(getreq[3]!=' ')){crcount=0;}   //no 'GET ' at the start, so change flag to cancel
  if(crcount==2){parsefile();}                                                             //complete request found, extract name of requested file
  if(fname[0]==0){servepage();sendstatus();crcount=0;}                                     //serve index page, reset crcount on fileserve
  if(strmatch("index.htm",fname)){servepage();sendstatus();crcount=0;}                     //serve index page, reset crcount on fileserve
  if(strmatch("DATA.CSV",fname)){sendcsv();crcount=0;}                                     //serve entire data file
  if(strmatch("SPARSE.CSV",fname)){sendcsvsparse(60);crcount=0;}                           //serve file with every nth sample
  if(strmatch("RECENT.CSV",fname)){sendcsvrecent(2000);crcount=0;}                         //serve header, and approximately last n (will typically be slightly more)
  if(crcount){serve404();sendstatus();}                                                    //no valid file served => 404 error
  WIFI.print(F("AT+CIPCLOSE="));                                                           //close
  WIFIcmd("",ok,2000);                                                                     //disconnect
  cxn=-1;                                                                                  //clear for next connection

void parsefile(){
  fname[0]=0;                                              //blank
  int p=5;                                                 //start after 'GET /'
  int t=0;                                                 //use ? to separate fields, ' ' to end
  while((getreq[p]!=' ')&&(getreq[p])&&(getreq[p]!='?')){  //terminate on space or end of string or ?
    fname[strlen(fname)+1]=0;                              //add to fname
    if(p>SBUFSIZE-2){p=SBUFSIZE-2;}                        //check bounds

void sendstatus(){                                   //to show logger status
  WIFI.print(F("AT+CIPSEND="));                      //send data
  WIFI.print(cxn);                                   //to client
  WIFI.println(strlen(STATUSTITLE)+1);               //data has length, needs to be same as string below, plus 1 for status
  WIFI.write((status%10)+'0');                       //exactly one digit

void servepage(){                                     //for serving a page of data
  WIFI.print(F("AT+CIPSEND="));                       //send data
  WIFI.print(cxn);                                    //to client
  WIFI.println(strlen(HTTPINDEX));                    //data has length, needs to be same as string below

void serve404(){                                //for serving a page of data
  WIFI.print(F("AT+CIPSEND="));                 //send data
  WIFI.print(cxn);                              //to client
  WIFI.println(strlen(HTTP404));                //data has length, needs to be same as string below

void sendcsv(){                         //for providing a csv document to download
  WIFI.print(F("AT+CIPSEND="));         //send data
  WIFI.print(cxn);                      //to client
  WIFI.println(strlen(HTTPCSV));        //data has length, needs to be same as string below
  WIFI.print(F(HTTPCSV));               //send HTTP header for csv data type, file content to follow
  pktbuf[0]=0;                          //empty buffer
  fhandle = SD.open(FILENAME);
  while (fhandle.available()) {         //send it all
    char c=fhandle.read();
    addtobuffer(pktbuf,PKTSIZE,c);      //add to buffer
    if(strlen(pktbuf)>PKTSIZE-2){       //if buffer full
      WIFIsenddata(pktbuf,cxn);         //send data
      pktbuf[0]=0;                      //empty buffer      
  if(pktbuf[0]){                        //send data if any left in buffer
  fhandle.close();                      //close file

void sendcsvsparse(int n){               //only send 1/n samples
  if(n==0){n=1;}                         //to avoid divide by zero error
  WIFI.print(F("AT+CIPSEND="));          //send data
  WIFI.print(cxn);                       //to client
  WIFI.println(strlen(HTTPCSV));         //header  has length, needs to be same as string below
  WIFI.print(F(HTTPCSV));                //send csv header
  pktbuf[0]=0;    //empty buffer
  unsigned int lfcount=0;                //only output when lfcount%n==0
  fhandle = SD.open(FILENAME);
  while (fhandle.available()) {          //scan it all
    char c=fhandle.read();
    if((lfcount%n)==0){                  //only every nth line (but first, with headers will get sent)
        WIFIsenddata(pktbuf,cxn);        //send data      
      pktbuf[0]=0;                       //empty buffer      

void sendcsvrecent(int n){                    //only send header line and last n bytes ((approximately)
  long p;
  WIFI.print(F("AT+CIPSEND="));               //send data
  WIFI.print(cxn);                            //to client
  WIFI.println(strlen(HTTPCSV));              //data has length, needs to be same as string below
  WIFI.print(F(HTTPCSV));                     //send csv header
  pktbuf[0]=0;                                //empty buffer
  unsigned int lfcount=0;                     //to make sure first line is sent
  fhandle = SD.open(FILENAME);
  while (fhandle.available()) {               //scan it all, send all except what gets skipped below
    char c=fhandle.read();
    addtobuffer(pktbuf,PKTSIZE,c);            //add to buffer
    if(strlen(pktbuf)>PKTSIZE-2){             //if buffer nearly full
      WIFIsenddata(pktbuf,cxn);               //send data      
      pktbuf[0]=0;                            //empty buffer
  if((c==10)&&(lfcount==0)){                  //after first lf
    lfcount=1;                                //tag that the next one isn't first
    p=fhandle.position();                     //find current file position
      }                                       //if we're not already near the end, seek there
    fhandle.find("\n");                       //need to find next lf to cleanly start line    
  if(pktbuf[0]){      //if buffer not empty, send it

void wifiinit(){
  WIFIcmd("AT+RST","ready\r\n",5000);                                               //reset
  WIFIcmd("AT+CWQAP",ok,5000);                                                      //exit any AP's
  WIFIcmd("AT+CWJAP=\""  SSIDNAME  "\",\"" SSIDPWD  "\"","WIFI GOT IP\r\n",10000);  //join AP
  WIFIcmd("ATE0",ok,1000);                                                          //turn echo off
  WIFIcmd("AT+CWMODE=1",ok,2000);                                                   //station mode only
  WIFIcmd("AT+CIPMUX=1",ok,2000);                                                   //MUX on (needed for server)
  WIFIcmd("AT+CIPSERVER=1,80",ok,2000);                                             //server on
  WIFIcmd("AT+CIPSTO=5",ok,2000);                                                   //disconnect after x time if no data

int WIFIcmd(char* c,char* r,long tmout){   //command c (nocrlf needed), returns true if response r received, otherwise times out
  long t;
  while(millis()-t<tmout){          //until timeout
      if(WIFI.find(r)){return 1;}   //response good
  return 0;       //response not found

void WIFIsenddata(char* d,int client){    //send data to client
  WIFI.print(F("AT+CIPSEND="));           //send data
  WIFI.print(client);                     //to client
  WIFI.println(strlen(d));                //data has length
  WIFI.print(d);                          //data
  WIFI.find("SEND OK");
  WIFIpurge();                            //clear incoming buffer

void WIFIpurge(){                         //empty serial buffer

int strmatch(char str1[], char str2[], int n) {   //test for match in first n characters
  int k = -1;                                     //default return success
  for (int i = 0; i < n; i++) {
    if (str1[i] != str2[i]) {
      k = 0;                                      //non match found
  return k;

int strmatch(char str1[], char str2[]) {    //test for absolute match
  int n =strlen(str2);                      //as above, n is length of second string
  if(n!=strlen(str1)){return 0;}            // not the same length, can't be the same
  int k = -1;                               //default return success   
  for (int i = 0; i < n; i++) {
    if (str1[i] != str2[i]) {
      k = 0;                                //non match found
  return k;

int addtobuffer(char buf[], int bufsize, char str[]){      //add str to end of buf, limited by bufsize
  int p=0;
  int k=strlen(buf);
  while((k+p<bufsize-1)&&str[p]){                          //while there's room
    buf[p+k]=str[p];                                       //add character
  buf[p+k]=0;                                              //terminate array
  return p;                                                //number of characters added

int addtobuffer(char buf[], int bufsize, char str){       //add char to end of buf, limited by bufsize
  int k=strlen(buf);
  if(k<bufsize-1){                                        //if there's room for one more
    buf[k]=str;                                           //add it
    return 1;                                             //1 character added
  return 0;

int addtobuffer(char buf[], int bufsize, long n){      //add n as string to end of buf, limited by bufsize, longs will work with ints, uns ints, longs
  char str[15]="";                                     //temporary buffer for number
  if(n<0){str[0]='-';str[1]=0;n=-n;}                   //leading negative sign
  int lzero=0;
  long d=1000000000L;                                  //decade divider
  long j;
  while(d>0){                                          //for all digits
    j=(n/d)%10;                                        //find digit
    if(j){lzero=1;}                                    //non zero character found
    if(lzero||(d==1)){                                 //always show units and any after non-zero
      str[strlen(str)]=j+'0';                          //add a digit
    d=d/10;                                            //next one
  int p=0;
  int k=strlen(buf);
  buf[p+k]=0;                                           //terminate array
  return p;                                             //number of characters added