Miguel Montiel Vega
Published

IoT GPS Tracker with Movement Alert and LoRaWAN

This is a low-power IoT GPS tracker with motion detection, designed for real-time asset and vehicle monitoring.

IntermediateWork in progress8 hours146
IoT GPS Tracker with Movement Alert and LoRaWAN

Things used in this project

Hardware components

RAK19003
×1
RAK4630
×1
RAK12033
×1
RAK12500
×1
WisGate Edge Lite 2
Arduino WisGate Edge Lite 2
×1

Software apps and online services

Arduino IDE
Arduino IDE
The Things Stack
The Things Industries The Things Stack

Hand tools and fabrication machines

Multitool, Screwdriver
Multitool, Screwdriver

Story

Read more

Code

GPS Tracker for Assets/People with Movement Alert

C/C++
C++
// Project: GPS Tracker for Assets/People with Movement Alert
// Board: RAK4631 (Nordic NRF52840)
// Base: RAK1903
// Sensors: RAK12500 ZOE-M8Q GNSS GPS Module, RAK12033 IM-42652 6-axis Accelerometer
// LoRaWAN Platform: The Things Stack
 
#include <Wire.h>
#include <TinyGPS++.h> // For GPS
#include <SoftwareSerial.h> // If GPS uses SoftwareSerial, otherwise use Serial1/HardwareSerial
#include <LoRaWan-RAK4631.h> // LoRaWAN library for RAK4631
#include <SPI.h>
 
// Library for accelerometer (example, may vary based on exact IM-42652 model)
// #include <SparkFun_ICM-20948_Arduino_Library.h> // If using ICM-20948
 
// LoRaWAN Configuration (OTAA) - REPLACE WITH YOUR THE THINGS STACK CREDENTIALS!
uint8_t nodeDeviceEUI[8] = {0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX};
uint8_t nodeAppEUI[8] = {0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX};
uint8_t nodeAppKey[16] = {0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX};
 
// LoRaWAN Region Configuration
LoRaMacRegion_t loraWanRegion = LORAMAC_REGION_EU868;
 
// Pins for GPS (RAK12500 connects via UART, use Serial1 on RAK4631)
#define GPS_SERIAL Serial1 // RAK12500 connects to UART1 of RAK4631
#define GPS_BAUDRATE 9600
 
// Instances
TinyGPSPlus gps;
// ICM_20948_I2C accel; // If using ICM-20948
 
// Variables to store readings
float latitude, longitude, altitude;
float accel_x, accel_y, accel_z;
bool movement_detected = false;
 
void setup() {
  Serial.begin(115200);
  delay(100);
  Serial.println("Starting IoT GPS Tracker...");
 
  Wire.begin(); // For I2C accelerometer
 
  // Initialize GPS
  GPS_SERIAL.begin(GPS_BAUDRATE);
 
  // Initialize accelerometer (placeholder)
  // if (accel.begin()!= ICM_20948_Stat_Ok) {
  //   Serial.println("Could not find accelerometer, check wiring.");
  //   while (1);
  // }
 
  // Initialize LoRaWAN
  if (api.lorawan.setRegion(loraWanRegion)) {
    Serial.printf("LoRaWAN region configured to %d\n", loraWanRegion);
  } else {
    Serial.println("Failed to configure LoRaWAN region.");
    while (1);
  }
 
  if (api.lorawan.setAppKey(nodeAppKey) &&
     api.lorawan.setAppEUI(nodeAppEUI) &&
     api.lorawan.setDevEUI(nodeDeviceEUI)) {
    Serial.println("LoRaWAN credentials configured.");
  } else {
    Serial.println("Failed to configure LoRaWAN credentials.");
    while (1);
  }
 
  Serial.println("Attempting to join LoRaWAN network (OTAA)...");
  if (api.lorawan.join()) {
    Serial.println("Joined LoRaWAN network!");
  } else {
    Serial.println("Failed to join LoRaWAN network. Retrying...");
    while(1);
  }
  delay(2000);
}
 
void loop() {
  readSensors();
 
  // Send data if there's a new GPS fix or if movement is detected
  if (gps.location.isUpdated() || movement_detected) {
    sendLoRaWANData();
  }
 
  Serial.println("Entering low power mode...");
  api.system.sleep.all(30000); // Sleep for 30 seconds
}
 
void readSensors() {
  // Process GPS data
  while (GPS_SERIAL.available() > 0) {
    gps.encode(GPS_SERIAL.read());
  }
 
  if (gps.location.isValid()) {
    latitude = gps.location.lat();
    longitude = gps.location.lng();
    altitude = gps.altitude.meters();
    Serial.printf("GPS: Lat %.6f, Lng %.6f, Alt %.2f m\n", latitude, longitude, altitude);
  } else {
    Serial.println("GPS: No fix or invalid data.");
  }
 
  // Read accelerometer (placeholder)
  // accel.readSensor();
  // accel_x = accel.getAccelX_G();
  // accel_y = accel.getAccelY_G();
  // accel_z = accel.getAccelZ_G();
  // Serial.printf("Accel X: %.2f, Y: %.2f, Z: %.2f G\n", accel_x, accel_y, accel_z);
 
  // Simple movement detection logic
  // movement_detected = (abs(accel_x) > 0.1 || abs(accel_y) > 0.1 || abs(accel_z) > 0.1); // Movement threshold
  movement_detected = false; // Placeholder if actual accelerometer is not used
  if (movement_detected) {
    Serial.println("Movement detected!");
  }
}
 
void sendLoRaWANData() {
  // Payload format:
  // Byte 0-3: Latitude (float to int32 x1000000)
  // Byte 4-7: Longitude (float to int32 x1000000)
  // Byte 8-9: Altitude (int16)
  // Byte 10: Movement status (0=No, 1=Yes)
 
  uint8_t payload[11]; // Changed from 12 to 11 to match new byte calculation
  int32_t lat_int = (int32_t)(latitude * 1000000);
  int32_t lng_int = (int32_t)(longitude * 1000000);
  int16_t alt_int = (int16_t)altitude;
 
  payload[0] = (lat_int >> 24) & 0xFF;
  payload[1] = (lat_int >> 16) & 0xFF;
  payload[2] = (lat_int >> 8) & 0xFF;
  payload[3] = lat_int & 0xFF;
 
  payload[4] = (lng_int >> 24) & 0xFF;
  payload[5] = (lng_int >> 16) & 0xFF;
  payload[6] = (lng_int >> 8) & 0xFF;
  payload[7] = lng_int & 0xFF;
 
  payload[8] = (alt_int >> 8) & 0xFF;
  payload[9] = alt_int & 0xFF;
 
  payload[10] = movement_detected? 1 : 0;
 
  Serial.print("Sending LoRaWAN packet: ");
  for (int i = 0; i < sizeof(payload); i++) {
    Serial.printf("%02X ", payload[i]);
  }
  Serial.println();
 
  if (api.lorawan.send(sizeof(payload), payload, 1, true)) { // Port 1, confirmed
    Serial.println("LoRaWAN packet sent successfully.");
  } else {
    Serial.println("Failed to send LoRaWAN packet.");
  }
}
 
// Example Payload Formatter (Decoder) for The Things Stack (Custom Javascript)
/*
function decodeUplink(input) {
  var data = {};
  var bytes = input.bytes;
 
  // Decode latitude (4 bytes, int32, x1000000)
  data.latitude = (bytes[0] << 24 | bytes[1] << 16 | bytes[2] << 8 | bytes[3]) / 1000000.0;
 
  // Decode longitude (4 bytes, int32, x1000000)
  data.longitude = (bytes[4] << 24 | bytes[5] << 16 | bytes[6] << 8 | bytes[7]) / 1000000.0;
 
  // Decode altitude (2 bytes, int16)
  data.altitude = (bytes[8] << 8 | bytes[9]);
 
  // Decode movement status (1 byte, boolean)
  data.movement_detected = (bytes[10] === 1);
 
  return {
    data: data,
    warnings: [],
    errors: []
  };
}
*/

Credits

Miguel Montiel Vega
11 projects • 9 followers
Teacher at Maude Studio & Erasmus+ project member: "Developing Solutions to Sustainability Using IoT" (2022-1-PT01-KA220-VET-000090202)
Thanks to Jose Miguel Fuentes.

Comments