Attila Tőkés
Published © CC BY

Soracom Powered GNSS Tracker

A Soracom GNSS tracker with motion detection, data visualization, and rule-based alerts.

IntermediateFull instructions provided24 hours3,191

Things used in this project

Hardware components

Wio LTE EU Version - 4G, Cat.1, GNSS, Espruino Compatible
Seeed Studio Wio LTE EU Version - 4G, Cat.1, GNSS, Espruino Compatible
Board + 2 x LTE antenna + GPS antenna + Li battery The US and AU version should work as well. The JP version may not have GNSS.
×1
Inertial Measurement Unit (IMU) (6 deg of freedom)
Inertial Measurement Unit (IMU) (6 deg of freedom)
GY-521 or other MPU6050 based board. Other accelerometer IC should work as well, but code change may be needed.
×1

Software apps and online services

Arduino IDE
Arduino IDE
with the Wio LTE Tracker board and the Wio LTE for Arduino library installed
SORACOM Air IoT Connectivity Platform
SORACOM Air IoT Connectivity Platform
LTE / 3G connectivity
SORACOM Harvest
SORACOM Harvest
time series / GNSS data storage + visualization
SORACOM Lagoon
SORACOM Lagoon
data visualization, dashboards and alerts
SORACOM Funnel
SORACOM Funnel
route the data to AWS Iot
SORACOM Beam - Data Transfer Support
SORACOM Beam - Data Transfer Support
alternative for the SORACOM Funnel
AWS IoT
Amazon Web Services AWS IoT
AWS's IoT cloud service
AWS Lambda
Amazon Web Services AWS Lambda
server-less functions
AWS SNS
Amazon Web Services AWS SNS
notifications: email, SMS, push
AWS Quicksight
Amazon Web Services AWS Quicksight
+ IoT Analytics - data visualization / analysis
AWS Kinesis
Amazon Web Services AWS Kinesis
data streaming to S3
AWS S3
Amazon Web Services AWS S3
data storage

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)
to 3D print the enclosure
Hot glue gun (generic)
Hot glue gun (generic)
for the enclosure assembly
Soldering iron (generic)
Soldering iron (generic)
to solder the Groove cable to the MPU-6050 module

Story

Read more

Schematics

Wio LTE Connections

Data Flow

AWS Data Flow

Soracom Data Flow

Code

GY-521 / MPU-6050 Arduino Example Sketch

Arduino
based on https://create.arduino.cc/projecthub/Nicholas_N/how-to-use-the-accelerometer-gyroscope-gy-521-6dfc19
#include <Wire.h>
#include <WioLTEforArduino.h>

const int MPU=0x68; 
int16_t AcX,AcY,AcZ,Tmp,GyX,GyY,GyZ;

WioLTE Wio;

void setup(){
  for (int i = 20; i > 0; --i) {
    SerialUSB.println(i);
    delay(1000);
  }

  SerialUSB.println("### I/O Initialize.");
  Wio.Init();

  SerialUSB.println("### Power supply grove.");
  Wio.PowerSupplyGrove(true);
  
  Wire.begin();
  Wire.beginTransmission(MPU);
  Wire.write(0x6B); 
  Wire.write(0);    
  Wire.endTransmission(/*true*/);
  //SerialUSB.begin(9600);
}
void loop(){
  Wire.beginTransmission(MPU);
  Wire.write(0x3B);  
  Wire.endTransmission(/*false*/);
  Wire.requestFrom(MPU,12/*,true*/);  
  AcX=Wire.read()<<8|Wire.read();    
  AcY=Wire.read()<<8|Wire.read();  
  AcZ=Wire.read()<<8|Wire.read();  
  GyX=Wire.read()<<8|Wire.read();  
  GyY=Wire.read()<<8|Wire.read();  
  GyZ=Wire.read()<<8|Wire.read();  
  
  SerialUSB.print("Accelerometer: ");
  SerialUSB.print("X = "); SerialUSB.print(AcX);
  SerialUSB.print(" | Y = "); SerialUSB.print(AcY);
  SerialUSB.print(" | Z = "); SerialUSB.println(AcZ); 
  
  SerialUSB.print("Gyroscope: ");
  SerialUSB.print("X = "); SerialUSB.print(GyX);
  SerialUSB.print(" | Y = "); SerialUSB.print(GyY);
  SerialUSB.print(" | Z = "); SerialUSB.println(GyZ);
  SerialUSB.println(" ");
  delay(333);
}

AWS IoT - OneUserOneThing policy

JSON
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "iot:Connect",
      "Resource": [
        "arn:aws:iot:*:*:client/${iot:ClientId}"
      ],
      "Condition": {
        "Bool": {
          "iot:Connection.Thing.IsAttached": [
            "true"
          ]
        }
      }
    },
    {
      "Effect": "Allow",
      "Action": [
        "iot:Publish",
        "iot:Receive"
      ],
      "Resource": [
        "arn:aws:iot:*:*:topic/$aws/things/${iot:Connection.Thing.ThingName}/*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": "iot:Subscribe",
      "Resource": [
        "arn:aws:iot:*:*:topicfilter/$aws/events/presence/*/${iot:ClientId}",
        "arn:aws:iot:*:*:topicfilter/$aws/events/subscriptions/*/${iot:ClientId}",
        "arn:aws:iot:*:*:topicfilter/$aws/things/${iot:Connection.Thing.ThingName}/*"
      ]
    }
  ]
}

Grafana Dashboard Config (JSON)

JSON
{
  "__inputs": [
    {
      "name": "DS_HARVEST",
      "label": "Harvest",
      "description": "",
      "type": "datasource",
      "pluginId": "soracom-harvest-datasource",
      "pluginName": "SORACOM Harvest Datasource"
    }
  ],
  "__requires": [
    {
      "type": "grafana",
      "id": "grafana",
      "name": "Grafana",
      "version": "5.2.3"
    },
    {
      "type": "panel",
      "id": "graph",
      "name": "Graph",
      "version": "5.0.0"
    },
    {
      "type": "datasource",
      "id": "soracom-harvest-datasource",
      "name": "SORACOM Harvest Datasource",
      "version": "0.0.1"
    },
    {
      "type": "panel",
      "id": "soracom-map-panel",
      "name": "Soracom Map Panel",
      "version": "0.0.1"
    }
  ],
  "annotations": {
    "list": [
      {
        "builtIn": 1,
        "datasource": "-- Grafana --",
        "enable": true,
        "hide": true,
        "iconColor": "rgba(0, 211, 255, 1)",
        "name": "Annotations & Alerts",
        "type": "dashboard"
      }
    ]
  },
  "editable": true,
  "gnetId": null,
  "graphTooltip": 0,
  "id": null,
  "links": [],
  "panels": [
    {
      "activeQuerySettings": [
        {
          "highColor": "rgba(245, 54, 54, 0.9)",
          "highImageURL": "",
          "highValue": 100,
          "iconHeight": 40,
          "iconWidth": 20,
          "lineAnimate": false,
          "lineColor": "rgba(237, 129, 40, 0.89)",
          "lineDashColor": "#FFFFFF",
          "lowColor": "rgba(50, 172, 45, 0.97)",
          "lowImageURL": "",
          "lowValue": 0,
          "mediumColor": "rgba(237, 129, 40, 0.89)",
          "mediumImageURL": "",
          "refId": "A",
          "showLatestOnly": false,
          "showLine": false,
          "useCustomIcon": false,
          "valueKey": ""
        }
      ],
      "circleMaxSize": "14",
      "circleMinSize": "1",
      "colors": [
        "rgba(245, 54, 54, 0.9)",
        "rgba(237, 129, 40, 0.89)",
        "rgba(50, 172, 45, 0.97)"
      ],
      "datasource": "${DS_HARVEST}",
      "decimals": 0,
      "esMetric": "Count",
      "geoJSON": "",
      "gridPos": {
        "h": 14,
        "w": 13,
        "x": 0,
        "y": 0
      },
      "hideEmpty": false,
      "hideZero": false,
      "id": 2,
      "initialZoom": "12",
      "links": [],
      "locationData": "countries",
      "mapCenter": "(0°, 0°)",
      "mapCenterLatitude": 0,
      "mapCenterLongitude": 0,
      "maxDataPoints": 1,
      "mouseWheelZoom": true,
      "querySettings": {
        "A": {
          "highColor": "rgba(245, 54, 54, 0.9)",
          "highImageURL": "",
          "highValue": 100,
          "iconHeight": 40,
          "iconWidth": 20,
          "lineAnimate": false,
          "lineColor": "rgba(237, 129, 40, 0.89)",
          "lineDashColor": "#FFFFFF",
          "lowColor": "rgba(50, 172, 45, 0.97)",
          "lowImageURL": "",
          "lowValue": 0,
          "mediumColor": "rgba(237, 129, 40, 0.89)",
          "mediumImageURL": "",
          "refId": "A",
          "showLatestOnly": false,
          "showLine": false,
          "useCustomIcon": false,
          "valueKey": ""
        }
      },
      "settingsUpdated": true,
      "showLegend": true,
      "showMostRecentOnly": false,
      "stickyLabels": false,
      "tableQueryOptions": {
        "geohashField": "geohash",
        "latitudeField": "latitude",
        "longitudeField": "longitude",
        "metricField": "metric",
        "queryType": "geohash"
      },
      "targets": [
        {
          "datatype": "map",
          "devicetype": "subscribers",
          "hide": false,
          "refId": "A",
          "target": "295050910083405",
          "type": "timeseries"
        }
      ],
      "thresholds": "0,10",
      "title": "GNSS Tracker",
      "type": "soracom-map-panel",
      "unitPlural": "",
      "unitSingle": "",
      "valueName": "total",
      "zoomToPoints": true
    },
    {
      "alert": {
        "conditions": [
          {
            "evaluator": {
              "params": [
                46.7000,
                46.7999
              ],
              "type": "outside_range"
            },
            "operator": {
              "type": "and"
            },
            "query": {
              "params": [
                "A",
                "5m",
                "now"
              ]
            },
            "reducer": {
              "params": [],
              "type": "avg"
            },
            "type": "query"
          },
          {
            "evaluator": {
              "params": [
                23.600,
                23.699
              ],
              "type": "outside_range"
            },
            "operator": {
              "type": "or"
            },
            "query": {
              "params": [
                "B",
                "5m",
                "now"
              ]
            },
            "reducer": {
              "params": [],
              "type": "avg"
            },
            "type": "query"
          }
        ],
        "executionErrorState": "alerting",
        "frequency": "60s",
        "handler": 1,
        "name": "GNSS Position",
        "noDataState": "no_data",
        "notifications": [
          {
            "id": 5
          },
          {
            "id": 5
          },
          {
            "id": 5
          }
        ]
      },
      "aliasColors": {},
      "bars": false,
      "dashLength": 10,
      "dashes": false,
      "datasource": "${DS_HARVEST}",
      "fill": 1,
      "gridPos": {
        "h": 7,
        "w": 11,
        "x": 13,
        "y": 0
      },
      "id": 4,
      "legend": {
        "avg": false,
        "current": false,
        "max": false,
        "min": false,
        "show": true,
        "total": false,
        "values": false
      },
      "lines": true,
      "linewidth": 1,
      "links": [],
      "nullPointMode": "null",
      "percentage": false,
      "pointradius": 5,
      "points": false,
      "renderer": "flot",
      "seriesOverrides": [
        {
          "alias": "lng",
          "yaxis": 2
        }
      ],
      "spaceLength": 10,
      "stack": false,
      "steppedLine": false,
      "targets": [
        {
          "datatype": "standard",
          "devicetype": "subscribers",
          "properties": "lat",
          "refId": "A",
          "target": "295050910083405",
          "type": "timeseries"
        },
        {
          "datatype": "standard",
          "devicetype": "subscribers",
          "properties": "lng",
          "refId": "B",
          "target": "295050910083405",
          "type": "timeseries"
        }
      ],
      "thresholds": [
        {
          "colorMode": "critical",
          "fill": true,
          "line": true,
          "op": "lt",
          "value": 46.7000
        },
        {
          "colorMode": "critical",
          "fill": true,
          "line": true,
          "op": "gt",
          "value": 46.7999
        }
      ],
      "timeFrom": null,
      "timeShift": null,
      "title": "GNSS Position (latitude, longitude)",
      "tooltip": {
        "shared": true,
        "sort": 0,
        "value_type": "individual"
      },
      "type": "graph",
      "xaxis": {
        "buckets": null,
        "mode": "time",
        "name": null,
        "show": true,
        "values": []
      },
      "yaxes": [
        {
          "format": "short",
          "label": "Latitude",
          "logBase": 1,
          "max": null,
          "min": null,
          "show": true
        },
        {
          "format": "short",
          "label": "Longitude",
          "logBase": 1,
          "max": null,
          "min": null,
          "show": true
        }
      ],
      "yaxis": {
        "align": false,
        "alignLevel": null
      }
    }
  ],
  "schemaVersion": 16,
  "style": "dark",
  "tags": [],
  "templating": {
    "list": []
  },
  "time": {
    "from": "now-15m",
    "to": "now"
  },
  "timepicker": {
    "refresh_intervals": [
      "1m",
      "5m",
      "15m",
      "30m",
      "1h",
      "2h",
      "1d"
    ],
    "time_options": [
      "5m",
      "15m",
      "1h",
      "6h",
      "12h",
      "24h",
      "2d",
      "7d",
      "30d"
    ]
  },
  "timezone": "",
  "title": "GNSS Tracker",
  "uid": "2u-sutZZk",
  "version": 16
}

AWS IAM - Soracom service user policy

JSON
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "iot:Connect",
                "iot:Publish"
            ],
            "Resource": [
                "arn:aws:iot:*:*:topic/gnssTracker",
                "arn:aws:iot:*:*:client/*"
            ]
        }
    ]
}

AWS: Funnel to Device Shadow Lamda Function code

Python
import logging
import json
import boto3

client = boto3.client('iot-data')
iot_client = boto3.client('iot')

# Setup logger
logger = logging.getLogger()
logger.setLevel(logging.INFO)

def lambda_handler(event, context):
    print(event)
    
    # Parse the JSON message 
    #jsonObj = json.loads(event)
    jsonObj = event
    
    imsi = jsonObj['imsi']
    payload = jsonObj['payloads']
    
    print("IMSI: ")
    print(imsi)
    print("Payload: ")
    print(payload)
  
    # iot thing lookup
    thingsResponse = iot_client.list_things(
        maxResults = 1,
        attributeName = "soracom_imsi",
        attributeValue = imsi
        #thingTypeName = '3d-printer'
    )
    
    if len(thingsResponse['things']) == 1:
        # thing found
        thingName = thingsResponse['things'][0]['thingName']
        print("Thing found: " + thingName)
            
    else:
        # thing not found
        print("Thing not found")
        return {
            'statusCode': 200,
            'body': json.dumps('Thing not found!')
        }
        
    # update shadow
    response = client.update_thing_shadow(
        thingName = thingName,
        payload = json.dumps({
            'state': {
                'reported': payload
            }
        }
        )
    )
    
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

Soracom-GNSS-Tracker

Arduino sketches, AWS and Soracom configuration file, 3D design files

Credits

Attila Tőkés

Attila Tőkés

36 projects • 220 followers
Software Engineer experimenting with hardware projects involving IoT, Computer Vision, ML & AI, FPGA, Crypto and other related technologies.

Comments