mirror of
https://github.com/espressif/esp-mqtt.git
synced 2026-09-15 13:40:04 +00:00
@@ -308,6 +308,20 @@ This function is used only for forcing the reconnect request, if you have an act
|
||||
|
||||
To disconnect from the broker use `esp_mqtt_client_disconnect`. It will perform a clean disconnect and if MQTT 5 is used and the client is configured to will send a disconnect message.
|
||||
|
||||
Querying client state
|
||||
---------------------
|
||||
|
||||
Use :cpp:func:`esp_mqtt_client_get_state` to read the current lifecycle of a client handle. Typical values:
|
||||
|
||||
* ``MQTT_CLIENT_STATE_NOT_INITIALIZED`` — the handle is ``NULL``.
|
||||
* ``MQTT_CLIENT_STATE_NOT_STARTED`` — the client is initialized, but its task is not running (never started, or already stopped).
|
||||
* ``MQTT_CLIENT_STATE_CONNECTING`` — the task is running and a connection attempt is in progress.
|
||||
* ``MQTT_CLIENT_STATE_CONNECTED`` — the client is connected to the broker.
|
||||
* ``MQTT_CLIENT_STATE_WAITING_RECONNECT`` — the client is waiting for the reconnect timeout (or a call to :cpp:func:`esp_mqtt_client_reconnect`).
|
||||
* ``MQTT_CLIENT_STATE_DISCONNECTED`` — the task is still running, but the client is not connected (for example while ``stop`` is completing).
|
||||
|
||||
This query is independent of event handlers: applications that reconfigure the broker URI or run a watchdog can ask whether the client is stopped, connecting, connected, or waiting to reconnect without mirroring every event.
|
||||
|
||||
Events
|
||||
------
|
||||
The following events may be posted by the MQTT client:
|
||||
|
||||
@@ -206,6 +206,20 @@ flash cache 被禁用时无法访问外部内存。因此,当任务栈位于
|
||||
任务分发的事件处理函数中运行的用户代码。
|
||||
|
||||
|
||||
查询客户端状态
|
||||
---------------------
|
||||
|
||||
使用 :cpp:func:`esp_mqtt_client_get_state` 可读取客户端句柄的当前生命周期。常见返回值如下:
|
||||
|
||||
* ``MQTT_CLIENT_STATE_NOT_INITIALIZED`` — 句柄为 ``NULL``。
|
||||
* ``MQTT_CLIENT_STATE_NOT_STARTED`` — 客户端已初始化,但其任务未运行(从未启动,或已经停止)。
|
||||
* ``MQTT_CLIENT_STATE_CONNECTING`` — 任务正在运行,并且正在尝试连接。
|
||||
* ``MQTT_CLIENT_STATE_CONNECTED`` — 客户端已连接到服务器。
|
||||
* ``MQTT_CLIENT_STATE_WAITING_RECONNECT`` — 客户端正在等待重连超时(或调用 :cpp:func:`esp_mqtt_client_reconnect`)。
|
||||
* ``MQTT_CLIENT_STATE_DISCONNECTED`` — 任务仍在运行,但客户端未连接到服务器(例如 ``stop`` 尚未完成时)。
|
||||
|
||||
该查询独立于事件处理函数:需要重新配置服务器 URI 或运行看门狗的应用,无需镜像全部事件即可判断客户端是已停止、正在连接、已连接,还是正在等待重连。
|
||||
|
||||
事件
|
||||
------------
|
||||
MQTT 客户端可能会发布以下事件:
|
||||
|
||||
+10
-6
@@ -158,10 +158,11 @@ typedef enum esp_mqtt_protocol_ver_t {
|
||||
*/
|
||||
typedef enum esp_mqtt_client_connection_state_t {
|
||||
MQTT_CLIENT_STATE_NOT_INITIALIZED = 0, /*!< MQTT Client is not initialized */
|
||||
MQTT_CLIENT_STATE_NOT_STARTED, /*!< MQTT Client is initialized, but not started */
|
||||
MQTT_CLIENT_STATE_DISCONNECTED, /*!< MQTT Client is started, but not connected to the broker */
|
||||
MQTT_CLIENT_STATE_NOT_STARTED, /*!< MQTT Client is initialized, but the client task is not running */
|
||||
MQTT_CLIENT_STATE_DISCONNECTED, /*!< MQTT Client task is running, but not connected to the broker */
|
||||
MQTT_CLIENT_STATE_CONNECTED, /*!< MQTT Client is connected to the broker */
|
||||
MQTT_CLIENT_STATE_WAITING_RECONNECT, /*!< MQTT Client is waiting for reconnection request */
|
||||
MQTT_CLIENT_STATE_CONNECTING, /*!< MQTT Client is connecting to the broker */
|
||||
} esp_mqtt_client_connection_state_t;
|
||||
|
||||
/**
|
||||
@@ -750,13 +751,16 @@ esp_transport_handle_t esp_mqtt_client_get_transport(esp_mqtt_client_handle_t cl
|
||||
/**
|
||||
* @brief Get MQTT client's current state
|
||||
*
|
||||
* Get the current state of MQTT client. Returns a value to indicate whether is it initialized, connected, waiting for
|
||||
* reconnection, or disconnected.
|
||||
* Get the current state of MQTT client.
|
||||
*
|
||||
* @param client *MQTT* client handle
|
||||
* @return
|
||||
* - MQTT client state on success
|
||||
* - MQTT_CLIENT_STATE_INVALID on error
|
||||
* - MQTT_CLIENT_STATE_NOT_INITIALIZED if client handle is NULL
|
||||
* - MQTT_CLIENT_STATE_NOT_STARTED if the client task is not running
|
||||
* - MQTT_CLIENT_STATE_CONNECTING if connecting to the broker
|
||||
* - MQTT_CLIENT_STATE_DISCONNECTED if started but not connected to the broker
|
||||
* - MQTT_CLIENT_STATE_CONNECTED if connected to the broker
|
||||
* - MQTT_CLIENT_STATE_WAITING_RECONNECT if waiting for the reconnection timeout
|
||||
*/
|
||||
esp_mqtt_client_connection_state_t esp_mqtt_client_get_state(esp_mqtt_client_handle_t client);
|
||||
|
||||
|
||||
@@ -135,6 +135,7 @@ struct esp_mqtt_client {
|
||||
EventGroupHandle_t status_bits;
|
||||
SemaphoreHandle_t api_lock;
|
||||
TaskHandle_t task_handle;
|
||||
atomic_bool task_running;
|
||||
#if MQTT_EVENT_QUEUE_SIZE > 1
|
||||
atomic_int queued_events;
|
||||
#endif
|
||||
|
||||
+24
-19
@@ -1041,6 +1041,7 @@ esp_mqtt_client_handle_t esp_mqtt_client_init(const esp_mqtt_client_config_t *co
|
||||
goto _mqtt_init_failed;
|
||||
}
|
||||
|
||||
atomic_init(&client->task_running, false);
|
||||
#ifdef MQTT_SUPPORTED_FEATURE_EVENT_LOOP
|
||||
esp_event_loop_args_t no_task_loop = {
|
||||
.queue_size = MQTT_EVENT_QUEUE_SIZE,
|
||||
@@ -2184,7 +2185,11 @@ static void esp_mqtt_task(void *pv)
|
||||
|
||||
esp_transport_close(client->transport);
|
||||
outbox_delete_all_items(client->outbox);
|
||||
MQTT_API_LOCK(client);
|
||||
client->state = MQTT_STATE_DISCONNECTED;
|
||||
client->task_handle = NULL;
|
||||
atomic_store(&client->task_running, false);
|
||||
MQTT_API_UNLOCK(client);
|
||||
xEventGroupSetBits(client->status_bits, STOPPED_BIT);
|
||||
#if MQTT_TASK_STACK_ON_EXTERNAL_MEMORY
|
||||
vTaskDeleteWithCaps(NULL);
|
||||
@@ -2209,6 +2214,7 @@ esp_err_t esp_mqtt_client_start(esp_mqtt_client_handle_t client)
|
||||
}
|
||||
|
||||
esp_err_t err = ESP_OK;
|
||||
client->state = MQTT_STATE_INIT;
|
||||
#if MQTT_CORE_SELECTION_ENABLED
|
||||
ESP_LOGD(TAG, "Core selection enabled on %u", MQTT_TASK_CORE);
|
||||
#else
|
||||
@@ -2232,6 +2238,7 @@ esp_err_t esp_mqtt_client_start(esp_mqtt_client_handle_t client)
|
||||
}
|
||||
|
||||
#endif
|
||||
atomic_store(&client->task_running, err == ESP_OK);
|
||||
MQTT_API_UNLOCK(client);
|
||||
return err;
|
||||
}
|
||||
@@ -2851,31 +2858,29 @@ esp_mqtt_client_connection_state_t esp_mqtt_client_get_state(esp_mqtt_client_han
|
||||
return MQTT_CLIENT_STATE_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
if (client->task_handle == NULL) {
|
||||
return MQTT_CLIENT_STATE_NOT_STARTED;
|
||||
}
|
||||
esp_mqtt_client_connection_state_t ret = MQTT_CLIENT_STATE_DISCONNECTED;
|
||||
|
||||
MQTT_API_LOCK(client);
|
||||
esp_mqtt_client_connection_state_t ret = MQTT_CLIENT_STATE_NOT_INITIALIZED;
|
||||
|
||||
switch (client->state) {
|
||||
case MQTT_STATE_INIT:
|
||||
if (!atomic_load(&client->task_running)) {
|
||||
ret = MQTT_CLIENT_STATE_NOT_STARTED;
|
||||
break;
|
||||
} else {
|
||||
switch (atomic_load(&client->state)) {
|
||||
case MQTT_STATE_INIT:
|
||||
ret = MQTT_CLIENT_STATE_CONNECTING;
|
||||
break;
|
||||
|
||||
case MQTT_STATE_CONNECTED:
|
||||
ret = MQTT_CLIENT_STATE_CONNECTED;
|
||||
break;
|
||||
case MQTT_STATE_CONNECTED:
|
||||
ret = MQTT_CLIENT_STATE_CONNECTED;
|
||||
break;
|
||||
|
||||
case MQTT_STATE_WAIT_RECONNECT:
|
||||
ret = MQTT_CLIENT_STATE_WAITING_RECONNECT;
|
||||
break;
|
||||
case MQTT_STATE_WAIT_RECONNECT:
|
||||
ret = MQTT_CLIENT_STATE_WAITING_RECONNECT;
|
||||
break;
|
||||
|
||||
case MQTT_STATE_DISCONNECTED:
|
||||
ret = MQTT_CLIENT_STATE_DISCONNECTED;
|
||||
break;
|
||||
case MQTT_STATE_DISCONNECTED:
|
||||
ret = MQTT_CLIENT_STATE_DISCONNECTED;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
MQTT_API_UNLOCK(client);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ This app exposes a console API for pytest-embedded HIL tests that target MQTT co
|
||||
- `config <base64_json>`: Apply base64-encoded JSON config to initialized client
|
||||
- `start`: Start MQTT client
|
||||
- `stop`: Stop MQTT client
|
||||
- `get_state`: Print the current MQTT client connection state
|
||||
- `disconnect`: Request disconnect
|
||||
- `reconnect`: Request reconnect
|
||||
- `destroy`: Destroy MQTT client
|
||||
|
||||
@@ -145,6 +145,17 @@ int do_stop(int argc, char **argv)
|
||||
return 0;
|
||||
}
|
||||
|
||||
int do_get_state(int argc, char **argv)
|
||||
{
|
||||
if (!client_available()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto state = esp_mqtt_client_get_state(command_context.mqtt_client);
|
||||
ESP_LOGI(TAG, "CLIENT_STATE=%d", static_cast<int>(state));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int do_disconnect(int argc, char **argv)
|
||||
{
|
||||
(void)argc;
|
||||
@@ -328,6 +339,15 @@ void register_commands()
|
||||
.func_w_context = nullptr,
|
||||
.context = nullptr,
|
||||
};
|
||||
const esp_console_cmd_t get_state = {
|
||||
.command = "get_state",
|
||||
.help = "Print current MQTT client connection state as CLIENT_STATE=<N>",
|
||||
.hint = nullptr,
|
||||
.func = &do_get_state,
|
||||
.argtable = nullptr,
|
||||
.func_w_context = nullptr,
|
||||
.context = nullptr,
|
||||
};
|
||||
const esp_console_cmd_t disconnect = {
|
||||
.command = "disconnect",
|
||||
.help = "Disconnect mqtt client",
|
||||
@@ -398,6 +418,7 @@ void register_commands()
|
||||
ESP_ERROR_CHECK(esp_console_cmd_register(&config_cmd));
|
||||
ESP_ERROR_CHECK(esp_console_cmd_register(&start));
|
||||
ESP_ERROR_CHECK(esp_console_cmd_register(&stop));
|
||||
ESP_ERROR_CHECK(esp_console_cmd_register(&get_state));
|
||||
ESP_ERROR_CHECK(esp_console_cmd_register(&disconnect));
|
||||
ESP_ERROR_CHECK(esp_console_cmd_register(&reconnect));
|
||||
ESP_ERROR_CHECK(esp_console_cmd_register(&destroy));
|
||||
|
||||
@@ -413,6 +413,19 @@ def started_client(dut: Dut) -> Generator[Dut, None, None]:
|
||||
stop_client(dut)
|
||||
|
||||
|
||||
# esp_mqtt_client_connection_state_t integer values
|
||||
MQTT_CLIENT_STATE_NOT_STARTED = 1
|
||||
MQTT_CLIENT_STATE_CONNECTED = 3
|
||||
MQTT_CLIENT_STATE_WAITING_RECONNECT = 4
|
||||
MQTT_CLIENT_STATE_CONNECTING = 5
|
||||
|
||||
|
||||
def assert_client_state(dut: Dut, expected: int) -> None:
|
||||
"""Issue get_state and assert the DUT reports the expected numeric state."""
|
||||
dut.write("get_state")
|
||||
dut.expect(re.compile(f"CLIENT_STATE={expected}(?![0-9])".encode()), timeout=DUT_CMD_TIMEOUT)
|
||||
|
||||
|
||||
def case_timeout(
|
||||
*,
|
||||
connect_operations: int = 0,
|
||||
@@ -500,6 +513,46 @@ def expect_n(
|
||||
return seen
|
||||
|
||||
|
||||
@pytest.mark.eth_ip101
|
||||
@pytest.mark.timeout(
|
||||
case_timeout(
|
||||
connect_operations=1,
|
||||
event_wait_operations=1,
|
||||
)
|
||||
)
|
||||
@idf_parametrize("target", ["esp32"], indirect=["target"])
|
||||
def test_client_state_transitions(dut: Dut) -> None:
|
||||
"""
|
||||
Verify esp_mqtt_client_get_state across the observable lifecycle:
|
||||
NOT_STARTED → CONNECTING → CONNECTED → WAITING_RECONNECT → NOT_STARTED.
|
||||
|
||||
Holding CONNACK makes the otherwise short CONNECTING state deterministic.
|
||||
"""
|
||||
with (
|
||||
broker_started(hold_packet_types=(MqttPacketType.CONNACK,)) as broker,
|
||||
initialized_mqtt_client(dut, broker.uri) as client,
|
||||
):
|
||||
assert_client_state(client, MQTT_CLIENT_STATE_NOT_STARTED)
|
||||
|
||||
client.write("start")
|
||||
try:
|
||||
broker.wait_for_held_packets(MqttPacketType.CONNACK, 1, timeout=DUT_CONNECT_TIMEOUT)
|
||||
assert_client_state(client, MQTT_CLIENT_STATE_CONNECTING)
|
||||
finally:
|
||||
broker.release_held_packets(MqttPacketType.CONNACK)
|
||||
|
||||
client.expect(re.compile(rb"MQTT_EVENT_CONNECTED"), timeout=DUT_CONNECT_TIMEOUT)
|
||||
assert_client_state(client, MQTT_CLIENT_STATE_CONNECTED)
|
||||
|
||||
client.write("disconnect")
|
||||
client.expect(re.compile(rb"MQTT_EVENT_DISCONNECTED"), timeout=DUT_EVENT_TIMEOUT)
|
||||
assert_client_state(client, MQTT_CLIENT_STATE_WAITING_RECONNECT)
|
||||
|
||||
client.write("stop")
|
||||
client.expect(re.compile(rb"Mqtt client stopped"), timeout=DUT_CMD_TIMEOUT)
|
||||
assert_client_state(client, MQTT_CLIENT_STATE_NOT_STARTED)
|
||||
|
||||
|
||||
@pytest.mark.eth_ip101
|
||||
@pytest.mark.timeout(
|
||||
case_timeout(
|
||||
|
||||
@@ -86,6 +86,22 @@ using unique_mqtt_client =
|
||||
esp_mqtt_client_destroy(client);
|
||||
}) >;
|
||||
|
||||
static BaseType_t create_fake_task(TaskFunction_t, const char *const, const uint32_t,
|
||||
void *const, UBaseType_t, TaskHandle_t *const created_task,
|
||||
const BaseType_t, int)
|
||||
{
|
||||
static int fake_task_storage;
|
||||
*created_task = reinterpret_cast<TaskHandle_t>(&fake_task_storage);
|
||||
return pdTRUE;
|
||||
}
|
||||
|
||||
static BaseType_t fail_to_create_fake_task(TaskFunction_t, const char *const, const uint32_t,
|
||||
void *const, UBaseType_t, TaskHandle_t *const,
|
||||
const BaseType_t, int)
|
||||
{
|
||||
return pdFALSE;
|
||||
}
|
||||
|
||||
SCENARIO("MQTT Client Operation")
|
||||
{
|
||||
// Set expectations for the mocked calls.
|
||||
@@ -319,6 +335,37 @@ SCENARIO("MQTT Client Operation")
|
||||
// Only need to start the client, destroy is called automatically at the
|
||||
// end of scope
|
||||
}
|
||||
SECTION("get_state reports client lifecycle correctly") {
|
||||
SECTION("returns NOT_INITIALIZED for null handle") {
|
||||
REQUIRE(esp_mqtt_client_get_state(nullptr) ==
|
||||
MQTT_CLIENT_STATE_NOT_INITIALIZED);
|
||||
}
|
||||
SECTION("returns NOT_STARTED before esp_mqtt_client_start is called") {
|
||||
REQUIRE(esp_mqtt_client_get_state(client.get()) ==
|
||||
MQTT_CLIENT_STATE_NOT_STARTED);
|
||||
}
|
||||
SECTION("returns CONNECTING after start, not NOT_STARTED") {
|
||||
/* After start the task handle is non-NULL but the task never
|
||||
* runs in the mock environment, so client->state stays at
|
||||
* MQTT_STATE_INIT (=0 from calloc). This is precisely the
|
||||
* condition that was buggy: get_state must return
|
||||
* MQTT_CLIENT_STATE_CONNECTING, not MQTT_CLIENT_STATE_NOT_STARTED.
|
||||
*
|
||||
* Use Stub (not ExpectAnyArgs) so the fake task handle is set
|
||||
* regardless of any stale expectations queued by sibling sections. */
|
||||
xTaskCreatePinnedToCore_Stub(create_fake_task);
|
||||
REQUIRE(esp_mqtt_client_start(client.get()) == ESP_OK);
|
||||
auto state = esp_mqtt_client_get_state(client.get());
|
||||
REQUIRE(state == MQTT_CLIENT_STATE_CONNECTING);
|
||||
REQUIRE(state != MQTT_CLIENT_STATE_NOT_STARTED);
|
||||
}
|
||||
SECTION("returns NOT_STARTED when task creation fails") {
|
||||
xTaskCreatePinnedToCore_Stub(fail_to_create_fake_task);
|
||||
REQUIRE(esp_mqtt_client_start(client.get()) == ESP_FAIL);
|
||||
REQUIRE(esp_mqtt_client_get_state(client.get()) ==
|
||||
MQTT_CLIENT_STATE_NOT_STARTED);
|
||||
}
|
||||
}
|
||||
}
|
||||
SECTION("Client with all allocating configuration set") {
|
||||
xQueueCreateMutex_IgnoreAndReturn(
|
||||
|
||||
Reference in New Issue
Block a user