From 973ba91487ca2800a3ec807d55bf4eb96ede0ba0 Mon Sep 17 00:00:00 2001 From: Jamie McCrae Date: Sun, 20 Oct 2024 10:22:50 +0100 Subject: [PATCH] mgmt: mcumgr: transport: Add LoRaWAN MCUmgr SMP transport Adds a transport that uses LoRaWAN for receiving and responding to messages Signed-off-by: Jamie McCrae --- include/zephyr/mgmt/mcumgr/transport/smp.h | 2 + subsys/mgmt/mcumgr/transport/CMakeLists.txt | 3 + subsys/mgmt/mcumgr/transport/Kconfig | 2 + subsys/mgmt/mcumgr/transport/Kconfig.lorawan | 93 ++++++ .../mgmt/mcumgr/transport/src/smp_lorawan.c | 267 ++++++++++++++++++ 5 files changed, 367 insertions(+) create mode 100644 subsys/mgmt/mcumgr/transport/Kconfig.lorawan create mode 100644 subsys/mgmt/mcumgr/transport/src/smp_lorawan.c diff --git a/include/zephyr/mgmt/mcumgr/transport/smp.h b/include/zephyr/mgmt/mcumgr/transport/smp.h index d8cec6878f7..38aa1b0e0c2 100644 --- a/include/zephyr/mgmt/mcumgr/transport/smp.h +++ b/include/zephyr/mgmt/mcumgr/transport/smp.h @@ -148,6 +148,8 @@ enum smp_transport_type { SMP_UDP_IPV4_TRANSPORT, /** SMP UDP IPv6 */ SMP_UDP_IPV6_TRANSPORT, + /** SMP LoRaWAN */ + SMP_LORAWAN_TRANSPORT, /** SMP user defined type */ SMP_USER_DEFINED_TRANSPORT }; diff --git a/subsys/mgmt/mcumgr/transport/CMakeLists.txt b/subsys/mgmt/mcumgr/transport/CMakeLists.txt index 2d8ea3e6d87..871fab1ecd0 100644 --- a/subsys/mgmt/mcumgr/transport/CMakeLists.txt +++ b/subsys/mgmt/mcumgr/transport/CMakeLists.txt @@ -25,6 +25,9 @@ zephyr_library_sources_ifdef(CONFIG_MCUMGR_TRANSPORT_UART zephyr_library_sources_ifdef(CONFIG_MCUMGR_TRANSPORT_UDP src/smp_udp.c ) +zephyr_library_sources_ifdef(CONFIG_MCUMGR_TRANSPORT_LORAWAN + src/smp_lorawan.c +) zephyr_library_sources_ifdef(CONFIG_MCUMGR_TRANSPORT_DUMMY src/smp_dummy.c ) diff --git a/subsys/mgmt/mcumgr/transport/Kconfig b/subsys/mgmt/mcumgr/transport/Kconfig index 3d99fb33ccf..a892caee8d6 100644 --- a/subsys/mgmt/mcumgr/transport/Kconfig +++ b/subsys/mgmt/mcumgr/transport/Kconfig @@ -77,6 +77,8 @@ rsource "Kconfig.dummy" rsource "Kconfig.bluetooth" +rsource "Kconfig.lorawan" + rsource "Kconfig.shell" rsource "Kconfig.uart" diff --git a/subsys/mgmt/mcumgr/transport/Kconfig.lorawan b/subsys/mgmt/mcumgr/transport/Kconfig.lorawan new file mode 100644 index 00000000000..a9dcc80d9f5 --- /dev/null +++ b/subsys/mgmt/mcumgr/transport/Kconfig.lorawan @@ -0,0 +1,93 @@ +# +# Copyright (c) 2024, Jamie McCrae +# +# SPDX-License-Identifier: Apache-2.0 +# + +# The Kconfig file is dedicated to the LoRaWAN transport of MCUmgr +# subsystem and provides Kconfig options to control aspects of +# the transport. +# +# Options defined in this file should be prefixed: +# MCUMGR_TRANSPORT_LORAWAN_ + +menuconfig MCUMGR_TRANSPORT_LORAWAN + bool "LoRaWAN MCUmgr SMP transport" + depends on LORAWAN + help + Enables handling of SMP commands received over LoRaWAN. + +if MCUMGR_TRANSPORT_LORAWAN + +config MCUMGR_TRANSPORT_LORAWAN_FRAME_PORT + int "LoRaWAN SMP frame port" + range 1 223 + default 2 + help + LoRaWAN download and uplink frame port used for communication. All messages received on + this port will be treated as SMP packets. + +config MCUMGR_TRANSPORT_LORAWAN_CONFIRMED_UPLINKS + bool "Use confirmed packets for uplinks" + default y + help + Will use confirmed uplink packets for responses if enabled, otherwise will use + unconfirmed packets. + +config MCUMGR_TRANSPORT_LORAWAN_REASSEMBLY + bool "Reassemble LoRaWAN SMP messages" + select MCUMGR_TRANSPORT_REASSEMBLY + default y + help + Will reassemble downlink LoRaWAN messages together to allow for messages larger than a + single message to be received, otherwise will support messages up to a single packet in + size. + +menuconfig MCUMGR_TRANSPORT_LORAWAN_POLL_FOR_DATA + bool "Send empty packet if partial packet received" + depends on MCUMGR_TRANSPORT_LORAWAN_REASSEMBLY + default y + help + Will send an empty packet if a partial (fragmented) message has been received from the + server, this will allow the next packet to be received without waiting for next + transmission window. + + Note: this requires a dedicated thread in order to prevent blocking the system workqueue. + +if MCUMGR_TRANSPORT_LORAWAN_POLL_FOR_DATA + +config MCUMGR_TRANSPORT_LORAWAN_POLL_FOR_DATA_STACK_SIZE + int "Poll thread stack size" + default 1800 + help + Stack size of the thread that will poll for empty additional packets when a partial + frame is received. + +config MCUMGR_TRANSPORT_LORAWAN_POLL_FOR_DATA_THREAD_PRIORITY + int "Poll thread priority" + default 3 + help + Priority of the thread for polling for empty additional packets when a partial frame + is received. + +config MCUMGR_TRANSPORT_LORAWAN_POLL_FOR_DATA_RETRIES + int "Poll thread retries" + default 3 + help + Number of LoRaWAN message send retries if sending fails for the thread for polling for + empty additional packets when a partial frame is received. + +endif # MCUMGR_TRANSPORT_LORAWAN_POLL_FOR_DATA + +config MCUMGR_TRANSPORT_LORAWAN_FRAGMENTED_UPLINKS + bool "Fragment uplink messages" + default y + help + Will fragment messages into multiple uplink messages if they are too big to fit into a + single uplink message. If disabled then uplinks that are too large will not be sent. + +module = MCUMGR_TRANSPORT_LORAWAN +module-str = LoRaWAN MCUmgr SMP transport +source "subsys/logging/Kconfig.template.log_config" + +endif # MCUMGR_TRANSPORT_LORAWAN diff --git a/subsys/mgmt/mcumgr/transport/src/smp_lorawan.c b/subsys/mgmt/mcumgr/transport/src/smp_lorawan.c new file mode 100644 index 00000000000..7e5864500f1 --- /dev/null +++ b/subsys/mgmt/mcumgr/transport/src/smp_lorawan.c @@ -0,0 +1,267 @@ +/* + * Copyright (c) 2024, Jamie McCrae + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +LOG_MODULE_REGISTER(smp_lorawan, CONFIG_MCUMGR_TRANSPORT_LORAWAN_LOG_LEVEL); + +static void smp_lorawan_downlink(uint8_t port, bool data_pending, int16_t rssi, int8_t snr, + uint8_t len, const uint8_t *hex_data); + +static int smp_lorawan_uplink(struct net_buf *nb); + +static uint16_t smp_lorawan_get_mtu(const struct net_buf *nb); + +static struct lorawan_downlink_cb lorawan_smp_downlink_cb = { + .port = CONFIG_MCUMGR_TRANSPORT_LORAWAN_FRAME_PORT, + .cb = smp_lorawan_downlink, +}; + +struct smp_transport smp_lorawan_transport = { + .functions.output = smp_lorawan_uplink, + .functions.get_mtu = smp_lorawan_get_mtu, +}; + +#ifdef CONFIG_SMP_CLIENT +struct smp_client_transport_entry smp_lorawan_client_transport = { + .smpt = &smp_lorawan_transport, + .smpt_type = SMP_LORAWAN_TRANSPORT, +}; +#endif + +#ifdef CONFIG_MCUMGR_TRANSPORT_LORAWAN_POLL_FOR_DATA +static struct k_thread smp_lorawan_thread; +K_KERNEL_STACK_MEMBER(smp_lorawan_stack, CONFIG_MCUMGR_TRANSPORT_LORAWAN_POLL_FOR_DATA_STACK_SIZE); +K_FIFO_DEFINE(smp_lorawan_fifo); + +struct smp_lorawan_uplink_message_t { + void *fifo_reserved; + struct net_buf *nb; + struct k_sem my_sem; +}; + +static struct smp_lorawan_uplink_message_t empty_message = { + .nb = NULL, +}; + +static void smp_lorawan_uplink_thread(void *p1, void *p2, void *p3) +{ + struct smp_lorawan_uplink_message_t *msg; + + while (1) { + msg = k_fifo_get(&smp_lorawan_fifo, K_FOREVER); + uint16_t size = 0; + uint16_t pos = 0; + + if (msg->nb != NULL) { + size = msg->nb->len; + } + + while (pos < size || size == 0) { + uint8_t *data = NULL; + uint8_t data_size; + uint8_t temp; + uint8_t tries = CONFIG_MCUMGR_TRANSPORT_LORAWAN_POLL_FOR_DATA_RETRIES; + + lorawan_get_payload_sizes(&data_size, &temp); + + if (data_size > size) { + data_size = size; + } + + if (size > 0) { + if ((data_size + pos) > size) { + data_size = size - pos; + } + + data = net_buf_pull_mem(msg->nb, data_size); + } + + while (tries > 0) { + int rc; + + rc = lorawan_send(CONFIG_MCUMGR_TRANSPORT_LORAWAN_FRAME_PORT, + data, data_size, +#if defined(CONFIG_MCUMGR_TRANSPORT_LORAWAN_CONFIRMED_UPLINKS) + LORAWAN_MSG_CONFIRMED +#else + LORAWAN_MSG_UNCONFIRMED +#endif + ); + + if (rc != 0) { + --tries; + } else { + break; + } + } + + if (size == 0) { + break; + } + + pos += data_size; + } + + /* For empty packets, do not trigger semaphore */ + if (size != 0) { + k_sem_give(&msg->my_sem); + } + } +} +#endif + +static void smp_lorawan_downlink(uint8_t port, bool data_pending, int16_t rssi, int8_t snr, + uint8_t len, const uint8_t *hex_data) +{ + ARG_UNUSED(data_pending); + ARG_UNUSED(rssi); + ARG_UNUSED(snr); + + if (port == CONFIG_MCUMGR_TRANSPORT_LORAWAN_FRAME_PORT) { +#ifdef CONFIG_MCUMGR_TRANSPORT_LORAWAN_REASSEMBLY + int rc; + + if (len == 0) { + /* Empty packet is used to clear partially queued data */ + (void)smp_reassembly_drop(&smp_lorawan_transport); + } else { + rc = smp_reassembly_collect(&smp_lorawan_transport, hex_data, len); + + if (rc == 0) { + rc = smp_reassembly_complete(&smp_lorawan_transport, false); + + if (rc) { + LOG_ERR("LoRaWAN SMP reassembly complete failed: %d", rc); + } + } else if (rc < 0) { + LOG_ERR("LoRaWAN SMP reassembly collect failed: %d", rc); + } else { + LOG_ERR("LoRaWAN SMP expected data left: %d", rc); + +#ifdef CONFIG_MCUMGR_TRANSPORT_LORAWAN_POLL_FOR_DATA + /* Send empty LoRaWAN packet to receive next packet from server */ + k_fifo_put(&smp_lorawan_fifo, &empty_message); +#endif + } + } +#else + if (len > sizeof(struct smp_hdr)) { + struct net_buf *nb; + + nb = smp_packet_alloc(); + + if (!nb) { + LOG_ERR("LoRaWAN SMP packet allocation failure"); + return; + } + + net_buf_add_mem(nb, hex_data, len); + smp_rx_req(&smp_lorawan_transport, nb); + } else { + LOG_ERR("Invalid LoRaWAN SMP downlink"); + } +#endif + } else { + LOG_ERR("Invalid LoRaWAN SMP downlink"); + } +} + +static int smp_lorawan_uplink(struct net_buf *nb) +{ + int rc = 0; + +#ifdef CONFIG_MCUMGR_TRANSPORT_LORAWAN_FRAGMENTED_UPLINKS + struct smp_lorawan_uplink_message_t tx_data = { + .nb = nb, + }; + + k_sem_init(&tx_data.my_sem, 0, 1); + k_fifo_put(&smp_lorawan_fifo, &tx_data); + k_sem_take(&tx_data.my_sem, K_FOREVER); +#else + uint8_t data_size; + uint8_t temp; + + lorawan_get_payload_sizes(&data_size, &temp); + + if (nb->len > data_size) { + LOG_ERR("Cannot send LoRaWAN SMP message, too large. Message: %d, maximum: %d", + nb->len, data_size); + } else { + rc = lorawan_send(CONFIG_MCUMGR_TRANSPORT_LORAWAN_FRAME_PORT, nb->data, nb->len, +#if defined(CONFIG_MCUMGR_TRANSPORT_LORAWAN_CONFIRMED_UPLINKS) + LORAWAN_MSG_CONFIRMED +#else + LORAWAN_MSG_UNCONFIRMED +#endif + ); + + if (rc != 0) { + LOG_ERR("Failed to send LoRaWAN SMP message: %d", rc); + } + } +#endif + + smp_packet_free(nb); + + return rc; +} + +static uint16_t smp_lorawan_get_mtu(const struct net_buf *nb) +{ + ARG_UNUSED(nb); + + uint8_t max_data_size; + uint8_t temp; + + lorawan_get_payload_sizes(&max_data_size, &temp); + + return (uint16_t)max_data_size; +} + +static void smp_lorawan_start(void) +{ + int rc; + + rc = smp_transport_init(&smp_lorawan_transport); + +#ifdef CONFIG_SMP_CLIENT + if (rc == 0) { + smp_client_transport_register(&smp_lorawan_client_transport); + } +#endif + + if (rc == 0) { + lorawan_register_downlink_callback(&lorawan_smp_downlink_cb); + } else { + LOG_ERR("Failed to init LoRaWAN MCUmgr SMP transport: %d", rc); + } + +#ifdef CONFIG_MCUMGR_TRANSPORT_LORAWAN_REASSEMBLY + smp_reassembly_init(&smp_lorawan_transport); +#endif + +#ifdef CONFIG_MCUMGR_TRANSPORT_LORAWAN_POLL_FOR_DATA + k_thread_create(&smp_lorawan_thread, smp_lorawan_stack, + K_KERNEL_STACK_SIZEOF(smp_lorawan_stack), + smp_lorawan_uplink_thread, NULL, NULL, NULL, + CONFIG_MCUMGR_TRANSPORT_LORAWAN_POLL_FOR_DATA_THREAD_PRIORITY, 0, + K_FOREVER); + + k_thread_start(&smp_lorawan_thread); +#endif +} + +MCUMGR_HANDLER_DEFINE(smp_lorawan, smp_lorawan_start);