BigW Consortium Gitlab

Commit f94500f6 by Zahid Chowdhury

Add locationService to use GPS if available & accurate, otherwise use wifi with…

Add locationService to use GPS if available & accurate, otherwise use wifi with Combain service to get location
parent a0e5b5c6
#include <curl/curl.h>
#include "CombainHttp.h"
#include "legato.h"
#include "interfaces.h"
static ThreadSafeQueue<std::tuple<ma_combainLocation_LocReqHandleRef_t, std::string>> *RequestJson;
static ThreadSafeQueue<std::tuple<ma_combainLocation_LocReqHandleRef_t, std::string>> *ResponseJson;
static le_event_Id_t ResponseAvailableEvent;
static struct ReceiveBuffer
{
size_t used;
uint8_t data[1024];
} HttpReceiveBuffer;
void CombainHttpInit(
ThreadSafeQueue<std::tuple<ma_combainLocation_LocReqHandleRef_t, std::string>> *requestJson,
ThreadSafeQueue<std::tuple<ma_combainLocation_LocReqHandleRef_t, std::string>> *responseJson,
le_event_Id_t responseAvailableEvent)
{
RequestJson = requestJson;
ResponseJson = responseJson;
ResponseAvailableEvent = responseAvailableEvent;
CURLcode res = curl_global_init(CURL_GLOBAL_ALL);
LE_ASSERT(res == 0);
}
void CombainHttpDeinit(void)
{
curl_global_cleanup();
}
static size_t WriteMemCallback(void *contents, size_t size, size_t nmemb, void *userp)
{
auto b = reinterpret_cast<ReceiveBuffer *>(userp);
const size_t numBytes = nmemb * size;
const size_t available = sizeof(b->data) - (b->used + 1);
const size_t numToCopy = std::min(available, numBytes);
memcpy(&b->data[b->used], contents, numToCopy);
b->used += numToCopy;
b->data[b->used] = 0;
return numToCopy;
}
void *CombainHttpThreadFunc(void *context)
{
le_cfg_ConnectService();
char combainApiKey[32];
const le_result_t cfgRes = le_cfg_QuickGetString(
"/ApiKey", combainApiKey, sizeof(combainApiKey) - 1, "");
LE_FATAL_IF(
cfgRes != LE_OK || combainApiKey[0] == '\0',
"Failed to read Combain API Key from config tree");
char combainUrl[128] = "https://cps.combain.com?key=";
strncat(combainUrl, combainApiKey, sizeof(combainUrl) - (1 + strlen(combainUrl)));
do {
auto t = RequestJson->dequeue();
// Bug existed as we kept tacking onto the existing buffer
HttpReceiveBuffer.used = 0;
HttpReceiveBuffer.data[0] = 0;
ma_combainLocation_LocReqHandleRef_t handle = std::get<0>(t);
std::string &requestBody = std::get<1>(t);
CURL* curl = curl_easy_init();
LE_ASSERT(curl);
LE_ASSERT(curl_easy_setopt(curl, CURLOPT_URL, combainUrl) == CURLE_OK);
struct curl_slist *httpHeaders = NULL;
httpHeaders = curl_slist_append(httpHeaders, "Content-Type:application/json");
LE_ASSERT(httpHeaders != NULL);
LE_ASSERT(curl_easy_setopt(curl, CURLOPT_HTTPHEADER, httpHeaders) == CURLE_OK);
LE_ASSERT(curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, requestBody.c_str()) == CURLE_OK);
LE_INFO("SENDING %d char: %s", requestBody.length(), requestBody.c_str());
LE_ASSERT(curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemCallback) == CURLE_OK);
LE_ASSERT(curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&HttpReceiveBuffer) == CURLE_OK);
const CURLcode res = curl_easy_perform(curl);
if (res != CURLE_OK)
{
LE_ERROR("libcurl returned error (%d): %s", res, curl_easy_strerror(res));
// TODO: better way to encode CURL errors?
ResponseJson->enqueue(std::make_tuple(handle, ""));
}
std::string json((char*)HttpReceiveBuffer.data, HttpReceiveBuffer.used);
LE_INFO("RECEIVED %d char: %s", HttpReceiveBuffer.used, (char*) HttpReceiveBuffer.data);
ResponseJson->enqueue(std::make_tuple(handle, json));
le_event_Report(ResponseAvailableEvent, NULL, 0);
curl_easy_cleanup(curl);
curl_slist_free_all(httpHeaders);
} while (true);
}
#ifndef COMBAIN_HTTP_H
#define COMBAIN_HTTP_H
#include "legato.h"
#include "interfaces.h"
#include "ThreadSafeQueue.h"
void CombainHttpInit(
ThreadSafeQueue<std::tuple<ma_combainLocation_LocReqHandleRef_t, std::string>> *requestJson,
ThreadSafeQueue<std::tuple<ma_combainLocation_LocReqHandleRef_t, std::string>> *responseJson,
le_event_Id_t responseAvailableEvent);
void CombainHttpDeinit(void);
void *CombainHttpThreadFunc(void *context);
#endif // COMBAIN_HTTP_H
#include "CombainRequestBuilder.h"
#include <jansson.h>
#include <sstream>
#include <iomanip>
static std::string macAddrToString(const uint8_t *mac);
static std::string ssidToString(const uint8_t *ssid, size_t ssidLen);
static std::string cellularTechnologyToString(ma_combainLocation_CellularTech_t cellTech);
WifiApScanItem::WifiApScanItem(
const uint8_t *bssid,
size_t bssidLen,
const uint8_t *ssid,
size_t ssidLen,
int16_t signalStrength)
{
if (bssidLen != 6)
{
throw std::runtime_error("BSSID length must be 6");
}
std::copy(&bssid[0], &bssid[6], &this->bssid[0]);
if (ssidLen > sizeof(this->ssid))
{
throw std::runtime_error("SSID is too long");
}
std::copy(&ssid[0], &ssid[ssidLen], &this->ssid[0]);
this->ssidLen = ssidLen;
if (signalStrength >= 0)
{
throw std::runtime_error("Signal strength should be negative");
}
this->signalStrength = signalStrength;
}
void CombainRequestBuilder::appendWifiAccessPoint(const WifiApScanItem& ap)
{
this->wifiAps.push_back(ap);
}
void CombainRequestBuilder::appendCellTower(const CellTowerScanItem& tower)
{
this->cellTowers.push_back(tower);
}
std::string CombainRequestBuilder::generateRequestBody(void) const
{
json_t *body = json_object();
if (!this->wifiAps.empty())
{
json_t *wifiApsArray = json_array();
for (auto const& ap : this->wifiAps)
{
json_t *jsonAp = json_object();
json_object_set_new(jsonAp, "macAddress", json_string(macAddrToString(ap.bssid).c_str()));
json_object_set_new(jsonAp, "ssid", json_string(ssidToString(ap.ssid, ap.ssidLen).c_str()));
json_object_set_new(jsonAp, "signalStrength", json_integer(ap.signalStrength));
json_array_append_new(wifiApsArray, jsonAp);
}
json_object_set_new(body, "wifiAccessPoints", wifiApsArray);
}
if (!this->cellTowers.empty())
{
json_t *cellTowersArray = json_array();
for (auto const& tower: this->cellTowers)
{
json_t *jsonTower = json_object();
json_object_set_new(jsonTower, "radioType", json_string(cellularTechnologyToString(tower.cellularTechnology).c_str()));
json_object_set_new(jsonTower, "mobileCountryCode", json_integer(tower.mcc));
json_object_set_new(jsonTower, "mobileNetworkCode", json_integer(tower.mnc));
json_object_set_new(jsonTower, "locationAccessCode", json_integer(tower.lac));
json_object_set_new(jsonTower, "cellId", json_integer(tower.cellId));
}
json_object_set_new(body, "cellTowers", cellTowersArray);
}
char* s = json_dumps(body, JSON_COMPACT);
LE_ASSERT(s != NULL);
std::string res(s);
free(s);
json_decref(body);
return res;
}
//----------------- STATIC
static std::string macAddrToString(const uint8_t *mac)
{
std::ostringstream s;
for (auto i = 0; i < 6; i++)
{
s << std::setw(2) << std::setfill('0') << std::hex << static_cast<unsigned int>(mac[i]);
if (i != 5)
{
s << ':';
}
}
return s.str();
}
static std::string ssidToString(const uint8_t *ssid, size_t ssidLen)
{
std::string s(reinterpret_cast<const char *>(ssid), ssidLen);
return s;
}
static std::string cellularTechnologyToString(ma_combainLocation_CellularTech_t cellTech)
{
switch (cellTech)
{
case MA_COMBAINLOCATION_CELL_TECH_GSM:
return "gsm";
break;
case MA_COMBAINLOCATION_CELL_TECH_CDMA:
return "cdma";
break;
case MA_COMBAINLOCATION_CELL_TECH_LTE:
return "lte";
break;
case MA_COMBAINLOCATION_CELL_TECH_WCDMA:
return "wcdma";
break;
default:
throw std::runtime_error("Invalid cellular technology");
break;
}
return "";
}
#ifndef COMBAIN_REQUEST_BUILDER_H
#define COMBAIN_REQUEST_BUILDER_H
#include "legato.h"
#include "interfaces.h"
#include <string>
#include <list>
struct WifiApScanItem
{
WifiApScanItem(
const uint8_t *bssid,
size_t bssidLen,
const uint8_t *ssid,
size_t ssidLen,
int16_t signalStrength);
uint8_t bssid[6];
uint8_t ssid[32];
size_t ssidLen;
int16_t signalStrength;
};
struct CellTowerScanItem
{
ma_combainLocation_CellularTech_t cellularTechnology;
uint16_t mcc;
uint16_t mnc;
uint32_t lac;
uint32_t cellId;
int32_t signalStrength;
};
class CombainRequestBuilder
{
public:
void appendWifiAccessPoint(const WifiApScanItem& ap);
void appendCellTower(const CellTowerScanItem& tower);
std::string generateRequestBody(void) const;
private:
std::list<WifiApScanItem> wifiAps;
std::list<CellTowerScanItem> cellTowers;
};
#endif // COMBAIN_REQUEST_BUILDER_H
#ifndef COMBAIN_REQUEST_BUILDER_H
#define COMBAIN_REQUEST_BUILDER_H
#include "legato.h"
#include "interfaces.h"
#include <string>
#include <list>
struct WifiApScanItem
{
WifiApScanItem(
const uint8_t *bssid,
size_t bssidLen,
const uint8_t *ssid,
size_t ssidLen,
int16_t signalStrength);
uint8_t bssid[6];
uint8_t ssid[32];
size_t ssidLen;
int16_t signalStrength;
};
struct CellTowerScanItem
{
ma_combainLocation_CellularTech_t cellularTechnology;
uint16_t mcc;
uint16_t mnc;
uint32_t lac;
uint32_t cellId;
int32_t signalStrength;
};
typedef struct GpsScanReading
{
int32_t lat;
int32_t lon;
int32_t hAccuracy;
int32_t alt;
int32_t vAccuracy;
} GpsScan_t ;
class CombainRequestBuilder
{
public:
void appendWifiAccessPoint(const WifiApScanItem& ap);
void appendCellTower(const CellTowerScanItem& tower);
void CreateGps(const GpsScan_t& gps);
std::string generateRequestBody(void) const;
private:
std::list<WifiApScanItem> wifiAps;
std::list<CellTowerScanItem> cellTowers;
GpsScan_t *gpsP;
};
#endif // COMBAIN_REQUEST_BUILDER_H
#include "CombainResult.h"
CombainResult::CombainResult(ma_combainLocation_Result_t type)
: type(type)
{}
ma_combainLocation_Result_t CombainResult::getType(void) const
{
return this->type;
}
CombainSuccessResponse::CombainSuccessResponse(
double latitude, double longitude, double accuracyInMeters)
: CombainResult(MA_COMBAINLOCATION_RESULT_SUCCESS),
latitude(latitude),
longitude(longitude),
accuracyInMeters(accuracyInMeters)
{}
CombainErrorResponse::CombainErrorResponse(
uint16_t code, const std::string &message, std::initializer_list<CombainError> errors)
: CombainResult(MA_COMBAINLOCATION_RESULT_ERROR),
code(code),
message(message),
errors(errors)
{}
CombainResponseParseFailure::CombainResponseParseFailure(const std::string &unparsed)
: CombainResult(MA_COMBAINLOCATION_RESULT_RESPONSE_PARSE_FAILURE),
unparsed(unparsed)
{}
CombainCommunicationFailure::CombainCommunicationFailure(void)
: CombainResult(MA_COMBAINLOCATION_RESULT_COMMUNICATION_FAILURE)
{}
#ifndef COMBAIN_RESULT_H
#define COMBAIN_RESULT_H
#include "legato.h"
#include "interfaces.h"
#include <initializer_list>
#include <list>
#include <string>
class CombainResult
{
public:
explicit CombainResult(ma_combainLocation_Result_t type);
ma_combainLocation_Result_t getType(void) const;
private:
ma_combainLocation_Result_t type;
};
struct CombainSuccessResponse : public CombainResult
{
CombainSuccessResponse(double latitude, double longitude, double accuracyInMeters);
double latitude;
double longitude;
double accuracyInMeters;
};
struct CombainError
{
std::string domain;
std::string reason;
std::string message;
};
struct CombainErrorResponse : public CombainResult
{
CombainErrorResponse(
uint16_t code, const std::string &message, std::initializer_list<CombainError> errors);
uint16_t code;
std::string message;
std::list<CombainError> errors;
};
struct CombainResponseParseFailure : public CombainResult
{
explicit CombainResponseParseFailure(const std::string& unparsed);
std::string unparsed;
};
struct CombainCommunicationFailure : public CombainResult
{
CombainCommunicationFailure(void);
};
#endif // COMBAIN_RESULT_H
cxxflags:
{
-std=c++14
-I$LEGATO_BUILD/framework/libjansson/include
}
sources:
{
combainLocationApi.cpp
CombainRequestBuilder.cpp
CombainResult.cpp
CombainHttp.cpp
}
provides:
{
api:
{
$CURDIR/../ma_combainLocation.api
}
}
ldflags:
{
-ljansson
}
requires:
{
api:
{
le_cfg.api
}
lib:
{
curl
}
component:
{
${LEGATO_ROOT}/components/3rdParty/curl
}
file:
{
/legato/systems/current/lib/libjansson.so /usr/lib/
/lib/libz.so.1 /lib/
/lib/libnss_dns.so.2 /lib/
/lib/libnss_dns-2.24.so /lib/
/lib/libresolv.so.2 /lib/
/lib/libresolv-2.24.so /lib/
/etc/resolv.conf /etc/
/etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
/usr/bin/curl /usr/bin/
}
}
bundles:
{
file:
{
#if file_exists(${LEGATO_SYSROOT}/usr/lib/libgnutls.so.30)
${LEGATO_SYSROOT}/usr/lib/libgnutls.so.30 /usr/lib/
#elif file_exists(${LEGATO_SYSROOT}/usr/lib/libgnutls.so.28)
${LEGATO_SYSROOT}/usr/lib/libgnutls.so.28 /usr/lib/
#endif
#if file_exists(${LEGATO_SYSROOT}/usr/lib/libidn.so.11)
${LEGATO_SYSROOT}/usr/lib/libidn.so.11 /usr/lib/
#endif
#if file_exists(${LEGATO_SYSROOT}/usr/lib/libnettle.so.6)
${LEGATO_SYSROOT}/usr/lib/libnettle.so.6 /usr/lib/
#elif file_exists(${LEGATO_SYSROOT}/usr/lib/libnettle.so.4)
${LEGATO_SYSROOT}/usr/lib/libnettle.so.4 /usr/lib/
#endif
#if file_exists(${LEGATO_SYSROOT}/usr/lib/libhogweed.so.4)
${LEGATO_SYSROOT}/usr/lib/libhogweed.so.4 /usr/lib/
#elif file_exists(${LEGATO_SYSROOT}/usr/lib/libhogweed.so.2)
${LEGATO_SYSROOT}/usr/lib/libhogweed.so.2 /usr/lib/
#endif
#if file_exists(${LEGATO_SYSROOT}/usr/lib/libgmp.so.10.3.1)
${LEGATO_SYSROOT}/usr/lib/libgmp.so.10.3.1 /usr/lib/
${LEGATO_SYSROOT}/usr/lib/libgmp.so.10 /usr/lib/
#elif file_exists(${LEGATO_SYSROOT}/usr/lib/libgmp.so.10.2.0)
${LEGATO_SYSROOT}/usr/lib/libgmp.so.10.2.0 /usr/lib/
${LEGATO_SYSROOT}/usr/lib/libgmp.so.10 /usr/lib/
#endif
}
}
\ No newline at end of file
#ifndef THREAD_SAFE_QUEUE_H
#define THREAD_SAFE_QUEUE_H
#include <queue>
#include <mutex>
#include <condition_variable>
// A threadsafe-queue.
template <class T> class ThreadSafeQueue
{
public:
ThreadSafeQueue(void)
: q() , m() , c()
{}
~ThreadSafeQueue(void)
{}
// Add an element to the queue.
void enqueue(T t)
{
std::lock_guard<std::mutex> lock(this->m);
this->q.push(t);
this->c.notify_one();
}
// Get the "front"-element.
// If the queue is empty, wait till a element is avaiable.
T dequeue(void)
{
std::unique_lock<std::mutex> lock(this->m);
this->c.wait(lock, [this]{ return !this->q.empty(); });
while(this->q.empty())
{
// release lock as long as the wait and reaquire it afterwards.
this->c.wait(lock);
}
T val = this->q.front();
this->q.pop();
return val;
}
private:
std::queue<T> q;
mutable std::mutex m;
std::condition_variable c;
};
#endif // THREAD_SAFE_QUEUE_H
#include "legato.h"
#include "interfaces.h"
#include <list>
#include <stdexcept>
#include <memory>
#include <algorithm>
#include <jansson.h>
#include "CombainRequestBuilder.h"
#include "CombainResult.h"
#include "CombainHttp.h"
#include "ThreadSafeQueue.h"
struct RequestRecord
{
ma_combainLocation_LocReqHandleRef_t handle;
le_msg_SessionRef_t clientSession;
std::shared_ptr<CombainRequestBuilder> request;
ma_combainLocation_LocationResultHandlerFunc_t responseHandler;
void *responseHandlerContext;
std::shared_ptr<CombainResult> result;
};
// Just use a list for all of the requests because it's very unlikely that there will be more than
// one or two active at a time.
static std::list<RequestRecord> Requests;
ThreadSafeQueue<std::tuple<ma_combainLocation_LocReqHandleRef_t, std::string>> RequestJson;
ThreadSafeQueue<std::tuple<ma_combainLocation_LocReqHandleRef_t, std::string>> ResponseJson;
le_event_Id_t ResponseAvailableEvent;
static ma_combainLocation_LocReqHandleRef_t GenerateHandle(void);
static RequestRecord* GetRequestRecordFromHandle(
ma_combainLocation_LocReqHandleRef_t handle, bool matchClientSession);
static bool TryParseAsSuccess(json_t *responseJson, std::shared_ptr<CombainResult>& result);
static bool TryParseAsError(json_t *responseJson, std::shared_ptr<CombainResult>& result);
ma_combainLocation_LocReqHandleRef_t ma_combainLocation_CreateLocationRequest
(
void
)
{
Requests.emplace_back();
auto& r = Requests.back();
r.handle = GenerateHandle();
r.clientSession = ma_combainLocation_GetClientSessionRef();
r.request.reset(new CombainRequestBuilder());
return r.handle;
}
le_result_t ma_combainLocation_AppendWifiAccessPoint
(
ma_combainLocation_LocReqHandleRef_t handle,
const uint8_t *bssid,
size_t bssidLen,
const uint8_t *ssid,
size_t ssidLen,
int16_t signalStrength
)
{
RequestRecord *requestRecord = GetRequestRecordFromHandle(handle, true);
if (!requestRecord)
{
LE_ERROR("Failed to GetRequestRecordFromHandle");
return LE_BAD_PARAMETER;
}
if (!requestRecord->request)
{
// Request builder doesn't exist, must have already been submitted
LE_ERROR("Request builder doesn't exist, must have already been submitted");
return LE_BUSY;
}
std::unique_ptr<WifiApScanItem> ap;
try {
ap.reset(new WifiApScanItem(bssid, bssidLen, ssid, ssidLen, signalStrength));
}
catch (std::runtime_error& e)
{
LE_ERROR("Failed to append AP info: %s", e.what());
return LE_BAD_PARAMETER;
}
requestRecord->request->appendWifiAccessPoint(*ap);
return LE_OK;
}
le_result_t ma_combainLocation_AppendCellTower
(
ma_combainLocation_LocReqHandleRef_t handle,
ma_combainLocation_CellularTech_t cellularTechnology,
uint16_t mcc,
uint16_t mnc, ///< Use systemId, (sid) for CDMA
uint32_t lac, ///< Network id for CDMA
uint32_t cellId, ///< Basestation Id for CDMAj
int32_t signalStrength ///< Signal strength in dBm
)
{
RequestRecord *requestRecord = GetRequestRecordFromHandle(handle, true);
if (!requestRecord)
{
return LE_BAD_PARAMETER;
}
if (!requestRecord->request)
{
// Request builder doesn't exist, must have already been submitted
return LE_BUSY;
}
// TODO
return LE_OK;
}
le_result_t ma_combainLocation_SubmitLocationRequest
(
ma_combainLocation_LocReqHandleRef_t handle,
ma_combainLocation_LocationResultHandlerFunc_t responseHandler,
void *context
)
{
RequestRecord *requestRecord = GetRequestRecordFromHandle(handle, true);
if (!requestRecord)
{
return LE_BAD_PARAMETER;
}
if (!requestRecord->request)
{
// Request builder doesn't exist, must have already been submitted
return LE_BUSY;
}
std::string requestBody = requestRecord->request->generateRequestBody();
LE_DEBUG("Submitting request: %s", requestBody.c_str());
{
FILE* f = fopen("request.txt", "w");
LE_ASSERT(f != NULL);
fwrite(requestBody.c_str(), 1, requestBody.size(), f);
fclose(f);
}
requestRecord->responseHandler = responseHandler;
requestRecord->responseHandlerContext = context;
// NULL out the request generator since we're done with it
requestRecord->request.reset();
RequestJson.enqueue(std::make_tuple(handle, requestBody));
return LE_OK;
}
void ma_combainLocation_DestroyLocationRequest
(
ma_combainLocation_LocReqHandleRef_t handle
)
{
std::remove_if(
Requests.begin(),
Requests.end(),
[handle] (const RequestRecord& rec) {
return handle == rec.handle &&
rec.clientSession == ma_combainLocation_GetClientSessionRef();
});
}
le_result_t ma_combainLocation_GetSuccessResponse
(
ma_combainLocation_LocReqHandleRef_t handle,
double *latitude,
double *longitude,
double *accuracyInMeters
)
{
RequestRecord *requestRecord = GetRequestRecordFromHandle(handle, true);
if (!requestRecord)
{
return LE_BAD_PARAMETER;
}
auto r = requestRecord->result;
if (!r)
{
return LE_UNAVAILABLE;
}
if (r->getType() != MA_COMBAINLOCATION_RESULT_SUCCESS)
{
return LE_UNAVAILABLE;
}
std::shared_ptr<CombainSuccessResponse> sr = std::static_pointer_cast<CombainSuccessResponse>(r);
*latitude = sr->latitude;
*longitude = sr->longitude;
*accuracyInMeters = sr->accuracyInMeters;
ma_combainLocation_DestroyLocationRequest(handle);
return LE_OK;
}
le_result_t ma_combainLocation_GetErrorResponse
(
ma_combainLocation_LocReqHandleRef_t handle,
char *firstDomain,
size_t firstDomainLen,
char *firstReason,
size_t firstReasonLen,
char *firstMessage,
size_t firstMessageLen,
uint16_t *code,
char *message,
size_t messageLen
)
{
RequestRecord *requestRecord = GetRequestRecordFromHandle(handle, true);
if (!requestRecord)
{
return LE_BAD_PARAMETER;
}
auto r = requestRecord->result;
if (!r)
{
return LE_UNAVAILABLE;
}
if (r->getType() != MA_COMBAINLOCATION_RESULT_ERROR)
{
return LE_UNAVAILABLE;
}
std::shared_ptr<CombainErrorResponse> er = std::static_pointer_cast<CombainErrorResponse>(r);
*code = er->code;
strncpy(message, er->message.c_str(), messageLen - 1);
message[messageLen - 1] = '\0';
auto it = er->errors.begin();
if (it != er->errors.end())
{
strncpy(firstDomain, it->domain.c_str(), firstDomainLen - 1);
firstDomain[firstDomainLen - 1] = '\0';
strncpy(firstReason, it->reason.c_str(), firstReasonLen - 1);
firstReason[firstReasonLen - 1] = '\0';
strncpy(firstMessage, it->message.c_str(), firstMessageLen - 1);
firstMessage[firstMessageLen - 1] = '\0';
}
ma_combainLocation_DestroyLocationRequest(handle);
return LE_OK;
}
le_result_t ma_combainLocation_GetParseFailureResult
(
ma_combainLocation_LocReqHandleRef_t handle,
char *unparsedResponse,
size_t unparsedResponseLen
)
{
RequestRecord *requestRecord = GetRequestRecordFromHandle(handle, true);
if (!requestRecord)
{
return LE_BAD_PARAMETER;
}
auto r = requestRecord->result;
if (!r)
{
return LE_UNAVAILABLE;
}
if (r->getType() != MA_COMBAINLOCATION_RESULT_RESPONSE_PARSE_FAILURE)
{
return LE_UNAVAILABLE;
}
std::shared_ptr<CombainResponseParseFailure> rpf =
std::static_pointer_cast<CombainResponseParseFailure>(r);
strncpy(
unparsedResponse,
rpf->unparsed.c_str(),
std::min(rpf->unparsed.length() + 1, unparsedResponseLen + 1));
return LE_OK;
}
//--------------------------------------------------------------------------------------------------
/**
* A handler for client disconnects which frees all resources associated with the client.
*/
//--------------------------------------------------------------------------------------------------
static void ClientSessionClosedHandler
(
le_msg_SessionRef_t clientSession,
void* context
)
{
std::remove_if(
Requests.begin(),
Requests.end(),
[clientSession] (const RequestRecord& rec) {
return rec.clientSession == clientSession;
});
}
static ma_combainLocation_LocReqHandleRef_t GenerateHandle(void)
{
static uint32_t next = 1;
ma_combainLocation_LocReqHandleRef_t h = (ma_combainLocation_LocReqHandleRef_t)next;
next += 2;
return h;
}
static RequestRecord* GetRequestRecordFromHandle(
ma_combainLocation_LocReqHandleRef_t handle, bool matchClientSession)
{
auto it = std::find_if(
Requests.begin(),
Requests.end(),
[handle, matchClientSession] (const RequestRecord& r) -> bool {
return (
r.handle == handle && (
!matchClientSession ||
r.clientSession == ma_combainLocation_GetClientSessionRef()));
});
return (it == Requests.end()) ? NULL : &(*it);
}
static void HandleResponseAvailable(void *reportPayload)
{
auto t = ResponseJson.dequeue();
ma_combainLocation_LocReqHandleRef_t handle = std::get<0>(t);
std::string responseJsonStr = std::get<1>(t);
RequestRecord *requestRecord = GetRequestRecordFromHandle(handle, false);
if (!requestRecord)
{
// Just do nothing the request no longer exists
LE_WARN("Received a response for an invalid handle");
return;
}
// There should never be a previous result
//LE_ASSERT(!requestRecord->result);
// TODO: This is a bit gross that we're using an empty response to signal a communication
// failure. We may wish to be more expressive about why the communication failed.
if (responseJsonStr.empty())
{
requestRecord->result.reset(new CombainCommunicationFailure());
}
else
{
// try to parse the response as json
json_error_t loadError;
const size_t loadFlags = 0;
json_t *responseJson = json_loads(responseJsonStr.c_str(), loadFlags, &loadError);
if (responseJson == NULL)
{
requestRecord->result.reset(new CombainResponseParseFailure(responseJsonStr));
}
else if (!TryParseAsSuccess(responseJson, requestRecord->result) &&
!TryParseAsError(responseJson, requestRecord->result))
{
requestRecord->result.reset(new CombainResponseParseFailure(responseJsonStr));
}
json_decref(responseJson);
}
requestRecord->responseHandler(
handle, requestRecord->result->getType(), requestRecord->responseHandlerContext);
}
static bool TryParseAsError(json_t *responseJson, std::shared_ptr<CombainResult>& result)
{
const char *domain;
const char *reason;
const char *errorMessage;
int code;
const char *message;
const int errorUnpackRes = json_unpack(
responseJson,
"{s:{s:{s:s,s:s,s:s},s:i,s:s}}",
"error",
"errors",
"domain",
&domain,
"reason",
&reason,
"message",
&errorMessage,
"code",
&code,
"message",
&message);
const bool parseSuccess = (errorUnpackRes == 0);
if (parseSuccess)
{
result.reset(new CombainErrorResponse(code, message, {{domain, reason, errorMessage}}));
}
return parseSuccess;
}
static bool TryParseAsSuccess(json_t *responseJson, std::shared_ptr<CombainResult>& result)
{
double latitude;
double longitude;
int accuracy;
const int successUnpackRes = json_unpack(
responseJson,
"{s:{s:F,s:F},s:i}",
"location",
"lat",
&latitude,
"lng",
&longitude,
"accuracy",
&accuracy);
const bool parseSuccess = (successUnpackRes == 0);
if (parseSuccess)
{
result.reset(new CombainSuccessResponse(latitude, longitude, accuracy));
}
return parseSuccess;
}
COMPONENT_INIT
{
ResponseAvailableEvent = le_event_CreateId("CombainResponseAvailable", 0);
le_event_AddHandler(
"CombainResponseAvailableHandler", ResponseAvailableEvent, HandleResponseAvailable);
// Register a handler to be notified when clients disconnect
le_msg_AddServiceCloseHandler(
ma_combainLocation_GetServiceRef(), ClientSessionClosedHandler, NULL);
CombainHttpInit(&RequestJson, &ResponseJson, ResponseAvailableEvent);
le_thread_Ref_t httpThread = le_thread_Create("CombainHttp", CombainHttpThreadFunc, NULL);
le_thread_Start(httpThread);
}
sandboxed: true
start: manual
version: 1.0
executables:
{
combainLocation = ( combain )
}
processes:
{
run:
{
( combainLocation )
}
envVars:
{
LE_LOG_LEVEL = DEBUG
}
faultAction: stopApp
}
extern:
{
combainLocation.combain.ma_combainLocation
}
requires:
{
configTree:
{
[r] .
}
}
//--------------------------------------------------------------------------------------------------
/**
* Component definition file for the positioning sensor
*/
//--------------------------------------------------------------------------------------------------
requires:
{
api:
{
positioning/le_posCtrl.api
positioning/le_pos.api
dhubIO = io.api
${CURDIR}/../../CombainLocation/ma_combainLocation.api
wifi/le_wifiClient.api
//modemServices/le_mrc.api
}
component:
{
periodicSensor
}
}
cflags:
{
"-std=c99"
}
sources:
{
location.c
}
//--------------------------------------------------------------------------------------------------
/**
* Implementation of the Brooklyn position sensor interface to the Data Hub.
*
* Copyright (C) Sierra Wireless Inc.
*/
//--------------------------------------------------------------------------------------------------
#include "legato.h"
#include "interfaces.h"
#include "periodicSensor.h"
#include <stdio.h>
#include <time.h>
#define HACCURACY_GOOD 50
#define HACCURACY_GREY_ZONE_UPPER 500
typedef enum {GPS, WIFI} Loc_t;
typedef struct ScanReading
{
double lat;
double lon;
double hAccuracy;
double alt;
double vAccuracy;
} Scan_t ;
// Hacking - assuming mangOH Yellow and the Cypress chip as wlan1 - TODO: add TI Wifi on wlan0?
//static const char *interfacePtr = "wlan1";
static struct
{
ma_combainLocation_LocReqHandleRef_t combainHandle;
bool waitingForWifiResults;
le_wifiClient_NewEventHandlerRef_t wifiHandler;
} State;
static psensor_Ref_t saved_ref= NULL;
uint64_t GetCurrentTimestamp(void)
{
struct timeval tv;
gettimeofday(&tv, NULL);
uint64_t utcMilliSec = (uint64_t)(tv.tv_sec) * 1000 + (uint64_t)(tv.tv_usec) / 1000;
return utcMilliSec;
}
static bool MacAddrStringToBinary(const char* s, uint8_t *b)
{
size_t inputOffset = 0;
const size_t macStrLen = (6 * 2) + (6 - 1);
while (inputOffset < macStrLen)
{
if (inputOffset % 3 == 2)
{
if (s[inputOffset] != ':')
{
return false;
}
}
else
{
uint8_t nibble;
if (s[inputOffset] >= '0' && s[inputOffset] <= '9')
{
nibble = s[inputOffset] - '0';
}
else if (s[inputOffset] >= 'a' && s[inputOffset] <= 'f')
{
nibble = 10 + s[inputOffset] - 'a';
}
else if (s[inputOffset] >= 'A' && s[inputOffset] <= 'F')
{
nibble = 10 + s[inputOffset] - 'A';
}
else
{
return false;
}
if (inputOffset % 3 == 0)
{
b[inputOffset / 3] = (nibble << 4);
}
else
{
b[inputOffset / 3] |= (nibble << 0);
}
}
inputOffset++;
}
if (s[inputOffset] != '\0')
{
return false;
}
return true;
}
static void PackJson
(
Loc_t loc,
Scan_t *scanp,
char *jsonp,
int jsonl
)
{
uint64_t ts = 0;
le_result_t res;
int len;
uint16_t hours;
uint16_t minutes;
uint16_t seconds;
uint16_t millis;
uint16_t years;
uint16_t months;
uint16_t days;
time_t rawtime;
struct tm * timeinfo;
res = le_pos_GetTime(&hours, &minutes, &seconds, &millis);
if (res == LE_OK){
res = le_pos_GetDate(&years, &months, &days);
if (res == LE_OK){
time ( &rawtime );
timeinfo = localtime ( &rawtime );
timeinfo->tm_year = years - 1900;
timeinfo->tm_mon = months - 1;
timeinfo->tm_mday = days;
timeinfo->tm_hour = hours;
timeinfo->tm_min = minutes;
timeinfo->tm_sec = seconds;
time_t converted = mktime(timeinfo);
ts = (uint64_t)converted * 1000;
} else {
LE_ERROR("Could not get position date: %s", LE_RESULT_TXT(res));
ts = GetCurrentTimestamp();
}
} else {
LE_ERROR("Could not get position time: %s", LE_RESULT_TXT(res));
ts = GetCurrentTimestamp();
}
if (loc == GPS)
len = snprintf(jsonp, jsonl,
"{ \"lat\": %lf, \"lon\": %lf, \"hAcc\": %lf,"
" \"alt\": %lf, \"vAcc\": %lf, \"fixType\" : \"GNSS\", \"ts\" : %ju}",
scanp->lat, scanp->lon, scanp->hAccuracy,
scanp->alt, scanp->vAccuracy, (uintmax_t)ts);
else if (loc == WIFI)
len = snprintf(jsonp, jsonl,
"{ \"lat\": %lf, \"lon\": %lf, \"hAcc\": %lf,"
" \"fixType\" : \"WIFI\", \"ts\" : %ju}",
scanp->lat, scanp->lon, scanp->hAccuracy, (uintmax_t)ts);
else
LE_FATAL("ILLEGAL Location Type: WIFI|GPS");
if (len >= jsonl)
{
LE_FATAL("JSON string (len %d) is longer than buffer (size %zu).", len, jsonl);
}
}
static void LocationResultHandler(
ma_combainLocation_LocReqHandleRef_t handle, ma_combainLocation_Result_t result, void *context)
{
switch (result)
{
case MA_COMBAINLOCATION_RESULT_SUCCESS:
{
Scan_t scan;
char json[256];
const le_result_t res = ma_combainLocation_GetSuccessResponse(
handle, &scan.lat, &scan.lon, &scan.hAccuracy);
if (res != LE_OK)
{
LE_INFO("Received result notification of type success response, but couldn't fetch the result\n");
exit(1);
}
else
{
LE_INFO("Location: latitude=%f, longitude=%f, accuracy=%f meters\n",
scan.lat, scan.lon, scan.hAccuracy);
PackJson(WIFI, &scan, json, sizeof(json));
LE_INFO("Sent dhub json: %s", json);
psensor_PushJson(saved_ref, 0 /* now */, json);
saved_ref = NULL;
}
break;
}
case MA_COMBAINLOCATION_RESULT_ERROR:
{
char firstDomain[65];
char firstReason[65];
char firstMessage[129];
uint16_t code;
char message[129];
const le_result_t res = ma_combainLocation_GetErrorResponse(
handle,
firstDomain,
sizeof(firstDomain) - 1,
firstReason,
sizeof(firstReason) - 1,
firstMessage,
sizeof(firstMessage) - 1,
&code,
message,
sizeof(message) - 1);
if (res != LE_OK)
{
LE_INFO("Received result notification of type success response, but couldn't fetch the result\n");
exit(1);
}
else
{
LE_INFO("Received an error response.\n");
LE_INFO(" firstDomain: %s\n", firstDomain);
LE_INFO(" firstReason: %s\n", firstReason);
LE_INFO(" firstMessage: %s\n", firstMessage);
LE_INFO(" code: %d\n", code);
LE_INFO(" message: %s\n", message);
exit(1);
}
break;
}
case MA_COMBAINLOCATION_RESULT_RESPONSE_PARSE_FAILURE:
{
char rawResult[257];
const le_result_t res = ma_combainLocation_GetParseFailureResult(
handle, rawResult, sizeof(rawResult) - 1);
if (res != LE_OK)
{
LE_INFO("Received result notification of type success response, but couldn't fetch the result\n");
exit(1);
}
else
{
LE_INFO("Received a result which couldn't be parsed \"%s\"\n", rawResult);
exit(1);
}
break;
}
case MA_COMBAINLOCATION_RESULT_COMMUNICATION_FAILURE:
LE_INFO("Couldn't communicate with Combain server\n");
exit(1);
break;
default:
LE_INFO("Received unhandled result type (%d)\n", result);
exit(1);
}
}
static bool TrySubmitRequest(void)
{
if (!State.waitingForWifiResults)
{
LE_INFO("Attempting to submit location request");
const le_result_t res = ma_combainLocation_SubmitLocationRequest(
State.combainHandle, LocationResultHandler, NULL);
if (res != LE_OK)
LE_FATAL("Failed to submit location request\n");
LE_INFO("Submitted request handle: %d", (uint32_t) State.combainHandle);
return true;
}
LE_INFO("Cannot submit WIFI location request to Combain as previous in transit");
return false;
}
static void WifiEventHandler(le_wifiClient_Event_t event, void *context)
{
LE_INFO("Called WifiEventHandler() with event=%d", event);
switch (event)
{
case LE_WIFICLIENT_EVENT_SCAN_DONE:
if (State.waitingForWifiResults)
{
State.waitingForWifiResults = false;
le_wifiClient_AccessPointRef_t ap = le_wifiClient_GetFirstAccessPoint();
while (ap != NULL)
{
uint8_t ssid[32];
size_t ssidLen = sizeof(ssid);
char bssid[(2 * 6) + (6 - 1) + 1]; // "nn:nn:nn:nn:nn:nn\0"
int16_t signalStrength;
le_result_t res;
uint8_t bssidBytes[6];
res = le_wifiClient_GetSsid(ap, ssid, &ssidLen);
if (res != LE_OK)
{
LE_INFO("Failed while fetching WiFi SSID\n");
exit(1);
}
res = le_wifiClient_GetBssid(ap, bssid, sizeof(bssid) - 1);
if (res != LE_OK)
{
LE_INFO("Failed while fetching WiFi BSSID\n");
exit(1);
}
// TODO: LE-10254 notes that an incorrect error code of 0xFFFF is mentioned in the
// documentation. The error code used in the implementation is 0xFFF.
signalStrength = le_wifiClient_GetSignalStrength(ap);
if (signalStrength == 0xFFF)
{
LE_INFO("Failed while fetching WiFi signal strength\n");
exit(1);
}
if (!MacAddrStringToBinary(bssid, bssidBytes))
{
LE_INFO("WiFi scan contained invalid bssid=\"%s\"\n", bssid);
exit(1);
}
res = ma_combainLocation_AppendWifiAccessPoint(
State.combainHandle, bssidBytes, 6, ssid, ssidLen, signalStrength);
LE_INFO("Submitted AccessPoint: %d ssid: %s", (uint32_t) State.combainHandle, (char *) ssid);
if (res != LE_OK)
{
LE_INFO("Failed to append WiFi scan results to combain request\n");
exit(1);
}
ap = le_wifiClient_GetNextAccessPoint();
}
TrySubmitRequest();
}
break;
case LE_WIFICLIENT_EVENT_SCAN_FAILED:
LE_INFO("WiFi scan failed\n");
exit(1);
break;
default:
// Do nothing - don't care about connect/disconnect events
break;
}
}
static void Sample
(
psensor_Ref_t ref
)
{
Scan_t scan;
char json[256];
int32_t lat;
int32_t lon;
int32_t hAccuracy;
int32_t alt;
int32_t vAccuracy;
le_result_t posRes = le_pos_Get3DLocation(&lat, &lon, &hAccuracy, &alt, &vAccuracy);
/* LE_INFO("le_pos_Get3DLocation returned: lat = %d lon: %d hAccuracy: %d alt: %d vAccuracy: %d",
lat, lon, hAccuracy, alt, vAccuracy); */
scan.lat = (double) lat / 1000000.0 ;
scan.lon = (double) lon / 1000000.0;
scan.hAccuracy = (double) hAccuracy;
scan.alt = (double) alt / 1000.0;
scan.vAccuracy = (double) vAccuracy;
/* LE_INFO("CVT double: lat = %f lon: %f hAccuracy: %f alt: %f vAccuracy: %f",
scan.lat, scan.lon, scan.hAccuracy, scan.alt, scan.vAccuracy); */
// No GPS or low accuracy GPS try Wifi Scan & Combain translation
if (posRes != LE_OK || scan.hAccuracy > HACCURACY_GOOD) {
le_result_t startRes;
if (!State.waitingForWifiResults) {
/* Legato WIFI is broken so we need to create a fake access point to do a scan */
const char *ssidPtr = "mangOH";
le_wifiClient_AccessPointRef_t createdAccessPoint;
createdAccessPoint = le_wifiClient_Create((const uint8_t *)ssidPtr, strlen(ssidPtr));
if (NULL == createdAccessPoint)
{
LE_INFO("le_wifiClient_Create returns NULL.\n");
exit(1);
}
startRes = le_wifiClient_SetInterface(createdAccessPoint, "wlan1");
if (LE_OK != startRes)
{
LE_FATAL("ERROR: le_wifiClient_SetInterface returns %d.\n", startRes);
exit(1);
}
startRes = le_wifiClient_Start();
if (startRes != LE_OK && startRes != LE_BUSY) {
LE_FATAL("Couldn't start the WiFi service error code: %s", LE_RESULT_TXT(startRes));
exit(1);
}
le_wifiClient_Scan();
State.combainHandle = ma_combainLocation_CreateLocationRequest();
LE_INFO("Create request handle: %d", (uint32_t) State.combainHandle);
saved_ref = ref;
State.waitingForWifiResults = true;
}
else
LE_INFO("le_wifiClient_Scan still RUNNING");
}
// Good GPS
else if (posRes == LE_OK && scan.hAccuracy <= HACCURACY_GOOD)
{
PackJson(GPS, &scan, json, sizeof(json));
psensor_PushJson(ref, 0 /* now */, json);
// Kill any errant WIFI sacn requests as we got GPS
if (State.waitingForWifiResults)
State.waitingForWifiResults = false;
}
else
LE_ERROR("SHOULD NOT REACH HERE");
}
COMPONENT_INIT
{
// Activate the positioning service.
le_posCtrl_ActivationRef_t posCtrlRef = le_posCtrl_Request();
LE_FATAL_IF(posCtrlRef == NULL, "Couldn't activate positioning service");
State.wifiHandler = le_wifiClient_AddNewEventHandler(WifiEventHandler, NULL);
// Use the periodic sensor component from the Data Hub to implement the timer and Data Hub
// interface. We'll provide samples as JSON structures.
psensor_Create("coordinates", DHUBIO_DATA_TYPE_JSON, "", Sample);
dhubIO_SetJsonExample("coordinates/value", "{\"lat\":0.1,\"lon\":0.2,"
"\"alt\":0.3,\"hAcc\":0.4,\"vAcc\":0.5,\"fixType\":\"GNSS\"}");
}
sandboxed: true
start: manual
version: 0.0.1
extern:
{
}
executables:
{
location = ( components )
}
processes:
{
run:
{
( location )
}
//faultAction: restartApp
envVars:
{
LE_LOG_LEVEL = DEBUG
}
}
bindings:
{
location.components.le_pos -> positioningService.le_pos
location.components.le_posCtrl -> positioningService.le_posCtrl
location.periodicSensor.dhubIO -> dataHub.io
location.components.dhubIO -> dataHub.io
location.components.ma_combainLocation -> combainLocation.ma_combainLocation
location.components.le_wifiClient -> wifiService.le_wifiClient
//location.components.le_mrc -> modemService.le_mrc
}
DEFINE WIFI_BSSID_BYTES = 6;
DEFINE WIFI_SSID_MAX_BYTES = 32;
//--------------------------------------------------------------------------------------------------
/**
* An opaque object for a location request.
*/
//--------------------------------------------------------------------------------------------------
REFERENCE LocReqHandle;
//--------------------------------------------------------------------------------------------------
/**
* Creates a location request object in the service.
*/
//--------------------------------------------------------------------------------------------------
FUNCTION LocReqHandle CreateLocationRequest();
//--------------------------------------------------------------------------------------------------
/**
* Append information about one WiFi access point to the request object
*/
//--------------------------------------------------------------------------------------------------
FUNCTION le_result_t AppendWifiAccessPoint
(
LocReqHandle handle IN, ///< Handle from CreateLocationRequest()
uint8 bssid[WIFI_BSSID_BYTES] IN, ///< BSSID of the WiFi AP. Must be exactly 6 bytes.
uint8 ssid[WIFI_SSID_MAX_BYTES] IN, ///< SSID of the WiFi AP. Not necessarily a string. Do not
///< null terminate.
int16 signalStrength IN ///< Signal strength in dB. E.g. -62.
);
ENUM CellularTech
{
CELL_TECH_GSM,
CELL_TECH_CDMA,
CELL_TECH_LTE,
CELL_TECH_WCDMA
};
FUNCTION le_result_t AppendCellTower
(
LocReqHandle handle IN, ///< Handle from CreateLocationRequest()
CellularTech cellularTechnology,
uint16 mcc,
uint16 mnc, ///< Use systemId, (sid) for CDMA
uint32 lac, ///< Network id for CDMA
uint32 cellId, ///< Basestation Id for CDMAj
int32 signalStrength ///< Signal strength in dBm
);
//--------------------------------------------------------------------------------------------------
/**
* Result type that is passed to the callback when after submitting a request
*/
//--------------------------------------------------------------------------------------------------
ENUM Result
{
RESULT_SUCCESS,
RESULT_ERROR,
RESULT_RESPONSE_PARSE_FAILURE,
RESULT_COMMUNICATION_FAILURE,
};
//--------------------------------------------------------------------------------------------------
/**
* Handler that will be called when a result of a request is available
*/
//--------------------------------------------------------------------------------------------------
HANDLER LocationResultHandler
(
LocReqHandle handle, ///< Handle of the request
Result result ///< What type of result is available
);
//--------------------------------------------------------------------------------------------------
/**
* Submits the location request to the Combain server for processing.
*/
//--------------------------------------------------------------------------------------------------
FUNCTION le_result_t SubmitLocationRequest
(
LocReqHandle handle IN, ///< Handle of the request to submit
LocationResultHandler resultHandler ///< Handler that will be called on completion of the
///< request if this function returns LE_OK
);
//--------------------------------------------------------------------------------------------------
/**
* Destroys a previously created request freeing the resources allocated in the service. Note that
* the GetSuccessResponse() and GetErrorResponse() functions implicitly destroy the request, so
* calling this function is only necessary if the client decides not to submit the request after
* creating it or decides not to retrieve the response.
*/
//--------------------------------------------------------------------------------------------------
FUNCTION DestroyLocationRequest
(
LocReqHandle handle IN ///< Handle of the request to destroy
);
//--------------------------------------------------------------------------------------------------
/**
* Gets the response data from a successfully completed request. Note that the request is implicitly
* destroyed by calling this function.
*
* @return LE_OK on success. If the result is not LE_OK, the content of the out parameters is
* undefined.
*/
//--------------------------------------------------------------------------------------------------
FUNCTION le_result_t GetSuccessResponse
(
LocReqHandle handle IN,
double latitude OUT,
double longitude OUT,
double accuracyInMeters OUT
// TODO: geoname, address fallback?
);
//--------------------------------------------------------------------------------------------------
/**
* Gets the response data from a request which failed to complete. Note that the request is
* implicitly destroyed by calling this function.
*
* @return LE_OK on success. If the result is not LE_OK, the content of the out parameters is
* undefined.
*/
//--------------------------------------------------------------------------------------------------
FUNCTION le_result_t GetErrorResponse
(
LocReqHandle handle IN,
string firstDomain[64] OUT,
string firstReason[64] OUT,
string firstMessage[128] OUT,
uint16 code OUT,
string message[128] OUT
);
//--------------------------------------------------------------------------------------------------
/**
* Gets the unparsed data from a parse failure result
*
* @return LE_OK on success. If the result is not LE_OK, the content of the out parameter is
* undefined.
*/
//--------------------------------------------------------------------------------------------------
FUNCTION le_result_t GetParseFailureResult
(
LocReqHandle handle IN,
string unparsedResponse[256] OUT
);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment