@@ -0,0 +1,47 @@
/*
* This file is subject to the terms and conditions defined in
* file 'LICENSE', which is part of this source code package.
* Tuan PM <tuanpm at live dot com>
*/
#ifndef _MQTT_OUTOBX_H_
#define _MQTT_OUTOBX_H_
#include "platform.h"

#ifdef __cplusplus
extern "C" {
#endif

typedef struct outbox_item {
char *buffer;
int len;
int msg_id;
int msg_type;
int tick;
int retry_count;
bool pending;
STAILQ_ENTRY(outbox_item) next;
} outbox_item_t;

STAILQ_HEAD(outbox_list_t, outbox_item);

typedef struct outbox_list_t * outbox_handle_t;
typedef outbox_item_t *outbox_item_handle_t;

outbox_handle_t outbox_init();
outbox_item_handle_t outbox_enqueue(outbox_handle_t outbox, uint8_t *data, int len, int msg_id, int msg_type, int tick);
outbox_item_handle_t outbox_dequeue(outbox_handle_t outbox);
outbox_item_handle_t outbox_get(outbox_handle_t outbox, int msg_id);
esp_err_t outbox_delete(outbox_handle_t outbox, int msg_id, int msg_type);
esp_err_t outbox_delete_msgid(outbox_handle_t outbox, int msg_id);
esp_err_t outbox_delete_msgtype(outbox_handle_t outbox, int msg_type);
esp_err_t outbox_delete_expired(outbox_handle_t outbox, int current_tick, int timeout);

esp_err_t outbox_set_pending(outbox_handle_t outbox, int msg_id);
int outbox_get_size(outbox_handle_t outbox);
esp_err_t outbox_cleanup(outbox_handle_t outbox, int max_size);
void outbox_destroy(outbox_handle_t outbox);

#ifdef __cplusplus
}
#endif
#endif
@@ -0,0 +1,37 @@
/*
* This file is subject to the terms and conditions defined in
* file 'LICENSE', which is part of this source code package.
* Tuan PM <tuanpm at live dot com>
*/
#ifndef _PLATFORM_H__
#define _PLATFORM_H__

//Support ESP32
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/queue.h"
#include "freertos/event_groups.h"

#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include "lwip/netdb.h"
#include "lwip/dns.h"

#include "rom/queue.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_system.h"

char *platform_create_id_string();
int platform_random(int max);
long long platform_tick_get_ms();
void ms_to_timeval(int timeout_ms, struct timeval *tv);

#define ESP_MEM_CHECK(TAG, a, action) if (!(a)) { \
ESP_LOGE(TAG,"%s:%d (%s): %s", __FILE__, __LINE__, __FUNCTION__, "Memory exhausted"); \
action; \
}

#endif
@@ -0,0 +1,242 @@
/*
* This file is subject to the terms and conditions defined in
* file 'LICENSE', which is part of this source code package.
* Tuan PM <tuanpm at live dot com>
*/
#ifndef _TRANSPORT_H_
#define _TRANSPORT_H_

#include <esp_err.h>

#ifdef __cplusplus
extern "C" {
#endif


typedef struct transport_list_t* transport_list_handle_t;
typedef struct transport_item_t* transport_handle_t;

typedef int (*connect_func)(transport_handle_t t, const char *host, int port, int timeout_ms);
typedef int (*io_func)(transport_handle_t t, const char *buffer, int len, int timeout_ms);
typedef int (*io_read_func)(transport_handle_t t, char *buffer, int len, int timeout_ms);
typedef int (*trans_func)(transport_handle_t t);
typedef int (*poll_func)(transport_handle_t t, int timeout_ms);

/**
* @brief Create transport list
*
* @return A handle can hold all transports
*/
transport_list_handle_t transport_list_init();

/**
* @brief Cleanup and free all transports, include itself,
* this function will invoke transport_destroy of every transport have added this the list
*
* @param[in] list The list
*
* @return
* - ESP_OK
* - ESP_FAIL
*/
esp_err_t transport_list_destroy(transport_list_handle_t list);

/**
* @brief Add a transport to the list, and define a scheme to indentify this transport in the list
*
* @param[in] list The list
* @param[in] t The Transport
* @param[in] scheme The scheme
*
* @return
* - ESP_OK
*/
esp_err_t transport_list_add(transport_list_handle_t list, transport_handle_t t, const char *scheme);

/**
* @brief This function will remove all transport from the list,
* invoke transport_destroy of every transport have added this the list
*
* @param[in] list The list
*
* @return
* - ESP_OK
* - ESP_ERR_INVALID_ARG
*/
esp_err_t transport_list_clean(transport_list_handle_t list);

/**
* @brief Get the transport by scheme, which has been defined when calling function `transport_list_add`
*
* @param[in] list The list
* @param[in] tag The tag
*
* @return The transport handle
*/
transport_handle_t transport_list_get_transport(transport_list_handle_t list, const char *scheme);

/**
* @brief Initialize a transport handle object
*
* @return The transport handle
*/
transport_handle_t transport_init();

/**
* @brief Cleanup and free memory the transport
*
* @param[in] t The transport handle
*
* @return
* - ESP_OK
* - ESP_FAIL
*/
esp_err_t transport_destroy(transport_handle_t t);

/**
* @brief Get default port number used by this transport
*
* @param[in] t The transport handle
*
* @return the port number
*/
int transport_get_default_port(transport_handle_t t);

/**
* @brief Set default port number that can be used by this transport
*
* @param[in] t The transport handle
* @param[in] port The port number
*
* @return
* - ESP_OK
* - ESP_FAIL
*/
esp_err_t transport_set_default_port(transport_handle_t t, int port);

/**
* @brief Transport connection function, to make a connection to server
*
* @param t The transport handle
* @param[in] host Hostname
* @param[in] port Port
* @param[in] timeout_ms The timeout milliseconds
*
* @return
* - socket for will use by this transport
* - (-1) if there are any errors, should check errno
*/
int transport_connect(transport_handle_t t, const char *host, int port, int timeout_ms);

/**
* @brief Transport read function
*
* @param t The transport handle
* @param buffer The buffer
* @param[in] len The length
* @param[in] timeout_ms The timeout milliseconds
*
* @return
* - Number of bytes was read
* - (-1) if there are any errors, should check errno
*/
int transport_read(transport_handle_t t, char *buffer, int len, int timeout_ms);

/**
* @brief Poll the transport until readable or timeout
*
* @param[in] t The transport handle
* @param[in] timeout_ms The timeout milliseconds
*
* @return
* - 0 Timeout
* - (-1) If there are any errors, should check errno
* - other The transport can read
*/
int transport_poll_read(transport_handle_t t, int timeout_ms);

/**
* @brief Transport write function
*
* @param t The transport handle
* @param buffer The buffer
* @param[in] len The length
* @param[in] timeout_ms The timeout milliseconds
*
* @return
* - Number of bytes was written
* - (-1) if there are any errors, should check errno
*/
int transport_write(transport_handle_t t, const char *buffer, int len, int timeout_ms);

/**
* @brief Poll the transport until writeable or timeout
*
* @param[in] t The transport handle
* @param[in] timeout_ms The timeout milliseconds
*
* @return
* - 0 Timeout
* - (-1) If there are any errors, should check errno
* - other The transport can write
*/
int transport_poll_write(transport_handle_t t, int timeout_ms);

/**
* @brief Transport close
*
* @param t The transport handle
*
* @return
* - 0 if ok
* - (-1) if there are any errors, should check errno
*/
int transport_close(transport_handle_t t);

/**
* @brief Get user data context of this transport
*
* @param[in] t The transport handle
*
* @return The user data context
*/
void *transport_get_context_data(transport_handle_t t);

/**
* @brief Set the user context data for this transport
*
* @param[in] t The transport handle
* @param data The user data context
*
* @return
* - ESP_OK
*/
esp_err_t transport_set_context_data(transport_handle_t t, void *data);

/**
* @brief Set transport functions for the transport handle
*
* @param[in] t The transport handle
* @param[in] _connect The connect function pointer
* @param[in] _read The read function pointer
* @param[in] _write The write function pointer
* @param[in] _close The close function pointer
* @param[in] _poll_read The poll read function pointer
* @param[in] _poll_write The poll write function pointer
* @param[in] _destroy The destroy function pointer
*
* @return
* - ESP_OK
*/
esp_err_t transport_set_func(transport_handle_t t,
connect_func _connect,
io_read_func _read,
io_func _write,
trans_func _close,
poll_func _poll_read,
poll_func _poll_write,
trans_func _destroy);
#ifdef __cplusplus
}
#endif
#endif
@@ -0,0 +1,39 @@
/*
* This file is subject to the terms and conditions defined in
* file 'LICENSE', which is part of this source code package.
* Tuan PM <tuanpm at live dot com>
*/
#ifndef _TRANSPORT_SSL_H_
#define _TRANSPORT_SSL_H_

#include "transport.h"

#ifdef __cplusplus
extern "C" {
#endif


/**
* @brief Create new SSL transport, the transport handle must be release transport_destroy callback
*
* @return the allocated transport_handle_t, or NULL if the handle can not be allocated
*/
transport_handle_t transport_ssl_init();

/**
* @brief Set SSL certificate data (as PEM format).
* Note that, this function stores the pointer to data, rather than making a copy.
* So we need to make sure to keep the data lifetime before cleanup the connection
*
* @param t ssl transport
* @param[in] data The pem data
* @param[in] len The length
*/
void transport_ssl_set_cert_data(transport_handle_t t, const char *data, int len);


#ifdef __cplusplus
}
#endif
#endif

@@ -0,0 +1,27 @@
/*
* This file is subject to the terms and conditions defined in
* file 'LICENSE', which is part of this source code package.
* Tuan PM <tuanpm at live dot com>
*/
#ifndef _TRANSPORT_TCP_H_
#define _TRANSPORT_TCP_H_

#include "transport.h"

#ifdef __cplusplus
extern "C" {
#endif

/**
* @brief Create TCP transport, the transport handle must be release transport_destroy callback
*
* @return the allocated transport_handle_t, or NULL if the handle can not be allocated
*/
transport_handle_t transport_tcp_init();


#ifdef __cplusplus
}
#endif

#endif
@@ -0,0 +1,46 @@
/*
* This file is subject to the terms and conditions defined in
* file 'LICENSE', which is part of this source code package.
* Tuan PM <tuanpm at live dot com>
*/

#ifndef _TRANSPORT_WS_H_
#define _TRANSPORT_WS_H_

#include "transport.h"

#ifdef __cplusplus
extern "C" {
#endif

#define WS_FIN 0x80
#define WS_OPCODE_TEXT 0x01
#define WS_OPCODE_BINARY 0x02
#define WS_OPCODE_CLOSE 0x08
#define WS_OPCODE_PING 0x09
#define WS_OPCODE_PONG 0x0a
// Second byte
#define WS_MASK 0x80
#define WS_SIZE16 126
#define WS_SIZE64 127
#define MAX_WEBSOCKET_HEADER_SIZE 10
#define WS_RESPONSE_OK 101

/**
* @brief Create TCP transport
*
* @return
* - transport
* - NULL
*/
transport_handle_t transport_ws_init(transport_handle_t parent_handle);

void transport_ws_set_path(transport_handle_t t, const char *path);



#ifdef __cplusplus
}
#endif

#endif

Large diffs are not rendered by default.

@@ -0,0 +1,151 @@
#include "mqtt_outbox.h"
#include <stdlib.h>
#include <string.h>
#include "rom/queue.h"
#include "esp_log.h"

static const char *TAG = "OUTBOX";

outbox_handle_t outbox_init()
{
outbox_handle_t outbox = calloc(1, sizeof(struct outbox_list_t));
ESP_MEM_CHECK(TAG, outbox, return NULL);
STAILQ_INIT(outbox);
return outbox;
}

outbox_item_handle_t outbox_enqueue(outbox_handle_t outbox, uint8_t *data, int len, int msg_id, int msg_type, int tick)
{
outbox_item_handle_t item = calloc(1, sizeof(outbox_item_t));
ESP_MEM_CHECK(TAG, item, return NULL);
item->msg_id = msg_id;
item->msg_type = msg_type;
item->tick = tick;
item->len = len;
item->buffer = malloc(len);
ESP_MEM_CHECK(TAG, item->buffer, {
free(item);
return NULL;
});
memcpy(item->buffer, data, len);
STAILQ_INSERT_TAIL(outbox, item, next);
ESP_LOGD(TAG, "ENQUEUE msgid=%d, msg_type=%d, len=%d, size=%d", msg_id, msg_type, len, outbox_get_size(outbox));
return item;
}

outbox_item_handle_t outbox_get(outbox_handle_t outbox, int msg_id)
{
outbox_item_handle_t item;
STAILQ_FOREACH(item, outbox, next) {
if (item->msg_id == msg_id) {
return item;
}
}
return NULL;
}

outbox_item_handle_t outbox_dequeue(outbox_handle_t outbox)
{
outbox_item_handle_t item;
STAILQ_FOREACH(item, outbox, next) {
if (!item->pending) {
return item;
}
}
return NULL;
}
esp_err_t outbox_delete(outbox_handle_t outbox, int msg_id, int msg_type)
{
outbox_item_handle_t item, tmp;
STAILQ_FOREACH_SAFE(item, outbox, next, tmp) {
if (item->msg_id == msg_id && item->msg_type == msg_type) {
STAILQ_REMOVE(outbox, item, outbox_item, next);
free(item->buffer);
free(item);
ESP_LOGD(TAG, "DELETED msgid=%d, msg_type=%d, remain size=%d", msg_id, msg_type, outbox_get_size(outbox));
return ESP_OK;
}

}
return ESP_FAIL;
}
esp_err_t outbox_delete_msgid(outbox_handle_t outbox, int msg_id)
{
outbox_item_handle_t item, tmp;
STAILQ_FOREACH_SAFE(item, outbox, next, tmp) {
if (item->msg_id == msg_id) {
STAILQ_REMOVE(outbox, item, outbox_item, next);
free(item->buffer);
free(item);
}

}
return ESP_OK;
}
esp_err_t outbox_set_pending(outbox_handle_t outbox, int msg_id)
{
outbox_item_handle_t item = outbox_get(outbox, msg_id);
if (item) {
item->pending = true;
return ESP_OK;
}
return ESP_FAIL;
}

esp_err_t outbox_delete_msgtype(outbox_handle_t outbox, int msg_type)
{
outbox_item_handle_t item, tmp;
STAILQ_FOREACH_SAFE(item, outbox, next, tmp) {
if (item->msg_type == msg_type) {
STAILQ_REMOVE(outbox, item, outbox_item, next);
free(item->buffer);
free(item);
}

}
return ESP_OK;
}

esp_err_t outbox_delete_expired(outbox_handle_t outbox, int current_tick, int timeout)
{
outbox_item_handle_t item, tmp;
STAILQ_FOREACH_SAFE(item, outbox, next, tmp) {
if (current_tick - item->tick > timeout) {
STAILQ_REMOVE(outbox, item, outbox_item, next);
free(item->buffer);
free(item);
}

}
return ESP_OK;
}

int outbox_get_size(outbox_handle_t outbox)
{
int siz = 0;
outbox_item_handle_t item;
STAILQ_FOREACH(item, outbox, next) {
siz += item->len;
}
return siz;
}

esp_err_t outbox_cleanup(outbox_handle_t outbox, int max_size)
{
while(outbox_get_size(outbox) > max_size) {
outbox_item_handle_t item = outbox_dequeue(outbox);
if (item == NULL) {
return ESP_FAIL;
}
STAILQ_REMOVE(outbox, item, outbox_item, next);
free(item->buffer);
free(item);
}
return ESP_OK;
}

void outbox_destroy(outbox_handle_t outbox)
{
outbox_cleanup(outbox, 0);
free(outbox);
}
@@ -0,0 +1,36 @@
#include "platform.h"

#include "esp_system.h"
#include <sys/time.h>

#define MAX_ID_STRING (32)

char *platform_create_id_string()
{
uint8_t mac[6];
char *id_string = calloc(1, MAX_ID_STRING);
ESP_MEM_CHECK("MQTT_CLIENT", id_string, return NULL);
esp_read_mac(mac, ESP_MAC_WIFI_STA);
sprintf(id_string, "ESP32_%02x%02X%02X", mac[3], mac[4], mac[5]);
return id_string;
}

int platform_random(int max)
{
return esp_random()%max;
}

long long platform_tick_get_ms()
{
struct timeval te;
gettimeofday(&te, NULL); // get current time
long long milliseconds = te.tv_sec*1000LL + te.tv_usec/1000; // calculate milliseconds
// printf("milliseconds: %lld\n", milliseconds);
return milliseconds;
}

void ms_to_timeval(int timeout_ms, struct timeval *tv)
{
tv->tv_sec = timeout_ms / 1000;
tv->tv_usec = (timeout_ms - (tv->tv_sec * 1000)) * 1000;
}
@@ -0,0 +1,218 @@
#include <stdlib.h>
#include <string.h>

#include "rom/queue.h"
#include "esp_log.h"

#include "transport.h"
#include "platform.h"


static const char *TAG = "TRANSPORT";

/**
* Transport layer structure, which will provide functions, basic properties for transport types
*/
struct transport_item_t {
int port;
int socket; /*!< Socket to use in this transport */
char *scheme; /*!< Tag name */
void *context; /*!< Context data */
void *data; /*!< Additional transport data */
connect_func _connect; /*!< Connect function of this transport */
io_read_func _read; /*!< Read */
io_func _write; /*!< Write */
trans_func _close; /*!< Close */
poll_func _poll_read; /*!< Poll and read */
poll_func _poll_write; /*!< Poll and write */
trans_func _destroy; /*!< Destroy and free transport */
STAILQ_ENTRY(transport_item_t) next;
};


/**
* This list will hold all transport available
*/
STAILQ_HEAD(transport_list_t, transport_item_t);


transport_list_handle_t transport_list_init()
{
transport_list_handle_t list = calloc(1, sizeof(struct transport_list_t));
ESP_MEM_CHECK(TAG, list, return NULL);
STAILQ_INIT(list);
return list;
}

esp_err_t transport_list_add(transport_list_handle_t list, transport_handle_t t, const char *scheme)
{
if (list == NULL || t == NULL) {
return ESP_ERR_INVALID_ARG;
}
t->scheme = calloc(1, strlen(scheme) + 1);
ESP_MEM_CHECK(TAG, t->scheme, return ESP_ERR_NO_MEM);
strcpy(t->scheme, scheme);
STAILQ_INSERT_TAIL(list, t, next);
return ESP_OK;
}

transport_handle_t transport_list_get_transport(transport_list_handle_t list, const char *scheme)
{
if (!list) {
return NULL;
}
if (scheme == NULL) {
return STAILQ_FIRST(list);
}
transport_handle_t item;
STAILQ_FOREACH(item, list, next) {
if (strcasecmp(item->scheme, scheme) == 0) {
return item;
}
}
return NULL;
}

esp_err_t transport_list_destroy(transport_list_handle_t list)
{
transport_list_clean(list);
free(list);
return ESP_OK;
}

esp_err_t transport_list_clean(transport_list_handle_t list)
{
transport_handle_t item = STAILQ_FIRST(list);
transport_handle_t tmp;
while (item != NULL) {
tmp = STAILQ_NEXT(item, next);
if (item->_destroy) {
item->_destroy(item);
}
transport_destroy(item);
item = tmp;
}
STAILQ_INIT(list);
return ESP_OK;
}

transport_handle_t transport_init()
{
transport_handle_t t = calloc(1, sizeof(struct transport_item_t));
ESP_MEM_CHECK(TAG, t, return NULL);
return t;
}

esp_err_t transport_destroy(transport_handle_t t)
{
if (t->scheme) {
free(t->scheme);
}
free(t);
return ESP_OK;
}

int transport_connect(transport_handle_t t, const char *host, int port, int timeout_ms)
{
int ret = -1;
if (t && t->_connect) {
return t->_connect(t, host, port, timeout_ms);
}
return ret;
}

int transport_read(transport_handle_t t, char *buffer, int len, int timeout_ms)
{
if (t && t->_read) {
return t->_read(t, buffer, len, timeout_ms);
}
return -1;
}

int transport_write(transport_handle_t t, const char *buffer, int len, int timeout_ms)
{
if (t && t->_write) {
return t->_write(t, buffer, len, timeout_ms);
}
return -1;
}

int transport_poll_read(transport_handle_t t, int timeout_ms)
{
if (t && t->_poll_read) {
return t->_poll_read(t, timeout_ms);
}
return -1;
}

int transport_poll_write(transport_handle_t t, int timeout_ms)
{
if (t && t->_poll_write) {
return t->_poll_write(t, timeout_ms);
}
return -1;
}

int transport_close(transport_handle_t t)
{
if (t && t->_close) {
return t->_close(t);
}
return 0;
}

void *transport_get_context_data(transport_handle_t t)
{
if (t) {
return t->data;
}
return NULL;
}

esp_err_t transport_set_context_data(transport_handle_t t, void *data)
{
if (t) {
t->data = data;
return ESP_OK;
}
return ESP_FAIL;
}

esp_err_t transport_set_func(transport_handle_t t,
connect_func _connect,
io_read_func _read,
io_func _write,
trans_func _close,
poll_func _poll_read,
poll_func _poll_write,
trans_func _destroy)
{
if (t == NULL) {
return ESP_FAIL;
}
t->_connect = _connect;
t->_read = _read;
t->_write = _write;
t->_close = _close;
t->_poll_read = _poll_read;
t->_poll_write = _poll_write;
t->_destroy = _destroy;
return ESP_OK;
}

int transport_get_default_port(transport_handle_t t)
{
if (t == NULL) {
return -1;
}
return t->port;
}

esp_err_t transport_set_default_port(transport_handle_t t, int port)
{
if (t == NULL) {
return ESP_FAIL;
}
t->port = port;
return ESP_OK;
}
@@ -0,0 +1,253 @@
#include <string.h>
#include <stdlib.h>

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include "lwip/netdb.h"
#include "lwip/dns.h"

#include "mbedtls/platform.h"
#include "mbedtls/net_sockets.h"
#include "mbedtls/esp_debug.h"
#include "mbedtls/ssl.h"
#include "mbedtls/entropy.h"
#include "mbedtls/ctr_drbg.h"
#include "mbedtls/error.h"
#include "mbedtls/certs.h"


#include "esp_log.h"
#include "esp_system.h"
#include "platform.h"

#include "transport.h"
#include "transport_ssl.h"

static const char *TAG = "TRANS_SSL";
/**
* mbedtls specific transport data
*/
typedef struct {
mbedtls_entropy_context entropy;
mbedtls_ctr_drbg_context ctr_drbg;
mbedtls_ssl_context ctx;
mbedtls_x509_crt cacert;
mbedtls_ssl_config conf;
mbedtls_net_context client_fd;
void *cert_pem_data;
int cert_pem_len;
bool ssl_initialized;
bool verify_server;
} transport_ssl_t;

static int ssl_close(transport_handle_t t);

static int ssl_connect(transport_handle_t t, const char *host, int port, int timeout_ms)
{
int ret = -1, flags;
struct timeval tv;
transport_ssl_t *ssl = transport_get_context_data(t);

if (!ssl) {
return -1;
}
ssl->ssl_initialized = true;
mbedtls_ssl_init(&ssl->ctx);
mbedtls_ctr_drbg_init(&ssl->ctr_drbg);
mbedtls_ssl_config_init(&ssl->conf);
mbedtls_entropy_init(&ssl->entropy);

if ((ret = mbedtls_ssl_config_defaults(&ssl->conf,
MBEDTLS_SSL_IS_CLIENT,
MBEDTLS_SSL_TRANSPORT_STREAM,
MBEDTLS_SSL_PRESET_DEFAULT)) != 0) {
ESP_LOGE(TAG, "mbedtls_ssl_config_defaults returned %d", ret);
goto exit;
}

if ((ret = mbedtls_ctr_drbg_seed(&ssl->ctr_drbg, mbedtls_entropy_func, &ssl->entropy, NULL, 0)) != 0) {
ESP_LOGE(TAG, "mbedtls_ctr_drbg_seed returned %d", ret);
goto exit;
}

if (ssl->cert_pem_data) {
mbedtls_x509_crt_init(&ssl->cacert);
ssl->verify_server = true;
if ((ret = mbedtls_x509_crt_parse(&ssl->cacert, ssl->cert_pem_data, ssl->cert_pem_len + 1)) < 0) {
ESP_LOGE(TAG, "mbedtls_x509_crt_parse returned -0x%x\n\nDATA=%s,len=%d", -ret, (char*)ssl->cert_pem_data, ssl->cert_pem_len);
goto exit;
}
mbedtls_ssl_conf_ca_chain(&ssl->conf, &ssl->cacert, NULL);
mbedtls_ssl_conf_authmode(&ssl->conf, MBEDTLS_SSL_VERIFY_REQUIRED);

if ((ret = mbedtls_ssl_set_hostname(&ssl->ctx, host)) != 0) {
ESP_LOGE(TAG, "mbedtls_ssl_set_hostname returned -0x%x", -ret);
goto exit;
}
} else {
mbedtls_ssl_conf_authmode(&ssl->conf, MBEDTLS_SSL_VERIFY_NONE);
}


mbedtls_ssl_conf_rng(&ssl->conf, mbedtls_ctr_drbg_random, &ssl->ctr_drbg);

#ifdef CONFIG_MBEDTLS_DEBUG
mbedtls_esp_enable_debug_log(&ssl->conf, 4);
#endif

if ((ret = mbedtls_ssl_setup(&ssl->ctx, &ssl->conf)) != 0) {
ESP_LOGE(TAG, "mbedtls_ssl_setup returned -0x%x\n\n", -ret);
goto exit;
}

mbedtls_net_init(&ssl->client_fd);

ms_to_timeval(timeout_ms, &tv);

setsockopt(ssl->client_fd.fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
ESP_LOGD(TAG, "Connect to %s:%d", host, port);
char port_str[8] = {0};
sprintf(port_str, "%d", port);
if ((ret = mbedtls_net_connect(&ssl->client_fd, host, port_str, MBEDTLS_NET_PROTO_TCP)) != 0) {
ESP_LOGE(TAG, "mbedtls_net_connect returned -%x", -ret);
goto exit;
}

mbedtls_ssl_set_bio(&ssl->ctx, &ssl->client_fd, mbedtls_net_send, mbedtls_net_recv, NULL);

if((ret = mbedtls_ssl_set_hostname(&ssl->ctx, host)) != 0) {
ESP_LOGE(TAG, " failed\n ! mbedtls_ssl_set_hostname returned %d\n\n", ret);
goto exit;
}

ESP_LOGD(TAG, "Performing the SSL/TLS handshake...");

while ((ret = mbedtls_ssl_handshake(&ssl->ctx)) != 0) {
if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
ESP_LOGE(TAG, "mbedtls_ssl_handshake returned -0x%x", -ret);
goto exit;
}
}

ESP_LOGD(TAG, "Verifying peer X.509 certificate...");

if ((flags = mbedtls_ssl_get_verify_result(&ssl->ctx)) != 0) {
/* In real life, we probably want to close connection if ret != 0 */
ESP_LOGW(TAG, "Failed to verify peer certificate!");
if (ssl->cert_pem_data) {
goto exit;
}
} else {
ESP_LOGD(TAG, "Certificate verified.");
}

ESP_LOGD(TAG, "Cipher suite is %s", mbedtls_ssl_get_ciphersuite(&ssl->ctx));
return ret;
exit:
ssl_close(t);
return ret;
}

static int ssl_poll_read(transport_handle_t t, int timeout_ms)
{
transport_ssl_t *ssl = transport_get_context_data(t);
fd_set readset;
FD_ZERO(&readset);
FD_SET(ssl->client_fd.fd, &readset);
struct timeval timeout;
ms_to_timeval(timeout_ms, &timeout);

return select(ssl->client_fd.fd + 1, &readset, NULL, NULL, &timeout);
}

static int ssl_poll_write(transport_handle_t t, int timeout_ms)
{
transport_ssl_t *ssl = transport_get_context_data(t);
fd_set writeset;
FD_ZERO(&writeset);
FD_SET(ssl->client_fd.fd, &writeset);
struct timeval timeout;
ms_to_timeval(timeout_ms, &timeout);
return select(ssl->client_fd.fd + 1, NULL, &writeset, NULL, &timeout);
}

static int ssl_write(transport_handle_t t, const char *buffer, int len, int timeout_ms)
{
int poll, ret;
transport_ssl_t *ssl = transport_get_context_data(t);

if ((poll = transport_poll_write(t, timeout_ms)) <= 0) {
ESP_LOGW(TAG, "Poll timeout or error, errno=%s, fd=%d, timeout_ms=%d", strerror(errno), ssl->client_fd.fd, timeout_ms);
return poll;
}
ret = mbedtls_ssl_write(&ssl->ctx, (const unsigned char *) buffer, len);
if (ret <= 0) {
ESP_LOGE(TAG, "mbedtls_ssl_write error, errno=%s", strerror(errno));
}
return ret;
}

static int ssl_read(transport_handle_t t, char *buffer, int len, int timeout_ms)
{
int ret;
transport_ssl_t *ssl = transport_get_context_data(t);
ret = mbedtls_ssl_read(&ssl->ctx, (unsigned char *)buffer, len);
if (ret == 0) {
return -1;
}
return ret;
}

static int ssl_close(transport_handle_t t)
{
int ret = -1;
transport_ssl_t *ssl = transport_get_context_data(t);
if (ssl->ssl_initialized) {
ESP_LOGD(TAG, "Cleanup mbedtls");
mbedtls_ssl_close_notify(&ssl->ctx);
mbedtls_ssl_session_reset(&ssl->ctx);
mbedtls_net_free(&ssl->client_fd);
mbedtls_ssl_config_free(&ssl->conf);
if (ssl->verify_server) {
mbedtls_x509_crt_free(&ssl->cacert);
}
mbedtls_ctr_drbg_free(&ssl->ctr_drbg);
mbedtls_entropy_free(&ssl->entropy);
mbedtls_ssl_free(&ssl->ctx);
ssl->ssl_initialized = false;
ssl->verify_server = false;
}
return ret;
}

static int ssl_destroy(transport_handle_t t)
{
transport_ssl_t *ssl = transport_get_context_data(t);
transport_close(t);
free(ssl);
return 0;
}

void transport_ssl_set_cert_data(transport_handle_t t, const char *data, int len)
{
transport_ssl_t *ssl = transport_get_context_data(t);
if (t && ssl) {
ssl->cert_pem_data = (void *)data;
ssl->cert_pem_len = len;
}
}

transport_handle_t transport_ssl_init()
{
transport_handle_t t = transport_init();
transport_ssl_t *ssl = calloc(1, sizeof(transport_ssl_t));
ESP_MEM_CHECK(TAG, ssl, return NULL);
mbedtls_net_init(&ssl->client_fd);
transport_set_context_data(t, ssl);
transport_set_func(t, ssl_connect, ssl_read, ssl_write, ssl_close, ssl_poll_read, ssl_poll_write, ssl_destroy);
return t;
}

@@ -0,0 +1,152 @@
#include <stdlib.h>
#include <string.h>

#include "lwip/sockets.h"
#include "lwip/dns.h"
#include "lwip/netdb.h"

#include "esp_log.h"
#include "esp_system.h"
#include "esp_err.h"

#include "platform.h"
#include "transport.h"

static const char *TAG = "TRANS_TCP";

typedef struct {
int sock;
} transport_tcp_t;

static int resolve_dns(const char *host, struct sockaddr_in *ip) {

struct hostent *he;
struct in_addr **addr_list;
he = gethostbyname(host);
if (he == NULL) {
return ESP_FAIL;
}
addr_list = (struct in_addr **)he->h_addr_list;
if (addr_list[0] == NULL) {
return ESP_FAIL;
}
ip->sin_family = AF_INET;
memcpy(&ip->sin_addr, addr_list[0], sizeof(ip->sin_addr));
return ESP_OK;
}

static int tcp_connect(transport_handle_t t, const char *host, int port, int timeout_ms)
{
struct sockaddr_in remote_ip;
struct timeval tv;
transport_tcp_t *tcp = transport_get_context_data(t);

bzero(&remote_ip, sizeof(struct sockaddr_in));

//if stream_host is not ip address, resolve it AF_INET,servername,&serveraddr.sin_addr
if (inet_pton(AF_INET, host, &remote_ip.sin_addr) != 1) {
if (resolve_dns(host, &remote_ip) < 0) {
return -1;
}
}

tcp->sock = socket(PF_INET, SOCK_STREAM, 0);

if (tcp->sock < 0) {
ESP_LOGE(TAG, "Error create socket");
return -1;
}

remote_ip.sin_family = AF_INET;
remote_ip.sin_port = htons(port);

ms_to_timeval(timeout_ms, &tv);

setsockopt(tcp->sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));

ESP_LOGD(TAG, "[sock=%d],connecting to server IP:%s,Port:%d...",
tcp->sock, ipaddr_ntoa((const ip_addr_t*)&remote_ip.sin_addr.s_addr), port);
if (connect(tcp->sock, (struct sockaddr *)(&remote_ip), sizeof(struct sockaddr)) != 0) {
close(tcp->sock);
tcp->sock = -1;
return -1;
}
return tcp->sock;
}

static int tcp_write(transport_handle_t t, const char *buffer, int len, int timeout_ms)
{
int poll;
transport_tcp_t *tcp = transport_get_context_data(t);
if ((poll = transport_poll_write(t, timeout_ms)) <= 0) {
return poll;
}
return write(tcp->sock, buffer, len);
}

static int tcp_read(transport_handle_t t, char *buffer, int len, int timeout_ms)
{
transport_tcp_t *tcp = transport_get_context_data(t);
int poll = -1;
if ((poll = transport_poll_read(t, timeout_ms)) <= 0) {
return poll;
}
int read_len = read(tcp->sock, buffer, len);
if (read_len == 0) {
return -1;
}
return read_len;
}

static int tcp_poll_read(transport_handle_t t, int timeout_ms)
{
transport_tcp_t *tcp = transport_get_context_data(t);
fd_set readset;
FD_ZERO(&readset);
FD_SET(tcp->sock, &readset);
struct timeval timeout;
ms_to_timeval(timeout_ms, &timeout);
return select(tcp->sock + 1, &readset, NULL, NULL, &timeout);
}

static int tcp_poll_write(transport_handle_t t, int timeout_ms)
{
transport_tcp_t *tcp = transport_get_context_data(t);
fd_set writeset;
FD_ZERO(&writeset);
FD_SET(tcp->sock, &writeset);
struct timeval timeout;
ms_to_timeval(timeout_ms, &timeout);
return select(tcp->sock + 1, NULL, &writeset, NULL, &timeout);
}

static int tcp_close(transport_handle_t t)
{
transport_tcp_t *tcp = transport_get_context_data(t);
int ret = -1;
if (tcp->sock >= 0) {
ret = close(tcp->sock);
tcp->sock = -1;
}
return ret;
}

static esp_err_t tcp_destroy(transport_handle_t t)
{
transport_tcp_t *tcp = transport_get_context_data(t);
transport_close(t);
free(tcp);
return 0;
}

transport_handle_t transport_tcp_init()
{
transport_handle_t t = transport_init();
transport_tcp_t *tcp = calloc(1, sizeof(transport_tcp_t));
ESP_MEM_CHECK(TAG, tcp, return NULL);
tcp->sock = -1;
transport_set_func(t, tcp_connect, tcp_read, tcp_write, tcp_close, tcp_poll_read, tcp_poll_write, tcp_destroy);
transport_set_context_data(t, tcp);

return t;
}
@@ -0,0 +1,251 @@
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#include "platform.h"
#include "transport.h"
#include "transport_tcp.h"
#include "transport_ws.h"
#include "mbedtls/base64.h"
#include "mbedtls/sha1.h"

static const char *TAG = "TRANSPORT_WS";

#define DEFAULT_WS_BUFFER (1024)

typedef struct {
char *path;
char *buffer;
transport_handle_t parent;
} transport_ws_t;

static char *trimwhitespace(const char *str)
{
char *end;

// Trim leading space
while (isspace((unsigned char)*str)) str++;

if (*str == 0) {
return (char *)str;
}

// Trim trailing space
end = (char *)(str + strlen(str) - 1);
while (end > str && isspace((unsigned char)*end)) end--;

// Write new null terminator
*(end + 1) = 0;

return (char *)str;
}


static char *get_http_header(const char *buffer, const char *key)
{
char *found = strstr(buffer, key);
if (found) {
found += strlen(key);
char *found_end = strstr(found, "\r\n");
if (found_end) {
found_end[0] = 0;//terminal string

return trimwhitespace(found);
}
}
return NULL;
}

static int ws_connect(transport_handle_t t, const char *host, int port, int timeout_ms)
{
transport_ws_t *ws = transport_get_context_data(t);
if (transport_connect(ws->parent, host, port, timeout_ms) < 0) {
ESP_LOGE(TAG, "Error connect to ther server");
}
unsigned char random_key[16] = { 0 }, client_key[32] = {0};
int i;
for (i = 0; i < sizeof(random_key); i++) {
random_key[i] = rand() & 0xFF;
}
size_t outlen = 0;
mbedtls_base64_encode(client_key, 32, &outlen, random_key, 16);
int len = snprintf(ws->buffer, DEFAULT_WS_BUFFER,
"GET %s HTTP/1.1\r\n"
"Connection: Upgrade\r\n"
"Host: %s:%d\r\n"
"Upgrade: websocket\r\n"
"Sec-WebSocket-Version: 13\r\n"
"Sec-WebSocket-Protocol: mqtt\r\n"
"Sec-WebSocket-Key: %s\r\n"
"User-Agent: ESP32 MQTT Client\r\n\r\n",
ws->path,
host, port,
client_key);
ESP_LOGD(TAG, "Write upgrate request\r\n%s", ws->buffer);
if (transport_write(ws->parent, ws->buffer, len, timeout_ms) <= 0) {
ESP_LOGE(TAG, "Error write Upgrade header %s", ws->buffer);
return -1;
}
if ((len = transport_read(ws->parent, ws->buffer, DEFAULT_WS_BUFFER, timeout_ms)) <= 0) {
ESP_LOGE(TAG, "Error read response for Upgrade header %s", ws->buffer);
return -1;
}
char *server_key = get_http_header(ws->buffer, "Sec-WebSocket-Accept:");
if (server_key == NULL) {
ESP_LOGE(TAG, "Sec-WebSocket-Accept not found");
return -1;
}

unsigned char client_key_b64[64], valid_client_key[20], accept_key[32] = {0};
int key_len = sprintf((char*)client_key_b64, "%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11", (char*)client_key);
mbedtls_sha1(client_key_b64, (size_t)key_len, valid_client_key);
mbedtls_base64_encode(accept_key, 32, &outlen, valid_client_key, 20);
accept_key[outlen] = 0;
ESP_LOGD(TAG, "server key=%s, send_key=%s, accept_key=%s", (char *)server_key, (char*)client_key, accept_key);
if (strcmp((char*)accept_key, (char*)server_key) != 0) {
ESP_LOGE(TAG, "Invalid websocket key");
return -1;
}
return 0;
}

static int ws_write(transport_handle_t t, const char *buff, int len, int timeout_ms)
{
transport_ws_t *ws = transport_get_context_data(t);
char ws_header[MAX_WEBSOCKET_HEADER_SIZE];
char *mask;
int header_len = 0, i;
char *buffer = (char *)buff;
int poll_write;
if ((poll_write = transport_poll_write(ws->parent, timeout_ms)) <= 0) {
return poll_write;
}

ws_header[header_len++] = WS_OPCODE_BINARY | WS_FIN;

// NOTE: no support for > 16-bit sized messages
if (len > 125) {
ws_header[header_len++] = WS_SIZE16 | WS_MASK;
ws_header[header_len++] = (uint8_t)(len >> 8);
ws_header[header_len++] = (uint8_t)(len & 0xFF);
} else {
ws_header[header_len++] = (uint8_t)(len | WS_MASK);
}
mask = &ws_header[header_len];
ws_header[header_len++] = rand() & 0xFF;
ws_header[header_len++] = rand() & 0xFF;
ws_header[header_len++] = rand() & 0xFF;
ws_header[header_len++] = rand() & 0xFF;

for (i = 0; i < len; ++i) {
buffer[i] = (buffer[i] ^ mask[i % 4]);
}
if (transport_write(ws->parent, ws_header, header_len, timeout_ms) != header_len) {
ESP_LOGE(TAG, "Error write header");
return -1;
}
return transport_write(ws->parent, buffer, len, timeout_ms);
}

static int ws_read(transport_handle_t t, char *buffer, int len, int timeout_ms)
{
transport_ws_t *ws = transport_get_context_data(t);
int payload_len;
char *data_ptr = buffer, opcode, mask, *mask_key = NULL;
int rlen;
int poll_read;
if ((poll_read = transport_poll_read(ws->parent, timeout_ms)) <= 0) {
return poll_read;
}
if ((rlen = transport_read(ws->parent, buffer, len, timeout_ms)) <= 0) {
ESP_LOGE(TAG, "Error read data");
return rlen;
}

opcode = (*data_ptr & 0x0F);
data_ptr ++;
mask = ((*data_ptr >> 7) & 0x01);
payload_len = (*data_ptr & 0x7F);
data_ptr++;
ESP_LOGD(TAG, "Opcode: %d, mask: %d, len: %d\r\n", opcode, mask, payload_len);
if (payload_len == 126) {
// headerLen += 2;
payload_len = data_ptr[0] << 8 | data_ptr[1];
data_ptr += 2;
} else if (payload_len == 127) {
// headerLen += 8;

if (data_ptr[0] != 0 || data_ptr[1] != 0 || data_ptr[2] != 0 || data_ptr[3] != 0) {
// really too big!
payload_len = 0xFFFFFFFF;
} else {
payload_len = data_ptr[4] << 24 | data_ptr[5] << 16 | data_ptr[6] << 8 | data_ptr[7];
}
data_ptr += 8;
}

if (mask) {
mask_key = data_ptr;
data_ptr += 4;
for (int i = 0; i < payload_len; i++) {
buffer[i] = (data_ptr[i] ^ mask_key[i % 4]);
}
} else {
memmove(buffer, data_ptr, payload_len);
}
return payload_len;
}

static int ws_poll_read(transport_handle_t t, int timeout_ms)
{
transport_ws_t *ws = transport_get_context_data(t);
return transport_poll_read(ws->parent, timeout_ms);
}

static int ws_poll_write(transport_handle_t t, int timeout_ms)
{
transport_ws_t *ws = transport_get_context_data(t);
return transport_poll_write(ws->parent, timeout_ms);;
}

static int ws_close(transport_handle_t t)
{
transport_ws_t *ws = transport_get_context_data(t);
return transport_close(ws->parent);
}

static esp_err_t ws_destroy(transport_handle_t t)
{
transport_ws_t *ws = transport_get_context_data(t);
free(ws->buffer);
free(ws->path);
free(ws);
return 0;
}
void transport_ws_set_path(transport_handle_t t, const char *path)
{
transport_ws_t *ws = transport_get_context_data(t);
ws->path = realloc(ws->path, strlen(path) + 1);
strcpy(ws->path, path);
}
transport_handle_t transport_ws_init(transport_handle_t parent_handle)
{
transport_handle_t t = transport_init();
transport_ws_t *ws = calloc(1, sizeof(transport_ws_t));
ESP_MEM_CHECK(TAG, ws, return NULL);
ws->parent = parent_handle;

ws->path = strdup("/");
ESP_MEM_CHECK(TAG, ws->path, return NULL);
ws->buffer = malloc(DEFAULT_WS_BUFFER);
ESP_MEM_CHECK(TAG, ws->buffer, {
free(ws->path);
free(ws);
return NULL;
});

transport_set_func(t, ws_connect, ws_read, ws_write, ws_close, ws_poll_read, ws_poll_write, ws_destroy);
transport_set_context_data(t, ws);
return t;
}

Large diffs are not rendered by default.

@@ -0,0 +1,32 @@
# Object files
*.o
*.ko
*.obj
*.elf

# Precompiled Headers
*.gch
*.pch

# Libraries
*.lib
*.a
*.la
*.lo

# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib

# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex

# Debug files
*.dSYM/
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) 2015 Jack Engqvist Johansson

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,9 @@
C Library for Parsing NMEA 0183 Sentences
=========================================

### This library was modified by LoBo (https://github.com/loboris)

as part of the [MicroPython ESP32 project](https://github.com/loboris/MicroPython_ESP32_psRAM_LoBo)

from the original [libnmea](https://github.com/jacketizer/libnmea) GitHub repository.

@@ -0,0 +1,39 @@
COMPONENT_SUBMODULES := src
COMPONENT_SRCDIRS := src/nmea src/parsers
COMPONENT_ADD_INCLUDEDIRS := src/nmea src/parsers
PARSER_OBJS := $(addprefix src/parsers/,\
gpgga.o \
gpgll.o \
gprmc.o \
gpgst.o \
gpvtg.o \
)

COMPONENT_OBJS := $(addprefix src/,\
nmea/nmea.o \
nmea/parser_static.o \
parsers/parse.o \
) \
$(PARSER_OBJS)

define RENAME_SYMBOLS
$(OBJCOPY) \
--redefine-sym init=nmea_$(1)_init \
--redefine-sym parse=nmea_$(1)_parse \
--redefine-sym set_default=nmea_$(1)_set_default \
--redefine-sym allocate_data=nmea_$(1)_allocate_data \
--redefine-sym free_data=nmea_$(1)_free_data \
src/parsers/$(1).o
endef

$(COMPONENT_LIBRARY): | rename_symbols

.PHONY: rename_symbols

rename_symbols: | $(PARSER_OBJS)
$(call RENAME_SYMBOLS,gpgga)
$(call RENAME_SYMBOLS,gpgll)
$(call RENAME_SYMBOLS,gprmc)
$(call RENAME_SYMBOLS,gpgst)
$(call RENAME_SYMBOLS,gpvtg)

@@ -0,0 +1,303 @@
#include "nmea.h"
#include "parser.h"
#include "parser_types.h"
#include "esp_log.h"

#define ARRAY_LENGTH(a) (sizeof a / sizeof (a[0]))

const char *NMEA_TAG = "NMEA";

/**
* Check if a value is not NULL and not empty.
*
* Returns 0 if set, otherwise -1.
*/
//-----------------------------------------
static int _is_value_set(const char *value)
{
if (NULL == value || '\0' == *value) {
return -1;
}

return 0;
}

/**
* Crop a sentence from the type word and checksum.
*
* The type word at the beginning along with the dollar sign ($) will be
* removed. If there is a checksum, it will also be removed. The two end
* characters (usually <CR><LF>) will not be included in the new string.
*
* sentence is a validated NMEA sentence string.
* length is the char length of the sentence string.
*
* Returns pointer (char *) to the new string.
*/
//--------------------------------------------------------
static char *_crop_sentence(char *sentence, size_t length)
{
/* Skip type word, 7 characters (including $ and ,) */
sentence += NMEA_PREFIX_LENGTH + NMEA_ID_LENGTH + 2;

/* Null terminate before end of line/sentence, 2 characters */
sentence[length - 9] = '\0';

/* Remove checksum, if there is one */
if ('*' == sentence[length - 12]) {
sentence[length - 12] = '\0';
}

return sentence;
}

/**
* Splits a string by comma.
*
* string is the string to split, will be manipulated. Needs to be
* null-terminated.
* values is a char pointer array that will be filled with pointers to the
* splitted values in the string.
* max_values is the maximum number of values to be parsed.
*
* Returns the number of values found in string.
*/
//----------------------------------------------------------------------------
static int _split_string_by_comma(char *string, char **values, int max_values)
{
int i = 0;

values[i++] = string;
while (i < max_values && NULL != (string = strchr(string, ','))) {
*string = '\0';
values[i++] = ++string;
}

return i;
}

/**
* Initiate the NMEA library and load the parser modules.
*
* This function will be called before the main() function.
*/
void __attribute__ ((constructor)) nmea_init(void);
//--------------
void nmea_init()
{
nmea_load_parsers();
}

/**
* Unload the parser modules.
*
* This function will be called after the exit() function.
*/
void __attribute__ ((destructor)) nmea_cleanup(void);
//-----------------
void nmea_cleanup()
{
nmea_unload_parsers();
}

//----------------------------------------
nmea_t nmea_get_type(const char *sentence)
{
nmea_parser_module_s *parser = nmea_get_parser_by_sentence(sentence);
if (NULL == parser) {
return NMEA_UNKNOWN;
}

return parser->parser.type;
}

//---------------------------------------------
uint8_t nmea_get_checksum(const char *sentence)
{
const char *n = sentence + 1;
uint8_t chk = 0;

/* While current char isn't '*' or sentence ending (newline) */
while ('*' != *n && NMEA_END_CHAR_1 != *n && '\0' != *n) {
chk ^= (uint8_t) *n;
n++;
}

return chk;
}

//--------------------------------------------------------
int nmea_has_checksum(const char *sentence, size_t length)
{
if ('*' == sentence[length - 5]) {
return 0;
}

return -1;
}

//------------------------------------------------------------------------
int nmea_validate(const char *sentence, size_t length, int check_checksum)
{
const char *n;

/* should have atleast 9 characters */
if (9 > length) {
ESP_LOGD(NMEA_TAG, "Sentence too short (%d)", length);
return -1;
}

/* should be less or equal to NMEA_MAX_LENGTH characters */
if (NMEA_MAX_LENGTH < length) {
ESP_LOGD(NMEA_TAG, "Sentence too long (%d)", length);
return -2;
}

/* should start with $ */
if ('$' != *sentence) {
ESP_LOGD(NMEA_TAG, "Sentence not starting with '$'");
return -3;
}

/* should end with \r\n, or other... */
if (NMEA_END_CHAR_2 != sentence[length - 1] || NMEA_END_CHAR_1 != sentence[length - 2]) {
ESP_LOGD(NMEA_TAG, "Sentence does not end with 'CRLF'");
return -4;
}

/* should have a 5 letter, upper case word */
n = sentence;
while (++n < sentence + 6) {
if (*n < 'A' || *n > 'Z') {
/* not upper case letter */
ESP_LOGD(NMEA_TAG, "Wrong sentence header");
return -5;
}
}

/* should have a comma after the type word */
if (',' != sentence[6]) {
ESP_LOGD(NMEA_TAG, "Wrong sentence header, no comma");
return -6;
}

/* test for not allowed characters */
n = sentence+6;
while (++n < (sentence + length - 2)) {
if ((*n < ' ') || (*n > 'z') || (*n == '$')) {
/* not allowed character */
ESP_LOGD(NMEA_TAG, "Sentence contains invalid characters");
return -7;
}
}

/* check for checksum */
if (1 == check_checksum && 0 == nmea_has_checksum(sentence, length)) {
uint8_t actual_chk;
uint8_t expected_chk;
char checksum[3];

checksum[0] = sentence[length - 4];
checksum[1] = sentence[length - 3];
checksum[2] = '\0';
actual_chk = nmea_get_checksum(sentence);
expected_chk = (uint8_t) strtol(checksum, NULL, 16);
if (expected_chk != actual_chk) {
ESP_LOGD(NMEA_TAG, "Wrong sentence checksum");
return -8;
}
}

return 0;
}

//--------------------------
void nmea_free(nmea_s *data)
{
nmea_parser_module_s *parser;

if (NULL == data) {
return;
}

parser = nmea_get_parser_by_type(data->type);
if (NULL == parser) {
return;
}

parser->free_data(data);
}

//-------------------------------------------------------------------
nmea_s *nmea_parse(char *sentence, size_t length, int check_checksum)
{
unsigned int n_vals, val_index;
char *value, *val_string;
char *values[255];
nmea_parser_module_s *parser;
nmea_t type;
int res;

/* Validate sentence string */
res = nmea_validate(sentence, length, check_checksum);
if (res < 0) {
ESP_LOGD(NMEA_TAG, "Validate error (%d)", res);
return (nmea_s *) NULL;
}

type = nmea_get_type(sentence);
if (NMEA_UNKNOWN == type) {
ESP_LOGD(NMEA_TAG, "Get type error");
return (nmea_s *) NULL;
}

/* Crop sentence from type word and checksum */
val_string = _crop_sentence(sentence, length);
if (NULL == val_string) {
ESP_LOGD(NMEA_TAG, "Sentence crop error");
return (nmea_s *) NULL;
}

/* Split the sentence into values */
n_vals = _split_string_by_comma(val_string, values, ARRAY_LENGTH(values));
if (0 == n_vals) {
ESP_LOGD(NMEA_TAG, "Sentence split error");
return (nmea_s *) NULL;
}

/* Get the right parser */
parser = nmea_get_parser_by_type(type);
if (NULL == parser) {
ESP_LOGD(NMEA_TAG, "Get parser error");
return (nmea_s *) NULL;
}

/* Allocate memory for parsed data */
parser->allocate_data((nmea_parser_s *) parser);
if (NULL == parser->parser.data) {
ESP_LOGD(NMEA_TAG, "Error allocating parser data");
return (nmea_s *) NULL;
}

/* Set default values */
parser->set_default((nmea_parser_s *) parser);
parser->errors = 0;

/* Loop through the values and parse them... */
for (val_index = 0; val_index < n_vals; val_index++) {
value = values[val_index];
if (-1 == _is_value_set(value)) {
continue;
}

if (-1 == parser->parse((nmea_parser_s *) parser, value, val_index)) {
parser->errors++;
ESP_LOGD(NMEA_TAG, "Parser error at index %d",val_index);
}
}

parser->parser.data->type = type;
parser->parser.data->errors = parser->errors;

return parser->parser.data;
}
@@ -0,0 +1,127 @@
#ifndef INC_NMEA_H
#define INC_NMEA_H

#include <stdlib.h>
#include <stdint.h>
#include <string.h>

/* NMEA sentence types */
typedef enum {
NMEA_UNKNOWN,
NMEA_GGA,
NMEA_GLL,
NMEA_RMC,
NMEA_GST,
NMEA_VTG
} nmea_t;

/* NMEA cardinal direction types */
typedef char nmea_cardinal_t;
#define NMEA_CARDINAL_DIR_NORTH (nmea_cardinal_t) 'N'
#define NMEA_CARDINAL_DIR_EAST (nmea_cardinal_t) 'E'
#define NMEA_CARDINAL_DIR_SOUTH (nmea_cardinal_t) 'S'
#define NMEA_CARDINAL_DIR_WEST (nmea_cardinal_t) 'W'
#define NMEA_CARDINAL_DIR_UNKNOWN (nmea_cardinal_t) '\0'

extern const char *NMEA_TAG;

/**
* NMEA data base struct
*
* This struct will be extended by the parser data structs (ex: nmea_gpgll_s).
*/
typedef struct {
nmea_t type;
int errors;
} nmea_s;

/* GPS position struct */
typedef struct {
double minutes;
int degrees;
nmea_cardinal_t cardinal;
} nmea_position;

/* NMEA sentence max length, including \r\n (chars) */
#define NMEA_MAX_LENGTH 83

/* NMEA sentence endings, should be \r\n according the NMEA 0183 standard */
#define NMEA_END_CHAR_1 '\r'
#define NMEA_END_CHAR_2 '\n'

/* NMEA sentence prefix length (num chars), Ex: GPGLL */
#define NMEA_PREFIX_LENGTH 3
#define NMEA_IDS_LENGTH 6
#define NMEA_ID_LENGTH 2

#ifdef __cplusplus
extern "C" {
#endif

/**
* Get the sentence type.
*
* sentence needs to be a validated NMEA sentence string.
*
* Returns nmea_t (int).
*/
extern nmea_t nmea_get_type(const char *sentence);

/**
* Calculate the checksum of the sentence.
*
* sentence needs to be a validated NMEA sentence string.
*
* Returns the calculated checksum (uint8_t).
*/
extern uint8_t nmea_get_checksum(const char *sentence);

/**
* Check if the sentence contains a precalculated checksum.
*
* sentence needs to be a validated NMEA sentence string.
* length is the character length of the sentence string.
*
* Return 0 if checksum exists, otherwise -1.
*/
extern int nmea_has_checksum(const char *sentence, size_t length);

/**
* Validate the sentence according to NMEA 0183.
*
* Criterias:
* - Should be between the correct length.
* - Should start with a dollar sign.
* - The next five characters should be uppercase letters.
* - If it has a checksum, check it.
* - Ends with the correct 2 characters.
*
* length is the character length of the sentence string.
*
* Returns 0 if sentence is valid, otherwise error code (< 0).
*/
extern int nmea_validate(const char *sentence, size_t length, int check_checksum);

/**
* Free an nmea data struct.
*
* data should be a pointer to a struct of type nmea_s.
*/
extern void nmea_free(nmea_s *data);

/**
* Parse an NMEA sentence string to a struct.
*
* sentence needs to be a validated NMEA sentence string.
* length is the character length of the sentence string.
* check_checksum, if 1 and there is a checksum, validate it.
*
* Returns a pointer to an NMEA data struct, or (nmea_s *) NULL if an error occurs.
*/
extern nmea_s *nmea_parse(char *sentence, size_t length, int check_checksum);

#ifdef __cplusplus
}
#endif

#endif /* INC_NMEA_H */
@@ -0,0 +1,68 @@
#ifndef INC_NMEA_PARSER_H
#define INC_NMEA_PARSER_H

#include <stdlib.h>
#include <string.h>
#include "nmea.h"
#include "parser_types.h"

typedef int (*allocate_data_f) (nmea_parser_s *);
typedef int (*set_default_f) (nmea_parser_s *);
typedef int (*free_data_f) (nmea_s *);
typedef int (*parse_f) (nmea_parser_s *, char *, int);
typedef int (*init_f) (nmea_parser_s *);

typedef struct {
nmea_parser_s parser;
int errors;
void *handle;

/* Functions */
allocate_data_f allocate_data;
set_default_f set_default;
free_data_f free_data;
parse_f parse;
} nmea_parser_module_s;

#ifdef __cplusplus
extern "C" {
#endif

/**
* Load the parser libs into array.
*
* Returns 0 on success, or -1 if an error occurs.
*/
int nmea_load_parsers();

/**
* Unload all the parser libs.
*/
void nmea_unload_parsers();

/**
* Initiate a parser.
*
* Returns a sentence parser struct, or (nmea_parser_module_s *) NULL if an error occurs.
*/
nmea_parser_module_s * nmea_init_parser(const char *filename);

/**
* Get a parser for a sentence type.
*
* Returns the sentence parser struct, should be checked for NULL.
*/
nmea_parser_module_s * nmea_get_parser_by_type(nmea_t type);

/**
* Get a parser for a sentence type by a sentence string.
*
* Returns the sentence parser struct, should be checked for NULL.
*/
nmea_parser_module_s * nmea_get_parser_by_sentence(const char *sentence);

#ifdef __cplusplus
}
#endif

#endif /* INC_NMEA_PARSER_H */
@@ -0,0 +1,93 @@
#include "nmea.h"
#include "parser.h"

#define PARSER_COUNT 5

#define DECLARE_PARSER_API(modname) \
extern int nmea_##modname##_init(nmea_parser_s *parser); \
extern int nmea_##modname##_allocate_data(nmea_parser_s *parser); \
extern int nmea_##modname##_set_default(nmea_parser_s *parser); \
extern int nmea_##modname##_free_data(nmea_s *data); \
extern int nmea_##modname##_parse(nmea_parser_s *parser, char *value, int val_index);

#define PARSER_LOAD(modname) \
parser = &(parsers[i]); \
parser->handle = NULL; \
parser->allocate_data = nmea_##modname##_allocate_data; \
parser->set_default = nmea_##modname##_set_default; \
parser->free_data = nmea_##modname##_free_data; \
parser->parse = nmea_##modname##_parse; \
if (-1 == nmea_##modname##_init((nmea_parser_s *) parser)) { \
return -1; \
} \
i++;

DECLARE_PARSER_API(gpgll)
DECLARE_PARSER_API(gpgga)
DECLARE_PARSER_API(gprmc)
DECLARE_PARSER_API(gpgst)
DECLARE_PARSER_API(gpvtg)

nmea_parser_module_s parsers[PARSER_COUNT];

//----------------------------------------------------------
nmea_parser_module_s *nmea_init_parser(const char *filename)
{
/* This function intentionally returns NULL */
return NULL;
}

//---------------------
int nmea_load_parsers()
{
int i = 0;
nmea_parser_module_s *parser;

PARSER_LOAD(gpgll);
PARSER_LOAD(gpgga);
PARSER_LOAD(gprmc);
PARSER_LOAD(gpgst);
PARSER_LOAD(gpvtg);

return PARSER_COUNT;
}

//------------------------
void nmea_unload_parsers()
{
/* This function body is intentionally left empty,
because there is no dynamic memory allocations. */
}

//--------------------------------------------------------
nmea_parser_module_s *nmea_get_parser_by_type(nmea_t type)
{
int i;

for (i = 0; i < PARSER_COUNT; i++) {
if (type == parsers[i].parser.type) {
return &(parsers[i]);
}
}

return (nmea_parser_module_s *) NULL;
}

//---------------------------------------------------------------------
nmea_parser_module_s *nmea_get_parser_by_sentence(const char *sentence)
{
int i;

char type_prefix[NMEA_ID_LENGTH+1] = {'\0'};
memcpy(type_prefix, sentence+1, NMEA_ID_LENGTH);
for (i = 0; i < PARSER_COUNT; i++) {
if (strstr(parsers[i].parser.type_prefixes, type_prefix) == NULL) {
continue;
}
if (0 == strncmp(sentence + NMEA_ID_LENGTH + 1, parsers[i].parser.type_word, NMEA_PREFIX_LENGTH)) {
return &(parsers[i]);
}
}

return (nmea_parser_module_s *) NULL;
}
@@ -0,0 +1,17 @@
#ifndef INC_NMEA_PARSER_TYPES_H
#define INC_NMEA_PARSER_TYPES_H

#include "nmea.h"

typedef struct {
nmea_t type;
char type_word[4];
char type_prefixes[7];
nmea_s *data;
} nmea_parser_s;

#define NMEA_PARSER_PREFIX(parser, type_prefix) strncpy(parser->type_word, type_prefix, NMEA_PREFIX_LENGTH)
#define NMEA_PARSER_IDS(parser, type_ids) strncpy(parser->type_prefixes, type_ids, NMEA_IDS_LENGTH)
#define NMEA_PARSER_TYPE(parser, nmea_type) parser->type = nmea_type

#endif /* INC_NMEA_PARSER_TYPES_H */
@@ -0,0 +1,148 @@
/* ----------------------------- GGA Data Struct ------------------------------ */

//GGA - essential fix data which provide 3D location and accuracy data.
//
// $GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47
//
//Where:
// GGA Global Positioning System Fix Data
// 123519 Fix taken at 12:35:19 UTC
// 4807.038,N Latitude 48 deg 07.038' N
// 01131.000,E Longitude 11 deg 31.000' E
// 1 Fix quality: 0 = invalid
// 1 = GPS fix (SPS)
// 2 = DGPS fix
// 3 = PPS fix
// 4 = Real Time Kinematic
// 5 = Float RTK
// 6 = estimated (dead reckoning) (2.3 feature)
// 7 = Manual input mode
// 8 = Simulation mode
// 08 Number of satellites being tracked
// 0.9 Horizontal dilution of position
// 545.4,M Altitude, Meters, above mean sea level
// 46.9,M Height of geoid (mean sea level) above WGS84
// ellipsoid
// (empty field) time in seconds since last DGPS update
// (empty field) DGPS station ID number
// *47 the checksum data, always begins with *
//
// If the height of geoid is missing then the altitude should be suspect. Some non-standard
// implementations report altitude with respect to the ellipsoid rather than geoid altitude.
// Some units do not report negative altitudes at alUART driver interface. This is the only
// sentence that reports altitude.

#include "../nmea/parser_types.h"
#include "gpgga.h"
#include "parse.h"

//-----------------------------
int init(nmea_parser_s *parser)
{
/* Declare what sentence type to parse */
NMEA_PARSER_TYPE(parser, NMEA_GGA);
NMEA_PARSER_PREFIX(parser, "GGA");
NMEA_PARSER_IDS(parser, "GPGNGL");
return 0;
}

//--------------------------------------
int allocate_data(nmea_parser_s *parser)
{
parser->data = malloc(sizeof (nmea_gpgga_s));
if (NULL == parser->data) {
return -1;
}

return 0;
}

//------------------------------------
int set_default(nmea_parser_s *parser)
{
memset(parser->data, 0, sizeof (nmea_gpgga_s));
return 0;
}

//-------------------------
int free_data(nmea_s *data)
{
free(data);
return 0;
}

// Parse Global Positioning System Fix Data sentence
//----------------------------------------------------------
int parse(nmea_parser_s *parser, char *value, int val_index)
{
nmea_gpgga_s *data = (nmea_gpgga_s *) parser->data;

switch (val_index) {
case NMEA_GPGGA_TIME:
/* Parse time */
if (-1 == nmea_time_parse(value, &data->time)) {
return -1;
}
break;

case NMEA_GPGGA_LATITUDE:
/* Parse latitude */
if (-1 == nmea_position_parse(value, &data->latitude)) {
return -1;
}
break;

case NMEA_GPGGA_LATITUDE_CARDINAL:
/* Parse cardinal direction */
data->latitude.cardinal = nmea_cardinal_direction_parse(value);
if (NMEA_CARDINAL_DIR_UNKNOWN == data->latitude.cardinal) {
return -1;
}
break;

case NMEA_GPGGA_LONGITUDE:
/* Parse longitude */
if (-1 == nmea_position_parse(value, &data->longitude)) {
return -1;
}
break;

case NMEA_GPGGA_LONGITUDE_CARDINAL:
/* Parse cardinal direction */
data->longitude.cardinal = nmea_cardinal_direction_parse(value);
if (NMEA_CARDINAL_DIR_UNKNOWN == data->longitude.cardinal) {
return -1;
}
break;

case NMEA_GPGGA_N_SATELLITES:
/* Parse number of satellies */
data->n_satellites = atoi(value);
break;

case NMEA_GPGGA_ALTITUDE:
/* Parse altitude */
data->altitude = strtof(value, NULL);
break;

case NMEA_GPGGA_ALTITUDE_UNIT:
/* Parse altitude unit */
data->altitude_unit = *value;
break;

case NMEA_GPGGA_QUALITY:
/* GPS Quality Indicator */
data->quality = (int)strtol(value, NULL, 0);
break;

case NMEA_GPGGA_DOP:
/* Horizontal Dilution of precision */
data->dop = strtof(value, NULL);
break;

default:
break;
}

return 0;
}
@@ -0,0 +1,33 @@
#ifndef INC_NMEA_GPGGA_H
#define INC_NMEA_GPGGA_H

#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <nmea.h>

typedef struct {
nmea_s base;
struct tm time;
nmea_position longitude;
nmea_position latitude;
int n_satellites;
float altitude;
char altitude_unit;
int quality;
float dop;
} nmea_gpgga_s;

/* Value indexes */
#define NMEA_GPGGA_TIME 0
#define NMEA_GPGGA_LATITUDE 1
#define NMEA_GPGGA_LATITUDE_CARDINAL 2
#define NMEA_GPGGA_LONGITUDE 3
#define NMEA_GPGGA_LONGITUDE_CARDINAL 4
#define NMEA_GPGGA_QUALITY 5
#define NMEA_GPGGA_N_SATELLITES 6
#define NMEA_GPGGA_DOP 7
#define NMEA_GPGGA_ALTITUDE 8
#define NMEA_GPGGA_ALTITUDE_UNIT 9

#endif /* INC_NMEA_GPGGA_H */
@@ -0,0 +1,94 @@
#include "../nmea/parser_types.h"
#include "gpgll.h"
#include "parse.h"

//-----------------------------
int init(nmea_parser_s *parser)
{
/* Declare what sentence type to parse */
NMEA_PARSER_TYPE(parser, NMEA_GLL);
NMEA_PARSER_PREFIX(parser, "GLL");
NMEA_PARSER_IDS(parser, "GPGNGL");
return 0;
}

//--------------------------------------
int allocate_data(nmea_parser_s *parser)
{
parser->data = malloc(sizeof (nmea_gpgll_s));
if (NULL == parser->data) {
return -1;
}

return 0;
}

//------------------------------------
int set_default(nmea_parser_s *parser)
{
memset(parser->data, 0, sizeof (nmea_gpgll_s));
return 0;
}

//-------------------------
int free_data(nmea_s *data)
{
free(data);
return 0;
}

// Parse Geographic Position – Latitude/Longitude sentence
//----------------------------------------------------------
int parse(nmea_parser_s *parser, char *value, int val_index)
{
nmea_gpgll_s *data = (nmea_gpgll_s *) parser->data;

switch (val_index) {
case NMEA_GPGLL_TIME:
/* Parse time */
if (-1 == nmea_time_parse(value, &data->time)) {
return -1;
}
break;

case NMEA_GPGLL_LATITUDE:
/* Parse latitude */
if (-1 == nmea_position_parse(value, &data->latitude)) {
return -1;
}
break;

case NMEA_GPGLL_LATITUDE_CARDINAL:
/* Parse cardinal direction */
data->latitude.cardinal = nmea_cardinal_direction_parse(value);
if (NMEA_CARDINAL_DIR_UNKNOWN == data->latitude.cardinal) {
return -1;
}
break;

case NMEA_GPGLL_LONGITUDE:
/* Parse longitude */
if (-1 == nmea_position_parse(value, &data->longitude)) {
return -1;
}
break;

case NMEA_GPGLL_LONGITUDE_CARDINAL:
/* Parse cardinal direction */
data->longitude.cardinal = nmea_cardinal_direction_parse(value);
if (NMEA_CARDINAL_DIR_UNKNOWN == data->longitude.cardinal) {
return -1;
}
break;

case NMEA_GPGLL_VALID:
/* Status A - Data Valid, V - Data Invalid */
data->valid = (strcmp(value, "A") == 0);
break;

default:
break;
}

return 0;
}
@@ -0,0 +1,25 @@
#ifndef INC_NMEA_GPGLL_H
#define INC_NMEA_GPGLL_H

#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <nmea.h>

typedef struct {
nmea_s base;
nmea_position longitude;
nmea_position latitude;
struct tm time;
uint8_t valid;
} nmea_gpgll_s;

/* Value indexes */
#define NMEA_GPGLL_LATITUDE 0
#define NMEA_GPGLL_LATITUDE_CARDINAL 1
#define NMEA_GPGLL_LONGITUDE 2
#define NMEA_GPGLL_LONGITUDE_CARDINAL 3
#define NMEA_GPGLL_TIME 4
#define NMEA_GPGLL_VALID 5

#endif /* INC_NMEA_GPGLL_H */
@@ -0,0 +1,94 @@
#include "../nmea/parser_types.h"
#include "gpgst.h"
#include "parse.h"

//-----------------------------
int init(nmea_parser_s *parser)
{
/* Declare what sentence type to parse */
NMEA_PARSER_TYPE(parser, NMEA_GST);
NMEA_PARSER_PREFIX(parser, "GST");
NMEA_PARSER_IDS(parser, "GPGNGL");
return 0;
}

//--------------------------------------
int allocate_data(nmea_parser_s *parser)
{
parser->data = malloc(sizeof (nmea_gpgst_s));
if (NULL == parser->data) {
return -1;
}

return 0;
}

//------------------------------------
int set_default(nmea_parser_s *parser)
{
memset(parser->data, 0, sizeof (nmea_gpgst_s));
return 0;
}

//-------------------------
int free_data(nmea_s *data)
{
free(data);
return 0;
}

// Parse Global Positioning System Fix Data sentence
//----------------------------------------------------------
int parse(nmea_parser_s *parser, char *value, int val_index)
{
nmea_gpgst_s *data = (nmea_gpgst_s *) parser->data;

switch (val_index) {
case NMEA_GPGST_TIME:
/* Parse time */
if (-1 == nmea_time_parse(value, &data->time)) {
return -1;
}
break;

case NMEA_GPGST_RMSSD:
/* Parse RMS value of the standard deviation of the range inputs */
data->rmssd = strtof(value, NULL);
break;

case NMEA_GPGST_SDMAJ:
/* Parse Standard deviation of semi-major axis of error ellipse */
data->sdmaj = strtof(value, NULL);
break;

case NMEA_GPGST_SDMIN:
/* Parse Standard deviation of semi-minor axis of error ellipse */
data->sdmin = strtof(value, NULL);
break;

case NMEA_GPGST_ORI:
/* Parse Orientation of semi-major axis of error ellipse */
data->ori = strtof(value, NULL);
break;

case NMEA_GPGST_LATSD:
/* Parse Standard deviation of latitude error, in meters */
data->latsd = strtof(value, NULL);
break;

case NMEA_GPGST_LONSD:
/* Parse Standard deviation of longitude error, in meters */
data->lonsd = strtof(value, NULL);
break;

case NMEA_GPGST_ALTSD:
/* Parse Standard deviation of altitude error, in meters */
data->altsd = strtof(value, NULL);
break;

default:
break;
}

return 0;
}
@@ -0,0 +1,31 @@
#ifndef INC_NMEA_GPGST_H
#define INC_NMEA_GPGST_H

#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <nmea.h>

typedef struct {
nmea_s base;
struct tm time;
float rmssd;
float sdmaj;
float sdmin;
float ori;
float latsd;
float lonsd;
float altsd;
} nmea_gpgst_s;

/* Value indexes */
#define NMEA_GPGST_TIME 0
#define NMEA_GPGST_RMSSD 1
#define NMEA_GPGST_SDMAJ 2
#define NMEA_GPGST_SDMIN 3
#define NMEA_GPGST_ORI 4
#define NMEA_GPGST_LATSD 5
#define NMEA_GPGST_LONSD 6
#define NMEA_GPGST_ALTSD 7

#endif /* INC_NMEA_GPGGA_H */
@@ -0,0 +1,129 @@
/* ----------------------------- RMC Data Struct ------------------------------ */

//RMC - NMEA has its own version of essential gps pvt (position, velocity, time) data.
//It is called RMC, The Recommended Minimum, which will look similar to:
//
//$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A
//
//Where:
// RMC Recommended Minimum sentence C
// 123519 Fix taken at 12:35:19 UTC
// A Status A=active or V=Void.
// 4807.038,N Latitude 48 deg 07.038' N
// 01131.000,E Longitude 11 deg 31.000' E
// 022.4 Speed over the ground in knots
// 084.4 Track angle in degrees True
// 230394 Date - 23rd of March 1994
// 003.1,W Magnetic Variation
// *6A The checksum data, always begins with *

#include "../nmea/parser_types.h"
#include "gprmc.h"
#include "parse.h"

//-----------------------------
int init(nmea_parser_s *parser)
{
/* Declare what sentence type to parse */
NMEA_PARSER_TYPE(parser, NMEA_RMC);
NMEA_PARSER_PREFIX(parser, "RMC");
NMEA_PARSER_IDS(parser, "GPGNGL");
return 0;
}

//--------------------------------------
int allocate_data(nmea_parser_s *parser)
{
parser->data = malloc(sizeof (nmea_gprmc_s));
if (NULL == parser->data) {
return -1;
}

return 0;
}

//------------------------------------
int set_default(nmea_parser_s *parser)
{
memset(parser->data, 0, sizeof (nmea_gprmc_s));
return 0;
}

//-------------------------
int free_data(nmea_s *data)
{
free(data);
return 0;
}

// Parse Recommended Minimum Navigation Information sentence
//----------------------------------------------------------
int parse(nmea_parser_s *parser, char *value, int val_index)
{
nmea_gprmc_s *data = (nmea_gprmc_s *) parser->data;
switch (val_index) {
case NMEA_GPRMC_TIME:
/* Parse time */
if (-1 == nmea_time_parse(value, &data->time)) {
return -1;
}
break;

case NMEA_GPRMC_LATITUDE:
/* Parse latitude */
if (-1 == nmea_position_parse(value, &data->latitude)) {
return -1;
}
break;

case NMEA_GPRMC_LATITUDE_CARDINAL:
/* Parse cardinal direction */
data->latitude.cardinal = nmea_cardinal_direction_parse(value);
if (NMEA_CARDINAL_DIR_UNKNOWN == data->latitude.cardinal) {
return -1;
}
break;

case NMEA_GPRMC_LONGITUDE:
/* Parse longitude */
if (-1 == nmea_position_parse(value, &data->longitude)) {
return -1;
}
break;

case NMEA_GPRMC_LONGITUDE_CARDINAL:
/* Parse cardinal direction */
data->longitude.cardinal = nmea_cardinal_direction_parse(value);
if (NMEA_CARDINAL_DIR_UNKNOWN == data->longitude.cardinal) {
return -1;
}
break;

case NMEA_GPRMC_DATE:
/* Parse date */
if (-1 == nmea_date_parse(value, &data->time)) {
return -1;
}
break;

case NMEA_GPRMC_VALID:
/* Status, V = Navigation receiver warning */
data->valid = (strcmp(value, "A") == 0);
break;

case NMEA_GPRMC_SPEED:
/* Speed over ground, knots */
data->speed = strtof(value, NULL);
break;

case NMEA_GPRMC_COURSE:
/* Track made good, degrees true */
data->course = strtof(value, NULL);
break;

default:
break;
}

return 0;
}
@@ -0,0 +1,30 @@
#ifndef INC_NMEA_GPRMC_H
#define INC_NMEA_GPRMC_H

#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <nmea.h>

typedef struct {
nmea_s base;
nmea_position longitude;
nmea_position latitude;
struct tm time;
float speed;
float course;
uint8_t valid;
} nmea_gprmc_s;

/* Value indexes */
#define NMEA_GPRMC_TIME 0
#define NMEA_GPRMC_VALID 1
#define NMEA_GPRMC_LATITUDE 2
#define NMEA_GPRMC_LATITUDE_CARDINAL 3
#define NMEA_GPRMC_LONGITUDE 4
#define NMEA_GPRMC_LONGITUDE_CARDINAL 5
#define NMEA_GPRMC_SPEED 6
#define NMEA_GPRMC_COURSE 7
#define NMEA_GPRMC_DATE 8

#endif /* INC_NMEA_GPRMC_H */
@@ -0,0 +1,100 @@
/* ----------------------------- VTG Data Struct ------------------------------ */

/*
Track Made Good and Ground Speed.
eg1. $GPVTG,360.0,T,348.7,M,000.0,N,000.0,K*43
eg2. $GPVTG,054.7,T,034.4,M,005.5,N,010.2,K
054.7,T True track made good
034.4,M Magnetic track made good
005.5,N Ground speed, knots
010.2,K Ground speed, Kilometers per hour
eg3. $GPVTG,t,T,,,s.ss,N,s.ss,K*hh
1 = Track made good
2 = Fixed text 'T' indicates that track made good is relative to true north
3 = not used
4 = not used
5 = Speed over ground in knots
6 = Fixed text 'N' indicates that speed over ground in in knots
7 = Speed over ground in kilometers/hour
8 = Fixed text 'K' indicates that speed over ground is in kilometers/hour
9 = Checksum
The actual track made good and speed relative to the ground.
$--VTG,x.x,T,x.x,M,x.x,N,x.x,K
x.x,T = Track, degrees True
x.x,M = Track, degrees Magnetic
x.x,N = Speed, knots
x.x,K = Speed, Km/hr
*/

#include "../nmea/parser_types.h"
#include "gpvtg.h"
#include "parse.h"

//-----------------------------
int init(nmea_parser_s *parser)
{
/* Declare what sentence type to parse */
NMEA_PARSER_TYPE(parser, NMEA_VTG);
NMEA_PARSER_PREFIX(parser, "VTG");
NMEA_PARSER_IDS(parser, "GPGNGL");
return 0;
}

//--------------------------------------
int allocate_data(nmea_parser_s *parser)
{
parser->data = malloc(sizeof (nmea_gpvtg_s));
if (NULL == parser->data) {
return -1;
}

return 0;
}

//------------------------------------
int set_default(nmea_parser_s *parser)
{
memset(parser->data, 0, sizeof (nmea_gpvtg_s));
return 0;
}

//-------------------------
int free_data(nmea_s *data)
{
free(data);
return 0;
}

// Parse Recommended Minimum Navigation Information sentence
//----------------------------------------------------------
int parse(nmea_parser_s *parser, char *value, int val_index)
{
nmea_gpvtg_s *data = (nmea_gpvtg_s *) parser->data;
switch (val_index) {
case NMEA_GPVTG_COURSE:
/* Track made good */
data->course = strtof(value, NULL);
break;

case NMEA_GPVTG_SPEED_KNOTS:
/* Speed over ground in knots */
data->speed_kn = strtof(value, NULL);
break;

case NMEA_GPVTG_SPEED_KMH:
/* Speed over ground in km/h */
data->speed_kmh = strtof(value, NULL);
break;

default:
break;
}

return 0;
}
@@ -0,0 +1,21 @@
#ifndef INC_NMEA_GPVTG_H
#define INC_NMEA_GPVTG_H

#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <nmea.h>

typedef struct {
nmea_s base;
float course;
float speed_kn;
float speed_kmh;
} nmea_gpvtg_s;

/* Value indexes */
#define NMEA_GPVTG_COURSE 0
#define NMEA_GPVTG_SPEED_KNOTS 4
#define NMEA_GPVTG_SPEED_KMH 6

#endif /* INC_NMEA_GPVTG_H */