Merge branch 'feature/esp_mqtt_as_managed_component' into 'master'

Moves mqtt build files to esp-mqtt and adds a component file

See merge request espressif/esp-mqtt!149
This commit is contained in:
David Čermák
2022-12-17 01:46:11 +08:00
12 changed files with 457 additions and 0 deletions

18
CMakeLists.txt Normal file
View File

@ -0,0 +1,18 @@
set(srcs mqtt_client.c lib/mqtt_msg.c lib/mqtt_outbox.c lib/platform_esp32_idf.c)
if(CONFIG_MQTT_PROTOCOL_5)
list(APPEND srcs lib/mqtt5_msg.c mqtt5_client.c)
endif()
list(TRANSFORM srcs PREPEND ${CMAKE_CURRENT_LIST_DIR}/)
idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS ${CMAKE_CURRENT_LIST_DIR}/include
PRIV_INCLUDE_DIRS ${CMAKE_CURRENT_LIST_DIR}/lib/include
REQUIRES esp_event tcp_transport
PRIV_REQUIRES esp_timer http_parser esp_hw_support heap
KCONFIG ${CMAKE_CURRENT_LIST_DIR}/Kconfig
)
target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-format")

166
Kconfig Normal file
View File

@ -0,0 +1,166 @@
menu "ESP-MQTT Configurations"
config MQTT_PROTOCOL_311
bool "Enable MQTT protocol 3.1.1"
default y
help
If not, this library will use MQTT protocol 3.1
config MQTT_PROTOCOL_5
bool "Enable MQTT protocol 5.0"
default n
help
If not, this library will not support MQTT 5.0
config MQTT_TRANSPORT_SSL
bool "Enable MQTT over SSL"
default y
help
Enable MQTT transport over SSL with mbedtls
config MQTT_TRANSPORT_WEBSOCKET
bool "Enable MQTT over Websocket"
default y
depends on WS_TRANSPORT
help
Enable MQTT transport over Websocket.
config MQTT_TRANSPORT_WEBSOCKET_SECURE
bool "Enable MQTT over Websocket Secure"
default y
depends on MQTT_TRANSPORT_WEBSOCKET
depends on MQTT_TRANSPORT_SSL
help
Enable MQTT transport over Websocket Secure.
config MQTT_MSG_ID_INCREMENTAL
bool "Use Incremental Message Id"
default n
help
Set this to true for the message id (2.3.1 Packet Identifier) to be generated
as an incremental number rather then a random value (used by default)
config MQTT_SKIP_PUBLISH_IF_DISCONNECTED
bool "Skip publish if disconnected"
default n
help
Set this to true to avoid publishing (enqueueing messages) if the client is disconnected.
The MQTT client tries to publish all messages by default, even in the disconnected state
(where the qos1 and qos2 packets are stored in the internal outbox to be published later)
The MQTT_SKIP_PUBLISH_IF_DISCONNECTED option allows applications to override this behaviour
and not enqueue publish packets in the disconnected state.
config MQTT_REPORT_DELETED_MESSAGES
bool "Report deleted messages"
default n
help
Set this to true to post events for all messages which were deleted from the outbox
before being correctly sent and confirmed.
config MQTT_USE_CUSTOM_CONFIG
bool "MQTT Using custom configurations"
default n
help
Custom MQTT configurations.
config MQTT_TCP_DEFAULT_PORT
int "Default MQTT over TCP port"
default 1883
depends on MQTT_USE_CUSTOM_CONFIG
help
Default MQTT over TCP port
config MQTT_SSL_DEFAULT_PORT
int "Default MQTT over SSL port"
default 8883
depends on MQTT_USE_CUSTOM_CONFIG
depends on MQTT_TRANSPORT_SSL
help
Default MQTT over SSL port
config MQTT_WS_DEFAULT_PORT
int "Default MQTT over Websocket port"
default 80
depends on MQTT_USE_CUSTOM_CONFIG
depends on MQTT_TRANSPORT_WEBSOCKET
help
Default MQTT over Websocket port
config MQTT_WSS_DEFAULT_PORT
int "Default MQTT over Websocket Secure port"
default 443
depends on MQTT_USE_CUSTOM_CONFIG
depends on MQTT_TRANSPORT_WEBSOCKET
depends on MQTT_TRANSPORT_WEBSOCKET_SECURE
help
Default MQTT over Websocket Secure port
config MQTT_BUFFER_SIZE
int "Default MQTT Buffer Size"
default 1024
depends on MQTT_USE_CUSTOM_CONFIG
help
This buffer size using for both transmit and receive
config MQTT_TASK_STACK_SIZE
int "MQTT task stack size"
default 6144
depends on MQTT_USE_CUSTOM_CONFIG
help
MQTT task stack size
config MQTT_DISABLE_API_LOCKS
bool "Disable API locks"
default n
depends on MQTT_USE_CUSTOM_CONFIG
help
Default config employs API locks to protect internal structures. It is possible to disable
these locks if the user code doesn't access MQTT API from multiple concurrent tasks
config MQTT_TASK_PRIORITY
int "MQTT task priority"
default 5
depends on MQTT_USE_CUSTOM_CONFIG
help
MQTT task priority. Higher number denotes higher priority.
config MQTT_EVENT_QUEUE_SIZE
int "Number of queued events."
default 1
depends on MQTT_USE_CUSTOM_CONFIG
help
A value higher than 1 enables multiple queued events.
config MQTT_TASK_CORE_SELECTION_ENABLED
bool "Enable MQTT task core selection"
help
This will enable core selection
choice MQTT_TASK_CORE_SELECTION
depends on MQTT_TASK_CORE_SELECTION_ENABLED
prompt "Core to use ?"
config MQTT_USE_CORE_0
bool "Core 0"
config MQTT_USE_CORE_1
bool "Core 1"
endchoice
config MQTT_CUSTOM_OUTBOX
bool "Enable custom outbox implementation"
default n
help
Set to true if a specific implementation of message outbox is needed (e.g. persistent outbox in NVM or
similar).
Note: Implementation of the custom outbox must be added to the mqtt component. These CMake commands
could be used to append the custom implementation to lib-mqtt sources:
idf_component_get_property(mqtt mqtt COMPONENT_LIB)
set_property(TARGET ${mqtt} PROPERTY SOURCES ${PROJECT_DIR}/custom_outbox.c APPEND)
config MQTT_OUTBOX_EXPIRED_TIMEOUT_MS
int "Outbox message expired timeout[ms]"
default 30000
depends on MQTT_USE_CUSTOM_CONFIG
help
Messages which stays in the outbox longer than this value before being published will be discarded.
endmenu

17
host_test/CMakeLists.txt Normal file
View File

@ -0,0 +1,17 @@
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
set(COMPONENTS main)
list(APPEND EXTRA_COMPONENT_DIRS
"mocks/heap/"
"$ENV{IDF_PATH}/tools/mocks/esp_hw_support/"
"$ENV{IDF_PATH}/tools/mocks/freertos/"
"$ENV{IDF_PATH}/tools/mocks/esp_timer/"
"$ENV{IDF_PATH}/tools/mocks/esp_event/"
"$ENV{IDF_PATH}/tools/mocks/lwip/"
"$ENV{IDF_PATH}/tools/mocks/esp-tls/"
"$ENV{IDF_PATH}/tools/mocks/http_parser/"
"$ENV{IDF_PATH}/tools/mocks/tcp_transport/"
)
project(host_mqtt_client_test)

30
host_test/README.md Normal file
View File

@ -0,0 +1,30 @@
| Supported Targets | Linux |
| ----------------- | ----- |
# Description
This directory contains test code for the mqtt client that runs on host.
Tests are written using [Catch2](https://github.com/catchorg/Catch2) test framework
# Build
Tests build regularly like an idf project.
```
idf.py build
```
# Run
The build produces an executable in the build folder.
Just run:
```
./build/host_mqtt_client_test.elf
```
The test executable have some options provided by the test framework.

View File

@ -0,0 +1,3 @@
idf_component_register(SRCS "test_mqtt_client.cpp"
INCLUDE_DIRS "$ENV{IDF_PATH}/tools/catch"
REQUIRES cmock mqtt esp_timer esp_hw_support http_parser log)

View File

@ -0,0 +1,126 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#define CATCH_CONFIG_MAIN // This tells the catch header to generate a main
#include "catch.hpp"
extern "C" {
#include "Mockesp_event.h"
#include "Mockesp_mac.h"
#include "Mockesp_transport.h"
#include "Mockesp_transport_ssl.h"
#include "Mockesp_transport_tcp.h"
#include "Mockesp_transport_ws.h"
#include "Mockevent_groups.h"
#include "Mockhttp_parser.h"
#include "Mockqueue.h"
#include "Mocktask.h"
#include "Mockesp_timer.h"
/*
* The following functions are not directly called but the generation of them
* from cmock is broken, so we need to define them here.
*/
esp_err_t esp_tls_get_and_clear_last_error(esp_tls_error_handle_t h, int *esp_tls_code, int *esp_tls_flags)
{
return ESP_OK;
}
}
#include "mqtt_client.h"
struct ClientInitializedFixture {
esp_mqtt_client_handle_t client;
ClientInitializedFixture()
{
[[maybe_unused]] auto protect = TEST_PROTECT();
int mtx;
int transport_list;
int transport;
int event_group;
uint8_t mac[] = {0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55};
esp_timer_get_time_IgnoreAndReturn(0);
xQueueTakeMutexRecursive_IgnoreAndReturn(true);
xQueueGiveMutexRecursive_IgnoreAndReturn(true);
xQueueCreateMutex_ExpectAnyArgsAndReturn(
reinterpret_cast<QueueHandle_t>(&mtx));
xEventGroupCreate_IgnoreAndReturn(reinterpret_cast<EventGroupHandle_t>(&event_group));
esp_transport_list_init_IgnoreAndReturn(reinterpret_cast<esp_transport_list_handle_t>(&transport_list));
esp_transport_tcp_init_IgnoreAndReturn(reinterpret_cast<esp_transport_handle_t>(&transport));
esp_transport_ssl_init_IgnoreAndReturn(reinterpret_cast<esp_transport_handle_t>(&transport));
esp_transport_ws_init_IgnoreAndReturn(reinterpret_cast<esp_transport_handle_t>(&transport));
esp_transport_ws_set_subprotocol_IgnoreAndReturn(ESP_OK);
esp_transport_list_add_IgnoreAndReturn(ESP_OK);
esp_transport_set_default_port_IgnoreAndReturn(ESP_OK);
http_parser_parse_url_IgnoreAndReturn(0);
http_parser_url_init_ExpectAnyArgs();
esp_event_loop_create_IgnoreAndReturn(ESP_OK);
esp_read_mac_IgnoreAndReturn(ESP_OK);
esp_read_mac_ReturnThruPtr_mac(mac);
esp_transport_list_destroy_IgnoreAndReturn(ESP_OK);
vEventGroupDelete_Ignore();
vQueueDelete_Ignore();
esp_mqtt_client_config_t config{};
client = esp_mqtt_client_init(&config);
}
~ClientInitializedFixture()
{
esp_mqtt_client_destroy(client);
}
};
TEST_CASE_METHOD(ClientInitializedFixture, "Client set uri")
{
struct http_parser_url ret_uri = {
.field_set = 1,
.port = 0,
.field_data = { { 0, 1} }
};
SECTION("User set a correct URI") {
http_parser_parse_url_StopIgnore();
http_parser_parse_url_ExpectAnyArgsAndReturn(0);
http_parser_parse_url_ReturnThruPtr_u(&ret_uri);
auto res = esp_mqtt_client_set_uri(client, " ");
REQUIRE(res == ESP_OK);
}
SECTION("Incorrect URI from user") {
http_parser_parse_url_StopIgnore();
http_parser_parse_url_ExpectAnyArgsAndReturn(1);
http_parser_parse_url_ReturnThruPtr_u(&ret_uri);
auto res = esp_mqtt_client_set_uri(client, " ");
REQUIRE(res == ESP_FAIL);
}
}
TEST_CASE_METHOD(ClientInitializedFixture, "Client Start")
{
SECTION("Successful start") {
esp_mqtt_client_config_t config{};
config.broker.address.uri = "mqtt://1.1.1.1";
struct http_parser_url ret_uri = {
.field_set = 1 | (1<<1),
.port = 0,
.field_data = { { 0, 4 } /*mqtt*/, { 7, 1 } } // at least *scheme* and *host*
};
http_parser_parse_url_StopIgnore();
http_parser_parse_url_ExpectAnyArgsAndReturn(0);
http_parser_parse_url_ReturnThruPtr_u(&ret_uri);
xTaskCreatePinnedToCore_ExpectAnyArgsAndReturn(pdTRUE);
auto res = esp_mqtt_set_config(client, &config);
REQUIRE(res == ESP_OK);
res = esp_mqtt_client_start(client);
REQUIRE(res == ESP_OK);
}
SECTION("Failed on initialization") {
xTaskCreatePinnedToCore_ExpectAnyArgsAndReturn(pdFALSE);
auto res = esp_mqtt_client_start(nullptr);
REQUIRE(res == ESP_ERR_INVALID_ARG);
}
SECTION("Client already started") {}
SECTION("Failed to start task") {
xTaskCreatePinnedToCore_ExpectAnyArgsAndReturn(pdFALSE);
auto res = esp_mqtt_client_start(client);
REQUIRE(res == ESP_FAIL);
}
}

View File

@ -0,0 +1,4 @@
idf_component_get_property(original_heap_dir heap COMPONENT_OVERRIDEN_DIR)
idf_component_register(SRCS heap_mock.c
INCLUDE_DIRS "${original_heap_dir}/include")

View File

@ -0,0 +1,11 @@
#include "esp_heap_caps.h"
#include <stdint.h>
#include <stdlib.h>
void *heap_caps_calloc(size_t n, size_t size, uint32_t caps) {
(void)caps;
return calloc(n, size);
}

View File

@ -0,0 +1,66 @@
#pragma once
/* Implementation from BSD headers*/
#define QMD_SAVELINK(name, link) void **name = (void *)&(link)
#define TRASHIT(x) do {(x) = (void *)-1;} while (0)
#define STAILQ_NEXT(elm, field) ((elm)->field.stqe_next)
#define STAILQ_FIRST(head) ((head)->stqh_first)
#define STAILQ_HEAD(name, type) \
struct name { \
struct type *stqh_first;/* first element */ \
struct type **stqh_last;/* addr of last next element */ \
}
#define STAILQ_ENTRY(type) \
struct { \
struct type *stqe_next; /* next element */ \
}
#define STAILQ_INSERT_TAIL(head, elm, field) do { \
STAILQ_NEXT((elm), field) = NULL; \
*(head)->stqh_last = (elm); \
(head)->stqh_last = &STAILQ_NEXT((elm), field); \
} while (0)
#define STAILQ_INIT(head) do { \
STAILQ_FIRST((head)) = NULL; \
(head)->stqh_last = &STAILQ_FIRST((head)); \
} while (0)
#define STAILQ_FOREACH(var, head, field) \
for((var) = STAILQ_FIRST((head)); \
(var); \
(var) = STAILQ_NEXT((var), field))
#define STAILQ_FOREACH_SAFE(var, head, field, tvar) \
for ((var) = STAILQ_FIRST((head)); \
(var) && ((tvar) = STAILQ_NEXT((var), field), 1); \
(var) = (tvar))
#define STAILQ_REMOVE_AFTER(head, elm, field) do { \
if ((STAILQ_NEXT(elm, field) = \
STAILQ_NEXT(STAILQ_NEXT(elm, field), field)) == NULL) \
(head)->stqh_last = &STAILQ_NEXT((elm), field); \
} while (0)
#define STAILQ_REMOVE_HEAD(head, field) do { \
if ((STAILQ_FIRST((head)) = \
STAILQ_NEXT(STAILQ_FIRST((head)), field)) == NULL) \
(head)->stqh_last = &STAILQ_FIRST((head)); \
} while (0)
#define STAILQ_REMOVE(head, elm, type, field) do { \
QMD_SAVELINK(oldnext, (elm)->field.stqe_next); \
if (STAILQ_FIRST((head)) == (elm)) { \
STAILQ_REMOVE_HEAD((head), field); \
} \
else { \
struct type *curelm = STAILQ_FIRST((head)); \
while (STAILQ_NEXT(curelm, field) != (elm)) \
curelm = STAILQ_NEXT(curelm, field); \
STAILQ_REMOVE_AFTER(head, curelm, field); \
} \
TRASHIT(*oldnext); \
} while (0)

View File

@ -0,0 +1,6 @@
CONFIG_IDF_TARGET="linux"
CONFIG_COMPILER_CXX_EXCEPTIONS=y
CONFIG_COMPILER_CXX_RTTI=y
CONFIG_COMPILER_CXX_EXCEPTIONS_EMG_POOL_SIZE=0
CONFIG_COMPILER_STACK_CHECK_NONE=y
CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n

5
idf_component.yml Normal file
View File

@ -0,0 +1,5 @@
version: "1.0.0"
description: esp-mqtt
dependencies:
idf:
version: ">=5.0"

View File

@ -235,6 +235,10 @@ esp_mqtt_set_transport_failed:
/* Checks if the user supplied config values are internally consistent */
static esp_err_t esp_mqtt_check_cfg_conflict(const mqtt_config_storage_t *cfg, const esp_mqtt_client_config_t *user_cfg)
{
if(cfg == NULL || user_cfg == NULL) {
ESP_LOGE(TAG, "Invalid configuration");
return ESP_ERR_INVALID_ARG;
}
esp_err_t ret = ESP_OK;
bool ssl_cfg_enabled = cfg->use_global_ca_store || cfg->cacert_buf || cfg->clientcert_buf || cfg->psk_hint_key || cfg->alpn_protos;
@ -517,6 +521,7 @@ esp_err_t esp_mqtt_set_config(esp_mqtt_client_handle_t client, const esp_mqtt_cl
if (config->broker.address.transport) {
free(client->config->scheme);
client->config->scheme = NULL;
if (config->broker.address.transport == MQTT_TRANSPORT_OVER_TCP) {
client->config->scheme = create_string(MQTT_OVER_TCP_SCHEME, strlen(MQTT_OVER_TCP_SCHEME));
ESP_MEM_CHECK(TAG, client->config->scheme, goto _mqtt_set_config_failed);