Please select an option to see content specific to your location and shop online.

  • Free delivery on orders over £60 Free delivery on orders over £60
  • Next day delivery (ordered before 16:00 GMT) Next day delivery (ordered before 16:00 GMT)
  • Super Fast Shipping from £2.99 Super Fast Shipping from £2.99

Project

Building a better IoT Device!

with the help of Arduino, The Things Network, OKdo Cloud and the Otii

Scroll down

So, creating an IoT device? Yes, naturally – I need it!

First, I started by listing my needs:

  • Battery operated – needs to be operational for a long time and without access to external power.
  • LoRaWAN connectivity – for easy usage and no need for my own infrastructure.
  • GPIOs – for flexibility.
  • I2C – a lot of sensors use this bus.
  • ADC – also for flexibility.
  • Module-based – easier to use as well as a faster result and the bonus: If needed in “production” It’s way more likely that the finished product can be certified or be certified faster than if I did everything myself.

 

So, I started out with the Arduino MKR series, since it such a nice size boards and the Arduino brand stands for quality. Furthermore, it has such a great community so you can find the help you need.

 

I went for the MKR 1300, which uses the LoRaWAN network. A nice thing about the MKR series is that they all share accessories and to some degree that same code, so if I later would like to use different network infrastructure, I just choose another MKR model – NICE!

 

What are LoRa and LoRaWAN?

LoRa Alliance is an open and non-profit association, established to create a Low Power Wide Area Network (LPWAN) for the Internet of Things. The Alliance also certifies modules to ensure interoperability.

LoRa is the physical layer and LoRaWAN is the protocol for it.

 

 

LoRa is license free (Works on the ISM bands), it’s cheap to deploy and maintain.

I decided to add my own LoRaWAN Gateway, in order to be in control of all part of my setup and to support The Things Network infrastructure

 

Why The Things Network?

I chose it because they are building an industrial grade LoRaWAN network covering most of the world – free of charge.

It’s free so why setup your own gateway? Well, it’s free because it’s powered by the community – by all the gateways the users deploy – I want to support it too. And as a bonus, I for sure have good coverage here.

Since I will be using The Things Networks LoRaWAN, I choose this Gateway: The Things Indoor Gateway (TTIG-868, the EU model). There is a bunch of gateways, some more DIY than others. Why I choose this one, because it’s a finished product, works out of the box is small in size and the thing, price – it’s cheap.

If you want to create your private environment you can do so too (I have added a link for it) However for this demo, I will be using the community environment.

 

Let’s get cranking 🙂

Setting up the Gateway:

It’s a tiny little box containing a complete 8 channel LoRaWAN gateway – perfect for this project.

It can be powered either thru USB-C or via the build-in 230V power supply.

In order to get started, we need to activate the gateway:

Press and hold the reset button for 5 sec (until the LED blinks rapidly green/red a couple of times.
Hold the setup button for 10 sec (until the LED blinks red).
The gateway now acts as a Wi-Fi Access point (SSID: MINIHUB-xxxxxx where the xxxxxx is the last 6 digits of the gateway ID), password for the Wi-Fi Access point is written on the back of the gateway (next to the other information).

Connect to the Gateway Wi-Fi Access point (192.168.4.1) via a web browser and access the configuration page.

Select the Wi-Fi Access point that the gateway needs to use and type in the password for it.

Select the” Save and Reboot” button.

The Gateway LED will blink green while it connects to the Wi-Fi Access point (Usually it takes a few secs).
The Gateway LED will now blink green/red, while it connects to The Things Network (Also usually a few secs).

IF everything works ok, the Gateway LED will now be a steady green.

Setting up the The Things Network back-end:

The Gateway ID is the EUI found on the back of the gateway (next to the MAC add and Wi-Fi AP password).

For the EUI to be in the right format you need to add FFFE after the first 6 characters so in this case 58A0CB800D31 become 58A0CBFFFE800D31

In order to use the TTIG-868 we need to click ”I’m using the legacy packet forwarder”.

OK now the gateway is setup and if it’s working you can see the status under the gateways:

We also need to be able to use our data to something, so let’s setup a cloud solution and for this, I have chosen OKdo’ Cloud offer:

Click on the “Add Application” button (under the “Applications” section.

Fill in the form (Unique ID and a description) leave the rest as is.

Under the “Applications” page choose our newly created application (Here a0a0c2c2a0i8h7b1).

Go to “Integrations” and click on “Get started by creating one”.

Choose the first one (AllThingsTalk). Create a Process ID and for Access Key use “default key (device)”.

 

Select device tab.

Click “Get started by registering one”.

Create a Device ID.

Click “Register”

 

Your device is now ready in the LoRaWAN system, now let’s continue to setup the system:

This means adding a cloud solution to the The Things Network.

Setting up the OKdo Cloud:

Open a new tap and head over to the OKdo Cloud and if you do not already have an account then create it (OKdo provide one free playground with up to ten devices free of charge 😊).

  • Create or choose a “Ground”.
  • Click “+ NEW DEVICE”, from the OKdo catalogue choose “lora”.

  • Click on “Your own LoRa device” tile.

  • Choose “The Things Network” tile.

Fill in the information (All found on the setup we did in The Things Network backend (you can find it under the device overview tab)).

 

  • Now is the time to setup device assets (depending on what your device will provide).

For this test, I will use a simple temperature and humidity sensor (DHT22) and a feedback LED.

I will create 3 assets: 2 sensors (with datatype: number) and 1 actuator (datatype: number).

 

Your device now looks like this:

 

First setup OKdo Cloud is done – let’s do the rest of the setup before we start to use the data for something, aka making some rules.

I did create 2 rules:

One is the temperature >= 22,5C if yes return LED:1

Second is the temperature <=22C if yes return LED:2

The only reason for the rules is to test the flow of the setup – later the rules will be more advanced.

The format OKdo Cloud will use is either ABCL or CBOR, here I will use CBOR and I will only send very limited data from my device, therefore, I will use The Things Networks backend to format it accordingly.

Changes to The Things Network data format

It will format the 5 bytes send from the MKR 1300 to a format that the OKdo Cloud understands – this could be done from the MKR 1300, but I prefer to do it in the backend rather than use resources at the device side.

 

Go to The Things Network, Console.

  • Choose Applications and Click on our application (a0a0c2c2a0ih7b1).
  • Click on the “Payload Formats” tab.
  • Click “remove decoder” and add this in the decoder field:
function Decoder(bytes, port) {
var humidity = (bytes[0] <<8) | bytes[1];
var temperature = (bytes[2] <<8) | bytes[3];
var LED = (bytes[4]);
var value = "value";

return {
  humidity:{"value": humidity /100},
  temperature:{"value": temperature /100},
  LED: {"value": LED}
  }
}
  • Click “Save payload functions” on the lower right side.

Setting up the Arduino IDE

Getting the IDE.

You can get it for all major OSs here:

Arduino Software

Since Arduino has done a great job out of creating a super getting started guide (Covers all the IDEs and boards), I will just link to it:

Arduino Guides

 

Call me old fashioned, but I prefer having the IDE installed on my local PC.

(There is an online version also: Arduino Online IDE).

 

For this use I have installed the following libraries also:

“DHT sensor library” by Adafruit version 1.3.8

“MKRWAN” by Arduino version 1.0.11

“RTCZero” by Arduino version 1.6.0

 

I made a simple test sketch in order to test the flow from the device to the OKdo cloud and return via LoRaWAN and The Things Network.

test sketch: “MKR1300a”:

/*
  Lora Send And Receive
  This sketch demonstrates how to send and receive data with the MKR WAN 1300 LoRa module.
  This example code is in the public domain.
*/

#include <MKRWAN.h>
#include <RTCZero.h>

#include "DHT.h"
#define DHTPIN 2  
#define DHTTYPE DHT22 
DHT dht(DHTPIN, DHTTYPE);

#define SWPIN 0

LoRaModem modem;

// Uncomment if using the Murata chip as a module
// LoRaModem modem(Serial1);

#include "arduino_secrets.h"
// Please enter your sensitive data in the Secret tab or arduino_secrets.h
String appEui = SECRET_APP_EUI;
String appKey = SECRET_APP_KEY;
byte LEDstatus = 0x00000000;

const int ledPin = 7;
const int ledPin2 = 3;
const int ledPin3 = 5;
const int ledPin4 = 6;

bool usbDetached = false;
bool case2 = false;
/*Interupt rutines setup start */
bool rtcAlarm = false;
int alarmInterval = 10;
const int interruptPin0 = 0;
const int interruptPin1 = 1;
const int interruptPin2 = 4;
int ISRnum = 1;
int ISRnumOld = 1;
/*Interupt retines setup end */

/* RTC setup start*/
RTCZero rtc;
int alarmMinutes;
int alarmSeconds;
/* Change these values to set the current initial time */
const byte seconds = 00;
const byte minutes = 00;
const byte hours = 00;
/* Change these values to set the current initial date */
const byte day = 01;
const byte month = 01;
const byte year = 19;

/* RTC setup end*/

void setup() {
  pinMode(ledPin2, OUTPUT);
  pinMode(ledPin3, OUTPUT);
  pinMode(ledPin, OUTPUT);
  pinMode(ledPin4, OUTPUT);

  pinMode(interruptPin0, INPUT_PULLDOWN);
  pinMode(interruptPin1, INPUT_PULLDOWN);
  pinMode(interruptPin2, INPUT_PULLDOWN);
  attachInterrupt(interruptPin0, ISRone, HIGH);
  attachInterrupt(interruptPin1, ISRtwo, HIGH);
  attachInterrupt(interruptPin2, ISRtree, HIGH);

  rtc.begin();
  rtc.setTime(hours, minutes, seconds);
  rtc.setDate(day, month, year);
  rtc.attachInterrupt(alarmMatch);
  
  // put your setup code here, to run once:
  digitalWrite(ledPin4, HIGH);
  Serial.begin(115200);
  while (!Serial);
  Serial1.begin(115200);
 //while (!Serial1);
 //delay(1000);
 //digitalWrite(ledPin4, HIGH);
  // change this to your regional band (eg. US915, AS923, ...)
  if (!modem.begin(EU868)) {
    Serial.println("Failed to start module");
    while (1) {}
  };
  Serial.print("Your module version is: ");
  Serial.println(modem.version());
  Serial.print("Your device EUI is: ");
  Serial.println(modem.deviceEUI());

  int connected = modem.joinOTAA(appEui, appKey);
  if (!connected) {
    Serial.println("Something went wrong; are you indoor? Move near a window and retry");
    while (1) {}
  }
  Serial1.println("Joined");
  // Set poll interval to 60 secs.
  modem.minPollInterval(50);
  // NOTE: independently by this setting the modem will
  // not allow to send more than one message every 2 minutes,
  // this is enforced by firmware and can not be changed.

 dht.begin();

}

void loop() {
      
      switch (ISRnum) {
        case 0: {
          case2 = false;
          Serial.println("CASE 0");
          Serial1.println("CASE 0");
          delay(100);
          alarmSeconds = rtc.getSeconds();
          alarmSeconds += alarmInterval;
          if (alarmSeconds >= 60) {
          alarmSeconds -= 60;
          }
          rtc.setAlarmTime(rtc.getHours(), rtc.getMinutes(), alarmSeconds);
          rtc.enableAlarm(rtc.MATCH_SS);   
          digitalWrite(ledPin, LOW);
          Serial1.println("CASE 0 - STB");
          delay(100);
          rtc.standbyMode();
          break;
        }
        
        case 1: {
          rtc.disableAlarm();
          case2 = false;
          Serial.println("CASE 1");
          Serial1.println("CASE 1");
          digitalWrite(ledPin, HIGH);
          //dht.begin();
          
          uint32_t humidity = dht.readHumidity(false) * 100;
          uint32_t temperature = dht.readTemperature(false) * 100;
        
          byte payload[5];
          payload[0] = highByte(humidity);
          payload[1] = lowByte(humidity);
          payload[2] = highByte(temperature);
          payload[3] = lowByte(temperature);
          payload[4] = LEDstatus;
     
          int err;
          modem.beginPacket();
          modem.write(payload, 5);
          err = modem.endPacket(true);
          if (err > 0) {
          //  Serial.println("Message sent correctly!");
          } else {
          //  Serial.println("Error sending message :(");
          //  Serial.println("(you may send a limited amount of messages per minute, depending on the signal strength");
          //  Serial.println("it may vary from 1 message every couple of seconds to 1 message every minute)");
          }
          digitalWrite(ledPin, LOW);
          Serial1.println("CASE 1 - before delay");
          delay(10000);
          Serial1.println("CASE 1 - after delay");
          if (!modem.available()) {
            Serial.println("No downlink message received at this time.");
            Serial1.println("No downlink message received at this time.");
            return;
          }
          char rcv[64];
          int i = 0;
            while (modem.available()) {
              rcv[i++] = (char)modem.read();
            }
      
          Serial.print("Received: ");
          for (unsigned int j = 0; j < i; j++) {
            Serial.print(rcv[j] >> 4, HEX);
            Serial.print(rcv[j] & 0xF, HEX);
            Serial.print(" ");
          }
          Serial.println();
           
            switch (rcv[5]) {
            case 0: {
            Serial.println("case: 0x00");
            Serial1.println("case: 0x00");
            digitalWrite(ledPin2, HIGH);
            digitalWrite(ledPin3, LOW);
            break;
            }
            case 1: {
            Serial.println("case: 0x01");
            Serial1.println("case: 0x01");
            digitalWrite(ledPin2, LOW);
            digitalWrite(ledPin3, HIGH);
            break;
            }
            case 2: {
            Serial.println("case: 0x02");
            Serial1.println("case: 0x02");
            digitalWrite(ledPin2, HIGH);
            digitalWrite(ledPin3, HIGH);
            break;
            }
            default: {
            digitalWrite(ledPin2, LOW);
            digitalWrite(ledPin3, LOW);
            }
          }
          
          if (rcv[5] != 0) {
            LEDstatus = rcv[5];
          }
          Serial1.println("CASE 1 - end");
          break;
          }
        //}
        
        case 2: {
          rtc.disableAlarm();
          if (!case2){
          case2 = true;
          Serial.println("CASE 2");
          Serial1.println("CASE 2");
          }
          Serial1.println("CASE 2");
          digitalWrite(ledPin4, HIGH);
          delay(200);
          
          digitalWrite(ledPin4, LOW);
          delay(200);
          digitalWrite(ledPin4, HIGH);
          delay(200);
          digitalWrite(ledPin4, LOW);
          delay(200);
          digitalWrite(ledPin4, HIGH);
          delay(200);
          digitalWrite(ledPin4, LOW);
          //do something
          break;
        }
      }
      
  if (rtcAlarm){
    Serial.println("rtcAlarm");
    Serial1.println("rtcAlarm");
    rtcAlarm = false;
    digitalWrite(ledPin, HIGH);
   
    uint32_t humidity = dht.readHumidity(false) * 100;
    uint32_t temperature = dht.readTemperature(false) * 100;

  
    byte payload[5];
    payload[0] = highByte(humidity);
    payload[1] = lowByte(humidity);
    payload[2] = highByte(temperature);
    payload[3] = lowByte(temperature);
    payload[4] = LEDstatus;

    int err;
    modem.beginPacket();
    modem.write(payload, 5);
    err = modem.endPacket(true);
    if (err > 0) {
      Serial.println("Message sent correctly!");
    } else {
      Serial.println("Error sending message :(");
      Serial.println("(you may send a limited amount of messages per minute, depending on the signal strength");
      Serial.println("it may vary from 1 message every couple of seconds to 1 message every minute)");
    }
  
    if (!modem.available()) {
      Serial.println("No downlink message received at this time.");
      return;
    }
    char rcv[64];
    int i = 0;
    while (modem.available()) {
      rcv[i++] = (char)modem.read();
    }

    Serial.print("Received: ");
    
    for (unsigned int j = 0; j < i; j++) {
      Serial.print(rcv[j] >> 4, HEX);
      Serial.print(rcv[j] & 0xF, HEX);
      Serial.print(" ");
    }
    Serial.println();
   
     switch (rcv[5]) {
        case 0: {
        Serial.println("case: 0x00");
        Serial1.println("case: 0x00");
        digitalWrite(ledPin2, HIGH);
        digitalWrite(ledPin3, LOW);
        break;
        }
        case 1: {
        Serial.println("case: 0x01");
        Serial1.println("case: 0x01");
        digitalWrite(ledPin2, LOW);
        digitalWrite(ledPin3, HIGH);
        break;
        }
        case 2: {
        Serial.println("case: 0x02");
        Serial1.println("case: 0x02");
        digitalWrite(ledPin2, HIGH);
        digitalWrite(ledPin3, HIGH);
        break;
        }
        default: {
        digitalWrite(ledPin2, LOW);
        digitalWrite(ledPin3, LOW);
        }
      }
    
     if (rcv[5] != 0) {
      LEDstatus = rcv[5];
     }
    digitalWrite(ledPin, LOW);
    
    ISRnum =0;
    
  }
  
}

/* Interrupt routines here under */
void alarmMatch() {
  rtcAlarm = true;
}

void ISRone() {
  ISRnum = 0;
}

void ISRtwo() {
  ISRnum = 1;
} 

void ISRtree() {
  ISRnum = 2;
} 

And the “arduino_secrets.h”

// Replace with keys obtained from TheThingsNetwork console
#define SECRET_APP_EUI "70B3D57ED002728B"
#define SECRET_APP_KEY "597235E5FD65757A8FEEC137947BF415"

/*
// Replace with keys obtained from TheThingsNetwork console
#define SECRET_APP_EUI "70B3D57ED002606B"
#define SECRET_APP_KEY "E59C2435CEB63C8367AC7F0B3D4EB58D"

*/

The number inline 41 (int ISRnum = 1;) determines what case the flow will start with as default (Here are both the normal (case 1) and deep sleep (case0) features (And case 2 for test use) in the same sketch).

Both need to be in the same working directory!

 

For this test I also added some switches and LED’s. Attached to the digital IO’s (Switches: D 0,1,4 (Interrupt capability) and LED’s: D 3,5,6,7).

This is “only” for testing purpose and can be omitted or the interupt function can be modified to your needs.

 

It works! What’s next?

Since I stated that I wanted it to run on batteries, so let’s see what we can do to optimize the power requirements of the device.

Otii and the magic of knowing what drains your current:

Otii gives you insights on the current profile of your device and application. It is a bundle containing both a hardware box and one or more software addons (Extra software for added features/functionality).

This developer tool is a combination of several instruments. It contains high precision ADCs and DACs tied up with some GPIOs, a UART and some clever software.  (Link to full specifications: Otii product details)

You can use the Otii to power your DUT and at the same time log the DUTs behavior – this can then be shared with others for review or analysis (The bundled “basic” software is free of charge and everyone can just download it from Qoitech/download).

 

The logging feature is super nice, you can directly see the impact on power consumption, when you have made changes to your code or hardware, simply by comparing the DUT with a previous DUT log.

Above is a comparison between case 0 (Red) and case 1 (Green) from the test sketch used in this example.

The CASE 0, is with the MKR WAN 1300 in standby mode and CASE 1 is when you just use a delay() instead.

I also added some buttons (interrupt driven) to change the mode.

 

The Otii software is intuitive to use, and the option to link UART/GPIO event to the measurement logging, makes it super easy to tie an event in your code to a specific part of the log and hence see exactly what is draining your battery.

Above, it’s easy to see find the area of interest, here I have marked the following places: (Red: CASE 0) rtcAlarm, CASE 0, Case 0 – STB and, (Green: CASE 1) CASE 1, CASE 1 – before delay, CASE 1 – after delay (As seen in the test sketch used in this example).

 

I really did a lot with the MKR WAN 1300 in order to minimize the power consumption, especially the quiescent consumption. But I was not happy with my result!

OK, time to start looking at the datasheets! The Murata RF module used on the MKR 1300 have a standby with RTC working of 1.65uA and the SAMD21 down to 348uA (typ.) when using ext clk or 20,6uA (typ.) with the int clk (Less precise). It’s far from what did archive (1,32mA).

I did dig a lot around and I am not the only one that was facing this problem. It’s a design fault in the MKR WAN 1300 on how Arduino implemented the Murata module (TTN/Forum).

Arduino has made a new module: MKR WAN 1310 where the previous problems (From MKR WAN 1300) have been fixed and therefore should be able to get to a considerable lower standby current, rumors mention 104uA – that’s 13 times better – now we are talking!

The MKR WAN 1300 is still a nice product and have all the desired functionality, only drawback and reason to get the MKR WAN 1310 is if you need to operate it on batteries or other limited power source.

This shows that if you only trust a module and the datasheets you will face a hard time achieving your goals…and when you do realize that you are lost, then where to look for the finish line.

So measure and check, the days of using datasheets and excel sheet for your energy budget planning only are long gone. Use Otii, it is the dream for anyone who is involved with IoT and/or battery-operated device development – I will go so far as calling it a mandatory asset in any lab!

Next steps for me?

I can´t get enough of the battery life aspect for this project so I will get an MKR 1310, and then I will do the test again and a full battery provisioning (with Otii Battery Toolbox as an add on to standard Otii)  to define exactly what battery I need to be able to use my device for an application without changing battery during the life time of it. I will use  Otii as a battery replacement (Emulate a battery, with the precise discharge curve based on the DUTs current profile).

Also, I will try the Battery profiling to see if the batteries that I have in mind will do the job for this and future projects.

So, stay tuned for an update/new projects: Battery part, IoT device part and finally playing around with the rules/OKdo cloud!

Final notes:

The OKdo Cloud offers a lot of pretty good functionality and is super simple to use, here I have created a PINBOARD for the MKR1300a device:

 

And the PULSE is a cool tool to see all activity on your Ground:

 

Likewise, The Things Networks backend also offers a visibility of the traffic:

Above is how the 2 cases look like.

 

One of my requirements was battery operation, this I have postponed until I get my hands on the MKR 1310. It doesn’t really make sense to do a lot of optimization work before we have fixed the quiescent current.

 

 

Links:

Here are just some use full links, related to this project.

Arduino

The Things Network

OKdo Cloud Sign In

CBOR test site

The Things Network – Private Environment

Like what you read? Why not show your appreciation by giving some love.

From a quick tap to smashing that love button and show how much you enjoyed this project.