Compare commits

..

28 Commits

Author SHA1 Message Date
840a561de4 Merge pull request #710 from david-cermak/feat/mosq_p2p_example
[mosq]: Add serverless broker example
2024-12-19 17:11:13 +01:00
e6fb8aa078 bump(mosq): 2.0.18~0 -> 2.0.20~0
2.0.20~0
Features
- Upgrade to mosquitto v2.0.20 (3b2c614d)
- Add support for on-message callback (cdeab8f5)
- Add example with two brokers synced on P2P (d57b8c5b)
Bug Fixes
- Fix dependency issues moving esp-tls to public deps (6cce87e4)
2024-12-19 16:51:49 +01:00
3b2c614d86 feat(mosq): Upgrade to mosquitto v2.0.20
Used tagged version v2.0.20
2024-12-19 16:41:37 +01:00
cdeab8f517 feat(mosq): Add support for on-message callback 2024-12-19 16:41:37 +01:00
6cce87e465 fix(mosq): Fix dependency issues moving esp-tls to public deps
Since esp-tls structs are using in public header files
2024-12-19 16:41:37 +01:00
d57b8c5b29 feat(mosq): Add example with two brokers synced on P2P
Broker-less two chip example which virtual private IoT
networks on MQTT protocol.
2024-12-19 16:40:02 +01:00
9c11003449 Merge pull request #713 from david-cermak/feat/sock_utiis_0.2
[sock-utils]: Bump 0.1 -> 0.2
2024-12-19 11:13:59 +01:00
85a7fc772c bump(sockutls): 0.1.0 -> 0.2.0
0.2.0
Features
- Declare socketpair and gai_strerror via standard headers (b090a3cb)
- Add support for gethostname() (f7c0b756)
2024-12-19 10:57:36 +01:00
b090a3cb69 feat(sockutls): Declare socketpair and gai_strerror via standard headers
Adding a reverse dependency to lwip and define macros, which
enable declarations of socketpair() and gai_strerror() in standard
heders (sys/socket.h and netdb.h)
2024-12-19 10:54:56 +01:00
42cde46c97 Merge pull request #714 from bryghtlabs-richard/chore/websocketTidying
Chore(websocket) tidy up two small issues
2024-12-17 11:35:57 +01:00
beb6e57e5e chore(websocket): align structure members 2024-12-16 16:20:21 -06:00
15d3a01e11 chore(websocket): remove unused client variable 2024-12-16 15:46:43 -06:00
e12ecb8e89 Merge pull request #707 from david-cermak/bugfix/sckutls_gethostname
[sock-utils]: Add support for gethostname()
2024-12-16 16:36:32 +01:00
54271a1b96 Merge pull request #709 from david-cermak/bump/modem_1.3
[modem]: bump 1.2.1 -> 1.3.0
2024-12-12 09:46:27 +01:00
886215032f bump(modem): 1.2.1 -> 1.3.0
1.3.0
Features
- Add mode detection to the example (18f196fa)
- Support for pausing network in C-API (1db83cd1)
- Add support for pausing netif (247f1681, #699)
Bug Fixes
- Minor cleanup of pppos example (5e929902)
- Fix PPP mode detection to accept LCP/conf (c989c6ad)
- Refine mode switch data->command (8b6ea331, #692)
- Detect serial ports properly (0cb59ff8)
- Fix CMUX enter to ignore URC before transition (1284f66d, #669)
2024-12-10 12:48:09 +01:00
269351f41c Merge pull request #700 from david-cermak/feat/modem_pause_network
[modem]: Add support for pausing netif
2024-12-10 12:14:10 +01:00
5e929902c7 fix(modem): Minor cleanup of pppos example
* Use CONFIG_EXAMPLE_DETECT_MODE_BEFORE_CONNECT to demonstrate mode
detect
* Use disconnection flag to indicate conneciton issue and gracefully
degrade to command mode
* Remove IDF-verion < v5.0 code
2024-12-10 11:36:10 +01:00
f7c0b7564a feat(sockutls): Add support for gethostname()
Closes https://github.com/espressif/esp-idf/issues/14849
2024-12-09 17:16:59 +01:00
c989c6adae fix(modem): Fix PPP mode detection to accept LCP/conf 2024-12-06 10:12:25 +01:00
18f196fa1e feat(modem): Add mode detection to the example 2024-12-06 09:48:00 +01:00
1db83cd1ca feat(modem): Support for pausing network in C-API
also adds a demo of this feature to pppos client example
2024-12-05 20:16:45 +01:00
247f1681e8 feat(modem): Add support for pausing netif
Closes https://github.com/espressif/esp-protocols/issues/699
2024-12-05 20:16:41 +01:00
32387f7e39 Merge pull request #702 from david-cermak/fix/modem_data_cmd_mode_refine
[modem]: Refine data -> command transition
2024-12-04 15:42:43 +01:00
dbc3ea6809 fix(common): Export IDF environment in bash shell
To avoid issues with IDF_PATH/export with the latest tools
2024-12-04 14:44:43 +01:00
8b6ea3311a fix(modem): Refine mode switch data->command
* netif.stop() moved after setting the transition callback
* send PPP escape sequence if enabled before waiting for transition to
complete
* add newline character before sync() command (after the "+++")

Closes https://github.com/espressif/esp-protocols/issues/692
2024-12-04 14:39:01 +01:00
8e55b93b59 Merge pull request #703 from david-cermak/fix/modem_cmux_data_before_switch
[modem]: CMUX: ignore URC before entering CMUX
2024-12-04 08:18:54 +01:00
0cb59ff80d fix(modem): Detect serial ports properly 2024-11-29 18:27:26 +01:00
1284f66d58 fix(modem): Fix CMUX enter to ignore URC before transition
Closes https://github.com/espressif/esp-protocols/issues/669
2024-11-29 17:32:35 +01:00
57 changed files with 1282 additions and 57 deletions

View File

@ -24,6 +24,7 @@ jobs:
chmod +x clang-tidy-sarif
curl -sSL https://raw.githubusercontent.com/espressif/idf-extra-components/master/.github/filter_sarif.py -o filter_sarif.py
- name: Install pyclang
shell: bash
run: |
. ${IDF_PATH}/export.sh
pip install pyclang~=0.2.0

View File

@ -80,7 +80,12 @@ jobs:
name: modem_target_bin_${{ matrix.idf_target }}_${{ matrix.idf_ver }}_${{ matrix.test.app }}
path: ${{ env.TEST_DIR }}/build
- name: Run Example Test on target
working-directory: ${{ env.TEST_DIR }}
env:
PIP_EXTRA_INDEX_URL: "https://dl.espressif.com/pypi/"
run: |
python -m pip install -r $GITHUB_WORKSPACE/ci/requirements.txt
python -m venv .venv
source .venv/bin/activate
pip install --prefer-binary cryptography pytest-embedded pytest-embedded-serial-esp pytest-embedded-idf pytest-custom_exit_code esptool
pip install -r $GITHUB_WORKSPACE/ci/requirements.txt
cd ${{ env.TEST_DIR }}
python -m pytest --log-cli-level DEBUG --target=${{ matrix.idf_target }}

View File

@ -17,7 +17,8 @@ jobs:
runs-on: ubuntu-22.04
container: espressif/idf:${{ matrix.idf_ver }}
env:
TEST_DIR: components/mosquitto/examples/broker
TEST_DIR: components/mosquitto/examples
TARGET_TEST: broker
TARGET_TEST_DIR: build_esp32_default
steps:
- name: Checkout esp-protocols
@ -29,14 +30,15 @@ jobs:
run: |
. ${IDF_PATH}/export.sh
pip install idf-component-manager idf-build-apps --upgrade
python ci/build_apps.py ${TEST_DIR}
cd ${TEST_DIR}
python ci/build_apps.py -c ${TEST_DIR} -m components/mosquitto/.build-test-rules.yml
# upload only the target test artifacts
cd ${TEST_DIR}/${TARGET_TEST}
${GITHUB_WORKSPACE}/ci/clean_build_artifacts.sh `pwd`/${TARGET_TEST_DIR}
zip -qur artifacts.zip ${TARGET_TEST_DIR}
- uses: actions/upload-artifact@v4
with:
name: mosq_target_esp32_${{ matrix.idf_ver }}
path: ${{ env.TEST_DIR }}/artifacts.zip
path: ${{ env.TEST_DIR }}/${{ env.TARGET_TEST }}/artifacts.zip
if-no-files-found: error
test_mosq:

View File

@ -0,0 +1 @@
components/mosquitto/examples/serverless_mqtt/components/libjuice/port/juice_random.c

View File

@ -3,6 +3,6 @@ commitizen:
bump_message: 'bump(modem): $current_version -> $new_version'
pre_bump_hooks: python ../../ci/changelog.py esp_modem
tag_format: modem-v$version
version: 1.2.1
version: 1.3.0
version_files:
- idf_component.yml

View File

@ -1,5 +1,21 @@
# Changelog
## [1.3.0](https://github.com/espressif/esp-protocols/commits/modem-v1.3.0)
### Features
- Add mode detection to the example ([18f196fa](https://github.com/espressif/esp-protocols/commit/18f196fa))
- Support for pausing network in C-API ([1db83cd1](https://github.com/espressif/esp-protocols/commit/1db83cd1))
- Add support for pausing netif ([247f1681](https://github.com/espressif/esp-protocols/commit/247f1681), [#699](https://github.com/espressif/esp-protocols/issues/699))
### Bug Fixes
- Minor cleanup of pppos example ([5e929902](https://github.com/espressif/esp-protocols/commit/5e929902))
- Fix PPP mode detection to accept LCP/conf ([c989c6ad](https://github.com/espressif/esp-protocols/commit/c989c6ad))
- Refine mode switch data->command ([8b6ea331](https://github.com/espressif/esp-protocols/commit/8b6ea331), [#692](https://github.com/espressif/esp-protocols/issues/692))
- Detect serial ports properly ([0cb59ff8](https://github.com/espressif/esp-protocols/commit/0cb59ff8))
- Fix CMUX enter to ignore URC before transition ([1284f66d](https://github.com/espressif/esp-protocols/commit/1284f66d), [#669](https://github.com/espressif/esp-protocols/issues/669))
## [1.2.1](https://github.com/espressif/esp-protocols/commits/modem-v1.2.1)
### Bug Fixes

View File

@ -76,4 +76,20 @@ menu "esp-modem"
help
If enabled, APIs to add URC handler are available
config ESP_MODEM_PPP_ESCAPE_BEFORE_EXIT
bool "Send escape sequence when switching PPP -> CMD"
default n
help
If enabled, the library sends a PPP escape ("+++" command)
to switch to command mode. This make switching from PPP to CMD
mode more robust for some devices (e.g. Quectel), but might cause
trouble for other devices (e.g. SIMCOM).
config ESP_MODEM_ADD_DEBUG_LOGS
bool "Add UART Tx/Rx logs"
default n
help
If enabled, the library dumps all transmitted and received data.
This option is only used for debugging.
endmenu

View File

@ -385,6 +385,17 @@ extern "C" void app_main(void)
return 0;
});
#endif
const ConsoleCommand PauseNetwork("pause_net", "toggle network pause", no_args, [&](ConsoleCommand * c) {
static int cnt = 0;
if (++cnt % 2) {
ESP_LOGI(TAG, "Pausing netif");
dce->pause_netif(true);
} else {
ESP_LOGI(TAG, "Unpausing netif");
dce->pause_netif(false);
}
return 0;
});
const struct SetApn {
SetApn(): apn(STR1, nullptr, nullptr, "<apn>", "APN (Access Point Name)") {}

View File

@ -201,4 +201,23 @@ menu "Example Configuration"
help
MQTT data message, which we publish and expect to receive.
config EXAMPLE_PAUSE_NETIF_TO_CHECK_SIGNAL
bool "Demonstrate netif pause"
default n
help
Set this to true to demonstrate network pausing.
If enabled, the example waits for an MQTT data, then temporarily
drops network to check signal quality, resumes networking and
publishes another MQTT message.
Connection to the MQTT broker should be kept.
config EXAMPLE_DETECT_MODE_BEFORE_CONNECT
bool "Detect mode before connect"
default n
help
Set this to true to demonstrate mode auto-detection.
If enabled, the example tries to recognize the actual mode.
If mode is detected correctly and it is not a command mode,
then the example switches to command mode.
endmenu

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
@ -34,6 +34,7 @@
static const char *TAG = "pppos_example";
static EventGroupHandle_t event_group = NULL;
static const int CONNECT_BIT = BIT0;
static const int DISCONNECT_BIT = BIT1;
static const int GOT_DATA_BIT = BIT2;
static const int USB_DISCONNECTED_BIT = BIT3; // Used only with USB DTE but we define it unconditionally, to avoid too many #ifdefs in the code
@ -55,6 +56,7 @@ static void usb_terminal_error_handler(esp_modem_terminal_error_t err)
}
#define CHECK_USB_DISCONNECTION(event_group) \
if ((xEventGroupGetBits(event_group) & USB_DISCONNECTED_BIT) == USB_DISCONNECTED_BIT) { \
ESP_LOGE(TAG, "USB_DISCONNECTED_BIT destroying modem dce"); \
esp_modem_destroy(dce); \
continue; \
}
@ -140,6 +142,7 @@ static void on_ip_event(void *arg, esp_event_base_t event_base,
ESP_LOGI(TAG, "GOT ip event!!!");
} else if (event_id == IP_EVENT_PPP_LOST_IP) {
ESP_LOGI(TAG, "Modem Disconnect from PPP Server");
xEventGroupSetBits(event_group, DISCONNECT_BIT);
} else if (event_id == IP_EVENT_GOT_IP6) {
ESP_LOGI(TAG, "GOT IPv6 event!");
@ -158,6 +161,7 @@ void app_main(void)
ESP_ERROR_CHECK(esp_event_handler_register(NETIF_PPP_STATUS, ESP_EVENT_ANY_ID, &on_ppp_changed, NULL));
/* Configure the PPP netif */
esp_err_t err;
esp_modem_dce_config_t dce_config = ESP_MODEM_DCE_DEFAULT_CONFIG(CONFIG_EXAMPLE_MODEM_PPP_APN);
esp_netif_config_t netif_ppp_config = ESP_NETIF_DEFAULT_PPP();
esp_netif_t *esp_netif = esp_netif_new(&netif_ppp_config);
@ -205,7 +209,7 @@ void app_main(void)
#endif
assert(dce);
if (dte_config.uart_config.flow_control == ESP_MODEM_FLOW_CONTROL_HW) {
esp_err_t err = esp_modem_set_flow_control(dce, 2, 2); //2/2 means HW Flow Control.
err = esp_modem_set_flow_control(dce, 2, 2); //2/2 means HW Flow Control.
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to set the set_flow_control mode");
return;
@ -246,7 +250,27 @@ void app_main(void)
#error Invalid serial connection to modem.
#endif
xEventGroupClearBits(event_group, CONNECT_BIT | GOT_DATA_BIT | USB_DISCONNECTED_BIT);
#if CONFIG_EXAMPLE_DETECT_MODE_BEFORE_CONNECT
xEventGroupClearBits(event_group, CONNECT_BIT | GOT_DATA_BIT | USB_DISCONNECTED_BIT | DISCONNECT_BIT);
err = esp_modem_set_mode(dce, ESP_MODEM_MODE_DETECT);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_modem_set_mode(ESP_MODEM_MODE_DETECT) failed with %d", err);
return;
}
esp_modem_dce_mode_t mode = esp_modem_get_mode(dce);
ESP_LOGI(TAG, "Mode detection completed: current mode is: %d", mode);
if (mode == ESP_MODEM_MODE_DATA) { // set back to command mode
err = esp_modem_set_mode(dce, ESP_MODEM_MODE_COMMAND);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_modem_set_mode(ESP_MODEM_MODE_COMMAND) failed with %d", err);
return;
}
ESP_LOGI(TAG, "Command mode restored");
}
#endif // CONFIG_EXAMPLE_DETECT_MODE_BEFORE_CONNECT
xEventGroupClearBits(event_group, CONNECT_BIT | GOT_DATA_BIT | USB_DISCONNECTED_BIT | DISCONNECT_BIT);
/* Run the modem demo app */
#if CONFIG_EXAMPLE_NEED_SIM_PIN == 1
@ -262,7 +286,7 @@ void app_main(void)
#endif
int rssi, ber;
esp_err_t err = esp_modem_get_signal_quality(dce, &rssi, &ber);
err = esp_modem_get_signal_quality(dce, &rssi, &ber);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_modem_get_signal_quality failed with %d %s", err, esp_err_to_name(err));
return;
@ -301,22 +325,41 @@ void app_main(void)
}
/* Wait for IP address */
ESP_LOGI(TAG, "Waiting for IP address");
xEventGroupWaitBits(event_group, CONNECT_BIT | USB_DISCONNECTED_BIT, pdFALSE, pdFALSE, portMAX_DELAY);
xEventGroupWaitBits(event_group, CONNECT_BIT | USB_DISCONNECTED_BIT | DISCONNECT_BIT, pdFALSE, pdFALSE,
pdMS_TO_TICKS(60000));
CHECK_USB_DISCONNECTION(event_group);
if ((xEventGroupGetBits(event_group) & CONNECT_BIT) != CONNECT_BIT) {
ESP_LOGW(TAG, "Modem not connected, switching back to the command mode");
err = esp_modem_set_mode(dce, ESP_MODEM_MODE_COMMAND);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_modem_set_mode(ESP_MODEM_MODE_COMMAND) failed with %d", err);
return;
}
ESP_LOGI(TAG, "Command mode restored");
return;
}
/* Config MQTT */
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
esp_mqtt_client_config_t mqtt_config = {
.broker.address.uri = CONFIG_EXAMPLE_MQTT_BROKER_URI,
};
#else
esp_mqtt_client_config_t mqtt_config = {
.uri = CONFIG_EXAMPLE_MQTT_BROKER_URI,
};
#endif
esp_mqtt_client_handle_t mqtt_client = esp_mqtt_client_init(&mqtt_config);
esp_mqtt_client_register_event(mqtt_client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL);
esp_mqtt_client_start(mqtt_client);
#if CONFIG_EXAMPLE_PAUSE_NETIF_TO_CHECK_SIGNAL
xEventGroupWaitBits(event_group, GOT_DATA_BIT, pdTRUE, pdFALSE, portMAX_DELAY);
esp_modem_pause_net(dce, true);
err = esp_modem_get_signal_quality(dce, &rssi, &ber);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_modem_get_signal_quality failed with %d", err);
return;
}
ESP_LOGI(TAG, "Signal quality: rssi=%d, ber=%d", rssi, ber);
esp_modem_pause_net(dce, false);
esp_mqtt_client_publish(mqtt_client, CONFIG_EXAMPLE_MQTT_TEST_TOPIC, CONFIG_EXAMPLE_MQTT_TEST_DATA, 0, 0, 0);
#endif // CONFIG_EXAMPLE_PAUSE_NETIF_TO_CHECK_SIGNAL
ESP_LOGI(TAG, "Waiting for MQTT data");
xEventGroupWaitBits(event_group, GOT_DATA_BIT | USB_DISCONNECTED_BIT, pdFALSE, pdFALSE, portMAX_DELAY);
CHECK_USB_DISCONNECTION(event_group);

View File

@ -12,7 +12,7 @@ def test_pppos_connect(dut):
4. checks that the client cleanly disconnects
"""
# Check the sequence of connecting, publishing, disconnecting
dut.expect('Modem Connect to PPP Server')
dut.expect('Modem Connect to PPP Server', timeout=90)
# Check for MQTT connection and the data event
dut.expect('MQTT_EVENT_CONNECTED')
dut.expect('MQTT_EVENT_DATA')

View File

@ -11,5 +11,7 @@ CONFIG_EXAMPLE_MODEM_DEVICE_SIM800=y
CONFIG_EXAMPLE_MODEM_DEVICE_BG96=n
CONFIG_EXAMPLE_MODEM_PPP_APN="lpwa.vodafone.com"
CONFIG_EXAMPLE_MQTT_TEST_TOPIC="/ci/esp-modem/pppos-client"
CONFIG_EXAMPLE_PAUSE_NETIF_TO_CHECK_SIGNAL=y
CONFIG_ESP_SYSTEM_PANIC_PRINT_HALT=y
CONFIG_ESP32_PANIC_PRINT_HALT=y
CONFIG_EXAMPLE_DETECT_MODE_BEFORE_CONNECT=y

View File

@ -16,3 +16,4 @@ CONFIG_COMPILER_CXX_EXCEPTIONS=y
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
CONFIG_EXAMPLE_CLOSE_CMUX_AT_END=y
CONFIG_EXAMPLE_MQTT_TEST_TOPIC="/ci/esp-modem/pppos-client"
CONFIG_BROKER_URI="mqtt://mqtt.eclipseprojects.io"

View File

@ -1,4 +1,4 @@
version: "1.2.1"
version: "1.3.0"
description: Library for communicating with cellular modems in command and data modes
url: https://github.com/espressif/esp-protocols/tree/master/components/esp_modem
issues: https://github.com/espressif/esp-protocols/issues

View File

@ -91,6 +91,11 @@ public:
return mode.set(dte.get(), device.get(), netif, m);
}
modem_mode get_mode()
{
return mode.get();
}
bool recover()
{
return dte->recover();
@ -103,6 +108,29 @@ public:
}
#endif
/**
* @brief Pauses/Unpauses network temporarily
* @param do_pause true to pause, false to unpause
* @param force true to ignore command failures and continue
* @return command_result of the underlying commands
*/
command_result pause_netif(bool do_pause, bool force = false, int delay = 1000)
{
command_result result;
if (do_pause) {
netif.pause();
Task::Delay(delay); // Mandatory 1s pause before
dte->set_command_callbacks();
result = device->set_command_mode();
} else {
result = device->resume_data_mode();
if (result == command_result::OK || force) {
netif.resume();
}
}
return result;
}
protected:
std::shared_ptr<DTE> dte;
std::shared_ptr<SpecificModule> device;

View File

@ -77,7 +77,9 @@ public:
if (set_command_mode() == command_result::OK) {
return true;
}
Task::Delay(1000); // Mandatory 1s pause after escape
// send a newline to delimit the escape from the upcoming sync command
uint8_t delim = '\n';
dte->write(&delim, 1);
if (sync() == command_result::OK) {
return true;
}

View File

@ -145,6 +145,12 @@ public:
*/
bool recover();
/**
* @brief Set internal command callbacks to the underlying terminal.
* Here we capture command replies to be processed by supplied command callbacks in struct command_cb.
*/
void set_command_callbacks();
protected:
/**
* @brief Allows for locking the DTE
@ -204,12 +210,6 @@ private:
} inflatable;
#endif // CONFIG_ESP_MODEM_USE_INFLATABLE_BUFFER_IF_NEEDED
/**
* @brief Set internal command callbacks to the underlying terminal.
* Here we capture command replies to be processed by supplied command callbacks in struct command_cb.
*/
void set_command_callbacks();
/**
* @brief This abstracts command callback processing and implements its locking, signaling of completion and timeouts.
*/

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -54,6 +54,16 @@ public:
*/
void stop();
/**
* @brief Pause the network interface
*/
void pause();
/**
* @brief Resume the network interface
*/
void resume();
void receive(uint8_t *data, size_t len);
private:

View File

@ -45,6 +45,8 @@ typedef enum esp_modem_dce_mode {
ESP_MODEM_MODE_CMUX_MANUAL_SWAP, /**< Swap terminals in CMUX manual mode */
ESP_MODEM_MODE_CMUX_MANUAL_DATA, /**< Set DATA mode in CMUX manual mode */
ESP_MODEM_MODE_CMUX_MANUAL_COMMAND, /**< Set COMMAND mode in CMUX manual mode */
ESP_MODEM_MODE_DETECT, /**< Detect the mode and resume it (if sucessfully detected) */
ESP_MODEM_MODE_UNDEF,
} esp_modem_dce_mode_t;
/**
@ -160,6 +162,18 @@ esp_err_t esp_modem_set_apn(esp_modem_dce_t *dce, const char *apn);
esp_err_t esp_modem_set_urc(esp_modem_dce_t *dce, esp_err_t(*got_line_cb)(uint8_t *data, size_t len));
#endif
/**
* @brief This API provides support for temporarily pausing networking in order
* to send/receive AT commands and resume networking afterwards.
* @note This function does not switch modes, the modem is still in data mode.
*
* @param dce Modem DCE handle
* @param pause true to pause the network interface, false to resume networking
* @return ESP_OK on success
*/
esp_err_t esp_modem_pause_net(esp_modem_dce_t *dce, bool pause);
esp_modem_dce_mode_t esp_modem_get_mode(esp_modem_dce_t *dce);
/**
* @}
*/

View File

@ -95,12 +95,43 @@ extern "C" esp_err_t esp_modem_sync(esp_modem_dce_t *dce_wrap)
return command_response_to_esp_err(dce_wrap->dce->sync());
}
extern "C" esp_modem_dce_mode_t esp_modem_get_mode(esp_modem_dce_t *dce_wrap)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_MODEM_MODE_UNDEF;
}
auto mode = dce_wrap->dce->get_mode();
switch (mode) {
default:
case modem_mode::UNDEF:
return ESP_MODEM_MODE_UNDEF;
case modem_mode::COMMAND_MODE:
return ESP_MODEM_MODE_COMMAND;
case modem_mode::DATA_MODE:
return ESP_MODEM_MODE_DATA;
case modem_mode::CMUX_MODE:
return ESP_MODEM_MODE_CMUX;
case modem_mode::CMUX_MANUAL_MODE:
return ESP_MODEM_MODE_CMUX_MANUAL;
case modem_mode::CMUX_MANUAL_EXIT:
return ESP_MODEM_MODE_CMUX_MANUAL_EXIT;
case modem_mode::CMUX_MANUAL_DATA:
return ESP_MODEM_MODE_CMUX_MANUAL_DATA;
case modem_mode::CMUX_MANUAL_COMMAND:
return ESP_MODEM_MODE_CMUX_MANUAL_COMMAND;
case modem_mode::CMUX_MANUAL_SWAP:
return ESP_MODEM_MODE_CMUX_MANUAL_SWAP;
}
}
extern "C" esp_err_t esp_modem_set_mode(esp_modem_dce_t *dce_wrap, esp_modem_dce_mode_t mode)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
switch (mode) {
case ESP_MODEM_MODE_UNDEF:
return dce_wrap->dce->set_mode(modem_mode::UNDEF) ? ESP_OK : ESP_FAIL;
case ESP_MODEM_MODE_DATA:
return dce_wrap->dce->set_mode(modem_mode::DATA_MODE) ? ESP_OK : ESP_FAIL;
case ESP_MODEM_MODE_COMMAND:
@ -117,6 +148,8 @@ extern "C" esp_err_t esp_modem_set_mode(esp_modem_dce_t *dce_wrap, esp_modem_dce
return dce_wrap->dce->set_mode(modem_mode::CMUX_MANUAL_DATA) ? ESP_OK : ESP_FAIL;
case ESP_MODEM_MODE_CMUX_MANUAL_COMMAND:
return dce_wrap->dce->set_mode(modem_mode::CMUX_MANUAL_COMMAND) ? ESP_OK : ESP_FAIL;
case ESP_MODEM_MODE_DETECT:
return dce_wrap->dce->set_mode(modem_mode::AUTODETECT) ? ESP_OK : ESP_FAIL;
}
return ESP_ERR_NOT_SUPPORTED;
}
@ -475,3 +508,19 @@ extern "C" esp_err_t esp_modem_set_urc(esp_modem_dce_t *dce_wrap, esp_err_t(*got
return ESP_OK;
}
#endif
extern "C" esp_err_t esp_modem_pause_net(esp_modem_dce_t *dce_wrap, bool pause)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
return command_response_to_esp_err(dce_wrap->dce->pause_netif(pause));
}
extern "C" esp_err_t esp_modem_hang_up(esp_modem_dce_t *dce_wrap)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
return command_response_to_esp_err(dce_wrap->dce->hang_up());
}

View File

@ -123,7 +123,12 @@ bool CMux::data_available(uint8_t *data, size_t len)
{
if (data && (type & FT_UIH) == FT_UIH && len > 0 && dlci > 0) { // valid payload on a virtual term
int virtual_term = dlci - 1;
if (virtual_term < MAX_TERMINALS_NUM && read_cb[virtual_term]) {
if (virtual_term < MAX_TERMINALS_NUM) {
if (read_cb[virtual_term] == nullptr) {
// ignore all virtual terminal's data before we completely establish CMUX
ESP_LOG_BUFFER_HEXDUMP("CMUX Rx before init", data, len, ESP_LOG_DEBUG);
return true;
}
// Post partial data (or defragment to post on CMUX footer)
#ifdef DEFRAGMENT_CMUX_PAYLOAD
if (payload_start == nullptr) {
@ -142,7 +147,11 @@ bool CMux::data_available(uint8_t *data, size_t len)
sabm_ack = dlci;
} else if (data == nullptr && dlci > 0) {
int virtual_term = dlci - 1;
if (virtual_term < MAX_TERMINALS_NUM && read_cb[virtual_term]) {
if (virtual_term < MAX_TERMINALS_NUM) {
if (read_cb[virtual_term] == nullptr) {
// silently ignore this CMUX frame (not finished entering CMUX, yet)
return true;
}
#ifdef DEFRAGMENT_CMUX_PAYLOAD
read_cb[virtual_term](payload_start, total_payload_size);
#endif

View File

@ -18,7 +18,6 @@ namespace transitions {
static bool exit_data(DTE &dte, ModuleIf &device, Netif &netif)
{
netif.stop();
auto signal = std::make_shared<SignalGroup>();
std::weak_ptr<SignalGroup> weak_signal = signal;
dte.set_read_cb([&netif, weak_signal](uint8_t *data, size_t len) -> bool {
@ -32,7 +31,7 @@ static bool exit_data(DTE &dte, ModuleIf &device, Netif &netif)
if (memchr(data, '\n', len))
{
ESP_LOG_BUFFER_HEXDUMP("esp-modem: debug_data (CMD)", data, len, ESP_LOG_DEBUG);
const auto pass = std::list<std::string_view>({"NO CARRIER", "DISCONNECTED"});
const auto pass = std::list<std::string_view>({"NO CARRIER", "DISCONNECTED", "OK"});
std::string_view response((char *) data, len);
for (auto &it : pass)
if (response.find(it) != std::string::npos) {
@ -44,8 +43,14 @@ static bool exit_data(DTE &dte, ModuleIf &device, Netif &netif)
}
return false;
});
netif.stop();
netif.wait_until_ppp_exits();
if (!signal->wait(1, 2000)) {
#ifdef ESP_MODEM_PPP_ESCAPE_BEFORE_EXIT
std::array<uint8_t, 3> ppp_escape = {'+', '+', '+'};
dte.write(ppp_escape.data(), ppp_escape.size());
#endif
if (!signal->wait(1, 2000)) { // wait for any of the disconnection messages
// if no reply -> set device to command mode
dte.set_read_cb(nullptr);
if (!device.set_mode(modem_mode::COMMAND_MODE)) {
return false;
@ -323,12 +328,19 @@ modem_mode DCE_Mode::guess_unsafe(DTE *dte, bool with_cmux)
if (reply_pos >= sizeof(probe::ppp::lcp_echo_reply_head)) {
// check for initial 2 bytes
auto *ptr = static_cast<uint8_t *>(memmem(reply, reply_pos, probe::ppp::lcp_echo_reply_head.data(), 2));
// and check the other two bytes for protocol ID: LCP
// and check the other two bytes for protocol ID:
// * either LCP reply
if (ptr && ptr[3] == probe::ppp::lcp_echo_reply_head[3] && ptr[4] == probe::ppp::lcp_echo_reply_head[4]) {
if (auto signal = weak_signal.lock()) {
signal->set(probe::ppp::mode);
}
}
// * or LCP conf request
if (ptr && ptr[3] == probe::ppp::lcp_echo_request[3] && ptr[4] == probe::ppp::lcp_echo_request[4]) {
if (auto signal = weak_signal.lock()) {
signal->set(probe::ppp::mode);
}
}
}
if (reply_pos >= 4 && memmem(reply, reply_pos, probe::cmd::reply, sizeof(probe::cmd::reply))) {
if (reply[0] != 0xf9) { // double check that the reply is not wrapped in CMUX headers

View File

@ -99,6 +99,20 @@ void Netif::stop()
signal.clear(PPP_STARTED);
}
void Netif::resume()
{
ppp_dte->set_read_cb([this](uint8_t *data, size_t len) -> bool {
receive(data, len);
return true;
});
signal.set(PPP_STARTED);
}
void Netif::pause()
{
signal.clear(PPP_STARTED);
}
Netif::~Netif()
{
if (signal.is_any(PPP_STARTED)) {

View File

@ -52,7 +52,6 @@ void Netif::start()
void Netif::stop()
{
ppp_dte->set_read_cb(nullptr);
signal.clear(PPP_STARTED);
}

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -176,13 +176,20 @@ int UartTerminal::read(uint8_t *data, size_t len)
uart_get_buffered_data_len(uart.port, &length);
length = std::min(len, length);
if (length > 0) {
return uart_read_bytes(uart.port, data, length, portMAX_DELAY);
int read_len = uart_read_bytes(uart.port, data, length, portMAX_DELAY);
#if CONFIG_ESP_MODEM_ADD_DEBUG_LOGS
ESP_LOG_BUFFER_HEXDUMP("uart-rx", data, read_len, ESP_LOG_DEBUG);
#endif
return read_len;
}
return 0;
}
int UartTerminal::write(uint8_t *data, size_t len)
{
#if CONFIG_ESP_MODEM_ADD_DEBUG_LOGS
ESP_LOG_BUFFER_HEXDUMP("uart-tx", data, len, ESP_LOG_DEBUG);
#endif
return uart_write_bytes_compat(uart.port, data, len);
}

View File

@ -9,6 +9,20 @@ from threading import Event, Thread
import netifaces
def is_esp32(port):
"""
Check if the given port is connected to an ESP32 using esptool.
"""
try:
result = subprocess.run(
['esptool.py', '--port', port, 'chip_id'],
stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True, text=True
)
return 'ESP32' in result.stdout
except subprocess.CalledProcessError:
return False
def run_server(server_stop, port, server_ip, client_ip, auth, auth_user, auth_password):
print('Starting PPP server on port: {}'.format(port))
try:
@ -66,13 +80,27 @@ def test_examples_protocol_pppos_connect(dut):
)
raise
# the PPP test env uses two ttyUSB's: one for ESP32 board, another one for ppp server
# use the other port for PPP server than the DUT/ESP
port = '/dev/ttyUSB0' if dut.serial.port == '/dev/ttyUSB1' else '/dev/ttyUSB1'
# the PPP test env uses three ttyUSB's: two for ESP32 board and another one for the ppp server
# we need to detect the server_port (for PPPD)
server_port = None
for i in ['/dev/ttyUSB0', '/dev/ttyUSB1', '/dev/ttyUSB2']:
if i == dut.serial.port:
print(f'DUT port: {i}')
elif is_esp32(i):
print(f'Some other ESP32: {i}')
else:
print(f'Port for PPPD: {i}')
server_port = i
if server_port is None:
print(
'ENV_TEST_FAILURE: Cannot locate PPPD port'
)
raise
# Start the PPP server
server_stop = Event()
t = Thread(target=run_server,
args=(server_stop, port, server_ip, client_ip, auth, auth_user, auth_password))
args=(server_stop, server_port, server_ip, client_ip, auth, auth_user, auth_password))
t.start()
try:
ppp_server_timeout = time.time() + 30

View File

@ -122,12 +122,11 @@ struct esp_websocket_client {
uint64_t ping_tick_ms;
uint64_t pingpong_tick_ms;
int wait_timeout_ms;
int auto_reconnect;
bool run;
bool wait_for_pong_resp;
bool selected_for_destroying;
EventGroupHandle_t status_bits;
SemaphoreHandle_t lock;
SemaphoreHandle_t lock;
size_t errormsg_size;
char *errormsg_buffer;
char *rx_buffer;

View File

@ -0,0 +1,3 @@
components/mosquitto/examples/serverless_mqtt:
disable:
- if: IDF_TARGET not in ["esp32", "esp32s3", "esp32c3"]

View File

@ -3,6 +3,6 @@ commitizen:
bump_message: 'bump(mosq): $current_version -> $new_version'
pre_bump_hooks: python ../../ci/changelog.py mosquitto
tag_format: mosq-v$version
version: 2.0.28~0
version: 2.0.20
version_files:
- idf_component.yml

View File

@ -1,5 +1,23 @@
# Changelog
## [2.0.20](https://github.com/espressif/esp-protocols/commits/mosq-v2.0.20)
### Features
- Upgrade to mosquitto v2.0.20 ([3b2c614d](https://github.com/espressif/esp-protocols/commit/3b2c614d))
- Add support for on-message callback ([cdeab8f5](https://github.com/espressif/esp-protocols/commit/cdeab8f5))
- Add example with two brokers synced on P2P ([d57b8c5b](https://github.com/espressif/esp-protocols/commit/d57b8c5b))
### Bug Fixes
- Fix dependency issues moving esp-tls to public deps ([6cce87e4](https://github.com/espressif/esp-protocols/commit/6cce87e4))
## [2.0.28~0](https://github.com/espressif/esp-protocols/commits/mosq-v2.0.28_0)
### Warning
Incorrect version number! This version published under `2.0.28~0` is based on upstream v2.0.18
### Features
- Added support for TLS transport using ESP-TLS ([1af4bbe1](https://github.com/espressif/esp-protocols/commit/1af4bbe1))

View File

@ -81,7 +81,8 @@ idf_component_register(SRCS ${m_srcs}
PRIV_INCLUDE_DIRS port/priv_include port/priv_include/sys ${m_dir} ${m_src_dir}
${m_incl_dir} ${m_lib_dir} ${m_deps_dir}
INCLUDE_DIRS ${m_incl_dir} port/include
PRIV_REQUIRES newlib esp-tls
REQUIRES esp-tls
PRIV_REQUIRES newlib
)
target_compile_definitions(${COMPONENT_LIB} PRIVATE "WITH_BROKER")

View File

@ -0,0 +1,6 @@
# The following five 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)
project(serverless_mqtt)

View File

@ -0,0 +1,53 @@
# Brokerless MQTT Example
MQTT served by (two) mosquitto's running on two ESP chips.
* Leverages MQTT connectivity between two private networks without cloud premisses.
* Creates two local MQTT servers (on ESP32x's) which are being synchronized over peer to peer connection (established via ICE protocol, by [libjuice](https://github.com/paullouisageneau/libjuice)).
## How it works
This example needs two ESP32 chipsets, that will create two separate Wi-Fi networks (IoT networks) used for IoT devices.
Each IoT network is served by an MQTT server (using mosquitto component).
This example will also synchronize these two MQTT brokers, as if there was only one IoT network with one broker.
This example creates a peer to peer connection between two chipsets to keep them synchronize. This connection utilizes libjuice (which implements a simplified ICE-UDP) to traverse NATs, which enabling direct connection between two private networks behind NATs.
* Diagram
![demo](serverless.png)
Here's a step-by-step procedure of establishing this remote connection:
1) Initialize and start Wi-Fi AP (for IoT networks) and Wi-Fi station (for internet connection)
2) Start mosquitto broker on IoT network
3) Start libjuice to gather connection candidates
4) Synchronize using a public MQTT broker and exchange ICE descriptors
5) Establish ICE UDP connection between the two ESP32 chipsets
6) Start forwarding mqtt messages
- Each remote datagram (received from ICE-UDP channel) is re-published to the local MQTT server
- Each local MQTT message (received from mosquitto on_message callback) is sent in ICE-UDP datagram
## How to use this example
You need two ESP32 devices that support Wi-Fi station and Wi-Fi software access point.
* Configure Wi-Fi credentials for both devices on both interfaces
* These devices would be deployed in distinct Wi-Fi environments, so the Wi-Fi station credentials would likely be different.
* They also create their own IoT network (on the soft-AP interface) Wi-Fi, so the AP credentials would likely be the same, suggesting the IoT networks will be keep synchronized (even though these are two distict Wi-Fi networks).
* Choose `CONFIG_EXAMPLE_SERVERLESS_ROLE_PEER1` for one device and `CONFIG_EXAMPLE_SERVERLESS_ROLE_PEER2` for another. It's not important which device is PEER1, since the code is symmetric, but these two devices need to have different role.
* Optionally: You can use `idf.py` `-D` and `-B` flag to keep separate build directories and sdkconfigs for these two roles
```
idf.py -B build1 -DSDKCONFIG=build1/sdkconfig menuconfig build flash monitor
```
* Flash and run the two devices and wait for them to connect and synchronize.
* Now you can test MQTT connectivity, for example:
* Join PEER1 device's AP and connect to the MQTT broker with one or more clients, subscribing to one or more topics.
* Join PEER2 device's AP and connect to the MQTT broker with one or more clients, subscribing to one or more topics.
* Whenever you publish to a topic, all subscribed clients should receive the message, no matter which Wi-Fi network they're connected to.
## Warning
This example uses libjuice as a dependency:
* libjuice (UDP Interactive Connectivity Establishment): https://github.com/paullouisageneau/libjuice
which is distributed under Mozilla Public License v2.0.

View File

@ -0,0 +1,44 @@
set(LIBJUICE_VERSION "73785387eafe15c02b6a210edb10f722474e8e14")
set(LIBJUICE_URL "https://github.com/paullouisageneau/libjuice/archive/${LIBJUICE_VERSION}.zip")
set(libjuice_dir ${CMAKE_BINARY_DIR}/libjuice/libjuice-${LIBJUICE_VERSION})
# Fetch the library
if(NOT EXISTS ${libjuice_dir})
message(STATUS "Downloading libjuice ${LIBJUICE_VERSION}...")
file(DOWNLOAD ${LIBJUICE_URL} ${CMAKE_BINARY_DIR}/libjuice.zip SHOW_PROGRESS)
execute_process(COMMAND unzip -o ${CMAKE_BINARY_DIR}/libjuice.zip -d ${CMAKE_BINARY_DIR}/libjuice
WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
endif()
set(JUICE_SOURCES ${libjuice_dir}/src/addr.c
${libjuice_dir}/src/agent.c
${libjuice_dir}/src/base64.c
${libjuice_dir}/src/conn.c
${libjuice_dir}/src/conn_mux.c
${libjuice_dir}/src/conn_poll.c
${libjuice_dir}/src/conn_thread.c
${libjuice_dir}/src/const_time.c
${libjuice_dir}/src/crc32.c
${libjuice_dir}/src/hash.c
${libjuice_dir}/src/ice.c
${libjuice_dir}/src/juice.c
${libjuice_dir}/src/log.c
${libjuice_dir}/src/server.c
${libjuice_dir}/src/stun.c
${libjuice_dir}/src/timestamp.c
${libjuice_dir}/src/turn.c
${libjuice_dir}/src/udp.c
# Use hmac from mbedtls and random numbers from esp_random:
# ${libjuice_dir}/src/hmac.c
# ${libjuice_dir}/src/random.c
)
idf_component_register(SRCS port/juice_random.c
${JUICE_SOURCES}
INCLUDE_DIRS "include" "${libjuice_dir}/include" "${libjuice_dir}/include/juice"
REQUIRES esp_netif
PRIV_REQUIRES sock_utils)
target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-format")
set_source_files_properties(${libjuice_dir}/src/udp.c PROPERTIES COMPILE_FLAGS -Wno-unused-variable)

View File

@ -0,0 +1,13 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#pragma once
// Purpose of this header is to replace udp_sendto() to avoid name conflict with lwip
// added here since ifaddrs.h is included from juice_udp sources
#define udp_sendto juice_udp_sendto
// other than that, let's just include the ifaddrs (from sock_utils)
#include_next "ifaddrs.h"

View File

@ -0,0 +1,40 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "esp_random.h"
void juice_random(void *buf, size_t size)
{
esp_fill_random(buf, size);
}
void juice_random_str64(char *buf, size_t size)
{
static const char chars64[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
size_t i = 0;
for (i = 0; i + 1 < size; ++i) {
uint8_t byte = 0;
juice_random(&byte, 1);
buf[i] = chars64[byte & 0x3F];
}
buf[i] = '\0';
}
uint32_t juice_rand32(void)
{
uint32_t r = 0;
juice_random(&r, sizeof(r));
return r;
}
uint64_t juice_rand64(void)
{
uint64_t r = 0;
juice_random(&r, sizeof(r));
return r;
}

View File

@ -0,0 +1,4 @@
idf_component_register(SRCS "serverless_mqtt.c"
"wifi_connect.c"
INCLUDE_DIRS "."
REQUIRES libjuice nvs_flash mqtt json esp_wifi)

View File

@ -0,0 +1,85 @@
menu "Example Configuration"
menu "AP Configuration"
comment "AP Configuration"
config EXAMPLE_AP_SSID
string "Wi-Fi SSID"
default "myssid"
help
Set the SSID of Wi-Fi ap interface.
config EXAMPLE_AP_PASSWORD
string "Wi-Fi Password"
default "12345678"
help
Set the password of Wi-Fi ap interface.
endmenu
menu "STA Configuration"
comment "STA Configuration"
config EXAMPLE_STA_SSID
string "WiFi Station SSID"
default "mystationssid"
help
SSID for the example's sta to connect to.
config EXAMPLE_STA_PASSWORD
string "WiFi Station Password"
default "mystationpassword"
help
WiFi station password for the example to use.
endmenu
config EXAMPLE_MQTT_BROKER_URI
string "MQTT Broker URL"
default "mqtt://mqtt.eclipseprojects.io"
help
URL of the mqtt broker use for synchronisation and exchanging
ICE connect info (description and candidates).
config EXAMPLE_MQTT_SYNC_TOPIC
string "MQTT topic for synchronisation"
default "/topic/serverless_mqtt"
help
MQTT topic used fo synchronisation.
config EXAMPLE_STUN_SERVER
string "Hostname of STUN server"
default "stun.l.google.com"
help
STUN server hostname.
config EXAMPLE_MQTT_CLIENT_STACK_SIZE
int "Stack size for mqtt client"
default 16384
help
Set stack size for the mqtt client.
Need more stack, since calling juice API from the handler.
config EXAMPLE_MQTT_BROKER_PORT
int "port for the mosquitto to listen to"
default 1883
help
This is a port which the local mosquitto uses.
choice EXAMPLE_SERVERLESS_ROLE
prompt "Choose your role"
default EXAMPLE_SERVERLESS_ROLE_PEER1
help
Choose either peer1 or peer2.
It's not very important which device is peer1
(peer-1 sends sync messages, peer2 listens for them)
It is important that we have two peers,
one with peer1 config, another one with peer2 config
config EXAMPLE_SERVERLESS_ROLE_PEER1
bool "peer1"
config EXAMPLE_SERVERLESS_ROLE_PEER2
bool "peer2"
endchoice
endmenu

View File

@ -0,0 +1,5 @@
## IDF Component Manager Manifest File
dependencies:
espressif/mosquitto:
override_path: ../../..
espressif/sock_utils: "*"

View File

@ -0,0 +1,374 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "mqtt_client.h"
#include "esp_wifi.h"
#include "esp_log.h"
#include "esp_random.h"
#include "esp_check.h"
#include "esp_sleep.h"
#include "mosq_broker.h"
#include "juice/juice.h"
#include "cJSON.h"
#if defined(CONFIG_EXAMPLE_SERVERLESS_ROLE_PEER1)
#define OUR_PEER "1"
#define THEIR_PEER "2"
#elif defined(CONFIG_EXAMPLE_SERVERLESS_ROLE_PEER2)
#define OUR_PEER "2"
#define THEIR_PEER "1"
#endif
#define PEER_SYNC0 BIT(0)
#define PEER_SYNC1 BIT(1)
#define PEER_SYNC2 BIT(2)
#define PEER_FAIL BIT(3)
#define PEER_GATHER_DONE BIT(4)
#define PEER_DESC_PUBLISHED BIT(5)
#define PEER_CONNECTED BIT(6)
#define SYNC_BITS (PEER_SYNC1 | PEER_SYNC2 | PEER_FAIL)
#define PUBLISH_SYNC_TOPIC CONFIG_EXAMPLE_MQTT_SYNC_TOPIC OUR_PEER
#define SUBSCRIBE_SYNC_TOPIC CONFIG_EXAMPLE_MQTT_SYNC_TOPIC THEIR_PEER
#define MAX_BUFFER_SIZE JUICE_MAX_SDP_STRING_LEN
typedef struct message_wrap {
uint16_t topic_len;
uint16_t data_len;
char data[];
} __attribute__((packed)) message_wrap_t;
static const char *TAG = "serverless_mqtt" OUR_PEER;
static char s_buffer[MAX_BUFFER_SIZE];
static EventGroupHandle_t s_state = NULL;
static juice_agent_t *s_agent = NULL;
static cJSON *s_peer_desc_json = NULL;
static char *s_peer_desc = NULL;
static esp_mqtt_client_handle_t s_local_mqtt = NULL;
char *wifi_get_ipv4(wifi_interface_t interface);
esp_err_t wifi_connect(void);
static esp_err_t sync_peers(void);
static esp_err_t create_candidates(void);
static esp_err_t create_local_client(void);
static esp_err_t create_local_broker(void);
void app_main(void)
{
__attribute__((__unused__)) esp_err_t ret;
ESP_GOTO_ON_ERROR(wifi_connect(), err, TAG, "Failed to initialize WiFi");
ESP_GOTO_ON_ERROR(create_local_broker(), err, TAG, "Failed to create local broker");
ESP_GOTO_ON_ERROR(create_candidates(), err, TAG, "Failed to create juice candidates");
ESP_GOTO_ON_ERROR(sync_peers(), err, TAG, "Failed to sync with the other peer");
EventBits_t bits = xEventGroupWaitBits(s_state, PEER_FAIL | PEER_CONNECTED, pdFALSE, pdFALSE, pdMS_TO_TICKS(90000));
if (bits & PEER_CONNECTED) {
ESP_LOGI(TAG, "Peer is connected!");
ESP_GOTO_ON_ERROR(create_local_client(), err, TAG, "Failed to create forwarding mqtt client");
ESP_LOGI(TAG, "Everything is ready, exiting main task");
return;
}
err:
ESP_LOGE(TAG, "Non recoverable error, going to sleep for some time (random, max 20s)");
esp_deep_sleep(1000000LL * (esp_random() % 20));
}
static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
{
esp_mqtt_event_handle_t event = event_data;
esp_mqtt_client_handle_t client = event->client;
switch ((esp_mqtt_event_id_t)event_id) {
case MQTT_EVENT_CONNECTED:
ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED");
if (esp_mqtt_client_subscribe(client, SUBSCRIBE_SYNC_TOPIC, 1) < 0) {
ESP_LOGE(TAG, "Failed to subscribe to the sync topic");
}
xEventGroupSetBits(s_state, PEER_SYNC0);
break;
case MQTT_EVENT_DISCONNECTED:
ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED");
xEventGroupSetBits(s_state, PEER_FAIL);
break;
case MQTT_EVENT_DATA:
ESP_LOGI(TAG, "MQTT_EVENT_DATA");
printf("TOPIC=%.*s\r\n", event->topic_len, event->topic);
printf("DATA=%.*s\r\n", event->data_len, event->data);
if (s_state == NULL || memcmp(event->topic, SUBSCRIBE_SYNC_TOPIC, event->topic_len) != 0) {
break;
}
EventBits_t bits = xEventGroupGetBits(s_state);
if (event->data_len > 1 && s_agent) {
cJSON *root = cJSON_Parse(event->data);
if (root == NULL) {
break;
}
cJSON *desc = cJSON_GetObjectItem(root, "desc");
if (desc == NULL) {
cJSON_Delete(root);
break;
}
printf("desc->valuestring:%s\n", desc->valuestring);
juice_set_remote_description(s_agent, desc->valuestring);
char cand_name[] = "cand0";
while (true) {
cJSON *cand = cJSON_GetObjectItem(root, cand_name);
if (cand == NULL) {
break;
}
printf("%s: cand->valuestring:%s\n", cand_name, cand->valuestring);
juice_add_remote_candidate(s_agent, cand->valuestring);
cand_name[4]++;
}
cJSON_Delete(root);
xEventGroupSetBits(s_state, PEER_DESC_PUBLISHED); // this will complete the sync process
// and destroy the mqtt client
}
#ifdef CONFIG_EXAMPLE_SERVERLESS_ROLE_PEER1
if (event->data_len == 1 && event->data[0] == '1' && (bits & PEER_SYNC2) == 0) {
if (esp_mqtt_client_publish(client, PUBLISH_SYNC_TOPIC, "2", 1, 1, 0) >= 0) {
xEventGroupSetBits(s_state, PEER_SYNC2);
} else {
xEventGroupSetBits(s_state, PEER_FAIL);
}
}
#else
if (event->data_len == 1 && event->data[0] == '0' && (bits & PEER_SYNC1) == 0) {
if (esp_mqtt_client_publish(client, PUBLISH_SYNC_TOPIC, "1", 1, 1, 0) >= 0) {
xEventGroupSetBits(s_state, PEER_SYNC1);
} else {
xEventGroupSetBits(s_state, PEER_FAIL);
}
} else if (event->data_len == 1 && event->data[0] == '2' && (bits & PEER_SYNC2) == 0) {
xEventGroupSetBits(s_state, PEER_SYNC2);
}
#endif
break;
case MQTT_EVENT_ERROR:
ESP_LOGI(TAG, "MQTT_EVENT_ERROR");
xEventGroupSetBits(s_state, PEER_FAIL);
break;
default:
ESP_LOGI(TAG, "Other event id:%d", event->event_id);
break;
}
}
static esp_err_t sync_peers(void)
{
esp_err_t ret = ESP_OK;
esp_mqtt_client_config_t mqtt_cfg = {
.broker.address.uri = CONFIG_EXAMPLE_MQTT_BROKER_URI,
.task.stack_size = CONFIG_EXAMPLE_MQTT_CLIENT_STACK_SIZE,
};
esp_mqtt_client_handle_t client = esp_mqtt_client_init(&mqtt_cfg);
ESP_GOTO_ON_FALSE(client, ESP_ERR_NO_MEM, err, TAG, "Failed to create mqtt client");
ESP_GOTO_ON_ERROR(esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL),
err, TAG, "Failed to register mqtt event handler");
ESP_GOTO_ON_ERROR(esp_mqtt_client_start(client), err, TAG, "Failed to start mqtt client");
ESP_GOTO_ON_FALSE(xEventGroupWaitBits(s_state, PEER_SYNC0, pdTRUE, pdTRUE, pdMS_TO_TICKS(10000)),
ESP_FAIL, err, TAG, "Failed to connect to the sync broker");
ESP_LOGI(TAG, "Waiting for the other peer...");
const int max_sync_retry = 60;
int retry = 0;
while (true) {
EventBits_t bits = xEventGroupWaitBits(s_state, SYNC_BITS, pdTRUE, pdFALSE, pdMS_TO_TICKS(1000));
if (bits & PEER_SYNC2) {
break;
}
if (bits & PEER_SYNC1) {
continue;
}
ESP_GOTO_ON_FALSE((bits & PEER_FAIL) == 0, ESP_FAIL, err, TAG, "Failed to sync with the other peer");
ESP_GOTO_ON_FALSE(retry++ < max_sync_retry, ESP_FAIL, err, TAG, "Failed to sync after %d seconds", retry);
#ifdef CONFIG_EXAMPLE_SERVERLESS_ROLE_PEER1
ESP_RETURN_ON_FALSE(esp_mqtt_client_publish(client, PUBLISH_SYNC_TOPIC, "0", 1, 1, 0) >= 0,
ESP_FAIL, TAG, "Failed to publish mqtt message");
#endif
}
ESP_LOGI(TAG, "Sync done");
ESP_RETURN_ON_FALSE(esp_mqtt_client_publish(client, PUBLISH_SYNC_TOPIC, s_peer_desc, 0, 1, 0) >= 0,
ESP_FAIL, TAG, "Failed to publish peer's description");
ESP_LOGI(TAG, "Waiting for the other peer description and candidates...");
ESP_GOTO_ON_FALSE(xEventGroupWaitBits(s_state, PEER_DESC_PUBLISHED, pdTRUE, pdTRUE, pdMS_TO_TICKS(10000)),
ESP_FAIL, err, TAG, "Timeout in waiting for the other peer candidates");
err:
free(s_peer_desc);
esp_mqtt_client_destroy(client);
return ret;
}
static void juice_state(juice_agent_t *agent, juice_state_t state, void *user_ptr)
{
ESP_LOGI(TAG, "JUICE state change: %s", juice_state_to_string(state));
if (state == JUICE_STATE_CONNECTED) {
xEventGroupSetBits(s_state, PEER_CONNECTED);
} else if (state == JUICE_STATE_FAILED || state == JUICE_STATE_DISCONNECTED) {
esp_restart();
}
}
static void juice_candidate(juice_agent_t *agent, const char *sdp, void *user_ptr)
{
static uint8_t cand_nr = 0;
if (s_peer_desc_json && cand_nr < 10) { // supporting only 10 candidates
char cand_name[] = "cand0";
cand_name[4] += cand_nr++;
cJSON_AddStringToObject(s_peer_desc_json, cand_name, sdp);
}
}
static void juice_gathering_done(juice_agent_t *agent, void *user_ptr)
{
ESP_LOGI(TAG, "Gathering done");
if (s_state) {
xEventGroupSetBits(s_state, PEER_GATHER_DONE);
}
}
#define ALIGN(size) (((size) + 3U) & ~(3U))
static void juice_recv(juice_agent_t *agent, const char *data, size_t size, void *user_ptr)
{
if (s_local_mqtt) {
message_wrap_t *message = (message_wrap_t *)data;
int topic_len = message->topic_len;
int payload_len = message->data_len;
int topic_len_aligned = ALIGN(topic_len);
char *topic = message->data;
char *payload = message->data + topic_len_aligned;
if (topic_len + topic_len_aligned + 4 > size) {
ESP_LOGE(TAG, "Received invalid message");
return;
}
ESP_LOGI(TAG, "forwarding remote message: topic:%s", topic);
ESP_LOGI(TAG, "forwarding remote message: payload:%.*s", payload_len, payload);
esp_mqtt_client_publish(s_local_mqtt, topic, payload, payload_len, 0, 0);
}
}
static esp_err_t create_candidates(void)
{
ESP_RETURN_ON_FALSE(s_state = xEventGroupCreate(), ESP_ERR_NO_MEM, TAG, "Failed to create state event group");
s_peer_desc_json = cJSON_CreateObject();
esp_err_t ret = ESP_OK;
juice_set_log_level(JUICE_LOG_LEVEL_INFO);
juice_config_t config = { .stun_server_host = CONFIG_EXAMPLE_STUN_SERVER,
.bind_address = wifi_get_ipv4(WIFI_IF_STA),
.stun_server_port = 19302,
.cb_state_changed = juice_state,
.cb_candidate = juice_candidate,
.cb_gathering_done = juice_gathering_done,
.cb_recv = juice_recv,
};
s_agent = juice_create(&config);
ESP_RETURN_ON_FALSE(s_agent, ESP_FAIL, TAG, "Failed to create juice agent");
ESP_GOTO_ON_FALSE(juice_get_local_description(s_agent, s_buffer, MAX_BUFFER_SIZE) == JUICE_ERR_SUCCESS,
ESP_FAIL, err, TAG, "Failed to get local description");
ESP_LOGI(TAG, "desc: %s", s_buffer);
cJSON_AddStringToObject(s_peer_desc_json, "desc", s_buffer);
ESP_GOTO_ON_FALSE(juice_gather_candidates(s_agent) == JUICE_ERR_SUCCESS,
ESP_FAIL, err, TAG, "Failed to start gathering candidates");
ESP_GOTO_ON_FALSE(xEventGroupWaitBits(s_state, PEER_GATHER_DONE, pdTRUE, pdTRUE, pdMS_TO_TICKS(30000)),
ESP_FAIL, err, TAG, "Failed to connect to the sync broker");
s_peer_desc = cJSON_Print(s_peer_desc_json);
ESP_LOGI(TAG, "desc: %s", s_peer_desc);
cJSON_Delete(s_peer_desc_json);
return ESP_OK;
err:
juice_destroy(s_agent);
s_agent = NULL;
cJSON_Delete(s_peer_desc_json);
s_peer_desc_json = NULL;
return ret;
}
static void local_handler(void *args, esp_event_base_t base, int32_t id, void *data)
{
switch (id) {
case MQTT_EVENT_CONNECTED:
ESP_LOGI(TAG, "local client connected");
break;
case MQTT_EVENT_DISCONNECTED:
ESP_LOGI(TAG, "local client disconnected");
break;
case MQTT_EVENT_ERROR:
ESP_LOGI(TAG, "local client error");
break;
default:
ESP_LOGI(TAG, "local client event id:%d", (int)id);
break;
}
}
static esp_err_t create_local_client(void)
{
esp_err_t ret = ESP_OK;
esp_mqtt_client_config_t mqtt_cfg = {
.broker.address.transport = MQTT_TRANSPORT_OVER_TCP,
.broker.address.hostname = wifi_get_ipv4(WIFI_IF_AP),
.broker.address.port = CONFIG_EXAMPLE_MQTT_BROKER_PORT,
.task.stack_size = CONFIG_EXAMPLE_MQTT_CLIENT_STACK_SIZE,
.credentials.client_id = "local_mqtt"
};
s_local_mqtt = esp_mqtt_client_init(&mqtt_cfg);
ESP_GOTO_ON_FALSE(s_local_mqtt, ESP_ERR_NO_MEM, err, TAG, "Failed to create mqtt client");
ESP_GOTO_ON_ERROR(esp_mqtt_client_register_event(s_local_mqtt, ESP_EVENT_ANY_ID, local_handler, NULL),
err, TAG, "Failed to register mqtt event handler");
ESP_GOTO_ON_ERROR(esp_mqtt_client_start(s_local_mqtt), err, TAG, "Failed to start mqtt client");
return ESP_OK;
err:
esp_mqtt_client_destroy(s_local_mqtt);
s_local_mqtt = NULL;
return ret;
}
static void handle_message(char *client, char *topic, char *payload, int len, int qos, int retain)
{
if (client && strcmp(client, "local_mqtt") == 0 ) {
// This is our little local client -- do not forward
return;
}
ESP_LOGI(TAG, "handle_message topic:%s", topic);
ESP_LOGI(TAG, "handle_message data:%.*s", len, payload);
ESP_LOGI(TAG, "handle_message qos=%d, retain=%d", qos, retain);
if (s_local_mqtt && s_agent) {
int topic_len = strlen(topic) + 1; // null term
int topic_len_aligned = ALIGN(topic_len);
int total_msg_len = 2 + 2 /* msg_wrap header */ + topic_len_aligned + len;
if (total_msg_len > MAX_BUFFER_SIZE) {
ESP_LOGE(TAG, "Fail to forward, message too long");
return;
}
message_wrap_t *message = (message_wrap_t *)s_buffer;
message->topic_len = topic_len;
message->data_len = len;
memcpy(s_buffer + 4, topic, topic_len);
memcpy(s_buffer + 4 + topic_len_aligned, payload, len);
juice_send(s_agent, s_buffer, total_msg_len);
}
}
static void broker_task(void *ctx)
{
struct mosq_broker_config config = { .host = wifi_get_ipv4(WIFI_IF_AP), .port = CONFIG_EXAMPLE_MQTT_BROKER_PORT, .handle_message_cb = handle_message };
mosq_broker_run(&config);
vTaskDelete(NULL);
}
static esp_err_t create_local_broker(void)
{
return xTaskCreate(broker_task, "mqtt_broker_task", 1024 * 32, NULL, 5, NULL) == pdTRUE ?
ESP_OK : ESP_FAIL;
}

View File

@ -0,0 +1,122 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include "nvs_flash.h"
#include "esp_event.h"
#include "esp_netif.h"
#include "esp_check.h"
#include "esp_wifi.h"
#include "esp_mac.h"
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
static const char *TAG = "serverless_wifi";
static EventGroupHandle_t s_wifi_events;
static int s_retry_num = 0;
static const int s_max_retry = 30;
static void wifi_event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
if (event_id == WIFI_EVENT_AP_STACONNECTED) {
wifi_event_ap_staconnected_t *event = (wifi_event_ap_staconnected_t *) event_data;
ESP_LOGI(TAG, "station "MACSTR" join, AID=%d",
MAC2STR(event->mac), event->aid);
} else if (event_id == WIFI_EVENT_AP_STADISCONNECTED) {
wifi_event_ap_stadisconnected_t *event = (wifi_event_ap_stadisconnected_t *) event_data;
ESP_LOGI(TAG, "station "MACSTR" leave, AID=%d",
MAC2STR(event->mac), event->aid);
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
if (s_retry_num < s_max_retry) {
esp_wifi_connect();
s_retry_num++;
ESP_LOGI(TAG, "retry to connect to the AP");
} else {
xEventGroupSetBits(s_wifi_events, WIFI_FAIL_BIT);
}
ESP_LOGI(TAG, "Connect to the AP fail");
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t *event = (ip_event_got_ip_t *) event_data;
ESP_LOGI(TAG, "Got ip:" IPSTR, IP2STR(&event->ip_info.ip));
s_retry_num = 0;
xEventGroupSetBits(s_wifi_events, WIFI_CONNECTED_BIT);
}
}
esp_err_t wifi_connect(void)
{
esp_err_t ret = ESP_OK;
ESP_GOTO_ON_FALSE(s_wifi_events = xEventGroupCreate(), ESP_ERR_NO_MEM, err, TAG, "Failed to create wifi_events");
ESP_GOTO_ON_ERROR(nvs_flash_init(), err, TAG, "Failed to init nvs flash");
ESP_GOTO_ON_ERROR(esp_netif_init(), err, TAG, "Failed to init esp_netif");
ESP_GOTO_ON_ERROR(esp_event_loop_create_default(), err, TAG, "Failed to create default event loop");
ESP_GOTO_ON_ERROR(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, wifi_event_handler, NULL),
err, TAG, "Failed to register WiFi event handler");
ESP_GOTO_ON_ERROR(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, wifi_event_handler, NULL),
err, TAG, "Failed to register IP event handler");
// Initialize WiFi
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_GOTO_ON_ERROR(esp_wifi_init(&cfg), err, TAG, "Failed to initialize WiFi");
ESP_GOTO_ON_ERROR(esp_wifi_set_mode(WIFI_MODE_APSTA), err, TAG, "Failed to set STA+AP mode");
// Initialize AP
esp_netif_t *ap = esp_netif_create_default_wifi_ap();
ESP_GOTO_ON_FALSE(ap, ESP_FAIL, err, TAG, "Failed to create AP network interface");
wifi_config_t wifi_ap_config = {
.ap = {
.ssid = CONFIG_EXAMPLE_AP_SSID,
.password = CONFIG_EXAMPLE_AP_PASSWORD,
.authmode = WIFI_AUTH_WPA2_PSK,
.max_connection = 4,
},
};
ESP_GOTO_ON_ERROR(esp_wifi_set_config(WIFI_IF_AP, &wifi_ap_config), err, TAG, "Failed to set AP config");
// Initialize STA
esp_netif_t *sta = esp_netif_create_default_wifi_sta();
ESP_GOTO_ON_FALSE(sta, ESP_FAIL, err, TAG, "Failed to create WiFi station network interface");
wifi_config_t wifi_sta_config = {
.sta = {
.ssid = CONFIG_EXAMPLE_STA_SSID,
.password = CONFIG_EXAMPLE_STA_PASSWORD,
},
};
ESP_GOTO_ON_ERROR(esp_wifi_set_config(WIFI_IF_STA, &wifi_sta_config), err, TAG, "Failed to set STA config");
// Start WiFi
ESP_GOTO_ON_ERROR(esp_wifi_start(), err, TAG, "Failed to start WiFi");
// Wait for connection
EventBits_t bits = xEventGroupWaitBits(s_wifi_events, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
pdFALSE, pdFALSE, pdMS_TO_TICKS(30000));
ESP_GOTO_ON_FALSE((bits & WIFI_CONNECTED_BIT) == WIFI_CONNECTED_BIT, ESP_FAIL, err,
TAG, "Failed to obtain IP address from WiFi station");
return ESP_OK;
err:
esp_wifi_stop();
esp_wifi_deinit();
nvs_flash_deinit();
esp_netif_deinit();
esp_event_loop_delete_default();
return ret;
}
_Thread_local char s_ipv4_addr[4 * 4]; // 4 octets + '.'/term
char *wifi_get_ipv4(wifi_interface_t interface)
{
esp_netif_t *netif = esp_netif_get_handle_from_ifkey(interface == WIFI_IF_AP ? "WIFI_AP_DEF" : "WIFI_STA_DEF");
ESP_RETURN_ON_FALSE(netif, NULL, TAG, "Failed to find default Wi-Fi netif");
esp_netif_ip_info_t ip_info;
ESP_RETURN_ON_FALSE(esp_netif_get_ip_info(netif, &ip_info) == ESP_OK, NULL, TAG, "Failed to get IP from netif");
ESP_RETURN_ON_FALSE(esp_ip4addr_ntoa(&ip_info.ip, s_ipv4_addr, sizeof(s_ipv4_addr)) != NULL, NULL, TAG, "Failed to convert IP");
return s_ipv4_addr;
}

View File

@ -0,0 +1,3 @@
CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y
CONFIG_ESP_MAIN_TASK_STACK_SIZE=16384
CONFIG_PTHREAD_TASK_STACK_SIZE_DEFAULT=32768

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

View File

@ -1,4 +1,4 @@
version: "2.0.28~0"
version: "2.0.20~0"
url: https://github.com/espressif/esp-protocols/tree/master/components/mosquitto
description: The component provides a simple ESP32 port of mosquitto broker
dependencies:

View File

@ -101,6 +101,8 @@ void mosq_broker_stop(void)
run = 0;
}
extern mosq_message_cb_t g_mosq_message_callback;
int mosq_broker_run(struct mosq_broker_config *broker_config)
{
@ -125,6 +127,9 @@ int mosq_broker_run(struct mosq_broker_config *broker_config)
if (broker_config->tls_cfg) {
net__set_tls_config(broker_config->tls_cfg);
}
if (broker_config->handle_message_cb) {
g_mosq_message_callback = broker_config->handle_message_cb;
}
db.config = &config;

View File

@ -13,7 +13,9 @@
#include "util_mosq.h"
#include "utlist.h"
#include "lib_load.h"
#include "mosq_broker.h"
mosq_message_cb_t g_mosq_message_callback = NULL;
int mosquitto_callback_register(
mosquitto_plugin_id_t *identifier,
@ -44,5 +46,8 @@ void plugin__handle_disconnect(struct mosquitto *context, int reason)
int plugin__handle_message(struct mosquitto *context, struct mosquitto_msg_store *stored)
{
if (g_mosq_message_callback) {
g_mosq_message_callback(context->id, stored->topic, stored->payload, stored->payloadlen, stored->qos, stored->retain);
}
return MOSQ_ERR_SUCCESS;
}

View File

@ -9,6 +9,7 @@
struct mosquitto__config;
typedef void (*mosq_message_cb_t)(char *client, char *topic, char *data, int len, int qos, int retain);
/**
* @brief Mosquitto configuration structure
*
@ -24,6 +25,10 @@ struct mosq_broker_config {
* You can open the respective docs with this idf.py command:
* `idf.py docs -sp api-reference/protocols/esp_tls.html`
*/
void (*handle_message_cb)(char *client, char *topic, char *data, int len, int qos, int retain); /*!<
* On message callback. If configured, user function is called
* whenever mosquitto processes a message.
*/
};
/**

View File

@ -3,6 +3,6 @@ commitizen:
bump_message: 'bump(sockutls): $current_version -> $new_version'
pre_bump_hooks: python ../../ci/changelog.py sock_utils
tag_format: sock_utils-v$version
version: 0.1.0
version: 0.2.0
version_files:
- idf_component.yml

View File

@ -1,5 +1,12 @@
# Changelog
## [0.2.0](https://github.com/espressif/esp-protocols/commits/sock_utils-v0.2.0)
### Features
- Declare socketpair and gai_strerror via standard headers ([b090a3cb](https://github.com/espressif/esp-protocols/commit/b090a3cb))
- Add support for gethostname() ([f7c0b756](https://github.com/espressif/esp-protocols/commit/f7c0b756))
## [0.1.0](https://github.com/espressif/esp-protocols/commits/sock_utils-v0.1.0)
### Features

View File

@ -2,5 +2,15 @@ idf_component_register(SRCS "src/getnameinfo.c"
"src/ifaddrs.c"
"src/gai_strerror.c"
"src/socketpair.c"
"src/gethostname.c"
INCLUDE_DIRS "include"
PRIV_REQUIRES lwip esp_netif)
# To support declarations from standard headers in lwip component
# - socket pair from lwip/sockets.h
# - gai_strerror from lwip/netdb.h
# also need to make lwip depend on the sock_utils lib
idf_component_get_property(lwip lwip COMPONENT_LIB)
target_compile_definitions(${lwip} PUBLIC LWIP_SOCKET_HAS_SOCKETPAIR=1)
target_compile_definitions(${lwip} PUBLIC LWIP_NETDB_HAS_GAI_STRERROR=1)
target_link_libraries(${lwip} PUBLIC ${COMPONENT_LIB})

View File

@ -6,12 +6,17 @@ This component provides simplified implementations of common socket-related util
## Supported Functions
| API | Description | Limitations |
|------------------|-------------------------------------------------------------|-------------------------------------------------------------------|
| `ifaddrs()` | Retrieves interface addresses using `esp_netif` | IPv4 addresses only |
| `socketpair()` | Creates a pair of connected sockets using `lwIP` loopback stream sockets | IPv4 sockets only |
| `pipe()` | Wraps `socketpair()` to provide unidirectional pipe-like functionality | Uses bidirectional sockets in place of true pipes |
| `getnameinfo()` | Converts IP addresses to human-readable form using `lwIP`'s `inet_ntop()` | IPv4 only; supports `NI_NUMERICHOST` and `NI_NUMERICSERV` flags only |
| `gai_strerror()` | Returns error code as a string | Simple numeric string representation only |
| API | Description | Limitations | Declared in |
|--------------------|-------------------------------------------------------------|-------------------------------------------------------------------|----------------------------------------|
| `ifaddrs()` | Retrieves interface addresses using `esp_netif` | IPv4 addresses only | `ifaddrs.h` |
| `socketpair()` *) | Creates a pair of connected sockets using `lwIP` loopback stream sockets | IPv4 sockets only | `socketpair.h`, `sys/socket.h` **) |
| `pipe()` *) | Wraps `socketpair()` to provide unidirectional pipe-like functionality | Uses bidirectional sockets in place of true pipes | `socketpair.h`, `unistd.h` ***) |
| `getnameinfo()` | Converts IP addresses to human-readable form using `lwIP`'s `inet_ntop()` | IPv4 only; supports `NI_NUMERICHOST` and `NI_NUMERICSERV` flags only | `getnameinfo.h`, `netdb.h` in ESP-IDF |
| `gai_strerror()` | Returns error code as a string | Simple numeric string representation only | `gai_strerror.h`, `netdb.h` **) |
| `gethostname()` | Returns lwip netif hostname | Not a system-wide hostname, but interface specific hostname | `gethostname.h`, `unistd.h` in ESP-IDF |
**Note**: `socketpair()` and `pipe()` are built on top of `lwIP` TCP sockets, inheriting the same characteristics. For instance, the maximum transmit buffer size is based on the `TCP_SND_BUF` setting.
**Notes**:
- **`*)`** `socketpair()` and `pipe()` are built on top of `lwIP` TCP sockets, inheriting the same characteristics. For instance, the maximum transmit buffer size is based on the `TCP_SND_BUF` setting.
- **`**)`** `socketpair()` and `gai_strerror()` are declared in sock_utils header files, the declaration is propagated to ESP-IDF from v5.5 to the official header files. If you're using older IDF version, you need to manually pre-include related header files from the sock_utils public include directory.
- **`***)`** `pipe()` is declared in compiler's `sys/unistd.h`.

View File

@ -1,4 +1,4 @@
version: 0.1.0
version: 0.2.0
description: The component provides helper implementation of common system/socket utilities
url: https://github.com/espressif/esp-protocols/tree/master/components/sock_utils
dependencies:

View File

@ -0,0 +1,51 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <unistd.h>
#include "sdkconfig.h"
#ifdef CONFIG_IDF_TARGET_LINUX
// namespace with esp_ on linux to avoid conflict of symbols
#define gethostname esp_gethostname
#endif
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Retrieves the hostname of the device.
*
* This function provides the hostname associated with the network interface.
* Unlike the standard behavior where the hostname represents a system-wide name,
* this implementation returns lwip netif hostname (used as a hostname in DHCP packets)
*
* @param[out] name A pointer to a buffer where the hostname will be stored.
* The buffer must be allocated by the caller.
* @param[in] len The size of the buffer pointed to by @p name. The hostname,
* including the null-terminator, must fit within this size.
*
* @return
* - 0 on success
* - -1 on error, with `errno` set to indicate the error:
* - `EINVAL`: Invalid argument, name is NULL, or hostname is too long
*
* @note This implementation retrieves the hostname associated with the network
* interface using the `esp_netif_get_hostname()` function, which in turn
* returns lwip netif hostname used in DHCP packets if LWIP_NETIF_HOSTNAME=1 (hardcoded)
* in ESP-IDF lwip port.
* As there could be multiple network interfaces in the system, the logic tries
* to find the default (active) netif first, then it looks for any (inactive) netif
* with highest route priority. If none of the above found or esp_netif_get_hostname() fails
* for the selected interface, this API returns the default value of `CONFIG_LWIP_LOCAL_HOSTNAME`,
* the local hostname from lwip component configuration menu.
*/
int gethostname(char *name, size_t len);
#ifdef __cplusplus
}
#endif

View File

@ -32,3 +32,10 @@
#ifndef AF_UNIX
#define AF_UNIX 1
#endif
#ifndef PF_LOCAL
/*
* In POSIX, AF_UNIX and PF_LOCAL are essentially synonymous.
*/
#define PF_LOCAL AF_UNIX
#endif

View File

@ -0,0 +1,46 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include "gethostname.h"
#include "esp_netif.h"
#include "errno.h"
#include "esp_log.h"
static bool highest_prio_netif(esp_netif_t *netif, void *ctx)
{
esp_netif_t **highest_so_far = ctx;
if (esp_netif_get_route_prio(netif) > esp_netif_get_route_prio(*highest_so_far)) {
*highest_so_far = netif;
}
return false; // go over the entire list to find the netif with the highest route-prio
}
int gethostname(char *name, size_t len)
{
if (name == NULL) {
errno = EINVAL;
return -1;
}
const char *netif_hostname = CONFIG_LWIP_LOCAL_HOSTNAME; // default value from Kconfig
// Find the default netif
esp_netif_t *default_netif = esp_netif_get_default_netif();
if (default_netif == NULL) { // if no netif is active/up -> find the highest prio netif
esp_netif_find_if(highest_prio_netif, &default_netif);
}
// now the `default_netif` could be NULL and/or the esp_netif_get_hostname() could fail
// but we ignore the return code, as if it fails, the `netif_hostname` still holds the default value
esp_netif_get_hostname(default_netif, &netif_hostname);
size_t hostname_len;
if (netif_hostname == NULL || len < (hostname_len = strlen(netif_hostname) + 1)) { // including the NULL terminator
errno = EINVAL;
return -1;
}
memcpy(name, netif_hostname, hostname_len);
return 0;
}

View File

@ -3,6 +3,7 @@
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "gethostname.h"
#include "ifaddrs.h"
#include "esp_netif.h"
#include "esp_event.h"
@ -148,6 +149,30 @@ TEST_CASE("gai_strerror()", "[sock_utils]")
CHECK(str_error != NULL);
}
TEST_CASE("gethostname()", "[sock_utils]")
{
const char *test_netif_name = "station";
char hostname[32];
int ret;
// expect failure
ret = gethostname(hostname, strlen(CONFIG_LWIP_LOCAL_HOSTNAME) - 1);
CHECK(ret == -1);
// happy flow with the default name
ret = gethostname(hostname, sizeof(hostname));
CHECK(ret == 0);
CHECK(strcmp(hostname, CONFIG_LWIP_LOCAL_HOSTNAME) == 0);
// happy flow with the netif name
esp_netif_t *esp_netif = create_test_netif(test_netif_name, 1);
REQUIRE(esp_netif != NULL);
CHECK(esp_netif_set_hostname(esp_netif, test_netif_name) == ESP_OK);
ret = gethostname(hostname, sizeof(hostname));
CHECK(ret == 0);
CHECK(strcmp(hostname, test_netif_name) == 0);
esp_netif_destroy(esp_netif);
}
extern "C" void app_main(void)
{