initial commmit

This commit is contained in:
Sam
2025-05-22 16:26:07 +10:00
commit 9ba0ff268f
1292 changed files with 225422 additions and 0 deletions

View File

@@ -0,0 +1 @@
2fb8e302a94e05fa40af4788fa45414369db53b2a8ccfaa7d07797192894c708

View File

@@ -0,0 +1,34 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# http://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[{*.md,*.rst}]
trim_trailing_whitespace = false
[{Makefile,*.mk,*.bat}]
indent_style = tab
indent_size = 2
[*/freertos/**]
indent_style = tab
indent_size = 4
[{*/freertos/**.S,**/FreeRTOSConfig.h}]
indent_style = space
indent_size = 4
[*.pem]
insert_final_newline = false
[*.py]
max_line_length = 119

36
managed_components/esp_mqtt/.gitignore vendored Normal file
View File

@@ -0,0 +1,36 @@
# 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/
*.su
build
examples/**/build
examples/**/sdkconfig*

View File

@@ -0,0 +1,65 @@
sudo: false
language: bash
os:
- linux
addons:
apt:
packages:
- gperf
- python
- python-serial
before_install:
# Save path to the git respository
- PROJECT_PATH=$(pwd)
# Have to checkout a temp branch for later in tree reference
- git checkout -b temporary_ref_branch
- CI_COMMIT_SHA=$(git rev-parse HEAD)
# Test building with latest (stable == v3.3 for now) IDF
- LTS_IDF=release/v3.3
install:
# Install ESP32 toochain following steps as desribed
# in http://esp-idf.readthedocs.io/en/latest/linux-setup.html
#
# Get required packages - already done above, see addons: apt: packages:
# - sudo apt-get install git wget make libncurses-dev flex bison gperf python python-serial
# Prepare directory for the toolchain
- mkdir -p ~/esp
- cd ~/esp
# Download binary toolchain for the ESP32
- wget https://dl.espressif.com/dl/xtensa-esp32-elf-linux64-1.22.0-80-g6c4433a-5.2.0.tar.gz
- tar -xzf xtensa-esp32-elf-linux64-1.22.0-80-g6c4433a-5.2.0.tar.gz
# Get ESP-IDF from github (non-recursive to save time, later we update submodules for different versions)
- git clone https://github.com/espressif/esp-idf.git
# Set the path to ESP-IDF directory
- export IDF_PATH=~/esp/esp-idf
- python -m pip install --user -r $IDF_PATH/requirements.txt
# Setup build tool: xtensa-esp32-elf and idf.py
- export PATH=$PATH:$HOME/esp/xtensa-esp32-elf/bin:$IDF_PATH/tools
script:
# Legacy build with IDF < 3.2
- cd $IDF_PATH
- git checkout v3.1 && git submodule update --init --recursive
- cd $PROJECT_PATH
- ./ci/modify_for_legacy_idf.sh ${LTS_IDF} || true
- cd $PROJECT_PATH/examples/tcp
- make defconfig
- make -j4
# Build with v3.3 (LTS) IDF
- cd $IDF_PATH
- git checkout ${LTS_IDF} && git submodule update --init --recursive
- cd $IDF_PATH/components/mqtt/esp-mqtt
- git remote add local $PROJECT_PATH/.git
- git fetch local
- git reset --hard $CI_COMMIT_SHA
- cd $IDF_PATH/examples/protocols/mqtt/tcp
- idf.py build
- cd $IDF_PATH/examples/protocols/mqtt/ssl
- idf.py build
- cd $IDF_PATH/examples/protocols/mqtt/ws
- idf.py build
- cd $IDF_PATH/examples/protocols/mqtt/wss
- idf.py build

View File

@@ -0,0 +1,14 @@
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
)

View File

@@ -0,0 +1,190 @@
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_POLL_READ_TIMEOUT_MS
int "MQTT transport poll read timeut"
default 1000
depends on MQTT_USE_CUSTOM_CONFIG
help
Timeout when polling underlying transport for read.
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_OUTBOX_DATA_ON_EXTERNAL_MEMORY
bool "Use external memory for outbox data"
default n
depends on MQTT_USE_CUSTOM_CONFIG
help
Set to true to use external memory for outbox data.
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.
config MQTT_TOPIC_PRESENT_ALL_DATA_EVENTS
bool "Enable publish topic in all data events"
default n
depends on MQTT_USE_CUSTOM_CONFIG
help
Set to true to have publish topic in all data events. This changes the behaviour
when the message is bigger than the receive buffer size. The first event of the sequence
always have the topic.
Note: This will allocate memory to store the topic only in case of messge bigger than the buffer size.
endmenu

View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2016 Tuan PM
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,41 @@
# ESP32 MQTT Library
![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/espressif/esp-mqtt/test-examples.yml?branch=master)
![License](https://img.shields.io/github/license/espressif/esp-mqtt)
![GitHub contributors](https://img.shields.io/github/contributors/espressif/esp-mqtt)
## Features
- Based on: <https://github.com/tuanpmt/esp_mqtt>
- Support MQTT over TCP, SSL with mbedtls, MQTT over Websocket, MQTT over Websocket Secure
- Easy to setup with URI
- Multiple instances (Multiple clients in one application)
- Support subscribing, publishing, authentication, will messages, keep alive pings and all 3 QoS levels (it should be a fully functional client).
- Support for MQTT 3.1.1 and 5.0
## How to use
[ESP-MQTT](https://github.com/espressif/esp-mqtt) is a standard [ESP-IDF](https://github.com/espressif/esp-idf) component.
Please refer to instructions in [ESP-IDF](https://github.com/espressif/esp-idf)
## Documentation
- Please refer to the standard [ESP-IDF](https://github.com/espressif/esp-idf), documentation for the latest version: <https://docs.espressif.com/projects/esp-idf/>
- Documentation of ESP-MQTT API: <https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/protocols/mqtt.html>
## License
- MQTT Package - [Stephen Robinson - contiki-mqtt](https://github.com/esar/contiki-mqtt)
- Others [@tuanpmt](https://twitter.com/tuanpmt)
Apache License
## Older IDF verisons
For [ESP-IDF](https://github.com/espressif/esp-idf) versions prior to IDFv3.2, please clone as a component of [ESP-IDF](https://github.com/espressif/esp-idf):
```
git submodule add https://github.com/espressif/esp-mqtt.git components/espmqtt
```
and checkout the [ESP-MQTT_FOR_IDF_3.1](https://github.com/espressif/esp-mqtt/tree/ESP-MQTT_FOR_IDF_3.1) tag

View File

@@ -0,0 +1,11 @@
# Documentation: .gitlab/ci/README.md#manifest-file-to-control-the-buildtest-apps
examples/protocols:
enable:
- if: IDF_TARGET in ["esp32"]
examples/protocols/mqtt/ssl_ds:
disable:
- if: SOC_DIG_SIGN_SUPPORTED != 1
temporary: false
reason: DS not present

View File

@@ -0,0 +1,22 @@
#!/usr/bin/env bash
# build mqtt examples with make if $1=="make", with cmake otherwise
set -o errexit # Exit if command failed.
if [ -z $IDF_PATH ] ; then
echo "Mandatory variables undefined"
exit 1;
fi;
examples="tcp ssl ssl_mutual_auth ws wss"
for i in $examples; do
echo "Building MQTT example $i"
cd $IDF_PATH/examples/protocols/mqtt/$i
if [[ "$1" = "make" ]]; then
make defconfig
make -j 4
else
rm -rf build sdkconfig
idf.py build
fi;
done

View File

@@ -0,0 +1,2 @@
build_dir = "$GITHUB_WORKSPACE/build_@t_@n"
config="sdkconfig.ci"

View File

@@ -0,0 +1,33 @@
#!/usr/bin/env bash
if [[ -z $1 ]]; then
LATEST_IDF=master
else
LATEST_IDF=$1
fi
# This snipped prepares environment for using esp-mqtt repository separately from idf -- legacy use before IDFv3.2
#
esp_mqtt_path=`pwd`
mkdir -p ${esp_mqtt_path}/examples
pushd
cd $IDF_PATH
former_commit_id=`git rev-parse HEAD`
git checkout ${LATEST_IDF}
for example in tcp; do
cp -r $IDF_PATH/examples/protocols/mqtt/${example} ${esp_mqtt_path}/examples
echo 'EXTRA_COMPONENT_DIRS += $(PROJECT_PATH)/../../../' > ${esp_mqtt_path}/examples/${example}/Makefile
cat $IDF_PATH/examples/protocols/mqtt/${example}/Makefile >> ${esp_mqtt_path}/examples/${example}/Makefile
echo "CONFIG_MQTT_TRANSPORT_SSL=" >> ${esp_mqtt_path}/examples/${example}/sdkconfig.defaults
echo "CONFIG_MQTT_TRANSPORT_WEBSOCKET=" >> ${esp_mqtt_path}/examples/${example}/sdkconfig.defaults
done
cp -r $IDF_PATH/components/tcp_transport ${esp_mqtt_path}/..
rm ${esp_mqtt_path}/../tcp_transport/transport_ssl.c
echo -e "#include \"esp_transport.h\"\nvoid esp_transport_ws_set_path(esp_transport_handle_t t, const char *path) {}" > ${esp_mqtt_path}/../tcp_transport/transport_ws.c
cp $IDF_PATH/components/mqtt/Kconfig ${esp_mqtt_path}
sed 's/esp-mqtt/\./g' $IDF_PATH/components/mqtt/component.mk > ${esp_mqtt_path}/component.mk
git checkout $former_commit_id
popd

View File

@@ -0,0 +1,7 @@
CaseConfig:
- name: test_app_protocol_mqtt_publish_connect
overwrite:
dut:
class: ESP32QEMUDUT
package: ttfw_idf

View File

@@ -0,0 +1,19 @@
#!/usr/bin/env bash
# sets up the IDF repo incl submodules with specified version as $1
set -o errexit # Exit if command failed.
if [ -z $IDF_PATH ] || [ -z $MQTT_PATH ] || [ -z $1 ] ; then
echo "Mandatory variables undefined"
exit 1;
fi;
echo "Checking out IDF version $1"
cd $IDF_PATH
# Cleans out the untracked files in the repo, so the next "git checkout" doesn't fail
git clean -f
git checkout $1
# Removes the mqtt submodule, so the next submodule update doesn't fail
rm -rf $IDF_PATH/components/mqtt/esp-mqtt
git submodule update --init --recursive

View File

@@ -0,0 +1,16 @@
#!/usr/bin/env bash
# sets the mqtt in IDF tree as a submodule with the version specified as $1
set -o errexit # Exit if command failed.
if [ -z $IDF_PATH ] || [ -z $MQTT_PATH ] || [ -z $1 ] ; then
echo "Mandatory variables undefined"
exit 1;
fi;
echo "Checking out MQTT version to $1"
# exchange remotes of mqtt submodules with plain copy
cd $IDF_PATH/components/mqtt/esp-mqtt
rm -rf .git # removes the actual IDF referenced version
cp -r $MQTT_PATH/.git . # replaces with the MQTT_PATH (CI checked tree)
git reset --hard $1 # sets the requested version

View File

@@ -0,0 +1,15 @@
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
set(COMPONENTS mqtt main)
list(APPEND EXTRA_COMPONENT_DIRS
"$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)

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,21 @@
idf_component_register(SRCS "test_mqtt_client.cpp"
REQUIRES cmock mqtt esp_timer esp_hw_support http_parser log
WHOLE_ARCHIVE)
target_compile_options(${COMPONENT_LIB} PUBLIC -fsanitize=address -fconcepts)
target_link_options(${COMPONENT_LIB} PUBLIC -fsanitize=address)
target_link_libraries(${COMPONENT_LIB} PUBLIC Catch2::Catch2WithMain)
idf_component_get_property(mqtt mqtt COMPONENT_LIB)
target_compile_definitions(${mqtt} PRIVATE SOC_WIFI_SUPPORTED=1)
target_compile_options(${mqtt} PUBLIC -fsanitize=address -fconcepts)
target_link_options(${mqtt} PUBLIC -fsanitize=address)
if(CONFIG_GCOV_ENABLED)
target_compile_options(${COMPONENT_LIB} PUBLIC --coverage -fprofile-arcs -ftest-coverage)
target_link_options(${COMPONENT_LIB} PUBLIC --coverage -fprofile-arcs -ftest-coverage)
idf_component_get_property(mqtt mqtt COMPONENT_LIB)
target_compile_options(${mqtt} PUBLIC --coverage -fprofile-arcs -ftest-coverage)
target_link_options(${mqtt} PUBLIC --coverage -fprofile-arcs -ftest-coverage)
endif()

View File

@@ -0,0 +1,9 @@
menu "Host-test config"
config GCOV_ENABLED
bool "Coverage analyzer"
default n
help
Enables coverage analyzing for host tests.
endmenu

View File

@@ -0,0 +1,6 @@
## IDF Component Manager Manifest File
dependencies:
espressif/catch2: "^3.5.2"
## Required IDF version
idf:
version: ">=5.0.0"

View File

@@ -0,0 +1,168 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <memory>
#include <net/if.h>
#include <random>
#include <string_view>
#include <type_traits>
#include "esp_transport.h"
#include <catch2/catch_test_macros.hpp>
#include "mqtt_client.h"
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"
#if __has_include ("Mockidf_additions.h")
/* Some functions were moved from "task.h" to "idf_additions.h" */
#include "Mockidf_additions.h"
#endif
#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;
}
}
auto random_string(std::size_t n)
{
static constexpr std::string_view char_set = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456790";
std::string str;
std::sample(char_set.begin(), char_set.end(), std::back_inserter(str), n,
std::mt19937 {std::random_device{}()});
return str;
}
using unique_mqtt_client = std::unique_ptr < std::remove_pointer_t<esp_mqtt_client_handle_t>, decltype([](esp_mqtt_client_handle_t client)
{
esp_mqtt_client_destroy(client);
}) >;
SCENARIO("MQTT Client Operation")
{
// Set expectations for the mocked calls.
int mtx = 0;
int transport_list = 0;
int transport = 0;
int event_group = 0;
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_url_init_Ignore();
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);
esp_transport_destroy_IgnoreAndReturn(ESP_OK);
vEventGroupDelete_Ignore();
vQueueDelete_Ignore();
GIVEN("An a minimal config") {
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_ExpectAnyArgsAndReturn(0);
http_parser_parse_url_ReturnThruPtr_u(&ret_uri);
xTaskCreatePinnedToCore_ExpectAnyArgsAndReturn(pdTRUE);
SECTION("Client with minimal config") {
auto client = unique_mqtt_client{esp_mqtt_client_init(&config)};
REQUIRE(client != nullptr);
SECTION("User will set a new 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.get(), " ");
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.get(), " ");
REQUIRE(res == ESP_FAIL);
}
}
SECTION("User set interface to use"){
http_parser_parse_url_ExpectAnyArgsAndReturn(0);
http_parser_parse_url_ReturnThruPtr_u(&ret_uri);
struct ifreq if_name = {.ifr_ifrn = {"custom"}};
config.network.if_name = &if_name;
SECTION("Client is not started"){
REQUIRE(esp_mqtt_set_config(client.get(), &config)== ESP_OK);
}
}
SECTION("After Start Client Is Cleanly destroyed") {
REQUIRE(esp_mqtt_client_start(client.get()) == ESP_OK);
// Only need to start the client, destroy is called automatically at the end of
// scope
}
}
SECTION("Client with all allocating configuration set") {
auto host = random_string(20);
auto path = random_string(10);
auto username = random_string(10);
auto client_id = random_string(10);
auto password = random_string(10);
auto lw_topic = random_string(10);
auto lw_msg = random_string(10);
config.broker = {.address = {
.hostname = host.data(),
.path = path.data()
}
};
config.credentials = {
.username = username.data(),
.client_id = client_id.data(),
.authentication = {
.password = password.data()
}
};
config.session = {
.last_will {
.topic = lw_topic.data(),
.msg = lw_msg.data()
}
};
auto client = unique_mqtt_client{esp_mqtt_client_init(&config)};
REQUIRE(client != nullptr);
}
}
}

View File

@@ -0,0 +1,71 @@
/*
* SPDX-FileCopyrightText: 1991-1993 The Regents of the University of California
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#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 @@
CONFIG_GCOV_ENABLED=y

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

View File

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

View File

@@ -0,0 +1,286 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef _MQTT5_CLIENT_H_
#define _MQTT5_CLIENT_H_
#include "mqtt_client.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct esp_mqtt_client *esp_mqtt5_client_handle_t;
/**
* MQTT5 protocol error reason code, more details refer to MQTT5 protocol document section 2.4
*/
typedef enum mqtt5_error_reason_code_t {
MQTT5_UNSPECIFIED_ERROR = 0x80,
MQTT5_MALFORMED_PACKET = 0x81,
MQTT5_PROTOCOL_ERROR = 0x82,
MQTT5_IMPLEMENT_SPECIFIC_ERROR = 0x83,
MQTT5_UNSUPPORTED_PROTOCOL_VER = 0x84,
MQTT5_INVAILD_CLIENT_ID __attribute__((deprecated)) = 0x85,
MQTT5_INVALID_CLIENT_ID = 0x85,
MQTT5_BAD_USERNAME_OR_PWD = 0x86,
MQTT5_NOT_AUTHORIZED = 0x87,
MQTT5_SERVER_UNAVAILABLE = 0x88,
MQTT5_SERVER_BUSY = 0x89,
MQTT5_BANNED = 0x8A,
MQTT5_SERVER_SHUTTING_DOWN = 0x8B,
MQTT5_BAD_AUTH_METHOD = 0x8C,
MQTT5_KEEP_ALIVE_TIMEOUT = 0x8D,
MQTT5_SESSION_TAKEN_OVER = 0x8E,
MQTT5_TOPIC_FILTER_INVAILD __attribute__((deprecated)) = 0x8F,
MQTT5_TOPIC_FILTER_INVALID = 0x8F,
MQTT5_TOPIC_NAME_INVAILD __attribute__((deprecated)) = 0x90,
MQTT5_TOPIC_NAME_INVALID = 0x90,
MQTT5_PACKET_IDENTIFIER_IN_USE = 0x91,
MQTT5_PACKET_IDENTIFIER_NOT_FOUND = 0x92,
MQTT5_RECEIVE_MAXIMUM_EXCEEDED = 0x93,
MQTT5_TOPIC_ALIAS_INVAILD __attribute__((deprecated)) = 0x94,
MQTT5_TOPIC_ALIAS_INVALID = 0x94,
MQTT5_PACKET_TOO_LARGE = 0x95,
MQTT5_MESSAGE_RATE_TOO_HIGH = 0x96,
MQTT5_QUOTA_EXCEEDED = 0x97,
MQTT5_ADMINISTRATIVE_ACTION = 0x98,
MQTT5_PAYLOAD_FORMAT_INVAILD __attribute__((deprecated)) = 0x99,
MQTT5_PAYLOAD_FORMAT_INVALID = 0x99,
MQTT5_RETAIN_NOT_SUPPORT = 0x9A,
MQTT5_QOS_NOT_SUPPORT = 0x9B,
MQTT5_USE_ANOTHER_SERVER = 0x9C,
MQTT5_SERVER_MOVED = 0x9D,
MQTT5_SHARED_SUBSCR_NOT_SUPPORTED = 0x9E,
MQTT5_CONNECTION_RATE_EXCEEDED = 0x9F,
MQTT5_MAXIMUM_CONNECT_TIME = 0xA0,
MQTT5_SUBSCRIBE_IDENTIFIER_NOT_SUPPORT = 0xA1,
MQTT5_WILDCARD_SUBSCRIBE_NOT_SUPPORT = 0xA2,
} esp_mqtt5_error_reason_code_t;
/**
* MQTT5 user property handle
*/
typedef struct mqtt5_user_property_list_t *mqtt5_user_property_handle_t;
/**
* MQTT5 protocol connect properties and will properties configuration, more details refer to MQTT5 protocol document section 3.1.2.11 and 3.3.2.3
*/
typedef struct {
uint32_t session_expiry_interval; /*!< The interval time of session expiry */
uint32_t maximum_packet_size; /*!< The maximum packet size that we can receive */
uint16_t receive_maximum; /*!< The maximum pakcket count that we process concurrently */
uint16_t topic_alias_maximum; /*!< The maximum topic alias that we support */
bool request_resp_info; /*!< This value to request Server to return Response information */
bool request_problem_info; /*!< This value to indicate whether the reason string or user properties are sent in case of failures */
mqtt5_user_property_handle_t user_property; /*!< The handle for user property, call function esp_mqtt5_client_set_user_property to set it */
uint32_t will_delay_interval; /*!< The time interval that server delays publishing will message */
uint32_t message_expiry_interval; /*!< The time interval that message expiry */
bool payload_format_indicator; /*!< This value is to indicator will message payload format */
const char *content_type; /*!< This value is to indicator will message content type, use a MIME content type string */
const char *response_topic; /*!< Topic name for a response message */
const char *correlation_data; /*!< Binary data for receiver to match the response message */
uint16_t correlation_data_len; /*!< The length of correlation data */
mqtt5_user_property_handle_t will_user_property; /*!< The handle for will message user property, call function esp_mqtt5_client_set_user_property to set it */
} esp_mqtt5_connection_property_config_t;
/**
* MQTT5 protocol publish properties configuration, more details refer to MQTT5 protocol document section 3.3.2.3
*/
typedef struct {
bool payload_format_indicator; /*!< This value is to indicator publish message payload format */
uint32_t message_expiry_interval; /*!< The time interval that message expiry */
uint16_t topic_alias; /*!< An interger value to identify the topic instead of using topic name string */
const char *response_topic; /*!< Topic name for a response message */
const char *correlation_data; /*!< Binary data for receiver to match the response message */
uint16_t correlation_data_len; /*!< The length of correlation data */
const char *content_type; /*!< This value is to indicator publish message content type, use a MIME content type string */
mqtt5_user_property_handle_t user_property; /*!< The handle for user property, call function esp_mqtt5_client_set_user_property to set it */
} esp_mqtt5_publish_property_config_t;
/**
* MQTT5 protocol subscribe properties configuration, more details refer to MQTT5 protocol document section 3.8.2.1
*/
typedef struct {
uint16_t subscribe_id; /*!< A variable byte represents the identifier of the subscription */
bool no_local_flag; /*!< Subscription Option to allow that server publish message that client sent */
bool retain_as_published_flag; /*!< Subscription Option to keep the retain flag as published option */
uint8_t retain_handle; /*!< Subscription Option to handle retain option */
bool is_share_subscribe; /*!< Whether subscribe is a shared subscription */
const char *share_name; /*!< The name of shared subscription which is a part of $share/{share_name}/{topic} */
mqtt5_user_property_handle_t user_property; /*!< The handle for user property, call function esp_mqtt5_client_set_user_property to set it */
} esp_mqtt5_subscribe_property_config_t;
/**
* MQTT5 protocol unsubscribe properties configuration, more details refer to MQTT5 protocol document section 3.10.2.1
*/
typedef struct {
bool is_share_subscribe; /*!< Whether subscribe is a shared subscription */
const char *share_name; /*!< The name of shared subscription which is a part of $share/{share_name}/{topic} */
mqtt5_user_property_handle_t user_property; /*!< The handle for user property, call function esp_mqtt5_client_set_user_property to set it */
} esp_mqtt5_unsubscribe_property_config_t;
/**
* MQTT5 protocol disconnect properties configuration, more details refer to MQTT5 protocol document section 3.14.2.2
*/
typedef struct {
uint32_t session_expiry_interval; /*!< The interval time of session expiry */
uint8_t disconnect_reason; /*!< The reason that connection disconnet, refer to mqtt5_error_reason_code */
mqtt5_user_property_handle_t user_property; /*!< The handle for user property, call function esp_mqtt5_client_set_user_property to set it */
} esp_mqtt5_disconnect_property_config_t;
/**
* MQTT5 protocol for event properties
*/
typedef struct {
bool payload_format_indicator; /*!< Payload format of the message */
char *response_topic; /*!< Response topic of the message */
int response_topic_len; /*!< Response topic length of the message */
char *correlation_data; /*!< Correlation data of the message */
uint16_t correlation_data_len; /*!< Correlation data length of the message */
char *content_type; /*!< Content type of the message */
int content_type_len; /*!< Content type length of the message */
uint16_t subscribe_id; /*!< Subscription identifier of the message */
mqtt5_user_property_handle_t user_property; /*!< The handle for user property, call function esp_mqtt5_client_delete_user_property to free the memory */
} esp_mqtt5_event_property_t;
/**
* MQTT5 protocol for user property
*/
typedef struct {
const char *key; /*!< Item key name */
const char *value; /*!< Item value string */
} esp_mqtt5_user_property_item_t;
/**
* @brief Set MQTT5 client connect property configuration
*
* @param client mqtt client handle
* @param connect_property connect property
*
* @return ESP_ERR_NO_MEM if failed to allocate
* ESP_ERR_INVALID_ARG on wrong initialization
* ESP_FAIL on fail
* ESP_OK on success
*/
esp_err_t esp_mqtt5_client_set_connect_property(esp_mqtt5_client_handle_t client, const esp_mqtt5_connection_property_config_t *connect_property);
/**
* @brief Set MQTT5 client publish property configuration
*
* This API will not store the publish property, it is one-time configuration.
* Before call `esp_mqtt_client_publish` to publish data, call this API to set publish property if have
*
* @param client mqtt client handle
* @param property publish property
*
* @return ESP_ERR_INVALID_ARG on wrong initialization
* ESP_FAIL on fail
* ESP_OK on success
*/
esp_err_t esp_mqtt5_client_set_publish_property(esp_mqtt5_client_handle_t client, const esp_mqtt5_publish_property_config_t *property);
/**
* @brief Set MQTT5 client subscribe property configuration
*
* This API will not store the subscribe property, it is one-time configuration.
* Before call `esp_mqtt_client_subscribe` to subscribe topic, call this API to set subscribe property if have
*
* @param client mqtt client handle
* @param property subscribe property
*
* @return ESP_ERR_INVALID_ARG on wrong initialization
* ESP_FAIL on fail
* ESP_OK on success
*/
esp_err_t esp_mqtt5_client_set_subscribe_property(esp_mqtt5_client_handle_t client, const esp_mqtt5_subscribe_property_config_t *property);
/**
* @brief Set MQTT5 client unsubscribe property configuration
*
* This API will not store the unsubscribe property, it is one-time configuration.
* Before call `esp_mqtt_client_unsubscribe` to unsubscribe topic, call this API to set unsubscribe property if have
*
* @param client mqtt client handle
* @param property unsubscribe property
*
* @return ESP_ERR_INVALID_ARG on wrong initialization
* ESP_FAIL on fail
* ESP_OK on success
*/
esp_err_t esp_mqtt5_client_set_unsubscribe_property(esp_mqtt5_client_handle_t client, const esp_mqtt5_unsubscribe_property_config_t *property);
/**
* @brief Set MQTT5 client disconnect property configuration
*
* This API will not store the disconnect property, it is one-time configuration.
* Before call `esp_mqtt_client_disconnect` to disconnect connection, call this API to set disconnect property if have
*
* @param client mqtt client handle
* @param property disconnect property
*
* @return ESP_ERR_NO_MEM if failed to allocate
* ESP_ERR_INVALID_ARG on wrong initialization
* ESP_FAIL on fail
* ESP_OK on success
*/
esp_err_t esp_mqtt5_client_set_disconnect_property(esp_mqtt5_client_handle_t client, const esp_mqtt5_disconnect_property_config_t *property);
/**
* @brief Set MQTT5 client user property configuration
*
* This API will allocate memory for user_property, please DO NOT forget `call esp_mqtt5_client_delete_user_property`
* after you use it.
* Before publish data, subscribe topic, unsubscribe, etc, call this API to set user property if have
*
* @param user_property user_property handle
* @param item array of user property data (eg. {{"var","val"},{"other","2"}})
* @param item_num number of items in user property data
*
* @return ESP_ERR_NO_MEM if failed to allocate
* ESP_FAIL on fail
* ESP_OK on success
*/
esp_err_t esp_mqtt5_client_set_user_property(mqtt5_user_property_handle_t *user_property, esp_mqtt5_user_property_item_t item[], uint8_t item_num);
/**
* @brief Get MQTT5 client user property
*
* @param user_property user_property handle
* @param item point that store user property data
* @param item_num number of items in user property data
*
* This API can use with `esp_mqtt5_client_get_user_property_count` to get list count of user property.
* And malloc number of count item array memory to store the user property data.
* Please DO NOT forget the item memory, key and value point in item memory when get user property data successfully.
*
* @return ESP_ERR_NO_MEM if failed to allocate
* ESP_FAIL on fail
* ESP_OK on success
*/
esp_err_t esp_mqtt5_client_get_user_property(mqtt5_user_property_handle_t user_property, esp_mqtt5_user_property_item_t *item, uint8_t *item_num);
/**
* @brief Get MQTT5 client user property list count
*
* @param user_property user_property handle
* @return user property list count
*/
uint8_t esp_mqtt5_client_get_user_property_count(mqtt5_user_property_handle_t user_property);
/**
* @brief Free the user property list
*
* @param user_property user_property handle
*
* This API will free the memory in user property list and free user_property itself
*/
void esp_mqtt5_client_delete_user_property(mqtt5_user_property_handle_t user_property);
#ifdef __cplusplus
}
#endif //__cplusplus
#endif

View File

@@ -0,0 +1,702 @@
/*
* 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_CLIENT_H_
#define _MQTT_CLIENT_H_
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include "esp_err.h"
#include "esp_event.h"
#include "esp_transport.h"
#ifdef CONFIG_MQTT_PROTOCOL_5
#include "mqtt5_client.h"
#endif
#ifdef __cplusplus
extern "C" {
#endif
#ifndef ESP_EVENT_DECLARE_BASE
// Define event loop types if macros not available
typedef void *esp_event_loop_handle_t;
typedef void *esp_event_handler_t;
#endif
typedef struct esp_mqtt_client *esp_mqtt_client_handle_t;
#define MQTT_OVER_TCP_SCHEME "mqtt"
#define MQTT_OVER_SSL_SCHEME "mqtts"
#define MQTT_OVER_WS_SCHEME "ws"
#define MQTT_OVER_WSS_SCHEME "wss"
/**
* @brief *MQTT* event types.
*
* User event handler receives context data in `esp_mqtt_event_t` structure with
* - client - *MQTT* client handle
* - various other data depending on event type
*
*/
typedef enum esp_mqtt_event_id_t {
MQTT_EVENT_ANY = -1,
MQTT_EVENT_ERROR =
0, /*!< on error event, additional context: connection return code, error
handle from esp_tls (if supported) */
MQTT_EVENT_CONNECTED, /*!< connected event, additional context:
session_present flag */
MQTT_EVENT_DISCONNECTED, /*!< disconnected event */
MQTT_EVENT_SUBSCRIBED, /*!< subscribed event, additional context:
- msg_id message id
- error_handle `error_type` in case subscribing failed
- data pointer to broker response, check for errors.
- data_len length of the data for this
event
*/
MQTT_EVENT_UNSUBSCRIBED, /*!< unsubscribed event, additional context: msg_id */
MQTT_EVENT_PUBLISHED, /*!< published event, additional context: msg_id */
MQTT_EVENT_DATA, /*!< data event, additional context:
- msg_id message id
- topic pointer to the received topic
- topic_len length of the topic
- data pointer to the received data
- data_len length of the data for this event
- current_data_offset offset of the current data for
this event
- total_data_len total length of the data received
- retain retain flag of the message
- qos QoS level of the message
- dup dup flag of the message
Note: Multiple MQTT_EVENT_DATA could be fired for one
message, if it is longer than internal buffer. In that
case only first event contains topic pointer and length,
other contain data only with current data length and
current data offset updating.
*/
MQTT_EVENT_BEFORE_CONNECT, /*!< The event occurs before connecting */
MQTT_EVENT_DELETED, /*!< Notification on delete of one message from the
internal outbox, if the message couldn't have been sent
or acknowledged before expiring defined in
OUTBOX_EXPIRED_TIMEOUT_MS. (events are not posted upon
deletion of successfully acknowledged messages)
- This event id is posted only if
MQTT_REPORT_DELETED_MESSAGES==1
- Additional context: msg_id (id of the deleted
message, always 0 for QoS = 0 messages).
*/
MQTT_USER_EVENT, /*!< Custom event used to queue tasks into mqtt event handler
All fields from the esp_mqtt_event_t type could be used to pass
an additional context data to the handler.
*/
} esp_mqtt_event_id_t;
/**
* *MQTT* connection error codes propagated via ERROR event
*/
typedef enum esp_mqtt_connect_return_code_t {
MQTT_CONNECTION_ACCEPTED = 0, /*!< Connection accepted */
MQTT_CONNECTION_REFUSE_PROTOCOL, /*!< *MQTT* connection refused reason: Wrong
protocol */
MQTT_CONNECTION_REFUSE_ID_REJECTED, /*!< *MQTT* connection refused reason: ID
rejected */
MQTT_CONNECTION_REFUSE_SERVER_UNAVAILABLE, /*!< *MQTT* connection refused
reason: Server unavailable */
MQTT_CONNECTION_REFUSE_BAD_USERNAME, /*!< *MQTT* connection refused reason:
Wrong user */
MQTT_CONNECTION_REFUSE_NOT_AUTHORIZED /*!< *MQTT* connection refused reason:
Wrong username or password */
} esp_mqtt_connect_return_code_t;
/**
* *MQTT* connection error codes propagated via ERROR event
*/
typedef enum esp_mqtt_error_type_t {
MQTT_ERROR_TYPE_NONE = 0,
MQTT_ERROR_TYPE_TCP_TRANSPORT,
MQTT_ERROR_TYPE_CONNECTION_REFUSED,
MQTT_ERROR_TYPE_SUBSCRIBE_FAILED
} esp_mqtt_error_type_t;
/**
* MQTT_ERROR_TYPE_TCP_TRANSPORT error type hold all sorts of transport layer
* errors, including ESP-TLS error, but in the past only the errors from
* MQTT_ERROR_TYPE_ESP_TLS layer were reported, so the ESP-TLS error type is
* re-defined here for backward compatibility
*/
#define MQTT_ERROR_TYPE_ESP_TLS MQTT_ERROR_TYPE_TCP_TRANSPORT
typedef enum esp_mqtt_transport_t {
MQTT_TRANSPORT_UNKNOWN = 0x0,
MQTT_TRANSPORT_OVER_TCP, /*!< *MQTT* over TCP, using scheme: ``MQTT`` */
MQTT_TRANSPORT_OVER_SSL, /*!< *MQTT* over SSL, using scheme: ``MQTTS`` */
MQTT_TRANSPORT_OVER_WS, /*!< *MQTT* over Websocket, using scheme:: ``ws`` */
MQTT_TRANSPORT_OVER_WSS /*!< *MQTT* over Websocket Secure, using scheme:
``wss`` */
} esp_mqtt_transport_t;
/**
* *MQTT* protocol version used for connection
*/
typedef enum esp_mqtt_protocol_ver_t {
MQTT_PROTOCOL_UNDEFINED = 0,
MQTT_PROTOCOL_V_3_1,
MQTT_PROTOCOL_V_3_1_1,
MQTT_PROTOCOL_V_5,
} esp_mqtt_protocol_ver_t;
/**
* @brief *MQTT* error code structure to be passed as a contextual information
* into ERROR event
*
* Important: This structure extends `esp_tls_last_error` error structure and is
* backward compatible with it (so might be down-casted and treated as
* `esp_tls_last_error` error, but recommended to update applications if used
* this way previously)
*
* Use this structure directly checking error_type first and then appropriate
* error code depending on the source of the error:
*
* | error_type | related member variables | note |
* | MQTT_ERROR_TYPE_TCP_TRANSPORT | esp_tls_last_esp_err, esp_tls_stack_err,
* esp_tls_cert_verify_flags, sock_errno | Error reported from
* tcp_transport/esp-tls | | MQTT_ERROR_TYPE_CONNECTION_REFUSED |
* connect_return_code | Internal error reported from *MQTT* broker on
* connection |
*/
typedef struct esp_mqtt_error_codes {
/* compatible portion of the struct corresponding to struct esp_tls_last_error
*/
esp_err_t esp_tls_last_esp_err; /*!< last esp_err code reported from esp-tls
component */
int esp_tls_stack_err; /*!< tls specific error code reported from underlying
tls stack */
int esp_tls_cert_verify_flags; /*!< tls flags reported from underlying tls
stack during certificate verification */
/* esp-mqtt specific structure extension */
esp_mqtt_error_type_t
error_type; /*!< error type referring to the source of the error */
esp_mqtt_connect_return_code_t
connect_return_code; /*!< connection refused error code reported from
*MQTT* broker on connection */
#ifdef CONFIG_MQTT_PROTOCOL_5
esp_mqtt5_error_reason_code_t
disconnect_return_code; /*!< disconnection reason code reported from
*MQTT* broker on disconnection */
#endif
/* tcp_transport extension */
int esp_transport_sock_errno; /*!< errno from the underlying socket */
} esp_mqtt_error_codes_t;
/**
* *MQTT* event configuration structure
*/
typedef struct esp_mqtt_event_t {
esp_mqtt_event_id_t event_id; /*!< *MQTT* event type */
esp_mqtt_client_handle_t client; /*!< *MQTT* client handle for this event */
char *data; /*!< Data associated with this event */
int data_len; /*!< Length of the data for this event */
int total_data_len; /*!< Total length of the data (longer data are supplied
with multiple events) */
int current_data_offset; /*!< Actual offset for the data associated with this
event */
char *topic; /*!< Topic associated with this event */
int topic_len; /*!< Length of the topic for this event associated with this
event */
int msg_id; /*!< *MQTT* messaged id of message */
int session_present; /*!< *MQTT* session_present flag for connection event */
esp_mqtt_error_codes_t
*error_handle; /*!< esp-mqtt error handle including esp-tls errors as well
as internal *MQTT* errors */
bool retain; /*!< Retained flag of the message associated with this event */
int qos; /*!< QoS of the messages associated with this event */
bool dup; /*!< dup flag of the message associated with this event */
esp_mqtt_protocol_ver_t protocol_ver; /*!< MQTT protocol version used for connection, defaults to value from menuconfig*/
#ifdef CONFIG_MQTT_PROTOCOL_5
esp_mqtt5_event_property_t *property; /*!< MQTT 5 property associated with this event */
#endif
} esp_mqtt_event_t;
typedef esp_mqtt_event_t *esp_mqtt_event_handle_t;
/**
* *MQTT* client configuration structure
*
* - Default values can be set via menuconfig
* - All certificates and key data could be passed in PEM or DER format. PEM format must have a terminating NULL
* character and the related len field set to 0. DER format requires a related len field set to the correct length.
*/
typedef struct esp_mqtt_client_config_t {
/**
* Broker related configuration
*/
struct broker_t {
/**
* Broker address
*
* - uri have precedence over other fields
* - If uri isn't set at least hostname, transport and port should.
*/
struct address_t {
const char *uri; /*!< Complete *MQTT* broker URI */
const char *hostname; /*!< Hostname, to set ipv4 pass it as string) */
esp_mqtt_transport_t transport; /*!< Selects transport*/
const char *path; /*!< Path in the URI*/
uint32_t port; /*!< *MQTT* server port */
} address; /*!< Broker address configuration */
/**
* Broker identity verification
*
* If fields are not set broker's identity isn't verified. it's recommended
* to set the options in this struct for security reasons.
*/
struct verification_t {
bool use_global_ca_store; /*!< Use a global ca_store, look esp-tls
documentation for details. */
esp_err_t (*crt_bundle_attach)(void *conf); /*!< Pointer to ESP x509 Certificate Bundle attach function for
the usage of certificate bundles. Client only attach the bundle, the clean up must be done by the user. */
const char *certificate; /*!< Certificate data, default is NULL. It's not copied nor freed by the client, user needs to clean up.*/
size_t certificate_len; /*!< Length of the buffer pointed to by certificate. */
const struct psk_key_hint *psk_hint_key; /*!< Pointer to PSK struct defined in esp_tls.h to enable PSK
authentication (as alternative to certificate verification).
PSK is enabled only if there are no other ways to
verify broker. It's not copied nor freed by the client, user needs to clean up.*/
bool skip_cert_common_name_check; /*!< Skip any validation of server certificate CN field, this reduces the
security of TLS and makes the *MQTT* client susceptible to MITM attacks */
const char **alpn_protos; /*!< NULL-terminated list of supported application protocols to be used for ALPN.*/
const char *common_name; /*!< Pointer to the string containing server certificate common name.
If non-NULL, server certificate CN must match this name,
If NULL, server certificate CN must match hostname.
This is ignored if skip_cert_common_name_check=true.
It's not copied nor freed by the client, user needs to clean up.*/
} verification; /*!< Security verification of the broker */
} broker; /*!< Broker address and security verification */
/**
* Client related credentials for authentication.
*/
struct credentials_t {
const char *username; /*!< *MQTT* username */
const char *client_id; /*!< Set *MQTT* client identifier. Ignored if set_null_client_id == true If NULL set
the default client id. Default client id is ``ESP32_%CHIPID%`` where `%CHIPID%` are
last 3 bytes of MAC address in hex format */
bool set_null_client_id; /*!< Selects a NULL client id */
/**
* Client authentication
*
* Fields related to client authentication by broker
*
* For mutual authentication using TLS, user could select certificate and key,
* secure element or digital signature peripheral if available.
*
*/
struct authentication_t {
const char *password; /*!< *MQTT* password */
const char *certificate; /*!< Certificate for ssl mutual authentication, not required if mutual
authentication is not needed. Must be provided with `key`. It's not copied nor freed by the client, user needs to clean up.*/
size_t certificate_len; /*!< Length of the buffer pointed to by certificate.*/
const char *key; /*!< Private key for SSL mutual authentication, not required if mutual authentication
is not needed. If it is not NULL, also `certificate` has to be provided. It's not copied nor freed by the client, user needs to clean up.*/
size_t key_len; /*!< Length of the buffer pointed to by key.*/
const char *key_password; /*!< Client key decryption password, not PEM nor DER, if provided
`key_password_len` must be correctly set.*/
int key_password_len; /*!< Length of the password pointed to by `key_password` */
bool use_secure_element; /*!< Enable secure element, available in ESP32-ROOM-32SE, for SSL connection */
void *ds_data; /*!< Carrier of handle for digital signature parameters, digital signature peripheral is
available in some Espressif devices. It's not copied nor freed by the client, user needs to clean up.*/
bool use_ecdsa_peripheral; /*!< Enable ECDSA peripheral, available in some Espressif devices. */
uint8_t ecdsa_key_efuse_blk; /*!< ECDSA key block number from efuse, available in some Espressif devices. */
} authentication; /*!< Client authentication */
} credentials; /*!< User credentials for broker */
/**
* *MQTT* Session related configuration
*/
struct session_t {
/**
* Last Will and Testament message configuration.
*/
struct last_will_t {
const char *topic; /*!< LWT (Last Will and Testament) message topic */
const char *msg; /*!< LWT message, may be NULL terminated*/
int msg_len; /*!< LWT message length, if msg isn't NULL terminated must have the correct length */
int qos; /*!< LWT message QoS */
int retain; /*!< LWT retained message flag */
} last_will; /*!< Last will configuration */
bool disable_clean_session; /*!< *MQTT* clean session, default clean_session is true */
int keepalive; /*!< *MQTT* keepalive, default is 120 seconds
When configuring this value, keep in mind that the client attempts
to communicate with the broker at half the interval that is actually set.
This conservative approach allows for more attempts before the broker's timeout occurs */
bool disable_keepalive; /*!< Set `disable_keepalive=true` to turn off keep-alive mechanism, keepalive is active
by default. Note: setting the config value `keepalive` to `0` doesn't disable
keepalive feature, but uses a default keepalive period */
esp_mqtt_protocol_ver_t protocol_ver; /*!< *MQTT* protocol version used for connection.*/
int message_retransmit_timeout; /*!< timeout for retransmitting of failed packet */
} session; /*!< *MQTT* session configuration. */
/**
* Network related configuration
*/
struct network_t {
int reconnect_timeout_ms; /*!< Reconnect to the broker after this value in miliseconds if auto reconnect is not
disabled (defaults to 10s) */
int timeout_ms; /*!< Abort network operation if it is not completed after this value, in milliseconds
(defaults to 10s). */
int refresh_connection_after_ms; /*!< Refresh connection after this value (in milliseconds) */
bool disable_auto_reconnect; /*!< Client will reconnect to server (when errors/disconnect). Set
`disable_auto_reconnect=true` to disable */
esp_transport_keep_alive_t tcp_keep_alive_cfg; /*!< Transport keep-alive config*/
esp_transport_handle_t transport; /*!< Custom transport handle to use, leave it NULL to allow MQTT client create or recreate its own. Warning: The transport should be valid during the client lifetime and is destroyed when esp_mqtt_client_destroy is called. */
struct ifreq * if_name; /*!< The name of interface for data to go through. Use the default interface without setting */
} network; /*!< Network configuration */
/**
* Client task configuration
*/
struct task_t {
int priority; /*!< *MQTT* task priority*/
int stack_size; /*!< *MQTT* task stack size*/
} task; /*!< FreeRTOS task configuration.*/
/**
* Client buffer size configuration
*
* Client have two buffers for input and output respectivelly.
*/
struct buffer_t {
int size; /*!< size of *MQTT* send/receive buffer*/
int out_size; /*!< size of *MQTT* output buffer. If not defined, defaults to the size defined by
``buffer_size`` */
} buffer; /*!< Buffer size configuration.*/
/**
* Client outbox configuration options.
*/
struct outbox_config_t {
uint64_t limit; /*!< Size limit for the outbox in bytes.*/
} outbox; /*!< Outbox configuration. */
} esp_mqtt_client_config_t;
/**
* Topic definition struct
*/
typedef struct topic_t {
const char *filter; /*!< Topic filter to subscribe */
int qos; /*!< Max QoS level of the subscription */
} esp_mqtt_topic_t;
/**
* @brief Creates *MQTT* client handle based on the configuration
*
* @param config *MQTT* configuration structure
*
* @return mqtt_client_handle if successfully created, NULL on error
*/
esp_mqtt_client_handle_t
esp_mqtt_client_init(const esp_mqtt_client_config_t *config);
/**
* @brief Sets *MQTT* connection URI. This API is usually used to overrides the
* URI configured in esp_mqtt_client_init
*
* @param client *MQTT* client handle
* @param uri
*
* @return ESP_FAIL if URI parse error, ESP_OK on success
*/
esp_err_t esp_mqtt_client_set_uri(esp_mqtt_client_handle_t client,
const char *uri);
/**
* @brief Starts *MQTT* client with already created client handle
*
* @param client *MQTT* client handle
*
* @return ESP_OK on success
* ESP_ERR_INVALID_ARG on wrong initialization
* ESP_FAIL on other error
*/
esp_err_t esp_mqtt_client_start(esp_mqtt_client_handle_t client);
/**
* @brief This api is typically used to force reconnection upon a specific event
*
* @param client *MQTT* client handle
*
* @return ESP_OK on success
* ESP_ERR_INVALID_ARG on wrong initialization
* ESP_FAIL if client is in invalid state
*/
esp_err_t esp_mqtt_client_reconnect(esp_mqtt_client_handle_t client);
/**
* @brief This api is typically used to force disconnection from the broker
*
* @param client *MQTT* client handle
*
* @return ESP_OK on success
* ESP_ERR_INVALID_ARG on wrong initialization
*/
esp_err_t esp_mqtt_client_disconnect(esp_mqtt_client_handle_t client);
/**
* @brief Stops *MQTT* client tasks
*
* * Notes:
* - Cannot be called from the *MQTT* event handler
*
* @param client *MQTT* client handle
*
* @return ESP_OK on success
* ESP_ERR_INVALID_ARG on wrong initialization
* ESP_FAIL if client is in invalid state
*/
esp_err_t esp_mqtt_client_stop(esp_mqtt_client_handle_t client);
#ifdef __cplusplus
#define esp_mqtt_client_subscribe esp_mqtt_client_subscribe_single
#else
/**
* @brief Convenience macro to select subscribe function to use.
*
* Notes:
* - Usage of `esp_mqtt_client_subscribe_single` is the same as previous
* esp_mqtt_client_subscribe, refer to it for details.
*
* @param client_handle *MQTT* client handle
* @param topic_type Needs to be char* for single subscription or `esp_mqtt_topic_t` for multiple topics
* @param qos_or_size It's either a qos when subscribing to a single topic or the size of the subscription array when subscribing to multiple topics.
*
* @return message_id of the subscribe message on success
* -1 on failure
* -2 in case of full outbox.
*/
#define esp_mqtt_client_subscribe(client_handle, topic_type, qos_or_size) _Generic((topic_type), \
char *: esp_mqtt_client_subscribe_single, \
const char *: esp_mqtt_client_subscribe_single, \
esp_mqtt_topic_t*: esp_mqtt_client_subscribe_multiple \
)(client_handle, topic_type, qos_or_size)
#endif /* __cplusplus*/
/**
* @brief Subscribe the client to defined topic with defined qos
*
* Notes:
* - Client must be connected to send subscribe message
* - This API is could be executed from a user task or
* from a *MQTT* event callback i.e. internal *MQTT* task
* (API is protected by internal mutex, so it might block
* if a longer data receive operation is in progress.
* - `esp_mqtt_client_subscribe` could be used to call this function.
*
* @param client *MQTT* client handle
* @param topic topic filter to subscribe
* @param qos Max qos level of the subscription
*
* @return message_id of the subscribe message on success
* -1 on failure
* -2 in case of full outbox.
*/
int esp_mqtt_client_subscribe_single(esp_mqtt_client_handle_t client,
const char *topic, int qos);
/**
* @brief Subscribe the client to a list of defined topics with defined qos
*
* Notes:
* - Client must be connected to send subscribe message
* - This API is could be executed from a user task or
* from a *MQTT* event callback i.e. internal *MQTT* task
* (API is protected by internal mutex, so it might block
* if a longer data receive operation is in progress.
* - `esp_mqtt_client_subscribe` could be used to call this function.
*
* @param client *MQTT* client handle
* @param topic_list List of topics to subscribe
* @param size size of topic_list
*
* @return message_id of the subscribe message on success
* -1 on failure
* -2 in case of full outbox.
*/
int esp_mqtt_client_subscribe_multiple(esp_mqtt_client_handle_t client,
const esp_mqtt_topic_t *topic_list, int size);
/**
* @brief Unsubscribe the client from defined topic
*
* Notes:
* - Client must be connected to send unsubscribe message
* - It is thread safe, please refer to `esp_mqtt_client_subscribe_single` for details
*
* @param client *MQTT* client handle
* @param topic
*
* @return message_id of the subscribe message on success
* -1 on failure
*/
int esp_mqtt_client_unsubscribe(esp_mqtt_client_handle_t client,
const char *topic);
/**
* @brief Client to send a publish message to the broker
*
* Notes:
* - This API might block for several seconds, either due to network timeout
* (10s) or if publishing payloads longer than internal buffer (due to message
* fragmentation)
* - Client doesn't have to be connected for this API to work, enqueueing the
* messages with qos>1 (returning -1 for all the qos=0 messages if
* disconnected). If MQTT_SKIP_PUBLISH_IF_DISCONNECTED is enabled, this API will
* not attempt to publish when the client is not connected and will always
* return -1.
* - It is thread safe, please refer to `esp_mqtt_client_subscribe` for details
*
* @param client *MQTT* client handle
* @param topic topic string
* @param data payload string (set to NULL, sending empty payload message)
* @param len data length, if set to 0, length is calculated from payload
* string
* @param qos QoS of publish message
* @param retain retain flag
*
* @return message_id of the publish message (for QoS 0 message_id will always
* be zero) on success. -1 on failure, -2 in case of full outbox.
*/
int esp_mqtt_client_publish(esp_mqtt_client_handle_t client, const char *topic,
const char *data, int len, int qos, int retain);
/**
* @brief Enqueue a message to the outbox, to be sent later. Typically used for
* messages with qos>0, but could be also used for qos=0 messages if store=true.
*
* This API generates and stores the publish message into the internal outbox
* and the actual sending to the network is performed in the mqtt-task context
* (in contrast to the esp_mqtt_client_publish() which sends the publish message
* immediately in the user task's context). Thus, it could be used as a non
* blocking version of esp_mqtt_client_publish().
*
* @param client *MQTT* client handle
* @param topic topic string
* @param data payload string (set to NULL, sending empty payload message)
* @param len data length, if set to 0, length is calculated from payload
* string
* @param qos QoS of publish message
* @param retain retain flag
* @param store if true, all messages are enqueued; otherwise only QoS 1 and
* QoS 2 are enqueued
*
* @return message_id if queued successfully, -1 on failure, -2 in case of full outbox.
*/
int esp_mqtt_client_enqueue(esp_mqtt_client_handle_t client, const char *topic,
const char *data, int len, int qos, int retain,
bool store);
/**
* @brief Destroys the client handle
*
* Notes:
* - Cannot be called from the *MQTT* event handler
*
* @param client *MQTT* client handle
*
* @return ESP_OK
* ESP_ERR_INVALID_ARG on wrong initialization
*/
esp_err_t esp_mqtt_client_destroy(esp_mqtt_client_handle_t client);
/**
* @brief Set configuration structure, typically used when updating the config
* (i.e. on "before_connect" event
*
* Notes:
* - When calling this function make sure to have all the intendend configurations
* set, otherwise default values are set.
* @param client *MQTT* client handle
*
* @param config *MQTT* configuration structure
*
* @return ESP_ERR_NO_MEM if failed to allocate
* ESP_ERR_INVALID_ARG if conflicts on transport configuration.
* ESP_OK on success
*/
esp_err_t esp_mqtt_set_config(esp_mqtt_client_handle_t client,
const esp_mqtt_client_config_t *config);
/**
* @brief Registers *MQTT* event
*
* @param client *MQTT* client handle
* @param event event type
* @param event_handler handler callback
* @param event_handler_arg handlers context
*
* @return ESP_ERR_NO_MEM if failed to allocate
* ESP_ERR_INVALID_ARG on wrong initialization
* ESP_OK on success
*/
esp_err_t esp_mqtt_client_register_event(esp_mqtt_client_handle_t client,
esp_mqtt_event_id_t event,
esp_event_handler_t event_handler,
void *event_handler_arg);
/**
* @brief Unregisters mqtt event
*
* @param client mqtt client handle
* @param event event ID
* @param event_handler handler to unregister
*
* @return ESP_ERR_NO_MEM if failed to allocate
* ESP_ERR_INVALID_ARG on invalid event ID
* ESP_OK on success
*/
esp_err_t esp_mqtt_client_unregister_event(esp_mqtt_client_handle_t client, esp_mqtt_event_id_t event, esp_event_handler_t event_handler);
/**
* @brief Get outbox size
*
* @param client *MQTT* client handle
* @return outbox size
* 0 on wrong initialization
*/
int esp_mqtt_client_get_outbox_size(esp_mqtt_client_handle_t client);
/**
* @brief Dispatch user event to the mqtt internal event loop
*
* @param client *MQTT* client handle
* @param event *MQTT* event handle structure
* @return ESP_OK on success
* ESP_ERR_TIMEOUT if the event couldn't be queued (ref also CONFIG_MQTT_EVENT_QUEUE_SIZE)
*/
esp_err_t esp_mqtt_dispatch_custom_event(esp_mqtt_client_handle_t client, esp_mqtt_event_t *event);
/**
* @brief Get a transport from the scheme
*
* Allows extra settings to be made on the selected transport,
* for convenience the scheme used by the mqtt client are defined as
* MQTT_OVER_TCP_SCHEME, MQTT_OVER_SSL_SCHEME, MQTT_OVER_WS_SCHEME and MQTT_OVER_WSS_SCHEME
* If the transport_scheme is NULL and the client was set with a custom transport the custom transport will be returned.
*
* Notes:
* - This function should be called only on MQTT_EVENT_BEFORE_CONNECT.
* - The intetion is to provide a way to set different configurations than the ones available from client config.
* - If esp_mqtt_client_destroy is called the returned pointer will be invalidated.
* - All the required settings should be made in the MQTT_EVENT_BEFORE_CONNECT event handler
*
* @param client *MQTT* client handle
* @param transport_scheme Transport handle to search for.
* @return the transport handle
* NULL in case of error
*
*/
esp_transport_handle_t esp_mqtt_client_get_transport(esp_mqtt_client_handle_t client, char *transport_scheme);
#ifdef __cplusplus
}
#endif //__cplusplus
#endif

View File

@@ -0,0 +1,79 @@
// Copyright 2015-2019 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef _MQTT_SUPPORTED_FEATURES_H_
#define _MQTT_SUPPORTED_FEATURES_H_
#if __has_include("esp_idf_version.h")
#include "esp_idf_version.h"
#endif
/**
* @brief This header defines supported features of IDF which mqtt module
* could use depending on specific version of ESP-IDF.
* In case "esp_idf_version.h" were not found, all additional
* features would be disabled
*/
#ifdef ESP_IDF_VERSION
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(3, 3, 0)
// Features supported from 3.3
#define MQTT_SUPPORTED_FEATURE_EVENT_LOOP
#define MQTT_SUPPORTED_FEATURE_SKIP_CRT_CMN_NAME_CHECK
#endif
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0)
// Features supported in 4.0
#define MQTT_SUPPORTED_FEATURE_WS_SUBPROTOCOL
#define MQTT_SUPPORTED_FEATURE_TRANSPORT_ERR_REPORTING
#endif
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 1, 0)
// Features supported in 4.1
#define MQTT_SUPPORTED_FEATURE_PSK_AUTHENTICATION
#define MQTT_SUPPORTED_FEATURE_DER_CERTIFICATES
#define MQTT_SUPPORTED_FEATURE_ALPN
#define MQTT_SUPPORTED_FEATURE_CLIENT_KEY_PASSWORD
#endif
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0)
// Features supported in 4.2
#define MQTT_SUPPORTED_FEATURE_SECURE_ELEMENT
#endif
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 3, 0)
// Features supported in 4.3
#define MQTT_SUPPORTED_FEATURE_DIGITAL_SIGNATURE
#define MQTT_SUPPORTED_FEATURE_TRANSPORT_SOCK_ERRNO_REPORTING
#endif
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)
// Features supported in 4.4
#define MQTT_SUPPORTED_FEATURE_CERTIFICATE_BUNDLE
#endif
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0)
// Features supported in 5.1.0
#define MQTT_SUPPORTED_FEATURE_CRT_CMN_NAME
#endif
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0)
// Features supported in 5.2.0
#define MQTT_SUPPORTED_FEATURE_ECDSA_PERIPHERAL
#endif
#endif /* ESP_IDF_VERSION */
#endif // _MQTT_SUPPORTED_FEATURES_H_

View File

@@ -0,0 +1,56 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef _MQTT5_CLIENT_PRIV_H_
#define _MQTT5_CLIENT_PRIV_H_
#include "mqtt5_client.h"
#include "mqtt_client_priv.h"
#include "mqtt5_msg.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct mqtt5_topic_alias {
char *topic;
uint16_t topic_len;
uint16_t topic_alias;
STAILQ_ENTRY(mqtt5_topic_alias) next;
} mqtt5_topic_alias_t;
STAILQ_HEAD(mqtt5_topic_alias_list_t, mqtt5_topic_alias);
typedef struct mqtt5_topic_alias_list_t *mqtt5_topic_alias_handle_t;
typedef struct mqtt5_topic_alias *mqtt5_topic_alias_item_t;
typedef struct {
esp_mqtt5_connection_property_storage_t connect_property_info;
esp_mqtt5_connection_will_property_storage_t will_property_info;
esp_mqtt5_connection_server_resp_property_t server_resp_property_info;
esp_mqtt5_disconnect_property_config_t disconnect_property_info;
const esp_mqtt5_publish_property_config_t *publish_property_info;
const esp_mqtt5_subscribe_property_config_t *subscribe_property_info;
const esp_mqtt5_unsubscribe_property_config_t *unsubscribe_property_info;
mqtt5_topic_alias_handle_t peer_topic_alias;
} mqtt5_config_storage_t;
void esp_mqtt5_increment_packet_counter(esp_mqtt5_client_handle_t client);
void esp_mqtt5_decrement_packet_counter(esp_mqtt5_client_handle_t client);
void esp_mqtt5_parse_pubcomp(esp_mqtt5_client_handle_t client);
void esp_mqtt5_parse_puback(esp_mqtt5_client_handle_t client);
void esp_mqtt5_parse_unsuback(esp_mqtt5_client_handle_t client);
void esp_mqtt5_parse_suback(esp_mqtt5_client_handle_t client);
void esp_mqtt5_parse_disconnect(esp_mqtt5_client_handle_t client, int *disconnect_rsp_code);
esp_err_t esp_mqtt5_parse_connack(esp_mqtt5_client_handle_t client, int *connect_rsp_code);
void esp_mqtt5_client_destory(esp_mqtt5_client_handle_t client);
esp_err_t esp_mqtt5_client_publish_check(esp_mqtt5_client_handle_t client, int qos, int retain);
esp_err_t esp_mqtt5_client_subscribe_check(esp_mqtt5_client_handle_t client, int qos);
esp_err_t esp_mqtt5_create_default_config(esp_mqtt5_client_handle_t client);
esp_err_t esp_mqtt5_get_publish_data(esp_mqtt5_client_handle_t client, uint8_t *msg_buf, size_t msg_read_len, char **msg_topic, size_t *msg_topic_len, char **msg_data, size_t *msg_data_len);
#ifdef __cplusplus
}
#endif //__cplusplus
#endif

View File

@@ -0,0 +1,142 @@
#ifndef MQTT5_MSG_H
#define MQTT5_MSG_H
#include <stdint.h>
#include <stdbool.h>
#include "sys/queue.h"
#include "mqtt_config.h"
#include "mqtt_msg.h"
#include "mqtt_client.h"
#ifdef __cplusplus
extern "C" {
#endif
enum mqtt_properties_type {
MQTT5_PROPERTY_PAYLOAD_FORMAT_INDICATOR = 0x01,
MQTT5_PROPERTY_MESSAGE_EXPIRY_INTERVAL = 0x02,
MQTT5_PROPERTY_CONTENT_TYPE = 0x03,
MQTT5_PROPERTY_RESPONSE_TOPIC = 0x08,
MQTT5_PROPERTY_CORRELATION_DATA = 0x09,
MQTT5_PROPERTY_SUBSCRIBE_IDENTIFIER = 0x0B,
MQTT5_PROPERTY_SESSION_EXPIRY_INTERVAL = 0x11,
MQTT5_PROPERTY_ASSIGNED_CLIENT_IDENTIFIER = 0x12,
MQTT5_PROPERTY_SERVER_KEEP_ALIVE = 0x13,
MQTT5_PROPERTY_AUTHENTICATION_METHOD = 0x15,
MQTT5_PROPERTY_AUTHENTICATION_DATA = 0x16,
MQTT5_PROPERTY_REQUEST_PROBLEM_INFO = 0x17,
MQTT5_PROPERTY_WILL_DELAY_INTERVAL = 0x18,
MQTT5_PROPERTY_REQUEST_RESP_INFO = 0x19,
MQTT5_PROPERTY_RESP_INFO = 0x1A,
MQTT5_PROPERTY_SERVER_REFERENCE = 0x1C,
MQTT5_PROPERTY_REASON_STRING = 0x1F,
MQTT5_PROPERTY_RECEIVE_MAXIMUM = 0x21,
MQTT5_PROPERTY_TOPIC_ALIAS_MAXIMIM = 0x22,
MQTT5_PROPERTY_TOPIC_ALIAS = 0x23,
MQTT5_PROPERTY_MAXIMUM_QOS = 0x24,
MQTT5_PROPERTY_RETAIN_AVAILABLE = 0x25,
MQTT5_PROPERTY_USER_PROPERTY = 0x26,
MQTT5_PROPERTY_MAXIMUM_PACKET_SIZE = 0x27,
MQTT5_PROPERTY_WILDCARD_SUBSCR_AVAILABLE = 0x28,
MQTT5_PROPERTY_SUBSCR_IDENTIFIER_AVAILABLE = 0x29,
MQTT5_PROPERTY_SHARED_SUBSCR_AVAILABLE = 0x2A,
};
typedef struct mqtt5_user_property {
char *key;
char *value;
STAILQ_ENTRY(mqtt5_user_property) next;
} mqtt5_user_property_t;
STAILQ_HEAD(mqtt5_user_property_list_t, mqtt5_user_property);
typedef struct mqtt5_user_property *mqtt5_user_property_item_t;
typedef struct {
uint32_t maximum_packet_size;
uint16_t receive_maximum;
uint16_t topic_alias_maximum;
uint8_t max_qos;
bool retain_available;
bool wildcard_subscribe_available;
bool subscribe_identifiers_available;
bool shared_subscribe_available;
char *response_info;
} esp_mqtt5_connection_server_resp_property_t;
typedef struct {
bool payload_format_indicator;
uint32_t message_expiry_interval;
uint16_t topic_alias;
char *response_topic;
int response_topic_len;
char *correlation_data;
uint16_t correlation_data_len;
char *content_type;
int content_type_len;
uint16_t subscribe_id;
} esp_mqtt5_publish_resp_property_t;
typedef struct {
uint32_t session_expiry_interval;
uint32_t maximum_packet_size;
uint16_t receive_maximum;
uint16_t topic_alias_maximum;
bool request_resp_info;
bool request_problem_info;
mqtt5_user_property_handle_t user_property;
} esp_mqtt5_connection_property_storage_t;
typedef struct {
uint32_t will_delay_interval;
uint32_t message_expiry_interval;
bool payload_format_indicator;
char *content_type;
char *response_topic;
char *correlation_data;
uint16_t correlation_data_len;
mqtt5_user_property_handle_t user_property;
} esp_mqtt5_connection_will_property_storage_t;
#define mqtt5_get_type mqtt_get_type
#define mqtt5_get_dup mqtt_get_dup
#define mqtt5_set_dup mqtt_set_dup
#define mqtt5_get_qos mqtt_get_qos
#define mqtt5_get_retain mqtt_get_retain
#define mqtt5_msg_init mqtt_msg_init
#define mqtt5_get_total_length mqtt_get_total_length
#define mqtt5_has_valid_msg_hdr mqtt_has_valid_msg_hdr
#define mqtt5_msg_pingreq mqtt_msg_pingreq
#define mqtt5_msg_pingresp mqtt_msg_pingresp
#define mqtt5_get_unsuback_data mqtt5_get_suback_data
#define mqtt5_get_pubcomp_data mqtt5_get_puback_data
uint16_t mqtt5_get_id(uint8_t *buffer, size_t length);
char *mqtt5_get_publish_property_payload(uint8_t *buffer, size_t buffer_length, char **msg_topic, size_t *msg_topic_len, esp_mqtt5_publish_resp_property_t *resp_property, uint16_t *property_len, size_t *payload_len, mqtt5_user_property_handle_t *user_property);
char *mqtt5_get_suback_data(uint8_t *buffer, size_t *length, mqtt5_user_property_handle_t *user_property);
char *mqtt5_get_puback_data(uint8_t *buffer, size_t *length, mqtt5_user_property_handle_t *user_property);
mqtt_message_t *mqtt5_msg_connect(mqtt_connection_t *connection, mqtt_connect_info_t *info, esp_mqtt5_connection_property_storage_t *property, esp_mqtt5_connection_will_property_storage_t *will_property);
mqtt_message_t *mqtt5_msg_publish(mqtt_connection_t *connection, const char *topic, const char *data, int data_length, int qos, int retain, uint16_t *message_id, const esp_mqtt5_publish_property_config_t *property, const char *resp_info);
esp_err_t mqtt5_msg_parse_connack_property(uint8_t *buffer, size_t buffer_len, mqtt_connect_info_t *connection_info, esp_mqtt5_connection_property_storage_t *connection_property, esp_mqtt5_connection_server_resp_property_t *resp_property, int *reason_code, uint8_t *ack_flag, mqtt5_user_property_handle_t *user_property);
int mqtt5_msg_get_reason_code(uint8_t *buffer, size_t length);
mqtt_message_t *mqtt5_msg_subscribe(mqtt_connection_t *connection, const esp_mqtt_topic_t *topic, int size, uint16_t *message_id, const esp_mqtt5_subscribe_property_config_t *property);
mqtt_message_t *mqtt5_msg_unsubscribe(mqtt_connection_t *connection, const char *topic, uint16_t *message_id, const esp_mqtt5_unsubscribe_property_config_t *property);
mqtt_message_t *mqtt5_msg_disconnect(mqtt_connection_t *connection, esp_mqtt5_disconnect_property_config_t *disconnect_property_info);
mqtt_message_t *mqtt5_msg_pubcomp(mqtt_connection_t *connection, uint16_t message_id);
mqtt_message_t *mqtt5_msg_pubrel(mqtt_connection_t *connection, uint16_t message_id);
mqtt_message_t *mqtt5_msg_pubrec(mqtt_connection_t *connection, uint16_t message_id);
mqtt_message_t *mqtt5_msg_puback(mqtt_connection_t *connection, uint16_t message_id);
#ifdef __cplusplus
}
#endif
#endif /* MQTT5_MSG_H */

View File

@@ -0,0 +1,149 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef _MQTT_CLIENT_PRIV_H_
#define _MQTT_CLIENT_PRIV_H_
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdatomic.h>
#include "esp_err.h"
#include "platform.h"
#include "esp_event.h"
#include "mqtt_client.h"
#include "mqtt_msg.h"
#ifdef MQTT_PROTOCOL_5
#include "mqtt5_client_priv.h"
#endif
#include "esp_transport.h"
#include "esp_transport_tcp.h"
#include "esp_transport_ssl.h"
#include "esp_transport_ws.h"
#include "esp_log.h"
#include "mqtt_outbox.h"
#include "freertos/event_groups.h"
#include <errno.h>
#include <string.h>
#include "mqtt_supported_features.h"
/* using uri parser */
#include "http_parser.h"
#ifdef __cplusplus
extern "C" {
#endif
#if CONFIG_NEWLIB_NANO_FORMAT
#define NEWLIB_NANO_COMPAT_FORMAT PRIu32
#define NEWLIB_NANO_COMPAT_CAST(size_t_var) (uint32_t)size_t_var
#else
#define NEWLIB_NANO_COMPAT_FORMAT "zu"
#define NEWLIB_NANO_COMPAT_CAST(size_t_var) size_t_var
#endif
#ifdef MQTT_DISABLE_API_LOCKS
# define MQTT_API_LOCK(c)
# define MQTT_API_UNLOCK(c)
#else
# define MQTT_API_LOCK(c) xSemaphoreTakeRecursive(c->api_lock, portMAX_DELAY)
# define MQTT_API_UNLOCK(c) xSemaphoreGiveRecursive(c->api_lock)
#endif /* MQTT_USE_API_LOCKS */
typedef struct mqtt_state {
uint8_t *in_buffer;
int in_buffer_length;
size_t message_length;
size_t in_buffer_read_len;
mqtt_connection_t connection;
uint16_t pending_msg_id;
int pending_msg_type;
int pending_publish_qos;
} mqtt_state_t;
typedef struct {
esp_event_loop_handle_t event_loop_handle;
int task_stack;
int task_prio;
char *uri;
char *host;
char *path;
char *scheme;
int port;
bool auto_reconnect;
int network_timeout_ms;
int refresh_connection_after_ms;
int reconnect_timeout_ms;
char **alpn_protos;
int num_alpn_protos;
char *clientkey_password;
int clientkey_password_len;
bool use_global_ca_store;
esp_err_t ((*crt_bundle_attach)(void *conf));
const char *cacert_buf;
size_t cacert_bytes;
const char *clientcert_buf;
size_t clientcert_bytes;
const char *clientkey_buf;
size_t clientkey_bytes;
const struct psk_key_hint *psk_hint_key;
bool skip_cert_common_name_check;
const char *common_name;
bool use_secure_element;
void *ds_data;
bool use_ecdsa_peripheral;
uint8_t ecdsa_key_efuse_blk;
int message_retransmit_timeout;
uint64_t outbox_limit;
esp_transport_handle_t transport;
struct ifreq * if_name;
esp_transport_keep_alive_t tcp_keep_alive_cfg;
} mqtt_config_storage_t;
typedef enum {
MQTT_STATE_INIT = 0,
MQTT_STATE_DISCONNECTED,
MQTT_STATE_CONNECTED,
MQTT_STATE_WAIT_RECONNECT,
} mqtt_client_state_t;
struct esp_mqtt_client {
esp_transport_list_handle_t transport_list;
esp_transport_handle_t transport;
mqtt_config_storage_t *config;
mqtt_state_t mqtt_state;
_Atomic mqtt_client_state_t state;
uint64_t refresh_connection_tick;
int64_t keepalive_tick;
uint64_t reconnect_tick;
#ifdef MQTT_PROTOCOL_5
mqtt5_config_storage_t *mqtt5_config;
uint16_t send_publish_packet_count; // This is for MQTT v5.0 flow control
#endif
int wait_timeout_ms;
int auto_reconnect;
esp_mqtt_event_t event;
bool run;
bool wait_for_ping_resp;
outbox_handle_t outbox;
EventGroupHandle_t status_bits;
SemaphoreHandle_t api_lock;
TaskHandle_t task_handle;
#if MQTT_EVENT_QUEUE_SIZE > 1
atomic_int queued_events;
#endif
};
bool esp_mqtt_set_if_config(char const *const new_config, char **old_config);
void esp_mqtt_destroy_config(esp_mqtt_client_handle_t client);
#ifdef __cplusplus
}
#endif //__cplusplus
#endif

View File

@@ -0,0 +1,118 @@
/*
* 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_CONFIG_H_
#define _MQTT_CONFIG_H_
#include "sdkconfig.h"
#ifdef CONFIG_MQTT_PROTOCOL_311
#define MQTT_PROTOCOL_311
#endif
#ifdef CONFIG_MQTT_PROTOCOL_5
#define MQTT_PROTOCOL_5
#endif
#define MQTT_RECON_DEFAULT_MS (10*1000)
#ifdef CONFIG_MQTT_POLL_READ_TIMEOUT_MS
#define MQTT_POLL_READ_TIMEOUT_MS CONFIG_MQTT_POLL_READ_TIMEOUT_MS
#else
#define MQTT_POLL_READ_TIMEOUT_MS (1000)
#endif
#define MQTT_MSG_ID_INCREMENTAL CONFIG_MQTT_MSG_ID_INCREMENTAL
#define MQTT_SKIP_PUBLISH_IF_DISCONNECTED CONFIG_MQTT_SKIP_PUBLISH_IF_DISCONNECTED
#define MQTT_REPORT_DELETED_MESSAGES CONFIG_MQTT_REPORT_DELETED_MESSAGES
#if CONFIG_MQTT_BUFFER_SIZE
#define MQTT_BUFFER_SIZE_BYTE CONFIG_MQTT_BUFFER_SIZE
#else
#define MQTT_BUFFER_SIZE_BYTE 1024
#endif
#if CONFIG_MQTT_TASK_PRIORITY
#define MQTT_TASK_PRIORITY CONFIG_MQTT_TASK_PRIORITY
#else
#define MQTT_TASK_PRIORITY 5
#endif
#if CONFIG_MQTT_TASK_STACK_SIZE
#define MQTT_TASK_STACK CONFIG_MQTT_TASK_STACK_SIZE
#else
#define MQTT_TASK_STACK (6*1024)
#endif
#define MQTT_KEEPALIVE_TICK (120)
#define MQTT_NETWORK_TIMEOUT_MS (10000)
#ifdef CONFIG_MQTT_TCP_DEFAULT_PORT
#define MQTT_TCP_DEFAULT_PORT CONFIG_MQTT_TCP_DEFAULT_PORT
#else
#define MQTT_TCP_DEFAULT_PORT 1883
#endif
#ifdef CONFIG_MQTT_SSL_DEFAULT_PORT
#define MQTT_SSL_DEFAULT_PORT CONFIG_MQTT_SSL_DEFAULT_PORT
#else
#define MQTT_SSL_DEFAULT_PORT 8883
#endif
#ifdef CONFIG_MQTT_WS_DEFAULT_PORT
#define MQTT_WS_DEFAULT_PORT CONFIG_MQTT_WS_DEFAULT_PORT
#else
#define MQTT_WS_DEFAULT_PORT 80
#endif
#ifdef CONFIG_MQTT_WSS_DEFAULT_PORT
#define MQTT_WSS_DEFAULT_PORT CONFIG_MQTT_WSS_DEFAULT_PORT
#else
#define MQTT_WSS_DEFAULT_PORT 443
#endif
#define MQTT_CORE_SELECTION_ENABLED CONFIG_MQTT_TASK_CORE_SELECTION_ENABLED
#ifdef CONFIG_MQTT_DISABLE_API_LOCKS
#define MQTT_DISABLE_API_LOCKS CONFIG_MQTT_DISABLE_API_LOCKS
#endif
#ifdef CONFIG_MQTT_USE_CORE_0
#define MQTT_TASK_CORE 0
#else
#ifdef CONFIG_MQTT_USE_CORE_1
#define MQTT_TASK_CORE 1
#else
#define MQTT_TASK_CORE 0
#endif
#endif
#ifdef CONFIG_MQTT_OUTBOX_EXPIRED_TIMEOUT_MS
#define OUTBOX_EXPIRED_TIMEOUT_MS CONFIG_MQTT_OUTBOX_EXPIRED_TIMEOUT_MS
#else
#define OUTBOX_EXPIRED_TIMEOUT_MS (30*1000)
#endif
#define MQTT_ENABLE_SSL CONFIG_MQTT_TRANSPORT_SSL
#define MQTT_ENABLE_WS CONFIG_MQTT_TRANSPORT_WEBSOCKET
#define MQTT_ENABLE_WSS CONFIG_MQTT_TRANSPORT_WEBSOCKET_SECURE
#define MQTT_DEFAULT_RETRANSMIT_TIMEOUT_MS 1000
#ifdef CONFIG_MQTT_EVENT_QUEUE_SIZE
#define MQTT_EVENT_QUEUE_SIZE CONFIG_MQTT_EVENT_QUEUE_SIZE
#else
#define MQTT_EVENT_QUEUE_SIZE 1
#endif
#ifdef CONFIG_MQTT_OUTBOX_DATA_ON_EXTERNAL_MEMORY
#define MQTT_OUTBOX_MEMORY MALLOC_CAP_SPIRAM
#else
#define MQTT_OUTBOX_MEMORY MALLOC_CAP_DEFAULT
#endif
#define OUTBOX_MAX_SIZE (4*1024)
#endif

View File

@@ -0,0 +1,152 @@
#ifndef MQTT_MSG_H
#define MQTT_MSG_H
#include <stdint.h>
#include <stdbool.h>
#include "mqtt_config.h"
#include "mqtt_client.h"
#ifdef __cplusplus
extern "C" {
#endif
/*
* Copyright (c) 2014, Stephen Robinson
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
/* 7 6 5 4 3 2 1 0 */
/*| --- Message Type---- | DUP Flag | QoS Level | Retain | */
/* Remaining Length */
enum mqtt_message_type {
MQTT_MSG_TYPE_CONNECT = 1,
MQTT_MSG_TYPE_CONNACK = 2,
MQTT_MSG_TYPE_PUBLISH = 3,
MQTT_MSG_TYPE_PUBACK = 4,
MQTT_MSG_TYPE_PUBREC = 5,
MQTT_MSG_TYPE_PUBREL = 6,
MQTT_MSG_TYPE_PUBCOMP = 7,
MQTT_MSG_TYPE_SUBSCRIBE = 8,
MQTT_MSG_TYPE_SUBACK = 9,
MQTT_MSG_TYPE_UNSUBSCRIBE = 10,
MQTT_MSG_TYPE_UNSUBACK = 11,
MQTT_MSG_TYPE_PINGREQ = 12,
MQTT_MSG_TYPE_PINGRESP = 13,
MQTT_MSG_TYPE_DISCONNECT = 14
};
typedef struct mqtt_message {
uint8_t *data;
size_t length;
size_t fragmented_msg_total_length; /*!< total len of fragmented messages (zero for all other messages) */
size_t fragmented_msg_data_offset; /*!< data offset of fragmented messages (zero for all other messages) */
} mqtt_message_t;
typedef struct mqtt_connect_info {
char *client_id;
char *username;
char *password;
char *will_topic;
char *will_message;
int64_t keepalive; /*!< keepalive=0 -> keepalive is disabled */
int will_length;
int will_qos;
int will_retain;
int clean_session;
esp_mqtt_protocol_ver_t protocol_ver;
} mqtt_connect_info_t;
typedef struct mqtt_connection {
mqtt_message_t outbound_message;
#if MQTT_MSG_ID_INCREMENTAL
uint16_t last_message_id; /*!< last used id if incremental message id configured */
#endif
uint8_t *buffer;
size_t buffer_length;
mqtt_connect_info_t information;
} mqtt_connection_t;
static inline int mqtt_get_type(const uint8_t *buffer)
{
return (buffer[0] & 0xf0) >> 4;
}
static inline int mqtt_get_connect_session_present(const uint8_t *buffer)
{
return buffer[2] & 0x01;
}
static inline int mqtt_get_connect_return_code(const uint8_t *buffer)
{
return buffer[3];
}
static inline int mqtt_get_dup(const uint8_t *buffer)
{
return (buffer[0] & 0x08) >> 3;
}
static inline void mqtt_set_dup(uint8_t *buffer)
{
buffer[0] |= 0x08;
}
static inline int mqtt_get_qos(const uint8_t *buffer)
{
return (buffer[0] & 0x06) >> 1;
}
static inline int mqtt_get_retain(const uint8_t *buffer)
{
return (buffer[0] & 0x01);
}
bool mqtt_header_complete(uint8_t *buffer, size_t buffer_length);
size_t mqtt_get_total_length(const uint8_t *buffer, size_t length, int *fixed_size_len);
char *mqtt_get_publish_topic(uint8_t *buffer, size_t *length);
char *mqtt_get_publish_data(uint8_t *buffer, size_t *length);
char *mqtt_get_suback_data(uint8_t *buffer, size_t *length);
uint16_t mqtt_get_id(uint8_t *buffer, size_t length);
int mqtt_has_valid_msg_hdr(uint8_t *buffer, size_t length);
esp_err_t mqtt_msg_buffer_init(mqtt_connection_t *connection, int buffer_size);
void mqtt_msg_buffer_destroy(mqtt_connection_t *connection);
mqtt_message_t *mqtt_msg_connect(mqtt_connection_t *connection, mqtt_connect_info_t *info);
mqtt_message_t *mqtt_msg_publish(mqtt_connection_t *connection, const char *topic, const char *data, int data_length, int qos, int retain, uint16_t *message_id);
mqtt_message_t *mqtt_msg_puback(mqtt_connection_t *connection, uint16_t message_id);
mqtt_message_t *mqtt_msg_pubrec(mqtt_connection_t *connection, uint16_t message_id);
mqtt_message_t *mqtt_msg_pubrel(mqtt_connection_t *connection, uint16_t message_id);
mqtt_message_t *mqtt_msg_pubcomp(mqtt_connection_t *connection, uint16_t message_id);
mqtt_message_t *mqtt_msg_subscribe(mqtt_connection_t *connection, const esp_mqtt_topic_t topic_list[], int size, uint16_t *message_id) __attribute__((nonnull));
mqtt_message_t *mqtt_msg_unsubscribe(mqtt_connection_t *connection, const char *topic, uint16_t *message_id);
mqtt_message_t *mqtt_msg_pingreq(mqtt_connection_t *connection);
mqtt_message_t *mqtt_msg_pingresp(mqtt_connection_t *connection);
mqtt_message_t *mqtt_msg_disconnect(mqtt_connection_t *connection);
#ifdef __cplusplus
}
#endif
#endif /* MQTT_MSG_H */

View File

@@ -0,0 +1,64 @@
/*
* 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"
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
struct outbox_item;
typedef struct outbox_t *outbox_handle_t;
typedef struct outbox_item *outbox_item_handle_t;
typedef struct outbox_message *outbox_message_handle_t;
typedef long long outbox_tick_t;
typedef struct outbox_message {
uint8_t *data;
int len;
int msg_id;
int msg_qos;
int msg_type;
uint8_t *remaining_data;
int remaining_len;
} outbox_message_t;
typedef enum pending_state {
QUEUED,
TRANSMITTED,
ACKNOWLEDGED,
CONFIRMED
} pending_state_t;
outbox_handle_t outbox_init(void);
outbox_item_handle_t outbox_enqueue(outbox_handle_t outbox, outbox_message_handle_t message, outbox_tick_t tick);
outbox_item_handle_t outbox_dequeue(outbox_handle_t outbox, pending_state_t pending, outbox_tick_t *tick);
outbox_item_handle_t outbox_get(outbox_handle_t outbox, int msg_id);
uint8_t *outbox_item_get_data(outbox_item_handle_t item, size_t *len, uint16_t *msg_id, int *msg_type, int *qos);
esp_err_t outbox_delete(outbox_handle_t outbox, int msg_id, int msg_type);
esp_err_t outbox_delete_item(outbox_handle_t outbox, outbox_item_handle_t item);
int outbox_delete_expired(outbox_handle_t outbox, outbox_tick_t current_tick, outbox_tick_t timeout);
/**
* @brief Deletes single expired message returning it's message id
*
* @return msg id of the deleted message, -1 if no expired message in the outbox
*/
int outbox_delete_single_expired(outbox_handle_t outbox, outbox_tick_t current_tick, outbox_tick_t timeout);
esp_err_t outbox_set_pending(outbox_handle_t outbox, int msg_id, pending_state_t pending);
pending_state_t outbox_item_get_pending(outbox_item_handle_t item);
esp_err_t outbox_set_tick(outbox_handle_t outbox, int msg_id, outbox_tick_t tick);
uint64_t outbox_get_size(outbox_handle_t outbox);
void outbox_destroy(outbox_handle_t outbox);
void outbox_delete_all_items(outbox_handle_t outbox);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,14 @@
/*
* 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
#ifdef ESP_PLATFORM
#include "platform_esp32_idf.h"
#endif
#endif

View File

@@ -0,0 +1,29 @@
/*
* 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 _ESP_PLATFORM_H__
#define _ESP_PLATFORM_H__
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include <stdint.h>
#include <sys/time.h>
char *platform_create_id_string(void);
int platform_random(int max);
uint64_t platform_tick_get_ms(void);
#define ESP_MEM_CHECK(TAG, a, action) if (!(a)) { \
ESP_LOGE(TAG,"%s(%d): %s", __FUNCTION__, __LINE__, "Memory exhausted"); \
action; \
}
#define ESP_OK_CHECK(TAG, a, action) if ((a) != ESP_OK) { \
ESP_LOGE(TAG,"%s(%d): %s", __FUNCTION__, __LINE__, "Failed"); \
action; \
}
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,637 @@
/*
* Copyright (c) 2014, Stephen Robinson
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
#include <string.h>
#include "mqtt_client.h"
#include "mqtt_msg.h"
#include "mqtt_config.h"
#include "platform.h"
#define MQTT_MAX_FIXED_HEADER_SIZE 5
#define MQTT_3_1_VARIABLE_HEADER_SIZE 12
#define MQTT_3_1_1_VARIABLE_HEADER_SIZE 10
enum mqtt_connect_flag {
MQTT_CONNECT_FLAG_USERNAME = 1 << 7,
MQTT_CONNECT_FLAG_PASSWORD = 1 << 6,
MQTT_CONNECT_FLAG_WILL_RETAIN = 1 << 5,
MQTT_CONNECT_FLAG_WILL = 1 << 2,
MQTT_CONNECT_FLAG_CLEAN_SESSION = 1 << 1
};
static int append_string(mqtt_connection_t *connection, const char *string, int len)
{
if (connection->outbound_message.length + len + 2 > connection->buffer_length) {
return -1;
}
connection->buffer[connection->outbound_message.length++] = len >> 8;
connection->buffer[connection->outbound_message.length++] = len & 0xff;
memcpy(connection->buffer + connection->outbound_message.length, string, len);
connection->outbound_message.length += len;
return len + 2;
}
static uint16_t append_message_id(mqtt_connection_t *connection, uint16_t message_id)
{
// If message_id is zero then we should assign one, otherwise
// we'll use the one supplied by the caller
while (message_id == 0) {
#if MQTT_MSG_ID_INCREMENTAL
message_id = ++connection->last_message_id;
#else
message_id = platform_random(65535);
#endif
}
if (connection->outbound_message.length + 2 > connection->buffer_length) {
return 0;
}
connection->buffer[connection->outbound_message.length++] = message_id >> 8;
connection->buffer[connection->outbound_message.length++] = message_id & 0xff;
return message_id;
}
static int set_message_header_size(mqtt_connection_t *connection)
{
connection->outbound_message.length = MQTT_MAX_FIXED_HEADER_SIZE;
return MQTT_MAX_FIXED_HEADER_SIZE;
}
static mqtt_message_t *fail_message(mqtt_connection_t *connection)
{
connection->outbound_message.data = connection->buffer;
connection->outbound_message.length = 0;
return &connection->outbound_message;
}
static mqtt_message_t *fini_message(mqtt_connection_t *connection, int type, int dup, int qos, int retain)
{
int message_length = connection->outbound_message.length - MQTT_MAX_FIXED_HEADER_SIZE;
int total_length = message_length;
int encoded_length = 0;
uint8_t encoded_lens[4] = {0};
// Check if we have fragmented message and update total_len
if (connection->outbound_message.fragmented_msg_total_length) {
total_length = connection->outbound_message.fragmented_msg_total_length - MQTT_MAX_FIXED_HEADER_SIZE;
}
// Encode MQTT message length
int len_bytes = 0; // size of encoded message length
do {
encoded_length = total_length % 128;
total_length /= 128;
if (total_length > 0) {
encoded_length |= 0x80;
}
encoded_lens[len_bytes] = encoded_length;
len_bytes++;
} while (total_length > 0);
// Sanity check for MQTT header
if (len_bytes + 1 > MQTT_MAX_FIXED_HEADER_SIZE) {
return fail_message(connection);
}
// Save the header bytes
connection->outbound_message.length = message_length + len_bytes + 1; // msg len + encoded_size len + type (1 byte)
int offs = MQTT_MAX_FIXED_HEADER_SIZE - 1 - len_bytes;
connection->outbound_message.data = connection->buffer + offs;
connection->outbound_message.fragmented_msg_data_offset -= offs;
// type byte
connection->buffer[offs++] = ((type & 0x0f) << 4) | ((dup & 1) << 3) | ((qos & 3) << 1) | (retain & 1);
// length bytes
for (int j = 0; j < len_bytes; j++) {
connection->buffer[offs++] = encoded_lens[j];
}
return &connection->outbound_message;
}
size_t mqtt_get_total_length(const uint8_t *buffer, size_t length, int *fixed_size_len)
{
int i;
size_t totlen = 0;
for (i = 1; i < length; ++i) {
totlen += (buffer[i] & 0x7f) << (7 * (i - 1));
if ((buffer[i] & 0x80) == 0) {
++i;
break;
}
}
totlen += i;
if (fixed_size_len) {
*fixed_size_len = i;
}
return totlen;
}
bool mqtt_header_complete(uint8_t *buffer, size_t buffer_length)
{
uint16_t i;
uint16_t topiclen;
for (i = 1; i < MQTT_MAX_FIXED_HEADER_SIZE; ++i) {
if (i >= buffer_length) {
return false;
}
if ((buffer[i] & 0x80) == 0) {
++i;
break;
}
}
// i is now the length of the fixed header
if (i + 2 >= buffer_length) {
return false;
}
topiclen = buffer[i++] << 8;
topiclen |= buffer[i++];
i += topiclen;
if (mqtt_get_qos(buffer) > 0) {
i += 2;
}
// i is now the length of the fixed + variable header
return buffer_length >= i;
}
char *mqtt_get_publish_topic(uint8_t *buffer, size_t *length)
{
int i;
int topiclen;
for (i = 1; i < *length; ++i) {
if ((buffer[i] & 0x80) == 0) {
++i;
break;
}
}
if (i + 2 >= *length) {
return NULL;
}
topiclen = buffer[i++] << 8;
topiclen |= buffer[i++];
if (i + topiclen > *length) {
return NULL;
}
*length = topiclen;
return (char *)(buffer + i);
}
char *mqtt_get_publish_data(uint8_t *buffer, size_t *length)
{
int i;
int totlen = 0;
int topiclen;
int blength = *length;
*length = 0;
for (i = 1; i < blength; ++i) {
totlen += (buffer[i] & 0x7f) << (7 * (i - 1));
if ((buffer[i] & 0x80) == 0) {
++i;
break;
}
}
totlen += i;
if (i + 2 >= blength) {
return NULL;
}
topiclen = buffer[i++] << 8;
topiclen |= buffer[i++];
if (i + topiclen >= blength) {
return NULL;
}
i += topiclen;
if (mqtt_get_qos(buffer) > 0) {
if (i + 2 >= blength) {
return NULL;
}
i += 2;
}
if (totlen < i) {
return NULL;
}
if (totlen <= blength) {
*length = totlen - i;
} else {
*length = blength - i;
}
return (char *)(buffer + i);
}
char *mqtt_get_suback_data(uint8_t *buffer, size_t *length)
{
// SUBACK payload length = total length - (fixed header (2 bytes) + variable header (2 bytes))
// This requires the remaining length to be encoded in 1 byte.
if (*length > 4) {
*length -= 4;
return (char *)(buffer + 4);
}
*length = 0;
return NULL;
}
uint16_t mqtt_get_id(uint8_t *buffer, size_t length)
{
if (length < 1) {
return 0;
}
switch (mqtt_get_type(buffer)) {
case MQTT_MSG_TYPE_PUBLISH: {
int i;
int topiclen;
for (i = 1; i < length; ++i) {
if ((buffer[i] & 0x80) == 0) {
++i;
break;
}
}
if (i + 2 >= length) {
return 0;
}
topiclen = buffer[i++] << 8;
topiclen |= buffer[i++];
if (i + topiclen > length) {
return 0;
}
i += topiclen;
if (mqtt_get_qos(buffer) > 0) {
if (i + 2 > length) {
return 0;
}
//i += 2;
} else {
return 0;
}
return (buffer[i] << 8) | buffer[i + 1];
}
case MQTT_MSG_TYPE_PUBACK:
case MQTT_MSG_TYPE_PUBREC:
case MQTT_MSG_TYPE_PUBREL:
case MQTT_MSG_TYPE_PUBCOMP:
case MQTT_MSG_TYPE_SUBACK:
case MQTT_MSG_TYPE_UNSUBACK:
case MQTT_MSG_TYPE_SUBSCRIBE:
case MQTT_MSG_TYPE_UNSUBSCRIBE: {
// This requires the remaining length to be encoded in 1 byte,
// which it should be.
if (length >= 4 && (buffer[1] & 0x80) == 0) {
return (buffer[2] << 8) | buffer[3];
} else {
return 0;
}
}
default:
return 0;
}
}
mqtt_message_t *mqtt_msg_connect(mqtt_connection_t *connection, mqtt_connect_info_t *info)
{
set_message_header_size(connection);
int header_len;
if (info->protocol_ver == MQTT_PROTOCOL_V_3_1) {
header_len = MQTT_3_1_VARIABLE_HEADER_SIZE;
} else {
header_len = MQTT_3_1_1_VARIABLE_HEADER_SIZE;
}
if (connection->outbound_message.length + header_len > connection->buffer_length) {
return fail_message(connection);
}
char *variable_header = (char *)(connection->buffer + connection->outbound_message.length);
connection->outbound_message.length += header_len;
int header_idx = 0;
variable_header[header_idx++] = 0; // Variable header length MSB
if (info->protocol_ver == MQTT_PROTOCOL_V_3_1) {
variable_header[header_idx++] = 6; // Variable header length LSB
memcpy(&variable_header[header_idx], "MQIsdp", 6); // Protocol name
header_idx = header_idx + 6;
variable_header[header_idx++] = 3; // Protocol version
} else {
/* Defaults to protocol version 3.1.1 values */
variable_header[header_idx++] = 4; // Variable header length LSB
memcpy(&variable_header[header_idx], "MQTT", 4); // Protocol name
header_idx = header_idx + 4;
variable_header[header_idx++] = 4; // Protocol version
}
int flags_offset = header_idx;
variable_header[header_idx++] = 0; // Flags
variable_header[header_idx++] = info->keepalive >> 8; // Keep-alive MSB
variable_header[header_idx] = info->keepalive & 0xff; // Keep-alive LSB
if (info->clean_session) {
variable_header[flags_offset] |= MQTT_CONNECT_FLAG_CLEAN_SESSION;
}
if (info->client_id != NULL && info->client_id[0] != '\0') {
if (append_string(connection, info->client_id, strlen(info->client_id)) < 0) {
return fail_message(connection);
}
} else {
if (append_string(connection, "", 0) < 0) {
return fail_message(connection);
}
}
if (info->will_topic != NULL && info->will_topic[0] != '\0') {
if (append_string(connection, info->will_topic, strlen(info->will_topic)) < 0) {
return fail_message(connection);
}
if (append_string(connection, info->will_message, info->will_length) < 0) {
return fail_message(connection);
}
variable_header[flags_offset] |= MQTT_CONNECT_FLAG_WILL;
if (info->will_retain) {
variable_header[flags_offset] |= MQTT_CONNECT_FLAG_WILL_RETAIN;
}
variable_header[flags_offset] |= (info->will_qos & 3) << 3;
}
if (info->username != NULL && info->username[0] != '\0') {
if (append_string(connection, info->username, strlen(info->username)) < 0) {
return fail_message(connection);
}
variable_header[flags_offset] |= MQTT_CONNECT_FLAG_USERNAME;
}
if (info->password != NULL && info->password[0] != '\0') {
if (info->username == NULL || info->username[0] == '\0') {
/* In case if password is set without username, we need to set a zero length username.
* (otherwise we violate: MQTT-3.1.2-22: If the User Name Flag is set to 0 then the Password Flag MUST be set to 0.)
*/
if (append_string(connection, "", 0) < 0) {
return fail_message(connection);
}
variable_header[flags_offset] |= MQTT_CONNECT_FLAG_USERNAME;
}
if (append_string(connection, info->password, strlen(info->password)) < 0) {
return fail_message(connection);
}
variable_header[flags_offset] |= MQTT_CONNECT_FLAG_PASSWORD;
}
return fini_message(connection, MQTT_MSG_TYPE_CONNECT, 0, 0, 0);
}
mqtt_message_t *mqtt_msg_publish(mqtt_connection_t *connection, const char *topic, const char *data, int data_length, int qos, int retain, uint16_t *message_id)
{
set_message_header_size(connection);
if (topic == NULL || topic[0] == '\0') {
return fail_message(connection);
}
if (append_string(connection, topic, strlen(topic)) < 0) {
return fail_message(connection);
}
if (data == NULL && data_length > 0) {
return fail_message(connection);
}
if (qos > 0) {
if ((*message_id = append_message_id(connection, 0)) == 0) {
return fail_message(connection);
}
} else {
*message_id = 0;
}
if (data != NULL) {
if (connection->outbound_message.length + data_length > connection->buffer_length) {
// Not enough size in buffer -> fragment this message
connection->outbound_message.fragmented_msg_data_offset = connection->outbound_message.length;
memcpy(connection->buffer + connection->outbound_message.length, data, connection->buffer_length - connection->outbound_message.length);
connection->outbound_message.length = connection->buffer_length;
connection->outbound_message.fragmented_msg_total_length = data_length + connection->outbound_message.fragmented_msg_data_offset;
} else {
memcpy(connection->buffer + connection->outbound_message.length, data, data_length);
connection->outbound_message.length += data_length;
connection->outbound_message.fragmented_msg_total_length = 0;
}
}
return fini_message(connection, MQTT_MSG_TYPE_PUBLISH, 0, qos, retain);
}
mqtt_message_t *mqtt_msg_puback(mqtt_connection_t *connection, uint16_t message_id)
{
set_message_header_size(connection);
if (append_message_id(connection, message_id) == 0) {
return fail_message(connection);
}
return fini_message(connection, MQTT_MSG_TYPE_PUBACK, 0, 0, 0);
}
mqtt_message_t *mqtt_msg_pubrec(mqtt_connection_t *connection, uint16_t message_id)
{
set_message_header_size(connection);
if (append_message_id(connection, message_id) == 0) {
return fail_message(connection);
}
return fini_message(connection, MQTT_MSG_TYPE_PUBREC, 0, 0, 0);
}
mqtt_message_t *mqtt_msg_pubrel(mqtt_connection_t *connection, uint16_t message_id)
{
set_message_header_size(connection);
if (append_message_id(connection, message_id) == 0) {
return fail_message(connection);
}
return fini_message(connection, MQTT_MSG_TYPE_PUBREL, 0, 1, 0);
}
mqtt_message_t *mqtt_msg_pubcomp(mqtt_connection_t *connection, uint16_t message_id)
{
set_message_header_size(connection);
if (append_message_id(connection, message_id) == 0) {
return fail_message(connection);
}
return fini_message(connection, MQTT_MSG_TYPE_PUBCOMP, 0, 0, 0);
}
mqtt_message_t *mqtt_msg_subscribe(mqtt_connection_t *connection, const esp_mqtt_topic_t topic_list[], int size, uint16_t *message_id)
{
set_message_header_size(connection);
if ((*message_id = append_message_id(connection, 0)) == 0) {
return fail_message(connection);
}
for (int topic_number = 0; topic_number < size; ++topic_number) {
if (topic_list[topic_number].filter[0] == '\0') {
return fail_message(connection);
}
if (append_string(connection, topic_list[topic_number].filter, strlen(topic_list[topic_number].filter)) < 0) {
return fail_message(connection);
}
if (connection->outbound_message.length + 1 > connection->buffer_length) {
return fail_message(connection);
}
connection->buffer[connection->outbound_message.length] = topic_list[topic_number].qos;
connection->outbound_message.length ++;
}
return fini_message(connection, MQTT_MSG_TYPE_SUBSCRIBE, 0, 1, 0);
}
mqtt_message_t *mqtt_msg_unsubscribe(mqtt_connection_t *connection, const char *topic, uint16_t *message_id)
{
set_message_header_size(connection);
if (topic == NULL || topic[0] == '\0') {
return fail_message(connection);
}
if ((*message_id = append_message_id(connection, 0)) == 0) {
return fail_message(connection);
}
if (append_string(connection, topic, strlen(topic)) < 0) {
return fail_message(connection);
}
return fini_message(connection, MQTT_MSG_TYPE_UNSUBSCRIBE, 0, 1, 0);
}
mqtt_message_t *mqtt_msg_pingreq(mqtt_connection_t *connection)
{
set_message_header_size(connection);
return fini_message(connection, MQTT_MSG_TYPE_PINGREQ, 0, 0, 0);
}
mqtt_message_t *mqtt_msg_pingresp(mqtt_connection_t *connection)
{
set_message_header_size(connection);
return fini_message(connection, MQTT_MSG_TYPE_PINGRESP, 0, 0, 0);
}
mqtt_message_t *mqtt_msg_disconnect(mqtt_connection_t *connection)
{
set_message_header_size(connection);
return fini_message(connection, MQTT_MSG_TYPE_DISCONNECT, 0, 0, 0);
}
/*
* check flags: [MQTT-2.2.2-1], [MQTT-2.2.2-2]
* returns 0 if flags are invalid, otherwise returns 1
*/
int mqtt_has_valid_msg_hdr(uint8_t *buffer, size_t length)
{
int qos, dup;
if (length < 1) {
return 0;
}
switch (mqtt_get_type(buffer)) {
case MQTT_MSG_TYPE_CONNECT:
case MQTT_MSG_TYPE_CONNACK:
case MQTT_MSG_TYPE_PUBACK:
case MQTT_MSG_TYPE_PUBREC:
case MQTT_MSG_TYPE_PUBCOMP:
case MQTT_MSG_TYPE_SUBACK:
case MQTT_MSG_TYPE_UNSUBACK:
case MQTT_MSG_TYPE_PINGREQ:
case MQTT_MSG_TYPE_PINGRESP:
case MQTT_MSG_TYPE_DISCONNECT:
return (buffer[0] & 0x0f) == 0; /* all flag bits are 0 */
case MQTT_MSG_TYPE_PUBREL:
case MQTT_MSG_TYPE_SUBSCRIBE:
case MQTT_MSG_TYPE_UNSUBSCRIBE:
return (buffer[0] & 0x0f) == 0x02; /* only bit 1 is set */
case MQTT_MSG_TYPE_PUBLISH:
qos = mqtt_get_qos(buffer);
dup = mqtt_get_dup(buffer);
/*
* there is no qos=3 [MQTT-3.3.1-4]
* dup flag must be set to 0 for all qos=0 messages [MQTT-3.3.1-2]
*/
return (qos < 3) && ((qos > 0) || (dup == 0));
default:
return 0;
}
}
esp_err_t mqtt_msg_buffer_init(mqtt_connection_t *connection, int buffer_size)
{
memset(&connection->outbound_message, 0, sizeof(mqtt_message_t));
connection->buffer = (uint8_t *)calloc(buffer_size, sizeof(uint8_t));
if (!connection->buffer) {
return ESP_ERR_NO_MEM;
}
connection->buffer_length = buffer_size;
return ESP_OK;
}
void mqtt_msg_buffer_destroy(mqtt_connection_t *connection)
{
if (connection) {
free(connection->buffer);
}
}

View File

@@ -0,0 +1,221 @@
#include "mqtt_outbox.h"
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "mqtt_config.h"
#include "sys/queue.h"
#include "esp_heap_caps.h"
#include "esp_log.h"
#ifndef CONFIG_MQTT_CUSTOM_OUTBOX
static const char *TAG = "outbox";
typedef struct outbox_item {
char *buffer;
int len;
int msg_id;
int msg_type;
int msg_qos;
outbox_tick_t tick;
pending_state_t pending;
STAILQ_ENTRY(outbox_item) next;
} outbox_item_t;
STAILQ_HEAD(outbox_list_t, outbox_item);
struct outbox_t {
_Atomic uint64_t size;
struct outbox_list_t *list;
};
outbox_handle_t outbox_init(void)
{
outbox_handle_t outbox = calloc(1, sizeof(struct outbox_t));
ESP_MEM_CHECK(TAG, outbox, return NULL);
outbox->list = calloc(1, sizeof(struct outbox_list_t));
ESP_MEM_CHECK(TAG, outbox->list, {free(outbox); return NULL;});
outbox->size = 0;
STAILQ_INIT(outbox->list);
return outbox;
}
outbox_item_handle_t outbox_enqueue(outbox_handle_t outbox, outbox_message_handle_t message, outbox_tick_t tick)
{
outbox_item_handle_t item = calloc(1, sizeof(outbox_item_t));
ESP_MEM_CHECK(TAG, item, return NULL);
item->msg_id = message->msg_id;
item->msg_type = message->msg_type;
item->msg_qos = message->msg_qos;
item->tick = tick;
item->len = message->len + message->remaining_len;
item->pending = QUEUED;
item->buffer = heap_caps_malloc(message->len + message->remaining_len, MQTT_OUTBOX_MEMORY);
ESP_MEM_CHECK(TAG, item->buffer, {
free(item);
return NULL;
});
memcpy(item->buffer, message->data, message->len);
if (message->remaining_data) {
memcpy(item->buffer + message->len, message->remaining_data, message->remaining_len);
}
STAILQ_INSERT_TAIL(outbox->list, item, next);
outbox->size += item->len;
ESP_LOGD(TAG, "ENQUEUE msgid=%d, msg_type=%d, len=%d, size=%"PRIu64, message->msg_id, message->msg_type, message->len + message->remaining_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->list, next) {
if (item->msg_id == msg_id) {
return item;
}
}
return NULL;
}
outbox_item_handle_t outbox_dequeue(outbox_handle_t outbox, pending_state_t pending, outbox_tick_t *tick)
{
outbox_item_handle_t item;
STAILQ_FOREACH(item, outbox->list, next) {
if (item->pending == pending) {
if (tick) {
*tick = item->tick;
}
return item;
}
}
return NULL;
}
esp_err_t outbox_delete_item(outbox_handle_t outbox, outbox_item_handle_t item_to_delete)
{
outbox_item_handle_t item;
STAILQ_FOREACH(item, outbox->list, next) {
if (item == item_to_delete) {
STAILQ_REMOVE(outbox->list, item, outbox_item, next);
outbox->size -= item->len;
free(item->buffer);
free(item);
return ESP_OK;
}
}
return ESP_FAIL;
}
uint8_t *outbox_item_get_data(outbox_item_handle_t item, size_t *len, uint16_t *msg_id, int *msg_type, int *qos)
{
if (item) {
*len = item->len;
*msg_id = item->msg_id;
*msg_type = item->msg_type;
*qos = item->msg_qos;
return (uint8_t *)item->buffer;
}
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->list, next, tmp) {
if (item->msg_id == msg_id && (0xFF & (item->msg_type)) == msg_type) {
STAILQ_REMOVE(outbox->list, item, outbox_item, next);
outbox->size -= item->len;
free(item->buffer);
free(item);
ESP_LOGD(TAG, "DELETED msgid=%d, msg_type=%d, remain size=%"PRIu64, msg_id, msg_type, outbox_get_size(outbox));
return ESP_OK;
}
}
return ESP_FAIL;
}
esp_err_t outbox_set_pending(outbox_handle_t outbox, int msg_id, pending_state_t pending)
{
outbox_item_handle_t item = outbox_get(outbox, msg_id);
if (item) {
item->pending = pending;
return ESP_OK;
}
return ESP_FAIL;
}
pending_state_t outbox_item_get_pending(outbox_item_handle_t item)
{
if (item) {
return item->pending;
}
return QUEUED;
}
esp_err_t outbox_set_tick(outbox_handle_t outbox, int msg_id, outbox_tick_t tick)
{
outbox_item_handle_t item = outbox_get(outbox, msg_id);
if (item) {
item->tick = tick;
return ESP_OK;
}
return ESP_FAIL;
}
int outbox_delete_single_expired(outbox_handle_t outbox, outbox_tick_t current_tick, outbox_tick_t timeout)
{
int msg_id = -1;
outbox_item_handle_t item;
STAILQ_FOREACH(item, outbox->list, next) {
if (current_tick - item->tick > timeout) {
STAILQ_REMOVE(outbox->list, item, outbox_item, next);
free(item->buffer);
outbox->size -= item->len;
msg_id = item->msg_id;
free(item);
return msg_id;
}
}
return msg_id;
}
int outbox_delete_expired(outbox_handle_t outbox, outbox_tick_t current_tick, outbox_tick_t timeout)
{
int deleted_items = 0;
outbox_item_handle_t item, tmp;
STAILQ_FOREACH_SAFE(item, outbox->list, next, tmp) {
if (current_tick - item->tick > timeout) {
STAILQ_REMOVE(outbox->list, item, outbox_item, next);
free(item->buffer);
outbox->size -= item->len;
free(item);
deleted_items ++;
}
}
return deleted_items;
}
uint64_t outbox_get_size(outbox_handle_t outbox)
{
return outbox->size;
}
void outbox_delete_all_items(outbox_handle_t outbox)
{
outbox_item_handle_t item, tmp;
STAILQ_FOREACH_SAFE(item, outbox->list, next, tmp) {
STAILQ_REMOVE(outbox->list, item, outbox_item, next);
outbox->size -= item->len;
free(item->buffer);
free(item);
}
}
void outbox_destroy(outbox_handle_t outbox)
{
outbox_delete_all_items(outbox);
free(outbox->list);
free(outbox);
}
#endif /* CONFIG_MQTT_CUSTOM_OUTBOX */

View File

@@ -0,0 +1,48 @@
#include "platform.h"
#ifdef ESP_PLATFORM
#include "esp_log.h"
#include "esp_mac.h"
#include "soc/soc_caps.h"
#include "esp_timer.h"
#include "esp_random.h"
#include <stdlib.h>
#include <stdint.h>
static const char *TAG = "platform";
#define MAX_ID_STRING (32)
#if defined SOC_WIFI_SUPPORTED
#define MAC_TYPE ESP_MAC_WIFI_STA
#elif defined SOC_EMAC_SUPPORTED
#define MAC_TYPE ESP_MAC_ETH
#elif defined SOC_IEEE802154_SUPPORTED
#define MAC_TYPE ESP_MAC_IEEE802154
#endif
char *platform_create_id_string(void)
{
char *id_string = calloc(1, MAX_ID_STRING);
ESP_MEM_CHECK(TAG, id_string, return NULL);
#ifndef MAC_TYPE
ESP_LOGW(TAG, "Soc doesn't provide MAC, client could be disconnected in case of device with same name in the broker.");
sprintf(id_string, "esp_mqtt_client_id");
#else
uint8_t mac[6];
esp_read_mac(mac, MAC_TYPE);
sprintf(id_string, "ESP32_%02x%02X%02X", mac[3], mac[4], mac[5]);
#endif
return id_string;
}
int platform_random(int max)
{
return esp_random() % max;
}
uint64_t platform_tick_get_ms(void)
{
return esp_timer_get_time()/(int64_t)1000;
}
#endif

View File

@@ -0,0 +1,768 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "mqtt_client_priv.h"
#include "esp_log.h"
#include <string.h>
static const char *TAG = "mqtt5_client";
static void esp_mqtt5_print_error_code(esp_mqtt5_client_handle_t client, int code);
static esp_err_t esp_mqtt5_client_update_topic_alias(mqtt5_topic_alias_handle_t topic_alias_handle, uint16_t topic_alias, char *topic, size_t topic_len);
static char *esp_mqtt5_client_get_topic_alias(mqtt5_topic_alias_handle_t topic_alias_handle, uint16_t topic_alias, size_t *topic_length);
static void esp_mqtt5_client_delete_topic_alias(mqtt5_topic_alias_handle_t topic_alias_handle);
static esp_err_t esp_mqtt5_user_property_copy(mqtt5_user_property_handle_t user_property_new, const mqtt5_user_property_handle_t user_property_old);
void esp_mqtt5_increment_packet_counter(esp_mqtt5_client_handle_t client)
{
bool msg_dup = mqtt5_get_dup(client->mqtt_state.connection.outbound_message.data);
if (msg_dup == false) {
client->send_publish_packet_count ++;
ESP_LOGD(TAG, "Sent (%d) qos > 0 publish packet without ack", client->send_publish_packet_count);
}
}
void esp_mqtt5_decrement_packet_counter(esp_mqtt5_client_handle_t client)
{
if (client->send_publish_packet_count > 0) {
client->send_publish_packet_count --;
ESP_LOGD(TAG, "Receive (%d) qos > 0 publish packet with ack", client->send_publish_packet_count);
}
}
void esp_mqtt5_parse_pubcomp(esp_mqtt5_client_handle_t client)
{
if (client->mqtt_state.connection.information.protocol_ver == MQTT_PROTOCOL_V_5) {
ESP_LOGD(TAG, "MQTT_MSG_TYPE_PUBCOMP return code is %d", mqtt5_msg_get_reason_code(client->mqtt_state.in_buffer, client->mqtt_state.in_buffer_read_len));
size_t msg_data_len = client->mqtt_state.in_buffer_read_len;
client->event.data = mqtt5_get_pubcomp_data(client->mqtt_state.in_buffer, &msg_data_len, &client->event.property->user_property);
client->event.data_len = msg_data_len;
client->event.total_data_len = msg_data_len;
client->event.current_data_offset = 0;
}
}
void esp_mqtt5_parse_puback(esp_mqtt5_client_handle_t client)
{
if (client->mqtt_state.connection.information.protocol_ver == MQTT_PROTOCOL_V_5) {
ESP_LOGD(TAG, "MQTT_MSG_TYPE_PUBACK return code is %d", mqtt5_msg_get_reason_code(client->mqtt_state.in_buffer, client->mqtt_state.in_buffer_read_len));
size_t msg_data_len = client->mqtt_state.in_buffer_read_len;
client->event.data = mqtt5_get_puback_data(client->mqtt_state.in_buffer, &msg_data_len, &client->event.property->user_property);
client->event.data_len = msg_data_len;
client->event.total_data_len = msg_data_len;
client->event.current_data_offset = 0;
}
}
void esp_mqtt5_parse_unsuback(esp_mqtt5_client_handle_t client)
{
if (client->mqtt_state.connection.information.protocol_ver == MQTT_PROTOCOL_V_5) {
ESP_LOGD(TAG, "MQTT_MSG_TYPE_UNSUBACK return code is %d", mqtt5_msg_get_reason_code(client->mqtt_state.in_buffer, client->mqtt_state.in_buffer_read_len));
size_t msg_data_len = client->mqtt_state.in_buffer_read_len;
client->event.data = mqtt5_get_unsuback_data(client->mqtt_state.in_buffer, &msg_data_len, &client->event.property->user_property);
client->event.data_len = msg_data_len;
client->event.total_data_len = msg_data_len;
client->event.current_data_offset = 0;
}
}
void esp_mqtt5_parse_suback(esp_mqtt5_client_handle_t client)
{
if (client->mqtt_state.connection.information.protocol_ver == MQTT_PROTOCOL_V_5) {
ESP_LOGD(TAG, "MQTT_MSG_TYPE_SUBACK return code is %d", mqtt5_msg_get_reason_code(client->mqtt_state.in_buffer, client->mqtt_state.in_buffer_read_len));
}
}
void esp_mqtt5_parse_disconnect(esp_mqtt5_client_handle_t client, int *disconnect_rsp_code)
{
if (client->mqtt_state.connection.information.protocol_ver == MQTT_PROTOCOL_V_5) {
*disconnect_rsp_code = mqtt5_msg_get_reason_code(client->mqtt_state.in_buffer, client->mqtt_state.in_buffer_read_len);
ESP_LOGD(TAG, "MQTT_MSG_TYPE_DISCONNECT return code is %d", *disconnect_rsp_code);
}
}
esp_err_t esp_mqtt5_parse_connack(esp_mqtt5_client_handle_t client, int *connect_rsp_code)
{
size_t len = client->mqtt_state.in_buffer_read_len;
client->mqtt_state.in_buffer_read_len = 0;
uint8_t ack_flag = 0;
if (mqtt5_msg_parse_connack_property(client->mqtt_state.in_buffer, len, &client->mqtt_state.
connection.information, &client->mqtt5_config->connect_property_info, &client->mqtt5_config->server_resp_property_info, connect_rsp_code, &ack_flag, &client->event.property->user_property) != ESP_OK) {
ESP_LOGE(TAG, "Failed to parse CONNACK packet");
return ESP_FAIL;
}
if (*connect_rsp_code == MQTT_CONNECTION_ACCEPTED) {
ESP_LOGD(TAG, "Connected");
client->event.session_present = ack_flag & 0x01;
return ESP_OK;
}
esp_mqtt5_print_error_code(client, *connect_rsp_code);
return ESP_FAIL;
}
esp_err_t esp_mqtt5_get_publish_data(esp_mqtt5_client_handle_t client, uint8_t *msg_buf, size_t msg_read_len, char **msg_topic, size_t *msg_topic_len, char **msg_data, size_t *msg_data_len)
{
// get property
uint16_t property_len = 0;
esp_mqtt5_publish_resp_property_t property = {0};
*msg_data = mqtt5_get_publish_property_payload(msg_buf, msg_read_len, msg_topic, msg_topic_len, &property, &property_len, msg_data_len, &client->event.property->user_property);
if (*msg_data == NULL) {
ESP_LOGE(TAG, "%s: mqtt5_get_publish_property_payload() failed", __func__);
return ESP_FAIL;
}
if (property.topic_alias > client->mqtt5_config->connect_property_info.topic_alias_maximum) {
ESP_LOGE(TAG, "%s: Broker response topic alias %d is over the max topic alias %d", __func__, property.topic_alias, client->mqtt5_config->connect_property_info.topic_alias_maximum);
return ESP_FAIL;
}
if (property.topic_alias) {
if (*msg_topic_len == 0) {
*msg_topic = esp_mqtt5_client_get_topic_alias(client->mqtt5_config->peer_topic_alias, property.topic_alias, msg_topic_len);
if (!*msg_topic) {
ESP_LOGE(TAG, "%s: esp_mqtt5_client_get_topic_alias() failed", __func__);
return ESP_FAIL;
}
} else {
if (esp_mqtt5_client_update_topic_alias(client->mqtt5_config->peer_topic_alias, property.topic_alias, *msg_topic, *msg_topic_len) != ESP_OK) {
ESP_LOGE(TAG, "%s: esp_mqtt5_client_update_topic_alias() failed", __func__);
return ESP_FAIL;
}
}
}
client->event.property->payload_format_indicator = property.payload_format_indicator;
client->event.property->response_topic = property.response_topic;
client->event.property->response_topic_len = property.response_topic_len;
client->event.property->correlation_data = property.correlation_data;
client->event.property->correlation_data_len = property.correlation_data_len;
client->event.property->content_type = property.content_type;
client->event.property->content_type_len = property.content_type_len;
client->event.property->subscribe_id = property.subscribe_id;
return ESP_OK;
}
esp_err_t esp_mqtt5_create_default_config(esp_mqtt5_client_handle_t client)
{
if (client->mqtt_state.connection.information.protocol_ver == MQTT_PROTOCOL_V_5) {
client->event.property = calloc(1, sizeof(esp_mqtt5_event_property_t));
ESP_MEM_CHECK(TAG, client->event.property, return ESP_FAIL)
client->mqtt5_config = calloc(1, sizeof(mqtt5_config_storage_t));
ESP_MEM_CHECK(TAG, client->mqtt5_config, return ESP_FAIL)
client->mqtt5_config->server_resp_property_info.max_qos = 2;
client->mqtt5_config->server_resp_property_info.retain_available = true;
client->mqtt5_config->server_resp_property_info.wildcard_subscribe_available = true;
client->mqtt5_config->server_resp_property_info.subscribe_identifiers_available = true;
client->mqtt5_config->server_resp_property_info.shared_subscribe_available = true;
client->mqtt5_config->server_resp_property_info.receive_maximum = 65535;
}
return ESP_OK;
}
static void esp_mqtt5_print_error_code(esp_mqtt5_client_handle_t client, int code)
{
switch (code) {
case MQTT5_UNSPECIFIED_ERROR:
ESP_LOGW(TAG, "Unspecified error");
break;
case MQTT5_MALFORMED_PACKET:
ESP_LOGW(TAG, "Malformed Packet");
break;
case MQTT5_PROTOCOL_ERROR:
ESP_LOGW(TAG, "Protocol Error");
break;
case MQTT5_IMPLEMENT_SPECIFIC_ERROR:
ESP_LOGW(TAG, "Implementation specific error");
break;
case MQTT5_UNSUPPORTED_PROTOCOL_VER:
ESP_LOGW(TAG, "Unsupported Protocol Version");
break;
case MQTT5_INVALID_CLIENT_ID:
ESP_LOGW(TAG, "Client Identifier not valid");
break;
case MQTT5_BAD_USERNAME_OR_PWD:
ESP_LOGW(TAG, "Bad User Name or Password");
break;
case MQTT5_NOT_AUTHORIZED:
ESP_LOGW(TAG, "Not authorized");
break;
case MQTT5_SERVER_UNAVAILABLE:
ESP_LOGW(TAG, "Server unavailable");
break;
case MQTT5_SERVER_BUSY:
ESP_LOGW(TAG, "Server busy");
break;
case MQTT5_BANNED:
ESP_LOGW(TAG, "Banned");
break;
case MQTT5_SERVER_SHUTTING_DOWN:
ESP_LOGW(TAG, "Server shutting down");
break;
case MQTT5_BAD_AUTH_METHOD:
ESP_LOGW(TAG, "Bad authentication method");
break;
case MQTT5_KEEP_ALIVE_TIMEOUT:
ESP_LOGW(TAG, "Keep Alive timeout");
break;
case MQTT5_SESSION_TAKEN_OVER:
ESP_LOGW(TAG, "Session taken over");
break;
case MQTT5_TOPIC_FILTER_INVALID:
ESP_LOGW(TAG, "Topic Filter invalid");
break;
case MQTT5_TOPIC_NAME_INVALID:
ESP_LOGW(TAG, "Topic Name invalid");
break;
case MQTT5_PACKET_IDENTIFIER_IN_USE:
ESP_LOGW(TAG, "Packet Identifier in use");
break;
case MQTT5_PACKET_IDENTIFIER_NOT_FOUND:
ESP_LOGW(TAG, "Packet Identifier not found");
break;
case MQTT5_RECEIVE_MAXIMUM_EXCEEDED:
ESP_LOGW(TAG, "Receive Maximum exceeded");
break;
case MQTT5_TOPIC_ALIAS_INVALID:
ESP_LOGW(TAG, "Topic Alias invalid");
break;
case MQTT5_PACKET_TOO_LARGE:
ESP_LOGW(TAG, "Packet too large");
break;
case MQTT5_MESSAGE_RATE_TOO_HIGH:
ESP_LOGW(TAG, "Message rate too high");
break;
case MQTT5_QUOTA_EXCEEDED:
ESP_LOGW(TAG, "Quota exceeded");
break;
case MQTT5_ADMINISTRATIVE_ACTION:
ESP_LOGW(TAG, "Administrative action");
break;
case MQTT5_PAYLOAD_FORMAT_INVALID:
ESP_LOGW(TAG, "Payload format invalid");
break;
case MQTT5_RETAIN_NOT_SUPPORT:
ESP_LOGW(TAG, "Retain not supported");
break;
case MQTT5_QOS_NOT_SUPPORT:
ESP_LOGW(TAG, "QoS not supported");
break;
case MQTT5_USE_ANOTHER_SERVER:
ESP_LOGW(TAG, "Use another server");
break;
case MQTT5_SERVER_MOVED:
ESP_LOGW(TAG, "Server moved");
break;
case MQTT5_SHARED_SUBSCR_NOT_SUPPORTED:
ESP_LOGW(TAG, "Shared Subscriptions not supported");
break;
case MQTT5_CONNECTION_RATE_EXCEEDED:
ESP_LOGW(TAG, "Connection rate exceeded");
break;
case MQTT5_MAXIMUM_CONNECT_TIME:
ESP_LOGW(TAG, "Maximum connect time");
break;
case MQTT5_SUBSCRIBE_IDENTIFIER_NOT_SUPPORT:
ESP_LOGW(TAG, "Subscription Identifiers not supported");
break;
case MQTT5_WILDCARD_SUBSCRIBE_NOT_SUPPORT:
ESP_LOGW(TAG, "Wildcard Subscriptions not supported");
break;
default:
ESP_LOGW(TAG, "Connection refused, Unknow reason");
break;
}
}
esp_err_t esp_mqtt5_client_subscribe_check(esp_mqtt5_client_handle_t client, int qos)
{
/* Check Server support QoS level */
if (client->mqtt5_config->server_resp_property_info.max_qos < qos) {
ESP_LOGE(TAG, "Server only support max QoS level %d", client->mqtt5_config->server_resp_property_info.max_qos);
return ESP_FAIL;
}
return ESP_OK;
}
esp_err_t esp_mqtt5_client_publish_check(esp_mqtt5_client_handle_t client, int qos, int retain)
{
/* Check Server support QoS level */
if (client->mqtt5_config->server_resp_property_info.max_qos < qos) {
ESP_LOGE(TAG, "Server only support max QoS level %d", client->mqtt5_config->server_resp_property_info.max_qos);
return ESP_FAIL;
}
/* Check Server support RETAIN */
if (!client->mqtt5_config->server_resp_property_info.retain_available && retain) {
ESP_LOGE(TAG, "Server not support retain");
return ESP_FAIL;
}
/* Flow control to check PUBLISH(No PUBACK or PUBCOMP received) packet sent count(Only record QoS1 and QoS2)*/
if (client->send_publish_packet_count > client->mqtt5_config->server_resp_property_info.receive_maximum) {
ESP_LOGE(TAG, "Client send more than %d QoS1 and QoS2 PUBLISH packet without no ack", client->mqtt5_config->server_resp_property_info.receive_maximum);
return ESP_FAIL;
}
return ESP_OK;
}
void esp_mqtt5_client_destory(esp_mqtt5_client_handle_t client)
{
if (client->mqtt_state.connection.information.protocol_ver == MQTT_PROTOCOL_V_5) {
if (client->mqtt5_config) {
free(client->mqtt5_config->will_property_info.content_type);
free(client->mqtt5_config->will_property_info.response_topic);
free(client->mqtt5_config->will_property_info.correlation_data);
free(client->mqtt5_config->server_resp_property_info.response_info);
esp_mqtt5_client_delete_topic_alias(client->mqtt5_config->peer_topic_alias);
esp_mqtt5_client_delete_user_property(client->mqtt5_config->connect_property_info.user_property);
esp_mqtt5_client_delete_user_property(client->mqtt5_config->will_property_info.user_property);
esp_mqtt5_client_delete_user_property(client->mqtt5_config->disconnect_property_info.user_property);
free(client->mqtt5_config);
}
free(client->event.property);
}
}
static void esp_mqtt5_client_delete_topic_alias(mqtt5_topic_alias_handle_t topic_alias_handle)
{
if (topic_alias_handle) {
mqtt5_topic_alias_item_t item, tmp;
STAILQ_FOREACH_SAFE(item, topic_alias_handle, next, tmp) {
STAILQ_REMOVE(topic_alias_handle, item, mqtt5_topic_alias, next);
free(item->topic);
free(item);
}
free(topic_alias_handle);
}
}
static esp_err_t esp_mqtt5_client_update_topic_alias(mqtt5_topic_alias_handle_t topic_alias_handle, uint16_t topic_alias, char *topic, size_t topic_len)
{
mqtt5_topic_alias_item_t item;
bool found = false;
STAILQ_FOREACH(item, topic_alias_handle, next) {
if (item->topic_alias == topic_alias) {
found = true;
break;
}
}
if (found) {
if ((item->topic_len != topic_len) || strncmp(topic, item->topic, topic_len)) {
free(item->topic);
item->topic = calloc(1, topic_len);
ESP_MEM_CHECK(TAG, item->topic, return ESP_FAIL);
memcpy(item->topic, topic, topic_len);
item->topic_len = topic_len;
}
} else {
item = calloc(1, sizeof(mqtt5_topic_alias_t));
ESP_MEM_CHECK(TAG, item, return ESP_FAIL);
item->topic_alias = topic_alias;
item->topic_len = topic_len;
item->topic = calloc(1, topic_len);
ESP_MEM_CHECK(TAG, item->topic, {
free(item);
return ESP_FAIL;
});
memcpy(item->topic, topic, topic_len);
STAILQ_INSERT_TAIL(topic_alias_handle, item, next);
}
return ESP_OK;
}
static char *esp_mqtt5_client_get_topic_alias(mqtt5_topic_alias_handle_t topic_alias_handle, uint16_t topic_alias, size_t *topic_length)
{
mqtt5_topic_alias_item_t item;
STAILQ_FOREACH(item, topic_alias_handle, next) {
if (item->topic_alias == topic_alias) {
*topic_length = item->topic_len;
return item->topic;
}
}
*topic_length = 0;
return NULL;
}
static esp_err_t esp_mqtt5_user_property_copy(mqtt5_user_property_handle_t user_property_new, const mqtt5_user_property_handle_t user_property_old)
{
if (!user_property_new || !user_property_old) {
ESP_LOGE(TAG, "Input is NULL");
return ESP_FAIL;
}
mqtt5_user_property_item_t old_item, new_item;
STAILQ_FOREACH(old_item, user_property_old, next) {
new_item = calloc(1, sizeof(mqtt5_user_property_t));
ESP_MEM_CHECK(TAG, new_item, return ESP_FAIL);
new_item->key = strdup(old_item->key);
ESP_MEM_CHECK(TAG, new_item->key, {
free(new_item);
return ESP_FAIL;
});
new_item->value = strdup(old_item->value);
ESP_MEM_CHECK(TAG, new_item->value, {
free(new_item->key);
free(new_item);
return ESP_FAIL;
});
STAILQ_INSERT_TAIL(user_property_new, new_item, next);
}
return ESP_OK;
}
esp_err_t esp_mqtt5_client_set_publish_property(esp_mqtt5_client_handle_t client, const esp_mqtt5_publish_property_config_t *property)
{
if (!client) {
ESP_LOGE(TAG, "Client was not initialized");
return ESP_ERR_INVALID_ARG;
}
MQTT_API_LOCK(client);
/* Check protocol version */
if (client->mqtt_state.connection.information.protocol_ver != MQTT_PROTOCOL_V_5) {
ESP_LOGE(TAG, "MQTT protocol version is not v5");
MQTT_API_UNLOCK(client);
return ESP_FAIL;
}
/* Check topic alias less than server maximum topic alias */
if (property->topic_alias > client->mqtt5_config->server_resp_property_info.topic_alias_maximum) {
ESP_LOGE(TAG, "Topic alias %d is bigger than server support %d", property->topic_alias, client->mqtt5_config->server_resp_property_info.topic_alias_maximum);
MQTT_API_UNLOCK(client);
return ESP_FAIL;
}
client->mqtt5_config->publish_property_info = property;
MQTT_API_UNLOCK(client);
return ESP_OK;
}
esp_err_t esp_mqtt5_client_set_subscribe_property(esp_mqtt5_client_handle_t client, const esp_mqtt5_subscribe_property_config_t *property)
{
if (!client) {
ESP_LOGE(TAG, "Client was not initialized");
return ESP_ERR_INVALID_ARG;
}
if (property->retain_handle > 2) {
ESP_LOGE(TAG, "retain_handle only support 0, 1, 2");
return -1;
}
MQTT_API_LOCK(client);
/* Check protocol version */
if (client->mqtt_state.connection.information.protocol_ver != MQTT_PROTOCOL_V_5) {
ESP_LOGE(TAG, "MQTT protocol version is not v5");
MQTT_API_UNLOCK(client);
return ESP_FAIL;
}
if (property->is_share_subscribe) {
if (property->no_local_flag) {
// MQTT-3.8.3-4 not allow that No Local bit to 1 on a Shared Subscription
ESP_LOGE(TAG, "Protocol error that no local flag set on shared subscription");
MQTT_API_UNLOCK(client);
return ESP_FAIL;
}
if (!client->mqtt5_config->server_resp_property_info.shared_subscribe_available) {
ESP_LOGE(TAG, "MQTT broker not support shared subscribe");
MQTT_API_UNLOCK(client);
return ESP_FAIL;
}
if (!property->share_name || !strlen(property->share_name)) {
ESP_LOGE(TAG, "Share name can't be empty for shared subscribe");
MQTT_API_UNLOCK(client);
return ESP_FAIL;
}
}
client->mqtt5_config->subscribe_property_info = property;
MQTT_API_UNLOCK(client);
return ESP_OK;
}
esp_err_t esp_mqtt5_client_set_unsubscribe_property(esp_mqtt5_client_handle_t client, const esp_mqtt5_unsubscribe_property_config_t *property)
{
if (!client) {
ESP_LOGE(TAG, "Client was not initialized");
return ESP_ERR_INVALID_ARG;
}
MQTT_API_LOCK(client);
/* Check protocol version */
if (client->mqtt_state.connection.information.protocol_ver != MQTT_PROTOCOL_V_5) {
ESP_LOGE(TAG, "MQTT protocol version is not v5");
MQTT_API_UNLOCK(client);
return ESP_FAIL;
}
if (property->is_share_subscribe) {
if (!client->mqtt5_config->server_resp_property_info.shared_subscribe_available) {
ESP_LOGE(TAG, "MQTT broker not support shared subscribe");
MQTT_API_UNLOCK(client);
return ESP_FAIL;
}
if (!property->share_name || !strlen(property->share_name)) {
ESP_LOGE(TAG, "Share name can't be empty for shared subscribe");
MQTT_API_UNLOCK(client);
return ESP_FAIL;
}
}
client->mqtt5_config->unsubscribe_property_info = property;
MQTT_API_UNLOCK(client);
return ESP_OK;
}
esp_err_t esp_mqtt5_client_set_disconnect_property(esp_mqtt5_client_handle_t client, const esp_mqtt5_disconnect_property_config_t *property)
{
if (!client) {
ESP_LOGE(TAG, "Client was not initialized");
return ESP_ERR_INVALID_ARG;
}
MQTT_API_LOCK(client);
/* Check protocol version */
if (client->mqtt_state.connection.information.protocol_ver != MQTT_PROTOCOL_V_5) {
ESP_LOGE(TAG, "MQTT protocol version is not v5");
MQTT_API_UNLOCK(client);
return ESP_FAIL;
}
if (property) {
if (property->session_expiry_interval) {
client->mqtt5_config->disconnect_property_info.session_expiry_interval = property->session_expiry_interval;
}
if (property->disconnect_reason) {
client->mqtt5_config->disconnect_property_info.disconnect_reason = property->disconnect_reason;
}
if (property->user_property) {
esp_mqtt5_client_delete_user_property(client->mqtt5_config->disconnect_property_info.user_property);
client->mqtt5_config->disconnect_property_info.user_property = calloc(1, sizeof(struct mqtt5_user_property_list_t));
ESP_MEM_CHECK(TAG, client->mqtt5_config->disconnect_property_info.user_property, {
MQTT_API_UNLOCK(client);
return ESP_ERR_NO_MEM;
});
STAILQ_INIT(client->mqtt5_config->disconnect_property_info.user_property);
if (esp_mqtt5_user_property_copy(client->mqtt5_config->disconnect_property_info.user_property, property->user_property) != ESP_OK) {
ESP_LOGE(TAG, "esp_mqtt5_user_property_copy fail");
free(client->mqtt5_config->disconnect_property_info.user_property);
client->mqtt5_config->disconnect_property_info.user_property = NULL;
MQTT_API_UNLOCK(client);
return ESP_FAIL;
}
}
}
MQTT_API_UNLOCK(client);
return ESP_OK;
}
esp_err_t esp_mqtt5_client_set_connect_property(esp_mqtt5_client_handle_t client, const esp_mqtt5_connection_property_config_t *connect_property)
{
if (!client) {
ESP_LOGE(TAG, "Client was not initialized");
return ESP_ERR_INVALID_ARG;
}
MQTT_API_LOCK(client);
/* Check protocol version */
if (client->mqtt_state.connection.information.protocol_ver != MQTT_PROTOCOL_V_5) {
ESP_LOGE(TAG, "MQTT protocol version is not v5");
MQTT_API_UNLOCK(client);
return ESP_FAIL;
}
if (connect_property) {
if (connect_property->session_expiry_interval) {
client->mqtt5_config->connect_property_info.session_expiry_interval = connect_property->session_expiry_interval;
}
if (connect_property->maximum_packet_size) {
if (connect_property->maximum_packet_size > client->mqtt_state.in_buffer_length) {
ESP_LOGW(TAG, "Connect maximum_packet_size property is over buffer_size(%d), Please first change it", client->mqtt_state.in_buffer_length);
MQTT_API_UNLOCK(client);
return ESP_FAIL;
} else {
client->mqtt5_config->connect_property_info.maximum_packet_size = connect_property->maximum_packet_size;
}
} else {
client->mqtt5_config->connect_property_info.maximum_packet_size = client->mqtt_state.in_buffer_length;
}
if (connect_property->receive_maximum) {
client->mqtt5_config->connect_property_info.receive_maximum = connect_property->receive_maximum;
}
if (connect_property->topic_alias_maximum) {
client->mqtt5_config->connect_property_info.topic_alias_maximum = connect_property->topic_alias_maximum;
if (!client->mqtt5_config->peer_topic_alias) {
client->mqtt5_config->peer_topic_alias = calloc(1, sizeof(struct mqtt5_topic_alias_list_t));
ESP_MEM_CHECK(TAG, client->mqtt5_config->peer_topic_alias, goto _mqtt_set_config_failed);
STAILQ_INIT(client->mqtt5_config->peer_topic_alias);
}
}
if (connect_property->request_resp_info) {
client->mqtt5_config->connect_property_info.request_resp_info = connect_property->request_resp_info;
}
if (connect_property->request_problem_info) {
client->mqtt5_config->connect_property_info.request_problem_info = connect_property->request_problem_info;
}
if (connect_property->user_property) {
esp_mqtt5_client_delete_user_property(client->mqtt5_config->connect_property_info.user_property);
client->mqtt5_config->connect_property_info.user_property = calloc(1, sizeof(struct mqtt5_user_property_list_t));
ESP_MEM_CHECK(TAG, client->mqtt5_config->connect_property_info.user_property, goto _mqtt_set_config_failed);
STAILQ_INIT(client->mqtt5_config->connect_property_info.user_property);
if (esp_mqtt5_user_property_copy(client->mqtt5_config->connect_property_info.user_property, connect_property->user_property) != ESP_OK) {
ESP_LOGE(TAG, "esp_mqtt5_user_property_copy fail");
goto _mqtt_set_config_failed;
}
}
if (connect_property->payload_format_indicator) {
client->mqtt5_config->will_property_info.payload_format_indicator = connect_property->payload_format_indicator;
}
if (connect_property->will_delay_interval) {
client->mqtt5_config->will_property_info.will_delay_interval = connect_property->will_delay_interval;
}
if (connect_property->message_expiry_interval) {
client->mqtt5_config->will_property_info.message_expiry_interval = connect_property->message_expiry_interval;
}
ESP_MEM_CHECK(TAG, esp_mqtt_set_if_config(connect_property->content_type, &client->mqtt5_config->will_property_info.content_type), goto _mqtt_set_config_failed);
ESP_MEM_CHECK(TAG, esp_mqtt_set_if_config(connect_property->response_topic, &client->mqtt5_config->will_property_info.response_topic), goto _mqtt_set_config_failed);
if (connect_property->correlation_data && connect_property->correlation_data_len) {
free(client->mqtt5_config->will_property_info.correlation_data);
client->mqtt5_config->will_property_info.correlation_data = malloc(connect_property->correlation_data_len);
ESP_MEM_CHECK(TAG, client->mqtt5_config->will_property_info.correlation_data, goto _mqtt_set_config_failed);
memcpy(client->mqtt5_config->will_property_info.correlation_data, connect_property->correlation_data, connect_property->correlation_data_len);
client->mqtt5_config->will_property_info.correlation_data_len = connect_property->correlation_data_len;
}
if (connect_property->will_user_property) {
esp_mqtt5_client_delete_user_property(client->mqtt5_config->will_property_info.user_property);
client->mqtt5_config->will_property_info.user_property = calloc(1, sizeof(struct mqtt5_user_property_list_t));
ESP_MEM_CHECK(TAG, client->mqtt5_config->will_property_info.user_property, goto _mqtt_set_config_failed);
STAILQ_INIT(client->mqtt5_config->will_property_info.user_property);
if (esp_mqtt5_user_property_copy(client->mqtt5_config->will_property_info.user_property, connect_property->will_user_property) != ESP_OK) {
ESP_LOGE(TAG, "esp_mqtt5_user_property_copy fail");
goto _mqtt_set_config_failed;
}
}
}
MQTT_API_UNLOCK(client);
return ESP_OK;
_mqtt_set_config_failed:
esp_mqtt_destroy_config(client);
MQTT_API_UNLOCK(client);
return ESP_ERR_NO_MEM;
}
esp_err_t esp_mqtt5_client_set_user_property(mqtt5_user_property_handle_t *user_property, esp_mqtt5_user_property_item_t item[], uint8_t item_num)
{
if (!item_num || !item) {
ESP_LOGE(TAG, "Input value is NULL");
return ESP_FAIL;
}
if (!*user_property) {
*user_property = calloc(1, sizeof(struct mqtt5_user_property_list_t));
ESP_MEM_CHECK(TAG, *user_property, return ESP_ERR_NO_MEM);
STAILQ_INIT(*user_property);
}
for (int i = 0; i < item_num; i ++) {
if (item[i].key && item[i].value) {
mqtt5_user_property_item_t user_property_item = calloc(1, sizeof(mqtt5_user_property_t));
ESP_MEM_CHECK(TAG, user_property_item, goto err);
size_t key_len = strlen(item[i].key);
size_t value_len = strlen(item[i].value);
user_property_item->key = calloc(1, key_len + 1);
ESP_MEM_CHECK(TAG, user_property_item->key, {
free(user_property_item);
goto err;
});
memcpy(user_property_item->key, item[i].key, key_len);
user_property_item->key[key_len] = '\0';
user_property_item->value = calloc(1, value_len + 1);
ESP_MEM_CHECK(TAG, user_property_item->value, {
free(user_property_item->key);
free(user_property_item);
goto err;
});
memcpy(user_property_item->value, item[i].value, value_len);
user_property_item->value[value_len] = '\0';
STAILQ_INSERT_TAIL(*user_property, user_property_item, next);
}
}
return ESP_OK;
err:
esp_mqtt5_client_delete_user_property(*user_property);
*user_property = NULL;
return ESP_ERR_NO_MEM;
}
esp_err_t esp_mqtt5_client_get_user_property(mqtt5_user_property_handle_t user_property, esp_mqtt5_user_property_item_t *item, uint8_t *item_num)
{
int i = 0, j = 0;
if (user_property && item && *item_num) {
mqtt5_user_property_item_t user_property_item;
uint8_t num = *item_num;
STAILQ_FOREACH(user_property_item, user_property, next) {
if (i < num) {
size_t item_key_len = strlen(user_property_item->key);
size_t item_value_len = strlen(user_property_item->value);
char *key = calloc(1, item_key_len + 1);
ESP_MEM_CHECK(TAG, key, goto err);
memcpy(key, user_property_item->key, item_key_len);
key[item_key_len] = '\0';
char *value = calloc(1, item_value_len + 1);
ESP_MEM_CHECK(TAG, value, {
free(key);
goto err;
});
memcpy(value, user_property_item->value, item_value_len);
value[item_value_len] = '\0';
item[i].key = key;
item[i].value = value;
i ++;
} else {
break;
}
}
*item_num = i;
return ESP_OK;
} else {
ESP_LOGE(TAG, "Input value is NULL or item_num is 0");
return ESP_FAIL;
}
err:
for (j = 0; j < i; j ++) {
if (item[j].key) {
free((char *)item[j].key);
}
if (item[j].value) {
free((char *)item[j].value);
}
}
return ESP_ERR_NO_MEM;
}
uint8_t esp_mqtt5_client_get_user_property_count(mqtt5_user_property_handle_t user_property)
{
uint8_t count = 0;
if (user_property) {
mqtt5_user_property_item_t item;
STAILQ_FOREACH(item, user_property, next) {
count ++;
}
}
return count;
}
void esp_mqtt5_client_delete_user_property(mqtt5_user_property_handle_t user_property)
{
if (user_property) {
mqtt5_user_property_item_t item, tmp;
STAILQ_FOREACH_SAFE(item, user_property, next, tmp) {
STAILQ_REMOVE(user_property, item, mqtt5_user_property, next);
free(item->key);
free(item->value);
free(item);
}
}
free(user_property);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,9 @@
limits:
"clang-analyzer-core.NullDereference" : 0
"clang-analyzer-unix.Malloc" : 0
ignore:
- "llvm-header-guard"
- "llvm-include-order"
skip:

View File

@@ -0,0 +1 @@
eabf03fe3cb1bdb5325d2725f420c6324c3e150033d893ce3737222929455a55

View File

@@ -0,0 +1,21 @@
## 1.0.3
- Fixed a bug with interface mismatch on EP IN transfer complete while several HID devices are present.
- Fixed a bug during device freeing, while detaching one of several attached HID devices.
## 1.0.2
- Added support for ESP32-P4
- Fixed device open procedure for HID devices with multiple non-sequential interfaces.
## 1.0.1
- Fixed a bug where configuring the driver with `create_background_task = false` did not properly initialize the driver. This lead to `the hid_host_uninstall()` hang-up.
- Fixed a bug where `hid_host_uninstall()` would cause a crash during the call while USB device has not been removed.
- Added `hid_host_get_device_info()` to get the basic information of a connected USB HID device.
## 1.0.0
- Initial version

View File

@@ -0,0 +1,3 @@
idf_component_register( SRCS "hid_host.c"
INCLUDE_DIRS "include"
PRIV_REQUIRES usb )

View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,52 @@
# USB Host HID (Human Interface Device) Driver
[![Component Registry](https://components.espressif.com/components/espressif/usb_host_hid/badge.svg)](https://components.espressif.com/components/espressif/usb_host_hid)
This directory contains an implementation of a USB HID Driver implemented on top of the [USB Host Library](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/api-reference/peripherals/usb_host.html).
HID driver allows access to HID devices.
## Usage
The following steps outline the typical API call pattern of the HID Class Driver:
1. Install the USB Host Library via 'usb_host_install()'
2. Install the HID driver via 'hid_host_install()'
3. The HID Host driver device callback provide the following events (via two callbacks):
- HID_HOST_DRIVER_EVENT_CONNECTED
- HID_HOST_INTERFACE_EVENT_INPUT_REPORT
- HID_HOST_INTERFACE_EVENT_TRANSFER_ERROR
- HID_HOST_INTERFACE_EVENT_DISCONNECTED
4. Specific HID device can be opened or closed with:
- 'hid_host_device_open()'
- 'hid_host_device_close()'
5. To enable / disable data receiving in case of event (keyboard key was pressed or mouse device was moved e.t.c) use:
- 'hid_host_device_start()'
- 'hid_host_device_stop()'
6. HID Class specific device requests:
- 'hid_host_interface_get_report_descriptor()'
- 'hid_class_request_get_report()'
- 'hid_class_request_get_idle()'
- 'hid_class_request_get_protocol()'
- 'hid_class_request_set_report()'
- 'hid_class_request_set_idle()'
- 'hid_class_request_set_protocol()'
7. When HID device event occurs the driver call an interface callback with events:
- HID_HOST_INTERFACE_EVENT_INPUT_REPORT
- HID_HOST_INTERFACE_EVENT_TRANSFER_ERROR
- HID_HOST_INTERFACE_EVENT_DISCONNECTED
8. The HID driver can be uninstalled via 'hid_host_uninstall()'
## Known issues
- Empty
## Examples
- For an example, refer to [hid_host_example](https://github.com/espressif/esp-idf/tree/master/examples/peripherals/usb/host/hid)
## Supported Devices
- HID Driver support any HID compatible device with a USB bIterfaceClass 0x03 (Human Interface Device).
- There are two options to handle HID device input data: either in RAW format or via special event handlers (which are available only for HID Devices which support Boot Protocol).

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,13 @@
dependencies:
idf: '>=4.4'
description: USB Host HID driver
repository: git://github.com/espressif/esp-usb.git
repository_info:
commit_sha: 39b0de39a90c9e391db26f06d54e4b10f58be231
path: host/class/hid/usb_host_hid
targets:
- esp32s2
- esp32s3
- esp32p4
url: https://github.com/espressif/esp-usb/tree/master/host/class/hid/usb_host_hid
version: 1.0.3

View File

@@ -0,0 +1,147 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief HID Subclass
*
* @see 4.2 Subclass, p.8 of Device Class Definition for Human Interface Devices (HID) Version 1.11
*/
typedef enum {
HID_SUBCLASS_NO_SUBCLASS = 0x00,
HID_SUBCLASS_BOOT_INTERFACE = 0x01
} __attribute__((packed)) hid_subclass_t;
/**
* @brief HID Protocols
*
* @see 4.3 Protocols, p.9 of Device Class Definition for Human Interface Devices (HID) Version 1.11
*/
typedef enum {
HID_PROTOCOL_NONE = 0x00,
HID_PROTOCOL_KEYBOARD = 0x01,
HID_PROTOCOL_MOUSE = 0x02,
HID_PROTOCOL_MAX
} __attribute__((packed)) hid_protocol_t;
/**
* @brief HID Descriptor
*
* @see 6.2.1 HID Descriptor, p.22 of Device Class Definition for Human Interface Devices (HID) Version 1.11
*/
typedef struct {
uint8_t bLength; // Numeric expression that is the total size of the HID descriptor
uint8_t bDescriptorType; // Constant name specifying type of HID descriptor
uint16_t bcdHID; // Numeric expression identifying the HIDClass Specification release
uint8_t bCountryCode; // Numeric expression identifying country code of the localized hardware
uint8_t bNumDescriptors; // Numeric expression specifying the number of class descriptors (always at least one i.e. Report descriptor.)
uint8_t bReportDescriptorType; // Constant name identifying type of class descriptor. See Section 7.1.2: Set_Descriptor Request for a table of class descriptor constants
uint16_t wReportDescriptorLength; // Numeric expression that is the total size of the Report descriptor
// Optional descriptors may follow further
} __attribute__((packed)) hid_descriptor_t;
/**
* @brief HID Country Codes
*
* @see 6.2.1 HID Descriptor, p.23 of Device Class Definition for Human Interface Devices (HID) Version 1.11
*/
typedef enum {
HID_COUNTRY_CODE_NOT_SUPPORTED = 0x00,
HID_COUNTRY_CODE_ARABIC = 0x01,
HID_COUNTRY_CODE_BELGIAN = 0x02,
HID_COUNTRY_CODE_CANADIAN_BILINGUAL = 0x03,
HID_COUNTRY_CODE_CANADIAN_FRENCH = 0x04,
HID_COUNTRY_CODE_CZECH = 0x05,
HID_COUNTRY_CODE_DANISH = 0x06,
HID_COUNTRY_CODE_FINNISH = 0x07,
HID_COUNTRY_CODE_FRENCH = 0x08,
HID_COUNTRY_CODE_GERMAN = 0x09,
HID_COUNTRY_CODE_GREEK = 0x0A,
HID_COUNTRY_CODE_HEBREW = 0x0B,
HID_COUNTRY_CODE_HUNGARY = 0x0C,
HID_COUNTRY_CODE_ISO = 0x0D,
HID_COUNTRY_CODE_ITALIAN = 0x0E,
HID_COUNTRY_CODE_JAPAN = 0x0F,
HID_COUNTRY_CODE_KOREAN = 0x10,
HID_COUNTRY_CODE_LATIN_AMERICAN = 0x11,
HID_COUNTRY_CODE_NETHERLANDS = 0x12,
HID_COUNTRY_CODE_NORWEGIAN = 0x13,
HID_COUNTRY_CODE_PERSIAN = 0x14,
HID_COUNTRY_CODE_POLAND = 0x15,
HID_COUNTRY_CODE_PORTUGUESE = 0x16,
HID_COUNTRY_CODE_RUSSIA = 0x17,
HID_COUNTRY_CODE_SLOVAKIA = 0x18,
HID_COUNTRY_CODE_SPANISH = 0x19,
HID_COUNTRY_CODE_SWEDISH = 0x1A,
HID_COUNTRY_CODE_SWISS_F = 0x1B,
HID_COUNTRY_CODE_SWISS_G = 0x1C,
HID_COUNTRY_CODE_SWITZERLAND = 0x1D,
HID_COUNTRY_CODE_TAIWAN = 0x1E,
HID_COUNTRY_CODE_TURKISH_Q = 0x1F,
HID_COUNTRY_CODE_UK = 0x20,
HID_COUNTRY_CODE_US = 0x21,
HID_COUNTRY_CODE_YUGOSLAVIA = 0x22,
HID_COUNTRY_CODE_TURKISH_F = 0x23
} __attribute__((packed)) hid_country_code_t;
/**
* @brief HID Class Descriptor Types
*
* @see 7.1, p.49 of Device Class Definition for Human Interface Devices (HID) Version 1.11
*/
typedef enum {
HID_CLASS_DESCRIPTOR_TYPE_HID = 0x21,
HID_CLASS_DESCRIPTOR_TYPE_REPORT = 0x22,
HID_CLASS_DESCRIPTOR_TYPE_PHYSICAL = 0x23
} __attribute__((packed)) hid_class_descritpor_type_t;
/**
* @brief HID Class-Specific Requests
*
* @see 7.2, p.50 of Device Class Definition for Human Interface Devices (HID) Version 1.11
*/
typedef enum {
HID_CLASS_SPECIFIC_REQ_GET_REPORT = 0x01,
HID_CLASS_SPECIFIC_REQ_GET_IDLE = 0x02,
HID_CLASS_SPECIFIC_REQ_GET_PROTOCOL = 0x03,
HID_CLASS_SPECIFIC_REQ_SET_REPORT = 0x09,
HID_CLASS_SPECIFIC_REQ_SET_IDLE = 0x0A,
HID_CLASS_SPECIFIC_REQ_SET_PROTOCOL = 0x0B
} __attribute__((packed)) hid_class_specific_req_t;
/**
* @brief HID Report Types
*
* @see 7.2.1, p.51 of Device Class Definition for Human Interface Devices (HID) Version 1.11
*/
typedef enum {
HID_REPORT_TYPE_INPUT = 0x01,
HID_REPORT_TYPE_OUTPUT = 0x02,
HID_REPORT_TYPE_FEATURE = 0x03,
} __attribute__((packed)) hid_report_type_t;
/**
* @brief HID Report protocol
*
* @see 7.2.5/7.2.6, p.54 of Device Class Definition for Human Interface Devices (HID) Version 1.11
*/
typedef enum {
HID_REPORT_PROTOCOL_BOOT = 0x00,
HID_REPORT_PROTOCOL_REPORT = 0x01,
HID_REPORT_PROTOCOL_MAX
} __attribute__((packed)) hid_report_protocol_t;
#ifdef __cplusplus
}
#endif //__cplusplus

View File

@@ -0,0 +1,313 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <wchar.h>
#include <stdint.h>
#include "esp_err.h"
#include <freertos/FreeRTOS.h>
#include "hid.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief USB HID HOST string descriptor maximal length
*
* The maximum possible number of characters in an embedded string is device specific.
* For USB devices, the maximum string length is 126 wide characters (not including the terminating NULL character).
* This is a length, which is available to upper level application during getting information
* of HID Device with 'hid_host_get_device_info' call.
*
* To decrease memory usage 32 wide characters (64 bytes per every string) is used.
*/
#define HID_STR_DESC_MAX_LENGTH 32
typedef struct hid_interface *hid_host_device_handle_t; /**< Device Handle. Handle to a particular HID interface */
// ------------------------ USB HID Host events --------------------------------
/**
* @brief USB HID HOST Device event id
*/
typedef enum {
HID_HOST_DRIVER_EVENT_CONNECTED = 0x00, /**< HID Device has been found in connected USB device (at least one) */
} hid_host_driver_event_t;
/**
* @brief USB HID HOST Interface event id
*/
typedef enum {
HID_HOST_INTERFACE_EVENT_INPUT_REPORT = 0x00, /**< HID Device input report */
HID_HOST_INTERFACE_EVENT_TRANSFER_ERROR, /**< HID Device transfer error */
HID_HOST_INTERFACE_EVENT_DISCONNECTED, /**< HID Device has been disconnected */
} hid_host_interface_event_t;
/**
* @brief HID device descriptor common data.
*/
typedef struct {
uint16_t VID;
uint16_t PID;
wchar_t iManufacturer[HID_STR_DESC_MAX_LENGTH];
wchar_t iProduct[HID_STR_DESC_MAX_LENGTH];
wchar_t iSerialNumber[HID_STR_DESC_MAX_LENGTH];
} hid_host_dev_info_t;
/**
* @brief USB HID Host device parameters
*/
typedef struct {
uint8_t addr; /**< USB Address of connected HID device */
uint8_t iface_num; /**< HID Interface Number */
uint8_t sub_class; /**< HID Interface SubClass */
uint8_t proto; /**< HID Interface Protocol */
} hid_host_dev_params_t;
// ------------------------ USB HID Host callbacks -----------------------------
/**
* @brief USB HID driver event callback.
*
* @param[in] hid_handle HID device handle (HID Interface)
* @param[in] event HID driver event
* @param[in] arg User argument from HID driver configuration structure
*/
typedef void (*hid_host_driver_event_cb_t)(hid_host_device_handle_t hid_device_handle,
const hid_host_driver_event_t event,
void *arg);
/**
* @brief USB HID Interface event callback.
*
* @param[in] hid_device_handle HID device handle (HID Interface)
* @param[in] event HID Interface event
* @param[in] arg User argument
*/
typedef void (*hid_host_interface_event_cb_t)(hid_host_device_handle_t hid_device_handle,
const hid_host_interface_event_t event,
void *arg);
// ----------------------------- Public ---------------------------------------
/**
* @brief HID configuration structure.
*/
typedef struct {
bool create_background_task; /**< When set to true, background task handling USB events is created.
Otherwise user has to periodically call hid_host_handle_events function */
size_t task_priority; /**< Task priority of created background task */
size_t stack_size; /**< Stack size of created background task */
BaseType_t core_id; /**< Select core on which background task will run or tskNO_AFFINITY */
hid_host_driver_event_cb_t callback; /**< Callback invoked when HID driver event occurs. Must not be NULL. */
void *callback_arg; /**< User provided argument passed to callback */
} hid_host_driver_config_t;
/**
* @brief HID device configuration structure (HID Interface)
*/
typedef struct {
hid_host_interface_event_cb_t callback; /**< Callback invoked when HID Interface event occurs */
void *callback_arg; /**< User provided argument passed to callback */
} hid_host_device_config_t;
/**
* @brief USB HID Host install USB Host HID Class driver
*
* @param[in] config configuration structure HID to create
* @return esp_err_r
*/
esp_err_t hid_host_install(const hid_host_driver_config_t *config);
/**
* @brief USB HID Host uninstall HID Class driver
* @return esp_err_t
*/
esp_err_t hid_host_uninstall(void);
/**
* @brief USB HID Host open a device with specific device parameters
*
* @param[in] iface_handle Handle of the HID device to open
* @param[in] config Configuration structure HID device to open
* @return esp_err_t
*/
esp_err_t hid_host_device_open(hid_host_device_handle_t hid_dev_handle,
const hid_host_device_config_t *config);
/**
* @brief USB HID Host close device
*
* @param[in] hid_dev_handle Handle of the HID device to close
* @return esp_err_t
*/
esp_err_t hid_host_device_close(hid_host_device_handle_t hid_dev_handle);
/**
* @brief HID Host USB event handler
*
* If HID Host install was made with create_background_task=false configuration,
* application needs to handle USB Host events itself.
* Do not used if HID host install was made with create_background_task=true configuration
*
* @param[in] timeout Timeout in ticks. For milliseconds, please use 'pdMS_TO_TICKS()' macros
* @return esp_err_t
*/
esp_err_t hid_host_handle_events(uint32_t timeout);
/**
* @brief HID Device get parameters by handle.
*
* @param[in] hid_dev_handle HID Device handle
* @param[out] dev_params Pointer to a dev_params struct to fill
*
* @return esp_err_t
*/
esp_err_t hid_host_device_get_params(hid_host_device_handle_t hid_dev_handle,
hid_host_dev_params_t *dev_params);
/**
* @brief HID Host get device raw input report data pointer by handle
*
* This functions should be called after HID Interface device event HID_HOST_INTERFACE_EVENT_INPUT_REPORT
* to get the actual raw data of input report.
*
* @param[in] hid_dev_handle HID Device handle
* @param[in] data Pointer to buffer where the input data will be copied
* @param[in] data_length_max Max length of data can be copied to data buffer
* @param[out] data_length Length of input report
*
* @return esp_err_t
*/
esp_err_t hid_host_device_get_raw_input_report_data(hid_host_device_handle_t hid_dev_handle,
uint8_t *data,
size_t data_length_max,
size_t *data_length);
// ------------------------ USB HID Host driver API ----------------------------
/**
* @brief HID Host start awaiting event from a device by handle
*
* Calls a callback when the HID Interface event has occurred.
*
* @param[in] hid_dev_handle HID Device handle
* @return esp_err_t
*/
esp_err_t hid_host_device_start(hid_host_device_handle_t hid_dev_handle);
/**
* @brief HID Host stop device
*
* @param[in] hid_dev_handle HID Device handle
*
* @return esp_err_t
*/
esp_err_t hid_host_device_stop(hid_host_device_handle_t hid_dev_handle);
/**
* @brief HID Host Get Report Descriptor
*
* @param[in] hid_dev_handle HID Device handle
* @param[out] report_desc_len Length of report descriptor
*
* @return a uint8_t pointer to report descriptor data
*/
uint8_t *hid_host_get_report_descriptor(hid_host_device_handle_t hid_dev_handle,
size_t *report_desc_len);
/**
* @brief HID Host Get device information
*
* @param[in] hid_dev_handle HID Device handle
*/
esp_err_t hid_host_get_device_info(hid_host_device_handle_t hid_dev_handle,
hid_host_dev_info_t *hid_dev_info);
/**
* @brief HID class specific request GET REPORT
*
* @param[in] hid_dev_handle HID Device handle
* @param[in] report_id Report ID
* @param[out] report Pointer to buffer for a report data
* @param[in/out] report_length Report data length, before the get report contain the maximum value of a report buffer.
* After get report there is a value of actual data in report buffer.
*
* @return esp_err_t
*/
esp_err_t hid_class_request_get_report(hid_host_device_handle_t hid_dev_handle,
uint8_t report_type,
uint8_t report_id,
uint8_t *report,
size_t *report_length);
/**
* @brief HID class specific request GET IDLE
*
* @param[in] hid_dev_handle HID Device handle
* @param[in] report_id ReportID
* @param[out] idle_rate Idle rate [ms]
*
* @return esp_err_t
*/
esp_err_t hid_class_request_get_idle(hid_host_device_handle_t hid_dev_handle,
uint8_t report_id,
uint8_t *idle_rate);
/**
* @brief HID class specific request GET PROTOCOL
*
* @param[in] hid_dev_handle HID Device handle
* @param[out] protocol Pointer to HID report protocol (boot or report) of device
*
* @return esp_err_t
*/
esp_err_t hid_class_request_get_protocol(hid_host_device_handle_t hid_dev_handle,
hid_report_protocol_t *protocol);
/**
* @brief HID class specific request SET REPORT
*
* @param[in] hid_dev_handle HID Device handle
* @param[in] report_type Report type
* @param[in] report_id Report ID
* @param[in] report Pointer to a buffer with report data
* @param[in] report_length Report data length
*
* @return esp_err_t
*/
esp_err_t hid_class_request_set_report(hid_host_device_handle_t hid_dev_handle,
uint8_t report_type,
uint8_t report_id,
uint8_t *report,
size_t report_length);
/**
* @brief HID class specific request SET IDLE
*
* @param[in] hid_dev_handle HID Device handle
* @param[in] duration 0 (zero) for the indefinite duration, non-zero, then a fixed duration used.
* @param[in] report_id If 0 (zero) the idle rate applies to all input reports generated by the device, otherwise ReportID
* @return esp_err_t
*/
esp_err_t hid_class_request_set_idle(hid_host_device_handle_t hid_dev_handle,
uint8_t duration,
uint8_t report_id);
/**
* @brief HID class specific request SET PROTOCOL
*
* @param[in] hid_dev_handle HID Device handle
* @param[in] protocol HID report protocol (boot or report)
* @return esp_err_t
*/
esp_err_t hid_class_request_set_protocol(hid_host_device_handle_t hid_dev_handle,
hid_report_protocol_t protocol);
#ifdef __cplusplus
}
#endif //__cplusplus

View File

@@ -0,0 +1,292 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
//------------------------------------------ HID usage keys ---------------------------------------------------------------
/**
* @brief HID Keys
*
*/
typedef enum {
HID_KEY_NO_PRESS = 0x00,
HID_KEY_ROLLOVER = 0x01,
HID_KEY_POST_FAIL = 0x02,
HID_KEY_ERROR_UNDEFINED = 0x03,
HID_KEY_A = 0x04,
HID_KEY_B = 0x05,
HID_KEY_C = 0x06,
HID_KEY_D = 0x07,
HID_KEY_E = 0x08,
HID_KEY_F = 0x09,
HID_KEY_G = 0x0A,
HID_KEY_H = 0x0B,
HID_KEY_I = 0x0C,
HID_KEY_J = 0x0D,
HID_KEY_K = 0x0E,
HID_KEY_L = 0x0F,
HID_KEY_M = 0x10,
HID_KEY_N = 0x11,
HID_KEY_O = 0x12,
HID_KEY_P = 0x13,
HID_KEY_Q = 0x14,
HID_KEY_R = 0x15,
HID_KEY_S = 0x16,
HID_KEY_T = 0x17,
HID_KEY_U = 0x18,
HID_KEY_V = 0x19,
HID_KEY_W = 0x1A,
HID_KEY_X = 0x1B,
HID_KEY_Y = 0x1C,
HID_KEY_Z = 0x1D,
HID_KEY_1 = 0x1E,
HID_KEY_2 = 0x1F,
HID_KEY_3 = 0x20,
HID_KEY_4 = 0x21,
HID_KEY_5 = 0x22,
HID_KEY_6 = 0x23,
HID_KEY_7 = 0x24,
HID_KEY_8 = 0x25,
HID_KEY_9 = 0x26,
HID_KEY_0 = 0x27,
HID_KEY_ENTER = 0x28,
HID_KEY_ESC = 0x29,
HID_KEY_DEL = 0x2A,
HID_KEY_TAB = 0x2B,
HID_KEY_SPACE = 0x2C,
HID_KEY_MINUS = 0x2D,
HID_KEY_EQUAL = 0x2E,
HID_KEY_OPEN_BRACKET = 0x2F,
HID_KEY_CLOSE_BRACKET = 0x30,
HID_KEY_BACK_SLASH = 0x31,
HID_KEY_SHARP = 0x32,
HID_KEY_COLON = 0x33,
HID_KEY_QUOTE = 0x34,
HID_KEY_TILDE = 0x35,
HID_KEY_LESS = 0x36,
HID_KEY_GREATER = 0x37,
HID_KEY_SLASH = 0x38,
HID_KEY_CAPS_LOCK = 0x39,
HID_KEY_F1 = 0x3A,
HID_KEY_F2 = 0x3B,
HID_KEY_F3 = 0x3C,
HID_KEY_F4 = 0x3D,
HID_KEY_F5 = 0x3E,
HID_KEY_F6 = 0x3F,
HID_KEY_F7 = 0x40,
HID_KEY_F8 = 0x41,
HID_KEY_F9 = 0x42,
HID_KEY_F10 = 0x43,
HID_KEY_F11 = 0x44,
HID_KEY_F12 = 0x45,
HID_KEY_PRINT_SCREEN = 0x46,
HID_KEY_SCROLL_LOCK = 0x47,
HID_KEY_PAUSE = 0x48,
HID_KEY_INSERT = 0x49,
HID_KEY_HOME = 0x4A,
HID_KEY_PAGEUP = 0x4B,
HID_KEY_DELETE = 0x4C,
HID_KEY_END = 0x4D,
HID_KEY_PAGEDOWN = 0x4E,
HID_KEY_RIGHT = 0x4F,
HID_KEY_LEFT = 0x50,
HID_KEY_DOWN = 0x51,
HID_KEY_UP = 0x52,
HID_KEY_NUM_LOCK = 0x53,
HID_KEY_KEYPAD_DIV = 0x54,
HID_KEY_KEYPAD_MUL = 0x55,
HID_KEY_KEYPAD_SUB = 0x56,
HID_KEY_KEYPAD_ADD = 0x57,
HID_KEY_KEYPAD_ENTER = 0x58,
HID_KEY_KEYPAD_1 = 0x59,
HID_KEY_KEYPAD_2 = 0x5A,
HID_KEY_KEYPAD_3 = 0x5B,
HID_KEY_KEYPAD_4 = 0x5C,
HID_KEY_KEYPAD_5 = 0x5D,
HID_KEY_KEYPAD_6 = 0x5E,
HID_KEY_KEYPAD_7 = 0x5F,
HID_KEY_KEYPAD_8 = 0x60,
HID_KEY_KEYPAD_9 = 0x61,
HID_KEY_KEYPAD_0 = 0x62,
HID_KEY_KEYPAD_DELETE = 0x63,
HID_KEY_KEYPAD_SLASH = 0x64,
HID_KEY_APPLICATION = 0x65,
HID_KEY_POWER = 0x66,
HID_KEY_KEYPAD_EQUAL = 0x67,
HID_KEY_F13 = 0x68,
HID_KEY_F14 = 0x69,
HID_KEY_F15 = 0x6A,
HID_KEY_F16 = 0x6B,
HID_KEY_F17 = 0x6C,
HID_KEY_F18 = 0x6D,
HID_KEY_F19 = 0x6E,
HID_KEY_F20 = 0x6F,
HID_KEY_F21 = 0x70,
HID_KEY_F22 = 0x71,
HID_KEY_F23 = 0x72,
HID_KEY_F24 = 0x73,
HID_KEY_EXECUTE = 0x74,
HID_KEY_HELP = 0x75,
HID_KEY_MENU = 0x76,
HID_KEY_SELECT = 0x77,
HID_KEY_STOP = 0x78,
HID_KEY_AGAIN = 0x79,
HID_KEY_UNDO = 0x7A,
HID_KEY_CUT = 0x7B,
HID_KEY_COPY = 0x7C,
HID_KEY_PASTE = 0x7D,
HID_KEY_FIND = 0x7E,
HID_KEY_MUTE = 0x7F,
HID_KEY_VOLUME_UP = 0x80,
HID_KEY_VOLUME_DOWN = 0x81,
HID_KEY_LOCKING_CAPS_LOCK = 0x82,
HID_KEY_LOCKING_NUM_LOCK = 0x83,
HID_KEY_LOCKING_SCROLL_LOCK = 0x84,
HID_KEY_KEYPAD_COMMA = 0x85,
HID_KEY_KEYPAD_EQUAL_SIGN = 0x86,
HID_KEY_INTERNATIONAL_1 = 0x87,
HID_KEY_INTERNATIONAL_2 = 0x88,
HID_KEY_INTERNATIONAL_3 = 0x89,
HID_KEY_INTERNATIONAL_4 = 0x8A,
HID_KEY_INTERNATIONAL_5 = 0x8B,
HID_KEY_INTERNATIONAL_6 = 0x8C,
HID_KEY_INTERNATIONAL_7 = 0x8D,
HID_KEY_INTERNATIONAL_8 = 0x8E,
HID_KEY_INTERNATIONAL_9 = 0x8F,
HID_KEY_LANG_1 = 0x90,
HID_KEY_LANG_2 = 0x91,
HID_KEY_LANG_3 = 0x92,
HID_KEY_LANG_4 = 0x93,
HID_KEY_LANG_5 = 0x94,
HID_KEY_LANG_6 = 0x95,
HID_KEY_LANG_7 = 0x96,
HID_KEY_LANG_8 = 0x97,
HID_KEY_LANG_9 = 0x98,
HID_KEY_ALTERNATE_ERASE = 0x99,
HID_KEY_SYSREQ = 0x9A,
HID_KEY_CANCEL = 0x9B,
HID_KEY_CLEAR = 0x9C,
HID_KEY_PRIOR = 0x9D,
HID_KEY_RETURN = 0x9E,
HID_KEY_SEPARATOR = 0x9F,
HID_KEY_OUT = 0xA0,
HID_KEY_OPER = 0xA1,
HID_KEY_CLEAR_AGAIN = 0xA2,
HID_KEY_CRSEL = 0xA3,
HID_KEY_EXSEL = 0xA4,
HID_KEY_KEYPAD_00 = 0xB0,
HID_KEY_KEYPAD_000 = 0xB1,
HID_KEY_THOUSANDS_SEPARATOR = 0xB2,
HID_KEY_DECIMAL_SEPARATOR = 0xB3,
HID_KEY_CURRENCY_UNIT = 0xB4,
HID_KEY_CURRENCY_SUB_UNIT = 0xB5,
HID_KEY_KEYPAD_OPEN_PARENTHESIS = 0xB6,
HID_KEY_KEYPAD_CLOSE_PARENTHESIS = 0xB7,
HID_KEY_KEYPAD_OPEN_BRACE = 0xB8,
HID_KEY_KEYPAD_CLOSE_BRACE = 0xB9,
HID_KEY_KEYPAD_TAB = 0xBA,
HID_KEY_KEYPAD_BACKSPACE = 0xBB,
HID_KEY_KEYPAD_A = 0xBC,
HID_KEY_KEYPAD_B = 0xBD,
HID_KEY_KEYPAD_C = 0xBE,
HID_KEY_KEYPAD_D = 0xBF,
HID_KEY_KEYPAD_E = 0xC0,
HID_KEY_KEYPAD_F = 0xC1,
HID_KEY_KEYPAD_XOR = 0xC2,
HID_KEY_KEYPAD_CARET = 0xC3,
HID_KEY_KEYPAD_PERCENT = 0xC4,
HID_KEY_KEYPAD_LESSER = 0xC5,
HID_KEY_KEYPAD_GREATER = 0xC6,
HID_KEY_KEYPAD_AND = 0xC7,
HID_KEY_KEYPAD_LOGICAL_AND = 0xC8,
HID_KEY_KEYPAD_OR = 0xC9,
HID_KEY_KEYPAD_LOGICAL_OR = 0xCA,
HID_KEY_KEYPAD_COLON = 0xCB,
HID_KEY_KEYPAD_SHARP = 0xCC,
HID_KEY_KEYPAD_SPACE = 0xCD,
HID_KEY_KEYPAD_AT = 0xCE,
HID_KEY_KEYPAD_BANG = 0xCF,
HID_KEY_KEYPAD_MEMORY_STORE = 0xD0,
HID_KEY_KEYPAD_MEMORY_RECALL = 0xD1,
HID_KEY_KEYPAD_MEMORY_CLEAD = 0xD2,
HID_KEY_KEYPAD_MEMORY_ADD = 0xD3,
HID_KEY_KEYPAD_MEMORY_SUBSTRACT = 0xD4,
HID_KEY_KEYPAD_MEMORY_MULTIPLY = 0xD5,
HID_KEY_KEYPAD_MEMORY_DIVIDE = 0xD6,
HID_KEY_KEYPAD_SIGN = 0xD7,
HID_KEY_KEYPAD_CLEAR = 0xD8,
HID_KEY_KEYPAD_CLEAR_ENTRY = 0xD9,
HID_KEY_KEYPAD_BINARY = 0xDA,
HID_KEY_KEYPAD_OCTAL = 0xDB,
HID_KEY_KEYPAD_DECIMAL = 0xDC,
HID_KEY_KEYPAD_HEXADECIMAL = 0xDD,
HID_KEY_LEFT_CONTROL = 0xE0,
HID_KEY_LEFT_SHIFT = 0xE1,
HID_KEY_LEFT_ALT = 0xE2,
HID_KEY_LEFT_GUI = 0xE3,
HID_KEY_RIGHT_CONTROL = 0xE0,
HID_KEY_RIGHT_SHIFT = 0xE1,
HID_KEY_RIGHT_ALT = 0xE2,
HID_KEY_RIGHT_GUI = 0xE3
} __attribute__((packed)) hid_key_t;
// Modifier bit mask
#define HID_LEFT_CONTROL (1 << 0)
#define HID_LEFT_SHIFT (1 << 1)
#define HID_LEFT_ALT (1 << 2)
#define HID_LEFT_GUI (1 << 3)
#define HID_RIGHT_CONTROL (1 << 4)
#define HID_RIGHT_SHIFT (1 << 5)
#define HID_RIGHT_ALT (1 << 6)
#define HID_RIGHT_GUI (1 << 7)
/**
* @brief HID Keyboard Key number for Boot Interface
*
* @see B.1, p.60 of Device Class Definition for Human Interface Devices (HID) Version 1.11
*/
typedef enum {
HID_KEYBOARD_KEY_NUMBER0 = 0,
HID_KEYBOARD_KEY_NUMBER1,
HID_KEYBOARD_KEY_NUMBER2,
HID_KEYBOARD_KEY_NUMBER3,
HID_KEYBOARD_KEY_NUMBER4,
HID_KEYBOARD_KEY_NUMBER5,
HID_KEYBOARD_KEY_MAX,
} hid_keyboard_key_number_t;
/**
* @brief HID Keyboard Input Report for Boot Interfaces
*
* @see B.1, p.60 of Device Class Definition for Human Interface Devices (HID) Version 1.11
*/
typedef struct {
union {
struct {
uint8_t left_ctr: 1;
uint8_t left_shift: 1;
uint8_t left_alt: 1;
uint8_t left_gui: 1;
uint8_t rigth_ctr: 1;
uint8_t right_shift: 1;
uint8_t right_alt: 1;
uint8_t right_gui: 1;
};
uint8_t val;
} modifier;
uint8_t reserved;
uint8_t key[HID_KEYBOARD_KEY_MAX];
} __attribute__((packed)) hid_keyboard_input_report_boot_t;
#ifdef __cplusplus
}
#endif //__cplusplus

View File

@@ -0,0 +1,36 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief HID Mouse Input Report for Boot Interfaces
*
* @see B.1, p.60 of Device Class Definition for Human Interface Devices (HID) Version 1.11
*/
typedef struct {
union {
struct {
uint8_t button1: 1;
uint8_t button2: 1;
uint8_t button3: 1;
uint8_t reserved: 5;
};
uint8_t val;
} buttons;
int8_t x_displacement;
int8_t y_displacement;
} __attribute__((packed)) hid_mouse_input_report_boot_t;
#ifdef __cplusplus
}
#endif //__cplusplus

View File

@@ -0,0 +1,15 @@
# The following lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
set(EXTRA_COMPONENT_DIRS
../../usb_host_hid
)
# Set the components to include the tests for.
if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER_EQUAL "5.0")
list(APPEND EXTRA_COMPONENT_DIRS ../../../../../device/esp_tinyusb)
endif()
project(test_app_usb_host_hid)

View File

@@ -0,0 +1,4 @@
| Supported Targets | ESP32-S2 | ESP32-S3 |
| ----------------- | -------- | -------- |
# USB: HID Class test application

View File

@@ -0,0 +1,25 @@
include($ENV{IDF_PATH}/tools/cmake/version.cmake)
set (TINYUSB_LIB)
if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER_EQUAL "5.0")
set(TINYUSB_LIB "esp_tinyusb")
else()
set(TINYUSB_LIB "tinyusb")
endif()
# TODO: once IDF_v4.4 is at the EOL support, use WHOLE_ARCHIVE
idf_component_register(SRC_DIRS .
INCLUDE_DIRS .
REQUIRES unity usb usb_host_hid ${TINYUSB_LIB})
# In order for the cases defined by `TEST_CASE` to be linked into the final elf,
# the component can be registered as WHOLE_ARCHIVE
# Due to the backward compatibility to IDFv4.4 (in which WHOLE_ARCHIVE is not implemented) we use following approach:
# Any non-static function test_app/main/*.c (apart from test_app_main.c) file is added as an undefined symbol
# because otherwise the linker will ignore test_app/main/*.c as it has no other files depending on any
# symbols in it.
# force-link test_hid_basic.c
set_property(TARGET ${COMPONENT_LIB} APPEND PROPERTY INTERFACE_LINK_LIBRARIES "-u test_hid_setup")
# force-link test_hid_err_handling.c
set_property(TARGET ${COMPONENT_LIB} APPEND PROPERTY INTERFACE_LINK_LIBRARIES "-u test_interface_callback_handler")

View File

@@ -0,0 +1,204 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include "sdkconfig.h"
#include "tinyusb.h"
#include "class/hid/hid_device.h"
#include "esp_idf_version.h"
#include "hid_mock_device.h"
static tusb_iface_count_t tusb_iface_count = 0;
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
/************* TinyUSB descriptors ****************/
#define TUSB_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN + CFG_TUD_HID * TUD_HID_DESC_LEN)
/**
* @brief HID report descriptor
*
* In this example we implement Keyboard + Mouse HID device,
* so we must define both report descriptors
*/
const uint8_t hid_report_descriptor[] = {
TUD_HID_REPORT_DESC_KEYBOARD(HID_REPORT_ID(HID_ITF_PROTOCOL_KEYBOARD) ),
TUD_HID_REPORT_DESC_MOUSE(HID_REPORT_ID(HID_ITF_PROTOCOL_MOUSE) )
};
const uint8_t hid_keyboard_report_descriptor[] = {
TUD_HID_REPORT_DESC_KEYBOARD(HID_REPORT_ID(HID_ITF_PROTOCOL_KEYBOARD) )
};
const uint8_t hid_mouse_report_descriptor[] = {
TUD_HID_REPORT_DESC_MOUSE(HID_REPORT_ID(HID_ITF_PROTOCOL_MOUSE) )
};
/**
* @brief String descriptor
*/
const char *hid_string_descriptor[5] = {
// array of pointer to string descriptors
(char[]){0x09, 0x04}, // 0: is supported language is English (0x0409)
"TinyUSB", // 1: Manufacturer
"TinyUSB Device", // 2: Product
"123456", // 3: Serials, should use chip ID
"Example HID interface", // 4: HID
};
/**
* @brief Configuration descriptor
*
* This is a simple configuration descriptor that defines 1 configuration and 1 HID interface
*/
static const uint8_t hid_configuration_descriptor_one_iface[] = {
// Configuration number, interface count, string index, total length, attribute, power in mA
TUD_CONFIG_DESCRIPTOR(1, CFG_TUD_HID, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
// Interface number, string index, boot protocol, report descriptor len, EP In address, size & polling interval
TUD_HID_DESCRIPTOR(0, 4, false, sizeof(hid_report_descriptor), 0x81, 16, 10),
TUD_HID_DESCRIPTOR(1, 4, false, sizeof(hid_report_descriptor), 0x82, 16, 10),
};
static const uint8_t hid_configuration_descriptor_two_ifaces[] = {
// Configuration number, interface count, string index, total length, attribute, power in mA
TUD_CONFIG_DESCRIPTOR(1, CFG_TUD_HID, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
// Interface number, string index, boot protocol, report descriptor len, EP In address, size & polling interval
TUD_HID_DESCRIPTOR(0, 4, HID_ITF_PROTOCOL_KEYBOARD, sizeof(hid_keyboard_report_descriptor), 0x81, 16, 10),
TUD_HID_DESCRIPTOR(2, 4, HID_ITF_PROTOCOL_MOUSE, sizeof(hid_mouse_report_descriptor), 0x82, 16, 10),
};
static const uint8_t *hid_configuration_descriptor_list[TUSB_IFACE_COUNT_MAX] = {
hid_configuration_descriptor_one_iface,
hid_configuration_descriptor_two_ifaces
};
#endif // // esp idf >= v5.0.0
/********* TinyUSB HID callbacks ***************/
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
// Invoked when received GET HID REPORT DESCRIPTOR request
// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete
uint8_t const *tud_hid_descriptor_report_cb(uint8_t instance)
{
switch (tusb_iface_count) {
case TUSB_IFACE_COUNT_ONE:
return hid_report_descriptor;
case TUSB_IFACE_COUNT_TWO:
return (!!instance) ? hid_mouse_report_descriptor : hid_keyboard_report_descriptor;
default:
break;
}
return NULL;
}
#endif // esp idf >= v5.0.0
#if ((ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)) && (ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)))
#define HID_ITF_PROTOCOL_KEYBOARD HID_PROTOCOL_KEYBOARD
#define HID_ITF_PROTOCOL_MOUSE HID_PROTOCOL_MOUSE
#endif // 4.4.0 <= esp idf < v5.0.0
/**
* @brief Get Keyboard report
*
* Fill buffer with test Keyboard report data
*
* @param[in] buffer Pointer to a buffer for filling
* @return uint16_t Length of copied data to buffer
*/
static inline uint16_t get_keyboard_report(uint8_t *buffer)
{
hid_keyboard_report_t kb_report = {
0, // Keyboard modifier
0, // Reserved
{ HID_KEY_M, HID_KEY_N, HID_KEY_O, HID_KEY_P, HID_KEY_Q, HID_KEY_R }
};
memcpy(buffer, &kb_report, sizeof(kb_report));
return sizeof(kb_report);
}
/**
* @brief Get Mouse report
*
* Fill buffer with test Mouse report data
*
* @param[in] buffer Pointer to a buffer for filling
* @return uint16_t Length of copied data to buffer
*/
static inline uint16_t get_mouse_report(uint8_t *buffer)
{
hid_mouse_report_t mouse_report = {
MOUSE_BUTTON_LEFT | MOUSE_BUTTON_RIGHT, // buttons
-1, // x
127, // y
0, // wheel
0 // pan
};
memcpy(buffer, &mouse_report, sizeof(mouse_report));
return sizeof(mouse_report);
}
// Invoked when received GET_REPORT control request
// Application must fill buffer report's content and return its length.
// Return zero will cause the stack to STALL request
uint16_t tud_hid_get_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t *buffer, uint16_t reqlen)
{
switch (report_id) {
case HID_ITF_PROTOCOL_KEYBOARD:
return get_keyboard_report(buffer);
case HID_ITF_PROTOCOL_MOUSE:
return get_mouse_report(buffer);
default:
printf("HID mock device, Unhandled ReportID %d\n", report_id);
break;
}
return 0;
}
// Invoked when received SET_REPORT control request or
// received data on OUT endpoint ( Report ID = 0, Type = 0 )
void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t const *buffer, uint16_t bufsize)
{
}
/**
* @brief HID Mock device start
*
* @param[in] iface_count Interface count, when TUSB_IFACE_COUNT_ONE then there is two Interfaces, but equal (Protocol=None).
* when TUSB_IFACE_COUNT_TWO then HID device mocked with two independent Interfaces (Protocol=BootKeyboard, Protocol=BootMouse).
*/
void hid_mock_device(tusb_iface_count_t iface_count)
{
if (iface_count > TUSB_IFACE_COUNT_MAX) {
printf("UHID mock device, wrong iface_count paramteter (%d)\n",
iface_count);
return;
}
// Global Interfaces count value
tusb_iface_count = iface_count;
const tinyusb_config_t tusb_cfg = {
.external_phy = false,
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
.device_descriptor = NULL,
.string_descriptor = hid_string_descriptor,
.string_descriptor_count = sizeof(hid_string_descriptor) / sizeof(hid_string_descriptor[0]),
.configuration_descriptor = hid_configuration_descriptor_list[tusb_iface_count],
#endif // esp idf >= v5.0.0
};
ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg));
printf("HID mock device with %s has been started\n",
(TUSB_IFACE_COUNT_ONE == tusb_iface_count)
? "1xInterface (Protocol=None)"
: "2xInterfaces (Protocol=BootKeyboard, Protocol=BootMouse)");
}

View File

@@ -0,0 +1,13 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
typedef enum {
TUSB_IFACE_COUNT_ONE = 0x00,
TUSB_IFACE_COUNT_TWO = 0x01,
TUSB_IFACE_COUNT_MAX
} tusb_iface_count_t;
void hid_mock_device(tusb_iface_count_t iface_count);

View File

@@ -0,0 +1,57 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include "unity.h"
#include "esp_heap_caps.h"
static size_t before_free_8bit;
static size_t before_free_32bit;
#define TEST_MEMORY_LEAK_THRESHOLD (-530)
static void check_leak(size_t before_free, size_t after_free, const char *type)
{
ssize_t delta = after_free - before_free;
printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\n", type, before_free, after_free, delta);
TEST_ASSERT_MESSAGE(delta >= TEST_MEMORY_LEAK_THRESHOLD, "memory leak");
}
void app_main(void)
{
// ____ ___ ___________________ __ __
// | | \/ _____/\______ \ _/ |_ ____ _______/ |_
// | | /\_____ \ | | _/ \ __\/ __ \ / ___/\ __\.
// | | / / \ | | \ | | \ ___/ \___ \ | |
// |______/ /_______ / |______ / |__| \___ >____ > |__|
// \/ \/ \/ \/
printf(" ____ ___ ___________________ __ __ \r\n");
printf("| | \\/ _____/\\______ \\ _/ |_ ____ _______/ |_ \r\n");
printf("| | /\\_____ \\ | | _/ \\ __\\/ __ \\ / ___/\\ __\\\r\n");
printf("| | / / \\ | | \\ | | \\ ___/ \\___ \\ | | \r\n");
printf("|______/ /_______ / |______ / |__| \\___ >____ > |__| \r\n");
printf(" \\/ \\/ \\/ \\/ \r\n");
UNITY_BEGIN();
unity_run_menu();
UNITY_END();
}
/* setUp runs before every test */
void setUp(void)
{
before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
}
/* tearDown runs after every test */
void tearDown(void)
{
size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
check_leak(before_free_8bit, after_free_8bit, "8BIT");
check_leak(before_free_32bit, after_free_32bit, "32BIT");
}

View File

@@ -0,0 +1,684 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include "unity.h"
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include "esp_private/usb_phy.h"
#include "usb/usb_host.h"
#include "usb/hid_host.h"
#include "usb/hid_usage_keyboard.h"
#include "usb/hid_usage_mouse.h"
#include "test_hid_basic.h"
#include "hid_mock_device.h"
// USB PHY for device discinnection emulation
static usb_phy_handle_t phy_hdl = NULL;
// Global variable to verify user arg passing through callbacks
static uint32_t user_arg_value = 0x8A53E0A4; // Just a constant renadom number
// Queue and task for possibility to interact with USB device
// IMPORTANT: Interaction is not possible within device/interface callback
static bool time_to_shutdown = false;
static bool time_to_stop_polling = false;
QueueHandle_t hid_host_test_event_queue;
TaskHandle_t hid_test_task_handle;
// Multiple tasks testing
static hid_host_device_handle_t global_hdl;
static int test_num_passed;
static const char *test_hid_sub_class_names[] = {
"NO_SUBCLASS",
"BOOT_INTERFACE",
};
static const char *test_hid_proto_names[] = {
"NONE",
"KEYBOARD",
"MOUSE"
};
typedef struct {
hid_host_device_handle_t hid_device_handle;
hid_host_driver_event_t event;
void *arg;
} hid_host_test_event_queue_t;
typedef enum {
HID_HOST_TEST_TOUCH_WAY_ASSERT = 0x00,
HID_HOST_TEST_TOUCH_WAY_SUDDEN_DISCONNECT = 0x01,
} hid_host_test_touch_way_t;
static void force_conn_state(bool connected, TickType_t delay_ticks)
{
TEST_ASSERT_NOT_NULL(phy_hdl);
if (delay_ticks > 0) {
//Delay of 0 ticks causes a yield. So skip if delay_ticks is 0.
vTaskDelay(delay_ticks);
}
ESP_ERROR_CHECK(usb_phy_action(phy_hdl, (connected) ? USB_PHY_ACTION_HOST_ALLOW_CONN : USB_PHY_ACTION_HOST_FORCE_DISCONN));
}
void hid_host_test_interface_callback(hid_host_device_handle_t hid_device_handle,
const hid_host_interface_event_t event,
void *arg)
{
uint8_t data[64] = { 0 };
size_t data_length = 0;
hid_host_dev_params_t dev_params;
TEST_ASSERT_EQUAL(ESP_OK, hid_host_device_get_params(hid_device_handle, &dev_params));
TEST_ASSERT_EQUAL_PTR_MESSAGE(&user_arg_value, arg, "User argument has lost");
switch (event) {
case HID_HOST_INTERFACE_EVENT_INPUT_REPORT:
printf("USB port %d, Interface num %d: ",
dev_params.addr,
dev_params.iface_num);
hid_host_device_get_raw_input_report_data(hid_device_handle,
data,
64,
&data_length);
for (int i = 0; i < data_length; i++) {
printf("%02x ", data[i]);
}
printf("\n");
break;
case HID_HOST_INTERFACE_EVENT_DISCONNECTED:
printf("USB port %d, iface num %d removed\n",
dev_params.addr,
dev_params.iface_num);
TEST_ASSERT_EQUAL(ESP_OK, hid_host_device_close(hid_device_handle) );
break;
case HID_HOST_INTERFACE_EVENT_TRANSFER_ERROR:
printf("USB Host transfer error\n");
break;
default:
TEST_FAIL_MESSAGE("HID Interface unhandled event");
break;
}
}
void hid_host_test_callback(hid_host_device_handle_t hid_device_handle,
const hid_host_driver_event_t event,
void *arg)
{
hid_host_dev_params_t dev_params;
TEST_ASSERT_EQUAL(ESP_OK, hid_host_device_get_params(hid_device_handle, &dev_params));
TEST_ASSERT_EQUAL_PTR_MESSAGE(&user_arg_value, arg, "User argument has lost");
switch (event) {
case HID_HOST_DRIVER_EVENT_CONNECTED:
printf("USB port %d, interface %d, '%s', '%s'\n",
dev_params.addr,
dev_params.iface_num,
test_hid_sub_class_names[dev_params.sub_class],
test_hid_proto_names[dev_params.proto]);
const hid_host_device_config_t dev_config = {
.callback = hid_host_test_interface_callback,
.callback_arg = (void *) &user_arg_value
};
TEST_ASSERT_EQUAL(ESP_OK, hid_host_device_open(hid_device_handle, &dev_config) );
TEST_ASSERT_EQUAL(ESP_OK, hid_host_device_start(hid_device_handle) );
break;
default:
TEST_FAIL_MESSAGE("HID Driver unhandled event");
break;
}
}
void hid_host_test_concurrent(hid_host_device_handle_t hid_device_handle,
const hid_host_driver_event_t event,
void *arg)
{
hid_host_dev_params_t dev_params;
TEST_ASSERT_EQUAL(ESP_OK, hid_host_device_get_params(hid_device_handle, &dev_params));
TEST_ASSERT_EQUAL_PTR_MESSAGE(&user_arg_value, arg, "User argument has lost");
switch (event) {
case HID_HOST_DRIVER_EVENT_CONNECTED:
printf("USB port %d, interface %d, '%s', '%s'\n",
dev_params.addr,
dev_params.iface_num,
test_hid_sub_class_names[dev_params.sub_class],
test_hid_proto_names[dev_params.proto]);
const hid_host_device_config_t dev_config = {
.callback = hid_host_test_interface_callback,
.callback_arg = &user_arg_value
};
TEST_ASSERT_EQUAL(ESP_OK, hid_host_device_open(hid_device_handle, &dev_config) );
TEST_ASSERT_EQUAL(ESP_OK, hid_host_device_start(hid_device_handle) );
global_hdl = hid_device_handle;
break;
default:
TEST_FAIL_MESSAGE("HID Driver unhandled event");
break;
}
}
void hid_host_test_device_callback_to_queue(hid_host_device_handle_t hid_device_handle,
const hid_host_driver_event_t event,
void *arg)
{
const hid_host_test_event_queue_t evt_queue = {
.hid_device_handle = hid_device_handle,
.event = event,
.arg = arg
};
xQueueSend(hid_host_test_event_queue, &evt_queue, 0);
}
void hid_host_test_requests_callback(hid_host_device_handle_t hid_device_handle,
const hid_host_driver_event_t event,
void *arg)
{
hid_host_dev_params_t dev_params;
TEST_ASSERT_EQUAL(ESP_OK, hid_host_device_get_params(hid_device_handle, &dev_params));
TEST_ASSERT_EQUAL_PTR_MESSAGE(&user_arg_value, arg, "User argument has lost");
uint8_t *test_buffer = NULL; // for report descriptor
unsigned int test_length = 0;
uint8_t tmp[10] = { 0 }; // for input report
size_t rep_len = 0;
switch (event) {
case HID_HOST_DRIVER_EVENT_CONNECTED:
printf("USB port %d, interface %d, '%s', '%s'\n",
dev_params.addr,
dev_params.iface_num,
test_hid_sub_class_names[dev_params.sub_class],
test_hid_proto_names[dev_params.proto]);
const hid_host_device_config_t dev_config = {
.callback = hid_host_test_interface_callback,
.callback_arg = &user_arg_value
};
TEST_ASSERT_EQUAL(ESP_OK, hid_host_device_open(hid_device_handle, &dev_config) );
// Class device requests
// hid_host_get_report_descriptor
test_buffer = hid_host_get_report_descriptor(hid_device_handle, &test_length);
TEST_ASSERT_NOT_NULL(test_buffer);
printf("HID Report descriptor length: %d\n", test_length);
// // HID Device info
hid_host_dev_info_t hid_dev_info;
TEST_ASSERT_EQUAL(ESP_OK, hid_host_get_device_info(hid_device_handle,
&hid_dev_info) );
printf("\t VID: 0x%04X\n", hid_dev_info.VID);
printf("\t PID: 0x%04X\n", hid_dev_info.PID);
wprintf(L"\t iProduct: %S \n", hid_dev_info.iProduct);
wprintf(L"\t iManufacturer: %S \n", hid_dev_info.iManufacturer);
wprintf(L"\t iSerialNumber: %S \n", hid_dev_info.iSerialNumber);
if (dev_params.proto == HID_PROTOCOL_NONE) {
// If Protocol NONE, based on hid1_11.pdf, p.78, all other devices should support
rep_len = sizeof(tmp);
// For testing with ESP32 we used ReportID = 0x01 (Keyboard ReportID)
if (ESP_OK == hid_class_request_get_report(hid_device_handle,
HID_REPORT_TYPE_INPUT, 0x01, tmp, &rep_len)) {
printf("HID Get Report, type %d, id %d, length: %d:\n",
HID_REPORT_TYPE_INPUT, 0, rep_len);
for (int i = 0; i < rep_len; i++) {
printf("%02X ", tmp[i]);
}
printf("\n");
}
rep_len = sizeof(tmp);
// For testing with ESP32 we used ReportID = 0x02 (Mouse ReportID)
if (ESP_OK == hid_class_request_get_report(hid_device_handle,
HID_REPORT_TYPE_INPUT, 0x02, tmp, &rep_len)) {
printf("HID Get Report, type %d, id %d, length: %d:\n",
HID_REPORT_TYPE_INPUT, 0, rep_len);
for (int i = 0; i < rep_len; i++) {
printf("%02X ", tmp[i]);
}
printf("\n");
}
} else {
// hid_class_request_get_protocol
hid_report_protocol_t proto;
if (ESP_OK == hid_class_request_get_protocol(hid_device_handle, &proto)) {
printf("HID protocol: %d\n", proto);
}
if (dev_params.proto == HID_PROTOCOL_KEYBOARD) {
uint8_t idle_rate;
// hid_class_request_get_idle
if (ESP_OK == hid_class_request_get_idle(hid_device_handle,
0, &idle_rate)) {
printf("HID idle rate: %d\n", idle_rate);
}
// hid_class_request_set_idle
if (ESP_OK == hid_class_request_set_idle(hid_device_handle,
0, 0)) {
printf("HID idle rate set to 0\n");
}
// hid_class_request_get_report
rep_len = sizeof(tmp);
if (ESP_OK == hid_class_request_get_report(hid_device_handle,
HID_REPORT_TYPE_INPUT, 0x01, tmp, &rep_len)) {
printf("HID get report type %d, id %d, length: %d\n",
HID_REPORT_TYPE_INPUT, 0x00, rep_len);
}
// hid_class_request_set_report
uint8_t rep[1] = { 0x00 };
if (ESP_OK == hid_class_request_set_report(hid_device_handle,
HID_REPORT_TYPE_OUTPUT, 0x01, rep, 1)) {
printf("HID set report type %d, id %d\n", HID_REPORT_TYPE_OUTPUT, 0x00);
}
}
if (dev_params.proto == HID_PROTOCOL_MOUSE) {
// hid_class_request_get_report
rep_len = sizeof(tmp);
if (ESP_OK == hid_class_request_get_report(hid_device_handle,
HID_REPORT_TYPE_INPUT, 0x02, tmp, &rep_len)) {
printf("HID get report type %d, id %d, length: %d\n",
HID_REPORT_TYPE_INPUT, 0x00, rep_len);
}
}
// hid_class_request_set_protocol
if (ESP_OK == hid_class_request_set_protocol(hid_device_handle,
HID_REPORT_PROTOCOL_BOOT)) {
printf("HID protocol change to BOOT: %d\n", proto);
}
}
TEST_ASSERT_EQUAL(ESP_OK, hid_host_device_start(hid_device_handle) );
break;
default:
TEST_FAIL_MESSAGE("HID Driver unhandled event");
break;
}
}
void hid_host_test_task(void *pvParameters)
{
hid_host_test_event_queue_t evt_queue;
// Create queue
hid_host_test_event_queue = xQueueCreate(10, sizeof(hid_host_test_event_queue_t));
// Wait queue
while (!time_to_shutdown) {
if (xQueueReceive(hid_host_test_event_queue, &evt_queue, pdMS_TO_TICKS(50))) {
hid_host_test_requests_callback(evt_queue.hid_device_handle,
evt_queue.event,
evt_queue.arg);
}
}
xQueueReset(hid_host_test_event_queue);
vQueueDelete(hid_host_test_event_queue);
vTaskDelete(NULL);
}
void hid_host_test_polling_task(void *pvParameters)
{
// Wait queue
while (!time_to_stop_polling) {
hid_host_handle_events(portMAX_DELAY);
}
vTaskDelete(NULL);
}
static void test_hid_host_device_touch(hid_host_dev_params_t *dev_params,
hid_host_test_touch_way_t touch_way)
{
uint8_t tmp[10] = { 0 }; // for input report
size_t rep_len = 0;
hid_report_protocol_t proto;
if (dev_params->proto == HID_PROTOCOL_NONE) {
rep_len = sizeof(tmp);
// For testing with ESP32 we used ReportID = 0x01 (Keyboard ReportID)
if (HID_HOST_TEST_TOUCH_WAY_ASSERT == touch_way) {
TEST_ASSERT_EQUAL(ESP_OK, hid_class_request_get_report(global_hdl,
HID_REPORT_TYPE_INPUT, 0x01, tmp, &rep_len));
} else {
hid_class_request_get_report(global_hdl,
HID_REPORT_TYPE_INPUT, 0x01, tmp, &rep_len);
}
} else {
// Get Protocol
TEST_ASSERT_EQUAL(ESP_OK, hid_class_request_get_protocol(global_hdl, &proto));
// Get Report for Keyboard protocol, ReportID = 0x00 (Boot Keyboard ReportID)
if (dev_params->proto == HID_PROTOCOL_KEYBOARD) {
rep_len = sizeof(tmp);
if (HID_HOST_TEST_TOUCH_WAY_ASSERT == touch_way) {
TEST_ASSERT_EQUAL(ESP_OK, hid_class_request_get_report(global_hdl,
HID_REPORT_TYPE_INPUT, 0x01, tmp, &rep_len));
} else {
hid_class_request_get_report(global_hdl,
HID_REPORT_TYPE_INPUT, 0x01, tmp, &rep_len);
}
}
if (dev_params->proto == HID_PROTOCOL_MOUSE) {
rep_len = sizeof(tmp);
if (HID_HOST_TEST_TOUCH_WAY_ASSERT == touch_way) {
TEST_ASSERT_EQUAL(ESP_OK, hid_class_request_get_report(global_hdl,
HID_REPORT_TYPE_INPUT, 0x02, tmp, &rep_len));
} else {
hid_class_request_get_report(global_hdl,
HID_REPORT_TYPE_INPUT, 0x02, tmp, &rep_len);
}
}
}
}
#define MULTIPLE_TASKS_TASKS_NUM 10
void concurrent_task(void *arg)
{
uint8_t *test_buffer = NULL;
unsigned int test_length = 0;
hid_host_dev_params_t dev_params;
TEST_ASSERT_EQUAL(ESP_OK, hid_host_device_get_params(global_hdl, &dev_params));
// Get Report descriptor
test_buffer = hid_host_get_report_descriptor(global_hdl, &test_length);
TEST_ASSERT_NOT_NULL(test_buffer);
test_hid_host_device_touch(&dev_params, HID_HOST_TEST_TOUCH_WAY_ASSERT);
test_num_passed++;
vTaskDelete(NULL);
}
void access_task(void *arg)
{
uint8_t *test_buffer = NULL;
unsigned int test_length = 0;
hid_host_dev_params_t dev_params;
TEST_ASSERT_EQUAL(ESP_OK, hid_host_device_get_params(global_hdl, &dev_params));
// Get Report descriptor
test_buffer = hid_host_get_report_descriptor(global_hdl, &test_length);
TEST_ASSERT_NOT_NULL(test_buffer);
while (!time_to_shutdown) {
test_hid_host_device_touch(&dev_params, HID_HOST_TEST_TOUCH_WAY_ASSERT/* HID_HOST_TEST_TOUCH_WAY_SUDDEN_DISCONNECT */);
}
vTaskDelete(NULL);
}
/**
* @brief Creates MULTIPLE_TASKS_TASKS_NUM to get report descriptor and get protocol from HID device.
* After test_num_passed - is a global static variable that increases during every successful task test.
*/
void test_multiple_tasks_access(void)
{
// Create tasks that will try to access HID dev with global hdl
for (int i = 0; i < MULTIPLE_TASKS_TASKS_NUM; i++) {
TEST_ASSERT_EQUAL(pdTRUE, xTaskCreate(concurrent_task, "HID multi touch", 4096, NULL, i + 3, NULL));
}
// Wait until all tasks finish
vTaskDelay(pdMS_TO_TICKS(500));
}
void test_task_access(void)
{
// Create task which will be touching the device with control requests, while device is present
TEST_ASSERT_EQUAL(pdTRUE, xTaskCreate(access_task, "HID touch", 4096, NULL, 3, NULL));
}
/**
* @brief Start USB Host and handle common USB host library events while devices/clients are present
*
* @param[in] arg Main task handle
*/
static void usb_lib_task(void *arg)
{
// Initialize the internal USB PHY to connect to the USB OTG peripheral.
// We manually install the USB PHY for testing
usb_phy_config_t phy_config = {
.controller = USB_PHY_CTRL_OTG,
.target = USB_PHY_TARGET_INT,
.otg_mode = USB_OTG_MODE_HOST,
.otg_speed = USB_PHY_SPEED_UNDEFINED, //In Host mode, the speed is determined by the connected device
};
TEST_ASSERT_EQUAL(ESP_OK, usb_new_phy(&phy_config, &phy_hdl));
const usb_host_config_t host_config = {
.skip_phy_setup = true,
.intr_flags = ESP_INTR_FLAG_LEVEL1,
};
TEST_ASSERT_EQUAL(ESP_OK, usb_host_install(&host_config) );
printf("USB Host installed\n");
xTaskNotifyGive(arg);
bool all_clients_gone = false;
bool all_dev_free = false;
while (!all_clients_gone || !all_dev_free) {
uint32_t event_flags;
usb_host_lib_handle_events(portMAX_DELAY, &event_flags);
// Release devices once all clients has deregistered
if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) {
usb_host_device_free_all();
printf("USB Event flags: NO_CLIENTS\n");
all_clients_gone = true;
}
// All devices were removed
if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) {
printf("USB Event flags: ALL_FREE\n");
all_dev_free = true;
time_to_stop_polling = true;
// Notify that device was being disconnected
xTaskNotifyGive(arg);
}
}
// Change global flag for all tasks still running
time_to_shutdown = true;
// Clean up USB Host
vTaskDelay(10); // Short delay to allow clients clean-up
TEST_ASSERT_EQUAL(ESP_OK, usb_host_uninstall());
TEST_ASSERT_EQUAL(ESP_OK, usb_del_phy(phy_hdl)); //Tear down USB PHY
phy_hdl = NULL;
vTaskDelete(NULL);
}
// ----------------------- Public -------------------------
/**
* @brief Setups HID testing
*
* - Create USB lib task
* - Install HID Host driver
*/
void test_hid_setup(hid_host_driver_event_cb_t device_callback,
hid_test_event_handle_t hid_test_event_handle)
{
TEST_ASSERT_EQUAL(pdTRUE, xTaskCreatePinnedToCore(usb_lib_task,
"usb_events",
4096,
xTaskGetCurrentTaskHandle(),
2, NULL, 0));
// Wait for notification from usb_lib_task
ulTaskNotifyTake(false, 1000);
// HID host driver config
const hid_host_driver_config_t hid_host_driver_config = {
.create_background_task = (hid_test_event_handle == HID_TEST_EVENT_HANDLE_IN_DRIVER)
? true
: false,
.task_priority = 5,
.stack_size = 4096,
.core_id = 0,
.callback = device_callback,
.callback_arg = (void *) &user_arg_value
};
TEST_ASSERT_EQUAL(ESP_OK, hid_host_install(&hid_host_driver_config) );
}
/**
* @brief Teardowns HID testing
* - Disconnect connected USB device manually by PHY triggering
* - Wait for USB lib task was closed
* - Uninstall HID Host driver
* - Clear the notification value to 0
* - Short delay to allow task to be cleaned up
*/
void test_hid_teardown(void)
{
force_conn_state(false, pdMS_TO_TICKS(1000));
vTaskDelay(50);
TEST_ASSERT_EQUAL(ESP_OK, hid_host_uninstall() );
ulTaskNotifyValueClear(NULL, 1);
vTaskDelay(20);
}
// ------------------------- HID Test ------------------------------------------
static void test_setup_hid_task(void)
{
// Task is working until the devices are gone
time_to_shutdown = false;
// Create process
TEST_ASSERT_EQUAL(pdTRUE, xTaskCreate(&hid_host_test_task,
"hid_task",
4 * 1024,
NULL,
3,
&hid_test_task_handle));
}
static void test_setup_hid_polling_task(void)
{
time_to_stop_polling = false;
TEST_ASSERT_EQUAL(pdTRUE, xTaskCreate(&hid_host_test_polling_task,
"hid_task_polling",
4 * 1024,
NULL, 2, NULL));
}
TEST_CASE("memory_leakage", "[hid_host]")
{
// Install USB and HID driver with the regular 'hid_host_test_callback'
test_hid_setup(hid_host_test_callback, HID_TEST_EVENT_HANDLE_IN_DRIVER);
// Tear down test
test_hid_teardown();
// Verify the memory leackage during test environment tearDown()
}
TEST_CASE("multiple_task_access", "[hid_host]")
{
// Install USB and HID driver with 'hid_host_test_concurrent'
test_hid_setup(hid_host_test_concurrent, HID_TEST_EVENT_HANDLE_IN_DRIVER);
// Wait for USB device appearing for 250 msec
vTaskDelay(250);
// Refresh the num passed test value
test_num_passed = 0;
// Start multiple task access to USB device with control requests
test_multiple_tasks_access();
// Tear down test
test_hid_teardown();
// Verify how much tests was done
TEST_ASSERT_EQUAL(MULTIPLE_TASKS_TASKS_NUM, test_num_passed);
// Verify the memory leackage during test environment tearDown()
}
TEST_CASE("class_specific_requests", "[hid_host]")
{
// Create external HID events task
test_setup_hid_task();
// Install USB and HID driver with 'hid_host_test_device_callback_to_queue'
test_hid_setup(hid_host_test_device_callback_to_queue, HID_TEST_EVENT_HANDLE_IN_DRIVER);
// All specific control requests will be verified during device connection callback 'hid_host_test_requests_callback'
// Wait for test completed for 250 ms
vTaskDelay(250);
// Tear down test
test_hid_teardown();
// Verify the memory leackage during test environment tearDown()
}
TEST_CASE("class_specific_requests_with_external_polling", "[hid_host]")
{
// Create external HID events task
test_setup_hid_task();
// Install USB and HID driver with 'hid_host_test_device_callback_to_queue'
test_hid_setup(hid_host_test_device_callback_to_queue, HID_TEST_EVENT_HANDLE_EXTERNAL);
// Create HID Driver events polling task
test_setup_hid_polling_task();
// All specific control requests will be verified during device connection callback 'hid_host_test_requests_callback'
// Wait for test completed for 250 ms
vTaskDelay(250);
// Tear down test
test_hid_teardown();
// Verify the memory leackage during test environment tearDown()
}
TEST_CASE("class_specific_requests_with_external_polling_without_polling", "[hid_host]")
{
// Create external HID events task
test_setup_hid_task();
// Install USB and HID driver with 'hid_host_test_device_callback_to_queue'
test_hid_setup(hid_host_test_device_callback_to_queue, HID_TEST_EVENT_HANDLE_EXTERNAL);
// Do not create HID Driver events polling task to eliminate events polling
// ...
// Wait for 250 ms
vTaskDelay(250);
// Tear down test
test_hid_teardown();
// Verify the memory leackage during test environment tearDown()
}
TEST_CASE("sudden_disconnect", "[hid_host]")
{
// Install USB and HID driver with 'hid_host_test_concurrent'
test_hid_setup(hid_host_test_concurrent, HID_TEST_EVENT_HANDLE_IN_DRIVER);
// Wait for USB device appearing for 250 msec
vTaskDelay(250);
// Start task to access USB device with control requests
test_task_access();
// Tear down test during thr task_access stress the HID device
test_hid_teardown();
}
TEST_CASE("mock_hid_device", "[hid_device][ignore]")
{
hid_mock_device(TUSB_IFACE_COUNT_ONE);
while (1) {
vTaskDelay(10);
}
}
TEST_CASE("mock_hid_device_with_two_ifaces", "[hid_device2][ignore]")
{
hid_mock_device(TUSB_IFACE_COUNT_TWO);
while (1) {
vTaskDelay(10);
}
}

View File

@@ -0,0 +1,29 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "usb/hid_host.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
HID_TEST_EVENT_HANDLE_IN_DRIVER = 0,
HID_TEST_EVENT_HANDLE_EXTERNAL
} hid_test_event_handle_t;
// ------------------------ HID Test -------------------------------------------
void test_hid_setup(hid_host_driver_event_cb_t device_callback,
hid_test_event_handle_t hid_test_event_handle);
void test_hid_teardown(void);
#ifdef __cplusplus
}
#endif //__cplusplus

View File

@@ -0,0 +1,213 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include "unity.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "usb/hid_host.h"
#include "test_hid_basic.h"
// ----------------------- Private -------------------------
/**
* @brief USB HID Host interface callback.
*
* Handle close event only.
*
* @param[in] event HID Host device event
* @param[in] arg Pointer to arguments, does not used
*
*/
static void test_hid_host_interface_event_close(hid_host_device_handle_t hid_device_handle,
const hid_host_interface_event_t event,
void *arg)
{
switch (event) {
case HID_HOST_INTERFACE_EVENT_INPUT_REPORT:
case HID_HOST_INTERFACE_EVENT_TRANSFER_ERROR:
break;
case HID_HOST_INTERFACE_EVENT_DISCONNECTED:
TEST_ASSERT_EQUAL(ESP_OK, hid_host_device_close(hid_device_handle) );
break;
}
}
/**
* @brief USB HID Host event callback stub.
*
* Does not handle anything.
*
* @param[in] event HID Host device event
* @param[in] arg Pointer to arguments, does not used
*
*/
static void test_hid_host_event_callback_stub(hid_host_device_handle_t hid_device_handle,
const hid_host_driver_event_t event,
void *arg)
{
if (event == HID_HOST_DRIVER_EVENT_CONNECTED) {
// Device connected
}
}
/**
* @brief USB HID Host event callback.
*
* Handle connected event and open a device.
*
* @param[in] event HID Host device event
* @param[in] arg Pointer to arguments, does not used
*
*/
static void test_hid_host_event_callback_open(hid_host_device_handle_t hid_device_handle,
const hid_host_driver_event_t event,
void *arg)
{
if (event == HID_HOST_DRIVER_EVENT_CONNECTED) {
const hid_host_device_config_t dev_config = {
.callback = test_hid_host_interface_event_close,
.callback_arg = NULL
};
TEST_ASSERT_EQUAL(ESP_OK, hid_host_device_open(hid_device_handle, &dev_config) );
}
}
// Install HID driver without USB Host and without configuration
static void test_install_hid_driver_without_config(void)
{
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, hid_host_install(NULL));
}
// Install HID driver without USB Host and with configuration
static void test_install_hid_driver_with_wrong_config(void)
{
const hid_host_driver_config_t hid_host_config_callback_null = {
.create_background_task = true,
.task_priority = 5,
.stack_size = 4096,
.core_id = 0,
.callback = NULL, /* error expected */
.callback_arg = NULL
};
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, hid_host_install(&hid_host_config_callback_null));
const hid_host_driver_config_t hid_host_config_stack_size_null = {
.create_background_task = true,
.task_priority = 5,
.stack_size = 0, /* error expected */
.core_id = 0,
.callback = test_hid_host_event_callback_stub,
.callback_arg = NULL
};
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, hid_host_install(&hid_host_config_stack_size_null));
const hid_host_driver_config_t hid_host_config_task_priority_null = {
.create_background_task = true,
.task_priority = 0,/* error expected */
.stack_size = 4096,
.core_id = 0,
.callback = test_hid_host_event_callback_stub,
.callback_arg = NULL
};
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, hid_host_install(&hid_host_config_task_priority_null));
const hid_host_driver_config_t hid_host_config_correct = {
.create_background_task = true,
.task_priority = 5,
.stack_size = 4096,
.core_id = 0,
.callback = test_hid_host_event_callback_stub,
.callback_arg = NULL
};
// Invalid state without USB Host installed
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, hid_host_install(&hid_host_config_correct));
}
void test_interface_callback_handler(hid_host_device_handle_t hid_device_handle,
const hid_host_interface_event_t event,
void *arg)
{
// ...
}
// Open device without installed driver
static void test_claim_interface_without_driver(void)
{
hid_host_device_handle_t hid_dev_handle = NULL;
const hid_host_device_config_t dev_config = {
.callback = test_interface_callback_handler,
.callback_arg = NULL
};
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE,
hid_host_device_open(hid_dev_handle, &dev_config) );
}
static void test_install_hid_driver_when_already_installed(void)
{
// Install USB and HID driver with the stub test_hid_host_event_callback_stub
test_hid_setup(test_hid_host_event_callback_stub, HID_TEST_EVENT_HANDLE_IN_DRIVER);
// Try to install HID driver again
const hid_host_driver_config_t hid_host_config = {
.create_background_task = true,
.task_priority = 5,
.stack_size = 4096,
.core_id = 0,
.callback = test_hid_host_event_callback_stub,
.callback_arg = NULL
};
// Verify error code
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, hid_host_install(&hid_host_config));
// Tear down test
test_hid_teardown();
}
static void test_uninstall_hid_driver_while_device_was_not_opened(void)
{
// Install USB and HID driver with the stub test_hid_host_event_callback_stub
test_hid_setup(test_hid_host_event_callback_stub, HID_TEST_EVENT_HANDLE_IN_DRIVER);
// Tear down test
test_hid_teardown();
}
static void test_uninstall_hid_driver_while_device_is_present(void)
{
// Install USB and HID driver with the stub test_hid_host_event_callback_stub
test_hid_setup(test_hid_host_event_callback_open, HID_TEST_EVENT_HANDLE_IN_DRIVER);
// Wait for USB device appearing for 250 msec
vTaskDelay(250);
// Uninstall HID Driver while device is still connected and verify a result
printf("HID Driver uninstall attempt while HID Device is still present ...\n");
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, hid_host_uninstall());
// Tear down test
test_hid_teardown();
}
// ----------------------- Public --------------------------
/**
* @brief HID Error handling test
*
* There are multiple erroneous scenarios checked in this test.
*
*/
TEST_CASE("error_handling", "[hid_host]")
{
test_install_hid_driver_without_config();
test_install_hid_driver_with_wrong_config();
test_claim_interface_without_driver();
test_install_hid_driver_when_already_installed();
test_uninstall_hid_driver_while_device_was_not_opened();
test_uninstall_hid_driver_while_device_is_present();
}

View File

@@ -0,0 +1,35 @@
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
from typing import Tuple
import pytest
from pytest_embedded_idf.dut import IdfDut
@pytest.mark.esp32s2
@pytest.mark.esp32s3
@pytest.mark.usb_host
@pytest.mark.parametrize('count', [
2,
], indirect=True)
def test_usb_host_hid(dut: Tuple[IdfDut, IdfDut]) -> None:
device = dut[0]
host = dut[1]
# 3.1 Prepare USB device with one Interface for HID tests
device.expect_exact('Press ENTER to see the list of tests.')
device.write('[hid_device]')
device.expect_exact('HID mock device with 1xInterface (Protocol=None) has been started')
# 3.2 Run HID tests
host.run_all_single_board_cases(group='hid_host')
# 3.3 Prepare USB device with two Interfaces for HID tests
device.serial.hard_reset()
device.expect_exact('Press ENTER to see the list of tests.')
device.write('[hid_device2]')
device.expect_exact('HID mock device with 2xInterfaces (Protocol=BootKeyboard, Protocol=BootMouse) has been started')
# 3.4 Run HID tests
host.run_all_single_board_cases(group='hid_host')

View File

@@ -0,0 +1,19 @@
# Configure TinyUSB, it will be used to mock USB devices
CONFIG_TINYUSB=y
CONFIG_TINYUSB_MSC_ENABLED=n
CONFIG_TINYUSB_CDC_ENABLED=n
CONFIG_TINYUSB_CDC_COUNT=0
CONFIG_TINYUSB_HID_COUNT=2
# Disable watchdogs, they'd get triggered during unity interactive menu
CONFIG_ESP_INT_WDT=n
CONFIG_ESP_TASK_WDT=n
# Run-time checks of Heap and Stack
CONFIG_HEAP_POISONING_COMPREHENSIVE=y
CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y
CONFIG_COMPILER_STACK_CHECK=y
CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL=y
CONFIG_COMPILER_CXX_EXCEPTIONS=y