Skip to content

Commit

Permalink
feat(mosq): Add example with two brokers synced on P2P
Browse files Browse the repository at this point in the history
Broker-less two chip example which virtual private IoT
networks on MQTT protocol.
  • Loading branch information
david-cermak committed Dec 19, 2024
1 parent 269351f commit d57b8c5
Show file tree
Hide file tree
Showing 15 changed files with 759 additions and 4 deletions.
10 changes: 6 additions & 4 deletions .github/workflows/mosq__build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down
1 change: 1 addition & 0 deletions ci/check_copyright_ignore.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
components/mosquitto/examples/serverless_mqtt/components/libjuice/port/juice_random.c
3 changes: 3 additions & 0 deletions components/mosquitto/.build-test-rules.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
components/mosquitto/examples/serverless_mqtt:
disable:
- if: IDF_TARGET not in ["esp32", "esp32s3", "esp32c3"]
6 changes: 6 additions & 0 deletions components/mosquitto/examples/serverless_mqtt/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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 changes: 53 additions & 0 deletions components/mosquitto/examples/serverless_mqtt/README.md
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -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)
Original file line number Diff line number Diff line change
@@ -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"
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
idf_component_register(SRCS "serverless_mqtt.c"
"wifi_connect.c"
INCLUDE_DIRS "."
REQUIRES libjuice nvs_flash mqtt json esp_wifi)
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
## IDF Component Manager Manifest File
dependencies:
espressif/mosquitto:
override_path: ../../..
espressif/sock_utils: "*"
Loading

0 comments on commit d57b8c5

Please sign in to comment.