Hardware components | ||||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
![]() |
| × | 1 | |||
Software apps and online services | ||||||
|
It's been a while since Day 2 of our project to develop an autonomous vessel. Sometimes personal issues get in the way of doing really fun stuff, but everything is out of the way and the past week we have been busy to do some testing and take off from where we left of previously.
As a small reminder, we can register a vessel with the Aquabots Client, and have just added a Grove GPS unit to our Arduino, so that we can pinpoint the location. today we will add a bit of code so that the Aquabots Client will be able to continuously get the updated position provided by the GPS. The hardware (therefore) is exactly similar to that of day 2.
Today we will include all the software sources, so that we can see the dependencies between the various sources. The main entry is the Vessels module, which contains the well-known Arduino functions setup() and loop(). This module constructs four other modules, each with a dedicated task:
1: WebClient: manages the communications with the Aquabots Client
2: Registration: registers the vessel with the Aquabots Client (see day 1)
3: TinyGPS: Manages the GPS Unit connected to Serial1 of the Arduino Mega (see day 2)
4: Vessel. This is the new kid on the block! Currently it does little more than send the latitude and longitude of the vessel to the Aquabots Client. However, in the coming days we will add the servo controller unit, so that our vessel will really come to life!
With all the preparation done, we can finally look forward to getting the propellers turning, but we will reserve that for day 4.
//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;
}
#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);
}
#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::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() {
}
#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
#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 );
}
#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::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);
}
#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
Comments
Please log in or sign up to comment.