Example: meteorological station

In this example we will develop an IoT project consisting on a meteorological station connected to an Oficloud gateway by means of LoRa technology. We will take advantage of the Node JS open source repository of IoT libraries for this purpose.
NOTE: This example is exclusively for educational purposes. The goal is not to produce a functional meteorological station but to learn how to integrate Oficloud with any similar meteorological station and how to integrate LoRa technology on any Oficloud project.

The materials needed are:

  • An Oficloud gateway based on a Raspberry Pi model 3 B (or B+). See instructions.
  • A DHT22-compatible temperature and humidity sensor.
  • A FC-37 rain sensor.
  • An Arduino UNO board.
  • Some resistances.
  • A protoboard.
  • 2 E32-TTL-100 LoRa transceptors.
  • 2 antennas for the E32-TTL-100 modules
  • A CP2102 USB-to-TTL serial converter
  • Female-to-female, male-to-male and male-to-female cable connectors.

You also need to choose between one of these options:

  • Configure an SSH connection to the gateway.
  • Configure a remote connection (VNC, etc) to the gateway.
  • Connect the gateway to a screen using an HDMI cable.

STEPS

1. Connect rain sensor to the Arduino One
Connect the sensor to the Arduino One. Follow instructions at this link.

2. Connect LoRa module to Arduino One
Connect the LoRa module to the Arduino One. Follow instructions at this link.

3. Connect DHT22 to Arduino One
Connect the DHT22 temperature & humidity sensor to the Arduino One. Follow instructions at this link.

4. Create Arduino project
Create an Arduino project with the Arduino IDE. Use this code for the project:

/**
 * Oficloud Meteo Station example with LoRa transceiver
  * @author Oficloud SL (www.oficloud.com)
 * @date 1 February 2019
*/


/*
 
 All the resources for this project:
 http://randomnerdtutorials.com/
 
*/

#include "DHT.h"

// Uncomment whatever type you're using!
//#define DHTTYPE DHT11   // DHT 11
#define DHTTYPE DHT22   // DHT 22  (AM2302), AM2321
//#define DHTTYPE DHT21   // DHT 21 (AM2301)
 
// Connect pin 1 (on the left) of the sensor to +5V
// NOTE: If using a board with 3.3V logic like an Arduino Due connect pin 1
// to 3.3V instead of 5V!
// Connect pin 2 of the sensor to whatever your DHTPIN is
// Connect pin 4 (on the right) of the sensor to GROUND
// Connect a 10K resistor from pin 2 (data) to pin 1 (power) of the sensor
 
const int DHTPin = 5;     // what digital pin we're connected to
 
DHT dht(DHTPin, DHTTYPE);

int rainPin = A1;
int greenLED = 6;
int redLED = 7;
// you can adjust the threshold value
int thresholdValue = 500;

bool g_is_wet=false;


/**
 * E32-TTL-100 Transceiver Interface
 *
 * @author Bob Chen (bob-0505@gotmail.com)
 * @date 1 November 2017
 * https://github.com/Bob0505/E32-TTL-100
 */
#include <SoftwareSerial.h<
#include <JeeLib.h<

#include "E32-TTL-100.h"


#undef Device_A

ISR(WDT_vect) { Sleepy::watchdogEvent(); } // Setup the watchdog


/*
 need series a 4.7k Ohm resistor between .
 UNO/NANO(5V mode)                E32-TTL-100
    *--------*                      *------*
    | D7     | <------------------< | M0   |
    | D8     | <------------------< | M1   |
    | A0     | <------------------< | AUX  |
    | D10(Rx)| <---< 4.7k Ohm <---< | Tx   |
    | D11(Tx)| <---< 4.7k Ohm <---< | Rx   |
    *--------*                      *------*
*/
#define M0_PIN  7
#define M1_PIN  8
#define AUX_PIN A0
#define SOFT_RX 10
#define SOFT_TX 11
#define LED_BUTTON 2



SoftwareSerial softSerial(SOFT_RX, SOFT_TX);  // RX, TX

//=== AUX ===========================================+
bool AUX_HL;
bool ReadAUX()
{
  int val = analogRead(AUX_PIN);

  if(val<50)
  {
    AUX_HL = LOW;
  }else {
    AUX_HL = HIGH;
  }

  return AUX_HL;
}

//return default status
RET_STATUS WaitAUX_H()
{
  RET_STATUS STATUS = RET_SUCCESS;

  uint8_t cnt = 0;
  uint8_t data_buf[100], data_len;

  while((ReadAUX()==LOW) && (cnt++<TIME_OUT_CNT))
  {
    Serial.print(".");
    delay(100);
  }

  if(cnt==0)
  {
  }
  else if(cnt<=TIME_OUT_CNT)
  {
    STATUS = RET_TIMEOUT;
    Serial.println(" TimeOut");
  }
  else
  {
    Serial.println("");
  }

  return STATUS;
}
//=== AUX ===========================================-
//=== Mode Select ===================================+
bool chkModeSame(MODE_TYPE mode)
{
  static MODE_TYPE pre_mode = MODE_INIT;

  if(pre_mode == mode)
  {
    //Serial.print("SwitchMode: (no need to switch) ");  Serial.println(mode, HEX);
    return true;
  }
  else
  {
    Serial.print("SwitchMode: from ");  Serial.print(pre_mode, HEX);  Serial.print(" to ");  Serial.println(mode, HEX);
    pre_mode = mode;
    return false;
  }
}

void SwitchMode(MODE_TYPE mode)
{
  if(!chkModeSame(mode))
  {
    WaitAUX_H();

    switch (mode)
    {
      case MODE_0_NORMAL:
        // Mode 0 | normal operation
        digitalWrite(M0_PIN, LOW);
        digitalWrite(M1_PIN, LOW);
        break;
      case MODE_1_WAKE_UP:
        digitalWrite(M0_PIN, HIGH);
        digitalWrite(M1_PIN, LOW);
        break;
      case MODE_2_POWER_SAVIN:
        digitalWrite(M0_PIN, LOW);
        digitalWrite(M1_PIN, HIGH);
        break;
      case MODE_3_SLEEP:
        // Mode 3 | Setting operation
        digitalWrite(M0_PIN, HIGH);
        digitalWrite(M1_PIN, HIGH);
        break;
      default:
        return ;
    }

    WaitAUX_H();
    delay(10);
  }
}
//=== Mode Select ===================================-
//=== Basic cmd =====================================+
void cleanUARTBuf()
{
  bool IsNull = true;

  while (softSerial.available())
  {
    IsNull = false;

    softSerial.read();
  }
}

void triple_cmd(SLEEP_MODE_CMD_TYPE Tcmd)
{
  uint8_t CMD[3] = {Tcmd, Tcmd, Tcmd};
  softSerial.write(CMD, 3);
  delay(50);  //need ti check
}

RET_STATUS Module_info(uint8_t* pReadbuf, uint8_t buf_len)
{
  RET_STATUS STATUS = RET_SUCCESS;
  uint8_t Readcnt, idx;

  Readcnt = softSerial.available();
  //Serial.print("softSerial.available(): ");  Serial.print(Readcnt);  Serial.println(" bytes.");
  if (Readcnt == buf_len)
  {
    for(idx=0;idx<buf_len;idx++)
    {
      *(pReadbuf+idx) = softSerial.read();
      Serial.print(" 0x");
      Serial.print(0xFF & *(pReadbuf+idx), HEX);    // print as an ASCII-encoded hexadecimal
    } Serial.println("");
  }
  else
  {
    STATUS = RET_DATA_SIZE_NOT_MATCH;
    Serial.print("  RET_DATA_SIZE_NOT_MATCH - Readcnt: ");  Serial.println(Readcnt);
    cleanUARTBuf();
  }

  return STATUS;
}
//=== Basic cmd =====================================-
//=== Sleep mode cmd ================================+
RET_STATUS Write_CFG_PDS(struct CFGstruct* pCFG)
{
  softSerial.write((uint8_t *)pCFG, 6);

  WaitAUX_H();
  delay(1200);  //need ti check

  return RET_SUCCESS;
}

RET_STATUS Read_CFG(struct CFGstruct* pCFG)
{
  RET_STATUS STATUS = RET_SUCCESS;

  //1. read UART buffer.
  cleanUARTBuf();

  //2. send CMD
  triple_cmd(R_CFG);

  //3. Receive configure
  STATUS = Module_info((uint8_t *)pCFG, sizeof(CFGstruct));
  if(STATUS == RET_SUCCESS)
  {
  Serial.print("  HEAD:     ");  Serial.println(pCFG-<HEAD, HEX);
  Serial.print("  ADDH:     ");  Serial.println(pCFG-<ADDH, HEX);
  Serial.print("  ADDL:     ");  Serial.println(pCFG-<ADDL, HEX);

  Serial.print("  CHAN:     ");  Serial.println(pCFG-<CHAN, HEX);
  }

  return STATUS;
}

RET_STATUS Read_module_version(struct MVerstruct* MVer)
{
  RET_STATUS STATUS = RET_SUCCESS;

  //1. read UART buffer.
  cleanUARTBuf();

  //2. send CMD
  triple_cmd(R_MODULE_VERSION);

  //3. Receive configure
  STATUS = Module_info((uint8_t *)MVer, sizeof(MVerstruct));
  if(STATUS == RET_SUCCESS)
  {
    Serial.print("  HEAD:     0x");  Serial.println(MVer-<HEAD, HEX);
    Serial.print("  Model:    0x");  Serial.println(MVer-<Model, HEX);
    Serial.print("  Version:  0x");  Serial.println(MVer-<Version, HEX);
    Serial.print("  features: 0x");  Serial.println(MVer-<features, HEX);
  }

  return RET_SUCCESS;
}

void Reset_module()
{
  triple_cmd(W_RESET_MODULE);

  WaitAUX_H();
  delay(1000);
}

RET_STATUS SleepModeCmd(uint8_t CMD, void* pBuff)
{
  RET_STATUS STATUS = RET_SUCCESS;

  Serial.print("SleepModeCmd: 0x");  Serial.println(CMD, HEX);
  WaitAUX_H();

  SwitchMode(MODE_3_SLEEP);

  switch (CMD)
  {
    case W_CFG_PWR_DWN_SAVE:
      STATUS = Write_CFG_PDS((struct CFGstruct* )pBuff);
      break;
    case R_CFG:
      STATUS = Read_CFG((struct CFGstruct* )pBuff);
      break;
    case W_CFG_PWR_DWN_LOSE:

      break;
    case R_MODULE_VERSION:
      Read_module_version((struct MVerstruct* )pBuff);
      break;
    case W_RESET_MODULE:
      Reset_module();
      break;

    default:
      return RET_INVALID_PARAM;
  }

  WaitAUX_H();
  return STATUS;
}
//=== Sleep mode cmd ================================-

RET_STATUS SettingModule(struct CFGstruct *pCFG)
{
  RET_STATUS STATUS = RET_SUCCESS;

#ifdef Device_A
  pCFG-<ADDH = DEVICE_A_ADDR_H;
  pCFG-<ADDL = DEVICE_A_ADDR_L;
#else
  pCFG-<ADDH = DEVICE_B_ADDR_H;
  pCFG-<ADDL = DEVICE_B_ADDR_L;
#endif

  pCFG-<OPTION_bits.trsm_mode =TRSM_FP_MODE;
  pCFG-<OPTION_bits.tsmt_pwr = TSMT_PWR_10DB;

  STATUS = SleepModeCmd(W_CFG_PWR_DWN_SAVE, (void* )pCFG);

  SleepModeCmd(W_RESET_MODULE, NULL);

  STATUS = SleepModeCmd(R_CFG, (void* )pCFG);

  return STATUS;
}

RET_STATUS ReceiveMsg(uint8_t *pdatabuf, uint8_t *data_len)
{

  RET_STATUS STATUS = RET_SUCCESS;
  uint8_t idx;

  SwitchMode(MODE_0_NORMAL);
  *data_len = softSerial.available();

  if (*data_len < 0)
  {
    Serial.print("ReceiveMsg: ");  Serial.print(*data_len);  Serial.println(" bytes.");

    for(idx=0;idx<*data_len;idx++)
      *(pdatabuf+idx) = softSerial.read();

    for(idx=0;idx<*data_len;idx++)
    {
      Serial.print(" 0x");
      Serial.print(0xFF & *(pdatabuf+idx), HEX);    // print as an ASCII-encoded hexadecimal
    } Serial.println("");
  }
  else
  {
    STATUS = RET_NOT_IMPLEMENT;
  }

  return STATUS;
}

RET_STATUS SendMsg(float temp,float hum)
{
  RET_STATUS STATUS = RET_SUCCESS;

  SwitchMode(MODE_0_NORMAL);

  if(ReadAUX()!=HIGH)
  {
    return RET_NOT_IMPLEMENT;
  }
  delay(10);
  if(ReadAUX()!=HIGH)
  {
    return RET_NOT_IMPLEMENT;
  }

  union cvt {
    float val;
    uint8_t b[4];
  } temp_bytes,hum_bytes;

  temp_bytes.val=temp;
  hum_bytes.val=hum;

  /*temp_bytes.b[0]=0x11;
  temp_bytes.b[1]=0x12;
  temp_bytes.b[2]=0x13;
  temp_bytes.b[3]=0x14;*/

  //TRSM_FP_MODE
  //Send format : ADDH ADDL CHAN DATA_0 DATA_1 DATA_2 ...
#ifdef Device_A
  uint8_t SendBuf[4] = { DEVICE_B_ADDR_H, DEVICE_B_ADDR_L, 0x17, random(0x00, 0x80)}; //for A
  softSerial.write(SendBuf, 4);
#else
  //uint8_t SendBuf[4] = { DEVICE_A_ADDR_H, DEVICE_A_ADDR_L, 0x17, random(0x81, 0xFF)}; //for B
  //uint8_t SendBuf[8] = { 0xff, 0xff, 17, g_is_wet, temp_bytes.b};  //for B
  //uint8_t SendBuf[8] = { 0xff, 0xff, 17, temp_bytes.b};  //for B
  uint8_t SendBuf[12] = { 0xff, 0xff, 17, g_is_wet,temp_bytes.b[0], temp_bytes.b[1], temp_bytes.b[2], temp_bytes.b[3], 
    hum_bytes.b[0], hum_bytes.b[1], hum_bytes.b[2], hum_bytes.b[3]}; //for A
  softSerial.write(SendBuf, 12);
#endif
  

  return STATUS;
}

bool jjled=LOW;
void blinkLED()
{
  static bool LedStatus = LOW;

  digitalWrite(LED_BUILTIN, LedStatus);
  LedStatus = !LedStatus;

  if(jjled==LOW)
    jjled=HIGH;
  else
    jjled=LOW;
  digitalWrite(greenLED, jjled);
  
}

void setup(){
  pinMode(rainPin, INPUT);
  pinMode(greenLED, OUTPUT);
  pinMode(redLED, OUTPUT);
  digitalWrite(greenLED, HIGH);
  digitalWrite(redLED, HIGH);
  

  RET_STATUS STATUS = RET_SUCCESS;
  struct CFGstruct CFG;
  struct MVerstruct MVer;

  pinMode(M0_PIN, OUTPUT);
  pinMode(M1_PIN, OUTPUT);
  pinMode(AUX_PIN, INPUT);
  pinMode(LED_BUILTIN, OUTPUT);

  softSerial.begin(9600);
  Serial.begin(9600);

  dht.begin();

  #ifdef Device_A
  Serial.println("[10-A] ");
#else
  Serial.println("[10-B] ");
#endif

  STATUS = SleepModeCmd(R_CFG, (void* )&CFG);
  STATUS = SettingModule(&CFG);

  STATUS = SleepModeCmd(R_MODULE_VERSION, (void* )&MVer);

  // Mode 0 | normal operation
  SwitchMode(MODE_0_NORMAL);

  //self-check initialization.
  WaitAUX_H();
  delay(10);

  
  
  if(STATUS == RET_SUCCESS)
    Serial.println("Setup init OK!!");
}



void loop() {
  // read the input on analog pin 0:
  int sensorValue = analogRead(rainPin);
  Serial.print(sensorValue);

  float h = dht.readHumidity();
  float t = dht.readTemperature();
  if (isnan(h) || isnan(t)) {
      Serial.println("Failed to read from DHT sensor!");
      return;
  }
  Serial.print("Humidity: ");
   Serial.print(h);
   Serial.print(" %\t");
   Serial.print("Temperature: ");
   Serial.print(t);
   Serial.print(" *C ");
  
  if(sensorValue < thresholdValue){
    Serial.println(" - It's wet");
    g_is_wet=true;
    //digitalWrite(greenLED, LOW);
    //digitalWrite(redLED, HIGH);
  }
  else {
    Serial.println(" - It's dry");
    g_is_wet=false;
    //digitalWrite(greenLED, HIGH);
    //digitalWrite(redLED, LOW);
  }

  if(SendMsg(t,h)==RET_SUCCESS)
  {
    //blinkLED();
    //Serial.println(" Blink2");
  }
  delay(10000);
  //Sleepy::loseSomeTime(10000);
}                                      
                            
This code will send through the LoRa device a packet every 5 minutes containing date, temperature, humidity and rain state.

5.Connect LoRa module to gateway
Connect the E32-TTL-100 LoRa module to the Raspberry Pi that makes the function of gateway.
From information found here and the manual we can see that we need to configure it by means of two input pins (M0 and M1). We will use pins 11 and 13 at the RaspBerry Pi 3B pinout as output pins.
The rest of the ports correspond to a standard serial connection. We will use the CP2102 USB-to-TTL serial converter to connect it to a USB port.
You can see here the result:

6. Download base project
Create a folder for your Node JS project and download the oficloud-base-project.
$ git clone https://github.com/juanjo75es/oficloud-sdk-base . 

7. Edit package configuration file
Edit package.json so that it looks like this .

{
  "dependencies": {
    "@trust/webcrypto": "^0.9.2",
    "aes-js": "^3.1.1",
    "argon2": "^0.19.3",
    "atob": "^2.1.2",
    "blob": "0.0.4",
    "btoa": "^1.2.1",
    "crypto": "^1.0.1",
    "dateformat": "^3.0.3",
    "form-data": "^2.3.2",
    "fs": "0.0.1-security",
    "get-random-values": "^1.2.0",
    "http": "0.0.0",
    "line-reader": "^0.4.0",
    "node-dht-sensor": "0.0.34",
    "node-forge": "^0.7.6",
    "querystring": "^0.2.0",
    "rpi-gpio": "^2.1.2",
    "rsa-pem-to-jwk": "^1.1.3",
    "secrets.js": "^0.1.8",
    "serialport": "^7.1.0",
    "sha1": "^1.1.1",
    "streamifier": "^0.1.1",
    "xmlhttprequest": "^1.8.0"
  }
}            
                            

8.Install libraries
Install Node JS auxiliar libraries with:
$ npm install

9. Edit Node JS script
Now edit main.js so that it configures the E32 TTL 100 device and reads messages received by it. We will configure it to read from channel 1 as we configured it in the Arduino project.

var oficloud = require('./oficloud');
var SerialPort = require('serialport');
var gpio = require('rpi-gpio')
var gpiop = gpio.promise;
var dateFormat = require('dateformat');

function setMode(mode)
{
    var m0=1;
    var m1=1;

    switch(mode)
    {
        case 0://normal
            m0=0;
            m1=0;
        break;
        case 1://wake-up
            m0=0;
            m1=1;
        break;
        case 2://power-saving
            m0=1;
            m1=0;
        break;
        case 3://sleep
            m0=1;
            m1=1;
        break;
    }
    try{
        gpiop.write(11, m0)
        gpiop.write(13, m1)
    }
    catch(err)
    {
        console.log('Error: ', err.toString())
    }


}


function init(ondone)
{
    gpiop.setup(11, gpio.DIR_OUT)
        .then(() => {
            gpiop.setup(13, gpio.DIR_OUT)
            .then(() => {
                ondone();
            })
            .catch((err) => {
                console.log('Error: ', err.toString())
            })    
        })
        .catch((err) => {
            console.log('Error: ', err.toString())
        })
}

var serialPort = new SerialPort("/dev/ttyUSB0", {
    baudRate: 9600,
    parser: new SerialPort.parsers.Readline("\n")
});


oficloud.login('myuser@gmail.com','MYPASSWORD',function(res){
    console.log("login res: "+res.res);
    
    if(res.e==-1)
        return;
    

	function onmsg(msg)
	{
		//Process received messages here
	}

	oficloud.open_channel('p',onmsg,function(err){

        if(typeof err !="undefined")
        {
            console.log("open channel error: "+err);
            return;
        }


        //Do your stuff here once you are logged in and joined a channel
        //(...)
        init(function (){
            setMode(3);
            var buf = new Buffer.from([ 0xC0, 0x05, 0x01, 0x1A, 17, 44 ]);
            //var buf = new Buffer([ 0xC3, 0xC3,0xC3 ]);
            serialPort.write(buf);
            setMode(0);
        });

	})
});


function data2float(data,pos)
{
  var d=data.slice(pos,pos+4);
  var res=new Float32Array(new Uint8Array(d).buffer)[0];
  return res;
}

var g_data=[];
var g_data_length=0;

serialPort.on('data', function(data){
    console.log(data);
    console.log("size: "+data.length);
    for(var i=0;i<data.length;i++)
    {
        g_data[g_data_length]=data[i];
        g_data_length++;
        if(g_data_length==9)
        {
          try{
                var temperature=data2float(g_data,1);
                var humidity=data2float(g_data,5);
                var state="dry";
                if(g_data[0])
                    state="wet";
                var today0 = new Date();
                var today=dateFormat(today0, "yyyy-mm-dd H:MM:ss");
                var s="{\"date\":\""+today+"\",\"state\":\""+state+"\",\"temperature\":"+temperature.toFixed(1)+",\"humidity\":"+humidity.toFixed(1)+"}";
                console.log(s);
                oficloud.send_post('p',s,function(){
                    
                })                    
          }
          catch(e){console.log(e.toString())}
        }
    }
});
                            

10.Done
That's all. Execute it with:
$ sudo node main.js
Your program will post a message to the 'MYCHANNEL' channel every 5 minutes reporting temperature, humidity and rain state.

You can read that data from any authorized node connected to the channel. For example, you can build an HTML dashboard like the one in this tutorial or import the data into a ThingsBoard dashboard like in this other tutorial.

You can also make some serial data processing easily with our SDK so that your gateway will make some statistical analysis, providing hourly, dayly, mensual an yearly statistics for every measure. You can find instructions here.