Hackster is hosting Hackster Holidays, Ep. 7: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Friday!Stream Hackster Holidays, Ep. 7 on Friday!
Md Zaidul Alam
Published © GPL3+

Forest Sense: Get the Heartbeat of the Forest

Monitor, sense, predict, and act!

IntermediateFull instructions provided18 hours4,606

Things used in this project

Hardware components

Thinxtra Xkit
Thinxtra devkit Xkit boasts a full suite of features and accessories to empower anyone to set up an IoT solution, even with very little hardware experience. Perfect for start-ups, design houses, universities and schools, the kit has everything you need to hit the ground running using the globally available Sigfox network.
×1
Arduino UNO
Arduino UNO
×1
Flame Detector Sensor Module for Arduino Projects
×1
MQ135 Air Quality Detector Sensor Module for Arduino Projects
×1
Arduino Compatible Soil Moisture Sensor Module
×1
Arduino Compatible PIR Motion Detector Module
×1
Arduino Compatible Sensor Expansion Shield
×1

Software apps and online services

AWS IoT
Amazon Web Services AWS IoT
AWS DynamoDB
Amazon Web Services AWS DynamoDB
AWS SNS
Amazon Web Services AWS SNS
AWS S3
Amazon Web Services AWS S3
AWS ML
Amazon Web Services AWS ML
Eclipse Paho
J2EE

Story

Read more

Schematics

Forest Sense Sensor connection

Thinxra Xkit uses most of the GPIOs of Arduino Uno board. Only a few analogues and one digital pin are not used by Xkit. I have added extra sensors on those pins.

Code

ForestSense Arduino Uno code

C/C++
Thinxra provided some demo codes to use in Xkit. I have modified their demo code to add more sensors and extra logic for sending exact 12-byte payload.
#include <WISOL.h>
#include <Tsensors.h>
#include <Wire.h>
#include <math.h>
#include <SimpleTimer.h>
#include <avr/wdt.h>
#define PIRPIN 4

Isigfox *Isigfox = new WISOL();
Tsensors *tSensors = new Tsensors();
SimpleTimer timer;
int watchdogCounter;
uint8_t buttonCounter;
uint8_t PublicModeSF;
uint8_t stateLED;
uint8_t ledCounter;
const uint8_t buttonPin = A1;
const int redLED = 6;

typedef union{
    float number;
    uint8_t bytes[4];
} FLOATUNION_t;

typedef union{
    uint16_t number;
    uint8_t bytes[2];
} UINT16_t;

typedef union{
    int16_t number;
    uint8_t bytes[2];
} INT16_t;
//-------------The Forest Sense Sensor Codes------
int firIndicator=0;
int flameCheck;
int soilPin = A0;
int motionCounter=0;
bool motionDetected=0;
int isMotion=0;
//---------------------------------------------

void setup() {
  int flagInit;
  
  Wire.begin();
  Wire.setClock(100000);

  Serial.begin(9600);

  // Init watchdog timer
  watchdogSetup();
  watchdogCounter = 0;
  
  // WISOL test
  flagInit = -1;
  while (flagInit == -1) {
  Serial.println(""); // Make a clean restart
  delay(1000);
  PublicModeSF = 0;
  flagInit = Isigfox->initSigfox();
  Isigfox->testComms();
  GetDeviceID();
  //Isigfox->setPublicKey(); // set public key for usage with SNEK
  }
  
  // Init sensors on Thinxtra Module
  tSensors->initSensors();
  tSensors->setReed(reedIR);
  buttonCounter = 0;
  tSensors->setButton(buttonIR);

  // Init LED
  stateLED = 0;
  ledCounter = 0;
//  pinMode(redLED, INPUT);

  // Init timer to send a Sigfox message every 10 minutes
  unsigned long sendInterval = 600000;
  timer.setInterval(sendInterval, timeIR);

  Serial.println(""); // Make a clean start
  delay(1000);
}

void loop() {
//--------The Forest Sense Sensor Codes
   static unsigned long timerValue=millis(); 
   flameCheck=getFlameData();
   if(flameCheck==2)//check if there is a fire
   {
        if(firIndicator==0 && millis() >= timerValue)
        {
          sendFiredata();
          timerValue=millis()+5000;//check after 5 secs again
         
        }
   }else
   {
        firIndicator=0;//if not set it back
   }

   static unsigned long MotiontimerValue=millis();
   motionDetected=getMotion();//getting motion data
   if(motionDetected && isMotion==0 && millis() >= MotiontimerValue)
   {
     motionCounter++;//and adds to motion count
     isMotion=1;
     MotiontimerValue=millis()+5000;//sometime, it can count the same motion for the same animal or bird twice, so wait 5 secs
  
     
   }
   else
   {
     isMotion=0;
   }
 //-------------------------------  
  timer.run();
  wdt_reset();
  watchdogCounter = 0;
}


void sendFiredata()// this is the fire incident data payload. 
{
      const uint8_t payloadSize = 12; //in bytes
    
      uint8_t buf_str[payloadSize];
      UINT16_t flame;
      flame.number=(uint16_t)(3);
    
      buf_str[0] = flame.bytes[0];//emergency data , fireindicator
      buf_str[1] = flame.bytes[1];
      buf_str[2] = 0;
      buf_str[3] = 0;
      buf_str[4] = 0;
      buf_str[5] = 0;
      buf_str[6] = 0;
      buf_str[7] = 0;
      buf_str[8] = 0;
      buf_str[9] = 0;
      buf_str[10] = 0;
      buf_str[11] = 0;
    
      Send_Pload(buf_str, payloadSize);
      firIndicator=1;
     
}


//-------------The breathing forest Codes------

int getFlameData()
{
      const int sensorMin = 0;     // sensor minimum
      const int sensorMax = 150; 
      int sensorReading = analogRead(A3);
       
      uint8_t range;
      if(sensorReading<20)
      {
        range=2;//Its fire in the forest now
        
      }else if (sensorReading <80 && sensorReading>=25)
      {
        range=1;//This can be a flame or just some evening glare
      }
      else
      {
        range=0;// nor fire
      }
      return range;
      
}


bool getMotion()
{
      //---PIR
      bool pir=0; //variable to hold input state
      pir=digitalRead(PIRPIN); 
      
      return pir;
}




//--------------------------
void Send_Sensors(){
  UINT16_t tempt, photo, pressure,airquality,motionCount,moisture;
 

  // Sending a float requires at least 4 bytes
  // In this demo, the measure values (temperature, pressure, sensor) are scaled to ranged from 0-65535.
  // Thus they can be stored in 2 bytes
  tempt.number = (uint16_t) (tSensors->getTemp() * 100);
  Serial.print("Temp: "); Serial.println((float)tempt.number/100);
  pressure.number =(uint16_t) (tSensors->getPressure()/3);
  Serial.print("Pressure: "); Serial.println((float)pressure.number*3);
  photo.number = (uint16_t) (tSensors->getPhoto() * 1000);
  Serial.print("Photo: "); Serial.println((float)photo.number/1000);

//------------Air quality sensor data
  int sensorValueAir = analogRead(A2);
  airquality.number=(uint16_t)(sensorValueAir);
  Serial.print("Air Quality = ");
  Serial.print(sensorValueAir);
  Serial.println();
//---------Motion sensor count data
  motionCount.number=(uint16_t)(motionCounter);
  Serial.print("PIR option: "); 
  Serial.print(motionCounter); 
  Serial.println();
//--------Soil moisture count data
  int moistureVal=analogRead(soilPin);
  moisture.number=(uint16_t)(moistureVal);
  Serial.print("moisture option: "); 
  Serial.print(moistureVal);
  Serial.println();

  const uint8_t payloadSize = 12; //in bytes
  uint8_t buf_str[payloadSize];

  buf_str[0] = tempt.bytes[0];
  buf_str[1] = tempt.bytes[1];
  buf_str[2] = pressure.bytes[0];
  buf_str[3] = pressure.bytes[1];
  buf_str[4] = photo.bytes[0];
  buf_str[5] = photo.bytes[1];
  buf_str[6] = airquality.bytes[0];
  buf_str[7] = airquality.bytes[1];
  buf_str[8] = moisture.bytes[0];
  buf_str[9] = moisture.bytes[1];
  buf_str[10] = motionCount.bytes[0];
  buf_str[11] = motionCount.bytes[1];

  Send_Pload(buf_str, payloadSize);
  motionCounter=0;

}

void reedIR(){
  Serial.println("Reed");
  timer.setTimeout(50, Send_Sensors); // send a Sigfox message after get out IRS
}

void buttonIR(){
  if (buttonCounter==0) {
    timer.setTimeout(500, checkLongPress); // check long click after 0.5s
  }
}

void checkLongPress() {
  buttonCounter++;
  if ((buttonCounter < 4)) {
    if (digitalRead(buttonPin) == 1) {
      Serial.println("Short Press");
      Send_Sensors();
      buttonCounter = 0;
    } else {
      timer.setTimeout(500, checkLongPress); // check long click after 0.5s
    }
  } else {
    Serial.println("Long Press");
    BlinkLED();
    pinMode(redLED, OUTPUT);
    if (PublicModeSF == 0) {
      Serial.println("Set public key");
      Isigfox->setPublicKey();
      PublicModeSF = 1;
  
    } else {
      Serial.println("Set private key");
      Isigfox->setPrivateKey();
      PublicModeSF = 0;
    }
    buttonCounter = 0;
  }
}


void BlinkLED() {
  ledCounter++;
  if (ledCounter<=6) {
    if (stateLED == 0){
      digitalWrite(redLED, HIGH);
      stateLED = 1;
      timer.setTimeout(200, BlinkLED);
    } else {
      digitalWrite(redLED, LOW);
      stateLED = 0;
      timer.setTimeout(200, BlinkLED);
    }
  } else {
    pinMode(redLED, INPUT);
    ledCounter = 0;
  }
  
  
}

void timeIR(){
  Serial.println("Time");
  Send_Sensors();
}

void getDLMsg(){
  recvMsg *RecvMsg;
  int result;

  RecvMsg = (recvMsg *)malloc(sizeof(recvMsg));
  result = Isigfox->getdownlinkMsg(RecvMsg);
  for (int i=0; i<RecvMsg->len; i++){
    Serial.print(RecvMsg->inData[i]);
  }
  Serial.println("");
  free(RecvMsg);
}


void Send_Pload(uint8_t *sendData, const uint8_t len){
  // No downlink message require
  recvMsg *RecvMsg;

  RecvMsg = (recvMsg *)malloc(sizeof(recvMsg));
  Isigfox->sendPayload(sendData, len, 0, RecvMsg);
  for (int i = 0; i < RecvMsg->len; i++) {
    Serial.print(RecvMsg->inData[i]);
  }
  Serial.println("");
  free(RecvMsg);


 
}


void GetDeviceID(){
  recvMsg *RecvMsg;
  const char msg[] = "AT$I=10";

  RecvMsg = (recvMsg *)malloc(sizeof(recvMsg));
  Isigfox->sendMessage(msg, 7, RecvMsg);

  Serial.print("Device ID: ");
  for (int i=0; i<RecvMsg->len; i++){
    Serial.print(RecvMsg->inData[i]);
  }
  Serial.println("");
  free(RecvMsg);
}


void watchdogSetup(void) { // Enable watchdog timer
  cli();  // disable all interrupts
  wdt_reset(); // reset the WDT timer
  
  // Enter Watchdog Configuration mode:
  // IF | IE | P3 | CE | E | P2 | P1 | P0
  WDTCSR |= B00011000;
  WDTCSR = B01110001;
//  WDTCSR |= (1<<WDCE) | (1<<WDE);
//  // Set Watchdog settings:
//   WDTCSR = (1<<WDIE) | (1<<WDE) | (1<<WDP3) | (1<<WDP2) | (1<<WDP1) | (1<<WDP0);
  sei();
}


void watchdog_disable() { // Disable watchdog timer
  cli();  // disable all interrupts
  WDTCSR |= B00011000;
  WDTCSR = B00110001;
  sei();
}


ISR(WDT_vect) // Watchdog timer interrupt.
{

  Serial.print("WD reset: ");
  Serial.println(watchdogCounter);
  watchdogCounter++;
  if (watchdogCounter == 20) { // reset CPU after about 180 s
      // Reset the CPU next time
      // Enable WD reset
      cli();  // disable all interrupts
      WDTCSR |= B00011000;
      WDTCSR = B01111001;
      sei();
      wdt_reset();
  } else if (watchdogCounter < 8) {
    wdt_reset();
  }
}

Forest Sense Data Pipe-line definition

JSON
This is the defination file for the Forest Sense AWS Data Pipeline. This Data pipeline exports forest sense sensor data from DynamoDB table to S3 bucket. AWS Machine Learning Model then uses this data to predict veaibales.
{
  "objects": [
    {
      "period": "1 days",
      "name": "Every 1 day",
      "id": "DefaultSchedule",
      "type": "Schedule",
      "startAt": "FIRST_ACTIVATION_DATE_TIME"
    },
    {
      "bootstrapAction": "s3://#{myDDBRegion}.elasticmapreduce/bootstrap-actions/configure-hadoop, --yarn-key-value,yarn.nodemanager.resource.memory-mb=11520,--yarn-key-value,yarn.scheduler.maximum-allocation-mb=11520,--yarn-key-value,yarn.scheduler.minimum-allocation-mb=1440,--yarn-key-value,yarn.app.mapreduce.am.resource.mb=2880,--mapred-key-value,mapreduce.map.memory.mb=5760,--mapred-key-value,mapreduce.map.java.opts=-Xmx4608M,--mapred-key-value,mapreduce.reduce.memory.mb=2880,--mapred-key-value,mapreduce.reduce.java.opts=-Xmx2304m,--mapred-key-value,mapreduce.map.speculative=false",
      "name": "EmrClusterForBackup",
      "coreInstanceCount": "1",
      "coreInstanceType": "m3.xlarge",
      "amiVersion": "3.9.0",
      "masterInstanceType": "m3.xlarge",
      "id": "EmrClusterForBackup",
      "region": "#{myDDBRegion}",
      "type": "EmrCluster",
      "terminateAfter": "5 Minutes"
    },
    {
      "output": {
        "ref": "S3BackupLocation"
      },
      "input": {
        "ref": "DDBSourceTable"
      },
      "maximumRetries": "2",
      "name": "TableBackupActivity",
      "step": "s3://dynamodb-emr-#{myDDBRegion}/emr-ddb-storage-handler/2.1.0/emr-ddb-2.1.0.jar,org.apache.hadoop.dynamodb.tools.DynamoDbExport,#{output.directoryPath},#{input.tableName},#{input.readThroughputPercent}",
      "id": "TableBackupActivity",
      "runsOn": {
        "ref": "EmrClusterForBackup"
      },
      "type": "EmrActivity",
      "resizeClusterBeforeRunning": "true"
    },
    {
      "readThroughputPercent": "#{myDDBReadThroughputRatio}",
      "name": "DDBSourceTable",
      "id": "DDBSourceTable",
      "type": "DynamoDBDataNode",
      "tableName": "#{myDDBTableName}"
    },
    {
      "directoryPath": "#{myOutputS3Loc}/#{format(@scheduledStartTime, 'YYYY-MM-dd-HH-mm-ss')}",
      "name": "S3BackupLocation",
      "id": "S3BackupLocation",
      "type": "S3DataNode"
    },
    {
      "failureAndRerunMode": "CASCADE",
      "schedule": {
        "ref": "DefaultSchedule"
      },
      "resourceRole": "DataPipelineDefaultResourceRole",
      "role": "DataPipelineDefaultRole",
      "pipelineLogUri": "s3://forest-sense-log/",
      "scheduleType": "cron",
      "name": "Default",
      "id": "Default"
    }
  ],
  "parameters": [
    {
      "description": "Output S3 folder",
      "id": "myOutputS3Loc",
      "type": "AWS::S3::ObjectKey"
    },
    {
      "description": "Source DynamoDB table name",
      "id": "myDDBTableName",
      "type": "String"
    },
    {
      "default": "0.25",
      "watermark": "Enter value between 0.1-1.0",
      "description": "DynamoDB read throughput ratio",
      "id": "myDDBReadThroughputRatio",
      "type": "Double"
    },
    {
      "default": "us-east-1",
      "watermark": "us-east-1",
      "description": "Region of the DynamoDB table",
      "id": "myDDBRegion",
      "type": "String"
    }
  ],
  "values": {
    "myDDBRegion": "ap-southeast-2",
    "myDDBTableName": "forestSense",
    "myDDBReadThroughputRatio": "0.25",
    "myOutputS3Loc": "s3://forest-sense/"
  }
}

Forest Sense Client Codes

The custom built web client for Forest Sense. Codes in AngularJS and used Eclipse Paho MQTT library to subscribe AWS IoT core MQTT broker. Orginal codes from Amazon demo code. Modified for this project.

Credits

Md Zaidul Alam
1 project • 4 followers
Postgraduate student (Part-time) of Carnegie Mellon University in Australia.
Thanks to Thinxtra.

Comments