Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/configs/sdkconfig.defaults
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ CONFIG_BT_SPP_ENABLED=y
# Support for TLS with a pre-shared key.
#CONFIG_ESP_TLS_PSK_VERIFICATION=y

# Compile-check the MQTT 5.0 code paths in CI.
CONFIG_MQTT_PROTOCOL_5=y

CONFIG_LWIP_PPP_SUPPORT=y
#CONFIG_LWIP_SLIP_SUPPORT=y

Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]

### Breaking
- MQTT: `MqttProtocolVersion::V5` variant and [`Mqtt5ConnectionPropertyConfig`] struct exposing MQTT 5.0 CONNECT properties (session/will/message expiry intervals, receive/packet/topic-alias maxima, request-response/problem info, payload format indicator). Gated on `CONFIG_MQTT_PROTOCOL_5=y`. To negotiate MQTT 5 and set properties:
```rust
MqttClientConfiguration {
protocol_version: Some(MqttProtocolVersion::V5),
mqtt5_connection_property: Some(Mqtt5ConnectionPropertyConfig {
session_expiry_interval: Some(60),
..Default::default()
}),
..Default::default()
}
```
- HTTP: Add `keep_alive: Option<KeepAlive>` and `so_linger: Option<Duration>` to server `Configuration`
- New events need to be handled in the WiFi event loop:
- `WifiEvent::StaNeighborRep` / `StaNeighborRepRef` (v5.3.0+)
Expand All @@ -26,6 +37,7 @@ remote_component = { name = "espressif/lan87xx", version = "1.*" }
### Fixed
- WiFi: receiving any of the six new events listed above on ESP-IDF v5.3+ / v5.5+ no longer causes a panic (fixes #618)
- WebSocket: `EspWebSocketClient::drop()` no longer panics when `esp_websocket_client_close` returns `ESP_FAIL` (e.g. after a network disconnection); errors are now logged instead of unwrapped
- MQTT: MQTT 5.0 CONNECT properties can now be set on `EspMqttClient` without deadlocking on `MQTT_API_LOCK`; they are applied inside the library between `esp_mqtt_client_init` and `esp_mqtt_client_start` via the new [`MqttClientConfiguration::mqtt5_connection_property`] field.
- BT: Fixed panic when an A2DP sink disconnects from the ESP while streaming audio.
- Thread: `scan`, `energy_scan` and the IPv6 receive callbacks no longer pass the wrong context pointer to OpenThread (the closure box instead of the `ThreadDriverInner`), fixing a type-confusion crash when the callbacks fire.
- Ethernet: `mod eth` is enabled again on ESP-IDF 6.0+ when SPI Ethernet PHYs are provided as managed components (`espressif/w5500`, `espressif/dm9051`, `espressif/ksz8851snl`), not only via the removed in-tree `CONFIG_ETH_SPI_ETHERNET_*` Kconfig options
Expand Down
78 changes: 78 additions & 0 deletions src/mqtt/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ extern crate alloc;
use alloc::boxed::Box;
use alloc::sync::Arc;

use ::log::error;
use embedded_svc::mqtt::client::{asynch, Client, Connection, Enqueue, ErrorType, Publish};

use crate::private::unblocker::Unblocker;
Expand All @@ -26,20 +27,50 @@ pub use embedded_svc::mqtt::client::{
pub use super::*;

#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum MqttProtocolVersion {
V3_1,
V3_1_1,
#[cfg(esp_idf_mqtt_protocol_5)]
V5,
}

impl From<MqttProtocolVersion> for esp_mqtt_protocol_ver_t {
fn from(pv: MqttProtocolVersion) -> Self {
match pv {
MqttProtocolVersion::V3_1 => esp_mqtt_protocol_ver_t_MQTT_PROTOCOL_V_3_1,
MqttProtocolVersion::V3_1_1 => esp_mqtt_protocol_ver_t_MQTT_PROTOCOL_V_3_1_1,
#[cfg(esp_idf_mqtt_protocol_5)]
MqttProtocolVersion::V5 => esp_mqtt_protocol_ver_t_MQTT_PROTOCOL_V_5,
}
}
}

/// MQTT 5.0 CONNECT properties. Requires `protocol_version: Some(MqttProtocolVersion::V5)`.
/// `None` on a field leaves the property unset and the broker applies the MQTT 5 default.
#[cfg(esp_idf_mqtt_protocol_5)]
#[derive(Debug, Clone, Default)]
pub struct Mqtt5ConnectionPropertyConfig {
/// Session Expiry Interval, seconds (MQTT5 Β§3.1.2.11.2).
pub session_expiry_interval: Option<u32>,
/// Will Delay Interval, seconds (MQTT5 Β§3.1.3.2.2).
pub will_delay_interval: Option<u32>,
/// Receive Maximum, max concurrent inbound QoS>0 PUBLISHes (MQTT5 Β§3.1.2.11.3).
pub receive_maximum: Option<u16>,
/// Maximum Packet Size the client accepts, bytes (MQTT5 Β§3.1.2.11.4).
pub maximum_packet_size: Option<u32>,
/// Topic Alias Maximum the broker may use (MQTT5 Β§3.1.2.11.5).
pub topic_alias_maximum: Option<u16>,
/// Request Response Information (MQTT5 Β§3.1.2.11.6). C field: `request_resp_info`.
pub request_response_info: Option<bool>,
/// Request Problem Information (MQTT5 Β§3.1.2.11.7). Protocol default is `true`.
pub request_problem_info: Option<bool>,
/// Will Message Expiry Interval, seconds (MQTT5 Β§3.3.2.3.3).
pub message_expiry_interval: Option<u32>,
/// Will Payload Format Indicator: UTF-8 (`true`) or bytes (`false`) (MQTT5 Β§3.3.2.3.2).
pub payload_format_indicator: Option<bool>,
}

#[derive(Debug)]
pub struct LwtConfiguration<'a> {
pub topic: &'a str,
Expand All @@ -52,6 +83,9 @@ pub struct LwtConfiguration<'a> {
pub struct MqttClientConfiguration<'a> {
pub protocol_version: Option<MqttProtocolVersion>,

#[cfg(esp_idf_mqtt_protocol_5)]
pub mqtt5_connection_property: Option<Mqtt5ConnectionPropertyConfig>,

pub client_id: Option<&'a str>,

pub connection_refresh_interval: time::Duration,
Expand Down Expand Up @@ -94,6 +128,9 @@ impl Default for MqttClientConfiguration<'_> {
Self {
protocol_version: None,

#[cfg(esp_idf_mqtt_protocol_5)]
mqtt5_connection_property: None,

client_id: None,

connection_refresh_interval: time::Duration::from_secs(0),
Expand Down Expand Up @@ -509,6 +546,47 @@ impl<'a> EspMqttClient<'a> {
)
})?;

// MQTT 5.0 CONNECT properties must be set after init and before start,
// otherwise the C call deadlocks on MQTT_API_LOCK held by mqtt_task.
#[cfg(esp_idf_mqtt_protocol_5)]
if let Some(props) = conf.mqtt5_connection_property.as_ref() {
if conf.protocol_version != Some(MqttProtocolVersion::V5) {
error!(
"mqtt5_connection_property requires protocol_version = Some(MqttProtocolVersion::V5)"
);
return Err(EspError::from_infallible::<ESP_ERR_INVALID_ARG>());
}
let mut c_props = esp_mqtt5_connection_property_config_t::default();
if let Some(v) = props.session_expiry_interval {
c_props.session_expiry_interval = v;
}
if let Some(v) = props.will_delay_interval {
c_props.will_delay_interval = v;
}
if let Some(v) = props.receive_maximum {
c_props.receive_maximum = v;
}
if let Some(v) = props.maximum_packet_size {
c_props.maximum_packet_size = v;
}
if let Some(v) = props.topic_alias_maximum {
c_props.topic_alias_maximum = v;
}
if let Some(v) = props.request_response_info {
c_props.request_resp_info = v;
}
if let Some(v) = props.request_problem_info {
c_props.request_problem_info = v;
}
if let Some(v) = props.message_expiry_interval {
c_props.message_expiry_interval = v;
}
if let Some(v) = props.payload_format_indicator {
c_props.payload_format_indicator = v;
}
esp!(unsafe { esp_mqtt5_client_set_connect_property(client.raw_client, &c_props) })?;
}

esp!(unsafe { esp_mqtt_client_start(client.raw_client) })?;

Ok(client)
Expand Down
Loading