Monday, July 14, 2014

The sketch in a nut shell

The developing of the sketch has been finished few days ago and can now be considered pretty much complete, so I guess it's time for a review.

The sketch.ino file is an easy 200 lines C++ source code that is supposed to work as a slave. Main purposes are:
  • read the current values of the sensors and provide them to the upper layer;
  • read the current values of the outputs and provide them to the upper layer;
  • get, from the upper layer, the expected status of the outputs and operate to address the request.



First of all we include any needed library in order to interact with the sensors and the serial port to communicate with the pc for logging purpose:

#include <Wire.h>
#include <Process.h>
#include <Console.h>
#include <DHT.h>
#include <RTClib.h>
#include <Adafruit_BMP085.h>

//Definition of the pin connected to the DHT sensor
#define DHTPIN 8    
#define DHTTYPE DHT22 

//Definition of the general purpose led used tfor diagnosys
#define LEDPIN 13

We define few global variables to manage the sensors and to create an array of pin we will use as outputs:

//Definition of the pin connected to the output
const int outputPin[] = {4, 5, 6, 7};

//Definition of the global variables
DHT dht(DHTPIN, DHTTYPE);
RTC_DS1307 rtc;
Adafruit_BMP085 bmp;

Now the "setup" function used to prepare Arduino to run our main loop. During this setup we will:

  1. inform the user that the setup procedure has been started (led on);
  2. initialize the Bridge to share our input/output with the upper layer;
  3. initialize the I2C communication to interact with the Real Time Clock (RTC) and the BMP sensor;
  4. initialize the RTC, BMP and DHT sensors using the functions provided by the related libraries;
  5. initialize the outputs and publish (and log) the current status;
  6. inform the user that the setup procedure has been completed (led off).
1 => Start the procedure:

//Definition of the pin connected to the output
void setup() {
  // Prepare the onboard led to blink once and then stay on 
  // until the setup procedure is complete
  pinMode(LEDPIN, OUTPUT);
  digitalWrite(LEDPIN, HIGH);

2 => Initialize the Bridge:

  // initialize serial communication:
  logBegin();
  Bridge.begin();
  log("You're connected");

3 => Initialize the I2C:

  // initialize I2C
  log("Initializing I2C...");
  Wire.begin();

4 => Initialize the RTC, DHT and BMP:

  // initialize RealTimeClock module
  log("Communicating with RTC...");
  rtc.begin();
  if (! rtc.isrunning()) {
    log("ERROR: RTC does not work correctly.");
    //rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  }

  // initialize DHT sensor
  log("Communicating with DHT...");
  dht.begin();

  // initialize BMP180 module
  log("Communicating with BMP...");
  if (!bmp.begin()) {
  log("ERROR: BMP180 does not work correctly.");
  }

5 => Inizialize output pin and publish the current output status:

  /*
   * Publish current output status through the mailbox
   * "outputResponse" is the key used to inform about 
   * the status of the ouput
   */
  int outputsNumber = sizeof(outputPin) / sizeof(int);
  for(int i=0; i < outputsNumber; i++) {
    pinMode(outputPin[i], OUTPUT);
    digitalWrite(outputPin[i], LOW);
  }
  String _response = "";
  for(int i=0; i < outputsNumber; i++)
    _response += String(digitalRead(outputPin[i]));
  Bridge.put("outputResponse", _response);
  for(int i=0; i < outputsNumber; i++) {
    log("Output Pin "+String(outputPin[i]) + " value " + String(digitalRead(outputPin[i])));
  }

6 => Complete the procedure:

  /*
  // Setup procedure complete
  digitalWrite(LEDPIN, LOW);
}


The main loop is pretty easy too:

  1. inform the user we are starting a new cycle of the main loop;
  2. retrieve the date and time from RTC;
  3. publish the status of the outputs before any change;
  4. change the output status according to the upper layer request;
  5. publish the status of the outputs after the change;
  6. retrieve sensors values
  7. publish timestamp and sensors values
  8. inform the user that the current cycle has been completed;
  9. delay 1 second before starting next cycle.

1 => We are starting a new cycle:

void loop() {
  digitalWrite(LEDPIN, HIGH);

2 => Get date and time of the current cycle:

  // Read timestamp
  DateTime now = rtc.now();
  String _timestamp = formattedDateTime(now);
  log("Timestamp: " + _timestamp);

3 => Publish the current output status (before change):

  /* 
   * Always provide the status of the outputs
   */
  int outputsNumber = sizeof(outputPin) / sizeof(int);
  String _response = "";
  for(int i=0; i < outputsNumber; i++)
    _response += "x";
  Bridge.put("outputResponse", _response);

4 => Get a request to change the output status and execute it:

  /*
   * Check if there is a request to change the
   * status of an output and in case change it.
   * "outputRequest" is the key used to receive 
   * the request to change the status of the outputs.
   * '0' means LOW, anything different from '0' means HIGH
   */
  char* _output;
  _output = (char *) malloc(outputsNumber);
  String key = "outputRequest";
  if(Bridge.get(key.c_str(), _output, outputsNumber) != 0) {
    for(int i=0; i < outputsNumber; i++){
      if (_output[i]=='0')
        digitalWrite(outputPin[i], LOW);
      else
        digitalWrite(outputPin[i], HIGH);
      log("Output Pin "+String(outputPin[i]) + " value " + _output[i]);
    }
  }
  free(_output);

5 => Publish the current output status (after change):

  _response = "";
  for(int i=0; i < outputsNumber; i++)
    _response += String(digitalRead(outputPin[i]));
  Bridge.put("outputResponse", _response);
  log("Output: " + _response); 

6 => Retrieve sensors values. To represent the decimal sensors values, we will use integer as fixed-point number, assuming that the last two digits of the number are the digits after the decimal point '.' (that's why we often we use '100' to scale the sensor value):

  // Read humidity and temperature from dht sensor
  int _humidity = (int)(dht.readHumidity()*100.0);
  int _temperature = (int)(dht.readTemperature()*100.0);
  if (isnan(_humidity) || isnan(_temperature)) {
    log("ERROR: Failed to read from DHT sensor");
  } else {
    log("Data from DHT sensor: "); 
    log(" -> Humidity: " + String(((float)_humidity)/100.0) + "%"); 
    log(" -> Temperature: " + String(((float)_temperature)/100.0) + "*C"); 
  }
  
  // Read pressure and temperature from bmp180 module
  unsigned long _pressure = bmp.readPressure();
  int _temperatureBMP = (int)(bmp.readTemperature()*100.0);
  if (isnan(_pressure) || isnan(_temperatureBMP)) {
    log("Failed to read from BMP180 module");
  } else {
    log("Data from BMP180 module: "); 
    log(" -> Pressure: " + String((float)(_pressure)/100.0) + "Pa"); 
    log(" -> Temperature: " + String(((float)_temperatureBMP)/100.0) + "*C");
  } 

7 => Publish all info (soil_moisture and luminosity not yet implemented):

  /*
   * Make sensors info available to outside
   * using the mailbox
   */
  Bridge.put(String("datetime"), _timestamp);
  Bridge.put(String("timestamp"), _timestamp);
  Bridge.put(String("temperature"), String(_temperature));
  Bridge.put(String("humidity"), String(_humidity));
  Bridge.put(String("pressure"), String(_pressure));
  Bridge.put(String("soil_moisture"), String(""));
  Bridge.put(String("luminosity"), String(""));

8 => Cycle completed:

  // Turn off the led to indicate the cycle has been completed
  digitalWrite(LEDPIN, LOW);

9 => Wait before starting next cycle. To make it easier we wait a fixed time of 1 second before starting a new cycle; later I'm going to change this instruction in order to run ONE cycle EVERY 1 second, that means I will pass to the 'delay()' function a variable value equal to (1000-_elapsedTime) where _elapsedTime will be the amount of milliseconds needed to execute the cycle itself:

  //Wait one second
  delay(1000);
}

To complete the code, following the definition of the functions used.

A function to provide a well formatted datetime string according to the more readable format "MM/DD/YYYY HH:MM:SS":

/*
 * Gets the datetime and return a well formatted string
 * according to the format: MM/DD/YYYY HH:MM:SS
 */
String formattedDateTime(DateTime now){
  String retval = "";
  retval += print2Char(now.month()) + "/";
  retval += print2Char(now.day()) + "/";
  if(now.year()>99) retval += now.year();
  else retval += "20" + now.year();
  retval += " ";
  retval += print2Char(now.hour()) + ":";
  retval += print2Char(now.minute()) + ":";
  retval += print2Char(now.second());
  return retval;
}

A function to provide a 2-digit string to be used in datetime:

/*
 * Gets an integer and returns a 2 digit string
 * in case the integer is less than 10.
 * To be used to format hours, minutes, seconds,
 * month and day in datetime string
 */
String print2Char(int value){
  if(value<10) return "0"+String(value);
  return String(value);  
}

Two log functions to enable and used logging:

void logBegin(){
  if(0) {
    Serial.begin(9600);
    while (!Serial);
  }  
}

void log(String message){
  if(0) Serial.println(message);
}

No comments:

Post a Comment