Table of Contents

LoRaWAN Tracking System

Introduction

The rapid growth of the Internet of Things (IoT) has led to the development of innovative solutions to enhance device performance, making them more user-friendly and cost-effective. IoT devices like sensors, actuators, and gateways form ecosystems that address various problems, with Low-Power Wide-Area Network (LPWAN) technologies playing a crucial role.

Traditional wireless technologies are often inefficient for specific embedded tasks. The need for extensive coverage, low energy consumption, and affordable equipment has driven the adoption of LPWAN technologies such as LoRaWAN, SigFox, and NB-IoT. Among these, LoRaWAN has become particularly popular due to its low energy consumption, high communication capacity, and robustness in urban environments.

Geolocation of IoT devices poses a significant challenge, especially with the high energy demands of GPS modules. This project aims to develop a geolocation system using the Heltec WiFi LoRa32(V2)-F board, combining WiFi scanning and LoRaWAN communication to accurately locate IoT devices in urban settings. This approach offers a practical and energy-efficient solution for IoT geolocation.

Objective

The objective of this project is to develop an integrated geolocation system using the Heltec WiFi LoRa32(V2)-F board that combines WiFi scanning and LoRaWAN communication to accurately determine the position of IoT devices in urban environments. This system aims to:

  1. Utilize the ESP32 module's WiFi capabilities to scan and identify surrounding WiFi networks, capturing SSID data to enrich the geolocation dataset.
  2. Transmit the collected WiFi SSID data over a low-power, wide-area LoRaWAN network to a central server for processing.
  3. Implement a custom payload decoder on The Things Network (TTN) to interpret the transmitted data and store it in Google Sheets for organized and accessible data management.
  4. Employ a MATLAB script to calculate the geoid coordinates of the ESP32 board by estimating distances from multiple gateways based on RSSI values, addressing the challenges posed by urban density and signal disruptions.
  5. Achieve a practical level of geolocation accuracy, while identifying areas for future improvements in gateway density, spatial distribution, and RSSI model calibration.

Through this project, I aim to demonstrate the feasibility and practical application of combining WiFi and LoRaWAN technologies for geolocation purposes in IoT deployments within urban settings.

Hardware

Heltec WiFi LoRa32(V2)-F

Software

Board's code

#include "LoRaWan_APP.h"
#include <WiFi.h>
#include <set> 

The First library provides the functions and definitions needed to work with LoRaWAN, a protocol for low-power, wide-area networks. The second one is used for managing WiFi connections on the device and the last one provides the std::set container, which is used to store unique elements in an ordered manner.

/* OTAA para */
uint8_t devEui[] = { hidden };
uint8_t appEui[] = { hidden };
uint8_t appKey[] = { hidden };

/* LoraWan region, select in Arduino IDE tools */
LoRaMacRegion_t loraWanRegion = ACTIVE_REGION;   //868

/* LoraWan Class, Class A and Class C are supported */
DeviceClass_t loraWanClass = CLASS_A;

These are the parameters for Over-The-Air Activation (OTAA) in LoRaWAN:

/* Prepares the payload of the frame */
static void prepareTxFrame(uint8_t port)
{
    appDataSize = 0; // Reset the payload size

    int n = WiFi.scanNetworks();
    Serial.println("scan done");

    if (n == 0) {
        Serial.println("no networks found");
        appData[appDataSize++] = 0x00; // Indicate no networks found
    } else {
        Serial.print(n);
        Serial.println(" networks found");

        std::set<String> ssidSet; // To store unique SSIDs

        for (int i = 0; i < n && ssidSet.size() < 5; ++i) {
            String ssid = WiFi.SSID(i);

            // Add unique SSID to the set
            if (ssidSet.find(ssid) == ssidSet.end()) {
                ssidSet.insert(ssid);
            }
        }

        // Add SSIDs to the payload
        for (const auto& ssid : ssidSet) {
            uint8_t ssidLength = ssid.length();

            // Ensure there is enough space in the buffer for the length byte and the SSID characters
            if (appDataSize + ssidLength + 1 > LORAWAN_APP_DATA_MAX_SIZE) {
                break;
            }

            appData[appDataSize++] = ssidLength; // Store SSID length
            for (int j = 0; j < ssidLength; ++j) {
                appData[appDataSize++] = ssid[j];
            }

            // Log the SSID being added
            Serial.print("SSID: ");
            Serial.print(ssid);
            Serial.print(" Length: ");
            Serial.println(ssidLength);
        }
    }
}

This function prepares the data to be sent over LoRaWAN:

void setup() {
  Serial.begin(115200);
  Mcu.begin(HELTEC_BOARD, SLOW_CLK_TPYE);
  WiFi.mode(WIFI_STA);
  WiFi.disconnect();

  if (firstrun) {
    LoRaWAN.displayMcuInit();
    firstrun = false;
  }
}

The setup function initializes the device:

void loop()
{
    switch (deviceState)
    {
    case DEVICE_STATE_INIT:
    {
#if (LORAWAN_DEVEUI_AUTO)
        LoRaWAN.generateDeveuiByChipID();
#endif
        LoRaWAN.init(loraWanClass, loraWanRegion);
        // both set join DR and DR when ADR off
        LoRaWAN.setDefaultDR(3);
        break;
    }
    case DEVICE_STATE_JOIN:
    {
        LoRaWAN.displayJoining();
        LoRaWAN.join();
        break;
    }
    case DEVICE_STATE_SEND:
    {
        LoRaWAN.displaySending();
        prepareTxFrame(appPort);
        LoRaWAN.send();
        deviceState = DEVICE_STATE_CYCLE;
        break;
    }
    case DEVICE_STATE_CYCLE:
    {
        // Schedule next packet transmission
        txDutyCycleTime = appTxDutyCycle + randr(-APP_TX_DUTYCYCLE_RND, APP_TX_DUTYCYCLE_RND);
        LoRaWAN.cycle(txDutyCycleTime);
        deviceState = DEVICE_STATE_SLEEP;
        break;
    }
    case DEVICE_STATE_SLEEP:
    {
        LoRaWAN.displayAck();
        LoRaWAN.sleep(loraWanClass);
        break;
    }
    default:
    {
        deviceState = DEVICE_STATE_INIT;
        break;
    }
    }
}

The loop function handles the main state machine for the device:

The Things Network Application

A TTN (The Things Network) application was created, and the device was registered using Over-The-Air Activation (OTAA). The device is configured to operate on the European frequency plan (863-870 MHz) and adheres to LoRaWAN Specification 1.0.2 with Regional Parameters version RP001 1.0.2 revision B. The live data section shows successful uplink and downlink communication events, indicating that the device is correctly sending and receiving data through the TTN gateway.

Payload Decoder

// Decode an uplink message from a buffer
// port: the port number
// bytes: the payload as a byte array
function decodeUplink(input) {
  var bytes = input.bytes;
  var port = input.fPort;
  var ssids = [];
  var i = 0;

  while (i < bytes.length) {
    var ssidLength = bytes[i];
    i += 1;

    if (ssidLength === 0 || i + ssidLength > bytes.length) {
      break;
    }

    var ssid = "";
    for (var j = 0; j < ssidLength; j++) {
      ssid += String.fromCharCode(bytes[i + j]);
    }
    ssids.push(ssid);
    i += ssidLength;
  }

  // Return the decoded values in an object
  return {
    data: {
      ssids: ssids
    }
  };
}

The purpose of this TTN (The Things Network) decoder function is to interpret the payload received from the device. This payload contains WiFi SSIDs detected by the device. Here’s a breakdown of what the function does:

Inputs: The function takes an input object containing two properties:

It initializes an empty array ssids to store the SSIDs and a variable i to keep track of the current position in the byte array.

The function enters a while loop that iterates through the bytes array:

Custom Webhook Integration

The webhook configuration is designed to bridge The Things Network (TTN) application with Google Sheets. The webhook, identified as “iot-sric-project-webhook,” is set to format data in JSON and directs uplink messages to a specified Google Script URL. This setup ensures that uplink messages received by the TTN application are forwarded to Google Sheets for logging and further analysis. This integration facilitates seamless data transfer from IoT devices to a user-friendly and accessible data management platform.

Google Sheets

The data received in Google Sheets includes the following fields: Device ID, Application ID, Device EUI, Device Address, Received At, Port, Undecoded Payload, SSIDs, Gateway ID, EUI, RSSI, SNR, Latitude, Longitude, and Altitude of the gateway.

Google Sheets Script

var SHEET_NAME = "Sheet1";

var SCRIPT_PROP = PropertiesService.getScriptProperties(); // new property service

function doGet(e) {
  return handleResponse(e);
}

function doPost(e) {
  return handleResponse(e);
}

function handleResponse(e) {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  var data = JSON.parse(e.postData.contents);

  if (sheet.getLastRow() === 0) {
    sheet.appendRow([
      'Device ID',
      'Application ID',
      'Device EUI',
      'Device Address',
      'Received At',
      'Port',
      'Payload',
      'SSIDs',
      'Gateway ID',
      'EUI',
      'RSSI',
      'SNR',
      'Latitude',
      'Longitude',
      'Altitude'
    ]);
  }

  var rowData = [];

  rowData.push(data.end_device_ids.device_id);
  rowData.push(data.end_device_ids.application_ids.application_id);
  rowData.push(data.end_device_ids.dev_eui);
  rowData.push(data.end_device_ids.dev_addr);
  rowData.push(data.received_at);
  rowData.push(data.uplink_message.f_port);
  rowData.push(data.uplink_message.frm_payload);

  // Push SSIDs into a single cell
  var decodedPayload = data.uplink_message.decoded_payload;
  var ssidList = decodedPayload.ssids.join(", ");

  // Check if SSID list already exists for the same device and timestamp
  var lastRow = sheet.getLastRow();
  var existingSSID = false;

  for (var i = 1; i <= lastRow; i++) {
    var deviceId = sheet.getRange(i, 1).getValue();
    var receivedAt = sheet.getRange(i, 5).getValue();
    var ssids = sheet.getRange(i, 8).getValue();
    if (deviceId === data.end_device_ids.device_id && receivedAt === data.received_at && ssids === ssidList) {
      existingSSID = true;
      break;
    }
  }

  if (!existingSSID) {
    rowData.push(ssidList);

    // Append the SSID data once
    sheet.appendRow(rowData);
  }

  // Append the gateway data for each gateway
  for (var i = 0; i < data.uplink_message.rx_metadata.length; i++) {
    var gateway = data.uplink_message.rx_metadata[i];
    var gatewayRowData = rowData.slice(0, 7);  // Make a copy of the base rowData without the SSID list

    gatewayRowData.push(''); // Empty cell for SSIDs
    gatewayRowData.push(gateway.gateway_ids.gateway_id);
    gatewayRowData.push(gateway.gateway_ids.eui);
    gatewayRowData.push(gateway.rssi);
    gatewayRowData.push(gateway.snr);
    gatewayRowData.push(gateway.location.latitude);
    gatewayRowData.push(gateway.location.longitude);
    gatewayRowData.push(gateway.location.altitude);

    sheet.appendRow(gatewayRowData);  // Write the new row of data
  }

  return ContentService.createTextOutput(JSON.stringify({ "status": "success" })).setMimeType(ContentService.MimeType.JSON);
}

function setup() {
  var doc = SpreadsheetApp.getActiveSpreadsheet();
  SCRIPT_PROP.setProperty("key", doc.getId());
}

This Google Sheets script is designed to handle HTTP GET and POST requests to log and process data from LoRaWAN devices into a Google Sheets spreadsheet.

Script Properties and Initialization:

HTTP Request Handling:

Response Handling:

Sheet Initialization:

Data Logging:

Gateway Metadata:

Response Generation:

The primary purpose of this script is to collect and organize data from LoRaWAN devices into a structured format within a Google Sheets spreadsheet. It handles both the data from the devices themselves (including SSIDs detected by the devices) and metadata from the gateways that received the uplink messages, ensuring all relevant information is logged efficiently.

Matlab Script

%% Calculate geoid coordinates of the esp32 board

%% fixed constants
% attenuation of signal - can be modified, depending on the local area
n = 3.6667;
% RSSI of the unit distance (1 meter) - need to be measured in advanced
A = -29;

%% Reference ellipsoid for World Geodetic System 1984
wgs84 = wgs84Ellipsoid('meter');

%% First end-device coordinates 

p(1).lat = 
p(1).long = 
p(1).altitude = 
rssi1 = 

% convert the coordinates from World Geodetic System to Earth-centered, Earth-fixed coordinate system
[p(1).x,p(1).y,p(1).z] = geodetic2ecef(wgs84,p(1).lat,p(1).long,p(1).altitude);

%calculate the distance between the first end-node and the gateway based on RSSI
dist1 = 10.^((A-rssi1)/(10*n));

%% Second end-device coordinates 
p(2).lat = 
p(2).long = 
p(2).altitude = 
rssi2 = 

% convert the coordinates from World Geodetic System to Earth-centered, Earth-fixed coordinate system
[p(2).x,p(2).y,p(2).z] = geodetic2ecef(wgs84,p(2).lat,p(2).long,p(2).altitude);

% calculate the distance between second end-device and the gateway based on the RSSI
dist2 = 10.^((A-rssi2)/(10*n));

%% Third end-device coordinates 
p(3).lat = 
p(3).long = 
p(3).altitude = 
rssi3 = 

% convert the coordinates from World Geodetic System to Earth-centered, Earth-fixed coordinate system
[p(3).x,p(3).y,p(3).z] = geodetic2ecef(wgs84,p(3).lat,p(3).long,p(3).altitude);

% calculate the distance between the second end-device and the gateway based on the RSSI
dist3 = 10.^((A-rssi3)/(10*n));

%% Fourth end-device coordinates 
p(4).lat = 
p(4).long = 
p(4).altitude = 
rssi4 = -

% convert the coordinates from World Geodetic System to Earth-centered, Earth-fixed coordinate system
[p(4).x,p(4).y,p(4).z] = geodetic2ecef(wgs84,p(4).lat,p(4).long,p(4).altitude);

% calculate the distance between the fourth end-device and the gateway based on the RSSI
dist4 = 10.^((A-rssi4)/(10*n));

%% Finding gateway coordinates

% calculate the initial Guess for GPS equations
x0 = (p(1).x + p(2).x + p(3).x + p(4).x) / 4;
y0 = (p(1).y + p(2).y + p(3).y + p(4).y) / 4;
z0 = (p(1).z + p(2).z + p(3).z + p(4).z) / 4;
Guess = [x0,y0,z0];

% matrix form - the coordinates of the four end-devices and distances btw them and gw
A = [p(1).x , p(2).x , p(3).x, p(4).x];
B = [p(1).y , p(2).y , p(3).y, p(4).y];
C = [p(1).z , p(2).z , p(3).z, p(4).z];
D = [dist1 , dist2 , dist3, dist4];

% vector A length
LA = length(A);

% solving GPS equations
% Result vector contains the ECEF coordinates of the gateway
[Result,~,~] = MVNewtonsInputs(Guess,A,B,C,D,LA);

% Convert the gateway coordinates from Earth-centered, Earth-fixed
% coordinate system to World Geodetic System 1984
[esp_lat,esp_lon,esp_alt] = ecef2geodetic(wgs84,Result(1),Result(2),Result(3))

The main MATLAB script calculates the geoid coordinates of an ESP32 board using RSSI values from four end-devices. It first defines constants for signal attenuation (`n`) and the RSSI at a unit distance (`A`). It uses the World Geodetic System 1984 (`wgs84`) as the reference ellipsoid.

For each end-device, the script assigns latitude, longitude, altitude, and RSSI values, then converts these geodetic coordinates to Earth-centered, Earth-fixed (ECEF) coordinates. Using the RSSI values, it calculates the distance between each end-device and the gateway.

An initial guess for the gateway’s coordinates is determined by averaging the ECEF coordinates of the four end-devices. The coordinates and distances are then arranged in matrix form, and the `MVNewtonsInputs` function is used to solve for the gateway’s ECEF coordinates. Finally, these ECEF coordinates are converted back to geodetic coordinates (latitude, longitude, altitude) using the `ecef2geodetic` function, giving the geoid coordinates of the ESP32 board.

Results

The table and map show the data collected from three gateways: mylps8, multitech-00009a75, and ttnd35. Only these three gateways were able to be received at the same time and from the same location. Although four gateways are typically needed for more accurate triangulation, it's very unlikely to receive signals from that many due to the urban density in Bucharest, so an estimate for a fourth was made.

The results are satisfactory, especially considering the significant signal disruption in an urban environment. The estimated error margin is around 200-300 meters. The gateway coordinates were manually entered by the user, which often results in unrealistic altitude values. Therefore, the altitude in the result does not accurately reflect reality.

Additionally, the LoRa packets sent by the ESP32 board also contain surrounding SSIDs identified by the WiFi module. These SSIDs are stored in Google Sheets, but due to privacy concerns, they have not been included in the displayed figure.

Conclusions

Hardware and Software Integration:

Data Collection and Decoding:

WiFi Scanning:

Geolocation Using RSSI:

Accuracy and Challenges:

Practical Implications:

Future Improvements:

Overall, the integration of hardware, software, and geolocation techniques proves to be a viable approach for IoT applications, with room for enhancements in gateway density, data accuracy, and the use of WiFi scans to provide additional context and data points for the geolocation process.

References

  1. Ertürk, M.A., Aydın, M.A., Büyükakkaşlar, M.T. and Evirgen, H., 2019. A survey on LoRaWAN architecture, protocol and technologies. Future internet, 11(10), p.216.
  2. Kwasme, H. and Ekin, S., 2019. RSSI-based localization using LoRaWAN technology. IEEE Access, 7, pp.99856-99866.