Get Started with Helium: How to Build a Sensor Node Using Arduino and a LoRaWAN Gateway

Scroll down

This project demonstrates how to build a sensor node for the Helium network using LoRaWAN. We show how to connect both analog and digital sensors to an Arduino MKR1310 and go through the steps to set up a Helium account and add the device to the network. We set up a soil moisture meter and configured Helium to forward soil moisture readings and environmental conditions to IFTTT and trigger push notifications to a mobile phone as an example application.

Difficulty: Medium

Time: 4 hrs

Steps: 10

Licence: None

Credit: None

Parts Needed:

Arduino MKR WAN 1310 Board

Arduino MKR WAN 1310 product image

DFRobot Soil Moisture Sensor

DFRobot Gravity: Analog Capacitive Soil Moisture Sensor product image

FinestraMiner LoRaWAN Hotspot

Finestra Miner E a, Helium Hotspot product image

Arduino Pentaband Antenna

Arduino Dipole Antenna Pentaband Outdoor product image

Step 1: Helium

Helium is the world’s largest LoRaWAN network for IoT devices. LoRaWAN enables low-cost, low-power sensor devices to transmit and receive data over long distances (up to several km) to cloud-hosted network servers. These can then forward your data streams to application services like data stores, visualisation services, MQTT servers etc., making it suitable for applications that only need to send a small amount of data every so often, like environmental monitoring or object tracking.

Check out our blog that explains all about Helium.

When you sign up for a Helium account, you get your own browser-based console for setting up and managing devices and services on the network. The console includes tools for adding devices (nodes), viewing and modifying data streams, creating integrations to forward data to applications and a live data viewer and debugger to help with development. We cover all of these as we build our example application.

You can see what the Helium coverage is like in your area on this map of gateways (called Hotspots):

We installed our own Hotspot using a Finestra Miner.

Step 2: Hardware Build

The Arduino MKR1310 makes building a LoraWAN node very straightforward. Based on the Arm® Cortex®-M0 32-bit SAMD21 microcontroller, it has a built-in Murata CMWX1ZZABZ LoRa® wireless module and LiPo battery circuit for power and charging. There is also a Microchip ATECC508 crypto chip which can store the LoRaWAN keys securely.

There is an official Arduino C/C++ library supporting development on the MKR1310 using the Arduino IDE, along with code examples. The whole module can be put into sleep mode, allowing it to run for several months on battery power.

Soil moisture content was measured using a capacitive soil sensor connected to one of the MKR1310 analogue inputs. This operates at 3.3V and gives a 10-bit resolution from 0V to 3.3V.

A Bosch BME280 sensor module was added to sample atmospheric conditions of temperature, pressure and humidity, connected to the I2C bus. Our module operates at 3.3V and has the necessary pull-up resistors for the SDA and SCL lines built in.

A 2000mAh / 3.7V LiPo battery is connected to the MKR1310 JST PHR-2 battery connector. If you also connect a USB cable, the battery starts recharging automatically.

We soldered female headers to a prototyping board to connect all the components together and mounted it in a 105 x 70 x 50mm ABS enclosure with the LiPo battery beneath the circuit board. The moisture sensor was held firmly in place with a cable tie. We made a small cutout in the base of the case to allow for the moisture sensor and antenna cable. 

Warning: The MKR1310 operates at 3.3V – be very careful not to exceed this voltage on any of the I/O pins, or the board may be permanently damaged.

Circuit Fritzing diagram:

Sensor node circuit board:

Step 3: Device EUI

All nodes connecting to the LoRaWAN network need a Device EUI – this is a unique 64-bit identifier for the device and is pre-set during the manufacture of the Arduino. To discover the identifier for your MKR1310:

  • Install the Arduino SAMD board package into the Arduino IDE with the Boards Manager to add support for the MKR1310;
  • Install the MKRWAN (not MKRWAN_v2) library into the Arduino IDE using the Library Manager;
  • Connect the Arduino to the PC with a USB cable and select MKR1310 in the Boards menu;
  • Upload the FirstConfigure.ino example from MRKWAN library to the Arduino;
  • Open the Serial Monitor and copy the Device EUI to a text file for use later.

Make a copy of the Device EUI and save it to a text file so that it can be added to the Helium network later.

Step 4: Helium Devices

If you do not already have one, go to Helium’s website and create an account. This will give you access to the Helium Console, where device and application management takes place.

  • Click on Device in the side menu and the Add New Device icon;
  • Give your device a name in the Name text box;
  • Copy the Device EUI from the previous step and overwrite the Device EUI text box;
  • An App EUI and secret App Key are automatically generated for use later;
  • Click the Save button.

The device, your helium hotspot, will now be added to the network, which can take up to 20 minutes as the details are added to the blockchain and synchronised across the network.

Step 5: First Connection

Now that your device has been registered with Helium, we can go back to the Arduino IDE and run the FirstConfiguration code. Helium uses the OTAA join method for connecting devices to the network:

  • Open FirstConfiguration.ino and set the Regional Band code in the modem.begin parameter – this example is for the EU region, which uses the 868MHz frequency band:
 // change this to your regional band (eg. US915, AS923, ...)

  if (!modem.begin(EU868)) {
    while (1) {}
  • Upload the code and open the Serial Monitor to run the code;
  • You will be prompted to enter the following information, which can be obtained for your device in the Helium Console;
  • Select the OTAA join option;
  • Copy and paste the App EUI from the Helium Console to the Serial Monitor;
  • Copy and paste the App Key (which must be kept secret).

After a few seconds, you should see the following output in the Serial Monitor that shows your first data packet was sent successfully.

Message sent correctly!

Now you can go back to the Helium Console to view the packet transfers that took place on the Device page. You can see the size of the packets, which Hotpots received them, and what Spreading Factor (SF) was used:

Step 6: Arduino Firmware

Now that our device is communicating with Helium, we can load the firmware to turn our node into a remote soil moisture sensor.

The full source code is available for download on the OKdo GitHub repository.

  • Download the zip version or clone the repo onto your PC;
  • Open helium-mkr1310-node.ino;
  • Add the Forced-BME280 and Arduino Low Power libraries to the Arduino IDE using the library manager;
  • Open the arduino_secrets.h tab and add your App EUI and App Key for the device from the Helium Console.
// Helium
  • Set the Regional Band for your region as above
  • Set Debugging on
  • Adjust the send interval to 2 minutes for testing purposes.
#define DEBUG
#define SEND_INTERVAL 2 // Send interval in mins

We set the initial data rate to 5, corresponding to a Spreading Factor of SF7BW125.

    DataRate  Modulation  SF  BW  bit/s
    0   LoRa  12  125   250
    1   LoRa  11  125   440
    2   LoRa  10  125   980
    3   LoRa  9   125   1'760
    4   LoRa  8   125   3'125
    5   LoRa  7   125   5'470
    6   LoRa  7   250   11'000

Information about Frequency Plans and Spreading Factors on Helium can be found here.

Tip: If you send packets too often, your packets will get blocked.

The moisture sensor outputs a voltage between 0 and 3.3V depending on how wet the soil is. The Arduino’s Analogue to Digital Converter (ADC) has a default resolution of 10-bits, so it converts this voltage to a 16-bit integer value between 0 and 1023 when analogRead is called. We mapped this to a percentage value and stored it in a 16-bit signed integer before adding it to the data packet.

 // Read moisture sensor
  int16_t moisture_raw = analogRead(moisturePin);
  if (moisture_raw > DRY) {
    moisture_raw = DRY;
  } else if (moisture_raw < WET) {
    moisture_raw = WET;
  int16_t moisture_pc = map(moisture_raw, DRY, WET, 0, 100);

To calibrate the moisture sensor when you run the code, make a note of the (Dry) reading, then put the sensor into a glass of water and note the (Wet) reading, then update the macros at the top of the code:

#define WET 106
#define DRY 608

The BME280 sensor module is connected to the  I2C bus. The library we used returns floating point values for temperature, pressure and humidity whenever the sensor is read. These were converted to integers by scaling with a multiple of 10 and casting to a 16-bit integer. There is a small loss in resolution using this method, but it is negligible for this type of application.

 int16_t temperature_raw = (int16_t)(climateSensor.getTemperatureCelcius() * 100.00);

Once all the sensors have been read and their values stored as 16-bit integers, each 16-bit value (2 bytes) can be converted into two 8-bit bytes and placed in a buffer to build up the data packet. That way, all 4 sensor readings, plus a counter for debugging, can be compressed into just 10 bytes. Keeping data packets small becomes important when sending data over LoRaWAN.

Here is the code and the function that does this bit manipulation:

// Buffer sensor readings
uint8_t buffer[BUFFER_SIZE] = {0};  // init to zero!
// Store moisture percent value in first 2 bytes of buffer
int_to_byte_array(moisture_pc, &buffer[0]);
void int_to_byte_array(int16_t n, uint8_t* buf) {
  *buf = n >> 8;
  *++buf = n & 0xFF;

Tip: Using size_t data types ensures this code works on different MCU’s where integers may be 16 or 32-bits wide.

Once the data buffer has been filled with sensor readings, the packet can be sent:

modem.write(buffer, BUFFER_SIZE);  // write n bytes
err = modem.endPacket(true);

Upload the code and check the output in the Serial Monitor. You should see the device details and sensor readings followed by confirmation the packet has been sent.

Tip: If you get message failures, you may be out of range of the nearest Hotspot, or you may need to increase the Spreading Factor by lowering the dataRate parameter. This will increase the range, but you may need to increase the interval between sends. It is a balance with LoRaWAN.

The final point of interest is putting the module to sleep in between sending data, which is done by calling the sleep function from the Arduino Low Power library with the interval in milli-seconds; this dramatically increases battery life, so we should see several months of usage in between charging cycles:

LowPower.sleep(SEND_INTERVAL * 60 * 1000);

Step 7: Helium Functions

Because data packets are converted to bytes and encrypted when they are sent over the network, you cannot see their contents unless you set up a decoding Function in the Helium Console. Functions are written in JavaScript and must adhere to a predefined format. This script reverses the bit manipulation done on the Arduino and returns the byte array back into floating point values:

  • Open the Functions page in the Helium Console side menu;
  • Click Add New Function;
  • Give the function a name and select Custom Function;
  • Paste the following JavaScrip into the text box.
function Decoder(bytes, port) {
  var decoded = {};
  decoded.moisture_pc = (bytes[0] << 8) + bytes[1];
  decoded.temperature_c = (((bytes[2] & 0x80 ? bytes[2] - 0x100 : bytes[2]) << 8) + bytes[3]) / 100;
  decoded.humidity_rel = ((bytes[4] << 8) + bytes[5]) / 100;
  decoded.pressure_mbar = ((bytes[6] << 8) + bytes[7]) / 10;
  decoded.counter = ((bytes[8] << 8) + bytes[9]);
  if (decoded.moisture_pc < 40) {
    decoded.soil_moisture = "dry";
  } else {
    decoded.soil_moisture = "wet";
  return {

Tip: the function must be named Decoder and return an object named decode.

If you paste example data encoded as bytes, you can use the Script Validator to see the converted output.

Now, when you use the Console debugger, you will be able to view the unencrypted payload contents, and the payload can be converted into a JSON object to be forwarded onto an application in the next Integration step.

Bench testing the node:

Step 8: Helium Integrations

Now that decoded data is being received from our sensor node, it can be forwarded to an application. For this example, we used IFTTT to generate a push notification containing the data payload as a JSON string, but this could be any cloud service that exposes a Webhook. There are already lots of partner applications pre-configured in Helium to make this step a point-and-click exercise.

In your IFTTT account, create a new Webhooks integration and test that it can trigger an event with an arbitrary JSON payload. There is a tool for testing this in the Documentation button at

We covered this in detail in a previous project showing how to use AI for sound recognition.

  • Now, open the Integrations page in Helium Console and click Add New Integration;
  • Click Add a custom HTTP integration and cut and paste your IFTTT endpoint into the URL text box;
  • Give your Integration a name and save it;
  • Add the following JavaScript Template to the Template body text box.
   "moisture_pc": "{{decoded.moisture_pc}}",   
   "temperature_c": "{{decoded.temperature_c}}",
   "humidity_rel": "{{decoded.humidity_rel}}", 
   "pressure_mbar": "{{decoded.pressure_mbar}}", "soil_moisture": "{{decoded.soil_moisture}}",
"counter": "{{decoded.counter}}", 
  "reported_at": "{{reported_at}}"

The template generates the JSON payload for IFTTT and references the decoded object created by the Decoder Function plus an additional reported_at object which will add a timestamp into the JSON string.

Step 9: Helium Flows

The last piece of the jigsaw is to connect all these components together. Helium calls this a Flow and it defines what happens to the data packet once it is received by the network. We will link our Device to our Decoder Function and connect the data to the IFTTT Integration created above.

  • Open the Flows page in Helium Console;
  • Click on the Devices icon and drag your device onto the canvas;
  • Click the Function icon and drag your Function onto the canvas;
  • Click the Integrations icon and drag toe Integration onto the canvas;
  • Finally, click the nodes on the objects to join them up as a Flow.

Each time a packet is sent by the node, the data will be decoded into a JSON payload and forwarded to IFTTT by an Integration Event. When the Webhook fires at IFTTT, we will receive a push notification on our mobile with the sensor data embedded in the JSON payload.

Step 10: Deployment

Once everything is working, comment out the debug macro and increase the send interval, then reflash the Arduino code. This will allow the MKR1310 to start without the Serial Monitor being attached and will reduce power consumption.

//#define DEBUG
#define SEND_INTERVAL 60 // Send interval in mins

The data rate is set to an initial value which determines the Spreading Factor being used. To prolong battery life and reduce the impact on the network, we want to use the lowest SF that gives reliable data transmission. This can be done by enabling Adaptive Data Rate (ADR) mode on the MKR modem. Now when 20 successive packets are received without failure, the network sends commands back to the modem to adjust the data rate.

  // Enable Adjustable Data Rate

Note: ADR is only suitable for static nodes with reliable signal integrity.

This data rate calculator can be used to set suitable data rates and send intervals for your application.

The airtime calculator is available here.

We tested the range of our node by setting the data rate to 0 (SF12BW125) and went on a walkabout in an urban housing development. We achieved a maximum range of about 0.3Km from our internal Finestra Miner Hotspot with lots of buildings blocking the line of sight!


Helium LoRaWAN Hotspots are springing up all over the place due to the attractiveness of their data mining capabilities, but their purpose is to create a ubiquitous LoRaWAN network for IoT devices.

This project shows how to build a low-cost, low-power node device that sends data over the Helium network. We used Helium to integrate the data to our back-end application, in this case, IFTTT, which generates push notifications containing the environmental sensor data to mobile devices. Other options might be to forward data to cloud storage and AI for trend analysis or to trigger an irrigation system when soil moisture drops too low.

We covered building the sensor hardware based on the Arduino MKR1310 and how to connect it to the Helium network, creating an application flow to a Webhook endpoint. We also touched on some of the technical considerations when sending data over LoRaWAN and the trade-offs that need to be considered.

Hopefully, this will encourage you to build your own node and experiment with Helium!

Looking for your next challenge? For more incredible and creative project ideas, visit our Project Hub.

Are you ready to explore further? Browse through our range of LoRa & Sigfox products to discover more exciting LoRaWAN gateways and Helium accessories, and many more.


Our website uses cookies and similar technologies to provide you with a better service while searching or placing an order, for analytical purposes and to personalise our advertising. You can change your cookie settings by reading our cookie policy. Otherwise, we’ll assume you’re OK with our use of cookies.


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

Browse the US site