From a30d2030fc45da5e146ae7bd710e31649a257842 Mon Sep 17 00:00:00 2001 From: ellebi2000 Date: Sat, 25 May 2024 12:57:03 +0200 Subject: [PATCH 01/11] added mqtt pub/sub example --- pico_w/mqtt/CMakeLists.txt | 0 pico_w/mqtt/mqtt_client/CMakeLists.txt | 0 pico_w/mqtt/mqtt_client/lwipopts.h | 90 ++++++++++++++ pico_w/mqtt/mqtt_client/mqtt_client.c | 158 +++++++++++++++++++++++++ 4 files changed, 248 insertions(+) create mode 100644 pico_w/mqtt/CMakeLists.txt create mode 100644 pico_w/mqtt/mqtt_client/CMakeLists.txt create mode 100644 pico_w/mqtt/mqtt_client/lwipopts.h create mode 100644 pico_w/mqtt/mqtt_client/mqtt_client.c diff --git a/pico_w/mqtt/CMakeLists.txt b/pico_w/mqtt/CMakeLists.txt new file mode 100644 index 000000000..e69de29bb diff --git a/pico_w/mqtt/mqtt_client/CMakeLists.txt b/pico_w/mqtt/mqtt_client/CMakeLists.txt new file mode 100644 index 000000000..e69de29bb diff --git a/pico_w/mqtt/mqtt_client/lwipopts.h b/pico_w/mqtt/mqtt_client/lwipopts.h new file mode 100644 index 000000000..9e71835b9 --- /dev/null +++ b/pico_w/mqtt/mqtt_client/lwipopts.h @@ -0,0 +1,90 @@ +#ifndef _LWIPOPTS_EXAMPLE_COMMONH_H +#define _LWIPOPTS_EXAMPLE_COMMONH_H + + +// Common settings used in most of the pico_w examples +// (see https://www.nongnu.org/lwip/2_1_x/group__lwip__opts.html for details) + +// allow override in some examples +#ifndef NO_SYS +#define NO_SYS 1 +#endif +// allow override in some examples +#ifndef LWIP_SOCKET +#define LWIP_SOCKET 0 +#endif +#if PICO_CYW43_ARCH_POLL +#define MEM_LIBC_MALLOC 1 +#else +// MEM_LIBC_MALLOC is incompatible with non polling versions +#define MEM_LIBC_MALLOC 0 +#endif +#define MEM_ALIGNMENT 4 +#define MEM_SIZE 4000 +#define MEMP_NUM_SYS_TIMEOUT (LWIP_NUM_SYS_TIMEOUT_INTERNAL+1) +#define MEMP_NUM_TCP_SEG 32 +#define MEMP_NUM_ARP_QUEUE 10 +#define PBUF_POOL_SIZE 24 +#define LWIP_ARP 1 +#define LWIP_ETHERNET 1 +#define LWIP_ICMP 1 +#define LWIP_RAW 1 +#define TCP_WND (8 * TCP_MSS) +#define TCP_MSS 1460 +#define TCP_SND_BUF (8 * TCP_MSS) +#define TCP_SND_QUEUELEN ((4 * (TCP_SND_BUF) + (TCP_MSS - 1)) / (TCP_MSS)) +#define LWIP_NETIF_STATUS_CALLBACK 1 +#define LWIP_NETIF_LINK_CALLBACK 1 +#define LWIP_NETIF_HOSTNAME 1 +#define LWIP_NETCONN 0 +#define MEM_STATS 0 +#define SYS_STATS 0 +#define MEMP_STATS 0 +#define LINK_STATS 0 +// #define ETH_PAD_SIZE 2 +#define LWIP_CHKSUM_ALGORITHM 3 +#define LWIP_DHCP 1 +#define LWIP_IPV4 1 +#define LWIP_TCP 1 +#define LWIP_UDP 1 +#define LWIP_DNS 1 +#define LWIP_TCP_KEEPALIVE 1 +#define LWIP_NETIF_TX_SINGLE_PBUF 1 +#define DHCP_DOES_ARP_CHECK 0 +#define LWIP_DHCP_DOES_ACD_CHECK 0 + +#ifndef NDEBUG +#define LWIP_DEBUG 1 +#define LWIP_STATS 1 +#define LWIP_STATS_DISPLAY 1 +#endif + +#define ETHARP_DEBUG LWIP_DBG_OFF +#define NETIF_DEBUG LWIP_DBG_OFF +#define PBUF_DEBUG LWIP_DBG_OFF +#define API_LIB_DEBUG LWIP_DBG_OFF +#define API_MSG_DEBUG LWIP_DBG_OFF +#define SOCKETS_DEBUG LWIP_DBG_OFF +#define ICMP_DEBUG LWIP_DBG_OFF +#define INET_DEBUG LWIP_DBG_OFF +#define IP_DEBUG LWIP_DBG_OFF +#define IP_REASS_DEBUG LWIP_DBG_OFF +#define RAW_DEBUG LWIP_DBG_OFF +#define MEM_DEBUG LWIP_DBG_OFF +#define MEMP_DEBUG LWIP_DBG_OFF +#define SYS_DEBUG LWIP_DBG_OFF +#define TCP_DEBUG LWIP_DBG_OFF +#define TCP_INPUT_DEBUG LWIP_DBG_OFF +#define TCP_OUTPUT_DEBUG LWIP_DBG_OFF +#define TCP_RTO_DEBUG LWIP_DBG_OFF +#define TCP_CWND_DEBUG LWIP_DBG_OFF +#define TCP_WND_DEBUG LWIP_DBG_OFF +#define TCP_FR_DEBUG LWIP_DBG_OFF +#define TCP_QLEN_DEBUG LWIP_DBG_OFF +#define TCP_RST_DEBUG LWIP_DBG_OFF +#define UDP_DEBUG LWIP_DBG_OFF +#define TCPIP_DEBUG LWIP_DBG_OFF +#define PPP_DEBUG LWIP_DBG_OFF +#define SLIP_DEBUG LWIP_DBG_OFF +#define DHCP_DEBUG LWIP_DBG_OFF +#endif /* __LWIPOPTS_H__ */ \ No newline at end of file diff --git a/pico_w/mqtt/mqtt_client/mqtt_client.c b/pico_w/mqtt/mqtt_client/mqtt_client.c new file mode 100644 index 000000000..ed94104a9 --- /dev/null +++ b/pico_w/mqtt/mqtt_client/mqtt_client.c @@ -0,0 +1,158 @@ +// +// Created by elliot on 25/05/24. +// +#include "pico/stdlib.h" +#include "hardware/gpio.h" +#include "hardware/irq.h" +#include "hardware/adc.h" +#include "pico/cyw43_arch.h" +#include "lwip/apps/mqtt.h" + + +// Temperature +#define TEMPERATURE_UNITS 'C' + +// WiFi +#define WIFI_SSID "Elliot" +#define WIFI_PASSWORD "cocoelle" +// MQTT +#define PORT 1883 +#define MQTT_CLIENT_ID "clientID" +#define MQTT_BROKER_IP "192.168.204.71" + + + +typedef struct { + mqtt_client_t* mqtt_client_inst; + struct mqtt_connect_client_info_t mqtt_client_info; + uint8_t data[MQTT_OUTPUT_RINGBUF_SIZE]; + uint8_t topic[100]; + uint32_t len; +} MQTT_CLIENT_DATA_T; + +MQTT_CLIENT_DATA_T *mqtt; + + + +/* References for this implementation: + * raspberry-pi-pico-c-sdk.pdf, Section '4.1.1. hardware_adc' + * pico-examples/adc/adc_console/adc_console.c */ +float read_onboard_temperature(const char unit) { + + /* 12-bit conversion, assume max value == ADC_VREF == 3.3 V */ + const float conversionFactor = 3.3f / (1 << 12); + + float adc = (float)adc_read() * conversionFactor; + float tempC = 27.0f - (adc - 0.706f) / 0.001721f; + + if (unit == 'C') { + return tempC; + } else if (unit == 'F') { + return tempC * 9 / 5 + 32; + } + + return -1.0f; +} + +void control_led(bool on) { + // Public state on /state topic + const char* message = on ? "On" : "Off"; + char state_topic[128]; + snprintf(state_topic, sizeof(state_topic), "%s/state", mqtt->topic); + mqtt_publish(mqtt->mqtt_client_inst, state_topic, message, strlen(message), 0, 0, NULL, NULL); +} + +void publish_temperature() { + //Public temperature on /temperature topic + float temperature = read_onboard_temperature(TEMPERATURE_UNITS); + char temp_str[16]; + snprintf(temp_str, sizeof(temp_str), "%.2f", temperature); + mqtt_publish(mqtt->mqtt_client_inst, "/temperature", temp_str, strlen(temp_str), 0, 0, NULL, NULL); +} +static void mqtt_incoming_data_cb(void *arg, const u8_t *data, u16_t len, u8_t flags) { + MQTT_CLIENT_DATA_T* mqtt_client = (MQTT_CLIENT_DATA_T*)arg; + strncpy(mqtt_client->data, data, len); + mqtt_client->len = len; + mqtt_client->data[len] = '\0'; + //Stampo i messaggi e i topic + printf("Topic: %s, Messaggio: %s\n", mqtt_client->topic, mqtt_client->data); + + + if (strcmp(mqtt->topic, "/led") == 0) + { + if (strcmp((const char *)mqtt_client->data, "On") == 0) + control_led(true); + else if (strcmp((const char *)mqtt_client->data, "Off") == 0) + control_led(false); + } + +} + +static void mqtt_incoming_publish_cb(void *arg, const char *topic, u32_t tot_len) { + MQTT_CLIENT_DATA_T* mqtt_client = (MQTT_CLIENT_DATA_T*)arg; + strcpy(mqtt_client->topic, topic); +} + +static void mqtt_connection_cb(mqtt_client_t *client, void *arg, mqtt_connection_status_t status) { + MQTT_CLIENT_DATA_T* mqtt_client = (MQTT_CLIENT_DATA_T*)arg; + if (status == MQTT_CONNECT_ACCEPTED) { + mqtt_sub_unsub(client, "/temperature", 0, NULL, arg, 1); + printf("Connesso al topic /temperature con successo\n"); + } +} +int main() { + + stdio_init_all(); + + adc_init(); + adc_set_temp_sensor_enabled(true); + adc_select_input(4); + + mqtt = (MQTT_CLIENT_DATA_T*)calloc(1, sizeof(MQTT_CLIENT_DATA_T)); + if (!mqtt) { + printf("Failed to inizialize MQTT client \n"); + return 1; + } + // MQTT CLIENT INFO + mqtt->mqtt_client_info.client_id = MQTT_CLIENT_ID; + mqtt->mqtt_client_info.keep_alive = 60; // Keep alive in secondi + + if (cyw43_arch_init()) { + printf("Failed to inizialize CYW43\n"); + return 1; + } + + cyw43_arch_enable_sta_mode(); + if (cyw43_arch_wifi_connect_timeout_ms(WIFI_SSID, WIFI_PASSWORD, CYW43_AUTH_WPA2_AES_PSK, 10000)) { + printf("Wi-Fi error\n"); + return 1; + } + printf("\nConnected to Wifi\n"); + + ip_addr_t addr; + if (!ip4addr_aton(MQTT_BROKER_IP, &addr)) { + printf("MQTT ip Address not valid !\n"); + return 1; + } + + mqtt->mqtt_client_inst = mqtt_client_new(); + if (!mqtt->mqtt_client_inst) { + printf("MQTT client instance creation error\n"); + return 1; + } + + if (mqtt_client_connect(mqtt->mqtt_client_inst, &addr, PORT, mqtt_connection_cb, mqtt, &mqtt->mqtt_client_info) != ERR_OK) { + printf("MQTT broker connection error\n"); + return 1; + } + printf("Successfully connected to the MQTT broker\n"); + mqtt_set_inpub_callback(mqtt->mqtt_client_inst, mqtt_incoming_publish_cb, mqtt_incoming_data_cb, mqtt); + + while (1) { + publish_temperature(); // Public temperature every 5 sec + sleep_ms(5000); + tight_loop_contents(); + } + + return 0; +} \ No newline at end of file From 62c0a139220f7d1cfe6e09b01f3f19da461e95b3 Mon Sep 17 00:00:00 2001 From: ellebi2000 Date: Sat, 25 May 2024 12:59:21 +0200 Subject: [PATCH 02/11] added mqtt pub/sub example --- pico_w/CMakeLists.txt | 6 +++++ pico_w/mqtt/CMakeLists.txt | 8 +++++++ pico_w/mqtt/mqtt_client/CMakeLists.txt | 31 ++++++++++++++++++++++++++ pico_w/mqtt/mqtt_client/mqtt_client.c | 19 ++++++++++------ 4 files changed, 57 insertions(+), 7 deletions(-) diff --git a/pico_w/CMakeLists.txt b/pico_w/CMakeLists.txt index f66c02e95..62e08892f 100644 --- a/pico_w/CMakeLists.txt +++ b/pico_w/CMakeLists.txt @@ -24,5 +24,11 @@ if (PICO_CYW43_SUPPORTED) # set by PICO_BOARD=pico_w else() add_subdirectory(bt) endif() + + if (NOT TARGET picow_bt_example_no_cyw43_lwip_background) + message("Skipping Pico W Mqtt examples as support is not available") + else() + add_subdirectory(mqtt) + endif () endif() endif() diff --git a/pico_w/mqtt/CMakeLists.txt b/pico_w/mqtt/CMakeLists.txt index e69de29bb..2f018792b 100644 --- a/pico_w/mqtt/CMakeLists.txt +++ b/pico_w/mqtt/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.12) + +project(test_mqtt C CXX ASM) +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) + +add_subdirectory(mqtt_client) +pico_sdk_init() diff --git a/pico_w/mqtt/mqtt_client/CMakeLists.txt b/pico_w/mqtt/mqtt_client/CMakeLists.txt index e69de29bb..ff66b2fb4 100644 --- a/pico_w/mqtt/mqtt_client/CMakeLists.txt +++ b/pico_w/mqtt/mqtt_client/CMakeLists.txt @@ -0,0 +1,31 @@ + + +add_executable(mqttApp + mqtt_client.c + ) +target_link_libraries(mqttApp + pico_stdlib + hardware_adc + pico_cyw43_arch_lwip_threadsafe_background + pico_lwip_mqtt +) +# Initialise pico_sdk from installed location +# (note this can come from environment, CMake cache etc) + +set(PICO_BOARD pico_w CACHE STRING "Board type") +# Pull in Raspberry Pi Pico SDK (must be before project) +if (PICO_SDK_VERSION_STRING VERSION_LESS "1.4.0") + message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.4.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}") +endif() +# Initialise the Raspberry Pi Pico SDK +# Add executable. Default name is the project name, version 0.1 +pico_set_program_name(mqttApp "mqttApp") +pico_set_program_version(mqttApp "0.1") +pico_enable_stdio_uart(mqttApp 1) +pico_enable_stdio_usb(mqttApp 1) +target_include_directories(mqttApp PRIVATE + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required +) + +pico_add_extra_outputs(mqttApp) \ No newline at end of file diff --git a/pico_w/mqtt/mqtt_client/mqtt_client.c b/pico_w/mqtt/mqtt_client/mqtt_client.c index ed94104a9..28d7d3d94 100644 --- a/pico_w/mqtt/mqtt_client/mqtt_client.c +++ b/pico_w/mqtt/mqtt_client/mqtt_client.c @@ -13,12 +13,12 @@ #define TEMPERATURE_UNITS 'C' // WiFi -#define WIFI_SSID "Elliot" -#define WIFI_PASSWORD "cocoelle" +#define WIFI_SSID "YOUR_WIFI_SSID" +#define WIFI_PASSWORD "YOUR_WIFI_PASSWD" // MQTT #define PORT 1883 #define MQTT_CLIENT_ID "clientID" -#define MQTT_BROKER_IP "192.168.204.71" +#define MQTT_BROKER_IP "YOUR_BROKER_IP" @@ -55,8 +55,13 @@ float read_onboard_temperature(const char unit) { } void control_led(bool on) { - // Public state on /state topic + // Public state on /state topic and on/off led board const char* message = on ? "On" : "Off"; + if (on) + cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, 1); + else + cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, 0); + char state_topic[128]; snprintf(state_topic, sizeof(state_topic), "%s/state", mqtt->topic); mqtt_publish(mqtt->mqtt_client_inst, state_topic, message, strlen(message), 0, 0, NULL, NULL); @@ -75,7 +80,7 @@ static void mqtt_incoming_data_cb(void *arg, const u8_t *data, u16_t len, u8_t f mqtt_client->len = len; mqtt_client->data[len] = '\0'; //Stampo i messaggi e i topic - printf("Topic: %s, Messaggio: %s\n", mqtt_client->topic, mqtt_client->data); + printf("Topic: %s, Message: %s\n", mqtt_client->topic, mqtt_client->data); if (strcmp(mqtt->topic, "/led") == 0) @@ -96,8 +101,8 @@ static void mqtt_incoming_publish_cb(void *arg, const char *topic, u32_t tot_len static void mqtt_connection_cb(mqtt_client_t *client, void *arg, mqtt_connection_status_t status) { MQTT_CLIENT_DATA_T* mqtt_client = (MQTT_CLIENT_DATA_T*)arg; if (status == MQTT_CONNECT_ACCEPTED) { - mqtt_sub_unsub(client, "/temperature", 0, NULL, arg, 1); - printf("Connesso al topic /temperature con successo\n"); + mqtt_sub_unsub(client, "/led", 0, NULL, arg, 1); + printf("Connected to the /led topic successfully\n"); } } int main() { From f83eefefeb739329ca743b8fe73f51123ad6e96a Mon Sep 17 00:00:00 2001 From: ellebi2000 Date: Sun, 2 Jun 2024 13:42:52 +0200 Subject: [PATCH 03/11] removed unnecessary comments --- pico_w/mqtt/mqtt_client/CMakeLists.txt | 5 +---- pico_w/mqtt/mqtt_client/mqtt_client.c | 6 ++---- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/pico_w/mqtt/mqtt_client/CMakeLists.txt b/pico_w/mqtt/mqtt_client/CMakeLists.txt index ff66b2fb4..fda9fcb2d 100644 --- a/pico_w/mqtt/mqtt_client/CMakeLists.txt +++ b/pico_w/mqtt/mqtt_client/CMakeLists.txt @@ -9,16 +9,13 @@ target_link_libraries(mqttApp pico_cyw43_arch_lwip_threadsafe_background pico_lwip_mqtt ) -# Initialise pico_sdk from installed location -# (note this can come from environment, CMake cache etc) set(PICO_BOARD pico_w CACHE STRING "Board type") # Pull in Raspberry Pi Pico SDK (must be before project) if (PICO_SDK_VERSION_STRING VERSION_LESS "1.4.0") message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.4.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}") endif() -# Initialise the Raspberry Pi Pico SDK -# Add executable. Default name is the project name, version 0.1 + pico_set_program_name(mqttApp "mqttApp") pico_set_program_version(mqttApp "0.1") pico_enable_stdio_uart(mqttApp 1) diff --git a/pico_w/mqtt/mqtt_client/mqtt_client.c b/pico_w/mqtt/mqtt_client/mqtt_client.c index 28d7d3d94..0441d44dc 100644 --- a/pico_w/mqtt/mqtt_client/mqtt_client.c +++ b/pico_w/mqtt/mqtt_client/mqtt_client.c @@ -79,9 +79,8 @@ static void mqtt_incoming_data_cb(void *arg, const u8_t *data, u16_t len, u8_t f strncpy(mqtt_client->data, data, len); mqtt_client->len = len; mqtt_client->data[len] = '\0'; - //Stampo i messaggi e i topic - printf("Topic: %s, Message: %s\n", mqtt_client->topic, mqtt_client->data); + printf("Topic: %s, Message: %s\n", mqtt_client->topic, mqtt_client->data); if (strcmp(mqtt->topic, "/led") == 0) { @@ -90,7 +89,6 @@ static void mqtt_incoming_data_cb(void *arg, const u8_t *data, u16_t len, u8_t f else if (strcmp((const char *)mqtt_client->data, "Off") == 0) control_led(false); } - } static void mqtt_incoming_publish_cb(void *arg, const char *topic, u32_t tot_len) { @@ -120,7 +118,7 @@ int main() { } // MQTT CLIENT INFO mqtt->mqtt_client_info.client_id = MQTT_CLIENT_ID; - mqtt->mqtt_client_info.keep_alive = 60; // Keep alive in secondi + mqtt->mqtt_client_info.keep_alive = 60; // Keep alive in sec if (cyw43_arch_init()) { printf("Failed to inizialize CYW43\n"); From 04f621fb7fd22a9deaf35cb1cbd741d258fe2f5e Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Wed, 29 Jan 2025 15:02:19 +0000 Subject: [PATCH 04/11] Move mqtt example to wifi folder --- pico_w/CMakeLists.txt | 6 ------ pico_w/wifi/CMakeLists.txt | 1 + pico_w/{ => wifi}/mqtt/CMakeLists.txt | 0 pico_w/{ => wifi}/mqtt/mqtt_client/CMakeLists.txt | 0 pico_w/{ => wifi}/mqtt/mqtt_client/lwipopts.h | 0 pico_w/{ => wifi}/mqtt/mqtt_client/mqtt_client.c | 0 6 files changed, 1 insertion(+), 6 deletions(-) rename pico_w/{ => wifi}/mqtt/CMakeLists.txt (100%) rename pico_w/{ => wifi}/mqtt/mqtt_client/CMakeLists.txt (100%) rename pico_w/{ => wifi}/mqtt/mqtt_client/lwipopts.h (100%) rename pico_w/{ => wifi}/mqtt/mqtt_client/mqtt_client.c (100%) diff --git a/pico_w/CMakeLists.txt b/pico_w/CMakeLists.txt index 62e08892f..f66c02e95 100644 --- a/pico_w/CMakeLists.txt +++ b/pico_w/CMakeLists.txt @@ -24,11 +24,5 @@ if (PICO_CYW43_SUPPORTED) # set by PICO_BOARD=pico_w else() add_subdirectory(bt) endif() - - if (NOT TARGET picow_bt_example_no_cyw43_lwip_background) - message("Skipping Pico W Mqtt examples as support is not available") - else() - add_subdirectory(mqtt) - endif () endif() endif() diff --git a/pico_w/wifi/CMakeLists.txt b/pico_w/wifi/CMakeLists.txt index 4599a21cd..31797084b 100644 --- a/pico_w/wifi/CMakeLists.txt +++ b/pico_w/wifi/CMakeLists.txt @@ -18,6 +18,7 @@ else() add_subdirectory_exclude_platforms(tcp_server) add_subdirectory_exclude_platforms(udp_beacon) add_subdirectory_exclude_platforms(http_client) + add_subdirectory_exclude_platforms(mqtt) if (NOT PICO_MBEDTLS_PATH) message("Skipping tls examples as PICO_MBEDTLS_PATH is not defined") diff --git a/pico_w/mqtt/CMakeLists.txt b/pico_w/wifi/mqtt/CMakeLists.txt similarity index 100% rename from pico_w/mqtt/CMakeLists.txt rename to pico_w/wifi/mqtt/CMakeLists.txt diff --git a/pico_w/mqtt/mqtt_client/CMakeLists.txt b/pico_w/wifi/mqtt/mqtt_client/CMakeLists.txt similarity index 100% rename from pico_w/mqtt/mqtt_client/CMakeLists.txt rename to pico_w/wifi/mqtt/mqtt_client/CMakeLists.txt diff --git a/pico_w/mqtt/mqtt_client/lwipopts.h b/pico_w/wifi/mqtt/mqtt_client/lwipopts.h similarity index 100% rename from pico_w/mqtt/mqtt_client/lwipopts.h rename to pico_w/wifi/mqtt/mqtt_client/lwipopts.h diff --git a/pico_w/mqtt/mqtt_client/mqtt_client.c b/pico_w/wifi/mqtt/mqtt_client/mqtt_client.c similarity index 100% rename from pico_w/mqtt/mqtt_client/mqtt_client.c rename to pico_w/wifi/mqtt/mqtt_client/mqtt_client.c From e239bc4d23ad91447b5429431499bd4d64197e3f Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Wed, 29 Jan 2025 17:06:56 +0000 Subject: [PATCH 05/11] Tidy up mqtt example Fix review comments and build warnings. Use MQTT_SERVER build variable instead of hard coding IP address. Make a DNS request for the server. Use async timer for publishing the temperature. --- pico_w/wifi/mqtt/mqtt_client/CMakeLists.txt | 41 +++-- pico_w/wifi/mqtt/mqtt_client/README | 33 ++++ pico_w/wifi/mqtt/mqtt_client/lwipopts.h | 92 +--------- pico_w/wifi/mqtt/mqtt_client/mqtt_client.c | 193 ++++++++++++-------- 4 files changed, 181 insertions(+), 178 deletions(-) create mode 100644 pico_w/wifi/mqtt/mqtt_client/README diff --git a/pico_w/wifi/mqtt/mqtt_client/CMakeLists.txt b/pico_w/wifi/mqtt/mqtt_client/CMakeLists.txt index fda9fcb2d..3846ddd4f 100644 --- a/pico_w/wifi/mqtt/mqtt_client/CMakeLists.txt +++ b/pico_w/wifi/mqtt/mqtt_client/CMakeLists.txt @@ -1,28 +1,35 @@ +# Define the host name of the MQTT server in an environment variable or pass it to cmake, +# e.g. cmake -DMQTT_SERVER=myserver .. +if (DEFINED ENV{MQTT_SERVER} AND (NOT MQTT_SERVER)) + set(MQTT_SERVER $ENV{MQTT_SERVER}) + message("Using MQTT_SERVER from environment ('${MQTT_SERVER}')") +endif() +if (NOT MQTT_SERVER) + return() +endif() + +# Define the name of an MQTT broker/server to enable this example +set(MQTT_SERVER "${MQTT_SERVER}" CACHE INTERNAL "MQTT server for examples") -add_executable(mqttApp +set(TARGET_NAME mqtt_client) + +add_executable(${TARGET_NAME} mqtt_client.c ) -target_link_libraries(mqttApp +target_link_libraries(${TARGET_NAME} pico_stdlib hardware_adc pico_cyw43_arch_lwip_threadsafe_background pico_lwip_mqtt ) - -set(PICO_BOARD pico_w CACHE STRING "Board type") -# Pull in Raspberry Pi Pico SDK (must be before project) -if (PICO_SDK_VERSION_STRING VERSION_LESS "1.4.0") - message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.4.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}") -endif() - -pico_set_program_name(mqttApp "mqttApp") -pico_set_program_version(mqttApp "0.1") -pico_enable_stdio_uart(mqttApp 1) -pico_enable_stdio_usb(mqttApp 1) -target_include_directories(mqttApp PRIVATE +target_include_directories(${TARGET_NAME} PRIVATE ${CMAKE_CURRENT_LIST_DIR} - ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required + ${CMAKE_CURRENT_LIST_DIR}/../.. # for our common lwipopts or any other standard includes, if required ) - -pico_add_extra_outputs(mqttApp) \ No newline at end of file +target_compile_definitions(${TARGET_NAME} PRIVATE + WIFI_SSID=\"${WIFI_SSID}\" + WIFI_PASSWORD=\"${WIFI_PASSWORD}\" + MQTT_SERVER=\"${MQTT_SERVER}\" + ) +pico_add_extra_outputs(${TARGET_NAME}) diff --git a/pico_w/wifi/mqtt/mqtt_client/README b/pico_w/wifi/mqtt/mqtt_client/README new file mode 100644 index 000000000..4886a4b1c --- /dev/null +++ b/pico_w/wifi/mqtt/mqtt_client/README @@ -0,0 +1,33 @@ +To use this example you will need to install an MQTT server on your network. +To install the Mosquitto MQTT client on a Raspberry Pi Linux device run the following... + +sudo apt install mosquitto +sudo apt install mosquitto-clients + +Check it works... + +mosquitto_pub -t test_topic -m "Yes it works" -r +mosquitto_sub -t test_topic -C 1 + +To allow an external client to connect to the server you will have to change the configuration. Add the following to /etc/mosquitto/conf.d/mosquitto.conf + +allow_anonymous true +listener 1883 0.0.0.0 + +Then restart the service. + +sudo service mosquitto restart + +When building the code set the host name of the MQTT server, e.g. + +export MQTT_SERVER=myhost +cmake .. + +The example should publish its core temperature to the /temperature topic. You can subscribe to this topic from another machine. + +mosquitto_sub -h $MQTT_SERVER -t '/temperature' + +You can turn the led on and off by publishing messages. + +mosquitto_pub -h $MQTT_SERVER -t '/led' -m on +mosquitto_pub -h $MQTT_SERVER -t '/led' -m off diff --git a/pico_w/wifi/mqtt/mqtt_client/lwipopts.h b/pico_w/wifi/mqtt/mqtt_client/lwipopts.h index 9e71835b9..e4817116f 100644 --- a/pico_w/wifi/mqtt/mqtt_client/lwipopts.h +++ b/pico_w/wifi/mqtt/mqtt_client/lwipopts.h @@ -1,90 +1,12 @@ -#ifndef _LWIPOPTS_EXAMPLE_COMMONH_H -#define _LWIPOPTS_EXAMPLE_COMMONH_H +#ifndef _LWIPOPTS_H +#define _LWIPOPTS_H +// Generally you would define your own explicit list of lwIP options +// (see https://www.nongnu.org/lwip/2_1_x/group__lwip__opts.html) +// +// This example uses a common include to avoid repetition +#include "lwipopts_examples_common.h" -// Common settings used in most of the pico_w examples -// (see https://www.nongnu.org/lwip/2_1_x/group__lwip__opts.html for details) - -// allow override in some examples -#ifndef NO_SYS -#define NO_SYS 1 -#endif -// allow override in some examples -#ifndef LWIP_SOCKET -#define LWIP_SOCKET 0 -#endif -#if PICO_CYW43_ARCH_POLL -#define MEM_LIBC_MALLOC 1 -#else -// MEM_LIBC_MALLOC is incompatible with non polling versions -#define MEM_LIBC_MALLOC 0 -#endif -#define MEM_ALIGNMENT 4 -#define MEM_SIZE 4000 #define MEMP_NUM_SYS_TIMEOUT (LWIP_NUM_SYS_TIMEOUT_INTERNAL+1) -#define MEMP_NUM_TCP_SEG 32 -#define MEMP_NUM_ARP_QUEUE 10 -#define PBUF_POOL_SIZE 24 -#define LWIP_ARP 1 -#define LWIP_ETHERNET 1 -#define LWIP_ICMP 1 -#define LWIP_RAW 1 -#define TCP_WND (8 * TCP_MSS) -#define TCP_MSS 1460 -#define TCP_SND_BUF (8 * TCP_MSS) -#define TCP_SND_QUEUELEN ((4 * (TCP_SND_BUF) + (TCP_MSS - 1)) / (TCP_MSS)) -#define LWIP_NETIF_STATUS_CALLBACK 1 -#define LWIP_NETIF_LINK_CALLBACK 1 -#define LWIP_NETIF_HOSTNAME 1 -#define LWIP_NETCONN 0 -#define MEM_STATS 0 -#define SYS_STATS 0 -#define MEMP_STATS 0 -#define LINK_STATS 0 -// #define ETH_PAD_SIZE 2 -#define LWIP_CHKSUM_ALGORITHM 3 -#define LWIP_DHCP 1 -#define LWIP_IPV4 1 -#define LWIP_TCP 1 -#define LWIP_UDP 1 -#define LWIP_DNS 1 -#define LWIP_TCP_KEEPALIVE 1 -#define LWIP_NETIF_TX_SINGLE_PBUF 1 -#define DHCP_DOES_ARP_CHECK 0 -#define LWIP_DHCP_DOES_ACD_CHECK 0 -#ifndef NDEBUG -#define LWIP_DEBUG 1 -#define LWIP_STATS 1 -#define LWIP_STATS_DISPLAY 1 #endif - -#define ETHARP_DEBUG LWIP_DBG_OFF -#define NETIF_DEBUG LWIP_DBG_OFF -#define PBUF_DEBUG LWIP_DBG_OFF -#define API_LIB_DEBUG LWIP_DBG_OFF -#define API_MSG_DEBUG LWIP_DBG_OFF -#define SOCKETS_DEBUG LWIP_DBG_OFF -#define ICMP_DEBUG LWIP_DBG_OFF -#define INET_DEBUG LWIP_DBG_OFF -#define IP_DEBUG LWIP_DBG_OFF -#define IP_REASS_DEBUG LWIP_DBG_OFF -#define RAW_DEBUG LWIP_DBG_OFF -#define MEM_DEBUG LWIP_DBG_OFF -#define MEMP_DEBUG LWIP_DBG_OFF -#define SYS_DEBUG LWIP_DBG_OFF -#define TCP_DEBUG LWIP_DBG_OFF -#define TCP_INPUT_DEBUG LWIP_DBG_OFF -#define TCP_OUTPUT_DEBUG LWIP_DBG_OFF -#define TCP_RTO_DEBUG LWIP_DBG_OFF -#define TCP_CWND_DEBUG LWIP_DBG_OFF -#define TCP_WND_DEBUG LWIP_DBG_OFF -#define TCP_FR_DEBUG LWIP_DBG_OFF -#define TCP_QLEN_DEBUG LWIP_DBG_OFF -#define TCP_RST_DEBUG LWIP_DBG_OFF -#define UDP_DEBUG LWIP_DBG_OFF -#define TCPIP_DEBUG LWIP_DBG_OFF -#define PPP_DEBUG LWIP_DBG_OFF -#define SLIP_DEBUG LWIP_DBG_OFF -#define DHCP_DEBUG LWIP_DBG_OFF -#endif /* __LWIPOPTS_H__ */ \ No newline at end of file diff --git a/pico_w/wifi/mqtt/mqtt_client/mqtt_client.c b/pico_w/wifi/mqtt/mqtt_client/mqtt_client.c index 0441d44dc..c7ae9a926 100644 --- a/pico_w/wifi/mqtt/mqtt_client/mqtt_client.c +++ b/pico_w/wifi/mqtt/mqtt_client/mqtt_client.c @@ -1,43 +1,48 @@ +/** + * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + // // Created by elliot on 25/05/24. // #include "pico/stdlib.h" +#include "pico/cyw43_arch.h" +#include "pico/unique_id.h" #include "hardware/gpio.h" #include "hardware/irq.h" #include "hardware/adc.h" -#include "pico/cyw43_arch.h" #include "lwip/apps/mqtt.h" - +#include "lwip/dns.h" // Temperature -#define TEMPERATURE_UNITS 'C' - -// WiFi -#define WIFI_SSID "YOUR_WIFI_SSID" -#define WIFI_PASSWORD "YOUR_WIFI_PASSWD" -// MQTT -#define PORT 1883 -#define MQTT_CLIENT_ID "clientID" -#define MQTT_BROKER_IP "YOUR_BROKER_IP" +#ifndef TEMPERATURE_UNITS +#define TEMPERATURE_UNITS 'C' // Set to 'F' for Fahrenheit +#endif +#ifndef MQTT_SERVER +#error Need to define MQTT_SERVER +#endif +// MQTT parameters +#ifndef MQTT_PORT +#define MQTT_PORT 1883 +#endif typedef struct { mqtt_client_t* mqtt_client_inst; struct mqtt_connect_client_info_t mqtt_client_info; - uint8_t data[MQTT_OUTPUT_RINGBUF_SIZE]; - uint8_t topic[100]; + char data[MQTT_OUTPUT_RINGBUF_SIZE]; + char topic[100]; uint32_t len; + ip_addr_t mqtt_server_address; } MQTT_CLIENT_DATA_T; -MQTT_CLIENT_DATA_T *mqtt; - - - /* References for this implementation: * raspberry-pi-pico-c-sdk.pdf, Section '4.1.1. hardware_adc' * pico-examples/adc/adc_console/adc_console.c */ -float read_onboard_temperature(const char unit) { +static float read_onboard_temperature(const char unit) { /* 12-bit conversion, assume max value == ADC_VREF == 3.3 V */ const float conversionFactor = 3.3f / (1 << 12); @@ -45,7 +50,7 @@ float read_onboard_temperature(const char unit) { float adc = (float)adc_read() * conversionFactor; float tempC = 27.0f - (adc - 0.706f) / 0.001721f; - if (unit == 'C') { + if (unit == 'C' || unit != 'F') { return tempC; } else if (unit == 'F') { return tempC * 9 / 5 + 32; @@ -54,8 +59,8 @@ float read_onboard_temperature(const char unit) { return -1.0f; } -void control_led(bool on) { - // Public state on /state topic and on/off led board +static void control_led(MQTT_CLIENT_DATA_T *state, bool on) { + // Publish state on /state topic and on/off led board const char* message = on ? "On" : "Off"; if (on) cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, 1); @@ -63,99 +68,135 @@ void control_led(bool on) { cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, 0); char state_topic[128]; - snprintf(state_topic, sizeof(state_topic), "%s/state", mqtt->topic); - mqtt_publish(mqtt->mqtt_client_inst, state_topic, message, strlen(message), 0, 0, NULL, NULL); + snprintf(state_topic, sizeof(state_topic), "%s/state", state->topic); + mqtt_publish(state->mqtt_client_inst, state_topic, message, strlen(message), 0, 0, NULL, NULL); } -void publish_temperature() { - //Public temperature on /temperature topic +static void publish_temperature(MQTT_CLIENT_DATA_T *state) { + static float old_temperature; + const char temperature_key[] = "/temperature"; float temperature = read_onboard_temperature(TEMPERATURE_UNITS); - char temp_str[16]; - snprintf(temp_str, sizeof(temp_str), "%.2f", temperature); - mqtt_publish(mqtt->mqtt_client_inst, "/temperature", temp_str, strlen(temp_str), 0, 0, NULL, NULL); + if (temperature != old_temperature) { + old_temperature = temperature; + // Publish temperature on /temperature topic + char temp_str[16]; + snprintf(temp_str, sizeof(temp_str), "%.2f", temperature); + printf("Publishing %s to %s\n", temp_str, temperature_key); + mqtt_publish(state->mqtt_client_inst, temperature_key, temp_str, strlen(temp_str), 0, 0, NULL, NULL); + } } + static void mqtt_incoming_data_cb(void *arg, const u8_t *data, u16_t len, u8_t flags) { - MQTT_CLIENT_DATA_T* mqtt_client = (MQTT_CLIENT_DATA_T*)arg; - strncpy(mqtt_client->data, data, len); - mqtt_client->len = len; - mqtt_client->data[len] = '\0'; + MQTT_CLIENT_DATA_T* state = (MQTT_CLIENT_DATA_T*)arg; + strncpy(state->data, (const char *)data, len); + state->len = len; + state->data[len] = '\0'; - printf("Topic: %s, Message: %s\n", mqtt_client->topic, mqtt_client->data); + printf("Topic: %s, Message: %s\n", state->topic, state->data); - if (strcmp(mqtt->topic, "/led") == 0) + if (strcmp(state->topic, "/led") == 0) { - if (strcmp((const char *)mqtt_client->data, "On") == 0) - control_led(true); - else if (strcmp((const char *)mqtt_client->data, "Off") == 0) - control_led(false); + if (lwip_stricmp((const char *)state->data, "On") == 0) + control_led(state, true); + else if (lwip_stricmp((const char *)state->data, "Off") == 0) + control_led(state, false); } } static void mqtt_incoming_publish_cb(void *arg, const char *topic, u32_t tot_len) { MQTT_CLIENT_DATA_T* mqtt_client = (MQTT_CLIENT_DATA_T*)arg; - strcpy(mqtt_client->topic, topic); + strncpy(mqtt_client->topic, topic, sizeof(mqtt_client->topic)); } +static void temperature_worker_fn(async_context_t *context, async_at_time_worker_t *worker) { + MQTT_CLIENT_DATA_T* state = (MQTT_CLIENT_DATA_T*)worker->user_data; + publish_temperature(state); + async_context_add_at_time_worker_in_ms(context, worker, 10000); // repeat in 10s +} +static async_at_time_worker_t temperature_worker = { .do_work = temperature_worker_fn }; + static void mqtt_connection_cb(mqtt_client_t *client, void *arg, mqtt_connection_status_t status) { - MQTT_CLIENT_DATA_T* mqtt_client = (MQTT_CLIENT_DATA_T*)arg; if (status == MQTT_CONNECT_ACCEPTED) { mqtt_sub_unsub(client, "/led", 0, NULL, arg, 1); printf("Connected to the /led topic successfully\n"); + + // Publish temperature every 10 sec if it's changed + temperature_worker.user_data = arg; + async_context_add_at_time_worker_in_ms(cyw43_arch_async_context(), &temperature_worker, 0); + } else { + panic("Unexpected status"); } } -int main() { +static void start_client(MQTT_CLIENT_DATA_T *state) { + state->mqtt_client_inst = mqtt_client_new(); + if (!state->mqtt_client_inst) { + panic("MQTT client instance creation error"); + } + printf("IP address of this device %s\n", ipaddr_ntoa(&(netif_list->ip_addr))); + printf("Connecting to mqtt server at %s\n", ipaddr_ntoa(&state->mqtt_server_address)); + + cyw43_arch_lwip_begin(); + if (mqtt_client_connect(state->mqtt_client_inst, &state->mqtt_server_address, MQTT_PORT, mqtt_connection_cb, state, &state->mqtt_client_info) != ERR_OK) { + panic("MQTT broker connection error"); + } + mqtt_set_inpub_callback(state->mqtt_client_inst, mqtt_incoming_publish_cb, mqtt_incoming_data_cb, state); + cyw43_arch_lwip_end(); +} + +// Call back with a DNS result +static void dns_found(const char *hostname, const ip_addr_t *ipaddr, void *arg) { + MQTT_CLIENT_DATA_T *state = (MQTT_CLIENT_DATA_T*)arg; + if (ipaddr) { + state->mqtt_server_address = *ipaddr; + start_client(state); + } else { + panic("dns request failed"); + } +} + +int main(void) { stdio_init_all(); adc_init(); adc_set_temp_sensor_enabled(true); adc_select_input(4); - mqtt = (MQTT_CLIENT_DATA_T*)calloc(1, sizeof(MQTT_CLIENT_DATA_T)); - if (!mqtt) { - printf("Failed to inizialize MQTT client \n"); - return 1; - } - // MQTT CLIENT INFO - mqtt->mqtt_client_info.client_id = MQTT_CLIENT_ID; - mqtt->mqtt_client_info.keep_alive = 60; // Keep alive in sec + static MQTT_CLIENT_DATA_T state; + + // Use board unique id for the client id + static char client_id_buf[PICO_UNIQUE_BOARD_ID_SIZE_BYTES * 2 + 1]; + pico_get_unique_board_id_string(client_id_buf, sizeof(client_id_buf)); + state.mqtt_client_info.client_id = client_id_buf; + state.mqtt_client_info.keep_alive = 60; // Keep alive in sec if (cyw43_arch_init()) { - printf("Failed to inizialize CYW43\n"); - return 1; + panic("Failed to inizialize CYW43"); } cyw43_arch_enable_sta_mode(); - if (cyw43_arch_wifi_connect_timeout_ms(WIFI_SSID, WIFI_PASSWORD, CYW43_AUTH_WPA2_AES_PSK, 10000)) { - printf("Wi-Fi error\n"); - return 1; + if (cyw43_arch_wifi_connect_timeout_ms(WIFI_SSID, WIFI_PASSWORD, CYW43_AUTH_WPA2_AES_PSK, 30000)) { + panic("Failed to connect"); } printf("\nConnected to Wifi\n"); - ip_addr_t addr; - if (!ip4addr_aton(MQTT_BROKER_IP, &addr)) { - printf("MQTT ip Address not valid !\n"); - return 1; - } - - mqtt->mqtt_client_inst = mqtt_client_new(); - if (!mqtt->mqtt_client_inst) { - printf("MQTT client instance creation error\n"); - return 1; + // We are not in a callback so locking is needed when calling lwip + // Make a DNS request for the MQTT server IP address + cyw43_arch_lwip_begin(); + int err = dns_gethostbyname(MQTT_SERVER, &state.mqtt_server_address, dns_found, &state); + cyw43_arch_lwip_end(); + + if (err == ERR_OK) { + // We have the address, just start the client + start_client(&state); + } else if (err != ERR_INPROGRESS) { // ERR_INPROGRESS means expect a callback + panic("dns request failed"); } - if (mqtt_client_connect(mqtt->mqtt_client_inst, &addr, PORT, mqtt_connection_cb, mqtt, &mqtt->mqtt_client_info) != ERR_OK) { - printf("MQTT broker connection error\n"); - return 1; - } - printf("Successfully connected to the MQTT broker\n"); - mqtt_set_inpub_callback(mqtt->mqtt_client_inst, mqtt_incoming_publish_cb, mqtt_incoming_data_cb, mqtt); - - while (1) { - publish_temperature(); // Public temperature every 5 sec - sleep_ms(5000); - tight_loop_contents(); + while (true) { + cyw43_arch_poll(); + cyw43_arch_wait_for_work_until(make_timeout_time_ms(10000)); } return 0; -} \ No newline at end of file +} From 90e64d8d840dadbfeacc686d5f35ec7f1c78b0f3 Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Wed, 29 Jan 2025 18:16:26 +0000 Subject: [PATCH 06/11] Add support for username and password --- pico_w/wifi/mqtt/mqtt_client/CMakeLists.txt | 38 ++++++++++++++------- pico_w/wifi/mqtt/mqtt_client/README | 27 +++++++++++++++ pico_w/wifi/mqtt/mqtt_client/mqtt_client.c | 7 ++++ 3 files changed, 60 insertions(+), 12 deletions(-) diff --git a/pico_w/wifi/mqtt/mqtt_client/CMakeLists.txt b/pico_w/wifi/mqtt/mqtt_client/CMakeLists.txt index 3846ddd4f..c2ea20927 100644 --- a/pico_w/wifi/mqtt/mqtt_client/CMakeLists.txt +++ b/pico_w/wifi/mqtt/mqtt_client/CMakeLists.txt @@ -8,6 +8,14 @@ endif() if (NOT MQTT_SERVER) return() endif() +if (DEFINED ENV{MQTT_USERNAME} AND (NOT MQTT_USERNAME)) + set(MQTT_USERNAME $ENV{MQTT_USERNAME}) + message("Using MQTT_USERNAME from environment ('${MQTT_USERNAME}')") +endif() +if (DEFINED ENV{MQTT_PASSWORD} AND (NOT MQTT_PASSWORD)) + set(MQTT_PASSWORD $ENV{MQTT_PASSWORD}) + message("Using MQTT_PASSWORD from environment") +endif() # Define the name of an MQTT broker/server to enable this example set(MQTT_SERVER "${MQTT_SERVER}" CACHE INTERNAL "MQTT server for examples") @@ -15,21 +23,27 @@ set(MQTT_SERVER "${MQTT_SERVER}" CACHE INTERNAL "MQTT server for examples") set(TARGET_NAME mqtt_client) add_executable(${TARGET_NAME} - mqtt_client.c - ) + mqtt_client.c +) target_link_libraries(${TARGET_NAME} - pico_stdlib - hardware_adc - pico_cyw43_arch_lwip_threadsafe_background - pico_lwip_mqtt + pico_stdlib + hardware_adc + pico_cyw43_arch_lwip_threadsafe_background + pico_lwip_mqtt ) target_include_directories(${TARGET_NAME} PRIVATE - ${CMAKE_CURRENT_LIST_DIR} - ${CMAKE_CURRENT_LIST_DIR}/../.. # for our common lwipopts or any other standard includes, if required + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/../.. # for our common lwipopts or any other standard includes, if required ) target_compile_definitions(${TARGET_NAME} PRIVATE - WIFI_SSID=\"${WIFI_SSID}\" - WIFI_PASSWORD=\"${WIFI_PASSWORD}\" - MQTT_SERVER=\"${MQTT_SERVER}\" - ) + WIFI_SSID=\"${WIFI_SSID}\" + WIFI_PASSWORD=\"${WIFI_PASSWORD}\" + MQTT_SERVER=\"${MQTT_SERVER}\" +) +if (MQTT_USERNAME AND MQTT_PASSWORD) + target_compile_definitions(${TARGET_NAME} PRIVATE + MQTT_USERNAME=\"${MQTT_USERNAME}\" + MQTT_PASSWORD=\"${MQTT_PASSWORD}\" + ) +endif() pico_add_extra_outputs(${TARGET_NAME}) diff --git a/pico_w/wifi/mqtt/mqtt_client/README b/pico_w/wifi/mqtt/mqtt_client/README index 4886a4b1c..b4c124f2f 100644 --- a/pico_w/wifi/mqtt/mqtt_client/README +++ b/pico_w/wifi/mqtt/mqtt_client/README @@ -1,33 +1,60 @@ +# Quick start + To use this example you will need to install an MQTT server on your network. To install the Mosquitto MQTT client on a Raspberry Pi Linux device run the following... +``` sudo apt install mosquitto sudo apt install mosquitto-clients +``` Check it works... +``` mosquitto_pub -t test_topic -m "Yes it works" -r mosquitto_sub -t test_topic -C 1 +``` To allow an external client to connect to the server you will have to change the configuration. Add the following to /etc/mosquitto/conf.d/mosquitto.conf +``` allow_anonymous true listener 1883 0.0.0.0 +``` Then restart the service. +``` sudo service mosquitto restart +``` When building the code set the host name of the MQTT server, e.g. +``` export MQTT_SERVER=myhost cmake .. +``` The example should publish its core temperature to the /temperature topic. You can subscribe to this topic from another machine. +``` mosquitto_sub -h $MQTT_SERVER -t '/temperature' +``` You can turn the led on and off by publishing messages. +``` mosquitto_pub -h $MQTT_SERVER -t '/led' -m on mosquitto_pub -h $MQTT_SERVER -t '/led' -m off +``` + +# Security + +If your server has a username and password, you can set these with the following variables. + +``` +export MQTT_USERNAME=user +export MQTT_PASSWORD=pass +``` + +Be aware that these details are sent in plain text unless you use TLS. diff --git a/pico_w/wifi/mqtt/mqtt_client/mqtt_client.c b/pico_w/wifi/mqtt/mqtt_client/mqtt_client.c index c7ae9a926..43acd700c 100644 --- a/pico_w/wifi/mqtt/mqtt_client/mqtt_client.c +++ b/pico_w/wifi/mqtt/mqtt_client/mqtt_client.c @@ -169,6 +169,13 @@ int main(void) { pico_get_unique_board_id_string(client_id_buf, sizeof(client_id_buf)); state.mqtt_client_info.client_id = client_id_buf; state.mqtt_client_info.keep_alive = 60; // Keep alive in sec +#if defined(MQTT_USERNAME) && defined(MQTT_PASSWORD) + state.mqtt_client_info.client_user = MQTT_USERNAME; + state.mqtt_client_info.client_pass = MQTT_PASSWORD; +#else + state.mqtt_client_info.client_user = NULL; + state.mqtt_client_info.client_pass = NULL; +#endif if (cyw43_arch_init()) { panic("Failed to inizialize CYW43"); From d50294d2aca964450326bb8e2245bd07e3a7bf22 Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Wed, 29 Jan 2025 19:29:26 +0000 Subject: [PATCH 07/11] Add TLS support Move the code down one level --- pico_w/wifi/lwipopts_examples_common.h | 2 + pico_w/wifi/mqtt/CMakeLists.txt | 82 +++++++++++++++++-- pico_w/wifi/mqtt/{mqtt_client => }/README | 34 ++++++++ pico_w/wifi/mqtt/certs/.gitignore | 2 + pico_w/wifi/mqtt/certs/makecerts.sh | 43 ++++++++++ pico_w/wifi/mqtt/certs/pub.sh | 9 ++ pico_w/wifi/mqtt/certs/sub.sh | 9 ++ pico_w/wifi/mqtt/lwipopts.h | 30 +++++++ pico_w/wifi/mqtt/mbedtls_config.h | 6 ++ .../wifi/mqtt/{mqtt_client => }/mqtt_client.c | 48 +++++++++-- pico_w/wifi/mqtt/mqtt_client/CMakeLists.txt | 49 ----------- pico_w/wifi/mqtt/mqtt_client/lwipopts.h | 12 --- 12 files changed, 251 insertions(+), 75 deletions(-) rename pico_w/wifi/mqtt/{mqtt_client => }/README (51%) create mode 100644 pico_w/wifi/mqtt/certs/.gitignore create mode 100755 pico_w/wifi/mqtt/certs/makecerts.sh create mode 100755 pico_w/wifi/mqtt/certs/pub.sh create mode 100755 pico_w/wifi/mqtt/certs/sub.sh create mode 100644 pico_w/wifi/mqtt/lwipopts.h create mode 100644 pico_w/wifi/mqtt/mbedtls_config.h rename pico_w/wifi/mqtt/{mqtt_client => }/mqtt_client.c (81%) delete mode 100644 pico_w/wifi/mqtt/mqtt_client/CMakeLists.txt delete mode 100644 pico_w/wifi/mqtt/mqtt_client/lwipopts.h diff --git a/pico_w/wifi/lwipopts_examples_common.h b/pico_w/wifi/lwipopts_examples_common.h index 217cb1357..3cc083c64 100644 --- a/pico_w/wifi/lwipopts_examples_common.h +++ b/pico_w/wifi/lwipopts_examples_common.h @@ -20,7 +20,9 @@ #define MEM_LIBC_MALLOC 0 #endif #define MEM_ALIGNMENT 4 +#ifndef MEM_SIZE #define MEM_SIZE 4000 +#endif #define MEMP_NUM_TCP_SEG 32 #define MEMP_NUM_ARP_QUEUE 10 #define PBUF_POOL_SIZE 24 diff --git a/pico_w/wifi/mqtt/CMakeLists.txt b/pico_w/wifi/mqtt/CMakeLists.txt index 2f018792b..c371d5fe6 100644 --- a/pico_w/wifi/mqtt/CMakeLists.txt +++ b/pico_w/wifi/mqtt/CMakeLists.txt @@ -1,8 +1,78 @@ -cmake_minimum_required(VERSION 3.12) +# Define the host name of the MQTT server in an environment variable or pass it to cmake, +# e.g. cmake -DMQTT_SERVER=myserver .. -project(test_mqtt C CXX ASM) -set(CMAKE_C_STANDARD 11) -set(CMAKE_CXX_STANDARD 17) +if (DEFINED ENV{MQTT_SERVER} AND (NOT MQTT_SERVER)) + set(MQTT_SERVER $ENV{MQTT_SERVER}) + message("Using MQTT_SERVER from environment ('${MQTT_SERVER}')") +endif() +if (NOT MQTT_SERVER) + return() +endif() +# Define the name of an MQTT broker/server to enable this example +set(MQTT_SERVER "${MQTT_SERVER}" CACHE INTERNAL "MQTT server for examples") -add_subdirectory(mqtt_client) -pico_sdk_init() +if (DEFINED ENV{MQTT_USERNAME} AND (NOT MQTT_USERNAME)) + set(MQTT_USERNAME $ENV{MQTT_USERNAME}) + message("Using MQTT_USERNAME from environment ('${MQTT_USERNAME}')") +endif() +set(MQTT_USERNAME "${MQTT_USERNAME}" CACHE INTERNAL "MQTT user name for examples") +if (DEFINED ENV{MQTT_PASSWORD} AND (NOT MQTT_PASSWORD)) + set(MQTT_PASSWORD $ENV{MQTT_PASSWORD}) + message("Using MQTT_PASSWORD from environment") +endif() +set(MQTT_PASSWORD "${MQTT_PASSWORD}" CACHE INTERNAL "MQTT password for examples") + +# Set path to the certificate include file +if (NOT MQTT_CERT_PATH) + set(MQTT_CERT_PATH ${CMAKE_CURRENT_LIST_DIR}/certs/${MQTT_SERVER}) +endif() + +# Set the name of the certificate include file +if (NOT MQTT_CERT_INC) + set(MQTT_CERT_INC mqtt_client.inc) +endif() + +set(TARGET_NAME mqtt_client) +add_executable(${TARGET_NAME} + mqtt_client.c + ) +target_link_libraries(${TARGET_NAME} + pico_stdlib + hardware_adc + pico_cyw43_arch_lwip_threadsafe_background + pico_lwip_mqtt + pico_mbedtls + pico_lwip_mbedtls + ) +target_include_directories(${TARGET_NAME} PRIVATE + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required + ) +target_compile_definitions(${TARGET_NAME} PRIVATE + WIFI_SSID=\"${WIFI_SSID}\" + WIFI_PASSWORD=\"${WIFI_PASSWORD}\" + MQTT_SERVER=\"${MQTT_SERVER}\" + ) +if (EXISTS "${MQTT_CERT_PATH}/${MQTT_CERT_INC}") + target_compile_definitions(${TARGET_NAME} PRIVATE + MQTT_CERT_INC=\"${MQTT_CERT_INC}\" # contains the tls certificates for MQTT_SERVER needed by the client + ALTCP_MBEDTLS_AUTHMODE=MBEDTLS_SSL_VERIFY_REQUIRED + ) + target_include_directories(${TARGET_NAME} PRIVATE + ${MQTT_CERT_PATH} + ) +endif() +if (MQTT_USERNAME AND MQTT_PASSWORD) + target_compile_definitions(${TARGET_NAME} PRIVATE + MQTT_USERNAME=\"${MQTT_USERNAME}\" + MQTT_PASSWORD=\"${MQTT_PASSWORD}\" + ) +endif() +pico_add_extra_outputs(${TARGET_NAME}) + +# Ignore warnings from lwip code +set_source_files_properties( + ${PICO_LWIP_PATH}/src/apps/altcp_tls/altcp_tls_mbedtls.c + PROPERTIES + COMPILE_OPTIONS "-Wno-unused-result" + ) \ No newline at end of file diff --git a/pico_w/wifi/mqtt/mqtt_client/README b/pico_w/wifi/mqtt/README similarity index 51% rename from pico_w/wifi/mqtt/mqtt_client/README rename to pico_w/wifi/mqtt/README index b4c124f2f..e1889bb61 100644 --- a/pico_w/wifi/mqtt/mqtt_client/README +++ b/pico_w/wifi/mqtt/README @@ -58,3 +58,37 @@ export MQTT_PASSWORD=pass ``` Be aware that these details are sent in plain text unless you use TLS. + +## Using TLS + +To use TLS and client server authentication you need some keys and certificates for the client and server. +The `certs/makecerts.sh` script demonstrates a way to make these. +It creates a folder named after `MQTT_SERVER` containing all the required files. +From these files it generates a header file `mqtt_client.inc` included by the code. +Your server will have to be configured to use TLS and the port 8883 rather than the non-TLS port 1883. + +``` +listener 1883 127.0.0.1 + +listener 8883 0.0.0.0 +allow_anonymous true +cafile /etc/mosquitto/ca_certificates/ca.crt +certfile /etc/mosquitto/certs/server.crt +keyfile /etc/mosquitto/certs/server.key +require_certificate true +``` + +To connect to your server with the mosquitto tools in linux you will have to pass extra parameters. + +``` +mosquitto_pub -h $MQTT_SERVER --cafile $MQTT_SERVER/ca.crt --key $MQTT_SERVER/client.key --cert $MQTT_SERVER/client.crt -t /led -m on +mosquitto_sub -h $MQTT_SERVER --cafile $MQTT_SERVER/ca.crt --key $MQTT_SERVER/client.key --cert $MQTT_SERVER/client.crt -t "/temperature" +``` + +There are some shell scripts in the certs folder to reduce the amount of typing assuming `MQTT_SERVER` is defined. + +``` +cd certs +./pub.sh /led on +./sub.sh /temperature +``` diff --git a/pico_w/wifi/mqtt/certs/.gitignore b/pico_w/wifi/mqtt/certs/.gitignore new file mode 100644 index 000000000..d92150c7c --- /dev/null +++ b/pico_w/wifi/mqtt/certs/.gitignore @@ -0,0 +1,2 @@ +*/ + diff --git a/pico_w/wifi/mqtt/certs/makecerts.sh b/pico_w/wifi/mqtt/certs/makecerts.sh new file mode 100755 index 000000000..c95b9c8f8 --- /dev/null +++ b/pico_w/wifi/mqtt/certs/makecerts.sh @@ -0,0 +1,43 @@ +#!/usr/bin/bash + +if [ "${PWD##*/}" != "certs" ]; then + echo Run this in the certs folder + exit 1 +fi +if [ -z "$MQTT_SERVER" ]; then + echo Define MQTT_SERVER + exit 1 +fi +SERVER_NAME=$MQTT_SERVER + +if [ -d "$SERVER_NAME" ]; then + echo Run \"rm -fr $SERVER_NAME\" to regenerate these keys + exit 1 +fi +mkdir $SERVER_NAME +echo Generating keys in $PWD/$SERVER_NAME + +openssl genrsa -out $SERVER_NAME/ca.key 2048 +openssl req -new -x509 -days 99999 -key $SERVER_NAME/ca.key -out $SERVER_NAME/ca.crt -subj "/C=UK/ST=Cambridgeshire/L=Cambridge/O=Raspberry Pi Ltd/OU=Software/CN=rpiroot" + +openssl genrsa -out $SERVER_NAME/server.key 2048 +openssl req -new -out $SERVER_NAME/server.csr -key $SERVER_NAME/server.key -subj "/C=UK/ST=Cambridgeshire/L=Cambridge/O=Raspberry Pi Ltd/OU=Software/CN=$SERVER_NAME" +openssl x509 -req -in $SERVER_NAME/server.csr -CA $SERVER_NAME/ca.crt -CAkey $SERVER_NAME/ca.key -CAcreateserial -out $SERVER_NAME/server.crt -days 9999 + +openssl genrsa -out $SERVER_NAME/client.key 2048 +openssl req -new -out $SERVER_NAME/client.csr -key $SERVER_NAME/client.key -subj "/C=UK/ST=Cambridgeshire/L=Cambridge/O=Raspberry Pi Ltd/OU=Software/CN=$SERVER_NAME" +openssl x509 -req -in $SERVER_NAME/client.csr -CA $SERVER_NAME/ca.crt -CAkey $SERVER_NAME/ca.key -CAcreateserial -out $SERVER_NAME/client.crt -days 999 + +echo -n \#define TLS_ROOT_CERT \" > $SERVER_NAME/mqtt_client.inc +cat $SERVER_NAME/ca.crt | awk '{printf "%s\\n\\\n", $0}' >> $SERVER_NAME/mqtt_client.inc +echo "\"" >> $SERVER_NAME/mqtt_client.inc +echo >> $SERVER_NAME/mqtt_client.inc + +echo -n \#define TLS_CLIENT_KEY \" >> $SERVER_NAME/mqtt_client.inc +cat $SERVER_NAME/client.key | awk '{printf "%s\\n\\\n", $0}' >> $SERVER_NAME/mqtt_client.inc +echo "\"" >> $SERVER_NAME/mqtt_client.inc +echo >> $SERVER_NAME/mqtt_client.inc + +echo -n \#define TLS_CLIENT_CERT \" >> $SERVER_NAME/mqtt_client.inc +cat $SERVER_NAME/client.crt | awk '{printf "%s\\n\\\n", $0}' >> $SERVER_NAME/mqtt_client.inc +echo "\"" >> $SERVER_NAME/mqtt_client.inc diff --git a/pico_w/wifi/mqtt/certs/pub.sh b/pico_w/wifi/mqtt/certs/pub.sh new file mode 100755 index 000000000..38b9b7d37 --- /dev/null +++ b/pico_w/wifi/mqtt/certs/pub.sh @@ -0,0 +1,9 @@ +#/usr/bin/bash + +if [ -z "$MQTT_SERVER" ]; then + echo Define MQTT_SERVER + exit 1 +fi + +mosquitto_pub -h $MQTT_SERVER --cafile $MQTT_SERVER/ca.crt --key $MQTT_SERVER/client.key --cert $MQTT_SERVER/client.crt -t "$1" -m "$2" + diff --git a/pico_w/wifi/mqtt/certs/sub.sh b/pico_w/wifi/mqtt/certs/sub.sh new file mode 100755 index 000000000..54b9376ad --- /dev/null +++ b/pico_w/wifi/mqtt/certs/sub.sh @@ -0,0 +1,9 @@ +#/usr/bin/bash + +if [ -z "$MQTT_SERVER" ]; then + echo Define MQTT_SERVER + exit 1 +fi + +mosquitto_sub -h $MQTT_SERVER --cafile $MQTT_SERVER/ca.crt --key $MQTT_SERVER/client.key --cert $MQTT_SERVER/client.crt -t "$1" + diff --git a/pico_w/wifi/mqtt/lwipopts.h b/pico_w/wifi/mqtt/lwipopts.h new file mode 100644 index 000000000..09c11cb8b --- /dev/null +++ b/pico_w/wifi/mqtt/lwipopts.h @@ -0,0 +1,30 @@ +#ifndef _LWIPOPTS_H +#define _LWIPOPTS_H + +// Need more memory for TLS +#ifdef MQTT_CERT_INC +#define MEM_SIZE 8000 +#endif + +// Generally you would define your own explicit list of lwIP options +// (see https://www.nongnu.org/lwip/2_1_x/group__lwip__opts.html) +// +// This example uses a common include to avoid repetition +#include "lwipopts_examples_common.h" + +#define MEMP_NUM_SYS_TIMEOUT (LWIP_NUM_SYS_TIMEOUT_INTERNAL+1) + +#ifdef MQTT_CERT_INC +#define LWIP_ALTCP 1 +#define LWIP_ALTCP_TLS 1 +#define LWIP_ALTCP_TLS_MBEDTLS 1 +#ifndef NDEBUG +#define ALTCP_MBEDTLS_DEBUG LWIP_DBG_ON +#endif +/* TCP WND must be at least 16 kb to match TLS record size + or you will get a warning "altcp_tls: TCP_WND is smaller than the RX decrypion buffer, connection RX might stall!" */ +#undef TCP_WND +#define TCP_WND 16384 +#endif // MQTT_CERT_INC + +#endif diff --git a/pico_w/wifi/mqtt/mbedtls_config.h b/pico_w/wifi/mqtt/mbedtls_config.h new file mode 100644 index 000000000..f182c5661 --- /dev/null +++ b/pico_w/wifi/mqtt/mbedtls_config.h @@ -0,0 +1,6 @@ +#ifndef MBEDTLS_CONFIG_TLS_CLIENT_H +#define MBEDTLS_CONFIG_TLS_CLIENT_H + +#include "mbedtls_config_examples_common.h" + +#endif \ No newline at end of file diff --git a/pico_w/wifi/mqtt/mqtt_client/mqtt_client.c b/pico_w/wifi/mqtt/mqtt_client.c similarity index 81% rename from pico_w/wifi/mqtt/mqtt_client/mqtt_client.c rename to pico_w/wifi/mqtt/mqtt_client.c index 43acd700c..e7b437dc2 100644 --- a/pico_w/wifi/mqtt/mqtt_client/mqtt_client.c +++ b/pico_w/wifi/mqtt/mqtt_client.c @@ -14,7 +14,9 @@ #include "hardware/irq.h" #include "hardware/adc.h" #include "lwip/apps/mqtt.h" +#include "lwip/apps/mqtt_priv.h" // needed to set hostname #include "lwip/dns.h" +#include "lwip/altcp_tls.h" // Temperature #ifndef TEMPERATURE_UNITS @@ -25,9 +27,9 @@ #error Need to define MQTT_SERVER #endif -// MQTT parameters -#ifndef MQTT_PORT -#define MQTT_PORT 1883 +// This file includes your client certificate for client server authentication +#ifdef MQTT_CERT_INC +#include MQTT_CERT_INC #endif typedef struct { @@ -129,6 +131,14 @@ static void mqtt_connection_cb(mqtt_client_t *client, void *arg, mqtt_connection } static void start_client(MQTT_CLIENT_DATA_T *state) { +#if LWIP_ALTCP && LWIP_ALTCP_TLS + const int port = MQTT_TLS_PORT; + printf("Using TLS\n"); +#else + const int port = MQTT_PORT; + printf("Warning: Not using TLS\n"); +#endif + state->mqtt_client_inst = mqtt_client_new(); if (!state->mqtt_client_inst) { panic("MQTT client instance creation error"); @@ -137,9 +147,13 @@ static void start_client(MQTT_CLIENT_DATA_T *state) { printf("Connecting to mqtt server at %s\n", ipaddr_ntoa(&state->mqtt_server_address)); cyw43_arch_lwip_begin(); - if (mqtt_client_connect(state->mqtt_client_inst, &state->mqtt_server_address, MQTT_PORT, mqtt_connection_cb, state, &state->mqtt_client_info) != ERR_OK) { + if (mqtt_client_connect(state->mqtt_client_inst, &state->mqtt_server_address, port, mqtt_connection_cb, state, &state->mqtt_client_info) != ERR_OK) { panic("MQTT broker connection error"); } +#if LWIP_ALTCP && LWIP_ALTCP_TLS + // This is important for MBEDTLS_SSL_SERVER_NAME_INDICATION + mbedtls_ssl_set_hostname(altcp_tls_context(state->mqtt_client_inst->conn), MQTT_SERVER); +#endif mqtt_set_inpub_callback(state->mqtt_client_inst, mqtt_incoming_publish_cb, mqtt_incoming_data_cb, state); cyw43_arch_lwip_end(); } @@ -157,6 +171,7 @@ static void dns_found(const char *hostname, const ip_addr_t *ipaddr, void *arg) int main(void) { stdio_init_all(); + printf("mqtt client starting\n"); adc_init(); adc_set_temp_sensor_enabled(true); @@ -164,6 +179,10 @@ int main(void) { static MQTT_CLIENT_DATA_T state; + if (cyw43_arch_init()) { + panic("Failed to inizialize CYW43"); + } + // Use board unique id for the client id static char client_id_buf[PICO_UNIQUE_BOARD_ID_SIZE_BYTES * 2 + 1]; pico_get_unique_board_id_string(client_id_buf, sizeof(client_id_buf)); @@ -176,10 +195,23 @@ int main(void) { state.mqtt_client_info.client_user = NULL; state.mqtt_client_info.client_pass = NULL; #endif - - if (cyw43_arch_init()) { - panic("Failed to inizialize CYW43"); - } +#if LWIP_ALTCP && LWIP_ALTCP_TLS + // TLS enabled +#ifdef MQTT_CERT_INC + static const uint8_t ca_cert[] = TLS_ROOT_CERT; + static const uint8_t client_key[] = TLS_CLIENT_KEY; + static const uint8_t client_cert[] = TLS_CLIENT_CERT; + // This confirms the indentity of the server and the client + state.mqtt_client_info.tls_config = altcp_tls_create_config_client_2wayauth(ca_cert, sizeof(ca_cert), + client_key, sizeof(client_key), NULL, 0, client_cert, sizeof(client_cert)); +#if ALTCP_MBEDTLS_AUTHMODE != MBEDTLS_SSL_VERIFY_REQUIRED + WARN_printf("Warning: tls without verification is insecure\n"); +#endif +#else + state->client_info.tls_config = altcp_tls_create_config_client(NULL, 0); + WARN_printf("Warning: tls without a certificate is insecure\n"); +#endif +#endif cyw43_arch_enable_sta_mode(); if (cyw43_arch_wifi_connect_timeout_ms(WIFI_SSID, WIFI_PASSWORD, CYW43_AUTH_WPA2_AES_PSK, 30000)) { diff --git a/pico_w/wifi/mqtt/mqtt_client/CMakeLists.txt b/pico_w/wifi/mqtt/mqtt_client/CMakeLists.txt deleted file mode 100644 index c2ea20927..000000000 --- a/pico_w/wifi/mqtt/mqtt_client/CMakeLists.txt +++ /dev/null @@ -1,49 +0,0 @@ -# Define the host name of the MQTT server in an environment variable or pass it to cmake, -# e.g. cmake -DMQTT_SERVER=myserver .. - -if (DEFINED ENV{MQTT_SERVER} AND (NOT MQTT_SERVER)) - set(MQTT_SERVER $ENV{MQTT_SERVER}) - message("Using MQTT_SERVER from environment ('${MQTT_SERVER}')") -endif() -if (NOT MQTT_SERVER) - return() -endif() -if (DEFINED ENV{MQTT_USERNAME} AND (NOT MQTT_USERNAME)) - set(MQTT_USERNAME $ENV{MQTT_USERNAME}) - message("Using MQTT_USERNAME from environment ('${MQTT_USERNAME}')") -endif() -if (DEFINED ENV{MQTT_PASSWORD} AND (NOT MQTT_PASSWORD)) - set(MQTT_PASSWORD $ENV{MQTT_PASSWORD}) - message("Using MQTT_PASSWORD from environment") -endif() - -# Define the name of an MQTT broker/server to enable this example -set(MQTT_SERVER "${MQTT_SERVER}" CACHE INTERNAL "MQTT server for examples") - -set(TARGET_NAME mqtt_client) - -add_executable(${TARGET_NAME} - mqtt_client.c -) -target_link_libraries(${TARGET_NAME} - pico_stdlib - hardware_adc - pico_cyw43_arch_lwip_threadsafe_background - pico_lwip_mqtt -) -target_include_directories(${TARGET_NAME} PRIVATE - ${CMAKE_CURRENT_LIST_DIR} - ${CMAKE_CURRENT_LIST_DIR}/../.. # for our common lwipopts or any other standard includes, if required -) -target_compile_definitions(${TARGET_NAME} PRIVATE - WIFI_SSID=\"${WIFI_SSID}\" - WIFI_PASSWORD=\"${WIFI_PASSWORD}\" - MQTT_SERVER=\"${MQTT_SERVER}\" -) -if (MQTT_USERNAME AND MQTT_PASSWORD) - target_compile_definitions(${TARGET_NAME} PRIVATE - MQTT_USERNAME=\"${MQTT_USERNAME}\" - MQTT_PASSWORD=\"${MQTT_PASSWORD}\" - ) -endif() -pico_add_extra_outputs(${TARGET_NAME}) diff --git a/pico_w/wifi/mqtt/mqtt_client/lwipopts.h b/pico_w/wifi/mqtt/mqtt_client/lwipopts.h deleted file mode 100644 index e4817116f..000000000 --- a/pico_w/wifi/mqtt/mqtt_client/lwipopts.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef _LWIPOPTS_H -#define _LWIPOPTS_H - -// Generally you would define your own explicit list of lwIP options -// (see https://www.nongnu.org/lwip/2_1_x/group__lwip__opts.html) -// -// This example uses a common include to avoid repetition -#include "lwipopts_examples_common.h" - -#define MEMP_NUM_SYS_TIMEOUT (LWIP_NUM_SYS_TIMEOUT_INTERNAL+1) - -#endif From 53f52b247e9f512556fd6c9d6d0f49b4d28695d6 Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Thu, 30 Jan 2025 12:03:52 +0000 Subject: [PATCH 08/11] Add some more topics print, just print the message to stdout ping, publish uptime to pong exit, quit the client --- pico_w/wifi/mqtt/mqtt_client.c | 92 ++++++++++++++++++++++++++++------ 1 file changed, 76 insertions(+), 16 deletions(-) diff --git a/pico_w/wifi/mqtt/mqtt_client.c b/pico_w/wifi/mqtt/mqtt_client.c index e7b437dc2..230126e69 100644 --- a/pico_w/wifi/mqtt/mqtt_client.c +++ b/pico_w/wifi/mqtt/mqtt_client.c @@ -39,8 +39,23 @@ typedef struct { char topic[100]; uint32_t len; ip_addr_t mqtt_server_address; + bool connect_done; + int subscribe_count; + bool stop_client; } MQTT_CLIENT_DATA_T; +#ifndef DEBUG_printf +#ifndef NDEBUG +#define DEBUG_printf printf +#else +#define DEBUG_printf(...) +#endif +#endif + +#ifndef INFO_printf +#define INFO_printf printf +#endif + /* References for this implementation: * raspberry-pi-pico-c-sdk.pdf, Section '4.1.1. hardware_adc' * pico-examples/adc/adc_console/adc_console.c */ @@ -83,31 +98,69 @@ static void publish_temperature(MQTT_CLIENT_DATA_T *state) { // Publish temperature on /temperature topic char temp_str[16]; snprintf(temp_str, sizeof(temp_str), "%.2f", temperature); - printf("Publishing %s to %s\n", temp_str, temperature_key); + INFO_printf("Publishing %s to %s\n", temp_str, temperature_key); mqtt_publish(state->mqtt_client_inst, temperature_key, temp_str, strlen(temp_str), 0, 0, NULL, NULL); } } +static void sub_request_cb(void *arg, err_t err) { + MQTT_CLIENT_DATA_T* state = (MQTT_CLIENT_DATA_T*)arg; + if (err != 0) { + panic("subscribe request failed %d", err); + } + state->subscribe_count++; +} + +static void unsub_request_cb(void *arg, err_t err) { + MQTT_CLIENT_DATA_T* state = (MQTT_CLIENT_DATA_T*)arg; + if (err != 0) { + panic("unsubscribe request failed %d", err); + } + state->subscribe_count--; + assert(state->subscribe_count >= 0); + + // Stop if requested + if (state->subscribe_count <= 0 && state->stop_client) { + mqtt_disconnect(state->mqtt_client_inst); + } +} + +static void sub_unsub_topics(MQTT_CLIENT_DATA_T* state, bool sub) { + mqtt_request_cb_t cb = sub ? sub_request_cb : unsub_request_cb; + mqtt_sub_unsub(state->mqtt_client_inst, "/led", 0, cb, state, sub); + mqtt_sub_unsub(state->mqtt_client_inst, "/print", 0, cb, state, sub); + mqtt_sub_unsub(state->mqtt_client_inst, "/ping", 0, cb, state, sub); + mqtt_sub_unsub(state->mqtt_client_inst, "/exit", 0, cb, state, sub); +} + static void mqtt_incoming_data_cb(void *arg, const u8_t *data, u16_t len, u8_t flags) { MQTT_CLIENT_DATA_T* state = (MQTT_CLIENT_DATA_T*)arg; strncpy(state->data, (const char *)data, len); state->len = len; state->data[len] = '\0'; - printf("Topic: %s, Message: %s\n", state->topic, state->data); - + DEBUG_printf("Topic: %s, Message: %s\n", state->topic, state->data); if (strcmp(state->topic, "/led") == 0) { if (lwip_stricmp((const char *)state->data, "On") == 0) control_led(state, true); else if (lwip_stricmp((const char *)state->data, "Off") == 0) control_led(state, false); + } else if (strcmp(state->topic, "/print") == 0) { + INFO_printf("%.*s\n", len, data); + } else if (strcmp(state->topic, "/ping") == 0) { + char buf[11]; + snprintf(buf, sizeof(buf), "%u", to_ms_since_boot(get_absolute_time()) / 1000); + mqtt_publish(state->mqtt_client_inst, "/pong", buf, strlen(buf), 0, 0, NULL, NULL); + } else if (strcmp(state->topic, "/exit") == 0) { + state->stop_client = true; // stop the client when ALL subscriptions are stopped + sub_unsub_topics(state, false); // unsubscribe } } static void mqtt_incoming_publish_cb(void *arg, const char *topic, u32_t tot_len) { - MQTT_CLIENT_DATA_T* mqtt_client = (MQTT_CLIENT_DATA_T*)arg; - strncpy(mqtt_client->topic, topic, sizeof(mqtt_client->topic)); + MQTT_CLIENT_DATA_T* state = (MQTT_CLIENT_DATA_T*)arg; + strncpy(state->topic, topic, sizeof(state->topic)); } static void temperature_worker_fn(async_context_t *context, async_at_time_worker_t *worker) { @@ -118,14 +171,20 @@ static void temperature_worker_fn(async_context_t *context, async_at_time_worker static async_at_time_worker_t temperature_worker = { .do_work = temperature_worker_fn }; static void mqtt_connection_cb(mqtt_client_t *client, void *arg, mqtt_connection_status_t status) { + MQTT_CLIENT_DATA_T* state = (MQTT_CLIENT_DATA_T*)arg; if (status == MQTT_CONNECT_ACCEPTED) { - mqtt_sub_unsub(client, "/led", 0, NULL, arg, 1); - printf("Connected to the /led topic successfully\n"); + state->connect_done = true; + sub_unsub_topics(state, true); // subscribe; // Publish temperature every 10 sec if it's changed - temperature_worker.user_data = arg; + temperature_worker.user_data = state; async_context_add_at_time_worker_in_ms(cyw43_arch_async_context(), &temperature_worker, 0); - } else { + } else if (status == MQTT_CONNECT_DISCONNECTED) { + if (!state->connect_done) { + panic("Failed to connect to mqtt server"); + } + } + else { panic("Unexpected status"); } } @@ -133,18 +192,18 @@ static void mqtt_connection_cb(mqtt_client_t *client, void *arg, mqtt_connection static void start_client(MQTT_CLIENT_DATA_T *state) { #if LWIP_ALTCP && LWIP_ALTCP_TLS const int port = MQTT_TLS_PORT; - printf("Using TLS\n"); + INFO_printf("Using TLS\n"); #else const int port = MQTT_PORT; - printf("Warning: Not using TLS\n"); + INFO_printf("Warning: Not using TLS\n"); #endif state->mqtt_client_inst = mqtt_client_new(); if (!state->mqtt_client_inst) { panic("MQTT client instance creation error"); } - printf("IP address of this device %s\n", ipaddr_ntoa(&(netif_list->ip_addr))); - printf("Connecting to mqtt server at %s\n", ipaddr_ntoa(&state->mqtt_server_address)); + INFO_printf("IP address of this device %s\n", ipaddr_ntoa(&(netif_list->ip_addr))); + INFO_printf("Connecting to mqtt server at %s\n", ipaddr_ntoa(&state->mqtt_server_address)); cyw43_arch_lwip_begin(); if (mqtt_client_connect(state->mqtt_client_inst, &state->mqtt_server_address, port, mqtt_connection_cb, state, &state->mqtt_client_info) != ERR_OK) { @@ -171,7 +230,7 @@ static void dns_found(const char *hostname, const ip_addr_t *ipaddr, void *arg) int main(void) { stdio_init_all(); - printf("mqtt client starting\n"); + INFO_printf("mqtt client starting\n"); adc_init(); adc_set_temp_sensor_enabled(true); @@ -217,7 +276,7 @@ int main(void) { if (cyw43_arch_wifi_connect_timeout_ms(WIFI_SSID, WIFI_PASSWORD, CYW43_AUTH_WPA2_AES_PSK, 30000)) { panic("Failed to connect"); } - printf("\nConnected to Wifi\n"); + INFO_printf("\nConnected to Wifi\n"); // We are not in a callback so locking is needed when calling lwip // Make a DNS request for the MQTT server IP address @@ -232,10 +291,11 @@ int main(void) { panic("dns request failed"); } - while (true) { + while (!state.connect_done || mqtt_client_is_connected(state.mqtt_client_inst)) { cyw43_arch_poll(); cyw43_arch_wait_for_work_until(make_timeout_time_ms(10000)); } + INFO_printf("mqtt client exiting\n"); return 0; } From 103a49601ddf5c9ec93f744866405e892295ae6e Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Thu, 30 Jan 2025 13:13:56 +0000 Subject: [PATCH 09/11] Add will topic /online Add some defines for some magic numbers --- pico_w/wifi/mqtt/lwipopts.h | 3 ++ pico_w/wifi/mqtt/mqtt_client.c | 56 ++++++++++++++++++++++++++++------ 2 files changed, 50 insertions(+), 9 deletions(-) diff --git a/pico_w/wifi/mqtt/lwipopts.h b/pico_w/wifi/mqtt/lwipopts.h index 09c11cb8b..c309d5cb3 100644 --- a/pico_w/wifi/mqtt/lwipopts.h +++ b/pico_w/wifi/mqtt/lwipopts.h @@ -27,4 +27,7 @@ #define TCP_WND 16384 #endif // MQTT_CERT_INC +// This defaults to 4 +#define MQTT_REQ_MAX_IN_FLIGHT 5 + #endif diff --git a/pico_w/wifi/mqtt/mqtt_client.c b/pico_w/wifi/mqtt/mqtt_client.c index 230126e69..ddd673358 100644 --- a/pico_w/wifi/mqtt/mqtt_client.c +++ b/pico_w/wifi/mqtt/mqtt_client.c @@ -56,6 +56,29 @@ typedef struct { #define INFO_printf printf #endif +#ifndef ERROR_printf +#define ERROR_printf printf +#endif + +// how often to measure our temperature +#define TEMP_WORKER_TIME_S 10 + +// keep alive in seconds +#define MQTT_KEEP_ALIVE_S 60 + +// qos passed to mqtt_subscribe +// At most once (QoS 0) +// At least once (QoS 1) +// Exactly once (QoS 2) +#define MQTT_SUBSCRIBE_QOS 1 +#define MQTT_PUBLISH_QOS 1 +#define MQTT_PUBLISH_RETAIN 0 + +// topic used for last will and testament +#define MQTT_WILL_TOPIC "/online" +#define MQTT_WILL_MSG "0" +#define MQTT_WILL_QOS 1 + /* References for this implementation: * raspberry-pi-pico-c-sdk.pdf, Section '4.1.1. hardware_adc' * pico-examples/adc/adc_console/adc_console.c */ @@ -76,6 +99,12 @@ static float read_onboard_temperature(const char unit) { return -1.0f; } +static void pub_request_cb(__unused void *arg, err_t err) { + if (err != 0) { + ERROR_printf("pub_request_cb failed %d", err); + } +} + static void control_led(MQTT_CLIENT_DATA_T *state, bool on) { // Publish state on /state topic and on/off led board const char* message = on ? "On" : "Off"; @@ -86,7 +115,7 @@ static void control_led(MQTT_CLIENT_DATA_T *state, bool on) { char state_topic[128]; snprintf(state_topic, sizeof(state_topic), "%s/state", state->topic); - mqtt_publish(state->mqtt_client_inst, state_topic, message, strlen(message), 0, 0, NULL, NULL); + mqtt_publish(state->mqtt_client_inst, state_topic, message, strlen(message), MQTT_PUBLISH_QOS, MQTT_PUBLISH_RETAIN, pub_request_cb, state); } static void publish_temperature(MQTT_CLIENT_DATA_T *state) { @@ -99,7 +128,7 @@ static void publish_temperature(MQTT_CLIENT_DATA_T *state) { char temp_str[16]; snprintf(temp_str, sizeof(temp_str), "%.2f", temperature); INFO_printf("Publishing %s to %s\n", temp_str, temperature_key); - mqtt_publish(state->mqtt_client_inst, temperature_key, temp_str, strlen(temp_str), 0, 0, NULL, NULL); + mqtt_publish(state->mqtt_client_inst, temperature_key, temp_str, strlen(temp_str), MQTT_PUBLISH_QOS, MQTT_PUBLISH_RETAIN, pub_request_cb, state); } } @@ -127,10 +156,10 @@ static void unsub_request_cb(void *arg, err_t err) { static void sub_unsub_topics(MQTT_CLIENT_DATA_T* state, bool sub) { mqtt_request_cb_t cb = sub ? sub_request_cb : unsub_request_cb; - mqtt_sub_unsub(state->mqtt_client_inst, "/led", 0, cb, state, sub); - mqtt_sub_unsub(state->mqtt_client_inst, "/print", 0, cb, state, sub); - mqtt_sub_unsub(state->mqtt_client_inst, "/ping", 0, cb, state, sub); - mqtt_sub_unsub(state->mqtt_client_inst, "/exit", 0, cb, state, sub); + mqtt_sub_unsub(state->mqtt_client_inst, "/led", MQTT_SUBSCRIBE_QOS, cb, state, sub); + mqtt_sub_unsub(state->mqtt_client_inst, "/print", MQTT_SUBSCRIBE_QOS, cb, state, sub); + mqtt_sub_unsub(state->mqtt_client_inst, "/ping", MQTT_SUBSCRIBE_QOS, cb, state, sub); + mqtt_sub_unsub(state->mqtt_client_inst, "/exit", MQTT_SUBSCRIBE_QOS, cb, state, sub); } static void mqtt_incoming_data_cb(void *arg, const u8_t *data, u16_t len, u8_t flags) { @@ -151,7 +180,7 @@ static void mqtt_incoming_data_cb(void *arg, const u8_t *data, u16_t len, u8_t f } else if (strcmp(state->topic, "/ping") == 0) { char buf[11]; snprintf(buf, sizeof(buf), "%u", to_ms_since_boot(get_absolute_time()) / 1000); - mqtt_publish(state->mqtt_client_inst, "/pong", buf, strlen(buf), 0, 0, NULL, NULL); + mqtt_publish(state->mqtt_client_inst, "/pong", buf, strlen(buf), MQTT_PUBLISH_QOS, MQTT_PUBLISH_RETAIN, pub_request_cb, state); } else if (strcmp(state->topic, "/exit") == 0) { state->stop_client = true; // stop the client when ALL subscriptions are stopped sub_unsub_topics(state, false); // unsubscribe @@ -166,7 +195,7 @@ static void mqtt_incoming_publish_cb(void *arg, const char *topic, u32_t tot_len static void temperature_worker_fn(async_context_t *context, async_at_time_worker_t *worker) { MQTT_CLIENT_DATA_T* state = (MQTT_CLIENT_DATA_T*)worker->user_data; publish_temperature(state); - async_context_add_at_time_worker_in_ms(context, worker, 10000); // repeat in 10s + async_context_add_at_time_worker_in_ms(context, worker, TEMP_WORKER_TIME_S * 1000); } static async_at_time_worker_t temperature_worker = { .do_work = temperature_worker_fn }; @@ -176,6 +205,11 @@ static void mqtt_connection_cb(mqtt_client_t *client, void *arg, mqtt_connection state->connect_done = true; sub_unsub_topics(state, true); // subscribe; + // indicate online + if (state->mqtt_client_info.will_topic) { + mqtt_publish(state->mqtt_client_inst, state->mqtt_client_info.will_topic, "1", 1, MQTT_WILL_QOS, true, pub_request_cb, state); + } + // Publish temperature every 10 sec if it's changed temperature_worker.user_data = state; async_context_add_at_time_worker_in_ms(cyw43_arch_async_context(), &temperature_worker, 0); @@ -246,7 +280,7 @@ int main(void) { static char client_id_buf[PICO_UNIQUE_BOARD_ID_SIZE_BYTES * 2 + 1]; pico_get_unique_board_id_string(client_id_buf, sizeof(client_id_buf)); state.mqtt_client_info.client_id = client_id_buf; - state.mqtt_client_info.keep_alive = 60; // Keep alive in sec + state.mqtt_client_info.keep_alive = MQTT_KEEP_ALIVE_S; // Keep alive in sec #if defined(MQTT_USERNAME) && defined(MQTT_PASSWORD) state.mqtt_client_info.client_user = MQTT_USERNAME; state.mqtt_client_info.client_pass = MQTT_PASSWORD; @@ -254,6 +288,10 @@ int main(void) { state.mqtt_client_info.client_user = NULL; state.mqtt_client_info.client_pass = NULL; #endif + state.mqtt_client_info.will_topic = MQTT_WILL_TOPIC; + state.mqtt_client_info.will_msg = MQTT_WILL_MSG; + state.mqtt_client_info.will_qos = MQTT_WILL_QOS; + state.mqtt_client_info.will_retain = true; #if LWIP_ALTCP && LWIP_ALTCP_TLS // TLS enabled #ifdef MQTT_CERT_INC From 3593551361dfad53d5d96703858928d8308c3e1e Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Thu, 30 Jan 2025 14:19:17 +0000 Subject: [PATCH 10/11] Unique topic names per device Disabled by default with MQTT_UNIQUE_TOPIC --- pico_w/wifi/mqtt/mqtt_client.c | 79 +++++++++++++++++++++++++--------- 1 file changed, 59 insertions(+), 20 deletions(-) diff --git a/pico_w/wifi/mqtt/mqtt_client.c b/pico_w/wifi/mqtt/mqtt_client.c index ddd673358..fdcd238a0 100644 --- a/pico_w/wifi/mqtt/mqtt_client.c +++ b/pico_w/wifi/mqtt/mqtt_client.c @@ -32,11 +32,15 @@ #include MQTT_CERT_INC #endif +#ifndef MQTT_TOPIC_LEN +#define MQTT_TOPIC_LEN 100 +#endif + typedef struct { mqtt_client_t* mqtt_client_inst; struct mqtt_connect_client_info_t mqtt_client_info; char data[MQTT_OUTPUT_RINGBUF_SIZE]; - char topic[100]; + char topic[MQTT_TOPIC_LEN]; uint32_t len; ip_addr_t mqtt_server_address; bool connect_done; @@ -79,6 +83,15 @@ typedef struct { #define MQTT_WILL_MSG "0" #define MQTT_WILL_QOS 1 +#ifndef MQTT_DEVICE_NAME +#define MQTT_DEVICE_NAME "pico" +#endif + +// Set to 1 to add the client name to topics, to support multiple devices using the same server +#ifndef MQTT_UNIQUE_TOPIC +#define MQTT_UNIQUE_TOPIC 0 +#endif + /* References for this implementation: * raspberry-pi-pico-c-sdk.pdf, Section '4.1.1. hardware_adc' * pico-examples/adc/adc_console/adc_console.c */ @@ -105,6 +118,16 @@ static void pub_request_cb(__unused void *arg, err_t err) { } } +static const char *full_topic(MQTT_CLIENT_DATA_T *state, const char *name) { +#if MQTT_UNIQUE_TOPIC + static char full_topic[MQTT_TOPIC_LEN]; + snprintf(full_topic, sizeof(full_topic), "/%s%s", state->mqtt_client_info.client_id, name); + return full_topic; +#else + return name; +#endif +} + static void control_led(MQTT_CLIENT_DATA_T *state, bool on) { // Publish state on /state topic and on/off led board const char* message = on ? "On" : "Off"; @@ -113,14 +136,12 @@ static void control_led(MQTT_CLIENT_DATA_T *state, bool on) { else cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, 0); - char state_topic[128]; - snprintf(state_topic, sizeof(state_topic), "%s/state", state->topic); - mqtt_publish(state->mqtt_client_inst, state_topic, message, strlen(message), MQTT_PUBLISH_QOS, MQTT_PUBLISH_RETAIN, pub_request_cb, state); + mqtt_publish(state->mqtt_client_inst, full_topic(state, "/led/state"), message, strlen(message), MQTT_PUBLISH_QOS, MQTT_PUBLISH_RETAIN, pub_request_cb, state); } static void publish_temperature(MQTT_CLIENT_DATA_T *state) { static float old_temperature; - const char temperature_key[] = "/temperature"; + const char *temperature_key = full_topic(state, "/temperature"); float temperature = read_onboard_temperature(TEMPERATURE_UNITS); if (temperature != old_temperature) { old_temperature = temperature; @@ -156,32 +177,37 @@ static void unsub_request_cb(void *arg, err_t err) { static void sub_unsub_topics(MQTT_CLIENT_DATA_T* state, bool sub) { mqtt_request_cb_t cb = sub ? sub_request_cb : unsub_request_cb; - mqtt_sub_unsub(state->mqtt_client_inst, "/led", MQTT_SUBSCRIBE_QOS, cb, state, sub); - mqtt_sub_unsub(state->mqtt_client_inst, "/print", MQTT_SUBSCRIBE_QOS, cb, state, sub); - mqtt_sub_unsub(state->mqtt_client_inst, "/ping", MQTT_SUBSCRIBE_QOS, cb, state, sub); - mqtt_sub_unsub(state->mqtt_client_inst, "/exit", MQTT_SUBSCRIBE_QOS, cb, state, sub); + mqtt_sub_unsub(state->mqtt_client_inst, full_topic(state, "/led"), MQTT_SUBSCRIBE_QOS, cb, state, sub); + mqtt_sub_unsub(state->mqtt_client_inst, full_topic(state, "/print"), MQTT_SUBSCRIBE_QOS, cb, state, sub); + mqtt_sub_unsub(state->mqtt_client_inst, full_topic(state, "/ping"), MQTT_SUBSCRIBE_QOS, cb, state, sub); + mqtt_sub_unsub(state->mqtt_client_inst, full_topic(state, "/exit"), MQTT_SUBSCRIBE_QOS, cb, state, sub); } static void mqtt_incoming_data_cb(void *arg, const u8_t *data, u16_t len, u8_t flags) { MQTT_CLIENT_DATA_T* state = (MQTT_CLIENT_DATA_T*)arg; +#if MQTT_UNIQUE_TOPIC + const char *basic_topic = state->topic + strlen(state->mqtt_client_info.client_id) + 1; +#else + const char *basic_topic = state->topic; +#endif strncpy(state->data, (const char *)data, len); state->len = len; state->data[len] = '\0'; DEBUG_printf("Topic: %s, Message: %s\n", state->topic, state->data); - if (strcmp(state->topic, "/led") == 0) + if (strcmp(basic_topic, "/led") == 0) { - if (lwip_stricmp((const char *)state->data, "On") == 0) + if (lwip_stricmp((const char *)state->data, "On") == 0 || strcmp((const char *)state->data, "1") == 0) control_led(state, true); - else if (lwip_stricmp((const char *)state->data, "Off") == 0) + else if (lwip_stricmp((const char *)state->data, "Off") == 0 || strcmp((const char *)state->data, "0") == 0) control_led(state, false); - } else if (strcmp(state->topic, "/print") == 0) { + } else if (strcmp(basic_topic, "/print") == 0) { INFO_printf("%.*s\n", len, data); - } else if (strcmp(state->topic, "/ping") == 0) { + } else if (strcmp(basic_topic, "/ping") == 0) { char buf[11]; snprintf(buf, sizeof(buf), "%u", to_ms_since_boot(get_absolute_time()) / 1000); - mqtt_publish(state->mqtt_client_inst, "/pong", buf, strlen(buf), MQTT_PUBLISH_QOS, MQTT_PUBLISH_RETAIN, pub_request_cb, state); - } else if (strcmp(state->topic, "/exit") == 0) { + mqtt_publish(state->mqtt_client_inst, full_topic(state, "/pong"), buf, strlen(buf), MQTT_PUBLISH_QOS, MQTT_PUBLISH_RETAIN, pub_request_cb, state); + } else if (strcmp(basic_topic, "/exit") == 0) { state->stop_client = true; // stop the client when ALL subscriptions are stopped sub_unsub_topics(state, false); // unsubscribe } @@ -276,9 +302,20 @@ int main(void) { panic("Failed to inizialize CYW43"); } - // Use board unique id for the client id - static char client_id_buf[PICO_UNIQUE_BOARD_ID_SIZE_BYTES * 2 + 1]; - pico_get_unique_board_id_string(client_id_buf, sizeof(client_id_buf)); + // Use board unique id + char unique_id_buf[5]; + pico_get_unique_board_id_string(unique_id_buf, sizeof(unique_id_buf)); + for(int i=0; i < sizeof(unique_id_buf) - 1; i++) { + unique_id_buf[i] = tolower(unique_id_buf[i]); + } + + // Generate a unique name, e.g. pico1234 + char client_id_buf[sizeof(MQTT_DEVICE_NAME) + sizeof(unique_id_buf) - 1]; + memcpy(&client_id_buf[0], MQTT_DEVICE_NAME, sizeof(MQTT_DEVICE_NAME) - 1); + memcpy(&client_id_buf[sizeof(MQTT_DEVICE_NAME) - 1], unique_id_buf, sizeof(unique_id_buf) - 1); + client_id_buf[sizeof(client_id_buf) - 1] = 0; + INFO_printf("Device name %s\n", client_id_buf); + state.mqtt_client_info.client_id = client_id_buf; state.mqtt_client_info.keep_alive = MQTT_KEEP_ALIVE_S; // Keep alive in sec #if defined(MQTT_USERNAME) && defined(MQTT_PASSWORD) @@ -288,7 +325,9 @@ int main(void) { state.mqtt_client_info.client_user = NULL; state.mqtt_client_info.client_pass = NULL; #endif - state.mqtt_client_info.will_topic = MQTT_WILL_TOPIC; + static char will_topic[MQTT_TOPIC_LEN]; + strncpy(will_topic, full_topic(&state, MQTT_WILL_TOPIC), sizeof(will_topic)); + state.mqtt_client_info.will_topic = will_topic; state.mqtt_client_info.will_msg = MQTT_WILL_MSG; state.mqtt_client_info.will_qos = MQTT_WILL_QOS; state.mqtt_client_info.will_retain = true; From 39cf06ecd0d0871177a3a41a2795b22e75e6fbf7 Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Wed, 5 Feb 2025 13:15:59 +0000 Subject: [PATCH 11/11] Some MQTT updates Change /pong to /uptime Display a message if MQTT_SERVER is not defined Put picow_ in the name like the other examples Add details to the readme! --- README.md | 1 + pico_w/wifi/mqtt/CMakeLists.txt | 3 ++- pico_w/wifi/mqtt/mqtt_client.c | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ab13be68e..01a98ff3e 100644 --- a/README.md +++ b/README.md @@ -191,6 +191,7 @@ App|Description [picow_httpd](pico_w/wifi/httpd) | Runs a LWIP HTTP server test app [picow_http_client](pico_w/wifi/http_client) | Demonstrates how to make http and https requests [picow_http_client_verify](pico_w/wifi/http_client) | Demonstrates how to make a https request with server authentication +[picow_mqtt_client](pico_w/wifi/mqtt) | Demonstrates how to implement an MQTT client application #### FreeRTOS examples diff --git a/pico_w/wifi/mqtt/CMakeLists.txt b/pico_w/wifi/mqtt/CMakeLists.txt index c371d5fe6..23dfa250f 100644 --- a/pico_w/wifi/mqtt/CMakeLists.txt +++ b/pico_w/wifi/mqtt/CMakeLists.txt @@ -6,6 +6,7 @@ if (DEFINED ENV{MQTT_SERVER} AND (NOT MQTT_SERVER)) message("Using MQTT_SERVER from environment ('${MQTT_SERVER}')") endif() if (NOT MQTT_SERVER) + message("Skipping MQTT example as MQTT_SERVER is not defined") return() endif() # Define the name of an MQTT broker/server to enable this example @@ -32,7 +33,7 @@ if (NOT MQTT_CERT_INC) set(MQTT_CERT_INC mqtt_client.inc) endif() -set(TARGET_NAME mqtt_client) +set(TARGET_NAME picow_mqtt_client) add_executable(${TARGET_NAME} mqtt_client.c ) diff --git a/pico_w/wifi/mqtt/mqtt_client.c b/pico_w/wifi/mqtt/mqtt_client.c index fdcd238a0..b70d6885d 100644 --- a/pico_w/wifi/mqtt/mqtt_client.c +++ b/pico_w/wifi/mqtt/mqtt_client.c @@ -206,7 +206,7 @@ static void mqtt_incoming_data_cb(void *arg, const u8_t *data, u16_t len, u8_t f } else if (strcmp(basic_topic, "/ping") == 0) { char buf[11]; snprintf(buf, sizeof(buf), "%u", to_ms_since_boot(get_absolute_time()) / 1000); - mqtt_publish(state->mqtt_client_inst, full_topic(state, "/pong"), buf, strlen(buf), MQTT_PUBLISH_QOS, MQTT_PUBLISH_RETAIN, pub_request_cb, state); + mqtt_publish(state->mqtt_client_inst, full_topic(state, "/uptime"), buf, strlen(buf), MQTT_PUBLISH_QOS, MQTT_PUBLISH_RETAIN, pub_request_cb, state); } else if (strcmp(basic_topic, "/exit") == 0) { state->stop_client = true; // stop the client when ALL subscriptions are stopped sub_unsub_topics(state, false); // unsubscribe