keesp
Published © Apache-2.0

Day 3: Updating the GPS Location

Day 2 of the Aquabots Autnomous Vessels Project: Updating the GPS location with the Aquabots Client

EasyFull instructions provided4
Day 3: Updating the GPS Location

Things used in this project

Hardware components

Seeed Grove-GPS
×1
Seeed Grove Blue Wrapper Pack 1-2
×1
Adafruit GPS Antenna
optional, but highly recommended for better performance
×1
Adafruit SMA to uFL/u.FL/IPX/IPEX RF Adapter Cable
×1
Seeed Grove Mega Shield
×1
Arduino Mega 2560 & Genuino Mega 2560
Arduino Mega 2560 & Genuino Mega 2560
×1

Software apps and online services

Aquabots Client

Story

Read more

Code

Vessel

C/C++
Implementation of the Vessel
//Constructor
Vessel::Vessel() {};

void Vessel::setup() {
  Serial.println(F("SETUP VESSEL" ));
  enable = true;
  Serial.print(F("VESSEL ENABLED: ")); Serial.println( enable );
  if (!enable )
    return;
  speed = 0;

  bool init = false;
  Serial.print(F("INITIALISING VESSEL: ")); Serial.println( !init );
  Serial.print(F("VESSEL READY ")); Serial.println( init );
  delay( 1000);
}


void Vessel::setCourse( double heading, double thrst ) {
  // Serial.print(F("Bearing: ")); Serial.print( bearing ); Serial.print(F(", Thrust: ")); Serial.println( thrst );
  bearing = heading;
  Serial.print(F("\nCourse updated (")); Serial.print( bearing); Serial.print(F(", "));
  Serial.print( thrst ); Serial.println(F(")\n"));
}

/**
   Send the current location and get a stack with the next steps to take.
   The first latlnh should be entered last, so that the can be popped
*/
bool Vessel::update( double latitde, double longitde, double bearing, double speed, bool updated ) {
  if ( ! webClient.connect() )
    return false;
  webClient.setContext( AQUABOTS_VESSEL_CONTEXT );
  data.latitude = latitde;
  data.longitude = longitde;
  String url = F("&lo=");
  url += String( data.longitude, 8 );
  url += F("&la=");
  url += String( data.latitude, 8);
  url += F("&b=");
  url += bearing;
  url += F("&s=");
  url += speed;
  url += F("&u=");
  url += updated;
  //Serial.print(F("UPDATE VESSEL: "));
  //Serial.println( url);

  boolean result = webClient.sendHttp( WebClient::UPDATE, false, url);
  if (!result ) {
    webClient.disconnect();
    return false;
  }

  //String response = webClient.printResponse( WebClient::UPDATE );
  //Serial.print(F("\n\nHEADING: \n")); Serial.println( response);

  size_t capacity = JSON_OBJECT_SIZE(11) + 79;
  DynamicJsonDocument doc(capacity);
  DeserializationError error = deserializeJson(doc, webClient.client);
  if (error) {
    Serial.println(F("Parsing update failed!"));
    webClient.disconnect();
    return false;
  }
  JsonObject root = doc.as<JsonObject>();
  data.heading = root["h"];
  data.thrust = root["t"];
  data.time = root["tm"];
  data.manual = root["mn"];
  webClient.disconnect();
  setCourse( data.heading, data.thrust);
  return true;
}

void Vessel::stop() {
  Serial.println("\n\nSTOPPING!!!");
  bearing = 0;
  speed = 0;
}

void Vessel::loop( double heading) {
  if ( !enable )
    return;
}

Vessels

C/C++
Main Arduino Code
#include <ArduinoJson.h>

#include "WebClient.h"
#include "Registration.h"
#include "TinyGPS.h"
#include "Vessel.h"

#define VESSEL F("MyBoatName")
#define PASSPHRASE F("MyPassPhrase")
#define TIME_OUT 3000 //msec

static WebClient webClient;
static Registration registration;
static TinyGPS gps;
static Vessel vessel;

long vesselId;

void setup() {
  Serial.begin(9600);
  Serial.print(F("Setup Vessel: ")); Serial.println( VESSEL );
  vesselId = -1;
  webClient.setup();
  registration.setup();
  gps.setup();
  vessel.setup();
}

void loop() {
  gps.loop();
  if ( vesselId < 0 ) {
    vesselId =  registration.registerVessel( VESSEL, PASSPHRASE, gps.getLatitude(), gps.getLongitude() );
    if ( vesselId > 0 ) {
      Serial.print(F("REGISTERED VESSEL: ")); Serial.println( vesselId );
      webClient.setAuthentication( vesselId, PASSPHRASE );
    } else
      Serial.print(F("REGISTRATION FAILED: ")); Serial.println( vesselId );
  } else {
    Serial.print(F("VESSEL: ")); Serial.println( vesselId );
    //vessel.loop( gps.getBearing());
  }

  delay(1000);
}

Registration.h

C/C++
Header file to register your vessel
#ifndef Registration_h
#define Registration_h

#define REGISTRATION "Registration"
#define REGISTRATION_ID "registration"

class Registration {

  private:
    bool enabled; //general purpose flag 
    long vesselId; 

  public: Registration(void);
    void setup();
    long registerVessel( String name, String passphrase, double latitude, double longitude);
    bool getConfig();
};

#endif

Registration

C/C++
Implementation of the Registration Module
Registration::Registration() {};

void Registration::setup( ) {
  enabled = true;
}

long Registration::registerVessel( String vesselName, String passphrase, double latitude, double longitude ) {
  if ( !enabled )
    return -1;
  if ( !webClient.connect() )
    return -2;

  webClient.setContext( AQUABOTS_REGISTRATION_CONTEXT ); 
  Serial.print(F("Registering Vessel: ")); Serial.println( vesselName );
  String url = F("&name=");
  url += String( vesselName );
  url += F("&passphrase=");
  url += String( passphrase);
  url += F("&latitude=");
  url += String( latitude);
  url += F("&longitude=");
  url += String( longitude);

  boolean result = webClient.sendHttp( WebClient::REGISTER_VESSEL, false, url);
  if (!result ) {
    webClient.disconnect();
    return -3;
  }
  String retval = "";
  while (webClient.client.available()) {
    char c = webClient.client.read();
    retval += c;
  }
  Serial.println( retval);
  vesselId = atol( retval.c_str() );
  webClient.disconnect();
  Serial.print(F("Vessel Registered: ")); Serial.println( vesselId );
  return vesselId;
}

bool Registration::getConfig() {
}

TinyGPS.h

C/C++
Header file for the GPS Unit
#ifndef TinyGPS_h
#define TinyGPS_h

#define TINY_GPS "TinyGPS"
#define TINY_GPS_ID "gps.tiny"

#include <SoftwareSerial.h>
#include <TinyGPS++.h>

class TinyGPS{

  private:
    TinyGPSPlus gps;
    double latitude;
    double longitude;

  public: TinyGPS(void);
    void setup();
    double getLatitude();
    double getLongitude();
    double getBearing( double latFrom, double lonFrom, double latTo, double lonTo );
    double getDistance( double latFrom, double lonFrom, double latTo, double lonTo );
    bool wait();//wait for processing of the nmea sentence
    void loop();
};

#endif

TinyGPS

C/C++
Implementation of the GPS unit
#include "TinyGPS.h"

TinyGPS::TinyGPS() {};

//Repeatedly feed it characters from your
void TinyGPS::setup( ) {
  Serial1.begin(9600);
  Serial.println(F("GPS INITIALISED"));
}

double TinyGPS::getLatitude() {
  return latitude;
}

double TinyGPS::getLongitude() {
  return longitude;
}

double TinyGPS::getBearing( double latFrom,  double lonFrom, double latTo, double lonTo ) {
  return gps.courseTo( latFrom, lonFrom, latTo, lonTo );
}

double TinyGPS::getDistance( double latFrom, double lonFrom, double latTo, double lonTo ) {
  return gps.distanceBetween( latFrom, lonFrom, latTo, lonTo );
}

void TinyGPS::loop() {
  Serial.println(F("CHECKING GPS"));
  bool updated = false;
  unsigned long current = millis();
  double speed = 0;
  double bearing = 0;
  while (Serial1.available() && ( millis() < ( current + TIME_OUT  ))) {
    char chr = Serial1.read();
    Serial.print(chr );
    gps.encode( chr );
    updated = gps.location.isUpdated();
    if ( updated ) {

      latitude = gps.location.lat();
      longitude = gps.location.lng();
      bearing = gps.course.deg();
      speed = gps.speed.mps();
      Serial.print(F("\n\nGPS Location Updated: "));
      Serial.print( latitude, 6 ); Serial.print("E ");
      Serial.print( longitude, 6 ), Serial.println("N\n\n "); // bearing, speed );
      break;
    }

  }

  if (( latitude >= 0 ) && ( longitude >= 0 ))
    vessel.update( latitude, longitude, bearing, speed, updated );
}

WebClient.h

C/C++
Header file of the communication with the Aquabots Client
#ifndef WebClient_h
#define WebClient_h

#include <SPI.h>
#include <Ethernet.h>

#define AQUABOTS_REGISTRATION_CONTEXT F("/arnac/registration/")
#define AQUABOTS_VESSEL_CONTEXT F("/arnac/rest/")
#define CONDAST_URL F("www.condast.com")

const unsigned long HTTP_TIMEOUT = 5000;// max respone time from server

/*
  Web client

  This sketch connects to a website
  using an Arduino Wiznet Ethernet shield.

  Circuit:
   Ethernet shield attached to pins 10, 11, 12, 13

  created 18 Dec 2009
  by David A. Mellis
  modified 9 Apr 2012
  by Tom Igoe, based on work by Adrian McEwen

*/

//Condast SERVER
// Set the static IP address to use if the DHCP fails to assign
//const char server[] = "www.condast.com";
//IPAddress ip(79, 170, 90, 5);
//const int PORT = 8080;

//LOCALHOST
// Set the static IP address to use if the DHCP fails to assign
IPAddress server(192, 168, 178, 41);
IPAddress ip(192, 168, 178, 41);
const int PORT = 10080;

//Huawei
//IPAddress server(192,168,8,100);
//IPAddress ip(192,168,8,100);
//const int PORT = 10081;

//Havenlab
//IPAddress server(192,168,10,110);
//IPAddress ip(192,168,10,110);
//const int PORT = 10080;

// Enter a MAC address for your controller below.
// Newer Ethernet shields have a MAC address printed on a sticker on the shield
const byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};

class WebClient {

  public: WebClient();
    enum request {
      UNKNOWN = 0,
      REGISTER_VESSEL = 1,
      VESSEL_CONFIG = 2,
      DEBUG = 3,
      FIELD = 4,
      UPDATE = 5,
      WAYPOINTS = 6,
      DATA = 7,
      NMEA = 8,
      OPTIONS = 9,
      LOG = 10,
      WAYPOINT = 11
     };

    const unsigned long HTTP_TIMEOUT = 5000;// max respone time from server

    EthernetClient client;
    void setup();
    bool connect();
    void disconnect();
    void setAuthentication( long id, String token ); 
    void setContext( String context ); 
    bool requestLog();
    bool logMessage( String message );
    bool getWaypoint();
    bool sendUpdate( String url );
    bool sendHttp( int request, String msg );
    bool sendHttp( int request, boolean post, String attrs );
    String urlencode(String str);
    String printResponse( int request );
    void logRequest( int request, boolean post, String attrs );//for debugging
    void logRequestStr( int request ); //dito
    void loop();

  private:
    String host;
    int port;
    bool connected;
    String context;
    long id;
    String token;

    // Initialize the Ethernet client library
    // with the IP address and port of the server
    // that you want to connect to (port 80 is default for HTTP):
    void requestService( int request );
    bool processResponse( int request );
    boolean update( JsonObject& root );
    String urldecode(String str);
    unsigned char h2int(char c);
};

#endif

WebClient

C/C++
Implementation of the communication with the Aquabots Client
WebClient::WebClient() {}

void WebClient::setup() {
  host = CONDAST_URL;
  port = PORT;
  context = AQUABOTS_REGISTRATION_CONTEXT;

  // start the Ethernet connection:
  Serial.print(F("SETUP WEB CLIENT: ")); Serial.println( ip );
  if (Ethernet.begin(mac) == 0) {
    Serial.println(F("Failed to configure Ethernet using DHCP"));
    // try to congifure using IP address instead of DHCP:
    Ethernet.begin(mac, ip);
  }
  // give the Ethernet shield a second to initialize:
  Serial.print(F("WEB CLIENT..."));
  delay(2000);
  Serial.println(Ethernet.localIP());
  Serial.println(F("connecting..."));
  connect();
}

bool WebClient::connect() {
  //Serial.print(F("Connecting to: ")); Serial.print( server ); Serial.print(F(":")); Serial.print( port ); Serial.print(F(" ..."));
  //client.setTimeout(5000);
  int result = client.connect(server, port);
  //Serial.print(F("Connected: ")); Serial.println( result );
  if ( result) {
    //Serial.print(F("success! "));
    //Serial.println(Ethernet.localIP());
    //Serial.println(Ethernet.gatewayIP());
    connected = result;
    return result;
  } else {
    //Serial.println(F("failed. "));
    client.stop();
  }
}

void WebClient::disconnect() {
  client.stop();
  //if ( connected )
  //  Serial.println(F("Disconnecting: Complete "));
  connected = false;
}

void WebClient::setAuthentication( long i, String t ){
    id = i;
    token = t;
}

void WebClient::setContext( String c ){
  context = c;  
}

void WebClient::requestService( int request ) {
  switch ( request ) {
    case REGISTER_VESSEL:
      client.print(F("register"));
      break;
    case VESSEL_CONFIG:
      client.print(F("config"));
      break;
    case DEBUG:
      client.print(F("debug"));
      break;
    case FIELD:
      client.print(F("field"));
      break;
    case DATA:
      client.print(F("data"));
      break;
    case NMEA:
      client.print(F("nmea"));
      break;
    case OPTIONS:
      client.print(F("options"));
      break;
    case LOG:
      client.print(F("log"));
      break;
    case WAYPOINTS:
      client.print(F("waypoints"));
      break;
    case WAYPOINT:
      client.print(F("waypoint"));
      break;
    case UPDATE:
      client.print(F("update"));
      break;
    default:
      client.print(F("unknown"));
      break;
  }
}

/**
   Is used to transform the int to a String
*/
void WebClient::logRequestStr( int request ) {
  switch ( request ) {
    case REGISTER_VESSEL:
      Serial.print(F("register"));
      break;
    case VESSEL_CONFIG:
      Serial.print(F("config"));
      break;
    case DEBUG:
      Serial.print(F("debug"));
      break;
    case FIELD:
      Serial.print(F("field"));
      break;
    case DATA:
      Serial.print(F("data"));
      break;
    case NMEA:
      Serial.print(F("nmea"));
      break;
    case LOG:
      Serial.print(F("log"));
      break;
    case WAYPOINTS:
      Serial.print(F("waypoints"));
      break;
    case WAYPOINT:
      Serial.print(F("waypoint"));
      break;
    case UPDATE:
      Serial.print(F("update"));
      break;
    case OPTIONS:
      Serial.print(F("options"));
      break;
    default:
      Serial.print(F("unknown (")); Serial.print( request ); Serial.print(F(")"));
      break;
  }
}

boolean WebClient::sendHttp( int request, String message ) {
  String msg = message;
  if ( msg.length() > 0 ) {
    msg = F("&msg=");
    msg += message;
  }
  return sendHttp( request, false, msg );
}

boolean WebClient::sendHttp( int request, boolean post, String attrs ) {
  if ( client.connected()) {
    //if ( request != NMEA )
      //Serial.print(F("REQUEST ")); logRequestStr( request ); 
     // Serial.print(F(" ?id")); Serial.print( id );
      //Serial.print(F("&token")); Serial.print( token );
      //Serial.print(F(" ")); Serial.println(attrs );
    //logRequest( request, post, attrs );

    // Make a HTTP request:
    client.print( post ? F("POST ") : F("GET ") );
    client.print( context );
    //Serial.print(F("context: ")); Serial.print( context );
    requestService( request );
    client.print(F("?id=" ));
    client.print( id );
    client.print(F("&token="));
    client.print( token );
    if ( !post && ( attrs.length() > 0 )) {
      client.print( attrs );
    }
    client.println(F(" HTTP/1.1" ));

    client.print(F("Host: "));
    client.println( host );
    client.println(F("Connection: close\r\n"));
    if ( post && ( attrs.length() > 0 )) {
      client.println( F("Accept: */*"));
      client.println( F("Content-Type: application/x-www-form-urlencoded ; charset=UTF-8" ));
      client.print( F("Content-Length: "));
      client.println( attrs.length() );
      client.println();
      client.println( urlencode( attrs ));
    }
    client.println();
    return processResponse( request );
  }
  return false;
}

/**
   Handle the response, by taking away header info and such
*/
bool WebClient::processResponse( int request ) {
  // Check HTTP status
  char status[32] = {"\0"};
  client.setTimeout(HTTP_TIMEOUT);
  client.readBytesUntil('\r', status, sizeof(status));
  char http_ok[32] = {"\0"};
  strcpy( http_ok, "HTTP/1.1 200 OK");
  if (strcmp(status, http_ok) != 0) {
    Serial.print(F( "Unexpected response (" )); logRequestStr( request); Serial.print(F( "):" ));
    Serial.println(status);
    return false;
  }

  // Skip HTTP headers
  char endOfHeaders[] = "\r\n\r\n";
  if (!client.find(endOfHeaders)) {
    Serial.println( F( "Invalid response (" )); logRequestStr( request); Serial.print(F( "):" ));
    return false;
  }
  return true;
}

void WebClient::logRequest( int request, boolean post, String attrs ) {
  // Make a HTTP request:
  Serial.print( post ? F("POST ") : F("GET "));
  Serial.print( context );
  logRequestStr( request );
  Serial.print(F( "?id=" ));
  Serial.print( id );
  Serial.print(F( "&token=" ));
  Serial.print( token );
  if ( !post && ( attrs.length() > 0 )) {
    Serial.print( attrs );
  }
  Serial.print(F( " HTTP/1.1" ));
  Serial.println();
  Serial.print(F( "Host: "));
  Serial.println( host );
  Serial.println(F( "Connection: close" ));
  if ( post && ( attrs.length() > 0 )) {
    Serial.println(F( "Accept: */*" ));
    Serial.println(F( "Content-Type: application/x-www-form-urlencoded ; charset=UTF-8"));
    Serial.print(F( "Content-Length: "));
    Serial.println( attrs.length() );
    Serial.println();
    Serial.println( attrs );
  }
}

/**
   Creates a String request from the client
*/
String WebClient::printResponse( int request ) {
  Serial.print( F("RESPONSE TO "));
  logRequestStr( request );
  // Serial.print(" PROCESSING: ");
  //Serial.print( client.available() );
  String retval = "";

  //  Serial.println();
  while (client.available()) {
    char c = client.read();
    retval += c;
  }
  Serial.print( F( ": ")); Serial.println( retval );
}

void WebClient::loop() {
  // if there are incoming bytes available
  // from the server, read them and print them:
  if (!client.connected()) {
    connect();
  }
}

/*
  ESP8266 Hello World urlencode by Steve Nelson
  URLEncoding is used all the time with internet urls. This is how urls handle funny characters
  in a URL. For example a space is: %20
  These functions simplify the process of encoding and decoding the urlencoded format.

  It has been tested on an esp12e (NodeMCU development board)
  This example code is in the public domain, use it however you want.
  Prerequisite Examples:
  https://github.com/zenmanenergy/ESP8266-Arduino-Examples/tree/master/helloworld_serial
*/
String WebClient::urldecode(String str) {
  String encodedString = "";
  char c;
  char code0;
  char code1;
  for (int i = 0; i < str.length(); i++) {
    c = str.charAt(i);
    if (c == '+') {
      encodedString += ' ';
    } else if (c == '%') {
      i++;
      code0 = str.charAt(i);
      i++;
      code1 = str.charAt(i);
      c = (h2int(code0) << 4) | h2int(code1);
      encodedString += c;
    } else {

      encodedString += c;
    }
    yield();
  }

  return encodedString;
}

String WebClient::urlencode(String str)
{
  String encodedString = "";
  char c;
  char code0;
  char code1;
  char code2;
  for (int i = 0; i < str.length(); i++) {
    c = str.charAt(i);
    if (c == ' ') {
      encodedString += '+';
    } else if (isalnum(c)) {
      encodedString += c;
    } else {
      code1 = (c & 0xf) + '0';
      if ((c & 0xf) > 9) {
        code1 = (c & 0xf) - 10 + 'A';
      }
      c = (c >> 4) & 0xf;
      code0 = c + '0';
      if (c > 9) {
        code0 = c - 10 + 'A';
      }
      code2 = '\0';
      encodedString += '%';
      encodedString += code0;
      encodedString += code1;
      //encodedString+=code2;
    }
    yield();
  }
  return encodedString;

}

unsigned char WebClient::h2int(char c)
{
  if (c >= '0' && c <= '9') {
    return ((unsigned char)c - '0');
  }
  if (c >= 'a' && c <= 'f') {
    return ((unsigned char)c - 'a' + 10);
  }
  if (c >= 'A' && c <= 'F') {
    return ((unsigned char)c - 'A' + 10);
  }
  return (0);
}

Vessel.h

C/C++
The header file for the functionality of the vessel
#ifndef Vessel_h
#define Vessel_h

#define DEFAULT_RANGE 12;//12 mtrs

class Vessel {
    /**
       Vessel Data object
    */
    struct VesselData {
      int time;
      double latitude;//current position
      double longitude;
      double heading;
      double thrust;
      bool manual;
    };
    
  public: Vessel();

    void setup();
    void setCourse( double bearing, double thrust );//returns true if the course is within the turn angle
    bool update( double latitude, double longitude, double bearing, double speed, bool updated );
    void loop( double bearing);
    void stop();

  private:
    bool enable;
    double bearing;//0-360
    double speed;
    VesselData data;
 };

#endif

Credits

keesp

keesp

4 projects • 1 follower

Comments

Add projectSign up / Login