Project tutorial
Sandro: Your Personal GPS Assistant

Sandro: Your Personal GPS Assistant © LGPL

Sandro let you monitor things ore people, providing you their position and status.

  • 5,511 views
  • 2 comments
  • 14 respects

Components and supplies

Apps and online services

About this project

What's that?

Sandro is a device that lets you monitor people and things, and tell you continuously their location, and the values of the sensors attached to it.

You can set limit values for the sensors, and when these are exceeded an SMS alert is sent to you.

You can also set restrictions from some places, in order to be alerted when the device is too closer or goes to far from these places.

It's also possible to communicate with the device through SMS, in order to get information about it.

What did you used to build it?

  • GPS antenna (included in Linkit ONE kit)
  • Battery (included in Linkit ONE kit)
  • GSM antenna and a SIM with internet connection, or Wi-Fi antenna (antennas included in Linkit ONE kit)
  • Web Host

I have everything needed, what do I do now?

If this is the first time you are using Linkit ONE, please visit this page from Mediatek Labs web site. You'll find a guide to setup the Arduino IDE and the board.

When you have your Google Maps API key, you will need to enable Javascript and Street View image API, in the Google Developer Console

Now put these file in the same directory on a web server:

Set your Linkit ONE to Mass Storage Bootup mode, and put "settings.JSON" in its internal storage.

Put the board in UART mode again, and load "Sandro.ino" on it with the Arduino IDE.

Now everything should be ready to work, so just power on Linkit one with the battery.

How do I use it?

Go to your web server and open "index.html".

The browser will ask you to allow the page to get your coordinates based on your internet connection, in order to see it on the map.

If there is an error while getting you location, it will set it to New York, or if your browser doesn't support geolocation, it will set it to Siberia or it will set your last known location, if there is one.

Then you should see something like this:

The blue marker is you and all the other markers are the devices you are monitoring.

When a marker is red, means that we haven't got news from it for more than 2 minutes. When it's yellow, the device is sending data, but there are less than 4 visible satellites, so the coordinates he sends are probably not correct. The green color instead indicates that the device is online and there are more than 3 satellites visible.

The marker starts bouncing when there is an alert (restriction violated or sensor alert) even if its color is yellow.

On the top there are your latitude and longitude, and you can edit them and press Update to make the blue marker move, and the distances from all the devices updated.

With the "Click n' set" function, you can change your location simply by clicking on the map.

Then press "Find me!" to restore your true position.

Pressing "Show charts" reduces the height of the map, to let you see the charts.

Under your location there are the informations about the selected device, which you can change from the dropdown menu.

When the web page is closed all data about you and the devices are saved in the "save.JSON" file.

How do I control Sandro if I don't have an internet connection?

You can monitor it by sending SMS like this:

To receive informations about the device location, send a message containing the word "where".

For the value of a sensor, write its name in the message.

To store data on the device's storage (for example when it's not connected to internet)

the massage must contain "log" and "start", "begin" or "enable". To disable it send "log" and "stop", "disable" or "end".

The data will be saved on a file in the local memory (or microSD) of the device, named "YYYY-MM-DD.txt", so a new file is created every day.

As you can see, if there is an alert, an SMS is sent every 3 minutes.

Nice, but how did you manage to do that?

settings.json:

This is the json file that we previously put in the Linkit One.

As you can see, you can set your pubnub keys, the device name and your phone number.

To add restrictions, just add their coordinates, minimum and maximum distance in the "places" json array.

The restriction can be for a static location, or for another device. if you set the "type" to "device", it will subscribe to the other's pubnub channel to get its position and calculate the distance in real-time.

For the sensors, you can change their limit values, but if you add or remove sensors, you will also need to edit the Linkit ONE's sketch.

Sandro.ino

Chose where to save the log file:

#define STORAGE LFlash // Use Internal Flash storage

// #define STORAGE LSD // Use SD card storageode>Chose GPRS or Wi-Fi connection:

//LWiFiClient c; //Uncomment this for Wi-Fi connection

LGPRSClient c; //Uncomment this for GPRS connection

..

while (!LGPRS.attachGPRS("your_apn", "username", "password")) delay(1000);

//while (0 == LWiFi.connect(WIFI_AP, LWiFiLoginInfo(WIFI_AUTH, WIFI_PASSWORD)))delay(1000); //Uncomment this line if you want o use WI-Fiode>

If you use Wi-Wi you need to set the AP name and password

#define WIFI_AP "AP_name"

#define WIFI_PASSWORD "password"

#define WIFI_AUTH LWIFI_WPA

You can set how often the device will publish messages on pubnub channel, and how often it will send SMS alerts:

const int pubTimeout = 5000, alertTimeout = 300000 ; //timeout for publishing pubnub messages and timout for sending SMS alertode>In the setup we start GPS, get informations from settings.json  and enable internet connection.

void readSensors():

This function reads your sensors values and put them in the json that will be sent on the pubnub channel.

So here you will need to read your sensors and put their  values in the json with something like this:

message["Light_sensor"] = analogRead(sensorPin);

The name of the sensor must correspond to what you written in setting file.

In this demostration, I used random value to simulate temperature and humidity sensor. 

message["Temperature"] = random(5, 30);

message["Humidity"] = random(10, 90);ode>

void checkSensors()

Called after readSensors(), checks if the sensors values don't excede limit values.

void checkPlaces()

Controls all the restrictions. if it's from a location, it calculates the distance from there, but if it's from a device it first subscribe to its channel, in order to get its coordinates.

The other functions are quite simple.

index.htmlHere you need to change your pubnub and Google Maps Keys:

save.JSON>

This file works as "settings.json", you can edit it accordin to your devices and your sensors

save_data.php

This file is called by index.html when you close the web page in order to save data:

I have a grove starter kit, what can I do?

You can do great things I'd say! This project is not for a specific type of sensors, so you can plug in every thing you want. The grove kit has some interesting sensor and they are very easy to use.

Lets make an example for a real temperature and humidity :

- First off all we go here to see how to use the sensor.

- We include libraries and initialze variables

#include "DHT.h" #define DHTPIN 2 // what pin we're connected to #define DHTTYPE DHT22 // DHT 22 (AM2302) DHT dht(DHTPIN, DHTTYPE);ode>

void setup()

{

..........

    dht.begin();

..........

}ode>- Now in the readSensors function, we read the sensor's values and put them in the json message

void readSensors()

{

    ..........

    float t = 0.0;

    float h = 0.0;

    if(dht.readHT(&t, &h))

    {

        message["Temperature"] = t;

        message["Humidity"] = h;

    }

    ..........

}

What else can I do?

The device provides you also many other GPS informations, and the battery level, so it's up to your immaginatio to think how to use these data.

What will you do?

since the beginning, this project had to have a touchscreen display on it, but the display I already had did't worked with Linkit ONE.

I had to submit the project before the deadline of the mediatek smart cities contest, so I decided to make it without the display for now.

When I'll find it, I'll continue with the project.

Why did you call it Sandro?

Sandro (Alessandro) is a friend of mine we both study at the university of Padua.

One day I told His "what about if I give your name to one of my projects?" 

And that's all

Code

Sandro.inoC/C++
#include <ArduinoJson.h>
#include <LGSM.h>
#include <LBattery.h>
#include <LFlash.h>
#include <LStorage.h>
#include <LGPS.h>
#include <LGPRS.h>
#include <LGPRSClient.h>
#include <LWiFi.h>
#include <LWiFiClient.h>

#define STORAGE LFlash          // Use Internal Flash storage
// #define STORAGE LSD           // Use SD card storage

#define WIFI_AP "AP_name"
#define WIFI_PASSWORD "password"
#define WIFI_AUTH LWIFI_WPA

const char pubnubServer[] = "pubsub.pubnub.com";
const int pubTimeout = 5000, alertTimeout = 300000 ; //timeout for publishing pubnub messages and timout for sending SMS alert
unsigned long pubTime = 0, alertTime = 0;
boolean enable_log = false; //If true, will log data into a file
String subTimetoken = "0"; //Used for set pubnub subscribe timetoken

DynamicJsonBuffer JSONBuffer;
JsonObject& message  = JSONBuffer.createObject(); //JSON object containing data to send trought pubnub
JsonObject& data = JSONBuffer.createObject();  //JSON object containg limit values for distances and sensors

double latitude = 0.00;
double longitude = 0.00;
float altitude = 0.00;
float dop = 100.00;  //Horizontal dilution of position
float geoid = 0.00;  //Height of geoid
float kn_speed = 0.00, kh_speed = 0.00; // Speed in knots and speed in km/h
float track_angle = 0.00;
int fix = 0;
int hour = 0, minute = 0, second = 0;
int sat_num = 0;
int day = 0, month = 0, year = 0;
String time_format = "00:00:00", date_format = "00:00:0000";
String lat_format = "0000.000000", lon_format = "0000.000000";

//LWiFiClient c; //Uncomment this for Wi-Fi connection
LGPRSClient c; //Uncomment this for GPRS connection

const char *phoneNum = "";  //Phone number for SMS and emergency alert
const char *pubKey = "";
const char *subKey = "";
const char *channel = "";
String status = ""; //A string rappresenting the actual status of the device


void setup()
{
  LGPS.powerOn();
  STORAGE.begin();
  LFile myFile = STORAGE.open("settings.json");
  if (myFile)
  {
    String str = "";
    myFile.seek(0);
    while (myFile.available())str += (char)myFile.read();
    myFile.close();
    JsonObject& j = JSONBuffer.parseObject(str);
    phoneNum = j["phoneNum"];
    pubKey = j["pubKey"];
    subKey = j["subKey"];
    channel = j["name"];
    data["places"] = j["places"];
    data["sensors"] = j["sensors"];
    while (!LSMS.ready())delay(1000); //Comment this line if there is not a SIM
    while (!LGPRS.attachGPRS("your_apn", "username", "password")) delay(1000); 
    //while (0 == LWiFi.connect(WIFI_AP, LWiFiLoginInfo(WIFI_AUTH, WIFI_PASSWORD)))delay(1000); //Uncomment this line if you want o use WI-FI
  }
  else while (true);

}

void loop()
{
  status = "";
  checkPlaces();
  checkSensors();
  if (enable_log)logData();
  message["battery_level"] = LBattery.level();
  if (status.length() == 0)status = "OK";
  else
  {
    if (millis() - alertTime > alertTimeout)
    {
      sendSMS(phoneNum , status);
      alertTime = millis();
    }
  }
  if (LSMS.available())
  {
    readSMS();
    LSMS.flush();
  }
  if (millis() - pubTime >= pubTimeout)
  {
    sendData();
    pubTime = millis();
  }
}

void checkSensors()
{
  readSensors();
  for (int i = 0; i < data["sensors"].size(); ++i)
  {
    String name = data["sensors"][i]["name"]; //Sensor name
    int val = message[name]; //sensor value
    if (val > data["sensors"][i]["max"] || val < data["sensors"][i]["min"]) //If i we are over a limit
    {
      status += name;
      status += " allarm: ";
      status += val;
      status += ". ";
    }
  }
}

void logData()
{
  String file_name = date_format;
  file_name += ".txt"; //file name: YYY-MM-DD.txt so all data collected in the same day, will be in the same log file
  LFile logFile = STORAGE.open(file_name.c_str(), FILE_WRITE);
  if (logFile)
  {
    message.printTo(logFile);
    logFile.write('\n');
    logFile.close();
  }
}

void readSensors()
{
  message["Temperature"] = random(5, 30);
  message["Humidity"] = random(10, 90);
}

void checkPlaces()
{
  getGPSData();
  if (true /*getGPSData()>3*/) //if we got atleast 3 satellites
  {
    for (int i = 0; i < data["places"].size(); ++i)
    {
      double aLat = -1, aLon = -1;
      String p = data["places"][i]["type"];
      if (strcmp(data["places"][i]["type"], "location") == 0)  //if the restriction is from a geografic point
      {
        aLat = data["places"][i]["lat"];
        aLon = data["places"][i]["lon"];
      }
      else //if the restriction is from another Sandro
      {
        c.flush();
        if (c.connect(pubnubServer, 80))
        {
          //c.setTimeout(2000);
          c.flush();
          c.print("GET /subscribe/");
          c.print(subKey);
          c.print("/");
          c.print((const char *)data["places"][i]["name"]);
          c.print("/0/");
          c.print(subTimetoken);
          c.println(" HTTP/1.1");
          c.println("Host: pubsub.pubnub.com");
          c.println("User-Agent: Linkit-ONE");
          c.println("Connection: close");
          c.println();
          if (c.find("\r\n\r\n")) //search for the body of the response
          {
            String response = "";
            while (c.available())response += (char)c.read();
            JsonArray& jResp = JSONBuffer.parseArray(response); 
            if (jResp.size() > 1)
            {
              JsonArray& msgs = jResp[0]; //A JSON array containing th messages post to the channel since last time we checcked
              if (msgs.size() > 0)
              {
                aLat = msgs[msgs.size() - 1]["lat"];
                aLon = msgs[msgs.size() - 1]["lon"];
              }
              subTimetoken = (const char*)jResp[1]; //Get the timetoken for the next subscribe request
            }
          }
          else //There aren't new messages on this channel
          {
            while (c.available())c.read(); // Not realy needed, but boh...just in case.
            subTimetoken = "0";
          }
          c.stop();
        }
      }
      if ((aLat != -1) && (aLon != -1))
      {
        double d = dist(aLat, aLon, latitude, longitude);
        if ((d > data["places"][i]["max"]) || (d < data["places"][i]["min"]))  //If we violated the distance restriction, note in in the device status
        {
          status += "Restriction ";
          status += i + 1;
          status += " violated: ";
          status += d;
          status += "km. ";
        }
      }
    }
  }
}


void sendData()
{
  message["status"] = status.equals("OK")?"allert" : status; //Al the other data are alredy in the message, so only the status is now left
  c.flush();
  if (c.connect(pubnubServer, 80))
  {
    c.flush();
    c.print("GET /publish/");
    c.print(pubKey);
    c.print("/");
    c.print(subKey);
    c.print("/0/");
    c.print(channel);
    c.print("/0/");
    String m = "";
    message.printTo(m); //I don't konw why, but message.printTo(c) don't always works
    c.print(m);
    c.println(" HTTP/1.1");
    c.println("Host: pubsub.pubnub.com");
    c.println("User-Agent: Linkit-ONE");
    c.println("Connection: close");
    c.println();
    if (c.find("\r\n\r\n")) //Search the body of the response
    {
      String response = "";
      while (c.available())response += (char)c.read();
      JsonArray& jResp = JSONBuffer.parseArray(response); //An array containing the esit of our publish request
      if (strcmp(jResp[1], "Sent") == 0)
      {
        //Serial.println("Message sent");
      }
    }
    else while (c.available())c.read();
    c.stop();
  }
}

boolean sendSMS(const char *num, String text)
{
  LSMS.beginSMS(num);
  LSMS.print(text);
  return LSMS.endSMS();
}


double dist(double lat1, double lon1, double lat2, double lon2)
{
  const double R = 6378.137;

  lat1 = PI * lat1 / 180;
  lat2 = PI * lat2 / 180;
  lon1 = PI * lon1 / 180;
  lon2 = PI * lon2 / 180;
  double d = R * acos(sin(lat2) * sin(lat1) + cos(lat2) * cos(lat1) * cos(fabs(lon1 - lon2)));
  return d;
}

void readSMS()
{
  String msg = "";
  String response = "";
  char number[20] = {'\0'};
  LSMS.remoteNumber(number, sizeof(number)); //Get the number of the sender
  while (LSMS.peek() > 0) msg += (char)LSMS.read(); //Get the content of the SMS
  msg.toLowerCase();
  if (msg.indexOf("where") > -1) //Maybe the sender wat to know our location
  {
    if (sat_num > 3) response += "I'm right here: "; //There are enought visible satellites, so the coordinates are attendible
    else response += "I'm not sure of my location, but before I was here: ";
    //Build a link to our position in Google Maps
    response += "http://www.google.com/maps?q=";
    response += lat_format;
    response += ",";
    response += lon_format;
    sendSMS(number, response);
    return;
  }
  for (int i = 0; i < data["sensors"].size(); ++i)  //Check if the message contains the name of one of the sensors
  {
    String lowerCase = (const char*) data["sensors"][i]["name"];
    lowerCase.toLowerCase();
    if (msg.indexOf(lowerCase) > -1)
    {
      response += (const char*) data["sensors"][i]["name"];
      response += " value is ";
      String name = data["sensors"][i]["name"]; //Sensor name
      int val = message[name]; //sensor value
      response += val;
      sendSMS(number, response);
      return;
    }
  }
  if (msg.indexOf("log") > -1) //Something about log
  {
    if ((msg.indexOf("start") > -1) || (msg.indexOf("begin") > -1) || (msg.indexOf("enable") > -1))
    {
      if (enable_log)response += "Log is already enabled";
      else
      {
        enable_log = true;
        response += "OK, I started logging";
      }
      sendSMS(number, response);
      return;
    }
    else if ((msg.indexOf("stop") > -1) || (msg.indexOf("end") > -1) || (msg.indexOf("disable") > -1))
    {
      if (!enable_log)response += "Log is already disabled";
      else
      {
        enable_log = false;
        response += "OK, I disabled logging";
      }
      sendSMS(number, response);
      return;
    }
    else
    {
      response += "Log is ";
      response += enable_log ? "enabled" : "disabled";
      sendSMS(number, response);
      return;
    }
  }
  response += "I don't understand wat you want, but here is everything I know: ";
  message.printTo(response);
  sendSMS(number, response);
  return;
}

byte getGPSData()
{
  gpsSentenceInfoStruct info;
  LGPS.getData(&info);
  if (info.GPGGA[0] == '$')
  {
    String str = (char*)(info.GPGGA);
    str = str.substring(str.indexOf(',') + 1);
    hour = str.substring(0, 2).toInt();
    minute = str.substring(2, 4).toInt();
    second = str.substring(4, 6).toInt();
    time_format = "";
    time_format += hour;
    time_format += ":";
    time_format += minute;
    time_format += ":";
    time_format += second;
    str = str.substring(str.indexOf(',') + 1);
    latitude = convert(str.substring(0, str.indexOf(',')), str.charAt(str.indexOf(',') + 1) == 'S');
    int val = latitude * 1000000;
    String s = String(val);
    lat_format = s.substring(0, (abs(latitude) < 100) ? 2 : 3);
    lat_format += '.';
    lat_format += s.substring((abs(latitude) < 100) ? 2 : 3);
    str = str.substring(str.indexOf(',') + 3);
    longitude = convert(str.substring(0, str.indexOf(',')), str.charAt(str.indexOf(',') + 1) == 'W');
    val = longitude * 1000000;
    s = String(val);
    lon_format = s.substring(0, (abs(longitude) < 100) ? 2 : 3);
    lon_format += '.';
    lon_format += s.substring((abs(longitude) < 100) ? 2 : 3);

    str = str.substring(str.indexOf(',') + 3);
    fix = str.charAt(0) - 48;
    str = str.substring(2);
    sat_num = str.substring(0, 2).toInt();
    str = str.substring(3);
    dop = str.substring(0, str.indexOf(',')).toFloat();
    str = str.substring(str.indexOf(',') + 1);
    altitude = str.substring(0, str.indexOf(',')).toFloat();
    str = str.substring(str.indexOf(',') + 3);
    geoid = str.substring(0, str.indexOf(',')).toFloat();

    if (info.GPRMC[0] == '$')
    {
      str = (char*)(info.GPRMC);
      int comma = 0;
      for (int i = 0; i < 60; ++i)
      {
        if (info.GPRMC[i] == ',')
        {
          comma++;
          if (comma == 7)
          {
            comma = i + 1;
            break;
          }
        }
      }
      str = str.substring(comma);
      kn_speed = str.substring(0, str.indexOf(',')).toFloat();
      kh_speed = kn_speed * 1.852;
      str = str.substring(str.indexOf(',') + 1);
      track_angle = str.substring(0, str.indexOf(',')).toFloat();
      str = str.substring(str.indexOf(',') + 1);
      day = str.substring(0, 2).toInt();
      month = str.substring(2, 4).toInt();
      year = str.substring(4, 6).toInt();
      date_format = "20";
      date_format += year;
      date_format += "-";
      date_format += month;
      date_format += "-";
      date_format += day;
      message["lat"] = lat_format;
      message["lon"] = lon_format;
      message["speed"] = kh_speed;
      message["sat_num"] = sat_num;
      return sat_num;
    }
  }
  return 0;
}


float convert(String str, boolean dir)
{
  double mm, dd;
  int point = str.indexOf('.');
  dd = str.substring(0, (point - 2)).toFloat();
  mm = str.substring(point - 2).toFloat() / 60.00;
  return (dir ? -1 : 1) * (dd + mm);
}
index.htlmHTML
<!DOCTYPE html>
<html>
    <head>
        <title>Sandro</title>
        <link href="style.css" rel="stylesheet">
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
        <script src="Chart.js"></script>
        <script src="http://cdn.pubnub.com/pubnub-3.7.15.min.js"></script>
    </head>
    <body  style="background-color:#20351C;">
        <div id="top">
        <div style="margin: 0px 0px 0px 50px;">
            <h2>MY LOCATION</h2>
            <div style="margin: 10px 0px 0px 70px; display: inline-block;">
                <h3 style="display: inline-block;">Latitude</h3>
                <input  style="display: inline-block;" type="text" id="myLat" size="9" >
                <h3 style="margin: 0px 10px 0px 0px;display: inline-block;">Longitude</h3>
                <input type="text" id="myLon" size="9" >
                <button style="margin: 0px 20px 0px 30px;" onclick="updateLocation()">Update</button>
                <button onclick="getLocation()">Find me!</button>
            </div>
            <div style="margin: 0px 0px 0px 70px;">
                <h3 style="display: inline-block;" >Click n' set</h3>
                <div class="checkbox" style="border: 2px solid white;" >
  		            <input type="checkbox" id="clicknset" onclick="clickNSet()"/>
	  	            <label for="clicknset"></label>
	  	        </div>
	  	        <h3 style="display: inline-block;" >Show path</h3>
                <div class="checkbox" style="border: 2px solid white;" >
  		            <input type="checkbox" id="showpath" onclick="showPath()" checked="checked"/>
	  	            <label for="showpath"></label>
	  	        </div>
	  	        <button id="show_charts">Show charts</button>
  	        </div>
        </div>
        <hr>
	    <div style="margin: 0px 0px 0px 50px;" id="box"><select name="sel" id="sel" style="background-color: #B1FCE7; " onchange="selected()"></select><h3> <a>Latitude</a><input type="text" size="9" id="lat" readonly><a>Longitude</a><input type="text" size="9" id="lon" readonly><a>Distance</a><input type="text" size="9" id="dist" readonly><a>Speed</a><input type="text" size="9" id="speed" readonly></h3>
	    </div>
	    </div>
        <div id="map"></div>
        <div id="charts" style="width: 100%"></div>
        <script type="text/javascript">
            
            var trace = true;
            var map;
            var data; 
            var save; //Used to save data when we close the browser
       	    var myLocation; //Our coordinates
       	    var myMarker; //A marker on our location on the map
       		var my_uuid = PUBNUB.uuid();  //Needed only if we'll use pubnub presence API
            var charts = new Array();
       		var cDiv = document.getElementById("charts");
       		var mapHeight;
       		
            pubnub = PUBNUB
            ({                          
            	publish_key   : 'YOUR_PUBLISH_KEY',
        	    subscribe_key : 'YOUR_SUBSCRIBE_KEY',
        	    uuid: my_uuid
    	     });
    	    
    	    var saveFile = new XMLHttpRequest();
            saveFile.open("GET", "save.JSON", false); //Open file containing settings and previous data
    	    saveFile.onreadystatechange = function ()
   		    {
           		if(saveFile.readyState === 4)
           		{
           		    if(saveFile.status === 200 || saveFile.status == 0)
               		{
               		    var content = saveFile.responseText;
                        data = JSON.parse(content);
                        save = JSON.parse(content);
                        var channels = new Array();
                        var channelReg = 0;
                        for(var i=0; i <  data["sandros"].length; ++i) 
                        {
                            pubnub.subscribe(
                            {                                     
            				    channel : data["sandros"][i]["name"],
        				        message : newMessage,
        				        uuid: my_uuid, 
        				        connect: function()
        				        {
        				            channelReg++;
        				            if(channelReg == data["sandros"].length) //If we registered to all the channels
        				            {
        				                updateLabels();
        				                $( window ).resize(function(){});
        				                $("#show_charts").click(function()
        				                {
                                            if($(this).text() == "Show charts") 
                                            {
                                                $(this).html('Hide charts')
                                                $("#map").animate({height:'300'});
                                            }
                                            else 
                                            {
                                                $(this).html('Show charts');                                                
                                                $("#map").animate({height: mapHeight});
                                            }
                                        });
                                        setInterval(function () //Check if the devices ar still alive
                                        {
											var now = new Date();
											for(var j=0; j <  data["sandros"].length; ++j)
											{
											    var sDate = new Date(data["sandros"][j]["date_time"]);
												if((now.getTime()-sDate.getTime())> 120000)
												{
													data["sandros"][j]["marker"].setIcon('http://maps.google.com/mapfiles/ms/icons/red-dot.png');
												}
											}
										},5000);
        				            }
        				        }
        				        
    					    });
                            var s = document.getElementById("sel");
						    var option = document.createElement("option");
						    option.text = data["sandros"][i]["name"];
						    s.add(option);   
                        }
                        createCharts();
           		    }
   			    }
   			    window.onbeforeunload = closing;
   		    }
   		    saveFile.send(null);
   		    
   		    
            function newMessage(message, env, channel)
            {
        	    for(var i=0; i <  data["sandros"].length; ++i)
        	    {
            	    if(channel == data["sandros"][i]["name"])
        	        {
            	        data["sandros"][i]["lat"] = message["lat"];
            	        data["sandros"][i]["status"] = message["status"];
        	            data["sandros"][i]["lon"] = message["lon"];
        	            data["sandros"][i]["speed"] = message["speed"];
        	            data["sandros"][i]["date_time"] = new Date();
        	            data["sandros"][i]["marker"].setPosition(new google.maps.LatLng(message["lat"],message["lon"]));
        	            data["sandros"][i]["line"].setPath([myLocation,data["sandros"][i]["marker"].getPosition()]);
        	            data["sandros"][i]["date_time"] = new Date();
        	            if(trace)data["sandros"][i]["path"].getPath().push(new google.maps.LatLng(message["lat"],message["lon"]));
                        var sel = document.getElementById("sel");
                        if(channel == sel.options[sel.selectedIndex].value)
                        {
                            if(data["sandros"][i].hasOwnProperty("sensors"))
                            {
            	                for(k = 0; k < data["sandros"][i]["sensors"].length;++k)
        	                    {
            	                    if(message.hasOwnProperty(data["sandros"][i]["sensors"][k]["name"]))
        	                        {
            	                        var d = new Date();
        	                            var time = ""+d.getHours() + ":" + d.getMinutes() + ":" + d.getSeconds();
                                        charts[k].chart.addData([message[data["sandros"][i]["sensors"][k]["name"]]], time);
                                        if(charts[k].datanum < 6)charts[k].datanum++;
                                        else charts[k].chart.removeData( );
        	                        }   
            	                }
                            }
        	            }
        	            if(message["status"] == "OK")data["sandros"][i]["marker"].setAnimation(null);
        	            else data["sandros"][i]["marker"].setAnimation(google.maps.Animation.BOUNCE); //there is an alert
        	            if(message["sat_num"] < 3)data["sandros"][i]["marker"].setIcon('http://maps.google.com/mapfiles/ms/icons/yellow-dot.png');
        	            else data["sandros"][i]["marker"].setIcon('http://maps.google.com/mapfiles/ms/icons/green-dot.png');
        	            updateLabels();
        	        }
        	    }
            }   		    
    	    
            function closing()
            {
                for(var i=0; i <  data["sandros"].length; ++i) 
                {
                    pubnub.unsubscribe({channel: data["sandros"][i]["name"],}); //Unsubscribe to each channels
                }
                var s = saveData(); //save data before closing the page
                if(s == "OK")return null;
                return "Error saving data";
            }

		    function initMap() 
		    {
		        var wHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight; //Get the windows height
		        mapHeight = "" + (wHeight - document.getElementById("top").clientHeight) + "px"; //Make the map fill the space left in th ebottom of the screen
		        document.getElementById("map").style.height = mapHeight;

    			map = new google.maps.Map(document.getElementById('map'), 
    		    {
        			center: {lat: -34.397, lng: 150.644}, //Australia
    			    zoom: 10
  			    });
                
                myMarker = new google.maps.Marker(
                {
                    position: myLocation,
                    icon: 'http://maps.google.com/mapfiles/ms/icons/blue-dot.png',
                    map: map,
                    color: 'blue',
                    title: 'Me'
                });
                
                for(var i=0; i <  data["sandros"].length; ++i)
                {
                    var marker = new google.maps.Marker(
                    {
                        position: new google.maps.LatLng(data["sandros"][i]["lat"], data["sandros"][i]["lon"]),
                        map: map,
                        animation: google.maps.Animation.DROP,
                        icon: 'http://maps.google.com/mapfiles/ms/icons/red-dot.png',
                        title: data["sandros"][i]["name"]
                    });
                    
                    data["sandros"][i]["marker"] = marker;
                    
                    data["sandros"][i]["line"] = new google.maps.Polyline( //A red line that connects me to sandro on the map
         		    {
                        geodesic: true,
                        strokeColor: '#FF0000',
                        strokeOpacity: 1.0,
                        strokeWeight: 2,
                        map: map,
                    });
                    
                    data["sandros"][i]["path"] = new google.maps.Polyline(
                    {
                        path: [],
                        geodesic: true,
                        strokeColor: getRandomColor(),
                        strokeOpacity: 1.0,
                        strokeWeight: 2,
                        map: map,
                    });
                    
                    var info_window = new google.maps.InfoWindow(
                    {
                        content: "info"
                    });
                    
                    data["sandros"][i]["info_window"] = info_window;
                    
                    data["sandros"][i]["marker"].addListener('click', clickEvent );
                    
                }
                getLocation();
		    }  
		    
		    
		    
		    function clickEvent() //when I click on a marker
		    {
		        
		        var content = '<img src="http://maps.googleapis.com/maps/api/streetview?size=400x400&location='+this.position.lat()+','+this.position.lat()+'&fov=90&heading=235&pitch=10&key=YOUR_MAPS_KEY">';
		        var info_window = new google.maps.InfoWindow({content: content});   
		        info_window.open(map, this);
		    }
		    
		    
		    function getRandomColor() //create random colors for the paths
		    {
                var hexLetters = '0123456789ABCDEF'.split('');
                var color = '#';
                for (var i = 0; i < 6; i++ ) 
                {
                    color += hexLetters[Math.floor(Math.random() * 16)];
                }
                return color;
            }

            function getLocation()
            {
                if(navigator.geolocation) 
                {
                    var siberia = new google.maps.LatLng(60, 105);
		    	    var newyork = new google.maps.LatLng(40.69847032728747, -73.9514422416687);
		    	    if(data.hasOwnProperty("my_last_location"))
                    {
                        if(data["my_last_location"].length > 1)
                        {
                            myLocation = new google.maps.LatLng(data["my_last_location"][0],data["my_last_location"][1]);
                        }
                    }
    			    navigator.geolocation.getCurrentPosition(function(position) 
                    {
          				myLocation = new google.maps.LatLng(position.coords.latitude,position.coords.longitude);
      				    setLocation();
    			    }, 
                    function() 
            	    {
      				    if(myLocation == undefined )
      				    {
      				        myLocation = newyork;
      				        alert("Geolocation service failed. Don, are you trying to connect from New York's subway again?");
      				    }
      				    else alert("Geolocation service failed. I will set your last location");
      				    setLocation();
    			    });
  			    }
			    else 
                {
      		    	if(myLocation == undefined )
      		    	{
      		    	    myLocation = siberia;
      		    	    alert("Your browser doesn't support geolocation. I hope its because you live in Siberia...");
      		    	}
      		    	else alert("Your browser doesn't support geolocation. I will set your last location"); 
      		    	setLocation();
                } 
            }	

            function setLocation()
            {
                document.getElementById("myLat").value = myLocation.lat().toFixed(6);
                document.getElementById("myLon").value = myLocation.lng().toFixed(6);
                myMarker.setPosition(myLocation);
                map.setCenter(myLocation);
                for(var i=0; i <  data["sandros"].length; ++i)
                {
                    data["sandros"][i]["line"].setPath([myLocation, data["sandros"][i]["marker"].getPosition()]);
                }
            } 

            function updateLocation()
            {   
                myLocation = new google.maps.LatLng(document.getElementById('myLat').value,document.getElementById('myLon'   ).value);
                setLocation();
            }            

	        function clickNSet()
	        {
	            if(document.getElementById("clicknset").checked)
	            {
	                google.maps.event.addListener(map, 'click', function(event) 
                    {
                        myLocation = event.latLng;
                        setLocation();
                        updateLabels();
                    });
	            }
	            else google.maps.event.clearListeners(map, 'click');
	        }            

            function updateLabels()
            {
                var index = document.getElementById("sel").selectedIndex;
                document.getElementById("lat").value = data["sandros"][index]["lat"];
                document.getElementById("lon").value = data["sandros"][index]["lon"];
                document.getElementById("dist").value = (google.maps.geometry.spherical.computeDistanceBetween (new google.maps.LatLng(data["sandros"][index]["lat"],data["sandros"][index]["lon"]), myLocation)/1000).toFixed(2);
                document.getElementById("speed").value = data["sandros"][index]["speed"];
            }
            
            function selected()
            {
                updateLabels();
                createCharts();
            }
            
            function saveData()
            {
                for(var i=0; i <  data["sandros"].length; ++i) 
                {
                    save["sandros"][i]["lat"] = data["sandros"][i]["lat"];
                    save["sandros"][i]["lon"] = data["sandros"][i]["lon"];
                    save["sandros"][i]["speed"] = data["sandros"][i]["speed"];
                    save["sandros"][i]["date_time"] = data["sandros"][i]["date_time"];
                }
                save["my_last_location"][0] = myLocation.lat();
                save["my_last_location"][1] = myLocation.lng();
                var url = "save_data.php";
                var params = "data="+JSON.stringify(save);
                var http = new XMLHttpRequest();
                var waitResponse = true;
                var response = "response text";
                http.open("POST", url, false);
                http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
                http.send(params);
                return http.responseText;
            }
            function showPath()
            {
                trace = !trace;
                for(var i=0; i <  data["sandros"].length; ++i) 
                {
                    data["sandros"][i]["path"].setPath([]);
                }
            }
            function createCharts()
            {
                var index = document.getElementById("sel").selectedIndex;
                charts = [];
                while (cDiv.firstChild) cDiv.removeChild(cDiv.firstChild);
                if(data["sandros"][index].hasOwnProperty("sensors"))
                {
                    for(var i=0; i < data["sandros"][index]["sensors"].length;++i)
                    {
                        var title = document.createElement("h2");
                        title.innerHTML = data["sandros"][index]["sensors"][i]["name"];
                        cDiv.appendChild(title);
                    
                        var canvas = document.createElement("canvas");
                        canvas.id = data["sandros"][index]["sensors"][i]["name"];
                        canvas.style.width = "100%";
                        canvas.style.height = "200px";
                        cDiv.appendChild(canvas);
                        cDiv.appendChild(document.createElement("hr"));
                        chartStyle = 
                        {
                            labels: [],
                            datasets: 
                            [
                                {
                                    fillColor: "rgba(255,255,255,0.2)",
                                    strokeColor: "rgba(255,255,255,1)",
                                    pointColor: "rgba(255,255,255,1)",
                                    pointStrokeColor: "#fff",
                                    data: []
                                }
                            ]
                        };
                        charts.push({canvas: canvas, chart: new Chart(canvas.getContext('2d') ).Line(chartStyle, {animationSteps: 15,  scaleFontColor: "#ffffff" }), datanum: 0});
                        charts[i].chart.fillColor =  "rgba(55,25,5,1)";
                        charts[i].chart.update();
                    }
                }
            }
        </script>
        <script async defer src="https://maps.googleapis.com/maps/api/js?key=YOUR_MAPS_KEY&v=3&libraries=geometry&callback=initMap">
        </script>
    </body>
</html>
save_data.phpPHP
<?php
    //GEts POST parameters
    function getParameter($par, $default = null)
	{
            if (isset($_POST[$par]) && strlen($_POST[$par])) return $_POST[$par];
            if (isset($_GET[$par]) && strlen($_GET[$par])) return $_GET[$par];
            else return $default;
    }
    $data = getParameter("data");
    if($data != null)
    {
        file_put_contents("save.JSON", json_encode(json_decode($data), JSON_PRETTY_PRINT), LOCK_EX); //Decode JSON and save data to file
        echo "OK";    
    }
    else echo "error";
?>
style.cssCSS
html, body { height: 100%; margin: 0; padding: 0; }
#sel { height: 25px; width: 100px; }
h2, h3 {color:white;}
input 
{
    border: 5px solid white; 
    -webkit-box-shadow: 
      inset 0 0 8px  rgba(0,0,0,0.1),
            0 0 16px rgba(0,0,0,0.1); 
    -moz-box-shadow: 
      inset 0 0 8px  rgba(0,0,0,0.1),
            0 0 16px rgba(0,0,0,0.1); 
    box-shadow: 
      inset 0 0 8px  rgba(0,0,0,0.1),
            0 0 16px rgba(0,0,0,0.1); 
    padding: 2px;
    background: rgba(19,91,84,1);
    margin: 0px 20px 0px 10px;
}

button 
{
	-moz-box-shadow:inset 0px 1px 3px 0px #91b8b3;
	-webkit-box-shadow:inset 0px 1px 3px 0px #91b8b3;
	box-shadow:inset 0px 1px 3px 0px #91b8b3;
	background-color:#135B54;
	-moz-border-radius:5px;
	-webkit-border-radius:5px;
	border-radius:5px;
	border:1px solid #566963;
	display:inline-block;
	cursor:pointer;
	color:#ffffff;
	font-family:Arial;
	font-size:15px;
	font-weight:bold;
	padding:6px 23px;
	text-decoration:none;
	text-shadow:0px -1px 0px #2b665e;
	margin: 0px 20px 0px 10px;
}

button:hover 
{
	background-color:#768d87;
}

button:active 
{
	position:relative;
	top:1px;
}



input[type=checkbox] 
{
	visibility: hidden;
}


.checkbox 
{
	width: 120px;
	height: 40px;
	background: #333;
	vertical-align: middle;
    display: inline-block;
	border-radius: 50px;
	margin: 0px 20px;
	position: relative;
}


.checkbox:before 
{
	content: 'On';
	position: absolute;
	top: 12px;
	left: 13px;
	height: 2px;
	color: #26ca28;

	font-size: 16px;
}


.checkbox:after 
{
	content: 'Off';
	position: absolute;
	top: 12px;
	left: 84px;
	height: 2px;
	color: white;
	font-size: 16px;
}



.checkbox label 
{
	display: block;
	width: 52px;
	height: 22px;
	border-radius: 50px;

	-webkit-transition: all .5s ease;
	-moz-transition: all .5s ease;
	-o-transition: all .5s ease;
	-ms-transition: all .5s ease;
	transition: all .5s ease;
	cursor: pointer;
	position: absolute;
	top: 9px;
	z-index: 1;
	left: 12px;
	background: #ddd;
}


.checkbox input[type=checkbox]:checked + label 
{
	left: 60px;
	background: #26ca28;
}
save.JSONJSON
{
    "my_last_location": [
        45.4090043,
        11.8896656
    ],
    "sandros": [
        {
            "name": "Sandro",
            "lat": "45.424945",
            "date_time": "2015-11-29T16:59:21.461Z",
            "sensors": [
                {
                    "name": "Temperature",
                    "min": 6,
                    "max": 50
                },
                {
                    "name": "Humidity",
                    "min": 10,
                    "max": 80
                }
            ],
            "lon": "11.876460",
            "speed": 0
        },
        {
            "name": "Cisca",
            "lat": "45.425270",
            "lon": "11.877168",
            "date_time": "2015-11-28T10:26:28.161Z",
            "speed": 0
        },
        {
            "name": "Faggio",
            "lat": "45.800135",
            "lon": "11.800747",
            "date_time": "2015-11-27T16:14:50.569Z",
            "speed": "10.64"
        }
    ]
}

Comments

Similar projects you might like

GPS Location Display With GPS And TFT Display Shields

Project tutorial by Boian Mitov

  • 15,600 views
  • 6 comments
  • 35 respects

Personal Home Assistant

Project tutorial by 3 developers

  • 5,354 views
  • 0 comments
  • 29 respects

GPS Datalogger, Spatial Analysis, and Azure IoT Hub.

Project tutorial by Shawn Cruise

  • 22,036 views
  • 4 comments
  • 81 respects

TIA Weak Artificial Intelligence IoT Assistant

Project tutorial by Adam Milton-Barker

  • 4,458 views
  • 0 comments
  • 23 respects

Personal Healthcare Assistant PHA

Project tutorial by Technomadic

  • 18,629 views
  • 7 comments
  • 148 respects
Add projectSign up / Login