Skip to content

Commit d57b8c5

Browse files
committed
feat(mosq): Add example with two brokers synced on P2P
Broker-less two chip example which virtual private IoT networks on MQTT protocol.
1 parent 269351f commit d57b8c5

File tree

15 files changed

+759
-4
lines changed

15 files changed

+759
-4
lines changed

.github/workflows/mosq__build.yml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ jobs:
1717
runs-on: ubuntu-22.04
1818
container: espressif/idf:${{ matrix.idf_ver }}
1919
env:
20-
TEST_DIR: components/mosquitto/examples/broker
20+
TEST_DIR: components/mosquitto/examples
21+
TARGET_TEST: broker
2122
TARGET_TEST_DIR: build_esp32_default
2223
steps:
2324
- name: Checkout esp-protocols
@@ -29,14 +30,15 @@ jobs:
2930
run: |
3031
. ${IDF_PATH}/export.sh
3132
pip install idf-component-manager idf-build-apps --upgrade
32-
python ci/build_apps.py ${TEST_DIR}
33-
cd ${TEST_DIR}
33+
python ci/build_apps.py -c ${TEST_DIR} -m components/mosquitto/.build-test-rules.yml
34+
# upload only the target test artifacts
35+
cd ${TEST_DIR}/${TARGET_TEST}
3436
${GITHUB_WORKSPACE}/ci/clean_build_artifacts.sh `pwd`/${TARGET_TEST_DIR}
3537
zip -qur artifacts.zip ${TARGET_TEST_DIR}
3638
- uses: actions/upload-artifact@v4
3739
with:
3840
name: mosq_target_esp32_${{ matrix.idf_ver }}
39-
path: ${{ env.TEST_DIR }}/artifacts.zip
41+
path: ${{ env.TEST_DIR }}/${{ env.TARGET_TEST }}/artifacts.zip
4042
if-no-files-found: error
4143

4244
test_mosq:

ci/check_copyright_ignore.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
components/mosquitto/examples/serverless_mqtt/components/libjuice/port/juice_random.c
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
components/mosquitto/examples/serverless_mqtt:
2+
disable:
3+
- if: IDF_TARGET not in ["esp32", "esp32s3", "esp32c3"]
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# The following five lines of boilerplate have to be in your project's
2+
# CMakeLists in this exact order for cmake to work correctly
3+
cmake_minimum_required(VERSION 3.16)
4+
5+
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
6+
project(serverless_mqtt)
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Brokerless MQTT Example
2+
3+
MQTT served by (two) mosquitto's running on two ESP chips.
4+
5+
* Leverages MQTT connectivity between two private networks without cloud premisses.
6+
* 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)).
7+
8+
## How it works
9+
10+
This example needs two ESP32 chipsets, that will create two separate Wi-Fi networks (IoT networks) used for IoT devices.
11+
Each IoT network is served by an MQTT server (using mosquitto component).
12+
This example will also synchronize these two MQTT brokers, as if there was only one IoT network with one broker.
13+
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.
14+
15+
* Diagram
16+
17+
![demo](serverless.png)
18+
19+
Here's a step-by-step procedure of establishing this remote connection:
20+
1) Initialize and start Wi-Fi AP (for IoT networks) and Wi-Fi station (for internet connection)
21+
2) Start mosquitto broker on IoT network
22+
3) Start libjuice to gather connection candidates
23+
4) Synchronize using a public MQTT broker and exchange ICE descriptors
24+
5) Establish ICE UDP connection between the two ESP32 chipsets
25+
6) Start forwarding mqtt messages
26+
- Each remote datagram (received from ICE-UDP channel) is re-published to the local MQTT server
27+
- Each local MQTT message (received from mosquitto on_message callback) is sent in ICE-UDP datagram
28+
29+
## How to use this example
30+
31+
You need two ESP32 devices that support Wi-Fi station and Wi-Fi software access point.
32+
33+
* Configure Wi-Fi credentials for both devices on both interfaces
34+
* These devices would be deployed in distinct Wi-Fi environments, so the Wi-Fi station credentials would likely be different.
35+
* 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).
36+
* 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.
37+
* Optionally: You can use `idf.py` `-D` and `-B` flag to keep separate build directories and sdkconfigs for these two roles
38+
```
39+
idf.py -B build1 -DSDKCONFIG=build1/sdkconfig menuconfig build flash monitor
40+
```
41+
* Flash and run the two devices and wait for them to connect and synchronize.
42+
* Now you can test MQTT connectivity, for example:
43+
* Join PEER1 device's AP and connect to the MQTT broker with one or more clients, subscribing to one or more topics.
44+
* Join PEER2 device's AP and connect to the MQTT broker with one or more clients, subscribing to one or more topics.
45+
* Whenever you publish to a topic, all subscribed clients should receive the message, no matter which Wi-Fi network they're connected to.
46+
47+
## Warning
48+
49+
This example uses libjuice as a dependency:
50+
51+
* libjuice (UDP Interactive Connectivity Establishment): https://github.com/paullouisageneau/libjuice
52+
53+
which is distributed under Mozilla Public License v2.0.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
set(LIBJUICE_VERSION "73785387eafe15c02b6a210edb10f722474e8e14")
2+
set(LIBJUICE_URL "https://github.com/paullouisageneau/libjuice/archive/${LIBJUICE_VERSION}.zip")
3+
4+
set(libjuice_dir ${CMAKE_BINARY_DIR}/libjuice/libjuice-${LIBJUICE_VERSION})
5+
6+
# Fetch the library
7+
if(NOT EXISTS ${libjuice_dir})
8+
message(STATUS "Downloading libjuice ${LIBJUICE_VERSION}...")
9+
file(DOWNLOAD ${LIBJUICE_URL} ${CMAKE_BINARY_DIR}/libjuice.zip SHOW_PROGRESS)
10+
execute_process(COMMAND unzip -o ${CMAKE_BINARY_DIR}/libjuice.zip -d ${CMAKE_BINARY_DIR}/libjuice
11+
WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
12+
endif()
13+
14+
set(JUICE_SOURCES ${libjuice_dir}/src/addr.c
15+
${libjuice_dir}/src/agent.c
16+
${libjuice_dir}/src/base64.c
17+
${libjuice_dir}/src/conn.c
18+
${libjuice_dir}/src/conn_mux.c
19+
${libjuice_dir}/src/conn_poll.c
20+
${libjuice_dir}/src/conn_thread.c
21+
${libjuice_dir}/src/const_time.c
22+
${libjuice_dir}/src/crc32.c
23+
${libjuice_dir}/src/hash.c
24+
${libjuice_dir}/src/ice.c
25+
${libjuice_dir}/src/juice.c
26+
${libjuice_dir}/src/log.c
27+
${libjuice_dir}/src/server.c
28+
${libjuice_dir}/src/stun.c
29+
${libjuice_dir}/src/timestamp.c
30+
${libjuice_dir}/src/turn.c
31+
${libjuice_dir}/src/udp.c
32+
# Use hmac from mbedtls and random numbers from esp_random:
33+
# ${libjuice_dir}/src/hmac.c
34+
# ${libjuice_dir}/src/random.c
35+
)
36+
37+
idf_component_register(SRCS port/juice_random.c
38+
${JUICE_SOURCES}
39+
INCLUDE_DIRS "include" "${libjuice_dir}/include" "${libjuice_dir}/include/juice"
40+
REQUIRES esp_netif
41+
PRIV_REQUIRES sock_utils)
42+
43+
target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-format")
44+
set_source_files_properties(${libjuice_dir}/src/udp.c PROPERTIES COMPILE_FLAGS -Wno-unused-variable)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
3+
*
4+
* SPDX-License-Identifier: Unlicense OR CC0-1.0
5+
*/
6+
#pragma once
7+
8+
// Purpose of this header is to replace udp_sendto() to avoid name conflict with lwip
9+
// added here since ifaddrs.h is included from juice_udp sources
10+
#define udp_sendto juice_udp_sendto
11+
12+
// other than that, let's just include the ifaddrs (from sock_utils)
13+
#include_next "ifaddrs.h"
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* Copyright (c) 2020 Paul-Louis Ageneau
3+
*
4+
* This Source Code Form is subject to the terms of the Mozilla Public
5+
* License, v. 2.0. If a copy of the MPL was not distributed with this
6+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
7+
*/
8+
#include "esp_random.h"
9+
10+
void juice_random(void *buf, size_t size)
11+
{
12+
esp_fill_random(buf, size);
13+
}
14+
15+
void juice_random_str64(char *buf, size_t size)
16+
{
17+
static const char chars64[] =
18+
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
19+
size_t i = 0;
20+
for (i = 0; i + 1 < size; ++i) {
21+
uint8_t byte = 0;
22+
juice_random(&byte, 1);
23+
buf[i] = chars64[byte & 0x3F];
24+
}
25+
buf[i] = '\0';
26+
}
27+
28+
uint32_t juice_rand32(void)
29+
{
30+
uint32_t r = 0;
31+
juice_random(&r, sizeof(r));
32+
return r;
33+
}
34+
35+
uint64_t juice_rand64(void)
36+
{
37+
uint64_t r = 0;
38+
juice_random(&r, sizeof(r));
39+
return r;
40+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
idf_component_register(SRCS "serverless_mqtt.c"
2+
"wifi_connect.c"
3+
INCLUDE_DIRS "."
4+
REQUIRES libjuice nvs_flash mqtt json esp_wifi)
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
menu "Example Configuration"
2+
3+
menu "AP Configuration"
4+
comment "AP Configuration"
5+
6+
config EXAMPLE_AP_SSID
7+
string "Wi-Fi SSID"
8+
default "myssid"
9+
help
10+
Set the SSID of Wi-Fi ap interface.
11+
12+
config EXAMPLE_AP_PASSWORD
13+
string "Wi-Fi Password"
14+
default "12345678"
15+
help
16+
Set the password of Wi-Fi ap interface.
17+
18+
endmenu
19+
20+
menu "STA Configuration"
21+
comment "STA Configuration"
22+
23+
config EXAMPLE_STA_SSID
24+
string "WiFi Station SSID"
25+
default "mystationssid"
26+
help
27+
SSID for the example's sta to connect to.
28+
29+
config EXAMPLE_STA_PASSWORD
30+
string "WiFi Station Password"
31+
default "mystationpassword"
32+
help
33+
WiFi station password for the example to use.
34+
endmenu
35+
36+
config EXAMPLE_MQTT_BROKER_URI
37+
string "MQTT Broker URL"
38+
default "mqtt://mqtt.eclipseprojects.io"
39+
help
40+
URL of the mqtt broker use for synchronisation and exchanging
41+
ICE connect info (description and candidates).
42+
43+
config EXAMPLE_MQTT_SYNC_TOPIC
44+
string "MQTT topic for synchronisation"
45+
default "/topic/serverless_mqtt"
46+
help
47+
MQTT topic used fo synchronisation.
48+
49+
config EXAMPLE_STUN_SERVER
50+
string "Hostname of STUN server"
51+
default "stun.l.google.com"
52+
help
53+
STUN server hostname.
54+
55+
config EXAMPLE_MQTT_CLIENT_STACK_SIZE
56+
int "Stack size for mqtt client"
57+
default 16384
58+
help
59+
Set stack size for the mqtt client.
60+
Need more stack, since calling juice API from the handler.
61+
62+
config EXAMPLE_MQTT_BROKER_PORT
63+
int "port for the mosquitto to listen to"
64+
default 1883
65+
help
66+
This is a port which the local mosquitto uses.
67+
68+
choice EXAMPLE_SERVERLESS_ROLE
69+
prompt "Choose your role"
70+
default EXAMPLE_SERVERLESS_ROLE_PEER1
71+
help
72+
Choose either peer1 or peer2.
73+
It's not very important which device is peer1
74+
(peer-1 sends sync messages, peer2 listens for them)
75+
It is important that we have two peers,
76+
one with peer1 config, another one with peer2 config
77+
78+
config EXAMPLE_SERVERLESS_ROLE_PEER1
79+
bool "peer1"
80+
81+
config EXAMPLE_SERVERLESS_ROLE_PEER2
82+
bool "peer2"
83+
endchoice
84+
85+
endmenu

0 commit comments

Comments
 (0)