Tag Archives: Ublox Neo 6M

GPS Tracker Using NEO 6M and ESP-12

The below code outputs current co-ordinate and speed in km/hr . The code is a first draft and there are optimizations to be done. It uses a Ublox NEO 6M for the GPS module and an ESP-12 (of the ESP 8266 family) for the collecting the GPS data from the NEO 6M module, processing the data and outputting in JSON format.

The ESP-12 is working as a webserver. When connected it first asks for the WiFi password set in the code as “WiFiAPPSK”. And for an added layer of security a password is also needed with the HTTP request. (This will made proper in future).  Without the password the server will reject the request.

Arduino IDE is used for programming the ESP-12.

 

//#include <SoftwareSerial.h> //causing the module to reset automatically intermittently
#include <ESP8266WiFi.h>
#include <EEPROM.h>

//SoftwareSerial gps(14,12);

char WiFiAPPSK[] = "123456#";
char savedPass[9] = "12345678", suppliedPass[9], hashedPass[9];
int addr = 0;

short int resetWifiFlag = 0;

WiFiServer server(80);

void encodePass(char *pass, char *hash) //encoding using XOR, need to change as this generates characters that causes problems while passed through GET method
{
  short int i;
  char key[9] = "ikirmiki"; //8 chars
  
  i = strlen(pass);
  
  //i is pointing at the end of the string. Now fillup the rest string with NULL
  for(; i<=9; i++)
  {
    pass[i] = '\0';
  }
  //at this point the pass is filled with pass and all empty cells are filled with NULL
  //now let's do the XOR
  for(i=0; i<=9;i++)
  {
    //hash[i] = pass[i] ^ key[i];
    *(hash+i) = *(pass+i) /*^ key[i]*/;
  }
}

void decodePass(char *pass, char *hash)
{
  short int i;
  char key[9] = "ikirmiki"; //8 chars
  
  //check the hash by decoding
  //now let's do the XOR
  for(i=0; i<=9;i++)
  {
    *(pass+i) = *(hash+i) /*^ key[i]*/; //Overridden for now as XOR creates some special characters sometimes which is not being interpreted by browser address bar properly.
  }
}

void explodeString(char *delimiter, char *str, char explodedArray[][32]) 
{
  int i = 0;
  char* token;
  
  token = strtok(str, delimiter);
  while(token != NULL)
  {
     //explodedArray[i] = (char *)malloc(strlen(token)+1);
     strcpy(explodedArray[i++],token);delay(1);
     token = strtok(NULL, delimiter);
  }
}


short int ensure8CharPass(char *pass, char *tempPass) //for the XOR ing with 8 chars key we need to ensure the pass is also 8 chars
{
  short int i;

  for(i=0;i<9;i++)
  {
    pass[i]=tempPass[i];
  }
  pass[i] = '\0';
  
  if(strlen(pass) != 8)
  {
    return 0;
  }
  else
  {
    return 1;
  }
}

String getSpeed(String rawLatLong)
{
  int firstPos, lastPos = 0;
  int i;

  firstPos = rawLatLong.indexOf("$GPVTG,");
  for(i=0; i < 7; i++)
  {
    firstPos = rawLatLong.indexOf(",", firstPos+1); //$GPVTG,279.67,T,,M,4.903,N,9.080,K,A*3F -- go to 9.080
    delay(1); 
  }
  firstPos = firstPos + 1; //skip the comma that is infront of 9.080
  lastPos = firstPos;
  for(i=0; i < 2; i++) //go to the comma after K
  {
    lastPos = rawLatLong.indexOf(",", lastPos+1);
    delay(1);
  }
  return rawLatLong.substring(firstPos,lastPos);
}


String getLatLong(String rawLatLong)
{
  int firstPos = 0, lastPos = 0, i, degree;
  char latLong[5][32], buff[64];
  float latitude, longitude, minute;
  String temp;

  firstPos = rawLatLong.indexOf("$GPRMC,"); //same concept as in above ($GPVTG)
  for(i=0; i < 3; i++)
  {
    firstPos = rawLatLong.indexOf(",", firstPos+1);
    delay(1);
  }
  firstPos = firstPos + 1; //skip the comma -- same concept as in above ($GPVTG)
  lastPos = firstPos;
  for(i=0; i < 4; i++)
  {
    lastPos = rawLatLong.indexOf(",", lastPos+1);
    delay(1);
  }
  
  temp = rawLatLong.substring(firstPos,lastPos); delay(1); // delay(1) need to checked if necessary or not. 
  temp.toCharArray(buff,64); delay(1); // delay(1) need to checked if necessary or not.
  explodeString(",",buff,latLong); delay(1);  // delay(1) need to checked if necessary or not.
  //at this point latLong must have all the elemnts in consequetive array
  //convert the values in position 0 and 2
  //fist check if the values at those positions are valid or not
  if(strlen(latLong[0]) && strlen(latLong[2])) //convert to decimal format from degree, minute
  {
    latitude = atof(latLong[0]);
    longitude = atof(latLong[2]);
    latitude = latitude / 100;
    longitude = longitude / 100;

    degree = (int)latitude;
    minute = latitude - degree;
    minute = minute * 100;
    latitude = degree + (minute/60);

    degree = (int)longitude;
    minute = longitude - degree;
    minute = minute * 100;
    longitude = degree + (minute/60);

    delay(1); //need to check if needed
    //Serial.println(String(latitude,6)+","+latLong[1]+","+String(longitude)+","+latLong[3]);
    return (String(latitude,6)+","+latLong[1]+","+String(longitude,6)+","+latLong[3]); //String(latitude,6) the second parameter to string sets the precision after decimal. 

    
  }
  else
  {
    return String("Yet to get a fix");
  }
  
}

void writeSettings(int addr, char *toWrite)
{
  int i;
  for(i=0; i<9; i++)
  {
    EEPROM.write((addr+i), toWrite[i]);
  }
  EEPROM.commit();
}

void readSettings(int addr, char *toRead) // will have to change this function - need to make it such t
{
  int i;
  for(i=0; i<9; i++)
  {
    toRead[i] = EEPROM.read((addr+i));
  }
  toRead[i] = '\0';
}

void setupWiFi()
{
  WiFi.mode(WIFI_AP);

  // Do a little work to get a unique-ish name. Append the
  // last two bytes of the MAC (HEX'd) to "Thing-":
  uint8_t mac[WL_MAC_ADDR_LENGTH];
  WiFi.softAPmacAddress(mac);
  String macID = String(mac[WL_MAC_ADDR_LENGTH - 2], HEX) +
                 String(mac[WL_MAC_ADDR_LENGTH - 1], HEX);
  macID.toUpperCase();
  String AP_NameString = "GPS Tracker " + macID;

  char AP_NameChar[AP_NameString.length() + 1];
  memset(AP_NameChar, 0, AP_NameString.length() + 1);

  for (int i=0; i<AP_NameString.length(); i++)
    AP_NameChar[i] = AP_NameString.charAt(i);

  WiFi.softAP(AP_NameChar, WiFiAPPSK);

  resetWifiFlag = 0; //look in loop for explanation
}


void setup() 
{
  // put your setup code here, to run once:
  Serial.begin(9600);
  EEPROM.begin(64); //initiate 64 bytes to write/read
  //gps.begin(9600);
 
  readSettings(addr+0, hashedPass); //read the saved pass at addr+0 location
  if(strlen(hashedPass) == 8) //id the saved password is of proper length then decode it and use it.
    decodePass(savedPass, hashedPass);
    
  strcpy(WiFiAPPSK,savedPass); //set the Wifi Password also
  setupWiFi(); //start the wifi
  
  server.begin();
}

void loop() 
{
  //check if any variable is redundant
  volatile unsigned long startTime, currentTime;
  String serialBuffer, latLong;
  int pos,flag,i,j,temp;
  short int passError = 1, len, latLongFlag = 0, speedFlag = 0;
  char serialDataByte;

  if(resetWifiFlag == 1) // if the password is changed then this will be called to re-initialize the WIFI-AP with the new password
   {
      setupWiFi();
   }
  
  //start the webserver and send the data
  // Check if a client has connected
  WiFiClient client = server.available();
  if (!client) 
  {
    delay(1);
    return;
  }
  else
  {
    delay(1);

    //Read the first line of the request
    String req = client.readStringUntil('\r');

    //now check if password change requested or not
    pos = req.indexOf("/changepass/");
    if(pos != -1)
    {
      //get the new pass
      len = pos+12+8; //12 chars of /changepass/ AND 8 of password
      for(i=pos+12,j=0; i<len; i++,j++)
      {
        if(req.charAt(i) == '/')
          break;
        else
          hashedPass[j] = req.charAt(i);
      }
      hashedPass[j] = '\0';
      decodePass(suppliedPass, hashedPass);
      strcpy(savedPass,suppliedPass);
      //save the password
      //Serial.println("New Pass: "+ String(hashedPass));
      writeSettings(addr+0, hashedPass); //save the password

      //not calling a function for sending the data to save the function call overhead.
      // Prepare the response. Start with the common header:
      String s = "HTTP/1.1 200 OK\r\n";
      s += "Content-Type: application/json\r\n\r\n";
      s += "{"succcess":"Password Changed To: "+ String(suppliedPass)+""}";
      // Send the response to the client
      client.print(s);
      client.flush();
      delay(1);
      client.stop();
      //ESP.restart(); //to make the new password in effect immediately - USING THIS WILL NOT DISPLAY THE RESULT. It seems the loop needs to be exited to send the result to client Using a delay of 10 seconds also didn't work
      //AS THE restart method is not available so setting up the wifi again with the new password
      strcpy(WiFiAPPSK,suppliedPass); //set the Wifi Password also
      resetWifiFlag =1; // if the wifi pass is changed here now then client will loose connection before printing the result. the loop() will have to be exited to print the result. so we are setting the flag here and changing the password in the next iteration of the loop()
      delay(1); //safe side
      return;
    }

    pos = req.indexOf("/pass/");
    if(pos == -1) // that means no password supplied
    {
      //not calling a function for sending the data to save the function call overhead.
      client.flush();
      // Prepare the response. Start with the common header:
      String s = "HTTP/1.1 200 OK\r\n";
      s += "Content-Type: application/json\r\n\r\n";
      s += "{"error":"Auhentication Error 1"}";
      // Send the response to the client
      client.print(s);
      client.flush();
      delay(1);
      client.stop();
      return;
    }
    else
    {
      //Serial.println("Client Connected");
      //read upto the next slash
      len = pos+6+8; //6 chars of /changepass/ AND 8 of password
      for(i=pos+6,j=0; i<len; i++,j++)
      {
        if(req.charAt(i) == '/')
          break;
        else
          hashedPass[j] = req.charAt(i);
      }
      hashedPass[j] = '\0';
      
      //now decode the pass 
      decodePass(suppliedPass, hashedPass);
      if(strcmp(suppliedPass,savedPass))
      {
        //not calling a function for sending the data to save the function call overhead.
        client.flush();
        // Prepare the response. Start with the common header:
        String s = "HTTP/1.1 200 OK\r\n";
        s += "Content-Type: application/json\r\n\r\n";
        s += "{"error":"Auhentication Error 2"}";
        // Send the response to the client
        client.print(s);
        client.flush();
        delay(1);
        client.stop();
        return;
      }
    }
   
    startTime = currentTime = 0;
    serialBuffer = latLong = "";
  
    flag = -1;
    
    startTime = millis();
    
    // put your main code here, to run repeatedly:
    while(flag == -1)  //read 5 seconds of data
    {
      if(Serial.available() > 0)
      {
        serialBuffer += (char)Serial.read();
        if(serialBuffer.indexOf("$GPRMC,") >=0)
        {

          while(1)
          {
            serialDataByte = (char)Serial.read();
            if(serialDataByte == '\r' || serialDataByte == '$') //need to optimize - check if \r is sufficient
            {
              latLong = getLatLong(serialBuffer);
              serialBuffer = serialDataByte; //reset the serial buffer for the next line
              break;
            }
            else
            {
              serialBuffer += serialDataByte;
            }
            delay(1);
          }
        }

        if(serialBuffer.indexOf("$GPVTG,") >=0)
        {
          while(1)
          {
            serialDataByte = (char)Serial.read();
            if(serialDataByte == '\r' || serialDataByte == '$') //need to optimize - check if \r is sufficient
            {
              flag = 1;
              latLong = latLong + "," + getSpeed(serialBuffer);
              latLong = "{"success":""+latLong+""}";
              break;
            }
            else
            {
              serialBuffer += serialDataByte;
            }
            delay(1);
          }
        }
      }

      if(millis()- startTime > 5000)
      {
        //Serial.println("TimeOut");
        latLong = "Timeout"; 
        latLong = "{"error":""+latLong+""}";
        latLongFlag = speedFlag = 0;
        break;
      }
      delay(1);
    } 
    //Serial.println("LN: "+latLong);
  }

  client.flush();
  // Prepare the response. Start with the common header:
  String s = "HTTP/1.1 200 OK\r\n";
  s += "Content-Type: application/json\r\n\r\n";
  //s += "<!DOCTYPE HTML>\r\n<html>\r\n<body>\r\n";
  s += latLong;
  //s += "</body>\r\n</html>\n";
  // Send the response to the client
  client.print(s);
  client.flush();
  //delay(100); Serial.println("Client disonnected");
  latLongFlag = speedFlag = 0;  
  delay(1);
  client.stop();
}

 

Notes:

  • The setupWifi code is from Sparkfun’s code
  • The Software Serial is causing trouble. The WiFi Access Point (AP) is dying after sometime if the Software Serial Library is used.
  • The delays are needed to allow the ESP8266 to do it’s own internal tasks (like maintaining the wifi connection). The delays are needed where there can be long loops.

UBLOX NEO 6M GPS Module

The NEO 6M from Ublox is a good and affordable GPS module. The characteristics are as follows.

It can be purchased with a ceramic antenna from online stores like Ebay or Aliexpress. Unfortunately the one I purchased had a problem with the Antenna connection due to which some time got wasted in trying to get a fix. After a little bit of tweaking with the antenna connector the module is getting a GPS fix even inside rooms.

I have supplied it with 5v and the RX and TX also has been connected to 5v I/O. There is an indicator (light) on the module that flashes when the module gets a GPS fix. (Please check with your module manufacturer for the correct voltage of the module)

Ublox provides a software called “u-center” for testing the module. But the software is good enough for monitoring also.  The u-center software can also be used to change/update configurations of the module. The software can be downloaded from this Ublox website’s link https://www.u-blox.com/en/product/u-center-windows . And as a backup it has been put up here also (at the time of writing this article the latest version was 8.21).

Ublox u-center software

The module can be connected using a USB to Serial module (PL2303 or CH340G) or using an Arduino. Below is the connection details for connecting through Arduino or USB to RS232 modules.

Connect to PC using Arduino 
Arduino                NEO 6M 
   RX         ----       RX
   TX         ----       TX
   5v         ----       5v
   GND        ----       GND

Connect to PC using USB to Serial Module
USB to RS232             NEO 6M 
 RX              ----       TX
 TX              ----       RX
 5v              ----       5v
 GND             ----       GND

 

The module works out of the box and no further configuration is required. It might take a while to get a fix.

Here is the datasheet.

The module outputs data in NMEA format. The details of the format can be found here. Here is a copy of the website material in case the site ever goes down (the data is of http://gpsinformation.org)

Please check with your module manufacturer for the correct voltage of the module

Notes:

  • Once the module somehow stopped working and responding. Removing the onboard battery helped. Re-attaching the battery later on didn’t have any problem and the module is working fine. The battery is for data backup, so removing it altogether doesn’t have any problem, just that it takes a lot of time to get a fix when the module is powered up again.