Tweeting House Plant

Thanks to https://developer.mbed.org/teams/Ece-4180-team-who/wiki/Using-Adafruit-ESP8266-Huzzah for example code this is project builds off of. ESP configuration must be run first as detailed there.

Description

This project makes use of a moisture sensor, the ESP8266 wifi module, and an Mbed to monitor the soil moisture of a house plant via Twitter. The moisture sensor's out is connected to the Mbed. The Mbed then composes a tweet using that reading. The ESP8266 acts as a client by sending HTTP POSTs to Twitter through Thingspeak, a service which simplifies the process of authentication with Twitter. (Essentially it acts as a middleman to allow POSTs to be sent to it with only a simple API key rather than having to sort through using OAuth.) The ESP8266 also acts as a server which can serve up a webpage. The webpage in this project provides two options: (1) who to send the sensor's readings to, and (2) at which frequency from a given set. In this project, Lua commands are sent directly to the ESP through serial. (An FTDI programmer could be used to directly program the ESP alternatively. It even has an AnalogIn, so really this could be done with the ESP alone..!) Because the arrival of settings from the webpage is asynchronous, a GPIO pin on the ESP is set high which causes an Interrupt to occur on the mbed which then runs code to handle the new setting data.

http://i.imgur.com/pQ9P2cD.jpg?1

Tape on the bottom left is to hold the barrel jack for powering the ESP in place... whatever works.

Hookup Guide

mbedESP8266moisture sensor
GNDGNDGND
VoutVcc
p20out
p8#5
p27TX
p28RX
V+ (5VDC)

p20 is AnalogIn. p8 is used for Interrupts from the ESP.

A Thingspeak account and a Twitter acount are needed. Once the Thingspeak account is created, go to https://thingspeak.com/apps/thingtweets to hookup your Twitter account with Thingspeak. Then, paste in your API key in the place specified in the code (search "APIKEY" to find it.)

Video Demo

Here I'm basically showing you that 1) it tweets and 2) that the behavior can be affected by the webpage (in this case the user it tweets at is changed.)

Code

#include "mbed.h"
#include "stdlib.h"

Serial pc(USBTX, USBRX);
Serial esp(p28, p27); // tx, rx

DigitalOut reset(p26);
DigitalOut led1(LED1);
DigitalOut led4(LED4);

AnalogIn moisture(p20);

InterruptIn espData(p8);

Timer timer;
Ticker tweeter;
Ticker myclock;
 
int  count,ended,timeout;
char buf[2024];

// Buffer to hold info coming FROM the ESP.
char espBuf[2024];
int espBufCount = 0;

// Buffer used to store Lua commands sent to the ESP.
char snd[1024];

int seconds = 0;

int tweetPeriod = 60;
char twitterHandleBuf[64];
char twitterHandle[16] = "\0"; // Used to check if a twitter handle has been supplied or not.

int gotTwitterHandle = 0; // Used to check enter the block that handles the Interrupt.
 
char ssid[32] = "...";     // enter WiFi router ssid inside the quotes
char pwd [32] = "..."; // enter WiFi router password inside the quotes

// This is how many volts my moisture sensor
// outputs when it is submerged in water.
float maxValue = 1.88f;
 
void SendCMD(), getreply(), ESPconfig(), ESPsetbaudrate(), sendTweet(), getTime(char* time), setUpServer(), getDataFromEsp();

// dev meaning the mbed
void dev_recv()
{
    led1 = !led1;
    while(esp.readable()) {
        pc.putc(esp.getc());
    }
}
 
void pc_recv()
{
    led4 = !led4;
    while(pc.readable()) {
        esp.putc(pc.getc());
    }
}        

void increment_time() {
   seconds++;
}
 
int main()
{
    reset=0; //hardware reset for 8266
    pc.baud(9600);  // set what you want here depending on your terminal program speed
    set_time(0);
    pc.printf("\f\n\r-------------ESP8266 Hardware Reset-------------\n\r");
    wait(0.5);
    reset=1;
    timeout=2;
    getreply();
 
    esp.baud(9600);   // change this to the new ESP8266 baudrate if it is changed at any time.
 
    //ESPsetbaudrate();   //******************  include this routine to set a different ESP8266 baudrate  ******************
 
    //ESPconfig();        //******************  include Config to set the ESP8266 configuration  ***********************
    
    pc.printf("\n---------- Get IP's ----------\r\n");
    strcpy(snd, "print(wifi.sta.getip())\r\n");
    SendCMD();
    timeout=3;
    getreply();
    pc.printf(buf);
    
    setUpServer();
 
    // Using a ticker to send tweets repeatedly.
    tweeter.attach(&sendTweet, tweetPeriod);
    
    // Increments clock (seconds since last reset).
    myclock.attach(&increment_time, 1);
 
    pc.attach(&pc_recv, Serial::RxIrq);
    esp.attach(&dev_recv, Serial::RxIrq);
    espData.rise(&getDataFromEsp);
    
    while(1) {
        sleep();
        
        if (gotTwitterHandle) {
            gotTwitterHandle = 0;
            strcpy(twitterHandleBuf, buf);
            
            int i, j = 0;
            // Extracting twitter handle and tweet frequency.
            // 22 is the index of the start of the twitter handle in the buffer.
            // 15 is the max number of characters in a twitter handle
            // 1 is for the ';' used to delimit the twitter handle and the tweet frequency. 
            for (i = 22; i < 22+15+1+1; i++) {
                if (twitterHandleBuf[i] != ';') {
                    twitterHandle[j] = twitterHandleBuf[i];
                } else {
                    twitterHandle[j] = '\0';
                    break;
                }
                j++;
            }
            // Converting the ASCII char to an integer -- numbers have ASCII codes
            // that are 48 larger than the number represented by the code.
            int tweetFrequency = twitterHandleBuf[i+1] - 48;
            pc.printf("%s\r\n", twitterHandle);
            pc.printf("%d\r\n", tweetFrequency);
            
            // Maps the tweetFrequency integer to an actual number of seconds.
            switch(tweetFrequency) {
                case 1: // Tweet every minute
                    tweetPeriod = 60;
                    break;
                case 2: // Tweet every 5 minutes
                    tweetPeriod = 60*5;
                    break;
                case 3: // Tweet every 15 minutes
                    tweetPeriod = 60*15;
                    break;
                case 4: // Tweet every 30 minutes
                    tweetPeriod = 60*30;
                    break;
                default:
                    tweetPeriod = 60;
            }
        }
    }
 
}
 
// Sets new ESP8266 baurate, change the esp.baud(xxxxx) to match your new setting once this has been executed
void ESPsetbaudrate()
{
    strcpy(snd, "AT+CIOBAUD=115200\r\n");   // change the numeric value to the required baudrate
    SendCMD();
}

void getDataFromEsp() {
    strcpy(snd, "print(twitterHandle)\r\n");
    SendCMD();
    timeout=3;
    getreply();
    gotTwitterHandle = 1;
}

void setUpServer() {
    
    // Setting up GPIO pin which should be connected to a DigitalIn on the Mbed.
    // When high, it means the ESP is busy.
    strcpy(snd, "pin = 1\r\n");
    SendCMD();
    wait(1);
    
    strcpy(snd, "gpio.mode(pin, gpio.OUTPUT)\r\n");
    SendCMD();
    wait(1);
    
    strcpy(snd, "srv = net.createServer(net.TCP)\r\n");
    SendCMD();
    wait(1);
    
    strcpy(snd, "srv:listen(80, function(conn1)\r\n");
    SendCMD();
    wait(1);
    
    strcpy(snd, "conn1:on(\"receive\",function(conn1, payload)\r\n");
    SendCMD();
    wait(1);
        
        // If the server receives a GET request, return the web page.
        strcpy(snd, "if string.find(payload, \"GET\") ~= nil then\r\n");
        SendCMD();
        wait(1);
        
            strcpy(snd, "conn1:send(\"<!DOCTYPE html>\")\r\n");
            SendCMD();
            wait(1);
            
            strcpy(snd, "conn1:send(\"<html>\")\r\n");
            SendCMD();
            wait(1);
            
            strcpy(snd, "conn1:send(\"<form method='POST' autocomplete='off'>\")\r\n");
            SendCMD();
            wait(1);
            
            strcpy(snd, "conn1:send(\"<b>Send tweets to: @</b> <input name='twitterHandle' value=''><br>\")\r\n");
            SendCMD();
            wait(1);
            
            strcpy(snd, "conn1:send(\"<b>Send tweet every:</b>  <input type='radio' name='freq' value='1' checked='checked'> Minute \")\r\n");
            SendCMD();
            wait(1);
            
            strcpy(snd, "conn1:send(\"<input type='radio' name='freq' value='2'> 5 Minutes \")\r\n");
            SendCMD();
            wait(1);
            
            strcpy(snd, "conn1:send(\"<input type='radio' name='freq' value='3'> 15 Minutes \")\r\n");
            SendCMD();
            wait(1);
            
            strcpy(snd, "conn1:send(\"<input type='radio' name='freq' value='4'> 30 Minutes \")\r\n");
            SendCMD();
            wait(1);
            
            strcpy(snd, "conn1:send(\"<input type='submit' value='Save settings'>\")\r\n");
            SendCMD();
            wait(1);
            
            strcpy(snd, "conn1:send(\"</form>\")\r\n");
            SendCMD();
            wait(1);
            
            strcpy(snd, "conn1:send(\"</html>\")\r\n");
            SendCMD();
            wait(1);
        
        strcpy(snd, "end\r\n");
        SendCMD();
        wait(1);
        
        strcpy(snd, "if string.find(payload, \"POST\") ~= nil then\r\n");
        SendCMD();
        wait(1);
            
            strcpy(snd, "tweetFreq = \"-1\"\r\n");
            SendCMD();
            wait(1);
            
            strcpy(snd, "twitterHandle = \"\"\r\n");
            SendCMD();
            wait(1);
            
            strcpy(snd, "contentLengthIndex = string.find(payload, \"Content\")\r\n");
            SendCMD();
            wait(1);
            
            // Adds 16 to the index of the start of 'Content-Length: '. This gives the index of content length's value.
            strcpy(snd, "contentLengthIndex = contentLengthIndex + 16\r\n");
            SendCMD();
            wait(1);
            
            // Assumes that content length is > 10 and < 100 (fine for this since twitter handles are <= 15 characters long.)
            strcpy(snd, "contentLength = payload:sub(contentLengthIndex, contentLengthIndex + 2)\r\n");
            SendCMD();
            wait(1);
            
            strcpy(snd, "twitterHandleLength = contentLength - 22\r\n");
            SendCMD();
            wait(1);
        
            strcpy(snd, "if string.find(payload, \"freq=1\") ~= nil then tweetFreq = \"1\" end\r\n");
            SendCMD();
            wait(1);
            
            strcpy(snd, "if string.find(payload, \"freq=2\") ~= nil then tweetFreq = \"2\" end\r\n");
            SendCMD();
            wait(1);
            
            strcpy(snd, "if string.find(payload, \"freq=3\") ~= nil then tweetFreq = \"3\" end\r\n");
            SendCMD();
            wait(1);
            
            strcpy(snd, "if string.find(payload, \"freq=4\") ~= nil then tweetFreq = \"4\" end\r\n");
            SendCMD();
            wait(1);
            
            strcpy(snd, "index = string.find(payload, \"twitterHandle=\")\r\n");
            SendCMD();
            wait(1);         
            
            strcpy(snd, "if index ~= nil then\r\n");
            SendCMD();
            wait(1);
            
                strcpy(snd, "index = index + 14\r\n");
                SendCMD();
                wait(1);
                
                strcpy(snd, "for i = 0, twitterHandleLength do\r\n");
                SendCMD();
                wait(1);
                
                    strcpy(snd, "twitterHandle = twitterHandle .. payload:sub(index,index)\r\n");
                    SendCMD();
                    wait(1);
                    
                    strcpy(snd, "index = index + 1\r\n");
                    SendCMD();
                    wait(1);
                
                strcpy(snd, "end\r\n");
                SendCMD();
                wait(1);
                
                strcpy(snd, "twitterHandle = twitterHandle .. \";\" .. tweetFreq\r\n");
                SendCMD();
                wait(1);
            
            strcpy(snd, "end\r\n");
            SendCMD();
            wait(1); 
            
            // ESP pin is high => trigger InterruptIn on mbed to get the twitterHandle
            strcpy(snd, "gpio.write(pin, gpio.HIGH)\r\n");
            SendCMD();
            wait(1);
            
            // Wait half a second
            strcpy(snd, "tmr.delay(500000)\r\n");
            SendCMD();
            wait(1);
            
            // Set the gpio back to low.
            strcpy(snd, "gpio.write(pin, gpio.LOW)\r\n");
            SendCMD();
            wait(1);
                       
            strcpy(snd, "conn1:send(\"Settings saved!\")\r\n");
            SendCMD();
            wait(1);
        
        strcpy(snd, "end\r\n");
        SendCMD();
        wait(1);
    
    strcpy(snd, "end)\r\n");
    SendCMD();
    wait(1);
    
    strcpy(snd, "conn1:on(\"sent\", function(conn1) conn1:close() end)\r\n");
    SendCMD();
    wait(1);
    
    strcpy(snd, "end)\r\n");
    SendCMD();
    wait(1);
    pc.printf("\r\nDONE");
    
}

void sendTweet() {
        
        pc.printf("%s\r\n", twitterHandle);
        pc.printf("%d\r\n", tweetPeriod);
        
        char message[256];
        float value = moisture * maxValue;
        
        char mainMessage[256];
        sprintf(mainMessage, "Soil moisture reading is %f. Seconds since last reset: %d .", value, seconds);
        
        if (twitterHandle[0] != '\0') {
            // This means there is a twitter handle set.
            // Tweet the message at that user.
            sprintf(message, "@%s %s", twitterHandle, mainMessage);
        } else {      
            // Tweeting at no one.
            strcpy(message, mainMessage);
            pc.printf("%s\r\n", message);
        }
        
        pc.printf("\n---------- Sending tweet ----------\r\n");
        
        strcpy(snd, "conn = net.createConnection(net.TCP, 0)\r\n");
        SendCMD();
        wait(1);
        
        strcpy(snd, "conn:on(\"connection\", function(conn, payload)\r\n");
        SendCMD();
        wait(1);
        
        /* APIKEY : Here, in the part of the url that says api_key=... replace ... with your API key. */
        sprintf(snd, "conn:send(\"POST /apps/thingtweet/1/statuses/update?api_key=...&status=%s HTTP/1.1\\r\\nHost: api.thingspeak.com\\r\\n\\r\\n\")\r\n", message);
        SendCMD();
        wait(1);
        
        strcpy(snd, "end)\r\n");
        SendCMD();
        wait(1);
        
        strcpy(snd, "conn:on(\"sent\", function(conn) conn:close() end)\r\n");
        SendCMD();
        wait(1);
        
        strcpy(snd, "conn:connect(80,\"api.thingspeak.com\")\r\n");
        SendCMD();
        wait(1);
}
 
void SendCMD()
{
    esp.printf("%s", snd);
}
 
void getreply()
{
    memset(buf, '\0', sizeof(buf));
    timer.start();
    ended=0;
    count=0;
    while(!ended) {
        if(esp.readable()) {
            buf[count] = esp.getc();
            count++;
        }
        if(timer.read() > timeout) {
            ended = 1;
            timer.stop();
            timer.reset();
        }
    }
}

//  +++++++++++++++++++++++++++++++++ This is for ESP8266 config only, run this once to set up the ESP8266 +++++++++++++++
void ESPconfig()
{

    wait(5);
    pc.printf("\f---------- Starting ESP Config ----------\r\n\n");
    strcpy(snd,".\r\n.\r\n");
    SendCMD();
    wait(1);
    pc.printf("---------- Reset & get Firmware ----------\r\n");
    strcpy(snd,"node.restart()\r\n");
    SendCMD();
    timeout=5;
    getreply();
    pc.printf(buf);
 
    wait(2);
 
    pc.printf("\n---------- Get Version ----------\r\n");
    strcpy(snd,"print(node.info())\r\n");
    SendCMD();
    timeout=4;
    getreply();
    pc.printf(buf);
 
    wait(3);
 
    // set CWMODE to 1=Station,2=AP,3=BOTH, default mode 1 (Station)
    pc.printf("\n---------- Setting Mode ----------\r\n");
    strcpy(snd, "wifi.setmode(wifi.STATION)\r\n");
    SendCMD();
    timeout=4;
    getreply();
    pc.printf(buf);
 
    wait(2);
 
    pc.printf("\n---------- Listing Access Points ----------\r\n");
    strcpy(snd, "function listap(t)\r\n");
        SendCMD();
        wait(1);
        strcpy(snd, "for k,v in pairs(t) do\r\n");
        SendCMD();
        wait(1);
        strcpy(snd, "print(k..\" : \"..v)\r\n");
        SendCMD();
        wait(1);
        strcpy(snd, "end\r\n");
        SendCMD();
        wait(1);
        strcpy(snd, "end\r\n");
        SendCMD();
        wait(1);
        strcpy(snd, "wifi.sta.getap(listap)\r\n");
        SendCMD();
        wait(1);
        timeout=15;
        getreply();
        pc.printf(buf);
 
    wait(2);
 
    pc.printf("\n---------- Connecting to AP ----------\r\n");
    pc.printf("ssid = %s   pwd = %s\r\n",ssid,pwd);
    strcpy(snd, "wifi.sta.config(\"");
    strcat(snd, ssid);
    strcat(snd, "\",\"");
    strcat(snd, pwd);
    strcat(snd, "\")\r\n");
    SendCMD();
    timeout=10;
    getreply();
    pc.printf(buf);
 
    wait(5);
 
    pc.printf("\n---------- Get IP's ----------\r\n");
    strcpy(snd, "print(wifi.sta.getip())\r\n");
    SendCMD();
    timeout=3;
    getreply();
    pc.printf(buf);
 
    wait(1);
 
    pc.printf("\n---------- Get Connection Status ----------\r\n");
    strcpy(snd, "print(wifi.sta.status())\r\n");
    SendCMD();
    timeout=5;
    getreply();
    pc.printf(buf);
 
    pc.printf("\n\n\n  If you get a valid (non zero) IP, ESP8266 has been set up.\r\n");
    pc.printf("  Run this if you want to reconfig the ESP8266 at any time.\r\n");
    pc.printf("  It saves the SSID and password settings internally\r\n");
    wait(10);
}


Please log in to post comments.