240 lines
5.4 KiB
C++
240 lines
5.4 KiB
C++
/*
|
|
* Stairs
|
|
* Copyright 2017 (c) Mark van Renswoude
|
|
*
|
|
* https://git.x2software.net/pub/Stairs
|
|
*/
|
|
#include "geocode.h"
|
|
#include <ESP8266WiFi.h>
|
|
#include <ESPAsyncTCP.h>
|
|
|
|
#include "../debug.h"
|
|
|
|
#ifdef SecretsPresent
|
|
#include "../secret.h"
|
|
#endif
|
|
|
|
|
|
struct ResponseBuffer
|
|
{
|
|
void* data;
|
|
size_t length;
|
|
ResponseBuffer* next;
|
|
};
|
|
|
|
|
|
struct RequestArg
|
|
{
|
|
String address;
|
|
AsyncWebServerRequest *request;
|
|
ResponseBuffer* responseData;
|
|
size_t responseOffset;
|
|
};
|
|
|
|
|
|
String urlencode(const String url)
|
|
{
|
|
String encoded;
|
|
|
|
for (int i = 0; i < url.length(); i++)
|
|
{
|
|
char c = url.charAt(i);
|
|
if (c == 0x20)
|
|
encoded += "%20";
|
|
else if (isalnum(c))
|
|
encoded += c;
|
|
else
|
|
{
|
|
encoded += "%";
|
|
if (c < 0x10) encoded += "0";
|
|
encoded += String(c, HEX);
|
|
}
|
|
}
|
|
|
|
return encoded;
|
|
}
|
|
|
|
|
|
void memcpy_unaligned(void* dest, void* source, size_t length)
|
|
{
|
|
uint8_t* destChar = (uint8_t*)dest;
|
|
uint8_t* sourceChar = (uint8_t*)source;
|
|
|
|
while (length > 0)
|
|
{
|
|
*destChar = *sourceChar;
|
|
|
|
sourceChar++;
|
|
destChar++;
|
|
length--;
|
|
}
|
|
}
|
|
|
|
|
|
void mapsGeocodeDisconnect(void* arg, AsyncClient* c);
|
|
void mapsGeocodeData(void* arg, AsyncClient* c, void* data, size_t len);
|
|
|
|
|
|
void mapsGeocodeConnect(void* arg, AsyncClient* client)
|
|
{
|
|
RequestArg* requestArg = (RequestArg*)arg;
|
|
|
|
client->onError(nullptr, nullptr);
|
|
client->onData(mapsGeocodeData, arg);
|
|
client->onDisconnect(mapsGeocodeDisconnect, arg);
|
|
|
|
#ifdef SecretsPresent
|
|
String url = "/maps/api/geocode/json?address=" + urlencode(requestArg->address) + "&key=" + urlencode(GoogleAPIKey);
|
|
#else
|
|
String url = "/maps/api/geocode/json?address=" + urlencode(requestArg->address);
|
|
#endif
|
|
|
|
client->write(String("GET " + url + " HTTP/1.0\r\nHost: maps.googleapis.com\n\r\n").c_str());
|
|
}
|
|
|
|
|
|
void mapsGeocodeData(void* arg, AsyncClient* c, void* data, size_t len)
|
|
{
|
|
RequestArg* requestArg = (RequestArg*)arg;
|
|
|
|
_dln("> OnData");
|
|
|
|
// Store all received chunks in a linked list
|
|
ResponseBuffer* next = new ResponseBuffer;
|
|
next->data = malloc(len);
|
|
memcpy_unaligned(next->data, data, len);
|
|
next->length = len;
|
|
next->next = nullptr;
|
|
|
|
if (requestArg->responseData == nullptr)
|
|
requestArg->responseData = next;
|
|
else
|
|
{
|
|
ResponseBuffer* prev = requestArg->responseData;
|
|
while (prev->next != nullptr)
|
|
prev = prev->next;
|
|
|
|
prev->next = next;
|
|
}
|
|
_dln("< OnData");
|
|
}
|
|
|
|
|
|
void mapsGeocodeDisconnect(void* arg, AsyncClient* client)
|
|
{
|
|
RequestArg* requestArg = (RequestArg*)arg;
|
|
|
|
_dln("> OnDisconnect");
|
|
if (requestArg->responseData == nullptr)
|
|
{
|
|
requestArg->request->send(500);
|
|
return;
|
|
}
|
|
|
|
// Send back the linked list using a chunked response
|
|
AsyncWebServerResponse *response = requestArg->request->beginChunkedResponse("application/json", [requestArg](uint8_t *buffer, size_t maxLen, size_t index) -> size_t
|
|
{
|
|
_dln("> ChunkedResponse");
|
|
if (requestArg->responseOffset >= requestArg->responseData->length)
|
|
{
|
|
// End of the chunk, go to the next one
|
|
ResponseBuffer* next = requestArg->responseData->next;
|
|
delete requestArg->responseData;
|
|
|
|
requestArg->responseData = next;
|
|
requestArg->responseOffset = 0;
|
|
}
|
|
|
|
if (requestArg->responseData == nullptr)
|
|
{
|
|
// We sent the last one, clean up what remains
|
|
ResponseBuffer* next;
|
|
while (requestArg->responseData != nullptr)
|
|
{
|
|
next = requestArg->responseData->next;
|
|
|
|
free(requestArg->responseData->data);
|
|
delete requestArg->responseData;
|
|
|
|
requestArg->responseData = next;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
if (maxLen >= requestArg->responseData->length - requestArg->responseOffset)
|
|
{
|
|
// Send the remainder of the chunk
|
|
memcpy_unaligned(buffer, (uint8_t*)requestArg->responseData->data + requestArg->responseOffset, requestArg->responseData->length - requestArg->responseOffset);
|
|
requestArg->responseOffset = requestArg->responseData->length;
|
|
}
|
|
else
|
|
{
|
|
memcpy_unaligned(buffer, (uint8_t*)requestArg->responseData->data + requestArg->responseOffset, maxLen);
|
|
requestArg->responseOffset += maxLen;
|
|
}
|
|
_dln("< ChunkedResponse");
|
|
});
|
|
|
|
requestArg->request->send(response);
|
|
|
|
delete requestArg;
|
|
delete client;
|
|
_dln("< OnDisconnect");
|
|
}
|
|
|
|
|
|
void handleGetLatLong(AsyncWebServerRequest *request)
|
|
{
|
|
_dln("API :: get lat long");
|
|
|
|
RequestArg* requestArg = new RequestArg();
|
|
requestArg->request = request;
|
|
requestArg->responseData = nullptr;
|
|
requestArg->responseOffset = 0;
|
|
|
|
AsyncWebParameter* addressParam = request->getParam("address");
|
|
if (addressParam == nullptr)
|
|
{
|
|
request->send(400);
|
|
delete requestArg;
|
|
return;
|
|
}
|
|
|
|
requestArg->address = addressParam->value();
|
|
if (!requestArg->address.length())
|
|
{
|
|
request->send(400);
|
|
delete requestArg;
|
|
return;
|
|
}
|
|
|
|
AsyncClient* httpClient = new AsyncClient();
|
|
httpClient->onError([](void* arg, AsyncClient* client, int error)
|
|
{
|
|
RequestArg* requestArg = (RequestArg*)arg;
|
|
|
|
_dln("API :: get lat long: OnError");
|
|
requestArg->request->send(500);
|
|
|
|
delete requestArg;
|
|
delete client;
|
|
});
|
|
|
|
httpClient->onConnect(mapsGeocodeConnect, requestArg);
|
|
if (!httpClient->connect("maps.googleapis.com", 443, true))
|
|
{
|
|
_dln("API :: get lat long: failed to connect to Google API");
|
|
delete requestArg;
|
|
delete httpClient;
|
|
|
|
request->send(500);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void registerGeocodeRoutes(AsyncWebServer* server)
|
|
{
|
|
server->on("/api/geo/latlong", HTTP_GET, handleGetLatLong);
|
|
} |