Connecting your SenseCAP sensor to The Things Network

If you are interested in measuring IoT telemetry using an ultra low power wide area sensor network solution, the Seeed SenseCAP is an viable choice.

Seeed offers a number of sensors for agriculture and industrial environments, connected to a LoRa network.

I got my hand on this TH sensor, able to read air temperature and air humidity, sending it to the nearest LoRa gateway:

Image result for sensecap TH sensor

Seeed provides a portal for thier sensors called SenseCAP Software. But other LoRa platforms are supported too.

In this blog, I show you how to connect this sensor to The Things Network LoRa backend.

This LoRa network is community-driven and ideal for your LoRa projects.

SenseCAP Sensors

The design of the SenseCAP sensors is pretty clever. Each type of sensor is a combination of two parts:

  1. a generic compartment with a large battery and the lora chip, antenna, and basic logic
  2. a specific part containing the sensor instrument. I can be screwed on top of the generic sensor. There are multiple ‘hats’ available, ech with its own sensors.

This results in a reliable, waterproof sensor with a battery life of up to three years:

The documentation is showing the list of all possible measurements supported:

With the sensor I have, I can read both the humidity and the temperature values from this sensor.

Let’s see how my sensor must be connected to the LoRa network.

Collecting keys

If we want to register a generic LoRa device in The Things Network (TTN) portal, we need to have three specific keys:

We need:

  1. Device EUI
  2. App Key
  3. App EUI

According to the documentation of the SenseCAP open API, the SenseCAP sensor device’s App EUI and App key have been flashed into the device by Seeed.

On the outside of my SenseCAP sensor, I see two keys marked as EUI and KEY. I need these to retrieve those hardcoded keys of my device.

Seeed makes these codes available using an API.

We use this HTTP Rest API to retrieve App EUI and App Key. You can use any HTTP tools or browser to issue an HTTP GET request:


// New API (returns same response)[eui]&deviceCode=[key]

Note: we do not need to be logged in into any portal to make this work

Note: Always use HTTP instead of HTTPS!

Once I execute this, I get everything I need:

With this, I am able to register my LoRa device in the TTN portal.

I can easily enter the dev_eui and the app_key. The app_eui needs an extra step though.

This app_eui has to be added to the eui list of your application:

Once this is done, you are able to select the newly added app_eui when you add your device.

Once the device is registered, check out the SenseCAP Sensor first.

Sending your first message

After all keys are entered ans saved in the portal, check if your sensor is setup correctly:

You have to switch it on (1). Watch for the led (3). It has to flash a few times (for two seconds) as an indication the sensor is connected to the TTN Application. If needed, you can try to reset your sensor using the reset button (2).

Note: It took me a couple of times to get this working.

Once connected, you will see a successful join request from your sensor and the arrival of your first messages in the TTN portal:

As you can see, we get a bunch of byte arrays. Somewhere in these bytes, our temperature and humidity are hidden.

Decoding the payload

Sensors typically do not communication in human readable messages. Trying to put all data inside a byte array is more efficient.

The smaller a message is, the bigger the chance it is send without errors and with less energy consumption over a longer distance. And the air time is limited too so we are allowed to send more messages without being throttled by the network.

So we have to transform this message back to the original (human readable) values.

I found a guide to the SenseCAP and in there, chapter five explained everything about packet parsing.

This is how it works:


is divided in these logical parts:
01 0110 9C4A0000 01 0210 A4CE0000 B1E6

01 = channel number (01 means the data is coming from the sensor)
0110 => 1001 => what follows is a temperature
9C4A0000 => 00004A9C hex => 19100 dec => 19.1 degrees celcius
01 = channel number 
0210 => 1002 => what follows is a humidity
A4CE0000 = 0000CEA4 hex => 52900 dec => 52.9 % 
B1E6 = checksum

Yes, we are now able to decode this packet to both the degrees celcius (19.1) and humidity (52.9).

Note: the fields in the packet are represented in little-endian byte order, so we have to shift some bytes to get to the actual hexadecimal values.

Note: It’s not clear to me how to compute the checksum. For now I ignore it.

This was not the only message format I got. I also got some information about the main body:


01 0100 01010001 = main board function
01 0200 D1020015 = sensor board function
01 0300 30F1F73C = EUI
01 0400 0A0C1114 = EUI
01 0500 00000000 = power on time
01 0600 00000000 = free air time
B094 = checksum

And I got some data regarding battery life and interval (together with the sensor data):


00 = channel number (00 means the data is coming from the main body)
0700 => what follows is a battry level and interval
6400 => the battery is at 100%
0500 => the interval is 5 minutes
010110785000000102109CE00000122A = temperature etc.

So we know how to decode these messages by hand. Let’s do that again using the payload function in the TTN portal.

Payload decode function

The TTN application supports automatic decoding of messages, but you have to write (or copy) these decode sourcecode yourself.

I did some basic decoding using this JavsScript code:

function Decoder(bytes, port) {
  var decoded = {};

  var battery = -99;
  var interval = -99;
  var temp = -99;
  var hum = -99;
  var ch1 = -99;
  var ch2 = -99;
  var ch3 = -99;
  var val01 = -99;
  var val02 = -99;
  var val03 = -99;
  ch01 = bytes[0];

  val01 = ((bytes[2] << 8) | bytes[1]);

  if (val01 === 7) { // battery
    battery = (bytes[4] << 8) | bytes[3];
    interval = (bytes[6] << 8) | bytes[5];

  if (val01 === 4097) // temperature
    temp = ((bytes[6] << 24) | (bytes[5] << 16) | (bytes[4] << 8) | bytes[3]) /1000 ;

  ch02 = bytes[7];

  val02 = ((bytes[9] << 8) | bytes[8]);

  if (val02 === 4097) // temperature
    temp = ((bytes[13] << 24) | (bytes[12] << 16) | (bytes[11] << 8) | bytes[10]) /1000 ;

  if (val02 === 4098)
    hum = ((bytes[13] << 24) | (bytes[12] << 16) | (bytes[11] << 8) | bytes[10]) / 1000;

  ch03 = bytes[14];

  val03 = ((bytes[16] << 8) | bytes[15]);

  if (val03 === 4097) // temperature
    temp = ((bytes[20] << 24) | (bytes[19] << 16) | (bytes[18] << 8) | bytes[17]) /1000 ;

  if (val03 === 4098)
    hum = ((bytes[20] << 24) | (bytes[19] << 16) | (bytes[18] << 8) | bytes[17]) / 1000;

  decoded.ch01 = ch01;
  decoded.val01 = val01;
  decoded.ch02 = ch02;
  decoded.val02 = val02;
  decoded.ch03 = ch03;
  decoded.val03 = val03;
  decoded.battery = battery;
  decoded.interval = interval;
  decoded.temperature = temp;
  decoded.humidity = hum;

  return decoded;

This code is able to read both the sensor data with temperature and humidity and the battery level indication and interval.

Note: This function is not optimized to handle all messages. It only support the bare minimum.

Once this payload function is saved, we can enjoy the receival of meaningful SenseCap messsages:


We are now able to receive Seeed SenseCAP messages using The Things Network portal.

We can decode the messsages in human readable JSON messages.

From there, we can connect to any platform we use.