mirror of
https://github.com/espressif/esp-protocols.git
synced 2025-07-03 21:56:40 +02:00
Compare commits
28 Commits
console_cm
...
mosq-v2.0.
Author | SHA1 | Date | |
---|---|---|---|
840a561de4 | |||
e6fb8aa078 | |||
3b2c614d86 | |||
cdeab8f517 | |||
6cce87e465 | |||
d57b8c5b29 | |||
9c11003449 | |||
85a7fc772c | |||
b090a3cb69 | |||
42cde46c97 | |||
beb6e57e5e | |||
15d3a01e11 | |||
e12ecb8e89 | |||
54271a1b96 | |||
886215032f | |||
269351f41c | |||
5e929902c7 | |||
f7c0b7564a | |||
c989c6adae | |||
18f196fa1e | |||
1db83cd1ca | |||
247f1681e8 | |||
32387f7e39 | |||
dbc3ea6809 | |||
8b6ea3311a | |||
8e55b93b59 | |||
0cb59ff80d | |||
1284f66d58 |
1
.github/workflows/clang-tidy.yml
vendored
1
.github/workflows/clang-tidy.yml
vendored
@ -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
|
||||
|
9
.github/workflows/modem__target-test.yml
vendored
9
.github/workflows/modem__target-test.yml
vendored
@ -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 }}
|
||||
|
10
.github/workflows/mosq__build.yml
vendored
10
.github/workflows/mosq__build.yml
vendored
@ -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:
|
||||
|
@ -0,0 +1 @@
|
||||
components/mosquitto/examples/serverless_mqtt/components/libjuice/port/juice_random.c
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)") {}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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')
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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:
|
||||
|
@ -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);
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)) {
|
||||
|
@ -52,7 +52,6 @@ void Netif::start()
|
||||
|
||||
void Netif::stop()
|
||||
{
|
||||
ppp_dte->set_read_cb(nullptr);
|
||||
signal.clear(PPP_STARTED);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
3
components/mosquitto/.build-test-rules.yml
Normal file
3
components/mosquitto/.build-test-rules.yml
Normal file
@ -0,0 +1,3 @@
|
||||
components/mosquitto/examples/serverless_mqtt:
|
||||
disable:
|
||||
- if: IDF_TARGET not in ["esp32", "esp32s3", "esp32c3"]
|
@ -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
|
||||
|
@ -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))
|
||||
|
@ -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")
|
||||
|
@ -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)
|
53
components/mosquitto/examples/serverless_mqtt/README.md
Normal file
53
components/mosquitto/examples/serverless_mqtt/README.md
Normal 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
|
||||
|
||||

|
||||
|
||||
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.
|
@ -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)
|
@ -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"
|
@ -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;
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
idf_component_register(SRCS "serverless_mqtt.c"
|
||||
"wifi_connect.c"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES libjuice nvs_flash mqtt json esp_wifi)
|
@ -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
|
@ -0,0 +1,5 @@
|
||||
## IDF Component Manager Manifest File
|
||||
dependencies:
|
||||
espressif/mosquitto:
|
||||
override_path: ../../..
|
||||
espressif/sock_utils: "*"
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
@ -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
|
BIN
components/mosquitto/examples/serverless_mqtt/serverless.png
Normal file
BIN
components/mosquitto/examples/serverless_mqtt/serverless.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 84 KiB |
@ -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:
|
||||
|
Submodule components/mosquitto/mosquitto updated: 3923526c6b...a196c2b244
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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.
|
||||
*/
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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})
|
||||
|
@ -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`.
|
||||
|
@ -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:
|
||||
|
51
components/sock_utils/include/gethostname.h
Normal file
51
components/sock_utils/include/gethostname.h
Normal 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
|
@ -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
|
||||
|
46
components/sock_utils/src/gethostname.c
Normal file
46
components/sock_utils/src/gethostname.c
Normal 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;
|
||||
}
|
@ -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)
|
||||
{
|
||||
|
Reference in New Issue
Block a user