| @@ -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 |
| @@ -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; | ||
| } | ||
|
|
| @@ -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 */ |