├── .gitignore ├── crypto_consts_example.h ├── README.md ├── mbedtls_config.h ├── CMakeLists.txt ├── mqtt-sni.patch ├── pico_sdk_import.cmake ├── lwipopts.h └── picow_iot.c /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | .vscode 3 | crypto_consts.h 4 | -------------------------------------------------------------------------------- /crypto_consts_example.h: -------------------------------------------------------------------------------- 1 | #ifndef CRYPTO_CONSTS_H_ 2 | #define CRYPTO_CONSTS_H_ 3 | 4 | // To use, copy to crypto_consts.h and define either 5 | // CRYPTO_AWS_IOT or CRYPTO_MOSQUITTO_TEST and populate desired values. 6 | 7 | // AWS IoT Core 8 | #ifdef CRYPTO_AWS_IOT 9 | #define MQTT_SERVER_HOST "example.iot.us-east-1.amazonaws.com" 10 | #define MQTT_SERVER_PORT 8883 11 | #define CRYPTO_CERT \ 12 | "-----BEGIN CERTIFICATE-----\n" \ 13 | "-----END CERTIFICATE-----\n"; 14 | #define CRYPTO_KEY \ 15 | "-----BEGIN RSA PRIVATE KEY-----\n" \ 16 | "-----END RSA PRIVATE KEY-----"; 17 | #define CRYPTO_CA \ 18 | "-----BEGIN CERTIFICATE-----\n" \ 19 | "-----END CERTIFICATE-----"; 20 | #endif 21 | 22 | // Mosquitto test servers 23 | #ifdef CRYPTO_MOSQUITTO_TEST 24 | #if MQTT_TLS 25 | #define MQTT_SERVER_PORT 8883 26 | #else 27 | #define MQTT_SERVER_PORT 1883 28 | #endif 29 | #define MQTT_SERVER_HOST "test.mosquitto.org" 30 | #define CRYPTO_CERT \ 31 | "-----BEGIN CERTIFICATE-----\n" \ 32 | "-----END CERTIFICATE-----\n" 33 | #endif 34 | 35 | #endif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # picow-iot 2 | 3 | This is a small example project that demonstrates connecting to an MQTT broker with a Pico W. It uses lwIP's MQTT client and mbedtls to connect to a broker and transmit a message to a topic. 4 | 5 | ## Important note about SNI 6 | 7 | Some cloud providers (e.g. AWS, HiveMQ) require that SNI be enabled on the client. There does not appear to be a way to configure lwip's MQTT app client to set up SNI. This code uses a patched version of LWIP to support SNI. 8 | 9 | If you want to connect to a cloud provider that requires SNI you'll need to apply the patch to the copy of LWIP thats used by the Pico SDK. 10 | 11 | To do so, in your expansion of `pico-sdk`, run 12 | 13 | ```bash 14 | cd lib/lwip 15 | git apply path/to/mqtt-sni.patch 16 | ``` 17 | 18 | Then, re-build your application making sure it uses your patched changes. 19 | 20 | ## Setup 21 | 22 | ### pico-sdk 23 | 24 | You should just need to set up pico-sdk as you would for pico-examples. 25 | 26 | ### cmake 27 | 28 | Configure cmake with the following variables, the same as pico-examples. 29 | - PICO_SDK_PATH 30 | - PICO_BOARD 31 | - WIFI_SSID 32 | - WIFI_PASSWORD 33 | 34 | ### crypto_consts.h custom header 35 | 36 | The build relies on a simple header file (crypto_consts.h) to provide cryptographic keys and certificates as well. 37 | 38 | See crytpo_consts_example.h for a setup for AWS IoT and Mosquitto test servers. 39 | 40 | ## TODO 41 | - Support additional configurations that work with background/FreeRTOS 42 | -------------------------------------------------------------------------------- /mbedtls_config.h: -------------------------------------------------------------------------------- 1 | /* Workaround for some mbedtls source files using INT_MAX without including limits.h */ 2 | #include 3 | 4 | #define MBEDTLS_NO_PLATFORM_ENTROPY 5 | #define MBEDTLS_ENTROPY_HARDWARE_ALT 6 | 7 | #define MBEDTLS_SSL_OUT_CONTENT_LEN 2048 8 | 9 | #define MBEDTLS_ALLOW_PRIVATE_ACCESS 10 | #define MBEDTLS_HAVE_TIME 11 | 12 | #define MBEDTLS_CIPHER_MODE_CBC 13 | #define MBEDTLS_ECP_DP_SECP192R1_ENABLED 14 | #define MBEDTLS_ECP_DP_SECP224R1_ENABLED 15 | #define MBEDTLS_ECP_DP_SECP256R1_ENABLED 16 | #define MBEDTLS_ECP_DP_SECP384R1_ENABLED 17 | #define MBEDTLS_ECP_DP_SECP521R1_ENABLED 18 | #define MBEDTLS_ECP_DP_SECP192K1_ENABLED 19 | #define MBEDTLS_ECP_DP_SECP224K1_ENABLED 20 | #define MBEDTLS_ECP_DP_SECP256K1_ENABLED 21 | #define MBEDTLS_ECP_DP_BP256R1_ENABLED 22 | #define MBEDTLS_ECP_DP_BP384R1_ENABLED 23 | #define MBEDTLS_ECP_DP_BP512R1_ENABLED 24 | #define MBEDTLS_ECP_DP_CURVE25519_ENABLED 25 | #define MBEDTLS_KEY_EXCHANGE_RSA_ENABLED 26 | #define MBEDTLS_PKCS1_V15 27 | #define MBEDTLS_SHA256_SMALLER 28 | #define MBEDTLS_SSL_SERVER_NAME_INDICATION 29 | #define MBEDTLS_AES_C 30 | #define MBEDTLS_ASN1_PARSE_C 31 | #define MBEDTLS_BIGNUM_C 32 | #define MBEDTLS_CIPHER_C 33 | #define MBEDTLS_CTR_DRBG_C 34 | #define MBEDTLS_ENTROPY_C 35 | #define MBEDTLS_ERROR_C 36 | #define MBEDTLS_MD_C 37 | #define MBEDTLS_MD5_C 38 | #define MBEDTLS_OID_C 39 | #define MBEDTLS_PKCS5_C 40 | #define MBEDTLS_PK_C 41 | #define MBEDTLS_PK_PARSE_C 42 | #define MBEDTLS_PLATFORM_C 43 | #define MBEDTLS_RSA_C 44 | #define MBEDTLS_SHA1_C 45 | #define MBEDTLS_SHA224_C 46 | #define MBEDTLS_SHA256_C 47 | #define MBEDTLS_SHA512_C 48 | #define MBEDTLS_SSL_CLI_C 49 | #define MBEDTLS_SSL_SRV_C 50 | #define MBEDTLS_SSL_TLS_C 51 | #define MBEDTLS_X509_CRT_PARSE_C 52 | #define MBEDTLS_X509_USE_C 53 | #define MBEDTLS_AES_FEWER_TABLES 54 | 55 | /* TLS 1.2 */ 56 | #define MBEDTLS_SSL_PROTO_TLS1_2 57 | #define MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED 58 | #define MBEDTLS_GCM_C 59 | #define MBEDTLS_ECDH_C 60 | #define MBEDTLS_ECP_C 61 | #define MBEDTLS_ECDSA_C 62 | #define MBEDTLS_ASN1_WRITE_C 63 | 64 | // The following is needed to parse a certificate 65 | #define MBEDTLS_PEM_PARSE_C 66 | #define MBEDTLS_BASE64_C 67 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | 3 | # Pull in SDK (must be before project) 4 | include(pico_sdk_import.cmake) 5 | 6 | project(picow_iot C CXX ASM) 7 | set(CMAKE_C_STANDARD 11) 8 | set(CMAKE_CXX_STANDARD 17) 9 | 10 | if (PICO_SDK_VERSION_STRING VERSION_LESS "1.3.0") 11 | message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.3.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}") 12 | endif() 13 | 14 | # Initialize the SDK 15 | pico_sdk_init() 16 | 17 | add_compile_options(-Wall 18 | -Wno-format # int != int32_t as far as the compiler is concerned because gcc has int32_t as long int 19 | -Wno-unused-function # we have some for the docs that aren't called 20 | -Wno-maybe-uninitialized 21 | ) 22 | 23 | if (PICO_CYW43_SUPPORTED) # set by BOARD=pico-w 24 | if (NOT TARGET pico_cyw43_arch) 25 | message("Skipping build as support is not available") 26 | else() 27 | set(WIFI_SSID "${WIFI_SSID}" CACHE INTERNAL "WiFi SSID") 28 | set(WIFI_PASSWORD "${WIFI_PASSWORD}" CACHE INTERNAL "WiFi password") 29 | 30 | if ("${WIFI_SSID}" STREQUAL "") 31 | message("Skipping build as WIFI_SSID is not defined") 32 | elseif ("${WIFI_PASSWORD}" STREQUAL "") 33 | message("Skipping build as WIFI_PASSWORD is not defined") 34 | else() 35 | add_executable(picow_iot 36 | picow_iot.c 37 | ) 38 | 39 | # enable usb output, disable uart output 40 | pico_enable_stdio_usb(picow_iot 0) 41 | pico_enable_stdio_uart(picow_iot 1) 42 | 43 | target_compile_definitions(picow_iot PRIVATE 44 | WIFI_SSID=\"${WIFI_SSID}\" 45 | WIFI_PASSWORD=\"${WIFI_PASSWORD}\" 46 | TEST_TCP_SERVER_IP=\"${TEST_TCP_SERVER_IP}\" 47 | NO_SYS=1 48 | ) 49 | 50 | target_include_directories(picow_iot PRIVATE 51 | ${CMAKE_CURRENT_LIST_DIR} 52 | ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts 53 | ) 54 | 55 | target_link_libraries(picow_iot 56 | pico_cyw43_arch_lwip_poll 57 | pico_stdlib 58 | pico_lwip_mbedtls 59 | pico_mbedtls 60 | pico_lwip_mqtt 61 | ) 62 | 63 | pico_add_extra_outputs(picow_iot) 64 | endif() 65 | endif() 66 | endif() 67 | 68 | -------------------------------------------------------------------------------- /mqtt-sni.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/apps/altcp_tls/altcp_tls_mbedtls.c b/src/apps/altcp_tls/altcp_tls_mbedtls.c 2 | index e787ae28..11dab664 100644 3 | --- a/src/apps/altcp_tls/altcp_tls_mbedtls.c 4 | +++ b/src/apps/altcp_tls/altcp_tls_mbedtls.c 5 | @@ -107,6 +107,8 @@ struct altcp_tls_config { 6 | u8_t pkey_count; 7 | u8_t pkey_max; 8 | mbedtls_x509_crt *ca; 9 | + /** Server name for setting SNI */ 10 | + const char *server_name; 11 | #if defined(MBEDTLS_SSL_CACHE_C) && ALTCP_MBEDTLS_USE_SESSION_CACHE 12 | /** Inter-connection cache for fast connection startup */ 13 | struct mbedtls_ssl_cache_context cache; 14 | @@ -611,6 +613,7 @@ altcp_mbedtls_setup(void *conf, struct altcp_pcb *conn, struct altcp_pcb *inner_ 15 | int ret; 16 | struct altcp_tls_config *config = (struct altcp_tls_config *)conf; 17 | altcp_mbedtls_state_t *state; 18 | + 19 | if (!conf) { 20 | return ERR_ARG; 21 | } 22 | @@ -637,6 +640,15 @@ altcp_mbedtls_setup(void *conf, struct altcp_pcb *conn, struct altcp_pcb *inner_ 23 | conn->inner_conn = inner_conn; 24 | conn->fns = &altcp_mbedtls_functions; 25 | conn->state = state; 26 | + 27 | + if (config->server_name != NULL) { 28 | + if ((ret = mbedtls_ssl_set_hostname(&state->ssl_context, config->server_name)) != 0) { 29 | + LWIP_DEBUGF(ALTCP_MBEDTLS_DEBUG, ("mbedtls_ssl_set_hostname failed\n")); 30 | + } 31 | + mbedtls_ssl_conf_authmode(&config->conf, MBEDTLS_SSL_VERIFY_REQUIRED); 32 | + } 33 | + 34 | + 35 | return ERR_OK; 36 | } 37 | 38 | @@ -1330,6 +1342,14 @@ altcp_mbedtls_dealloc(struct altcp_pcb *conn) 39 | } 40 | } 41 | 42 | +void altcp_tls_set_server_name(struct altcp_tls_config *config, const char *server_name) { 43 | + config->server_name = server_name; 44 | +} 45 | + 46 | +const char *altcp_tls_get_server_name(struct altcp_tls_config *config) { 47 | + return config->server_name; 48 | +} 49 | + 50 | const struct altcp_functions altcp_mbedtls_functions = { 51 | altcp_mbedtls_set_poll, 52 | altcp_mbedtls_recved, 53 | diff --git a/src/include/lwip/altcp_tls.h b/src/include/lwip/altcp_tls.h 54 | index fcb784d8..a62203e5 100644 55 | --- a/src/include/lwip/altcp_tls.h 56 | +++ b/src/include/lwip/altcp_tls.h 57 | @@ -187,6 +187,11 @@ err_t altcp_tls_set_session(struct altcp_pcb *conn, struct altcp_tls_session *fr 58 | */ 59 | void altcp_tls_free_session(struct altcp_tls_session *dest); 60 | 61 | + 62 | +void altcp_tls_set_server_name(struct altcp_tls_config *config, const char *server_name); 63 | + 64 | +const char *altcp_tls_get_server_name(struct altcp_tls_config *config); 65 | + 66 | #ifdef __cplusplus 67 | } 68 | #endif 69 | -------------------------------------------------------------------------------- /pico_sdk_import.cmake: -------------------------------------------------------------------------------- 1 | # This is a copy of /external/pico_sdk_import.cmake 2 | 3 | # This can be dropped into an external project to help locate this SDK 4 | # It should be include()ed prior to project() 5 | 6 | if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) 7 | set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) 8 | message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") 9 | endif () 10 | 11 | if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) 12 | set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) 13 | message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") 14 | endif () 15 | 16 | if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) 17 | set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) 18 | message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") 19 | endif () 20 | 21 | set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") 22 | set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") 23 | set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") 24 | 25 | if (NOT PICO_SDK_PATH) 26 | if (PICO_SDK_FETCH_FROM_GIT) 27 | include(FetchContent) 28 | set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) 29 | if (PICO_SDK_FETCH_FROM_GIT_PATH) 30 | get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") 31 | endif () 32 | # GIT_SUBMODULES_RECURSE was added in 3.17 33 | if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") 34 | FetchContent_Declare( 35 | pico_sdk 36 | GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk 37 | GIT_TAG master 38 | GIT_SUBMODULES_RECURSE FALSE 39 | ) 40 | else () 41 | FetchContent_Declare( 42 | pico_sdk 43 | GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk 44 | GIT_TAG master 45 | ) 46 | endif () 47 | 48 | if (NOT pico_sdk) 49 | message("Downloading Raspberry Pi Pico SDK") 50 | FetchContent_Populate(pico_sdk) 51 | set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) 52 | endif () 53 | set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) 54 | else () 55 | message(FATAL_ERROR 56 | "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." 57 | ) 58 | endif () 59 | endif () 60 | 61 | get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") 62 | if (NOT EXISTS ${PICO_SDK_PATH}) 63 | message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") 64 | endif () 65 | 66 | set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) 67 | if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) 68 | message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") 69 | endif () 70 | 71 | set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) 72 | 73 | include(${PICO_SDK_INIT_CMAKE_FILE}) 74 | -------------------------------------------------------------------------------- /lwipopts.h: -------------------------------------------------------------------------------- 1 | #ifndef _LWIPOPTS_H 2 | #define _LWIPOPTS_H 3 | 4 | // allow override in some examples 5 | #ifndef NO_SYS 6 | #define NO_SYS 1 7 | #endif 8 | #define NO_SYS 1 9 | // allow override in some examples 10 | #ifndef LWIP_SOCKET 11 | #define LWIP_SOCKET 0 12 | #endif 13 | #if PICO_CYW43_ARCH_POLL 14 | #define MEM_LIBC_MALLOC 1 15 | #else 16 | // MEM_LIBC_MALLOC is incompatible with non polling versions 17 | #define MEM_LIBC_MALLOC 0 18 | #endif 19 | #define MEM_ALIGNMENT 4 20 | #define MEM_SIZE 4000 21 | #define MEMP_NUM_TCP_SEG 32 22 | #define MEMP_NUM_ARP_QUEUE 10 23 | #define PBUF_POOL_SIZE 24 24 | #define LWIP_ARP 1 25 | #define LWIP_ETHERNET 1 26 | #define LWIP_ICMP 1 27 | #define LWIP_RAW 1 28 | #define TCP_WND 16384 29 | #define TCP_MSS 1460 30 | #define TCP_SND_BUF (8 * TCP_MSS) 31 | #define TCP_SND_QUEUELEN ((4 * (TCP_SND_BUF) + (TCP_MSS - 1)) / (TCP_MSS)) 32 | #define LWIP_NETIF_STATUS_CALLBACK 1 33 | #define LWIP_NETIF_LINK_CALLBACK 1 34 | #define LWIP_NETIF_HOSTNAME 1 35 | #define LWIP_NETCONN 0 36 | #define MEM_STATS 0 37 | #define SYS_STATS 0 38 | #define MEMP_STATS 0 39 | #define LINK_STATS 0 40 | // #define ETH_PAD_SIZE 2 41 | #define LWIP_CHKSUM_ALGORITHM 3 42 | #define LWIP_DHCP 1 43 | #define LWIP_IPV4 1 44 | #define LWIP_TCP 1 45 | #define LWIP_UDP 1 46 | #define LWIP_DNS 1 47 | #define LWIP_TCP_KEEPALIVE 1 48 | #define LWIP_NETIF_TX_SINGLE_PBUF 1 49 | #define DHCP_DOES_ARP_CHECK 0 50 | #define LWIP_DHCP_DOES_ACD_CHECK 0 51 | 52 | #ifndef NDEBUG 53 | #define LWIP_DEBUG 1 54 | #define LWIP_STATS 1 55 | #define LWIP_STATS_DISPLAY 1 56 | #endif 57 | 58 | // #define MEM_OVERFLOW_CHECK 1 59 | // #define MEMP_OVERFLOW_CHECK 1 60 | 61 | #define ETHARP_DEBUG LWIP_DBG_OFF 62 | #define NETIF_DEBUG LWIP_DBG_OFF 63 | #define PBUF_DEBUG LWIP_DBG_OFF 64 | #define API_LIB_DEBUG LWIP_DBG_OFF 65 | #define API_MSG_DEBUG LWIP_DBG_OFF 66 | #define SOCKETS_DEBUG LWIP_DBG_OFF 67 | #define ICMP_DEBUG LWIP_DBG_OFF 68 | #define INET_DEBUG LWIP_DBG_OFF 69 | #define IP_DEBUG LWIP_DBG_OFF 70 | #define IP_REASS_DEBUG LWIP_DBG_OFF 71 | #define RAW_DEBUG LWIP_DBG_OFF 72 | #define MEM_DEBUG LWIP_DBG_OFF 73 | #define MEMP_DEBUG LWIP_DBG_OFF 74 | #define SYS_DEBUG LWIP_DBG_OFF 75 | #define TCP_DEBUG LWIP_DBG_OFF 76 | #define TCP_INPUT_DEBUG LWIP_DBG_OFF 77 | #define TCP_OUTPUT_DEBUG LWIP_DBG_OFF 78 | #define TCP_RTO_DEBUG LWIP_DBG_OFF 79 | #define TCP_CWND_DEBUG LWIP_DBG_OFF 80 | #define TCP_WND_DEBUG LWIP_DBG_OFF 81 | #define TCP_FR_DEBUG LWIP_DBG_OFF 82 | #define TCP_QLEN_DEBUG LWIP_DBG_OFF 83 | #define TCP_RST_DEBUG LWIP_DBG_OFF 84 | #define UDP_DEBUG LWIP_DBG_OFF 85 | #define TCPIP_DEBUG LWIP_DBG_OFF 86 | #define PPP_DEBUG LWIP_DBG_OFF 87 | #define SLIP_DEBUG LWIP_DBG_OFF 88 | #define DHCP_DEBUG LWIP_DBG_OFF 89 | #define ALTCP_MBEDTLS_DEBUG LWIP_DBG_ON 90 | #define MQTT_DEBUG LWIP_DBG_OFF 91 | #define ALTCP_MBEDTLS_MEM_DEBUG LWIP_DBG_OFF 92 | 93 | #define LWIP_ALTCP 1 94 | #define LWIP_ALTCP_TLS 1 95 | #define LWIP_ALTCP_TLS_MBEDTLS 1 96 | #define MEMP_NUM_SYS_TIMEOUT LWIP_NUM_SYS_TIMEOUT_INTERNAL + 1 97 | #endif 98 | -------------------------------------------------------------------------------- /picow_iot.c: -------------------------------------------------------------------------------- 1 | #include "hardware/structs/rosc.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "pico/stdlib.h" 7 | #include "pico/cyw43_arch.h" 8 | 9 | #include "lwip/pbuf.h" 10 | #include "lwip/tcp.h" 11 | #include "lwip/dns.h" 12 | 13 | #include "lwip/altcp_tcp.h" 14 | #include "lwip/altcp_tls.h" 15 | #include "lwip/apps/mqtt.h" 16 | 17 | #include "lwip/apps/mqtt_priv.h" 18 | 19 | // #include "tusb.h" 20 | 21 | #define DEBUG_printf printf 22 | 23 | #define MQTT_TLS 1 // needs to be 1 for AWS IoT. Also set published QoS to 0 or 1 24 | //#define CRYPTO_MOSQUITTO_LOCAL 25 | #define CRYPTO_AWS_IOT 26 | #include "crypto_consts.h" 27 | 28 | #if MQTT_TLS 29 | #ifdef CRYPTO_CERT 30 | const char *cert = CRYPTO_CERT; 31 | #endif 32 | #ifdef CRYPTO_CA 33 | const char *ca = CRYPTO_CA; 34 | #endif 35 | #ifdef CRYPTO_KEY 36 | const char *key = CRYPTO_KEY; 37 | #endif 38 | #endif 39 | 40 | typedef struct MQTT_CLIENT_T_ { 41 | ip_addr_t remote_addr; 42 | mqtt_client_t *mqtt_client; 43 | u32_t received; 44 | u32_t counter; 45 | u32_t reconnect; 46 | } MQTT_CLIENT_T; 47 | 48 | err_t mqtt_test_connect(MQTT_CLIENT_T *state); 49 | 50 | // Perform initialisation 51 | static MQTT_CLIENT_T* mqtt_client_init(void) { 52 | MQTT_CLIENT_T *state = calloc(1, sizeof(MQTT_CLIENT_T)); 53 | if (!state) { 54 | DEBUG_printf("failed to allocate state\n"); 55 | return NULL; 56 | } 57 | state->received = 0; 58 | return state; 59 | } 60 | 61 | void dns_found(const char *name, const ip_addr_t *ipaddr, void *callback_arg) { 62 | MQTT_CLIENT_T *state = (MQTT_CLIENT_T*)callback_arg; 63 | DEBUG_printf("DNS query finished with resolved addr of %s.\n", ip4addr_ntoa(ipaddr)); 64 | state->remote_addr = *ipaddr; 65 | } 66 | 67 | void run_dns_lookup(MQTT_CLIENT_T *state) { 68 | DEBUG_printf("Running DNS query for %s.\n", MQTT_SERVER_HOST); 69 | 70 | cyw43_arch_lwip_begin(); 71 | err_t err = dns_gethostbyname(MQTT_SERVER_HOST, &(state->remote_addr), dns_found, state); 72 | cyw43_arch_lwip_end(); 73 | 74 | if (err == ERR_ARG) { 75 | DEBUG_printf("failed to start DNS query\n"); 76 | return; 77 | } 78 | 79 | if (err == ERR_OK) { 80 | DEBUG_printf("no lookup needed"); 81 | return; 82 | } 83 | 84 | while (state->remote_addr.addr == 0) { 85 | cyw43_arch_poll(); 86 | sleep_ms(1); 87 | } 88 | } 89 | 90 | u32_t data_in = 0; 91 | 92 | u8_t buffer[1025]; 93 | u8_t data_len = 0; 94 | 95 | static void mqtt_pub_start_cb(void *arg, const char *topic, u32_t tot_len) { 96 | DEBUG_printf("mqtt_pub_start_cb: topic %s\n", topic); 97 | 98 | if (tot_len > 1024) { 99 | DEBUG_printf("Message length exceeds buffer size, discarding"); 100 | } else { 101 | data_in = tot_len; 102 | data_len = 0; 103 | } 104 | } 105 | 106 | static void mqtt_pub_data_cb(void *arg, const u8_t *data, u16_t len, u8_t flags) { 107 | if (data_in > 0) { 108 | data_in -= len; 109 | memcpy(&buffer[data_len], data, len); 110 | data_len += len; 111 | 112 | if (data_in == 0) { 113 | buffer[data_len] = 0; 114 | DEBUG_printf("Message received: %s\n", &buffer); 115 | } 116 | } 117 | } 118 | 119 | static void mqtt_connection_cb(mqtt_client_t *client, void *arg, mqtt_connection_status_t status) { 120 | if (status != 0) { 121 | DEBUG_printf("Error during connection: err %d.\n", status); 122 | } else { 123 | DEBUG_printf("MQTT connected.\n"); 124 | } 125 | } 126 | 127 | void mqtt_pub_request_cb(void *arg, err_t err) { 128 | MQTT_CLIENT_T *state = (MQTT_CLIENT_T *)arg; 129 | DEBUG_printf("mqtt_pub_request_cb: err %d\n", err); 130 | state->received++; 131 | } 132 | 133 | void mqtt_sub_request_cb(void *arg, err_t err) { 134 | DEBUG_printf("mqtt_sub_request_cb: err %d\n", err); 135 | } 136 | 137 | err_t mqtt_test_publish(MQTT_CLIENT_T *state) 138 | { 139 | char buffer[128]; 140 | 141 | sprintf(buffer, "{\"message\":\"hello from picow %d / %d\"}", state->received, state->counter); 142 | 143 | err_t err; 144 | u8_t qos = 0; /* 0 1 or 2, see MQTT specification. AWS IoT does not support QoS 2 */ 145 | u8_t retain = 0; 146 | cyw43_arch_lwip_begin(); 147 | err = mqtt_publish(state->mqtt_client, "pico_w/test", buffer, strlen(buffer), qos, retain, mqtt_pub_request_cb, state); 148 | cyw43_arch_lwip_end(); 149 | if(err != ERR_OK) { 150 | DEBUG_printf("Publish err: %d\n", err); 151 | } 152 | 153 | return err; 154 | } 155 | 156 | err_t mqtt_test_connect(MQTT_CLIENT_T *state) { 157 | struct mqtt_connect_client_info_t ci; 158 | err_t err; 159 | 160 | memset(&ci, 0, sizeof(ci)); 161 | 162 | ci.client_id = "PicoW"; 163 | ci.client_user = NULL; 164 | ci.client_pass = NULL; 165 | ci.keep_alive = 0; 166 | ci.will_topic = NULL; 167 | ci.will_msg = NULL; 168 | ci.will_retain = 0; 169 | ci.will_qos = 0; 170 | 171 | #if MQTT_TLS 172 | 173 | struct altcp_tls_config *tls_config; 174 | 175 | #if defined(CRYPTO_CA) && defined(CRYPTO_KEY) && defined(CRYPTO_CERT) 176 | 177 | DEBUG_printf("Setting up TLS with 2wayauth.\n"); 178 | tls_config = altcp_tls_create_config_client_2wayauth( 179 | (const u8_t *)ca, 1 + strlen((const char *)ca), 180 | (const u8_t *)key, 1 + strlen((const char *)key), 181 | (const u8_t *)"", 0, 182 | (const u8_t *)cert, 1 + strlen((const char *)cert) 183 | ); 184 | 185 | // enable SNI on request 186 | // see mqtt-sni.patch for changes to support this. 187 | altcp_tls_set_server_name(tls_config, MQTT_SERVER_HOST); 188 | 189 | #elif defined(CRYPTO_CERT) 190 | DEBUG_printf("Setting up TLS with cert.\n"); 191 | tls_config = altcp_tls_create_config_client((const u8_t *) cert, 1 + strlen((const char *) cert)); 192 | 193 | // enable SNI on request 194 | // see mqtt-sni.patch for changes to support this. 195 | altcp_tls_set_server_name(tls_config, MQTT_SERVER_HOST); 196 | #endif 197 | 198 | if (tls_config == NULL) { 199 | DEBUG_printf("Failed to initialize config\n"); 200 | return -1; 201 | } 202 | 203 | ci.tls_config = tls_config; 204 | #endif 205 | 206 | const struct mqtt_connect_client_info_t *client_info = &ci; 207 | 208 | err = mqtt_client_connect(state->mqtt_client, &(state->remote_addr), MQTT_SERVER_PORT, mqtt_connection_cb, state, client_info); 209 | 210 | if (err != ERR_OK) { 211 | DEBUG_printf("mqtt_connect return %d\n", err); 212 | } 213 | 214 | return err; 215 | } 216 | 217 | void mqtt_run_test(MQTT_CLIENT_T *state) { 218 | state->mqtt_client = mqtt_client_new(); 219 | 220 | state->counter = 0; 221 | 222 | if (state->mqtt_client == NULL) { 223 | DEBUG_printf("Failed to create new mqtt client\n"); 224 | return; 225 | } 226 | // psa_crypto_init(); 227 | if (mqtt_test_connect(state) == ERR_OK) { 228 | absolute_time_t timeout = nil_time; 229 | bool subscribed = false; 230 | mqtt_set_inpub_callback(state->mqtt_client, mqtt_pub_start_cb, mqtt_pub_data_cb, 0); 231 | 232 | while (true) { 233 | cyw43_arch_poll(); 234 | absolute_time_t now = get_absolute_time(); 235 | if (is_nil_time(timeout) || absolute_time_diff_us(now, timeout) <= 0) { 236 | if (mqtt_client_is_connected(state->mqtt_client)) { 237 | cyw43_arch_lwip_begin(); 238 | 239 | if (!subscribed) { 240 | mqtt_sub_unsub(state->mqtt_client, "pico_w/recv", 0, mqtt_sub_request_cb, 0, 1); 241 | subscribed = true; 242 | } 243 | 244 | if (mqtt_test_publish(state) == ERR_OK) { 245 | if (state->counter != 0) { 246 | DEBUG_printf("published %d\n", state->counter); 247 | } 248 | timeout = make_timeout_time_ms(5000); 249 | state->counter++; 250 | } // else ringbuffer is full and we need to wait for messages to flush. 251 | cyw43_arch_lwip_end(); 252 | } else { 253 | // DEBUG_printf("."); 254 | } 255 | } 256 | } 257 | } 258 | } 259 | 260 | int main() { 261 | stdio_init_all(); 262 | 263 | if (cyw43_arch_init()) { 264 | DEBUG_printf("failed to initialise\n"); 265 | return 1; 266 | } 267 | cyw43_arch_enable_sta_mode(); 268 | 269 | DEBUG_printf("Connecting to WiFi...\n"); 270 | if (cyw43_arch_wifi_connect_timeout_ms(WIFI_SSID, WIFI_PASSWORD, CYW43_AUTH_WPA2_AES_PSK, 30000)) { 271 | DEBUG_printf("failed to connect.\n"); 272 | return 1; 273 | } else { 274 | DEBUG_printf("Connected.\n"); 275 | } 276 | 277 | MQTT_CLIENT_T *state = mqtt_client_init(); 278 | 279 | run_dns_lookup(state); 280 | 281 | mqtt_run_test(state); 282 | 283 | cyw43_arch_deinit(); 284 | return 0; 285 | } --------------------------------------------------------------------------------