Talk2 Whisper node & ESP8266 base station code

I have my basic whisper code ready and working, I used part of the talk2 library code from their bitbucket repository and changed it to my needs.

It still needs some refactoring and tweaking battery saving functionality, for instance I would like to:

  • Have the sensor nodes sleep for 60 minutes, before they re-read the distance (the maximum sleeptime is 8 seconds, but they can go to sleep again immediately to save batterylife.
  • Only read voltage every 6 hours or so, reading the voltage every hour will switch a mosfet to high for a moment, which will cost some batterylife.

As mentioned in my previous post the ESP8266 will receive serial messages, and send them to the internet.
I already posted most of the esp8266 code on github, that code is without the serial connection part, I will post a basic serial receiving code here, you can merge it with your own code, or take the code from my github and combine the 2 to receive a serial message on the ESP8266 and send it to a webserver.

I use the $ character on the esp to catch a message, and the # to catch the end of the message, this way I know when the message is complete and I can send the data to my webserver for further parsing and saving.

 

This code is’nt completely optimized, for instance in my project I send the amount of times the node needs to go to sleep as a reply from the “base station”, and I removed any delays to save energy, but I hope it helps you get on the way with sending and receiving data.

Starter code ESP8266 Serial connection:

int i, messageLength;
void setup() {
  // Start serial
  Serial.begin(115200);
}

void loop() {
  //Listen for begin of message
  if(Serial.read() == '$')
  {
    int Message[100];
    for(i=1; i<100; i++)
    {
      delay(1);
      Message[i] = Serial.read();
      if(Message[i] == '#')
      {
        break;
      }
    }
    //We know the exact length of the message
    messageLength = i;
    Serial.println("I just received this?");

    for(i =0; i< messageLength; i++)
    {
      Serial.print(char(Message[i]));
    }

    //Here you can process your message, post it to a webserver for instance
  }
}

 

My whisper server code:

/*
  Blink
  Turns on an LED on for one second, then off for one second, repeatedly.

  Most Arduinos have an on-board LED you can control. On the Uno and
  Leonardo, it is attached to digital pin 13. If you're unsure what
  pin the on-board LED is connected to on your Arduino model, check
  the documentation at http://www.arduino.cc

  This example code is in the public domain.

  modified 8 May 2014
  by Scott Fitzgerald
*/
#include <RH_RF69.h>
#include <T2WhisperNode.h>

// RFM69 Radio
#define RADIO_FREQUENCY 868.0
#define RADIO_TX_POWER 13
#define RADIO_ENCRYPTION_KEY { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F }

RH_RF69 rf69;

uint8_t radioBuf[(T2_MESSAGE_HEADERS_LEN + T2_MESSAGE_MAX_DATA_LEN)];

// T2 Message
T2Message myMsg;
#define clientAddr 0x99
#define routerAddr 0x0A
#define testAddr 0x1F

uint16_t batVoltage = 0;


// the setup function runs once when you press reset or power the board
void setup() {
  Serial.begin(115200);
  if (!rf69.init())
    Serial.println("init failed");

  rf69.setModemConfig(RH_RF69::FSK_Rb125Fd125);
  // Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, +13dbM
  // No encryption
  
  if (!rf69.setFrequency(RADIO_FREQUENCY))
    Serial.println("setFrequency failed");
  // If you are using a high power RF69, you *must* set a Tx power in the
  // range 14 to 20 like this:
  // rf69.setTxPower(14);
  // The encryption key has to be the same as the one in the server
  uint8_t myRadioEncryptionKey[] = RADIO_ENCRYPTION_KEY;
  rf69.setEncryptionKey(myRadioEncryptionKey);

  rf69.setTxPower(RADIO_TX_POWER);
}

// the loop function runs over and over again forever
void loop() {
  /* RFM69 Receive */
  uint8_t radioBufLen = sizeof(radioBuf);
  if (rf69.recv(radioBuf, &radioBufLen))
  {
    myMsg.setSerializedMessage(radioBuf, radioBufLen);
    // Uncomment bellow to print every received message, just be careful as
    // delays here can cause messages to be lost.
    //myMsg.printMessage();

    // We can perform some message filtering based on it's headers
    if (myMsg.dst == testAddr)
    {
      String result;
      int distance;

      switch (myMsg.sdx)
      {
        case 0x64: //Get sensor distance measurement and voltage print a serial message for  the ESP

          distance = myMsg.data[0];
          batVoltage = myMsg.data[1] << 8;
          batVoltage |= myMsg.data[2];

          //Print result to serial, for the ESP8266 to read and post.
          Serial.println(String('$') + String(myMsg.src) + String(':') + String(distance) + String(':') + String(batVoltage) + String('#'));

          break;

        default: // Can define an operation for everything else
          // Do something
          Serial.println(F("Unexpected message received: "));
          myMsg.printMessage();

          break;
      }
    }

    //This obviously needs to be removed or changed for any use, maybe send an OK, so the node knows message has been received..
    uint8_t data[] = "And hello back to you";
    rf69.send(data, sizeof(data));
    rf69.waitPacketSent();
  }
}

Whisper Client sourcecode:

/*
  Blink
  Turns on an LED on for one second, then off for one second, repeatedly.

  Most Arduinos have an on-board LED you can control. On the Uno and
  Leonardo, it is attached to digital pin 13. If you're unsure what
  pin the on-board LED is connected to on your Arduino model, check
  the documentation at http://www.arduino.cc

  This example code is in the public domain.

  modified 8 May 2014
  by Scott Fitzgerald
*/

#include <RH_RF69.h>
#include <LowPower.h>
#include <T2WhisperNode.h>
#include "sensor.h"
// RFM69 Radio
#define RADIO_FREQUENCY 868.0
#define RADIO_TX_POWER 13
#define RADIO_ENCRYPTION_KEY { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F }

RH_RF69 rf69;

uint8_t radioBuf[(T2_MESSAGE_HEADERS_LEN + T2_MESSAGE_MAX_DATA_LEN)];

/* Flash */
#define T2_WPN_FLASH_SPI_CS     8     // Chip Select
#define T2_WPN_FLASH_SPI_MOSI   11
#define T2_WPN_FLASH_SPI_MISO   12
#define T2_WPN_FLASH_SPI_SCK    13

T2Flash myFlash;

uint8_t uniqueId[8];

// T2 Message
T2Message myMsg;
#define routerAddr 0x0A
#define serverAddr 0x1F

int sleepCount= 0;

void setup() {

  //Setup Ultrasonic Sensor
  setupSensor();

  //For now I use part of the SPI Flash uniqueid to identify my nodes
  myFlash.init(T2_WPN_FLASH_SPI_CS);
  memcpy(uniqueId, myFlash.uniqueId, 8);
  Serial.begin(115200);

  if (!rf69.init())
    Serial.println("init failed");

  rf69.setModemConfig(RH_RF69::FSK_Rb125Fd125);
  // Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, +13dbM
  // No encryption
  if (!rf69.setFrequency(RADIO_FREQUENCY))
    Serial.println("setFrequency failed");

  // The encryption key has to be the same as the one in the server
  uint8_t myRadioEncryptionKey[] = RADIO_ENCRYPTION_KEY;
  rf69.setEncryptionKey(myRadioEncryptionKey);
  rf69.setTxPower(RADIO_TX_POWER);

}

// the loop function runs over and over again forever
void loop() {

  if (sleepCount!= 0 && (sleepCount < 3))
  {
    sleepCount++;
    LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
    return;
  }
  uint16_t voltage = 0;
  int dist = getDistance();
  uint8_t radioBufLen = 0;

  //Read battery voltage, and add it to data
  voltage = T2Utils::readVoltage(T2_WPN_VBAT_VOLTAGE, T2_WPN_VBAT_CONTROL);

  // Send Node ID on start-up as Event
  myMsg.cmd = 0x00;
  myMsg.idx = 0x06;
  myMsg.sdx = 0x64;
  myMsg.src = uniqueId[5];
  myMsg.dst = serverAddr;

  //reset myMsg.data just to be sure..
  memset(myMsg.data, 0, sizeof(myMsg.data));

  //Send distance and current battery voltage vbat
  String tstring = String(dist) + ":" + String(voltage);

  myMsg.data[0] = dist;
  myMsg.data[1] = voltage >> 8;
  myMsg.data[2] = voltage;
  myMsg.len = 3; //Update length
  radioBufLen = 0;

  myMsg.getSerializedMessage(radioBuf, &radioBufLen);
  rf69.send(radioBuf, radioBufLen);
  rf69.waitPacketSent();

  // Now wait for a reply
  uint8_t buf[RH_RF69_MAX_MESSAGE_LEN];
  uint8_t len = sizeof(buf);
  if (rf69.waitAvailableTimeout(500))
  {
    // Should be a reply message for us now
    if (rf69.recv(buf, &len))
    {
      Serial.print("got reply: ");
      Serial.println((char*)buf);
    }
    else
    {
      Serial.println("recv failed");
    }
  }
  else
  {
    Serial.println("No reply, is rf69_server running?");

  }
  delay(1);
  rf69.sleep();
  sleepCount = 1;
  LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);

}

sensor.h code for the whisper_client:

#ifndef SENSOR_H
#define SENSOR_H


/* US100 ultrasonic sensor */
#define TRIGGER 17
#define ECHO    18


/*
 * Setup ultrasonic distance thingy
 */
void setupSensor() {
  Serial.print("setting up sensor");
    pinMode(TRIGGER, OUTPUT);
    pinMode(ECHO, INPUT);
}

/*
 * Measure distance, and return
 */
long getDistance() {
  long duration, distance;
  digitalWrite(TRIGGER, LOW);  
  delayMicroseconds(2); 
  
  digitalWrite(TRIGGER, HIGH);
  delayMicroseconds(10); 
  
  digitalWrite(TRIGGER, LOW);
  duration = pulseIn(ECHO, HIGH);
  distance = (duration/2) / 29.1;

  return distance;
}

#endif

 

16 Comments

  1. Hi Thomas, the code looks great! You might be able to save some extra microamps if you add the following line after you copy the SPI Flash ID:

    myFlash.powerDown(); //You should save at least 10uA

    I also see that you wait up to 500ms to receive a message back. This can be a pitfall in case your base is off-line or it takes while to reply the message. You have a few options here:

    1. Reduce the time-out;

    2. Slow down the CPU while waiting for a message, by basically doing:
    #include
    (…)
    clock_prescale_set(clock_div_16); //Slow down the MCU
    if (rf69.waitAvailableTimeout(32)) //used to be 500, but as the MCU runs at 1MHz now, 1 milli represents 16 millis on this section of the code
    {
    clock_prescale_set(clock_div_1); //Restore full speed
    (…)
    }
    clock_prescale_set(clock_div_1); //Restore full speed

    Note that at 1MHz the MCU should consume less than 1mA, where it normally consumes over 8mA at 16MHz. This technique can be done at any point in your code, just keep in mind that functions using the hardcoded “F_CPU” will need to be compensated – mostly timing functions. You might be able to reduce the whole sketch speed depending on your application (http://www.atmel.com/Images/Atmel-42735-8-bit-AVR-Microcontroller-ATmega328-328P_datasheet.pdf page 379)

    3. Put the Radio into receive mode, remove the “rf69.waitAvailableTimeout(500)” and set the MCU sleep for 500ms, so if the Radio receives something, it’ll generate an interrupt, which will wake up the MCU and it’ll be ready to receive the message from the buffer. Otherwise, the MCU will wake up and continue the code normally.

    Finally, removing all “Serial.print” when you have your application deployed will also save some extra energy.

    Cheers,
    Mike M.

    1. Hi there Mike,

      thanks for all the extra information, In the currently running battery test I already removed all println and reduced the delays greatly. I will just run this test out, I’m guessing my current code will keep it up for about 6 days with reading sensor data every minute, since my project will only read every hour I should be able to get 60x that amount (or let’s say 50x for margin).

      I will add the current source code to the post when I have time this week.

      Thanks for some more info about spi flash, reducing CPU speed and waking up on receive!

  2. Hi Thomas, the code looks great! You might be able to save some extra microamps if you add the following line after you copy the SPI Flash ID:

    myFlash.powerDown(); //You should save at least 10uA

    I also see that you wait up to 500ms to receive a message back. This can be a pitfall in case your base is off-line or it takes while to reply the message. You have a few options here:

    1. Reduce the time-out;

    2. Slow down the CPU while waiting for a message, by basically doing:
    #include
    (…)
    clock_prescale_set(clock_div_16); //Slow down the MCU
    if (rf69.waitAvailableTimeout(32)) //used to be 500, but as the MCU runs at 1MHz now, 1 milli represents 16 millis on this section of the code
    {
    clock_prescale_set(clock_div_1); //Restore full speed
    (…)
    }
    clock_prescale_set(clock_div_1); //Restore full speed

    Note that at 1MHz the MCU should consume less than 1mA, where it normally consumes over 8mA at 16MHz. This technique can be done at any point in your code, just keep in mind that functions using the hardcoded “F_CPU” will need to be compensated – mostly timing functions. You might be able to reduce the whole sketch speed depending on your application (http://www.atmel.com/Images/Atmel-42735-8-bit-AVR-Microcontroller-ATmega328-328P_datasheet.pdf page 379)

    3. Put the Radio into receive mode, remove the “rf69.waitAvailableTimeout(500)” and set the MCU sleep for 500ms, so if the Radio receives something, it’ll generate an interrupt, which will wake up the MCU and it’ll be ready to receive the message from the buffer. Otherwise, the MCU will wake up and continue the code normally.

    Finally, removing all “Serial.print” when you have your application deployed will also save some extra energy.

    Cheers,
    Mike M.

    1. Hi there Mike,

      thanks for all the extra information, In the currently running battery test I already removed all println and reduced the delays greatly. I will just run this test out, I’m guessing my current code will keep it up for about 6 days with reading sensor data every minute, since my project will only read every hour I should be able to get 60x that amount (or let’s say 50x for margin).

      I will add the current source code to the post when I have time this week.

      Thanks for some more info about spi flash, reducing CPU speed and waking up on receive!

  3. Thanks for sharing your adventures! I’m thinking about a similar
    setup myself, but did not start so far – Your code will be very useful 🙂

    I saw that the LowPower lib can only power down for 8secs. Since you are measuring the temperature anyway, wouldn’t the RTC Kit be the logical addon? The DS3231M’s integrated temperature sensor can be used to measure the temperature. I might be wrong, but then you could use the RTC to power down for a custom amount of time (eg 1 full minute) using RTC alarms. I guess that would extend the battery life even further (?)

    1. Hi there, thanks for your comment 🙂

      I’m measuring the distance of an object, not the temperature at the moment.

      I added a piece of code which instantly puts the whisper node into sleep mode again, if a certain time hasn’t been reached, in this example it will go to sleep 10 times in a row:

      
        if (lastRun != 0 && (millis() - lastRun <= 10))
        {
          delay(1);
          LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
          return;
        }
      
      //This will run after the 10x sleep
      
      
      //Turn radio on, send sensor data etc..
      
      lastRun = millis();
      //Turn radio off, go to sleep
      

      for the millisecond it will be on every 8 seconds the power consumption will be insignificant, since the radio etc will still be off.

      I guess the RTC will provide some benefits, but it also requires me to solder it on, and will use some extra power (although not significant).

      I will be doing a power usage test this week, and add a thingspeak graph on my blog, so I can keep track of the CR2032 battery draining, I’m hoping to get at least 8 weeks out of it, the measurements will be done every 2 hours for this test.

      1. Oh, my bad. I too the image in the previous post as a hint, one node was named “temperature sensor”.
        But nonetheless I’m looking forward to your follow-up posts 🙂

        1. I have removed the delay(1); in the code, and removed the millis() counter, it’s better to just compare the amount of sleeps with a variable and go into sleep again, it will only take microseconds, instead of 1 millisecond, which will further reduce drain.

          Edit: I use the base station to send the amount of sleepcycles the node needs to make before resending new sensor data, it will log maximum of once per hour, this should give me at least 12 months of power on a single battery according to Talk2, hope to setup my batterytest tomorrow, otherwise beginning of next week.

    2. Hi there Mike, thanks for all the extra information, In the currently running battery test I already removed all println and reduced the delays greatly. I will just run this test out, I’m guessing my current code will keep it up for about 6 days with reading sensor data every minute, since my project will only read every hour I should be able to get 60x that amount (or let’s say 50x for margin).

      I will add the current source code to the post when I have time 🙂

      Thanks for some more info about spi flash, reducing CPU speed and waking up on receive!

  4. Thanks for sharing your adventures! I’m thinking about a similar
    setup myself, but did not start so far – Your code will be very useful 🙂

    I saw that the LowPower lib can only power down for 8secs. Since you are measuring the temperature anyway, wouldn’t the RTC Kit be the logical addon? The DS3231M’s integrated temperature sensor can be used to measure the temperature. I might be wrong, but then you could use the RTC to power down for a custom amount of time (eg 1 full minute) using RTC alarms. I guess that would extend the battery life even further (?)

    1. Hi there, thanks for your comment 🙂

      I’m measuring the distance of an object, not the temperature at the moment.

      I added a piece of code which instantly puts the whisper node into sleep mode again, if a certain time hasn’t been reached, in this example it will go to sleep 10 times in a row:

      
        if (lastRun != 0 && (millis() - lastRun <= 10))
        {
          delay(1);
          LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
          return;
        }
      
      //This will run after the 10x sleep
      
      
      //Turn radio on, send sensor data etc..
      
      lastRun = millis();
      //Turn radio off, go to sleep
      

      for the millisecond it will be on every 8 seconds the power consumption will be insignificant, since the radio etc will still be off.

      I guess the RTC will provide some benefits, but it also requires me to solder it on, and will use some extra power (although not significant).

      I will be doing a power usage test this week, and add a thingspeak graph on my blog, so I can keep track of the CR2032 battery draining, I’m hoping to get at least 8 weeks out of it, the measurements will be done every 2 hours for this test.

      1. Oh, my bad. I too the image in the previous post as a hint, one node was named “temperature sensor”.
        But nonetheless I’m looking forward to your follow-up posts 🙂

        1. I have removed the delay(1); in the code, and removed the millis() counter, it’s better to just compare the amount of sleeps with a variable and go into sleep again, it will only take microseconds, instead of 1 millisecond, which will further reduce drain.

          Edit: I use the base station to send the amount of sleepcycles the node needs to make before resending new sensor data, it will log maximum of once per hour, this should give me at least 12 months of power on a single battery according to Talk2, hope to setup my batterytest tomorrow, otherwise beginning of next week.

    2. Hi there Mike, thanks for all the extra information, In the currently running battery test I already removed all println and reduced the delays greatly. I will just run this test out, I’m guessing my current code will keep it up for about 6 days with reading sensor data every minute, since my project will only read every hour I should be able to get 60x that amount (or let’s say 50x for margin).

      I will add the current source code to the post when I have time 🙂

      Thanks for some more info about spi flash, reducing CPU speed and waking up on receive!

Leave a Reply

Your email address will not be published.