Using 1Sheeld to interact with the Uber API and make a ride request

Hi guys! So I’m trying to use Uber’s API so that I can call a cab from my Arduino. To make things easier, I’m using 1Sheeld (which talks to my phone over bluetooth so I can use my phone to provide a connection to the internet or GPS data etc.)

I’m reasonably new to Arduino so I’ve been having some trouble getting this to work. Reading the documentation for 1Sheeld and the Uber API, I’ve got as far as working out that I need to make a POST request to a particular endpoint, then read the response to get a “fare_id”, which I need to then use in a POST request to another endpoint.

The part I’m having real trouble with is reading the response and putting the fare ID into the next request. I’ve tried to model my code around this working example of something similar I found on the 1Sheeld forum: https://1sheeld.com/topic/cant-get-this-code-to-work/

I have posted on the 1Sheeld forums, but I think this forum has got a few more eyes on it and you guys are always so helpful I thought I’d try my luck here as well :slight_smile: My code is below, I’ve obviously redacted my token and GPS locations! Also, eventually the main code is going to be triggered by a button press, but rather than coding this now I’ve just added a large delay to the end.

When I run it, the first request goes fine and I get the response I’m expecting, but the second request gives me an error, something like “unable to parse JSON in request body code: invalid_json”. I’d really appreciate any feedback you can give :slight_smile:

(Also, 1Sheeld documentation here: https://1sheeld.com/shields/internet-shield/)

#define CUSTOM_SETTINGS
#define INCLUDE_INTERNET_SHIELD
#define INCLUDE_TERMINAL_SHIELD
#define INCLUDE_GPS_SHIELD
#define INCLUDE_PUSH_BUTTON_SHIELD

#include <OneSheeld.h>

HttpRequest RideRequest("https://sandbox-api.uber.com/v1.2/requests");
HttpRequest RideEstimate("https://sandbox-api.uber.com/v1.2/requests/estimate");


void setup()
{
  OneSheeld.begin();
   
    RideEstimate.addHeader("Authorization", "Bearer <TOKEN>");
    RideEstimate.addHeader("Accept-Language", "en_US");
    RideEstimate.addHeader("Content-Type", "application/json"); 
    RideEstimate.addRawData("{\"start_latitude\": <LATITUDE>, \"start_longitude\": <LONGITUDE>, \"end_latitude\": <LATITUDE>, \"end_longitude\": <LONGITUDE> }");
    
    RideEstimate.setOnSuccess(&onSuccess);
    RideEstimate.getResponse().setOnJsonResponse(&onJsonReply);
    
    RideRequest.addHeader("Authorization", "Bearer <TOKEN>");
    RideRequest.addHeader("Content-Type", "application/json");  
    RideRequest.addHeader("Accept-Language", "en_EN");

    OneSheeld.delay(1000);
}

void loop()
{
  
    Internet.performPost(RideEstimate);
    HttpResponse response = RideEstimate.getResponse();
    
    
    OneSheeld.delay(1000);
    Terminal.println("Ordering your Uber"); 
    Internet.performPost(RideRequest);
    Terminal.println("Request made");
    
    OneSheeld.delay(100000);

}

//functions

void onSuccess(HttpResponse & response )
{

response["value"][1]["fare_id"].query();  

}

void onJsonReply(JsonKeyChain & hell, char * output)
{
  RideRequest.addRawData("\"{\"fare_id\":");
  RideRequest.addRawData(output);
  RideRequest.addRawData("\", \"start_latitude\": <LATITUDE>, \"start_longitude\": <LONGITUDE>, \"end_latitude\": <LATITUDE>, \"end_longitude\": <LONGITUDE>}");
}

Oh, also, I’m totally open to suggestions if anyone knows where else I might be able to look for answers! I’ve run out of ideas and Googled until my fingers blistered.

    Internet.performPost(RideEstimate);
    HttpResponse response = RideEstimate.getResponse();
   
   
    OneSheeld.delay(1000);
    Terminal.println("Ordering your Uber");
    Internet.performPost(RideRequest);
    Terminal.println("Request made");

What, exactly, does performPost() do? You seem to assume that will succeed, and that you can ignore the response and make another request, which you also assume will succeed. I'm curious why you make those assumptions.

I'm curious what the response object actually contains. I would think that you should be, too.

PaulS:

    Internet.performPost(RideEstimate);

HttpResponse response = RideEstimate.getResponse();
 
 
    OneSheeld.delay(1000);
    Terminal.println(“Ordering your Uber”);
    Internet.performPost(RideRequest);
    Terminal.println(“Request made”);



What, exactly, does performPost() do? You seem to assume that will succeed, and that you can ignore the response and make another request, which you also assume will succeed. I'm curious why you make those assumptions.

I'm curious what the response object actually contains. I would think that you should be, too.

Thanks for your response!

As far as I can work out, performPost() is meant to make a HTTP POST request to the URL specified above the setup, with all the headers/data added in the setup – is it not doing that? Genuine question! I’m trying to figure it out! In the 1Sheeld app the first POST request seems to be happening as I expect.

I guess I do assume that it succeeds at the moment, but I was hoping I wasn’t ignoring the response? In the setup of the function I have a setOnSuccess that is meant to be dealing with the success by reading the response. I wasn’t sure where that code should live, so I’ve modelled mine off of the example I found (https://1sheeld.com/topic/cant-get-this-code-to-work/), where that code lived in the setup and came before any HTTP requests were performed. Should I move the code for onSuccess to just after the request? Add some error handling? The 1Sheeld app is showing me that the first HTTP POST is succeeding and giving me the response I want, so I wasn’t too worried about the case when the request wasn’t successful for now, although I was definitely going to add some code to deal with that eventuality afterwards. I guess eventually I’ll put the second request in a conditional that only runs if the first request has succeeded, or should I do that now?

I’m very curious what the response object contains! Is there a way for me to find out? I couldn’t work it out from the documentation on the 1Sheeld website (https://1sheeld.com/shields/internet-shield/) and I assumed that it contained the JSON response from the server, again, mostly worked out from the example code for the weather app on the 1Sheeld forum…

Hope I’ve made some sense! I really appreciate your help!

is it not doing that?

Maybe it is. But, what, exactly, does that POST request look like? If you make the same POST request from a PC, what happens?

but I was hoping I wasn't ignoring the response?

Aren't you? Where do you use the response object?

In the setup of the function I have a setOnSuccess that is meant to be dealing with the success by reading the response.

No, it might do something if the response indicates success. But, if it doesn't, you make the second request anyway.

Do you have a different function that gets called after the second POST request? Do you really want to do the same thing after both POST requests? Why does onSuccess() not tell you anything?

I'm very curious what the response object contains! Is there a way for me to find out?

One of the libraries you are using, without providing links. defines a class called httpResponse. Surely that class provides methods to examine the response object.

PaulS: Maybe it is. But, what, exactly, does that POST request look like? If you make the same POST request from a PC, what happens?

I'm really sorry if I'm missing a hint here, but the POST request looks fine to me. In the 1Sheeld app I can see all the headers are correct and so is the body. And I can see the response I get and it looks as I am expecting. I tried making the same POST request from a PC and got the same result.

PaulS: Aren't you? Where do you use the response object? No, it might do something if the response indicates success. But, if it doesn't, you make the second request anyway.

Right, so onSuccess will tell me the request was successfully sent but doesn't tell me anything about the response? I need something else to check whether I've got the whole response? Like there's a bool "isSentFully" that I can use to check if the response has been sent before I try to query it? (And have the next request happen after this).

PaulS: Do you have a different function that gets called after the second POST request? Do you really want to do the same thing after both POST requests? Why does onSuccess() not tell you anything? One of the libraries you are using, without providing links. defines a class called httpResponse. Surely that class provides methods to examine the response object.

At the moment I think I'm not doing anything after the second POST request? I was trying to get the rest figured out before I dealt what happens next :)

Sorry – I thought the link I provided in my original post to the documentation for the library was sufficient, but I see that it probably wasn't. I thought that was all that I had, because I didn't think to look in the library itself and investigate the class. I'm attaching that code below. Do I want one of the things that starts with JsonKeyChain at the bottom? I'm sorry if I'm not asking the most sensible questions, this is just very new to me.

/*

  Project:       1Sheeld Library 
  File:          HttpResponse.cpp
                 
  Version:       1.2

  Compiler:      Arduino avr-gcc 4.3.2

  Author:        Integreight
                 
  Date:          2015.1

*/
#define FROM_ONESHEELD_LIBRARY
#include "OneSheeld.h"
#include "InternetShield.h"


HttpResponse::HttpResponse()
{
 isInit=false;
 isDisposedTriggered=false;
 statusCode=0;
 totalBytesCount=0;
 bytes=NULL;
 bytesCount = 0;
 requestId = 0;
 index =0;
 callbacksRequested = 0;
}

int HttpResponse::getStatusCode()
{
 return statusCode;
}

int HttpResponse::getBytesCount()
{
 return bytesCount;
}

char * HttpResponse::getBytes()
{
 return bytes;
}

unsigned long HttpResponse::getTotalBytesCount()
{
 return totalBytesCount;
}

unsigned long HttpResponse::getCurrentIndex()
{
 return index;
}

void HttpResponse::getTheseBytes(unsigned long start,int size)
{
 if(isInit)
 {
 index=start;
 byte startArray[4] ;
   startArray[0] = start & 0xFF;
   startArray[1] = (start >> 8) & 0xFF;
   startArray[2] = (start >> 16) & 0xFF;
   startArray[3] = (start >> 24) & 0xFF;

   byte sizeArray[2] ;
   sizeArray[1] = (size >> 8) & 0xFF;
   sizeArray[0] = size & 0xFF;

   byte reqId[2] ;
   reqId[1] = (requestId >> 8) & 0xFF;
   reqId[0] = requestId & 0xFF;

 OneSheeld.sendShieldFrame(INTERNET_ID,0,RESPONSE_GET_NEXT_BYTES,3,new FunctionArg(2,reqId),
 new FunctionArg(4,startArray),
 new FunctionArg(2,sizeArray));
 }

}

void HttpResponse::getNextBytes(int size)
{
 getTheseBytes(index,size);
}

void HttpResponse::setOnNextResponseBytesUpdate(void (*userFunction)(HttpResponse &))
{
 callbacksRequested |= RESPONSE_GET_NEXT_RESPONSE_BIT;
 getNextCallBack = userFunction;
}

void HttpResponse::setOnError(void (*userFunction)(int errorNumber))
{
 callbacksRequested |= RESPONSE_GET_ERROR_BIT;
 getErrorCallBack = userFunction;
}

void HttpResponse::setOnJsonResponse(void (*userFunction)(JsonKeyChain & chain,char data[]))
{
 callbacksRequested |= RESPONSE_GET_JSON_BIT;
 getJsonCallBack = userFunction;
}

void HttpResponse::setOnJsonArrayLengthResponse(void (*userFunction)(JsonKeyChain & chain,unsigned long size))
{
 callbacksRequested |= RESPONSE_GET_JSON_ARRAY_LENGTH_BIT;
 getJsonArrayLengthCallBack = userFunction;
}

bool HttpResponse::isSentFully()
{
 return (index>=totalBytesCount);
}

void HttpResponse::dispose(bool sendFrame)
{
 isDisposedTriggered = true;
 if(isInit && bytesCount!=0 && bytes!=NULL)
 {
 free(bytes);
 }
 isInit=false;
 bytes=NULL;
 index = 0;
 bytesCount = 0;
 statusCode = 0;
 totalBytesCount = 0;
 byte reqId[2] ;
   reqId[1] = (requestId >> 8) & 0xFF;
   reqId[0] = requestId & 0xFF;

 if(sendFrame)
 {
 OneSheeld.sendShieldFrame(INTERNET_ID,0,RESPONSE_DISPOSE,1,new FunctionArg(2,reqId));
 callbacksRequested=0;
 }
}

bool HttpResponse::isDisposed()
{
 return isDisposedTriggered;
}

void HttpResponse::resetIndex(unsigned long x)
{
 index=x;
}

void HttpResponse::getHeader(const char * headerName , void (*userFunction)(char incomingheaderName [],char IncomingHeaderValue[]))
{
 if(isInit)
 {
 //Check length of string 
 int headerNameLength = strlen(headerName);
 if(!headerNameLength) return;
 callbacksRequested |= RESPONSE_INPUT_GET_HEADER_BIT;
 getHeaderCallBack = userFunction;

 byte reqId[2] ;
   reqId[1] = (requestId >> 8) & 0xFF;
   reqId[0] = requestId & 0xFF;
   
 OneSheeld.sendShieldFrame(INTERNET_ID,0,RESPONSE_GET_HEADER,2,new FunctionArg(2,reqId),new FunctionArg(headerNameLength,(byte *)headerName));
 }

}

JsonKeyChain HttpResponse::operator[](int key)
{
    JsonKeyChain chain(requestId);
    return chain[key];
}

JsonKeyChain HttpResponse::operator[](const char *key)
{
 if(!strlen(key)) return 0;
    JsonKeyChain chain(requestId);
    return chain[key];
}

HttpResponse::~HttpResponse()
{
 if(isInit && bytesCount!=0)free(bytes);
}

response.getStatusCode() should return 200, to tell you that the POST was successful. Anything else is a clue that it wasn't.

response.getBytes() should return a pointer to the response, that you can print.

PaulS: response.getStatusCode() should return 200, to tell you that the POST was successful. Anything else is a clue that it wasn't.

response.getBytes() should return a pointer to the response, that you can print.

Great, thanks. It returns 200 fine (and eventually I can use this so my second request only gets made if the first succeeds), and the response is printing fine.

Is the best way to extract what I want from the response to save it to a string?

Is the best way to extract what I want from the response to save it to a string?

Yes.

PaulS: Yes.

Is there definitely not a neater way considering the response is JSON? If I save the whole response to strings I'm going to have to do lots of work to extract the value I want from them. Isn't there a way to extract the "fare_id" straight from the response seeing as that's all I want? Thanks for all your help!

Is there definitely not a neater way considering the response is JSON?

Doesn't matter if the response is ancient sanskrit. You MUST store the data in order to be able to parse it.

PaulS:
Doesn’t matter if the response is ancient sanskrit. You MUST store the data in order to be able to parse it.

Good advice to keep in mind! Thanks!

Forgive me though, I know you’ve helped me a great deal already, but there’s still something I don’t understand. I based my code off the example I linked to in the original post, and in that code I can’t see where they store the response? It looks as though they’re just selectively taking in the part they want and using that elsewhere in a function, rather than saving the whole thing. I’ll put the code below

/* Include 1Sheeld library. */
#define CUSTOM_SETTINGS
#define INCLUDE_INTERNET_SHIELD
#define INCLUDE_TERMINAL_SHIELD
#define INCLUDE_TEXT_TO_SPEECH_SHIELD
#define INCLUDE_VOICE_RECOGNIZER_SHIELD

#include <OneSheeld.h>

const char question[] = "What is the weather";

/* Create an Http request with openweathermap.org api url. */
/* It's important to be created here as a global object. */
HttpRequest request("https://api.forecast.io/forecast/28beef1f9f32195861b1072e4c68f5ab/42.0000,21.4333");
HttpRequest request2("http://api.wunderground.com/api/e08a4f661f00d457/forecast/lang:EN/q/Macedonia/Skopje.json");
/* Set a RGB LED on pin 8,9 and 10. */
void setup()
{
  /* Start communication. */
  OneSheeld.begin();
  /* Subscribe to success callback for the request. */
  request.setOnSuccess(&onSuccess);
  request2.setOnSuccess(&onSuccess2);
  /* Subscribe to json value replies. */
  request.getResponse().setOnJsonResponse(&onJsonReply);
  request2.getResponse().setOnJsonResponse(&onJsonReply2);
  /* Subscribe to response errors. */
  request.getResponse().setOnError(&onResponseError);
  request2.getResponse().setOnError(&onResponseError);
  /* Added perform get for the two requests.*/
  Internet.performGet(request);
  Internet.performGet(request2);
  /* Subscribe to Internet errors. */
  Internet.setOnError(&onInternetError);

  /* LED pin modes OUTPUT.*/

  VoiceRecognition.start();
}

void onSuccess(HttpResponse & response)
{
  /* Using the response to query the Json chain till the required value. */
  /* i.e. Get the value of 'main' in the first object of the array 'weather' in the response. */
  /* Providng that the response is in JSON format. */
  response["currently"]["summary"].query();
}

void onSuccess2(HttpResponse & response2)
{
  /* Using the response to query the Json chain till the required value. */
  /* i.e. Get the value of 'main' in the first object of the array 'weather' in the response. */
  /* Providing that the response is in JSON format. */
  response2["forecast"]["simpleforecast"]["forecastday"][0]["high"]["celsius"].query();
}

void onJsonReply(JsonKeyChain & hell, char * output)
{
  /* 1Sheeld responds using text-to-speech shield. */

  TextToSpeech.say("Weather is");
  OneSheeld.delay(2000);
  TextToSpeech.say(output);

}
void onJsonReply2(JsonKeyChain & hell, char * output)
{
  /* 1Sheeld responds using text-to-speech shield. */
  TextToSpeech.say("Temperature is");
  OneSheeld.delay(2000);
  TextToSpeech.say(output);
  OneSheeld.delay(1000);
  TextToSpeech.say("Degrees");

}

/* Set the pins of the RGB LED to the yellow color. */

void loop()
{
  /* Check if a new command is received. */
  if (VoiceRecognition.isNewCommandReceived())
  {
    /* Add paramter to the URL with the name of the country from the voice recognition. */
    request.addParameter("q", VoiceRecognition.getLastCommand());
    /* Perform a GET request using the Internet shield. */
    Internet.performGet(request);
    /* Wait for 5 seconds. */
    OneSheeld.delay(5000);
  }
}

void onResponseError(int errorNumber)
{
  /* Print out error Number.*/
  Terminal.print("Response error:");
  switch (errorNumber)
  {
    case INDEX_OUT_OF_BOUNDS: Terminal.println("INDEX_OUT_OF_BOUNDS"); break;
    case RESPONSE_CAN_NOT_BE_FOUND: Terminal.println("RESPONSE_CAN_NOT_BE_FOUND"); break;
    case HEADER_CAN_NOT_BE_FOUND: Terminal.println("HEADER_CAN_NOT_BE_FOUND"); break;
    case NO_ENOUGH_BYTES: Terminal.println("NO_ENOUGH_BYTES"); break;
    case REQUEST_HAS_NO_RESPONSE: Terminal.println("REQUEST_HAS_NO_RESPONSE"); break;
    case SIZE_OF_REQUEST_CAN_NOT_BE_ZERO: Terminal.println("SIZE_OF_REQUEST_CAN_NOT_BE_ZERO"); break;
    case UNSUPPORTED_HTTP_ENTITY: Terminal.println("UNSUPPORTED_HTTP_ENTITY"); break;
    case JSON_KEYCHAIN_IS_WRONG: Terminal.println("JSON_KEYCHAIN_IS_WRONG"); break;
  }
}

void onInternetError(int requestId, int errorNumber)
{
  /* Print out error Number.*/
  Terminal.print("Request id:");
  Terminal.println(requestId);
  Terminal.print("Internet error:");
  switch (errorNumber)
  {
    case REQUEST_CAN_NOT_BE_FOUND: Terminal.println("REQUEST_CAN_NOT_BE_FOUND"); break;
    case NOT_CONNECTED_TO_NETWORK: Terminal.println("NOT_CONNECTED_TO_NETWORK"); break;
    case URL_IS_NOT_FOUND: Terminal.println("URL_IS_NOT_FOUND"); break;
    case ALREADY_EXECUTING_REQUEST: Terminal.println("ALREADY_EXECUTING_REQUEST"); break;
    case URL_IS_WRONG: Terminal.println("URL_IS_WRONG"); break;
  }
}

It looks as though they're just selectively taking in the part they want and using that elsewhere in a function, rather than saving the whole thing.

There is lot the library does, or can do, for you, apparently.

In your onJsonReply() handler, what are you being passed? Where is the handler for the second request?