Friday, July 13, 2012

DIY Remote Temperature Logging Software



I bread boarded all the parts together using the default pin numbers mentioned in each part's documentation and started investing time in getting the software working together.

Once I get a schematic editor working - I'll get the schematic here. Sorry for any delay

The software and Libraries that it took to make it all work.

RF24 - This is used to drive the radios. The PingPair_Sleepy example sketch is the basis of the project.  Getting two Arduinos working on the same computer is a bit of a challenge.  It helps that with this sketch the two Arduino are running the same code.  Who is talking and who is listening is controlled by pin7.  If it is high - you are the talker and if pin7 is at ground - that Arduino is listening, a nice touch. The pin7 is tested and routes the program flow to the proper code to talk or listen. It helps that you actually run a jumper from pin7 to VCC+. It gets somewhat iffy if it is just left floating and assumed to be high.

I got stuck at first by changing the code to send an array of floats over the radio.  I got confused by declaring the array length and actually accessing its members.  I originally used ht[1] as the two value array.  I needed to have declared it as ht[2] and then reference it as ht[0], and ht[1].  Even though it worked in the sketch declared as ht[1] - when it came to sending the values to the radio using radio.send (&ht,sizeof(ht)) the size of returns the declared array size in bytes which is  4 for 1 member array of float and sent only one value of the array.  The compiler must have stuck a little extra space in memory that allowed a second value in a 1 character array.  Watch out for this.

DHT22 - Adafruit does such a nice job with their libraries and examples.  This was used to get the temperature and humidity from the DHT22 sensor to send back and forth.
Very straight forward - not any problems with library at all.

NueWire's Seriot - Currently I am using this application to send the data to ThingSpeak for display.  It was a bit of a challenge to get it set up and having it send all three values I wanted to graph.  It listens to the serial port and I rotate between the three different values sent 15 seconds apart.  Thing Speak will not allow updates closer than 15 seconds apart.


The Thing Speak Setup is


The operative part of the Seriot config file is


add key="tsSensor-1" value="APIKEY,field1,OutsideTempF"
add key="tsSensor-2" value="APIKEY,field2,OutsideHumidity"
add key="tsSensor-3" value="APIKEY,field3,OutsideTempC"

The data is sent from the sketch as:

###BOD
OutsideTempF,88.52
###EOD


How it all hooks together is:

  • Seriot matches up the first string in the csv line with its matching value in the config file (case counts)
  • Seriot then constructs a call to ThinkSpeak using the field# on the matching line in the file - evidently it only sends one value each call.  I had to put a 15sec wait in the sketch and then rotate through the 3 values to get them all posted using the same APIKEY.
Eventually I will have an Ethernet shield doing this, but the breadboard and the parts on the expansion shield doesn't give enough clearance to plug one in.  I was impatient to get something working so I went this route while I still had a change to move wires around.

Current Arduino Sketch
------------------------------------------------------


#include <SPI.h>
#include <avr/sleep.h>
#include <avr/power.h>
#include "nRF24L01.h"
#include "RF24.h"
#include "printf.h"
#include "DHT.h"

//DHT specific declarations
#define DHTPIN 2     // what pin we're connected to
#define DHTTYPE DHT22   // DHT 22  (AM2302)
DHT dht(DHTPIN, DHTTYPE);

//store the vales to transmit 0= humidity, 1=temp
float ht[2];

//pins used by the nRF24L01 radio chip module
RF24 radio(9,10);

// sets the role of this unit in hardware.  Connect to GND to be the 'pong' listener
// Connect to 5v to be the ping talker
const int role_pin = 7;

// Radio pipe addresses for the 2 nodes to communicate.
const uint64_t pipes[2] = { 0xF0F0F0F0E1LL, 0xF0F0F0F0D2LL };
typedef enum { role_ping_out = 1, role_pong_back } role_e;
const char* role_friendly_name[] = { "invalid", "Ping out", "Pong back"};

// The role of the current running sketch
role_e role;

// Sleep declarations
typedef enum { wdt_16ms = 0, wdt_32ms, wdt_64ms, wdt_128ms, wdt_250ms, wdt_500ms, wdt_1s, wdt_2s, wdt_4s, wdt_8s } wdt_prescalar_e;
void setup_watchdog(uint8_t prescalar);
void do_sleep(void);
const short sleep_cycles_per_transmission = 4;
volatile short sleep_cycles_remaining = sleep_cycles_per_transmission;
  int counter=0;
void setup(void)
{
   //used to rotate through the three data values sent to ThingSpeak
   counter=0;
  // set up the role pin
  pinMode(role_pin, INPUT);
  digitalWrite(role_pin,HIGH);
  delay(20); // Just to get a solid reading on the role pin
  // read the address pin, establish our role
  if ( digitalRead(role_pin) )
    role = role_ping_out;
  else
    role = role_pong_back;

  Serial.begin(9600);
  //initialize the DHT22 library
  dht.begin();
  printf_begin();
  printf("\n\rRF24/examples/pingpair_sleepy/\n\r");
  printf("ROLE: %s\n\r",role_friendly_name[role]);
  // Only the ping out role sleeps.  Wake up every 4s to send a ping
  if ( role == role_ping_out )
    setup_watchdog(wdt_1s);
  // Setup and configure rf radio
  radio.begin();
  // Open pipes to other nodes for communication
  if ( role == role_ping_out )
  {
    radio.openWritingPipe(pipes[0]);
    radio.openReadingPipe(1,pipes[1]);
  }
  else
  {
    radio.openWritingPipe(pipes[1]);
    radio.openReadingPipe(1,pipes[0]);
  }
  // Start listening
  radio.startListening();
  // Dump the configuration of the rf unit for debugging
  //radio.printDetails();
}

void loop(void)
{
  // Ping out role.  Repeatedly send the current time
  if (role == role_ping_out)
  {
    //get the data from the dht22
    printtemp();
    // First, stop listening so we can talk.
    radio.stopListening();
    // Take the time, and send it.  This will block until complete
    printf("Now sending ");
    Serial.print("sizeog(ht) ");
    Serial.println(sizeof(ht));
    radio.write( &ht, sizeof(ht) );
    // Now, continue listening
    radio.startListening();
    // Wait here until we get a response, or timeout (250ms)
    unsigned long started_waiting_at = millis();
    bool timeout = false;
    while ( ! radio.available() && ! timeout )
      if (millis() - started_waiting_at > 250 )
        timeout = true;
    // Describe the results
    if ( timeout )
    {
      printf("response timed out.\n\r");
    }else{
      // Grab the response, compare, and send to debugging spew
        // Spew it
      radio.read( &ht, sizeof(ht) );
      printf("Got response \r\n");
      Serial.println (ht[1]);
    }
    // Shut down the system
    // Experiment with some delay here to see if it has an effect
    delay(500);
    // Power down the radio.  Note that the radio will get powered back up
    // on the next write() call.
    radio.powerDown();
    // Sleep the MCU.  The watchdog timer will awaken in a short while, and
    // continue execution here.
    while( sleep_cycles_remaining )
      do_sleep();

 sleep_cycles_remaining = sleep_cycles_per_transmission;
  }
  // Pong back role.  Receive each packet, dump it out, and send it back
  if ( role == role_pong_back )
  {
    // if there is data ready
    if ( radio.available() )
    {
      // Dump the payloads until we've gotten everything
      bool done = false;
      while (!done)
      {
        // Fetch the payload, and see if this was the last one.
        done = radio.read( &ht, sizeof(ht) );
        // Spew it.
        //printf("Got payload %lu @ %lu...",got_time,millis());
        //printf("Got payload ...");
//if (isnan(ht[0]) || isnan(ht[1])) {
if (isnan(ht[0]))
        {
          Serial.println("Failed to read from DHT");
        } else {
//convert the temperature to F
float f=(ht[1] * 9.0 / 5.0) + 32.0;
          if (counter==0){
          Serial.println("###BOD");
          Serial.print("OutsideTempF,");
          Serial.println(f);
          Serial.println("###EOD");
          }
          else if(counter==1){
          Serial.println("###BOD");
          Serial.print("OutsideHumidity,");
          Serial.println(ht[0]);
          Serial.println("###EOD");
          }else if (counter==2){
          Serial.println("###BOD");
          Serial.print("OutsideTempC,");
          Serial.println(ht[1]);
          Serial.println("###EOD");
        }
          if(++counter>2) {
          counter=0;
        }
        }//endif isnan
      }//endif radio.available()

      // First, stop listening so we can talk
      radio.stopListening();
      // Send the final one back.
      radio.write( &ht, sizeof(ht) );
      //printf("Sent response.\n\r");
      // Now, resume listening so we catch the next packets.
          delay(15000);//need to put the delay in the ping out code block to sleep longer (later)
      radio.startListening();
    }
  }
}

//gets the data from the DHT22 sensor and sends it to the serial port (only on the sender)
void printtemp()
{
  float h = dht.readHumidity();
  float t = dht.readTemperature();

  ht[0]=h;
  ht[1]=t;
  // check if returns are valid, if they are NaN (not a number) then something went wrong!
  if (isnan(t) || isnan(h)) {
    Serial.println("Failed to read from DHT");
  } else {
    Serial.print("Humidity: ");
    Serial.print(ht[0]);
    Serial.print(" %\t");
    Serial.print("Temperature: ");
    Serial.print(ht[1]);
    Serial.print(" *C\t");
    t = (t * 9.0 / 5.0) + 32.0;
    Serial.print("Temperature: ");
    Serial.print(t);
    Serial.println(" *F");
 
  }
}
// Sleep helpers
// 0=16ms, 1=32ms,2=64ms,3=125ms,4=250ms,5=500ms
// 6=1 sec,7=2 sec, 8=4 sec, 9= 8sec
void setup_watchdog(uint8_t prescalar)
{
  prescalar = min(9,prescalar);
  uint8_t wdtcsr = prescalar & 7;
  if ( prescalar & 8 )
    wdtcsr |= _BV(WDP3);

  MCUSR &= ~_BV(WDRF);
  WDTCSR = _BV(WDCE) | _BV(WDE);
  WDTCSR = _BV(WDCE) | wdtcsr | _BV(WDIE);
}

ISR(WDT_vect)
{
  --sleep_cycles_remaining;
}

void do_sleep(void)
{
  set_sleep_mode(SLEEP_MODE_PWR_DOWN); // sleep mode is set here
  sleep_enable();
  sleep_mode();                        // System sleeps here
  sleep_disable();                     // System continues execution here when watchdog timed out
}

// vim:ai:cin:sts=2 sw=2 ft=cpp