diff --git a/FreeRTOS-Plus/Demo/Common/coreMQTT_Agent_Interface/freertos_agent_message.c b/FreeRTOS-Plus/Demo/Common/coreMQTT_Agent_Interface/freertos_agent_message.c index 0ab66a961b..ead65ac0ca 100644 --- a/FreeRTOS-Plus/Demo/Common/coreMQTT_Agent_Interface/freertos_agent_message.c +++ b/FreeRTOS-Plus/Demo/Common/coreMQTT_Agent_Interface/freertos_agent_message.c @@ -44,14 +44,14 @@ /*-----------------------------------------------------------*/ bool Agent_MessageSend( const AgentMessageContext_t * pMsgCtx, - const void * pData, + Command_t * const * pCommandToSend, uint32_t blockTimeMs ) { BaseType_t queueStatus = pdFAIL; - if( ( pMsgCtx != NULL ) && ( pData != NULL ) ) + if( ( pMsgCtx != NULL ) && ( pCommandToSend != NULL ) ) { - queueStatus = xQueueSendToBack( pMsgCtx->queue, pData, pdMS_TO_TICKS( blockTimeMs ) ); + queueStatus = xQueueSendToBack( pMsgCtx->queue, pCommandToSend, pdMS_TO_TICKS( blockTimeMs ) ); } return ( queueStatus == pdPASS ) ? true : false; @@ -60,14 +60,14 @@ bool Agent_MessageSend( const AgentMessageContext_t * pMsgCtx, /*-----------------------------------------------------------*/ bool Agent_MessageReceive( const AgentMessageContext_t * pMsgCtx, - void * pBuffer, + Command_t ** pReceivedCommand, uint32_t blockTimeMs ) { BaseType_t queueStatus = pdFAIL; - if( ( pMsgCtx != NULL ) && ( pBuffer != NULL ) ) + if( ( pMsgCtx != NULL ) && ( pReceivedCommand != NULL ) ) { - queueStatus = xQueueReceive( pMsgCtx->queue, pBuffer, pdMS_TO_TICKS( blockTimeMs ) ); + queueStatus = xQueueReceive( pMsgCtx->queue, pReceivedCommand, pdMS_TO_TICKS( blockTimeMs ) ); } return ( queueStatus == pdPASS ) ? true : false; diff --git a/FreeRTOS-Plus/Demo/Common/coreMQTT_Agent_Interface/freertos_command_pool.c b/FreeRTOS-Plus/Demo/Common/coreMQTT_Agent_Interface/freertos_command_pool.c index bfbd6b052e..6bfc3d8a30 100644 --- a/FreeRTOS-Plus/Demo/Common/coreMQTT_Agent_Interface/freertos_command_pool.c +++ b/FreeRTOS-Plus/Demo/Common/coreMQTT_Agent_Interface/freertos_command_pool.c @@ -104,7 +104,6 @@ void Agent_InitializePool( void ) Command_t * Agent_GetCommand( uint32_t blockTimeMs ) { Command_t * structToUse = NULL; - size_t i; bool structRetrieved = false; /* Check queue has been created. */ @@ -125,7 +124,6 @@ Command_t * Agent_GetCommand( uint32_t blockTimeMs ) bool Agent_ReleaseCommand( Command_t * pCommandToRelease ) { - size_t i; bool structReturned = false; configASSERT( initStatus == QUEUE_INITIALIZED ); diff --git a/FreeRTOS-Plus/Demo/Common/coreMQTT_Agent_Interface/include/freertos_agent_message.h b/FreeRTOS-Plus/Demo/Common/coreMQTT_Agent_Interface/include/freertos_agent_message.h index 47458519a2..6bd29bed69 100644 --- a/FreeRTOS-Plus/Demo/Common/coreMQTT_Agent_Interface/include/freertos_agent_message.h +++ b/FreeRTOS-Plus/Demo/Common/coreMQTT_Agent_Interface/include/freertos_agent_message.h @@ -58,13 +58,13 @@ struct AgentMessageContext * Must be thread safe. * * @param[in] pMsgCtx An #AgentMessageContext_t. - * @param[in] pData Pointer to element to send to queue. + * @param[in] pCommandToSend Pointer to address to send to queue. * @param[in] blockTimeMs Block time to wait for a send. * * @return `true` if send was successful, else `false`. */ bool Agent_MessageSend( const AgentMessageContext_t * pMsgCtx, - const void * pData, + Command_t * const * pCommandToSend, uint32_t blockTimeMs ); /** @@ -72,13 +72,13 @@ bool Agent_MessageSend( const AgentMessageContext_t * pMsgCtx, * Must be thread safe. * * @param[in] pMsgCtx An #AgentMessageContext_t. - * @param[in] pBuffer Pointer to buffer to write received data. + * @param[in] pReceivedCommand Pointer to write address of received command. * @param[in] blockTimeMs Block time to wait for a receive. * * @return `true` if receive was successful, else `false`. */ bool Agent_MessageReceive( const AgentMessageContext_t * pMsgCtx, - void * pBuffer, + Command_t ** pReceivedCommand, uint32_t blockTimeMs ); #endif /* FREERTOS_AGENT_MESSAGE_H */ diff --git a/FreeRTOS-Plus/Demo/coreMQTT_Windows_Simulator/MQTT_Multitask/DemoTasks/MultitaskMQTTExample.c b/FreeRTOS-Plus/Demo/coreMQTT_Windows_Simulator/MQTT_Multitask/DemoTasks/MultitaskMQTTExample.c deleted file mode 100644 index 7a6ef60224..0000000000 --- a/FreeRTOS-Plus/Demo/coreMQTT_Windows_Simulator/MQTT_Multitask/DemoTasks/MultitaskMQTTExample.c +++ /dev/null @@ -1,2224 +0,0 @@ -/* - * FreeRTOS V202012.00 - * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * https://www.FreeRTOS.org - * https://github.com/FreeRTOS - * - */ - -/* - * This demo shows how to use coreMQTT in a multithreaded environment - it does not - * yet go as far as encapsulating the MQTT library within its own agent (or daemon) - * task - although the prvCommandLoop() function demonstrates how that might be done. - * Also see https://www.freertos.org/mqtt/mqtt-agent-demo.html? for an - * example that does use an agent task. Executing the MQTT protocol in an agent - * task removes the need for the application writer to explicitly manage any MQTT - * state or call the MQTT_ProcessLoop() API function. Using an agent task - * also enables multiple application tasks to more easily share a single - * MQTT connection. - * - * In this task prvCommandLoop() is only executed from a single thread and is the - * only function that is allowed to use the coreMQTT API directly. Anything else - * needing to interact with the coreMQTT API does so by posting commands to - * prvCommandLoop() via a queue. Future coreMQTT releases will build an agent into - * the library itself, and then encapsulate the queues into the implementation of a - * thread safe coreMQTT API. - * - * To use this demo with TLS set democonfigUSE_TLS to 1. To use this demo without - * TLS (so plain text) set democonfigUSE_TLS to 0. democonfigUSE_TLS is defined - * in demo_config.h. - * - *!!! Plain text connections are only used for ease of demonstration. Do not send - *!!! sensitive data on unencrypted connections. Production devices should used - *!!! mutually authenticated and encrypted connections. - * - * There are four tasks to note in this demo: - * - prvMQTTDemoTask() manages multiple iterations of the demo. Each iteration - * creates the other tasks, calls prvCommandLoop() to handle the MQTT traffic, - * then cleans up ready for the next iteration. - * - prvSyncPublishTask() which demonstrates synchronous publishes. The task creates - * a series of publish operations that are sent over the command queue to be - * processed by prvCommandLoop(), waiting for each publish to complete before - * sending the next. - * - prvAsyncPublishTask() which demonstrates asynchronous publishes. Like - * prvSyncPublishTask(), the task creates a series of publish operations that are - * sent over the command queue to be processed by prvCommandLoop(), but unlike - * prvSyncPublishTask() this task does not wait for each publish to be complete - * until after all the publish commands are sent. Note that the distinction - * between synchronous and asynchronous publishes is only in the behavior of the - * task, not in the actual publish command. - * - prvSubscribeTask() which creates an MQTT subscription to a topic filter - * matching the topics published on by the two publishing tasks, and in doing so, - * ensures the demo received a publish command back for each publish command it - * sends. It loops while waiting for publish messages to be received. - * - * Tasks can have queues to hold received publish messages, and the command task - * will push incoming publishes to the queue of each task that is subscribed to - * the incoming topic. - */ - -/* Standard includes. */ -#include -#include -#include - -/* Kernel includes. */ -#include "FreeRTOS.h" -#include "task.h" -#include "queue.h" - -/* FreeRTOS+TCP includes. */ -#include "FreeRTOS_IP.h" -#include "FreeRTOS_Sockets.h" - -/* Demo Specific configs. */ -#include "demo_config.h" - -/* MQTT library includes. */ -#include "core_mqtt.h" -#include "core_mqtt_state.h" - -/* Exponential backoff retry include. */ -#include "backoff_algorithm.h" - -/* Transport interface include. */ -#if defined( democonfigUSE_TLS ) && ( democonfigUSE_TLS == 1 ) - #include "using_mbedtls.h" -#else - #include "using_plaintext.h" -#endif - -/** - * These configuration settings are required to run the demo. - */ - -/** - * @brief The size to use for the network buffer. - */ -#ifndef mqttexampleNETWORK_BUFFER_SIZE - #define mqttexampleNETWORK_BUFFER_SIZE ( 1024U ) -#endif - -/** - * @brief The maximum number of retries for network operation with server. - */ -#define mqttexampleRETRY_MAX_ATTEMPTS ( 5U ) - -/** - * @brief The maximum back-off delay (in milliseconds) for retrying failed operation - * with server. - */ -#define mqttexampleRETRY_MAX_BACKOFF_DELAY_MS ( 5000U ) - -/** - * @brief The base back-off delay (in milliseconds) to use for network operation retry - * attempts. - */ -#define mqttexampleRETRY_BACKOFF_BASE_MS ( 500U ) - -/** - * @brief Timeout for receiving CONNACK packet in milliseconds. - */ -#define mqttexampleCONNACK_RECV_TIMEOUT_MS ( 1000U ) - -/** - * @brief Time to wait between each cycle of the demo implemented by prvMQTTDemoTask(). - */ -#define mqttexampleDELAY_BETWEEN_DEMO_ITERATIONS ( pdMS_TO_TICKS( 5000U ) ) - -/** - * @brief Timeout for MQTT_ProcessLoop function in milliseconds. - * - * This demo uses no delay for the process loop, so each invocation will run - * one iteration, and will only receive a single packet. However, if there is - * no data available on the socket, the entire socket timeout value will elapse. - */ -#define mqttexamplePROCESS_LOOP_TIMEOUT_MS ( 0U ) - -/** - * @brief The maximum time interval in seconds which is allowed to elapse - * between two Control Packets. - * - * It is the responsibility of the Client to ensure that the interval between - * Control Packets being sent does not exceed the this Keep Alive value. In the - * absence of sending any other Control Packets, the Client MUST send a - * PINGREQ Packet. - */ -#define mqttexampleKEEP_ALIVE_INTERVAL_SECONDS ( 60U ) - -/** - * @brief Transport timeout in milliseconds for transport send and receive. - */ -#define mqttexampleTRANSPORT_SEND_RECV_TIMEOUT_MS ( 200 ) - -/** - * @brief Milliseconds per second. - */ -#define mqttexampleMILLISECONDS_PER_SECOND ( 1000U ) - -/** - * @brief Milliseconds per FreeRTOS tick. - */ -#define mqttexampleMILLISECONDS_PER_TICK ( mqttexampleMILLISECONDS_PER_SECOND / configTICK_RATE_HZ ) - -/** - * @brief Ticks to wait for task notifications. - */ -#define mqttexampleDEMO_TICKS_TO_WAIT pdMS_TO_TICKS( 1000 ) - -/** - * @brief Maximum number of operations awaiting an ack packet from the broker. - */ -#define mqttexamplePENDING_ACKS_MAX_SIZE 20 - -/** - * @brief Maximum number of subscriptions to store in the subscription list. - */ -#define mqttexampleSUBSCRIPTIONS_MAX_COUNT 10 - -/** - * @brief Number of publishes done by the publisher in this demo. - */ -#define mqttexamplePUBLISH_COUNT 16 - -/** - * @brief Size of statically allocated buffers for holding topic names and payloads in this demo. - */ -#define mqttexampleDEMO_BUFFER_SIZE 100 - -/** - * @brief Size of dynamically allocated buffers for holding topic names and payloads in this demo. - */ -#define mqttexampleDYNAMIC_BUFFER_SIZE 25 - -/** - * @brief Max number of commands that can be enqueued. - */ -#define mqttexampleCOMMAND_QUEUE_SIZE 25 - -/** - * @brief Max number of received publishes that can be enqueued for a task. - */ -#define mqttexamplePUBLISH_QUEUE_SIZE 20 - -/** - * @brief Delay for the subscriber task. If no publishes are waiting in the - * task's message queue, it will wait this many milliseconds before checking - * it again. - */ -#define mqttexampleSUBSCRIBE_TASK_DELAY_MS 400U - -/** - * @brief Delay for the synchronous publisher task between publishes. - */ -#define mqttexamplePUBLISH_DELAY_SYNC_MS 100U - -/** - * @brief Delay for the asynchronous publisher task between publishes. - */ -#define mqttexamplePUBLISH_DELAY_ASYNC_MS 100U - -/** - * @brief Notification bit indicating completion of publisher task. - */ -#define mqttexamplePUBLISHER_SYNC_COMPLETE_BIT ( 1U << 1 ) - -/** - * @brief Notification bit indicating completion of second publisher task. - */ -#define mqttexamplePUBLISHER_ASYNC_COMPLETE_BIT ( 1U << 2 ) - -/** - * @brief Notification bit indicating completion of subscriber task. - */ -#define mqttexampleSUBSCRIBE_TASK_COMPLETE_BIT ( 1U << 3 ) - -/** - * @brief Notification bit used by subscriber task for subscribe operation. - */ -#define mqttexampleSUBSCRIBE_COMPLETE_BIT ( 1U << 0 ) - -/** - * @brief Notification bit used by subscriber task for unsubscribe operation. - */ -#define mqttexampleUNSUBSCRIBE_COMPLETE_BIT ( 1U << 1 ) - -/** - * @brief The maximum number of loop iterations to wait before declaring failure. - * - * Each `while` loop waiting for a task notification will wait for a total - * number of ticks equal to `mqttexampleDEMO_TICKS_TO_WAIT` * this number of - * iterations before the loop exits. - * - * @note This value should not be too small, as the reason for a long loop - * may be a loss of network connection. - */ -#define mqttexampleMAX_WAIT_ITERATIONS ( 20 ) - -/** - * @brief Topic filter used by the subscriber task. - */ -#define mqttexampleSUBSCRIBE_TOPIC_FILTER "filter/+/+" - -/** - * @brief Format string used by the publisher task for topic names. - */ -#define mqttexamplePUBLISH_TOPIC_FORMAT_STRING "filter/%s/%i" - -/** - * @brief Format string used by the publisher task for payloads. - */ -#define mqttexamplePUBLISH_PAYLOAD_FORMAT "Hello World! %s: %d" - -/*-----------------------------------------------------------*/ - -/** - * @brief A type of command for interacting with the MQTT API. - */ -typedef enum CommandType -{ - PROCESSLOOP, /**< @brief Call MQTT_ProcessLoop(). */ - PUBLISH, /**< @brief Call MQTT_Publish(). */ - SUBSCRIBE, /**< @brief Call MQTT_Subscribe(). */ - UNSUBSCRIBE, /**< @brief Call MQTT_Unsubscribe(). */ - PING, /**< @brief Call MQTT_Ping(). */ - DISCONNECT, /**< @brief Call MQTT_Disconnect(). */ - RECONNECT, /**< @brief Reconnect a broken connection. */ - TERMINATE /**< @brief Exit the command loop and stop processing commands. */ -} CommandType_t; - -/** - * @brief Struct containing context for a specific command. - * - * @note An instance of this struct and any variables it points to MUST stay - * in scope until the associated command is processed, and its callback called. - * The command callback will set the `xIsComplete` flag, and notify the calling task. - */ -typedef struct CommandContext -{ - MQTTPublishInfo_t * pxPublishInfo; - MQTTSubscribeInfo_t * pxSubscribeInfo; - size_t ulSubscriptionCount; - MQTTStatus_t xReturnStatus; - bool xIsComplete; - - /* The below fields are specific to this FreeRTOS implementation. */ - TaskHandle_t xTaskToNotify; - uint32_t ulNotificationBit; - QueueHandle_t pxResponseQueue; -} CommandContext_t; - -/** - * @brief Callback function called when a command completes. - */ -typedef void (* CommandCallback_t )( CommandContext_t * ); - -/** - * @brief A command for interacting with the MQTT API. - */ -typedef struct Command -{ - CommandType_t xCommandType; - CommandContext_t * pxCmdContext; - CommandCallback_t vCallback; -} Command_t; - -/** - * @brief Information for a pending MQTT ack packet expected by the demo. - */ -typedef struct ackInfo -{ - uint16_t usPacketId; - Command_t xOriginalCommand; -} AckInfo_t; - -/** - * @brief An element in the list of subscriptions maintained in the demo. - * - * @note This demo allows multiple tasks to subscribe to the same topic. - * In this case, another element is added to the subscription list, differing - * in the destination response queue. - */ -typedef struct subscriptionElement -{ - char pcSubscriptionFilter[ mqttexampleDEMO_BUFFER_SIZE ]; - uint16_t usFilterLength; - QueueHandle_t pxResponseQueue; -} SubscriptionElement_t; - -/** - * @brief An element for a task's response queue for received publishes. - * - * @note Since elements are copied to queues, this struct needs to hold - * buffers for the payload and topic of incoming publishes, as the original - * pointers are out of scope. When processing a publish from this struct, - * the `pcTopicNameBuf` and `pcPayloadBuf` pointers need to be set to point to the - * static buffers in this struct. - */ -typedef struct publishElement -{ - MQTTPublishInfo_t xPublishInfo; - uint8_t pcPayloadBuf[ mqttexampleDEMO_BUFFER_SIZE ]; - uint8_t pcTopicNameBuf[ mqttexampleDEMO_BUFFER_SIZE ]; -} PublishElement_t; - -/*-----------------------------------------------------------*/ - -/** - * @brief Each compilation unit that consumes the NetworkContext must define it. - * It should contain a single pointer to the type of your desired transport. - * When using multiple transports in the same compilation unit, define this pointer as void *. - * - * @note Transport stacks are defined in FreeRTOS-Plus/Source/Application-Protocols/network_transport. - */ -struct NetworkContext -{ - #if defined( democonfigUSE_TLS ) && ( democonfigUSE_TLS == 1 ) - TlsTransportParams_t * pParams; - #else - PlaintextTransportParams_t * pParams; - #endif -}; - -/*-----------------------------------------------------------*/ - -/** - * @brief Initializes an MQTT context, including transport interface and - * network buffer. - * - * @param[in] pxMQTTContext MQTT Context to initialize. - * @param[in] pxNetworkContext Network context. - * - * @return `MQTTSuccess` if the initialization succeeds, else `MQTTBadParameter`. - */ -static MQTTStatus_t prvMQTTInit( MQTTContext_t * pxMQTTContext, - NetworkContext_t * pxNetworkContext ); - -/** - * @brief Sends an MQTT Connect packet over the already connected TCP socket. - * - * @param[in] pxMQTTContext MQTT context pointer. - * @param[in] xCleanSession If a clean session should be established. - * - * @return `MQTTSuccess` if connection succeeds, else appropriate error code - * from MQTT_Connect. - */ -static MQTTStatus_t prvMQTTConnect( MQTTContext_t * pxMQTTContext, - bool xCleanSession ); - -/** - * @brief Resume a session by resending publishes if a session is present in - * the broker, or reestablish subscriptions if not. - * - * @param[in] xSessionPresent The session present flag from the broker. - * - * @return `MQTTSuccess` if it succeeds in resending publishes, else an - * appropriate error code from `MQTT_Publish()` - */ -static MQTTStatus_t prvResumeSession( bool xSessionPresent ); - - -/** - * @brief Form a TCP connection to a server. - * - * @param[in] pxNetworkContext Network context. - * - * @return `pdPASS` if connection succeeds, else `pdFAIL`. - */ -static BaseType_t prvSocketConnect( NetworkContext_t * pxNetworkContext ); - -/** - * @brief Disconnect a TCP connection. - * - * @param[in] pxNetworkContext Network context. - * - * @return `pdPASS` if disconnect succeeds, else `pdFAIL`. - */ -static BaseType_t prvSocketDisconnect( NetworkContext_t * pxNetworkContext ); - -/** - * @brief Callback for adding a process loop call to a command queue, when data - * is available on a socket. - * - * @param[in] pxSocket Socket with data, unused. - */ -static void prvMQTTClientSocketWakeupCallback( Socket_t pxSocket ); - -/** - * @brief Track an operation by adding it to a list, indicating it is anticipating - * an acknowledgment. - * - * @param[in] usPacketId Packet ID of pending ack. - * @param[in] pxCommand Copy of command that is expecting an ack. - * - * @return `true` if the operation was added; else `false` - */ -static bool prvAddAwaitingOperation( uint16_t usPacketId, - Command_t * pxCommand ); - -/** - * @brief Retrieve an operation from the list of pending acks, and optionally - * remove it. - * - * @param[in] usPacketId Packet ID of incoming ack. - * @param[in] xRemove Flag indicating if the operation should be removed. - * - * @return Stored information about the operation awaiting the ack. - */ -static AckInfo_t prvGetAwaitingOperation( uint16_t usPacketId, - bool xRemove ); - -/** - * @brief Add a subscription to the subscription list. - * - * @note Multiple tasks can be subscribed to the same topic. However, a single - * task may only subscribe to the same topic filter once. - * - * @param[in] pcTopicFilter Topic filter of subscription. - * @param[in] usTopicFilterLength Length of topic filter. - * @param[in] pxQueue Response queue in which to enqueue received publishes. - */ -static void prvAddSubscription( const char * pcTopicFilter, - uint16_t usTopicFilterLength, - QueueHandle_t pxQueue ); - -/** - * @brief Remove a subscription from the subscription list. - * - * @note If the topic filter exists multiple times in the subscription list, - * then every instance of the subscription will be removed. - * - * @param[in] pcTopicFilter Topic filter of subscription. - * @param[in] usTopicFilterLength Length of topic filter. - */ -static void prvRemoveSubscription( const char * pcTopicFilter, - uint16_t usTopicFilterLength ); - -/** - * @brief Populate the parameters of a #Command_t - * - * @param[in] xCommandType Type of command. - * @param[in] pxContext Context and necessary structs for command. - * @param[in] xCallback Callback for when command completes. - * @param[out] pxCommand Pointer to initialized command. - * - * @return `true` if all necessary structs for the command exist in pxContext, - * else `false` - */ -static bool prvCreateCommand( CommandType_t xCommandType, - CommandContext_t * pxContext, - CommandCallback_t xCallback, - Command_t * pxCommand ); - -/** - * @brief Add a command to the global command queue. - * - * @param[in] pxCommand Pointer to command to copy to queue. - * - * @return true if the command was added to the queue, else false. - */ -static BaseType_t prvAddCommandToQueue( Command_t * pxCommand ); - -/** - * @brief Copy an incoming publish to a response queue. - * - * @param[in] pxPublishInfo Info of incoming publish. - * @param[in] pxResponseQueue Queue to which the publish is copied. - * - * @return true if the publish was copied to the queue, else false. - */ -static BaseType_t prvCopyPublishToQueue( MQTTPublishInfo_t * pxPublishInfo, - QueueHandle_t pxResponseQueue ); - -/** - * @brief Process a #Command_t. - * - * @note This demo does not check existing subscriptions before sending a - * SUBSCRIBE or UNSUBSCRIBE packet. If a subscription already exists, then - * a SUBSCRIBE packet will be sent anyway, and if multiple tasks are subscribed - * to a topic filter, then they will all be unsubscribed after an UNSUBSCRIBE. - * - * @param[in] pxCommand Pointer to command to process. - * - * @return status of MQTT library API call. - */ -static MQTTStatus_t prvProcessCommand( Command_t * pxCommand ); - -/** - * @brief Dispatch an incoming publish to the appropriate response queues. - * - * @param[in] pxPublishInfo Incoming publish information. - */ -static void prvHandleIncomingPublish( MQTTPublishInfo_t * pxPublishInfo ); - -/** - * @brief Add or delete subscription information from a SUBACK or UNSUBACK. - * - * @param[in] pxPacketInfo Pointer to incoming packet. - * @param[in] pxDeserializedInfo Pointer to deserialized information from - * the incoming packet. - * @param[in] pxAckInfo Pointer to stored information for the original subscribe - * or unsubscribe operation resulting in the received packet. - * @param[in] ucPacketType The type of the incoming packet, either SUBACK or UNSUBACK. - */ -static void prvHandleSubscriptionAcks( MQTTPacketInfo_t * pxPacketInfo, - MQTTDeserializedInfo_t * pxDeserializedInfo, - AckInfo_t * pxAckInfo, - uint8_t ucPacketType ); - -/** - * @brief Dispatch incoming publishes and acks to response queues and - * command callbacks. - * - * @param[in] pMqttContext MQTT Context - * @param[in] pPacketInfo Pointer to incoming packet. - * @param[in] pDeserializedInfo Pointer to deserialized information from - * the incoming packet. - */ -static void prvEventCallback( MQTTContext_t * pMqttContext, - MQTTPacketInfo_t * pPacketInfo, - MQTTDeserializedInfo_t * pDeserializedInfo ); - -/** - * @brief Process commands from the command queue in a loop. - * - * This demo requires a process loop command to be enqueued before calling this - * function, and will re-add a process loop command every time one is processed. - * This demo will exit the loop after receiving an unsubscribe operation. - */ -static void prvCommandLoop( void ); - -/** - * @brief Common callback for commands in this demo. - * - * This callback marks the command as complete and notifies the calling task. - * - * @param[in] pxContext Context of the initial command. - */ -static void prvCommandCallback( CommandContext_t * pxContext ); - -/** - * @brief Wait for a task notification in a loop. - * - * @param[in] pulNotification pointer holding notification value. - * @param[in] ulExpectedBits Bits to wait for. - * @param[in] xClearBits If bits should be cleared. - * - * @return `true` if notification received without exceeding the timeout, - * else `false`. - */ -static bool prvNotificationWaitLoop( uint32_t * pulNotification, - uint32_t ulExpectedBits, - bool xClearBits ); - -/** - * @brief A task used to create publish operations, waiting for each to complete - * before creating the next one. - * - * This task creates a series of publish operations to push to a command queue, - * which are in turn executed serially by the main task. This task demonstrates - * synchronous execution, waiting for each publish delivery to complete before - * proceeding. - * - * @param[in] pvParameters Parameters as passed at the time of task creation. Not - * used in this example. - */ -void prvSyncPublishTask( void * pvParameters ); - -/** - * @brief A task used to create publish operations, without waiting for - * completion between each new publish. - * - * This task creates publish operations asynchronously, meaning it will not - * wait for a publish to complete before scheduling the next one. Note there - * is no difference in the actual publish operation, only in the behavior of - * this task. - * - * @param[in] pvParameters Parameters as passed at the time of task creation. Not - * used in this example. - */ -void prvAsyncPublishTask( void * pvParameters ); - -/** - * @brief The task used to wait for incoming publishes. - * - * This task subscribes to a topic filter that matches the topics on which the - * publisher task publishes. It then enters a loop waiting for publish messages - * from a message queue, to which the main loop will be pushing when publishes - * are received from the broker. After `mqttexamplePUBLISH_COUNT` messages have been received, - * this task will unsubscribe, and then tell the main loop to terminate. - * - * @param[in] pvParameters Parameters as passed at the time of task creation. Not - * used in this example. - */ -void prvSubscribeTask( void * pvParameters ); - -/** - * @brief The main task used in the MQTT demo. - * - * After creating the publisher and subscriber tasks, this task will enter a - * loop, processing commands from the command queue and calling the MQTT API. - * After the termination command is received on the command queue, the task - * will break from the loop. - * - * @param[in] pvParameters Parameters as passed at the time of task creation. Not - * used in this example. - */ -static void prvMQTTDemoTask( void * pvParameters ); - -/** - * @brief The timer query function provided to the MQTT context. - * - * @return Time in milliseconds. - */ -static uint32_t prvGetTimeMs( void ); - -/** - * @brief Cleans any persistent sessions that may already exist - * This demo uses a persistent session that can be re-connected if disconnected. - * Clean any lingering sessions that may exist from previous executions of the - * demo. - */ -static void prvCleanExistingPersistentSession( void ); - -/*-----------------------------------------------------------*/ - -/** - * @brief Global MQTT context. - */ -static MQTTContext_t globalMqttContext; - -/** - * @brief Global Network context. - */ -static NetworkContext_t xNetworkContext; - -#if defined( democonfigUSE_TLS ) && ( democonfigUSE_TLS == 1 ) - -/** - * @brief The parameters for the network context using a TLS channel. - */ - static TlsTransportParams_t xTlsTransportParams; -#else - -/** - * @brief The parameters for the network context using a non-encrypted channel. - */ - static PlaintextTransportParams_t xPlaintextTransportParams; -#endif - -/** - * @brief List of operations that are awaiting an ack from the broker. - */ -static AckInfo_t pxPendingAcks[ mqttexamplePENDING_ACKS_MAX_SIZE ]; - -/** - * @brief List of active subscriptions. - */ -static SubscriptionElement_t pxSubscriptions[ mqttexampleSUBSCRIPTIONS_MAX_COUNT ]; - -/** - * @brief Array of subscriptions to resubscribe to. - */ -static MQTTSubscribeInfo_t pxResendSubscriptions[ mqttexampleSUBSCRIPTIONS_MAX_COUNT ]; - -/** - * @brief Context to use for a resubscription after a reconnect. - */ -static CommandContext_t xResubscribeContext; - -/** - * @brief Queue for main task to handle MQTT operations. - */ -static QueueHandle_t xCommandQueue; - -/** - * @brief Response queue for prvSubscribeTask. - */ -static QueueHandle_t xSubscriberResponseQueue; - -/** - * @brief Response queue for publishes received on non-subscribed topics. - */ -static QueueHandle_t xDefaultResponseQueue; - -/** - * @brief Handle for prvMQTTDemoTask. - */ -static TaskHandle_t xMainTask; - -/** - * @brief Handle for prvSyncPublishTask. - */ -static TaskHandle_t xSyncPublisherTask; - -/** - * @brief Handle of prvAsyncPublishTask. - */ -static TaskHandle_t xAsyncPublisherTask; - -/** - * @brief Handle for prvSubscribeTask. - */ -static TaskHandle_t xSubscribeTask; - -/** - * @brief The network buffer must remain valid for the lifetime of the MQTT context. - */ -static uint8_t pcNetworkBuffer[ mqttexampleNETWORK_BUFFER_SIZE ]; - -/** - * @brief Global entry time into the application to use as a reference timestamp - * in the #prvGetTimeMs function. #prvGetTimeMs will always return the difference - * between the current time and the global entry time. This will reduce the chances - * of overflow for the 32 bit unsigned integer used for holding the timestamp. - */ -static uint32_t ulGlobalEntryTimeMs; - -/*-----------------------------------------------------------*/ - -/* - * @brief Create the task that demonstrates the MQTT Connection sharing demo. - */ -void vStartSimpleMQTTDemo( void ) -{ - /* This example uses one application task to process the command queue for - * MQTT operations, and creates additional tasks to add operations to that - * queue. */ - xTaskCreate( prvMQTTDemoTask, /* Function that implements the task. */ - "DemoTask", /* Text name for the task - only used for debugging. */ - democonfigDEMO_STACKSIZE, /* Size of stack (in words, not bytes) to allocate for the task. */ - NULL, /* Task parameter - not used in this case. */ - tskIDLE_PRIORITY + 1, /* Task priority, must be between 0 and configMAX_PRIORITIES - 1. */ - &xMainTask ); /* Used to pass out a handle to the created task. */ -} -/*-----------------------------------------------------------*/ - -static MQTTStatus_t prvMQTTInit( MQTTContext_t * pxMQTTContext, - NetworkContext_t * pxNetworkContext ) -{ - TransportInterface_t xTransport; - MQTTFixedBuffer_t xNetworkBuffer; - - /* Fill the values for network buffer. */ - xNetworkBuffer.pBuffer = pcNetworkBuffer; - xNetworkBuffer.size = mqttexampleNETWORK_BUFFER_SIZE; - - /* Fill in Transport Interface send and receive function pointers. */ - xTransport.pNetworkContext = pxNetworkContext; - #if defined( democonfigUSE_TLS ) && ( democonfigUSE_TLS == 1 ) - xTransport.send = TLS_FreeRTOS_send; - xTransport.recv = TLS_FreeRTOS_recv; - #else - xTransport.send = Plaintext_FreeRTOS_send; - xTransport.recv = Plaintext_FreeRTOS_recv; - #endif - - /* Initialize MQTT library. */ - return MQTT_Init( pxMQTTContext, &xTransport, prvGetTimeMs, prvEventCallback, &xNetworkBuffer ); -} - -static MQTTStatus_t prvMQTTConnect( MQTTContext_t * pxMQTTContext, - bool xCleanSession ) -{ - MQTTStatus_t xResult; - MQTTConnectInfo_t xConnectInfo; - bool xSessionPresent = false; - - /* Many fields are not used in this demo so start with everything at 0. */ - memset( &xConnectInfo, 0x00, sizeof( xConnectInfo ) ); - - /* Start with a clean session i.e. direct the MQTT broker to discard any - * previous session data. Also, establishing a connection with clean session - * will ensure that the broker does not store any data when this client - * gets disconnected. */ - xConnectInfo.cleanSession = xCleanSession; - - /* The client identifier is used to uniquely identify this MQTT client to - * the MQTT broker. In a production device the identifier can be something - * unique, such as a device serial number. */ - xConnectInfo.pClientIdentifier = democonfigCLIENT_IDENTIFIER; - xConnectInfo.clientIdentifierLength = ( uint16_t ) strlen( democonfigCLIENT_IDENTIFIER ); - - /* Set MQTT keep-alive period. It is the responsibility of the application - * to ensure that the interval between Control Packets being sent does not - * exceed the Keep Alive value. In the absence of sending any other Control - * Packets, the Client MUST send a PINGREQ Packet. */ - xConnectInfo.keepAliveSeconds = mqttexampleKEEP_ALIVE_INTERVAL_SECONDS; - - /* Append metrics when connecting to the AWS IoT Core broker. */ - #ifdef democonfigUSE_AWS_IOT_CORE_BROKER - #ifdef democonfigCLIENT_USERNAME - xConnectInfo.pUserName = CLIENT_USERNAME_WITH_METRICS; - xConnectInfo.userNameLength = ( uint16_t ) strlen( CLIENT_USERNAME_WITH_METRICS ); - xConnectInfo.pPassword = democonfigCLIENT_PASSWORD; - xConnectInfo.passwordLength = ( uint16_t ) strlen( democonfigCLIENT_PASSWORD ); - #else - xConnectInfo.pUserName = AWS_IOT_METRICS_STRING; - xConnectInfo.userNameLength = AWS_IOT_METRICS_STRING_LENGTH; - /* Password for authentication is not used. */ - xConnectInfo.pPassword = NULL; - xConnectInfo.passwordLength = 0U; - #endif - #else /* ifdef democonfigUSE_AWS_IOT_CORE_BROKER */ - #ifdef democonfigCLIENT_USERNAME - xConnectInfo.pUserName = democonfigCLIENT_USERNAME; - xConnectInfo.userNameLength = ( uint16_t ) strlen( democonfigCLIENT_USERNAME ); - xConnectInfo.pPassword = democonfigCLIENT_PASSWORD; - xConnectInfo.passwordLength = ( uint16_t ) strlen( democonfigCLIENT_PASSWORD ); - #endif /* ifdef democonfigCLIENT_USERNAME */ - #endif /* ifdef democonfigUSE_AWS_IOT_CORE_BROKER */ - - /* Send MQTT CONNECT packet to broker. MQTT's Last Will and Testament feature - * is not used in this demo, so it is passed as NULL. */ - xResult = MQTT_Connect( pxMQTTContext, - &xConnectInfo, - NULL, - mqttexampleCONNACK_RECV_TIMEOUT_MS, - &xSessionPresent ); - - LogInfo( ( "Session present: %d\n", xSessionPresent ) ); - - /* Resume a session if desired. */ - if( ( xResult == MQTTSuccess ) && !xCleanSession ) - { - xResult = prvResumeSession( xSessionPresent ); - } - - return xResult; -} - -/*-----------------------------------------------------------*/ - -static MQTTStatus_t prvResumeSession( bool xSessionPresent ) -{ - MQTTStatus_t xResult = MQTTSuccess; - - /* Resend publishes if session is present. NOTE: It's possible that some - * of the operations that were in progress during the network interruption - * were subscribes. In that case, we would want to mark those operations - * as completing with error and remove them from the list of operations, so - * that the calling task can try subscribing again. We do not handle that - * case in this demo for simplicity, since only one subscription packet is - * sent per iteration of this demo. */ - if( xSessionPresent ) - { - MQTTStateCursor_t cursor = MQTT_STATE_CURSOR_INITIALIZER; - uint16_t packetId = MQTT_PACKET_ID_INVALID; - AckInfo_t xFoundAck; - - packetId = MQTT_PublishToResend( &globalMqttContext, &cursor ); - - while( packetId != MQTT_PACKET_ID_INVALID ) - { - /* Retrieve the operation but do not remove it from the list. */ - xFoundAck = prvGetAwaitingOperation( packetId, false ); - - if( xFoundAck.usPacketId == packetId ) - { - /* Set the DUP flag. */ - xFoundAck.xOriginalCommand.pxCmdContext->pxPublishInfo->dup = true; - xResult = MQTT_Publish( &globalMqttContext, xFoundAck.xOriginalCommand.pxCmdContext->pxPublishInfo, packetId ); - - if( xResult != MQTTSuccess ) - { - LogError( ( "Error in resending publishes. Error code=%s\n", MQTT_Status_strerror( xResult ) ) ); - break; - } - } - - packetId = MQTT_PublishToResend( &globalMqttContext, &cursor ); - } - } - - /* If we wanted to resume a session but none existed with the broker, we - * should mark all in progress operations as errors so that the tasks that - * created them can try again. Also, we will resubscribe to the filters in - * the subscription list, so tasks do not unexpectedly lose their subscriptions. */ - else - { - int32_t i = 0, j = 0; - Command_t xNewCommand; - bool xCommandCreated = false; - BaseType_t xCommandAdded; - - /* We have a clean session, so clear all operations pending acknowledgments. */ - for( i = 0; i < mqttexamplePENDING_ACKS_MAX_SIZE; i++ ) - { - if( pxPendingAcks[ i ].usPacketId != MQTT_PACKET_ID_INVALID ) - { - if( pxPendingAcks[ i ].xOriginalCommand.vCallback != NULL ) - { - /* Bad response to indicate network error. */ - pxPendingAcks[ i ].xOriginalCommand.pxCmdContext->xReturnStatus = MQTTBadResponse; - pxPendingAcks[ i ].xOriginalCommand.vCallback( pxPendingAcks[ i ].xOriginalCommand.pxCmdContext ); - } - - /* Now remove it from the list. */ - prvGetAwaitingOperation( pxPendingAcks[ i ].usPacketId, true ); - } - } - - /* Populate the array of MQTTSubscribeInfo_t. It's possible there may be - * repeated subscriptions in the list. This is fine, since clients - * are able to subscribe to a topic with an existing subscription. */ - for( i = 0; i < mqttexampleSUBSCRIPTIONS_MAX_COUNT; i++ ) - { - if( pxSubscriptions[ i ].usFilterLength != 0 ) - { - pxResendSubscriptions[ j ].pTopicFilter = pxSubscriptions[ i ].pcSubscriptionFilter; - pxResendSubscriptions[ j ].topicFilterLength = pxSubscriptions[ i ].usFilterLength; - pxResendSubscriptions[ j ].qos = MQTTQoS1; - j++; - } - } - - /* Resubscribe if needed. */ - if( j > 0 ) - { - memset( ( void * ) &xResubscribeContext, 0x00, sizeof( xResubscribeContext ) ); - xResubscribeContext.pxSubscribeInfo = pxResendSubscriptions; - xResubscribeContext.ulSubscriptionCount = j; - /* Set to NULL so existing queues will not be overwritten. */ - xResubscribeContext.pxResponseQueue = NULL; - xResubscribeContext.xTaskToNotify = NULL; - xCommandCreated = prvCreateCommand( SUBSCRIBE, &xResubscribeContext, prvCommandCallback, &xNewCommand ); - configASSERT( xCommandCreated == true ); - /* Send to the front of the queue so we will resubscribe as soon as possible. */ - xCommandAdded = xQueueSendToFront( xCommandQueue, &xNewCommand, mqttexampleDEMO_TICKS_TO_WAIT ); - configASSERT( xCommandAdded == pdTRUE ); - } - } - - return xResult; -} - -/*-----------------------------------------------------------*/ - -static BaseType_t prvSocketConnect( NetworkContext_t * pxNetworkContext ) -{ - BaseType_t xConnected = pdFAIL; - BackoffAlgorithmStatus_t xBackoffAlgStatus = BackoffAlgorithmSuccess; - BackoffAlgorithmContext_t xReconnectParams; - uint16_t usNextRetryBackOff = 0U; - - configASSERT( pxNetworkContext != NULL && pxNetworkContext->pParams != NULL ); - - #if defined( democonfigUSE_TLS ) && ( democonfigUSE_TLS == 1 ) - TlsTransportStatus_t xNetworkStatus = TLS_TRANSPORT_CONNECT_FAILURE; - NetworkCredentials_t xNetworkCredentials = { 0 }; - - #ifdef democonfigUSE_AWS_IOT_CORE_BROKER - - /* ALPN protocols must be a NULL-terminated list of strings. Therefore, - * the first entry will contain the actual ALPN protocol string while the - * second entry must remain NULL. */ - char * pcAlpnProtocols[] = { NULL, NULL }; - - /* The ALPN string changes depending on whether username/password authentication is used. */ - #ifdef democonfigCLIENT_USERNAME - pcAlpnProtocols[ 0 ] = AWS_IOT_CUSTOM_AUTH_ALPN; - #else - pcAlpnProtocols[ 0 ] = AWS_IOT_MQTT_ALPN; - #endif - xNetworkCredentials.pAlpnProtos = pcAlpnProtocols; - #endif /* ifdef democonfigUSE_AWS_IOT_CORE_BROKER */ - - /* Set the credentials for establishing a TLS connection. */ - xNetworkCredentials.pRootCa = ( const unsigned char * ) democonfigROOT_CA_PEM; - xNetworkCredentials.rootCaSize = sizeof( democonfigROOT_CA_PEM ); - #ifdef democonfigCLIENT_CERTIFICATE_PEM - xNetworkCredentials.pClientCert = ( const unsigned char * ) democonfigCLIENT_CERTIFICATE_PEM; - xNetworkCredentials.clientCertSize = sizeof( democonfigCLIENT_CERTIFICATE_PEM ); - xNetworkCredentials.pPrivateKey = ( const unsigned char * ) democonfigCLIENT_PRIVATE_KEY_PEM; - xNetworkCredentials.privateKeySize = sizeof( democonfigCLIENT_PRIVATE_KEY_PEM ); - #endif - xNetworkCredentials.disableSni = democonfigDISABLE_SNI; - #else /* if defined( democonfigUSE_TLS ) && ( democonfigUSE_TLS == 1 ) */ - PlaintextTransportStatus_t xNetworkStatus = PLAINTEXT_TRANSPORT_CONNECT_FAILURE; - #endif /* if defined( democonfigUSE_TLS ) && ( democonfigUSE_TLS == 1 ) */ - - /* We will use a retry mechanism with an exponential backoff mechanism and - * jitter. We initialize the context required for backoff period calculation here. */ - BackoffAlgorithm_InitializeParams( &xReconnectParams, - mqttexampleRETRY_BACKOFF_BASE_MS, - mqttexampleRETRY_MAX_BACKOFF_DELAY_MS, - mqttexampleRETRY_MAX_ATTEMPTS ); - - /* Attempt to connect to MQTT broker. If connection fails, retry after a - * timeout. Timeout value will exponentially increase until the maximum - * number of attempts are reached. - */ - do - { - /* Establish a TCP connection with the MQTT broker. This example connects to - * the MQTT broker as specified in democonfigMQTT_BROKER_ENDPOINT and - * democonfigMQTT_BROKER_PORT at the top of this file. */ - #if defined( democonfigUSE_TLS ) && ( democonfigUSE_TLS == 1 ) - LogInfo( ( "Creating a TLS connection to %s:%d.", - democonfigMQTT_BROKER_ENDPOINT, - democonfigMQTT_BROKER_PORT ) ); - xNetworkStatus = TLS_FreeRTOS_Connect( pxNetworkContext, - democonfigMQTT_BROKER_ENDPOINT, - democonfigMQTT_BROKER_PORT, - &xNetworkCredentials, - mqttexampleTRANSPORT_SEND_RECV_TIMEOUT_MS, - mqttexampleTRANSPORT_SEND_RECV_TIMEOUT_MS ); - xConnected = ( xNetworkStatus == TLS_TRANSPORT_SUCCESS ) ? pdPASS : pdFAIL; - #else /* if defined( democonfigUSE_TLS ) && ( democonfigUSE_TLS == 1 ) */ - LogInfo( ( "Creating a TCP connection to %s:%d.", - democonfigMQTT_BROKER_ENDPOINT, - democonfigMQTT_BROKER_PORT ) ); - xNetworkStatus = Plaintext_FreeRTOS_Connect( pxNetworkContext, - democonfigMQTT_BROKER_ENDPOINT, - democonfigMQTT_BROKER_PORT, - mqttexampleTRANSPORT_SEND_RECV_TIMEOUT_MS, - mqttexampleTRANSPORT_SEND_RECV_TIMEOUT_MS ); - xConnected = ( xNetworkStatus == PLAINTEXT_TRANSPORT_SUCCESS ) ? pdPASS : pdFAIL; - #endif /* if defined( democonfigUSE_TLS ) && ( democonfigUSE_TLS == 1 ) */ - - if( !xConnected ) - { - /* Generate a random number and calculate backoff value (in milliseconds) for - * the next connection retry. - * Note: It is recommended to seed the random number generator with a device-specific - * entropy source so that possibility of multiple devices retrying failed network operations - * at similar intervals can be avoided. */ - xBackoffAlgStatus = BackoffAlgorithm_GetNextBackoff( &xReconnectParams, uxRand(), &usNextRetryBackOff ); - - if( xBackoffAlgStatus == BackoffAlgorithmRetriesExhausted ) - { - LogError( ( "Connection to the broker failed, all attempts exhausted." ) ); - } - else if( xBackoffAlgStatus == BackoffAlgorithmSuccess ) - { - LogWarn( ( "Connection to the broker failed. " - "Retrying connection with backoff and jitter." ) ); - vTaskDelay( pdMS_TO_TICKS( usNextRetryBackOff ) ); - } - } - } while( ( xConnected != pdPASS ) && ( xBackoffAlgStatus == BackoffAlgorithmSuccess ) ); - - /* Set the socket wakeup callback. */ - if( xConnected ) - { - ( void ) FreeRTOS_setsockopt( pxNetworkContext->pParams->tcpSocket, - 0, /* Level - Unused. */ - FREERTOS_SO_WAKEUP_CALLBACK, - ( void * ) prvMQTTClientSocketWakeupCallback, - sizeof( &( prvMQTTClientSocketWakeupCallback ) ) ); - } - - return xConnected; -} - -/*-----------------------------------------------------------*/ - -static BaseType_t prvSocketDisconnect( NetworkContext_t * pxNetworkContext ) -{ - BaseType_t xDisconnected = pdFAIL; - - configASSERT( pxNetworkContext != NULL && pxNetworkContext->pParams != NULL ); - - /* Set the wakeup callback to NULL since the socket will disconnect. */ - ( void ) FreeRTOS_setsockopt( pxNetworkContext->pParams->tcpSocket, - 0, /* Level - Unused. */ - FREERTOS_SO_WAKEUP_CALLBACK, - ( void * ) NULL, - sizeof( void * ) ); - - #if defined( democonfigUSE_TLS ) && ( democonfigUSE_TLS == 1 ) - LogInfo( ( "Disconnecting TLS connection.\n" ) ); - TLS_FreeRTOS_Disconnect( pxNetworkContext ); - xDisconnected = pdPASS; - #else - LogInfo( ( "Disconnecting TCP connection.\n" ) ); - PlaintextTransportStatus_t xNetworkStatus = PLAINTEXT_TRANSPORT_CONNECT_FAILURE; - xNetworkStatus = Plaintext_FreeRTOS_Disconnect( pxNetworkContext ); - xDisconnected = ( xNetworkStatus == PLAINTEXT_TRANSPORT_SUCCESS ) ? pdPASS : pdFAIL; - #endif - return xDisconnected; -} - -/*-----------------------------------------------------------*/ - -static void prvMQTTClientSocketWakeupCallback( Socket_t pxSocket ) -{ - BaseType_t xResult; - Command_t xCommand; - - /* Just to avoid compiler warnings. The socket is not used but the function - * prototype cannot be changed because this is a callback function. */ - ( void ) pxSocket; - - configASSERT( xCommandQueue ); - - /* A socket used by the MQTT task may need attention. Send an event - * to the MQTT task to make sure the task is not blocked on xCommandQueue. */ - if( uxQueueMessagesWaiting( xCommandQueue ) == ( UBaseType_t ) 0 ) - { - prvCreateCommand( PROCESSLOOP, NULL, NULL, &xCommand ); - xResult = prvAddCommandToQueue( &xCommand ); - configASSERT( xResult == pdTRUE ); - } -} - -/*-----------------------------------------------------------*/ - -static bool prvAddAwaitingOperation( uint16_t usPacketId, - Command_t * pxCommand ) -{ - int32_t i = 0; - bool xAckAdded = false; - - for( i = 0; i < mqttexamplePENDING_ACKS_MAX_SIZE; i++ ) - { - if( pxPendingAcks[ i ].usPacketId == MQTT_PACKET_ID_INVALID ) - { - pxPendingAcks[ i ].usPacketId = usPacketId; - pxPendingAcks[ i ].xOriginalCommand = *pxCommand; - xAckAdded = true; - break; - } - } - - return xAckAdded; -} - -/*-----------------------------------------------------------*/ - -static AckInfo_t prvGetAwaitingOperation( uint16_t usPacketId, - bool xRemove ) -{ - int32_t i = 0; - AckInfo_t xFoundAck = { 0 }; - - for( i = 0; i < mqttexamplePENDING_ACKS_MAX_SIZE; i++ ) - { - if( pxPendingAcks[ i ].usPacketId == usPacketId ) - { - xFoundAck = pxPendingAcks[ i ]; - - if( xRemove ) - { - pxPendingAcks[ i ].usPacketId = MQTT_PACKET_ID_INVALID; - memset( &( pxPendingAcks[ i ].xOriginalCommand ), 0x00, sizeof( Command_t ) ); - } - - break; - } - } - - if( xFoundAck.usPacketId == MQTT_PACKET_ID_INVALID ) - { - LogError( ( "No ack found for packet id %u.\n", usPacketId ) ); - } - - return xFoundAck; -} - -/*-----------------------------------------------------------*/ - -static void prvAddSubscription( const char * pcTopicFilter, - uint16_t usTopicFilterLength, - QueueHandle_t pxQueue ) -{ - int32_t i = 0, ulAvailableIndex = mqttexampleSUBSCRIPTIONS_MAX_COUNT; - - /* Start at end of array, so that we will insert at the first available index. */ - for( i = mqttexampleSUBSCRIPTIONS_MAX_COUNT - 1; i >= 0; i-- ) - { - if( pxSubscriptions[ i ].usFilterLength == 0 ) - { - ulAvailableIndex = i; - } - else if( ( pxSubscriptions[ i ].usFilterLength == usTopicFilterLength ) && - ( strncmp( pcTopicFilter, pxSubscriptions[ i ].pcSubscriptionFilter, usTopicFilterLength ) == 0 ) ) - { - /* If a subscription already exists, don't do anything. */ - if( pxSubscriptions[ i ].pxResponseQueue == pxQueue ) - { - LogWarn( ( "Subscription already exists.\n" ) ); - ulAvailableIndex = mqttexampleSUBSCRIPTIONS_MAX_COUNT; - break; - } - } - } - - if( ( ulAvailableIndex < mqttexampleSUBSCRIPTIONS_MAX_COUNT ) && ( pxQueue != NULL ) ) - { - pxSubscriptions[ ulAvailableIndex ].usFilterLength = usTopicFilterLength; - pxSubscriptions[ ulAvailableIndex ].pxResponseQueue = pxQueue; - memcpy( pxSubscriptions[ ulAvailableIndex ].pcSubscriptionFilter, pcTopicFilter, usTopicFilterLength ); - } -} - -/*-----------------------------------------------------------*/ - -static void prvRemoveSubscription( const char * pcTopicFilter, - uint16_t usTopicFilterLength ) -{ - int32_t i = 0; - - for( i = 0; i < mqttexampleSUBSCRIPTIONS_MAX_COUNT; i++ ) - { - if( pxSubscriptions[ i ].usFilterLength == usTopicFilterLength ) - { - if( strncmp( pxSubscriptions[ i ].pcSubscriptionFilter, pcTopicFilter, usTopicFilterLength ) == 0 ) - { - pxSubscriptions[ i ].usFilterLength = 0; - pxSubscriptions[ i ].pxResponseQueue = NULL; - memset( pxSubscriptions[ i ].pcSubscriptionFilter, 0x00, mqttexampleDEMO_BUFFER_SIZE ); - } - } - } -} - -/*-----------------------------------------------------------*/ - -static bool prvCreateCommand( CommandType_t xCommandType, - CommandContext_t * pxContext, - CommandCallback_t xCallback, - Command_t * pxCommand ) -{ - bool xIsValid = true; - - /* Determine if required parameters are present in context. */ - switch( xCommandType ) - { - case PUBLISH: - xIsValid = ( pxContext != NULL ) ? ( pxContext->pxPublishInfo != NULL ) : false; - break; - - case SUBSCRIBE: - case UNSUBSCRIBE: - xIsValid = ( pxContext != NULL ) ? ( ( pxContext->pxSubscribeInfo != NULL ) && ( pxContext->ulSubscriptionCount > 0 ) ) : false; - break; - - default: - /* Other operations don't need a context. */ - break; - } - - if( xIsValid ) - { - memset( pxCommand, 0x00, sizeof( Command_t ) ); - pxCommand->xCommandType = xCommandType; - pxCommand->pxCmdContext = pxContext; - pxCommand->vCallback = xCallback; - } - - return xIsValid; -} - -/*-----------------------------------------------------------*/ - -static BaseType_t prvAddCommandToQueue( Command_t * pxCommand ) -{ - return xQueueSendToBack( xCommandQueue, pxCommand, mqttexampleDEMO_TICKS_TO_WAIT ); -} - -/*-----------------------------------------------------------*/ - -static BaseType_t prvCopyPublishToQueue( MQTTPublishInfo_t * pxPublishInfo, - QueueHandle_t pxResponseQueue ) -{ - PublishElement_t xCopiedPublish; - - memset( &xCopiedPublish, 0x00, sizeof( xCopiedPublish ) ); - memcpy( &( xCopiedPublish.xPublishInfo ), pxPublishInfo, sizeof( MQTTPublishInfo_t ) ); - - /* Since adding an MQTTPublishInfo_t to a queue will not copy its string buffers, - * we need to add buffers to a struct and copy the entire structure. We don't - * need to set xCopiedPublish.xPublishInfo's pointers yet since the actual address - * will change after the struct is copied into the queue. */ - memcpy( xCopiedPublish.pcTopicNameBuf, pxPublishInfo->pTopicName, pxPublishInfo->topicNameLength ); - memcpy( xCopiedPublish.pcPayloadBuf, pxPublishInfo->pPayload, pxPublishInfo->payloadLength ); - - /* Add to response queue. */ - return xQueueSendToBack( pxResponseQueue, ( void * ) &xCopiedPublish, mqttexampleDEMO_TICKS_TO_WAIT ); -} - -/*-----------------------------------------------------------*/ - -static MQTTStatus_t prvProcessCommand( Command_t * pxCommand ) -{ - MQTTStatus_t xStatus = MQTTSuccess; - uint16_t usPacketId = MQTT_PACKET_ID_INVALID; - bool xAddAckToList = false, xAckAdded = false; - BaseType_t xNetworkResult = pdFAIL; - MQTTPublishInfo_t * pxPublishInfo; - MQTTSubscribeInfo_t * pxSubscribeInfo; - - switch( pxCommand->xCommandType ) - { - case PROCESSLOOP: - - /* The process loop will run at the end of every command, so we don't - * need to call it again here. */ - LogDebug( ( "Running Process Loop." ) ); - break; - - case PUBLISH: - configASSERT( pxCommand->pxCmdContext != NULL ); - pxPublishInfo = pxCommand->pxCmdContext->pxPublishInfo; - configASSERT( pxPublishInfo != NULL ); - - if( pxPublishInfo->qos != MQTTQoS0 ) - { - usPacketId = MQTT_GetPacketId( &globalMqttContext ); - } - - LogDebug( ( "Publishing message to %.*s.", ( int ) pxPublishInfo->topicNameLength, pxPublishInfo->pTopicName ) ); - xStatus = MQTT_Publish( &globalMqttContext, pxPublishInfo, usPacketId ); - pxCommand->pxCmdContext->xReturnStatus = xStatus; - - /* Add to pending ack list, or call callback if QoS 0. */ - xAddAckToList = ( pxPublishInfo->qos != MQTTQoS0 ) && ( xStatus == MQTTSuccess ); - break; - - case SUBSCRIBE: - case UNSUBSCRIBE: - configASSERT( pxCommand->pxCmdContext != NULL ); - pxSubscribeInfo = pxCommand->pxCmdContext->pxSubscribeInfo; - configASSERT( pxSubscribeInfo != NULL ); - configASSERT( pxSubscribeInfo->pTopicFilter != NULL ); - usPacketId = MQTT_GetPacketId( &globalMqttContext ); - - if( pxCommand->xCommandType == SUBSCRIBE ) - { - /* Even if some subscriptions already exist in the subscription list, - * it is fine to send another subscription request. A valid use case - * for this is changing the maximum QoS of the subscription. */ - xStatus = MQTT_Subscribe( &globalMqttContext, - pxSubscribeInfo, - pxCommand->pxCmdContext->ulSubscriptionCount, - usPacketId ); - } - else - { - xStatus = MQTT_Unsubscribe( &globalMqttContext, - pxSubscribeInfo, - pxCommand->pxCmdContext->ulSubscriptionCount, - usPacketId ); - } - - pxCommand->pxCmdContext->xReturnStatus = xStatus; - xAddAckToList = ( xStatus == MQTTSuccess ); - break; - - case PING: - xStatus = MQTT_Ping( &globalMqttContext ); - - if( pxCommand->pxCmdContext != NULL ) - { - pxCommand->pxCmdContext->xReturnStatus = xStatus; - } - - break; - - case DISCONNECT: - xStatus = MQTT_Disconnect( &globalMqttContext ); - - if( pxCommand->pxCmdContext != NULL ) - { - pxCommand->pxCmdContext->xReturnStatus = xStatus; - } - - break; - - case RECONNECT: - /* Reconnect TCP. */ - xNetworkResult = prvSocketDisconnect( &xNetworkContext ); - configASSERT( xNetworkResult == pdPASS ); - xNetworkResult = prvSocketConnect( &xNetworkContext ); - configASSERT( xNetworkResult == pdPASS ); - /* MQTT Connect with a persistent session. */ - xStatus = prvMQTTConnect( &globalMqttContext, false ); - break; - - case TERMINATE: - LogInfo( ( "Terminating command loop." ) ); - - default: - break; - } - - if( xAddAckToList ) - { - xAckAdded = prvAddAwaitingOperation( usPacketId, pxCommand ); - - /* Set the return status if no memory was available to store the operation - * information. */ - if( !xAckAdded ) - { - LogError( ( "No memory to wait for acknowledgment for packet %u\n", usPacketId ) ); - - /* All operations that can wait for acks (publish, subscribe, unsubscribe) - * require a context. */ - configASSERT( pxCommand->pxCmdContext != NULL ); - pxCommand->pxCmdContext->xReturnStatus = MQTTNoMemory; - } - } - - if( !xAckAdded ) - { - /* The command is complete, call the callback. */ - if( pxCommand->vCallback != NULL ) - { - pxCommand->vCallback( pxCommand->pxCmdContext ); - } - } - - /* Run a single iteration of the process loop if there were no errors and - * the MQTT connection still exists. */ - if( ( xStatus == MQTTSuccess ) && ( globalMqttContext.connectStatus == MQTTConnected ) ) - { - xStatus = MQTT_ProcessLoop( &globalMqttContext, mqttexamplePROCESS_LOOP_TIMEOUT_MS ); - } - - return xStatus; -} - -/*-----------------------------------------------------------*/ - -static void prvHandleIncomingPublish( MQTTPublishInfo_t * pxPublishInfo ) -{ - bool xIsMatched = false, xRelayedPublish = false; - MQTTStatus_t xStatus; - size_t i; - BaseType_t xPublishCopied = pdFALSE; - - configASSERT( pxPublishInfo != NULL ); - - for( i = 0; i < mqttexampleSUBSCRIPTIONS_MAX_COUNT; i++ ) - { - if( pxSubscriptions[ i ].usFilterLength > 0 ) - { - xStatus = MQTT_MatchTopic( pxPublishInfo->pTopicName, - pxPublishInfo->topicNameLength, - pxSubscriptions[ i ].pcSubscriptionFilter, - pxSubscriptions[ i ].usFilterLength, - &xIsMatched ); - /* The call can't fail if the topic name and filter is valid. */ - configASSERT( xStatus == MQTTSuccess ); - - if( xIsMatched ) - { - LogDebug( ( "Adding publish to response queue for %.*s\n", - pxSubscriptions[ i ].usFilterLength, - pxSubscriptions[ i ].pcSubscriptionFilter ) ); - xPublishCopied = prvCopyPublishToQueue( pxPublishInfo, pxSubscriptions[ i ].pxResponseQueue ); - /* Ensure the publish was copied to the queue. */ - configASSERT( xPublishCopied == pdTRUE ); - xRelayedPublish = true; - } - } - } - - /* It is possible a publish was sent on an unsubscribed topic. This is - * possible on topics reserved by the broker, e.g. those beginning with - * '$'. In this case, we copy the publish to a queue we configured to - * receive these publishes. */ - if( !xRelayedPublish ) - { - LogWarn( ( "Publish received on topic %.*s with no subscription.\n", - pxPublishInfo->topicNameLength, - pxPublishInfo->pTopicName ) ); - xPublishCopied = prvCopyPublishToQueue( pxPublishInfo, xDefaultResponseQueue ); - /* Ensure the publish was copied to the queue. */ - configASSERT( xPublishCopied == pdTRUE ); - } -} - -/*-----------------------------------------------------------*/ - -static void prvHandleSubscriptionAcks( MQTTPacketInfo_t * pxPacketInfo, - MQTTDeserializedInfo_t * pxDeserializedInfo, - AckInfo_t * pxAckInfo, - uint8_t ucPacketType ) -{ - size_t i; - CommandContext_t * pxAckContext = NULL; - CommandCallback_t vAckCallback = NULL; - uint8_t * pcSubackCodes = NULL; - MQTTSubscribeInfo_t * pxSubscribeInfo = NULL; - - configASSERT( pxAckInfo != NULL ); - - pxAckContext = pxAckInfo->xOriginalCommand.pxCmdContext; - vAckCallback = pxAckInfo->xOriginalCommand.vCallback; - pxSubscribeInfo = pxAckContext->pxSubscribeInfo; - pcSubackCodes = pxPacketInfo->pRemainingData + 2U; - - for( i = 0; i < pxAckContext->ulSubscriptionCount; i++ ) - { - if( ucPacketType == MQTT_PACKET_TYPE_SUBACK ) - { - if( pcSubackCodes[ i ] != MQTTSubAckFailure ) - { - LogInfo( ( "Adding subscription to %.*s", - pxSubscribeInfo[ i ].topicFilterLength, - pxSubscribeInfo[ i ].pTopicFilter ) ); - prvAddSubscription( pxSubscribeInfo[ i ].pTopicFilter, - pxSubscribeInfo[ i ].topicFilterLength, - pxAckContext->pxResponseQueue ); - } - else - { - LogError( ( "Subscription to %.*s failed.\n", - pxSubscribeInfo[ i ].topicFilterLength, - pxSubscribeInfo[ i ].pTopicFilter ) ); - } - } - else - { - LogInfo( ( "Removing subscription to %.*s", - pxSubscribeInfo[ i ].topicFilterLength, - pxSubscribeInfo[ i ].pTopicFilter ) ); - prvRemoveSubscription( pxSubscribeInfo[ i ].pTopicFilter, - pxSubscribeInfo[ i ].topicFilterLength ); - } - } - - pxAckContext->xReturnStatus = pxDeserializedInfo->deserializationResult; - - if( vAckCallback != NULL ) - { - vAckCallback( pxAckContext ); - } -} - -/*-----------------------------------------------------------*/ - -static void prvEventCallback( MQTTContext_t * pMqttContext, - MQTTPacketInfo_t * pPacketInfo, - MQTTDeserializedInfo_t * pDeserializedInfo ) -{ - configASSERT( pMqttContext != NULL ); - configASSERT( pPacketInfo != NULL ); - AckInfo_t xAckInfo; - uint16_t packetIdentifier = pDeserializedInfo->packetIdentifier; - CommandContext_t * pxAckContext = NULL; - CommandCallback_t vAckCallback = NULL; - - /* Handle incoming publish. The lower 4 bits of the publish packet - * type is used for the dup, QoS, and retain flags. Hence masking - * out the lower bits to check if the packet is publish. */ - if( ( pPacketInfo->type & 0xF0U ) == MQTT_PACKET_TYPE_PUBLISH ) - { - prvHandleIncomingPublish( pDeserializedInfo->pPublishInfo ); - } - else - { - /* Handle other packets. */ - switch( pPacketInfo->type ) - { - case MQTT_PACKET_TYPE_PUBACK: - case MQTT_PACKET_TYPE_PUBCOMP: - xAckInfo = prvGetAwaitingOperation( packetIdentifier, true ); - - if( xAckInfo.usPacketId == packetIdentifier ) - { - pxAckContext = xAckInfo.xOriginalCommand.pxCmdContext; - vAckCallback = xAckInfo.xOriginalCommand.vCallback; - pxAckContext->xReturnStatus = pDeserializedInfo->deserializationResult; - - if( vAckCallback != NULL ) - { - vAckCallback( pxAckContext ); - } - } - - break; - - case MQTT_PACKET_TYPE_SUBACK: - case MQTT_PACKET_TYPE_UNSUBACK: - xAckInfo = prvGetAwaitingOperation( packetIdentifier, true ); - - if( xAckInfo.usPacketId == packetIdentifier ) - { - prvHandleSubscriptionAcks( pPacketInfo, pDeserializedInfo, &xAckInfo, pPacketInfo->type ); - } - else - { - LogError( ( "No subscription or unsubscribe operation found matching packet id %u.\n", packetIdentifier ) ); - } - - break; - - /* Nothing to do for these packets since they don't indicate command completion. */ - case MQTT_PACKET_TYPE_PUBREC: - case MQTT_PACKET_TYPE_PUBREL: - break; - - case MQTT_PACKET_TYPE_PINGRESP: - - /* Nothing to be done from application as library handles - * PINGRESP with the use of MQTT_ProcessLoop API function. */ - LogWarn( ( "PINGRESP should not be handled by the application " - "callback when using MQTT_ProcessLoop.\n" ) ); - break; - - /* Any other packet type is invalid. */ - default: - LogError( ( "Unknown packet type received:(%02x).\n", - pPacketInfo->type ) ); - } - } -} - -/*-----------------------------------------------------------*/ - -static void prvCommandLoop( void ) -{ - Command_t xCommand; - Command_t xNewCommand; - MQTTStatus_t xStatus = MQTTSuccess; - static int lNumProcessed = 0; - bool xTerminateReceived = false; - BaseType_t xCommandAdded = pdTRUE; - - /* Loop until we receive a terminate command. */ - for( ; ; ) - { - /* If there is no command in the queue, try again. */ - if( xQueueReceive( xCommandQueue, &xCommand, mqttexampleDEMO_TICKS_TO_WAIT ) == pdFALSE ) - { - LogInfo( ( "No commands in the queue. Trying again." ) ); - continue; - } - - xStatus = prvProcessCommand( &xCommand ); - - /* Add connect operation to front of queue if status was not successful. */ - if( xStatus != MQTTSuccess ) - { - LogError( ( "MQTT operation failed with status %s\n", - MQTT_Status_strerror( xStatus ) ) ); - prvCreateCommand( RECONNECT, NULL, NULL, &xNewCommand ); - xCommandAdded = xQueueSendToFront( xCommandQueue, &xNewCommand, mqttexampleDEMO_TICKS_TO_WAIT ); - /* Ensure the command was added to the queue. */ - configASSERT( xCommandAdded == pdTRUE ); - } - - /* Keep a count of processed operations, for debug logs. */ - lNumProcessed++; - - /* Delay after sending a subscribe. This is to so that the broker - * creates a subscription for us before processing our next publish, - * which should be immediately after this. Only required because the - * subscribe and publish commands are coming from separate tasks, which - * would not normally be the case. */ - if( xCommand.xCommandType == SUBSCRIBE ) - { - LogDebug( ( "Sleeping for %d ms after sending SUBSCRIBE packet.", mqttexampleSUBSCRIBE_TASK_DELAY_MS ) ); - vTaskDelay( mqttexampleSUBSCRIBE_TASK_DELAY_MS ); - } - - /* Terminate the loop if we receive the termination command. */ - if( xCommand.xCommandType == TERMINATE ) - { - xTerminateReceived = true; - break; - } - - LogDebug( ( "Processed %d operations.", lNumProcessed ) ); - } - - /* Make sure we exited the loop due to receiving a terminate command and not - * due to the queue being empty. */ - configASSERT( xTerminateReceived == true ); - - LogInfo( ( "Creating Disconnect operation." ) ); - MQTT_Disconnect( &globalMqttContext ); - LogInfo( ( "Disconnected from broker." ) ); -} - -/*-----------------------------------------------------------*/ - -static void prvCommandCallback( CommandContext_t * pxContext ) -{ - pxContext->xIsComplete = true; - - if( pxContext->xTaskToNotify != NULL ) - { - xTaskNotify( pxContext->xTaskToNotify, pxContext->ulNotificationBit, eSetBits ); - } -} - -/*-----------------------------------------------------------*/ - -static bool prvNotificationWaitLoop( uint32_t * pulNotification, - uint32_t ulExpectedBits, - bool xClearBits ) -{ - uint32_t ulWaitCounter = 0U; - bool ret = true; - - configASSERT( pulNotification != NULL ); - - while( ( *pulNotification & ulExpectedBits ) != ulExpectedBits ) - { - xTaskNotifyWait( 0, - ( xClearBits ) ? ulExpectedBits : 0, - pulNotification, - mqttexampleDEMO_TICKS_TO_WAIT ); - - if( ++ulWaitCounter > mqttexampleMAX_WAIT_ITERATIONS ) - { - LogError( ( "Loop exceeded maximum wait time.\n" ) ); - ret = false; - break; - } - } - - return ret; -} - -/*-----------------------------------------------------------*/ - -void prvSyncPublishTask( void * pvParameters ) -{ - ( void ) pvParameters; - Command_t xCommand; - MQTTPublishInfo_t xPublishInfo = { 0 }; - char payloadBuf[ mqttexampleDEMO_BUFFER_SIZE ]; - char topicBuf[ mqttexampleDEMO_BUFFER_SIZE ]; - CommandContext_t xContext; - uint32_t ulNotification = 0U; - BaseType_t xCommandAdded = pdTRUE; - - /* We use QoS 1 so that the operation won't be counted as complete until we - * receive the publish acknowledgment. */ - xPublishInfo.qos = MQTTQoS1; - xPublishInfo.pTopicName = topicBuf; - xPublishInfo.pPayload = payloadBuf; - - /* Synchronous publishes. In case mqttexamplePUBLISH_COUNT is odd, round up. */ - for( int i = 0; i < ( ( mqttexamplePUBLISH_COUNT + 1 ) / 2 ); i++ ) - { - snprintf( payloadBuf, mqttexampleDEMO_BUFFER_SIZE, mqttexamplePUBLISH_PAYLOAD_FORMAT, "Sync", i + 1 ); - xPublishInfo.payloadLength = ( uint16_t ) strlen( payloadBuf ); - snprintf( topicBuf, mqttexampleDEMO_BUFFER_SIZE, mqttexamplePUBLISH_TOPIC_FORMAT_STRING, "sync", i + 1 ); - xPublishInfo.topicNameLength = ( uint16_t ) strlen( topicBuf ); - - memset( ( void * ) &xContext, 0x00, sizeof( xContext ) ); - xContext.xTaskToNotify = xTaskGetCurrentTaskHandle(); - xContext.ulNotificationBit = 1 << i; - xContext.pxPublishInfo = &xPublishInfo; - LogInfo( ( "Adding publish operation for message %s \non topic %.*s", payloadBuf, xPublishInfo.topicNameLength, xPublishInfo.pTopicName ) ); - prvCreateCommand( PUBLISH, &xContext, prvCommandCallback, &xCommand ); - xCommandAdded = prvAddCommandToQueue( &xCommand ); - /* Ensure command was added to queue. */ - configASSERT( xCommandAdded == pdTRUE ); - LogInfo( ( "Waiting for publish %d to complete.", i + 1 ) ); - - if( prvNotificationWaitLoop( &ulNotification, ( 1U << i ), true ) != true ) - { - LogError( ( "Synchronous publish loop iteration %d" - " exceeded maximum wait time.\n", ( i + 1 ) ) ); - } - - configASSERT( ( ulNotification & ( 1U << i ) ) == ( 1U << i ) ); - - LogInfo( ( "Publish operation complete. Sleeping for %d ms.\n", mqttexamplePUBLISH_DELAY_SYNC_MS ) ); - vTaskDelay( pdMS_TO_TICKS( mqttexamplePUBLISH_DELAY_SYNC_MS ) ); - } - - LogInfo( ( "Finished sync publishes.\n" ) ); - - /* Clear this task's notifications. */ - xTaskNotifyStateClear( NULL ); - ulNotification = ulTaskNotifyValueClear( NULL, ~( 0U ) ); - - /* Notify main task this task has completed. */ - xTaskNotify( xMainTask, mqttexamplePUBLISHER_SYNC_COMPLETE_BIT, eSetBits ); - - /* Delete this task. */ - LogInfo( ( "Deleting Sync Publisher task." ) ); - vTaskDelete( NULL ); -} - -/*-----------------------------------------------------------*/ - -void prvAsyncPublishTask( void * pvParameters ) -{ - ( void ) pvParameters; - Command_t xCommand; - MQTTPublishInfo_t pxPublishes[ mqttexamplePUBLISH_COUNT / 2 ]; - uint32_t ulNotification = 0U; - uint32_t ulExpectedNotifications = 0U; - BaseType_t xCommandAdded = pdTRUE; - /* The following arrays are used to hold pointers to dynamically allocated memory. */ - char * payloadBuffers[ mqttexamplePUBLISH_COUNT / 2 ]; - char * topicBuffers[ mqttexamplePUBLISH_COUNT / 2 ]; - CommandContext_t * pxContexts[ mqttexamplePUBLISH_COUNT / 2 ] = { 0 }; - - /* Add a delay. The main task will not be sending publishes for this interval - * anyway, as we want to give the broker ample time to process the - * subscription. */ - vTaskDelay( mqttexampleSUBSCRIBE_TASK_DELAY_MS ); - - /* Asynchronous publishes. Although not necessary, we use dynamic - * memory here to avoid declaring many static buffers. */ - for( int i = 0; i < mqttexamplePUBLISH_COUNT / 2; i++ ) - { - pxContexts[ i ] = ( CommandContext_t * ) pvPortMalloc( sizeof( CommandContext_t ) ); - memset( ( void * ) pxContexts[ i ], 0x00, sizeof( CommandContext_t ) ); - pxContexts[ i ]->xTaskToNotify = xTaskGetCurrentTaskHandle(); - - /* Set the notification bit to be the publish number. This prevents this demo - * from having more than 32 publishes. If many publishes are desired, semaphores - * can be used instead of task notifications. */ - pxContexts[ i ]->ulNotificationBit = 1U << i; - ulExpectedNotifications |= 1U << i; - payloadBuffers[ i ] = ( char * ) pvPortMalloc( mqttexampleDYNAMIC_BUFFER_SIZE ); - topicBuffers[ i ] = ( char * ) pvPortMalloc( mqttexampleDYNAMIC_BUFFER_SIZE ); - snprintf( payloadBuffers[ i ], mqttexampleDYNAMIC_BUFFER_SIZE, mqttexamplePUBLISH_PAYLOAD_FORMAT, "Async", i + 1 ); - snprintf( topicBuffers[ i ], mqttexampleDYNAMIC_BUFFER_SIZE, mqttexamplePUBLISH_TOPIC_FORMAT_STRING, "async", i + 1 ); - /* Set publish info. */ - memset( &( pxPublishes[ i ] ), 0x00, sizeof( MQTTPublishInfo_t ) ); - pxPublishes[ i ].pPayload = payloadBuffers[ i ]; - pxPublishes[ i ].payloadLength = strlen( payloadBuffers[ i ] ); - pxPublishes[ i ].pTopicName = topicBuffers[ i ]; - pxPublishes[ i ].topicNameLength = ( uint16_t ) strlen( topicBuffers[ i ] ); - pxPublishes[ i ].qos = MQTTQoS1; - pxContexts[ i ]->pxPublishInfo = &( pxPublishes[ i ] ); - LogInfo( ( "Adding publish operation for message %s \non topic %.*s", - payloadBuffers[ i ], - pxPublishes[ i ].topicNameLength, - pxPublishes[ i ].pTopicName ) ); - prvCreateCommand( PUBLISH, pxContexts[ i ], prvCommandCallback, &xCommand ); - xCommandAdded = prvAddCommandToQueue( &xCommand ); - /* Ensure command was added to queue. */ - configASSERT( xCommandAdded == pdTRUE ); - /* Short delay so we do not bombard the broker with publishes. */ - LogInfo( ( "Publish operation queued. Sleeping for %d ms.\n", mqttexamplePUBLISH_DELAY_ASYNC_MS ) ); - vTaskDelay( pdMS_TO_TICKS( mqttexamplePUBLISH_DELAY_ASYNC_MS ) ); - } - - LogInfo( ( "Finished async publishes.\n" ) ); - - /* Receive all task notifications. We may receive notifications in a - * different order, so we have two loops. If all notifications have been - * received, we can break early. */ - ( void ) prvNotificationWaitLoop( &ulNotification, ulExpectedNotifications, false ); - - for( int i = 0; i < mqttexamplePUBLISH_COUNT / 2; i++ ) - { - configASSERT( ( ulNotification & ( 1U << i ) ) == ( 1U << i ) ); - - LogInfo( ( "Freeing publish context %d.", i + 1 ) ); - vPortFree( pxContexts[ i ] ); - vPortFree( topicBuffers[ i ] ); - vPortFree( payloadBuffers[ i ] ); - LogInfo( ( "Publish context %d freed.", i + 1 ) ); - pxContexts[ i ] = NULL; - } - - /* Clear this task's notifications. */ - xTaskNotifyStateClear( NULL ); - ulNotification = ulTaskNotifyValueClear( NULL, ~( 0U ) ); - - /* Notify main task this task has completed. */ - xTaskNotify( xMainTask, mqttexamplePUBLISHER_ASYNC_COMPLETE_BIT, eSetBits ); - - /* Delete this task. */ - LogInfo( ( "Deleting Async Publisher task." ) ); - vTaskDelete( NULL ); -} - -/*-----------------------------------------------------------*/ - -void prvSubscribeTask( void * pvParameters ) -{ - ( void ) pvParameters; - MQTTSubscribeInfo_t xSubscribeInfo; - Command_t xCommand; - BaseType_t xCommandAdded = pdTRUE; - MQTTPublishInfo_t * pxReceivedPublish = NULL; - uint16_t usNumReceived = 0; - uint32_t ulNotification = 0; - CommandContext_t xContext; - PublishElement_t xReceivedPublish; - uint32_t ulWaitCounter = 0; - - /* The QoS does not affect when subscribe operations are marked completed - * as it does for publishes. However, we still use QoS 1 here so that the - * broker will resend publishes if there is a network disconnect. */ - xSubscribeInfo.qos = MQTTQoS1; - xSubscribeInfo.pTopicFilter = mqttexampleSUBSCRIBE_TOPIC_FILTER; - xSubscribeInfo.topicFilterLength = ( uint16_t ) strlen( xSubscribeInfo.pTopicFilter ); - LogInfo( ( "Topic filter: %.*s", xSubscribeInfo.topicFilterLength, xSubscribeInfo.pTopicFilter ) ); - - /* Create the context and subscribe command. */ - memset( &xContext, 0x00, sizeof( xContext ) ); - xContext.pxResponseQueue = xSubscriberResponseQueue; - xContext.xTaskToNotify = xTaskGetCurrentTaskHandle(); - xContext.ulNotificationBit = mqttexampleSUBSCRIBE_COMPLETE_BIT; - xContext.pxSubscribeInfo = &xSubscribeInfo; - xContext.ulSubscriptionCount = 1; - LogInfo( ( "Adding subscribe operation" ) ); - prvCreateCommand( SUBSCRIBE, &xContext, prvCommandCallback, &xCommand ); - xCommandAdded = prvAddCommandToQueue( &xCommand ); - /* Ensure command was added to queue. */ - configASSERT( xCommandAdded == pdTRUE ); - - /* This demo relies on the server processing our subscription before any publishes. - * Since this demo uses multiple tasks, we do not retry failed subscriptions, as the - * server has likely already processed our first publish, and so this demo will not - * complete successfully. */ - LogInfo( ( "Waiting for subscribe operation to complete." ) ); - ( void ) prvNotificationWaitLoop( &ulNotification, mqttexampleSUBSCRIBE_COMPLETE_BIT, true ); - - configASSERT( ( ulNotification & mqttexampleSUBSCRIBE_COMPLETE_BIT ) == mqttexampleSUBSCRIBE_COMPLETE_BIT ); - configASSERT( xContext.xReturnStatus == MQTTSuccess ); - - LogInfo( ( "Operation wait complete.\n" ) ); - - for( ; ; ) - { - /* It is possible that there is nothing to receive from the queue, and - * this is expected, as there are delays between each publish. For this - * reason, we keep track of the number of publishes received, and break - * from the outermost while loop when we have received all of them. If - * the queue is empty, we add a delay before checking it again. */ - while( xQueueReceive( xSubscriberResponseQueue, &xReceivedPublish, mqttexampleDEMO_TICKS_TO_WAIT ) != pdFALSE ) - { - pxReceivedPublish = &( xReceivedPublish.xPublishInfo ); - pxReceivedPublish->pTopicName = ( const char * ) xReceivedPublish.pcTopicNameBuf; - pxReceivedPublish->pPayload = xReceivedPublish.pcPayloadBuf; - LogInfo( ( "Received publish on topic %.*s\nMessage payload: %.*s\n", - pxReceivedPublish->topicNameLength, - pxReceivedPublish->pTopicName, - ( int ) pxReceivedPublish->payloadLength, - ( const char * ) pxReceivedPublish->pPayload ) ); - usNumReceived++; - /* Reset the wait counter every time a publish is received. */ - ulWaitCounter = 0; - } - - /* Since this is an infinite loop, we want to break if all publishes have - * been received. */ - if( usNumReceived >= mqttexamplePUBLISH_COUNT ) - { - break; - } - - /* Break if we have been stuck in this loop for too long. The total wait - * here will be ( (loop delay + queue check delay) * `mqttexampleMAX_WAIT_ITERATIONS` ). - * For example, with a 1000 ms queue delay, a 400 ms loop delay, and a - * maximum iteration of 20, this will wait 28 seconds after receiving - * the last publish. */ - if( ++ulWaitCounter > mqttexampleMAX_WAIT_ITERATIONS ) - { - LogError( ( "Publish receive loop exceeded maximum wait time.\n" ) ); - break; - } - - /* Delay a bit to give more time for publish messages to be received. */ - LogInfo( ( "No messages queued, received %u publish%s, sleeping for %d ms\n", - usNumReceived, - ( usNumReceived == 1 ) ? "" : "es", - mqttexampleSUBSCRIBE_TASK_DELAY_MS ) ); - vTaskDelay( pdMS_TO_TICKS( mqttexampleSUBSCRIBE_TASK_DELAY_MS ) ); - } - - LogInfo( ( "Finished receiving\n" ) ); - prvCreateCommand( UNSUBSCRIBE, &xContext, prvCommandCallback, &xCommand ); - memset( ( void * ) &xContext, 0x00, sizeof( xContext ) ); - xContext.pxResponseQueue = xSubscriberResponseQueue; - xContext.xTaskToNotify = xTaskGetCurrentTaskHandle(); - xContext.ulNotificationBit = mqttexampleUNSUBSCRIBE_COMPLETE_BIT; - xContext.pxSubscribeInfo = &xSubscribeInfo; - xContext.ulSubscriptionCount = 1; - LogInfo( ( "Adding unsubscribe operation\n" ) ); - xCommandAdded = prvAddCommandToQueue( &xCommand ); - /* Ensure command was added to queue. */ - configASSERT( xCommandAdded == pdTRUE ); - - LogInfo( ( "Waiting for unsubscribe operation to complete." ) ); - ( void ) prvNotificationWaitLoop( &ulNotification, mqttexampleUNSUBSCRIBE_COMPLETE_BIT, true ); - - configASSERT( ( ulNotification & mqttexampleUNSUBSCRIBE_COMPLETE_BIT ) == mqttexampleUNSUBSCRIBE_COMPLETE_BIT ); - LogInfo( ( "Operation wait complete.\n" ) ); - - /* Create command to stop command loop. */ - LogInfo( ( "Beginning command queue termination." ) ); - prvCreateCommand( TERMINATE, NULL, NULL, &xCommand ); - xCommandAdded = prvAddCommandToQueue( &xCommand ); - /* Ensure command was added to queue. */ - configASSERT( xCommandAdded == pdTRUE ); - - /* Notify main task this task has completed. */ - xTaskNotify( xMainTask, mqttexampleSUBSCRIBE_TASK_COMPLETE_BIT, eSetBits ); - - /* Delete this task. */ - LogInfo( ( "Deleting Subscriber task." ) ); - vTaskDelete( NULL ); -} - -/*-----------------------------------------------------------*/ - -static void prvCleanExistingPersistentSession( void ) -{ - BaseType_t xNetworkStatus = pdFAIL; - MQTTStatus_t xMQTTStatus; - - /* Connect to the broker. We connect here with the "clean session" flag set - * to true in order to clear any prior state in the broker. We will disconnect - * and later form a persistent session, so that it may be resumed if the - * network suddenly disconnects. */ - xNetworkStatus = prvSocketConnect( &xNetworkContext ); - configASSERT( xNetworkStatus == pdPASS ); - LogInfo( ( "Creating a clean session to clear any broker state information." ) ); - xMQTTStatus = prvMQTTInit( &globalMqttContext, &xNetworkContext ); - configASSERT( xMQTTStatus == MQTTSuccess ); - xMQTTStatus = prvMQTTConnect( &globalMqttContext, true ); - configASSERT( xMQTTStatus == MQTTSuccess ); - - /* Disconnect. */ - xMQTTStatus = MQTT_Disconnect( &globalMqttContext ); - configASSERT( xMQTTStatus == MQTTSuccess ); - xNetworkStatus = prvSocketDisconnect( &xNetworkContext ); - configASSERT( xNetworkStatus == pdPASS ); -} - -/*-----------------------------------------------------------*/ - - /* Also see https://www.freertos.org/mqtt/mqtt-agent-demo.html? for an - * alternative run time model whereby coreMQTT runs in an autonomous - * background agent task. Executing the MQTT protocol in an agent task - * removes the need for the application writer to explicitly manage any MQTT - * state or call the MQTT_ProcessLoop() API function. Using an agent task - * also enables multiple application tasks to more easily share a single - * MQTT connection. */ -static void prvMQTTDemoTask( void * pvParameters ) -{ - BaseType_t xNetworkStatus = pdFAIL; - MQTTStatus_t xMQTTStatus; - BaseType_t xResult = pdFALSE; - uint32_t ulNotification = 0; - uint32_t ulExpectedNotifications = mqttexamplePUBLISHER_SYNC_COMPLETE_BIT | - mqttexampleSUBSCRIBE_TASK_COMPLETE_BIT | - mqttexamplePUBLISHER_ASYNC_COMPLETE_BIT; - - ( void ) pvParameters; - - /* Set the pParams member of the network context with desired transport. */ - #if defined( democonfigUSE_TLS ) && ( democonfigUSE_TLS == 1 ) - xNetworkContext.pParams = &xTlsTransportParams; - #else - xNetworkContext.pParams = &xPlaintextTransportParams; - #endif - - ulGlobalEntryTimeMs = prvGetTimeMs(); - - /* Create command queue for processing MQTT commands. */ - xCommandQueue = xQueueCreate( mqttexampleCOMMAND_QUEUE_SIZE, sizeof( Command_t ) ); - /* Create response queues for each task. */ - xSubscriberResponseQueue = xQueueCreate( mqttexamplePUBLISH_QUEUE_SIZE, sizeof( PublishElement_t ) ); - - /* In this demo, send publishes on non-subscribed topics to this queue. - * Note that this value is not meant to be changed after `prvCommandLoop` has - * been called, since access to this variable is not protected by thread - * synchronization primitives. */ - xDefaultResponseQueue = xQueueCreate( 1, sizeof( PublishElement_t ) ); - - /* This demo uses a persistent session that can be re-connected if disconnected. - * Clean any lingering sessions that may exist from previous executions of the - * demo. */ - prvCleanExistingPersistentSession(); - - for( ; ; ) - { - /* Clear the lists of subscriptions and pending acknowledgments. */ - memset( pxPendingAcks, 0x00, mqttexamplePENDING_ACKS_MAX_SIZE * sizeof( AckInfo_t ) ); - memset( pxSubscriptions, 0x00, mqttexampleSUBSCRIPTIONS_MAX_COUNT * sizeof( SubscriptionElement_t ) ); - - /* Connect to the broker. */ - xNetworkStatus = prvSocketConnect( &xNetworkContext ); - configASSERT( xNetworkStatus == pdPASS ); - /* Form an MQTT connection with a persistent session. */ - xMQTTStatus = prvMQTTConnect( &globalMqttContext, false ); - configASSERT( xMQTTStatus == MQTTSuccess ); - configASSERT( globalMqttContext.connectStatus == MQTTConnected ); - - /* Give subscriber task higher priority so the subscribe will be processed - * before the first publish. This must be less than or equal to the priority of - * the main task. */ - xResult = xTaskCreate( prvSubscribeTask, "Subscriber", democonfigDEMO_STACKSIZE, NULL, tskIDLE_PRIORITY + 1, &xSubscribeTask ); - configASSERT( xResult == pdPASS ); - xResult = xTaskCreate( prvSyncPublishTask, "SyncPublisher", democonfigDEMO_STACKSIZE, NULL, tskIDLE_PRIORITY, &xSyncPublisherTask ); - configASSERT( xResult == pdPASS ); - xResult = xTaskCreate( prvAsyncPublishTask, "AsyncPublisher", democonfigDEMO_STACKSIZE, NULL, tskIDLE_PRIORITY, &xAsyncPublisherTask ); - configASSERT( xResult == pdPASS ); - - LogInfo( ( "Running command loop" ) ); - prvCommandLoop(); - - /* Delete created queues. Wait for tasks to exit before cleaning up. */ - LogInfo( ( "Waiting for tasks to exit." ) ); - ( void ) prvNotificationWaitLoop( &ulNotification, ulExpectedNotifications, false ); - - configASSERT( ( ulNotification & ulExpectedNotifications ) == ulExpectedNotifications ); - - /* Reset queues. */ - xQueueReset( xCommandQueue ); - xQueueReset( xDefaultResponseQueue ); - xQueueReset( xSubscriberResponseQueue ); - - /* Clear task notifications. */ - ulNotification = ulTaskNotifyValueClear( NULL, ~( 0U ) ); - - /* Disconnect. */ - xNetworkStatus = prvSocketDisconnect( &xNetworkContext ); - configASSERT( xNetworkStatus == pdPASS ); - - LogInfo( ( "\r\n\r\nprvMQTTDemoTask() completed an iteration successfully. Total free heap is %u.\r\n", xPortGetFreeHeapSize() ) ); - LogInfo( ( "Demo completed successfully.\r\n" ) ); - LogInfo( ( "Short delay before starting the next iteration.... \r\n\r\n" ) ); - vTaskDelay( mqttexampleDELAY_BETWEEN_DEMO_ITERATIONS ); - } -} - -/*-----------------------------------------------------------*/ - -static uint32_t prvGetTimeMs( void ) -{ - TickType_t xTickCount = 0; - uint32_t ulTimeMs = 0UL; - - /* Get the current tick count. */ - xTickCount = xTaskGetTickCount(); - - /* Convert the ticks to milliseconds. */ - ulTimeMs = ( uint32_t ) xTickCount * mqttexampleMILLISECONDS_PER_TICK; - - /* Reduce ulGlobalEntryTimeMs from obtained time so as to always return the - * elapsed time in the application. */ - ulTimeMs = ( uint32_t ) ( ulTimeMs - ulGlobalEntryTimeMs ); - - return ulTimeMs; -} - -/*-----------------------------------------------------------*/ diff --git a/FreeRTOS-Plus/Demo/coreMQTT_Windows_Simulator/MQTT_Multitask/DemoTasks/mqtt-agent-task.c b/FreeRTOS-Plus/Demo/coreMQTT_Windows_Simulator/MQTT_Multitask/DemoTasks/mqtt-agent-task.c new file mode 100644 index 0000000000..a043091db1 --- /dev/null +++ b/FreeRTOS-Plus/Demo/coreMQTT_Windows_Simulator/MQTT_Multitask/DemoTasks/mqtt-agent-task.c @@ -0,0 +1,952 @@ +/* + * FreeRTOS V202012.00 + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * https://www.FreeRTOS.org + * https://github.com/FreeRTOS + * + */ + +/* + * This demo creates multiple tasks, all of which use the MQTT agent API to + * communicate with an MQTT broker through the same MQTT connection. + * + * This file contains the initial task created after the TCP/IP stack connects + * to the network. The task: + * + * 1) Connects to the MQTT broker. + * 2) Creates the other demo tasks, in accordance with the #defines set in + * demo_config.h. For example, if demo_config.h contains the following + * setting: + * + * #define democonfigNUM_SIMPLE_SUB_PUB_TASKS_TO_CREATE 3 + * + * then the initial task will create three instances of the task + * implemented in simple_sub_pub_demo.c. See the comments at the top + * of that file for more information. + * + * 3) After creating the demo tasks the initial task could create the MQTT + * agent task. However, as it has no other operations to perform, rather + * than create the MQTT agent as a separate task the initial task just calls + * the agent's implementing function - effectively turning itself into the + * MQTT agent. + */ + + +/* Standard includes. */ +#include +#include +#include + +/* Kernel includes. */ +#include "FreeRTOS.h" +#include "queue.h" +#include "task.h" + +/* FreeRTOS+TCP includes. */ +#include "FreeRTOS_IP.h" +#include "FreeRTOS_Sockets.h" + +/* Demo Specific configs. */ +#include "demo_config.h" + +/* MQTT library includes. */ +#include "core_mqtt.h" + +/* MQTT agent include. */ +#include "mqtt_agent.h" + +/* MQTT Agent ports. */ +#include "freertos_agent_message.h" +#include "freertos_command_pool.h" + +/* Exponential backoff retry include. */ +#include "backoff_algorithm.h" + +/* Subscription manager header include. */ +#include "subscription_manager.h" + + +/* Transport interface include. */ +#if defined( democonfigUSE_TLS ) && ( democonfigUSE_TLS == 1 ) + #include "using_mbedtls.h" +#else + #include "using_plaintext.h" +#endif + +/* This demo uses compile time options to select the demo tasks to created. + * Ensure the compile time options are defined. These should be defined in + * demo_config.h. */ +#if !defined( democonfigNUM_SIMPLE_SUB_PUB_TASKS_TO_CREATE ) || ( democonfigNUM_SIMPLE_SUB_PUB_TASKS_TO_CREATE < 1 ) + #error Please set democonfigNUM_SIMPLE_SUB_PUB_TASKS_TO_CREATE to the number of tasks to create in vStartSimpleSubscribePublishTask(). +#endif + +#if ( democonfigNUM_SIMPLE_SUB_PUB_TASKS_TO_CREATE > 0 ) && !defined( democonfigSIMPLE_SUB_PUB_TASK_STACK_SIZE ) + #error Please define democonfigSIMPLE_SUB_PUB_TASK_STACK_SIZE in demo_config.h to set the stack size (in words, not bytes) for the tasks created by vStartSimpleSubscribePublishTask(). +#endif + +/** + * @brief Dimensions the buffer used to serialize and deserialize MQTT packets. + * @note Specified in bytes. Must be large enough to hold the maximum + * anticipated MQTT payload. + */ +#ifndef MQTT_AGENT_NETWORK_BUFFER_SIZE + #define MQTT_AGENT_NETWORK_BUFFER_SIZE ( 5000 ) +#endif + +/** + * @brief The length of the queue used to hold commands for the agent. + */ +#ifndef MQTT_AGENT_COMMAND_QUEUE_LENGTH + #define MQTT_AGENT_COMMAND_QUEUE_LENGTH ( 10 ) +#endif + + +/** + * These configuration settings are required to run the demo. + */ + +/** + * @brief Timeout for receiving CONNACK after sending an MQTT CONNECT packet. + * Defined in milliseconds. + */ +#define mqttexampleCONNACK_RECV_TIMEOUT_MS ( 1000U ) + +/** + * @brief The maximum number of retries for network operation with server. + */ +#define RETRY_MAX_ATTEMPTS ( 5U ) + +/** + * @brief The maximum back-off delay (in milliseconds) for retrying failed operation + * with server. + */ +#define RETRY_MAX_BACKOFF_DELAY_MS ( 5000U ) + +/** + * @brief The base back-off delay (in milliseconds) to use for network operation retry + * attempts. + */ +#define RETRY_BACKOFF_BASE_MS ( 500U ) + +/** + * @brief The maximum time interval in seconds which is allowed to elapse + * between two Control Packets. + * + * It is the responsibility of the Client to ensure that the interval between + * Control Packets being sent does not exceed the this Keep Alive value. In the + * absence of sending any other Control Packets, the Client MUST send a + * PINGREQ Packet. + *//*_RB_ Move to be the responsibility of the agent. */ +#define mqttexampleKEEP_ALIVE_INTERVAL_SECONDS ( 60U ) + +/** + * @brief Socket send and receive timeouts to use. Specified in milliseconds. + */ +#define mqttexampleTRANSPORT_SEND_RECV_TIMEOUT_MS ( 750 ) + +/** + * @brief Used to convert times to/from ticks and milliseconds. + */ +#define mqttexampleMILLISECONDS_PER_SECOND ( 1000U ) +#define mqttexampleMILLISECONDS_PER_TICK ( mqttexampleMILLISECONDS_PER_SECOND / configTICK_RATE_HZ ) + +/*-----------------------------------------------------------*/ + +/** + * @brief Each compilation unit that consumes the NetworkContext must define it. + * It should contain a single pointer to the type of your desired transport. + * When using multiple transports in the same compilation unit, define this pointer as void *. + * + * @note Transport stacks are defined in FreeRTOS-Plus/Source/Application-Protocols/network_transport. + */ +struct NetworkContext +{ + #if defined( democonfigUSE_TLS ) && ( democonfigUSE_TLS == 1 ) + TlsTransportParams_t * pParams; + #else + PlaintextTransportParams_t * pParams; + #endif +}; + +/*-----------------------------------------------------------*/ + +/** + * @brief Initializes an MQTT context, including transport interface and + * network buffer. + * + * @return `MQTTSuccess` if the initialization succeeds, else `MQTTBadParameter`. + */ +static MQTTStatus_t prvMQTTInit( void ); + +/** + * @brief Sends an MQTT Connect packet over the already connected TCP socket. + * + * @param[in] pxMQTTContext MQTT context pointer. + * @param[in] xCleanSession If a clean session should be established. + * + * @return `MQTTSuccess` if connection succeeds, else appropriate error code + * from MQTT_Connect. + */ +static MQTTStatus_t prvMQTTConnect( bool xCleanSession ); + +/** + * @brief Connect a TCP socket to the MQTT broker. + * + * @param[in] pxNetworkContext Network context. + * + * @return `pdPASS` if connection succeeds, else `pdFAIL`. + */ +static BaseType_t prvSocketConnect( NetworkContext_t * pxNetworkContext ); + +/** + * @brief Disconnect a TCP connection. + * + * @param[in] pxNetworkContext Network context. + * + * @return `pdPASS` if disconnect succeeds, else `pdFAIL`. + */ +static BaseType_t prvSocketDisconnect( NetworkContext_t * pxNetworkContext ); + +/** + * @brief Callback executed when there is activity on the TCP socket that is + * connected to the MQTT broker. If there are no messages in the MQTT agent's + * command queue then the callback send a message to ensure the MQTT agent + * task unblocks and can therefore process whatever is necessary on the socket + * (if anything) as quickly as possible. + * + * @param[in] pxSocket Socket with data, unused. + */ +static void prvMQTTClientSocketWakeupCallback( Socket_t pxSocket ); + +/** + * @brief Fan out the incoming publishes to the callbacks registered by different + * tasks. If there are no callbacks registered for the incoming publish, it will be + * passed to the unsolicited publish handler. + * + * @param[in] pMqttAgentContext Agent context. + * @param[in] packetId Packet ID of publish. + * @param[in] pxPublishInfo Info of incoming publish. + */ +static void prvIncomingPublishCallback( MQTTAgentContext_t * pMqttAgentContext, + uint16_t packetId, + MQTTPublishInfo_t * pxPublishInfo ); + +/** + * @brief Function to attempt to resubscribe to the topics already present in the + * subscription list. + * + * This function will be invoked when this demo requests the broker to + * reestablish the session and the broker cannot do so. This function will + * enqueue commands to the MQTT Agent queue and will be processed once the + * command loop starts. + * + * @return `MQTTSuccess` if adding subscribes to the command queue succeeds, else + * appropriate error code from MQTTAgent_Subscribe. + * */ +static MQTTStatus_t prvHandleResubscribe( void ); + +/** + * @brief Passed into MQTTAgent_Subscribe() as the callback to execute when the + * broker ACKs the SUBSCRIBE message. This callback implementation is used for + * handling the completion of resubscribes. Any topic filter failed to resubscribe + * will be removed from the subscription list. + * + * See https://freertos.org/mqtt/mqtt-agent-demo.html#example_mqtt_api_call + * + * @param[in] pxCommandContext Context of the initial command. + * @param[in] pxReturnInfo The result of the command. + */ +static void prvSubscriptionCommandCallback( void * pxCommandContext, + MQTTAgentReturnInfo_t * pxReturnInfo ); + +/** + * @brief Task used to run the MQTT agent. In this example the first task that + * is created is responsible for creating all the other demo tasks. Then, + * rather than create prvMQTTAgentTask() as a separate task, it simply calls + * prvMQTTAgentTask() to become the agent task itself. + * + * This task calls MQTTAgent_CommandLoop() in a loop, until MQTTAgent_Terminate() + * is called. If an error occurs in the command loop, then it will reconnect the + * TCP and MQTT connections. + * + * @param[in] pvParameters Parameters as passed at the time of task creation. Not + * used in this example. + */ +static void prvMQTTAgentTask( void * pvParameters ); + +/** + * @brief The main task used in the MQTT demo. + * + * This task creates the network connection and all other demo tasks. Then, + * rather than create prvMQTTAgentTask() as a separate task, it simply calls + * prvMQTTAgentTask() to become the agent task itself. + * + * @param[in] pvParameters Parameters as passed at the time of task creation. Not + * used in this example. + */ +static void prvConnectAndCreateDemoTasks( void * pvParameters ); + +/** + * @brief The timer query function provided to the MQTT context. + * + * @return Time in milliseconds. + */ +static uint32_t prvGetTimeMs( void ); + +/** + * @brief Connects a TCP socket to the MQTT broker, then creates and MQTT + * connection to the same. + */ +static void prvConnectToMQTTBroker( void ); + +/* + * Functions that start the tasks demonstrated by this project. + */ + +extern void vStartSimpleSubscribePublishTask( uint32_t ulTaskNumber, + configSTACK_DEPTH_TYPE uxStackSize, + UBaseType_t uxPriority ); + +/*-----------------------------------------------------------*/ + +/** + * @brief The network context used by the MQTT library transport interface. + * See https://www.freertos.org/network-interface.html + */ +static NetworkContext_t xNetworkContext; + +#if defined( democonfigUSE_TLS ) && ( democonfigUSE_TLS == 1 ) + +/** + * @brief The parameters for the network context using a TLS channel. + */ + static TlsTransportParams_t xTlsTransportParams; +#else + +/** + * @brief The parameters for the network context using a non-encrypted channel. + */ + static PlaintextTransportParams_t xPlaintextTransportParams; +#endif + +/** + * @brief Global entry time into the application to use as a reference timestamp + * in the #prvGetTimeMs function. #prvGetTimeMs will always return the difference + * between the current time and the global entry time. This will reduce the chances + * of overflow for the 32 bit unsigned integer used for holding the timestamp. + */ +static uint32_t ulGlobalEntryTimeMs; + +MQTTAgentContext_t xGlobalMqttAgentContext; + +static uint8_t xNetworkBuffer[ MQTT_AGENT_NETWORK_BUFFER_SIZE ]; + +static AgentMessageContext_t xCommandQueue; + +/** + * @brief The global array of subscription elements. + * + * @note No thread safety is required to this array, since the updates the array + * elements are done only from one task at a time. The subscription manager + * implementation expects that the array of the subscription elements used for + * storing subscriptions to be initialized to 0. As this is a global array, it + * will be initialized to 0 by default. + */ +SubscriptionElement_t xGlobalSubscriptionList[ SUBSCRIPTION_MANAGER_MAX_SUBSCRIPTIONS ]; + +/*-----------------------------------------------------------*/ + +/* + * @brief Create the task that demonstrates the MQTT Connection sharing demo. + */ +void vStartSimpleMQTTDemo( void ) +{ + /* prvConnectAndCreateDemoTasks() connects to the MQTT broker, creates the + * tasks that will interact with the broker via the MQTT agent, then turns + * itself into the MQTT agent task. */ + xTaskCreate( prvConnectAndCreateDemoTasks, /* Function that implements the task. */ + "ConnectManager", /* Text name for the task - only used for debugging. */ + democonfigDEMO_STACKSIZE, /* Size of stack (in words, not bytes) to allocate for the task. */ + NULL, /* Optional - task parameter - not used in this case. */ + tskIDLE_PRIORITY + 1, /* Task priority, must be between 0 and configMAX_PRIORITIES - 1. */ + NULL ); /* Optional - used to pass out a handle to the created task. */ +} + +/*-----------------------------------------------------------*/ + +static MQTTStatus_t prvMQTTInit( void ) +{ + TransportInterface_t xTransport; + MQTTStatus_t xReturn; + MQTTFixedBuffer_t xFixedBuffer = { .pBuffer = xNetworkBuffer, .size = MQTT_AGENT_NETWORK_BUFFER_SIZE }; + static uint8_t staticQueueStorageArea[ MQTT_AGENT_COMMAND_QUEUE_LENGTH * sizeof( Command_t * ) ]; + static StaticQueue_t staticQueueStructure; + AgentMessageInterface_t messageInterface = + { + .pMsgCtx = NULL, + .send = Agent_MessageSend, + .recv = Agent_MessageReceive, + .getCommand = Agent_GetCommand, + .releaseCommand = Agent_ReleaseCommand + }; + + LogDebug( ( "Creating command queue." ) ); + xCommandQueue.queue = xQueueCreateStatic( MQTT_AGENT_COMMAND_QUEUE_LENGTH, + sizeof( Command_t * ), + staticQueueStorageArea, + &staticQueueStructure ); + configASSERT( xCommandQueue.queue ); + messageInterface.pMsgCtx = &xCommandQueue; + + /* Initialize the task pool. */ + Agent_InitializePool(); + + /* Fill in Transport Interface send and receive function pointers. */ + xTransport.pNetworkContext = &xNetworkContext; + #if defined( democonfigUSE_TLS ) && ( democonfigUSE_TLS == 1 ) + xTransport.send = TLS_FreeRTOS_send; + xTransport.recv = TLS_FreeRTOS_recv; + #else + xTransport.send = Plaintext_FreeRTOS_send; + xTransport.recv = Plaintext_FreeRTOS_recv; + #endif + + /* Initialize MQTT library. */ + xReturn = MQTTAgent_Init( &xGlobalMqttAgentContext, + &messageInterface, + &xFixedBuffer, + &xTransport, + prvGetTimeMs, + prvIncomingPublishCallback, + /* Context to pass into the callback. Passing the pointer to subscription array. */ + xGlobalSubscriptionList ); + + return xReturn; +} + +/*-----------------------------------------------------------*/ + +static MQTTStatus_t prvMQTTConnect( bool xCleanSession ) +{ + MQTTStatus_t xResult; + MQTTConnectInfo_t xConnectInfo; + bool xSessionPresent = false; + + /* Many fields are not used in this demo so start with everything at 0. */ + memset( &xConnectInfo, 0x00, sizeof( xConnectInfo ) ); + + /* Start with a clean session i.e. direct the MQTT broker to discard any + * previous session data. Also, establishing a connection with clean session + * will ensure that the broker does not store any data when this client + * gets disconnected. */ + xConnectInfo.cleanSession = xCleanSession; + + /* The client identifier is used to uniquely identify this MQTT client to + * the MQTT broker. In a production device the identifier can be something + * unique, such as a device serial number. */ + xConnectInfo.pClientIdentifier = democonfigCLIENT_IDENTIFIER; + xConnectInfo.clientIdentifierLength = ( uint16_t ) strlen( democonfigCLIENT_IDENTIFIER ); + + /* Set MQTT keep-alive period. It is the responsibility of the application + * to ensure that the interval between Control Packets being sent does not + * exceed the Keep Alive value. In the absence of sending any other Control + * Packets, the Client MUST send a PINGREQ Packet. This responsibility will + * be moved inside the agent. */ + xConnectInfo.keepAliveSeconds = mqttexampleKEEP_ALIVE_INTERVAL_SECONDS; + + /* Append metrics when connecting to the AWS IoT Core broker. */ + #ifdef democonfigUSE_AWS_IOT_CORE_BROKER + #ifdef democonfigCLIENT_USERNAME + xConnectInfo.pUserName = CLIENT_USERNAME_WITH_METRICS; + xConnectInfo.userNameLength = ( uint16_t ) strlen( CLIENT_USERNAME_WITH_METRICS ); + xConnectInfo.pPassword = democonfigCLIENT_PASSWORD; + xConnectInfo.passwordLength = ( uint16_t ) strlen( democonfigCLIENT_PASSWORD ); + #else + xConnectInfo.pUserName = AWS_IOT_METRICS_STRING; + xConnectInfo.userNameLength = AWS_IOT_METRICS_STRING_LENGTH; + /* Password for authentication is not used. */ + xConnectInfo.pPassword = NULL; + xConnectInfo.passwordLength = 0U; + #endif + #else /* ifdef democonfigUSE_AWS_IOT_CORE_BROKER */ + #ifdef democonfigCLIENT_USERNAME + xConnectInfo.pUserName = democonfigCLIENT_USERNAME; + xConnectInfo.userNameLength = ( uint16_t ) strlen( democonfigCLIENT_USERNAME ); + xConnectInfo.pPassword = democonfigCLIENT_PASSWORD; + xConnectInfo.passwordLength = ( uint16_t ) strlen( democonfigCLIENT_PASSWORD ); + #endif /* ifdef democonfigCLIENT_USERNAME */ + #endif /* ifdef democonfigUSE_AWS_IOT_CORE_BROKER */ + + /* Send MQTT CONNECT packet to broker. MQTT's Last Will and Testament feature + * is not used in this demo, so it is passed as NULL. */ + xResult = MQTT_Connect( &( xGlobalMqttAgentContext.mqttContext ), + &xConnectInfo, + NULL, + mqttexampleCONNACK_RECV_TIMEOUT_MS, + &xSessionPresent ); + + LogInfo( ( "Session present: %d\n", xSessionPresent ) ); + + /* Resume a session if desired. */ + if( ( xResult == MQTTSuccess ) && ( xCleanSession == false ) ) + { + xResult = MQTTAgent_ResumeSession( &xGlobalMqttAgentContext, xSessionPresent ); + + /* Resubscribe to all the subscribed topics. */ + if( ( xResult == MQTTSuccess ) && ( xSessionPresent == false ) ) + { + xResult = prvHandleResubscribe(); + } + } + + return xResult; +} + +/*-----------------------------------------------------------*/ + +static MQTTStatus_t prvHandleResubscribe( void ) +{ + MQTTStatus_t xResult = MQTTBadParameter; + uint32_t ulIndex = 0U; + uint16_t usNumSubscriptions = 0U; + + /* These variables need to stay in scope until command completes. */ + static MQTTAgentSubscribeArgs_t xSubArgs = { 0 }; + static MQTTSubscribeInfo_t xSubInfo[ SUBSCRIPTION_MANAGER_MAX_SUBSCRIPTIONS ] = { 0 }; + static CommandInfo_t xCommandParams = { 0 }; + + /* Loop through each subscription in the subscription list and add a subscribe + * command to the command queue. */ + for( ulIndex = 0U; ulIndex < SUBSCRIPTION_MANAGER_MAX_SUBSCRIPTIONS; ulIndex++ ) + { + /* Check if there is a subscription in the subscription list. This demo + * doesn't check for duplicate subscriptions. */ + if( xGlobalSubscriptionList[ ulIndex ].usFilterStringLength != 0 ) + { + xSubInfo[ usNumSubscriptions ].pTopicFilter = xGlobalSubscriptionList[ ulIndex ].pcSubscriptionFilterString; + xSubInfo[ usNumSubscriptions ].topicFilterLength = xGlobalSubscriptionList[ ulIndex ].usFilterStringLength; + + /* QoS1 is used for all the subscriptions in this demo. */ + xSubInfo[ usNumSubscriptions ].qos = MQTTQoS1; + + LogInfo( ( "Resubscribe to the topic %.*s will be attempted.", + xSubInfo[ usNumSubscriptions ].topicFilterLength, + xSubInfo[ usNumSubscriptions ].pTopicFilter ) ); + + usNumSubscriptions++; + } + } + + if( usNumSubscriptions > 0U ) + { + xSubArgs.pSubscribeInfo = xSubInfo; + xSubArgs.numSubscriptions = usNumSubscriptions; + + /* The block time can be 0 as the command loop is not running at this point. */ + xCommandParams.blockTimeMs = 0U; + xCommandParams.cmdCompleteCallback = prvSubscriptionCommandCallback; + xCommandParams.pCmdCompleteCallbackContext = ( void * ) &xSubArgs; + + /* Enqueue subscribe to the command queue. These commands will be processed only + * when command loop starts. */ + xResult = MQTTAgent_Subscribe( &xGlobalMqttAgentContext, &xSubArgs, &xCommandParams ); + } + else + { + /* Mark the resubscribe as success if there is nothing to be subscribed. */ + xResult = MQTTSuccess; + } + + if( xResult != MQTTSuccess ) + { + LogError( ( "Failed to enqueue the MQTT subscribe command. xResult=%s.", + MQTT_Status_strerror( xResult ) ) ); + } + + return xResult; +} + +/*-----------------------------------------------------------*/ + +static void prvSubscriptionCommandCallback( void * pxCommandContext, + MQTTAgentReturnInfo_t * pxReturnInfo ) +{ + size_t lIndex = 0; + MQTTAgentSubscribeArgs_t * pxSubscribeArgs = ( MQTTAgentSubscribeArgs_t * ) pxCommandContext; + + /* If the return code is success, no further action is required as all the topic filters + * are already part of the subscription list. */ + if( pxReturnInfo->returnCode != MQTTSuccess ) + { + /* Check through each of the suback codes and determine if there are any failures. */ + for( lIndex = 0; lIndex < pxSubscribeArgs->numSubscriptions; lIndex++ ) + { + /* This demo doesn't attempt to resubscribe in the event that a SUBACK failed. */ + if( pxReturnInfo->pSubackCodes[ lIndex ] == MQTTSubAckFailure ) + { + LogError( ( "Failed to resubscribe to topic %.*s.", + pxSubscribeArgs->pSubscribeInfo[ lIndex ].topicFilterLength, + pxSubscribeArgs->pSubscribeInfo[ lIndex ].pTopicFilter ) ); + /* Remove subscription callback for unsubscribe. */ + removeSubscription( xGlobalSubscriptionList, + pxSubscribeArgs->pSubscribeInfo[ lIndex ].pTopicFilter, + pxSubscribeArgs->pSubscribeInfo[ lIndex ].topicFilterLength ); + } + } + + /* Hit an assert as some of the tasks won't be able to proceed correctly without + * the subscriptions. This logic will be updated with exponential backoff and retry. */ + configASSERT( pdTRUE ); + } +} + +/*-----------------------------------------------------------*/ + +static BaseType_t prvSocketConnect( NetworkContext_t * pxNetworkContext ) +{ + BaseType_t xConnected = pdFAIL; + BackoffAlgorithmStatus_t xBackoffAlgStatus = BackoffAlgorithmSuccess; + BackoffAlgorithmContext_t xReconnectParams = { 0 }; + uint16_t usNextRetryBackOff = 0U; + const TickType_t xTransportTimeout = 0UL; + + #if defined( democonfigUSE_TLS ) && ( democonfigUSE_TLS == 1 ) + TlsTransportStatus_t xNetworkStatus = TLS_TRANSPORT_CONNECT_FAILURE; + NetworkCredentials_t xNetworkCredentials = { 0 }; + + #ifdef democonfigUSE_AWS_IOT_CORE_BROKER + + /* ALPN protocols must be a NULL-terminated list of strings. Therefore, + * the first entry will contain the actual ALPN protocol string while the + * second entry must remain NULL. */ + char * pcAlpnProtocols[] = { NULL, NULL }; + + /* The ALPN string changes depending on whether username/password authentication is used. */ + #ifdef democonfigCLIENT_USERNAME + pcAlpnProtocols[ 0 ] = AWS_IOT_CUSTOM_AUTH_ALPN; + #else + pcAlpnProtocols[ 0 ] = AWS_IOT_MQTT_ALPN; + #endif + xNetworkCredentials.pAlpnProtos = pcAlpnProtocols; + #endif /* ifdef democonfigUSE_AWS_IOT_CORE_BROKER */ + + /* Set the credentials for establishing a TLS connection. */ + xNetworkCredentials.pRootCa = ( const unsigned char * ) democonfigROOT_CA_PEM; + xNetworkCredentials.rootCaSize = sizeof( democonfigROOT_CA_PEM ); + #ifdef democonfigCLIENT_CERTIFICATE_PEM + xNetworkCredentials.pClientCert = ( const unsigned char * ) democonfigCLIENT_CERTIFICATE_PEM; + xNetworkCredentials.clientCertSize = sizeof( democonfigCLIENT_CERTIFICATE_PEM ); + xNetworkCredentials.pPrivateKey = ( const unsigned char * ) democonfigCLIENT_PRIVATE_KEY_PEM; + xNetworkCredentials.privateKeySize = sizeof( democonfigCLIENT_PRIVATE_KEY_PEM ); + #endif + xNetworkCredentials.disableSni = democonfigDISABLE_SNI; + #else /* if defined( democonfigUSE_TLS ) && ( democonfigUSE_TLS == 1 ) */ + PlaintextTransportStatus_t xNetworkStatus = PLAINTEXT_TRANSPORT_CONNECT_FAILURE; + #endif /* if defined( democonfigUSE_TLS ) && ( democonfigUSE_TLS == 1 ) */ + + /* We will use a retry mechanism with an exponential backoff mechanism and + * jitter. That is done to prevent a fleet of IoT devices all trying to + * reconnect at exactly the same time should they become disconnected at + * the same time. We initialize reconnect attempts and interval here. */ + BackoffAlgorithm_InitializeParams( &xReconnectParams, + RETRY_BACKOFF_BASE_MS, + RETRY_MAX_BACKOFF_DELAY_MS, + RETRY_MAX_ATTEMPTS ); + + /* Attempt to connect to MQTT broker. If connection fails, retry after a + * timeout. Timeout value will exponentially increase until the maximum + * number of attempts are reached. + */ + do + { + /* Establish a TCP connection with the MQTT broker. This example connects to + * the MQTT broker as specified in democonfigMQTT_BROKER_ENDPOINT and + * democonfigMQTT_BROKER_PORT at the top of this file. */ + #if defined( democonfigUSE_TLS ) && ( democonfigUSE_TLS == 1 ) + LogInfo( ( "Creating a TLS connection to %s:%d.", + democonfigMQTT_BROKER_ENDPOINT, + democonfigMQTT_BROKER_PORT ) ); + xNetworkStatus = TLS_FreeRTOS_Connect( pxNetworkContext, + democonfigMQTT_BROKER_ENDPOINT, + democonfigMQTT_BROKER_PORT, + &xNetworkCredentials, + mqttexampleTRANSPORT_SEND_RECV_TIMEOUT_MS, + mqttexampleTRANSPORT_SEND_RECV_TIMEOUT_MS ); + xConnected = ( xNetworkStatus == TLS_TRANSPORT_SUCCESS ) ? pdPASS : pdFAIL; + #else /* if defined( democonfigUSE_TLS ) && ( democonfigUSE_TLS == 1 ) */ + LogInfo( ( "Creating a TCP connection to %s:%d.", + democonfigMQTT_BROKER_ENDPOINT, + democonfigMQTT_BROKER_PORT ) ); + xNetworkStatus = Plaintext_FreeRTOS_Connect( pxNetworkContext, + democonfigMQTT_BROKER_ENDPOINT, + democonfigMQTT_BROKER_PORT, + mqttexampleTRANSPORT_SEND_RECV_TIMEOUT_MS, + mqttexampleTRANSPORT_SEND_RECV_TIMEOUT_MS ); + xConnected = ( xNetworkStatus == PLAINTEXT_TRANSPORT_SUCCESS ) ? pdPASS : pdFAIL; + #endif /* if defined( democonfigUSE_TLS ) && ( democonfigUSE_TLS == 1 ) */ + + if( !xConnected ) + { + /* Get back-off value (in milliseconds) for the next connection retry. */ + xBackoffAlgStatus = BackoffAlgorithm_GetNextBackoff( &xReconnectParams, uxRand(), &usNextRetryBackOff ); + + if( xBackoffAlgStatus == BackoffAlgorithmSuccess ) + { + LogWarn( ( "Connection to the broker failed. " + "Retrying connection in %hu ms.", + usNextRetryBackOff ) ); + vTaskDelay( pdMS_TO_TICKS( usNextRetryBackOff ) ); + } + } + + if( xBackoffAlgStatus == BackoffAlgorithmRetriesExhausted ) + { + LogError( ( "Connection to the broker failed, all attempts exhausted." ) ); + } + } while( ( xConnected != pdPASS ) && ( xBackoffAlgStatus == BackoffAlgorithmSuccess ) ); + + /* Set the socket wakeup callback and ensure the read block time. */ + if( xConnected ) + { + ( void ) FreeRTOS_setsockopt( pxNetworkContext->pParams->tcpSocket, + 0, /* Level - Unused. */ + FREERTOS_SO_WAKEUP_CALLBACK, + ( void * ) prvMQTTClientSocketWakeupCallback, + sizeof( &( prvMQTTClientSocketWakeupCallback ) ) ); + + ( void ) FreeRTOS_setsockopt( pxNetworkContext->pParams->tcpSocket, + 0, + FREERTOS_SO_RCVTIMEO, + &xTransportTimeout, + sizeof( TickType_t ) ); + } + + return xConnected; +} + +/*-----------------------------------------------------------*/ + +static BaseType_t prvSocketDisconnect( NetworkContext_t * pxNetworkContext ) +{ + BaseType_t xDisconnected = pdFAIL; + + /* Set the wakeup callback to NULL since the socket will disconnect. */ + ( void ) FreeRTOS_setsockopt( pxNetworkContext->pParams->tcpSocket, + 0, /* Level - Unused. */ + FREERTOS_SO_WAKEUP_CALLBACK, + ( void * ) NULL, + sizeof( void * ) ); + + #if defined( democonfigUSE_TLS ) && ( democonfigUSE_TLS == 1 ) + LogInfo( ( "Disconnecting TLS connection.\n" ) ); + TLS_FreeRTOS_Disconnect( pxNetworkContext ); + xDisconnected = pdPASS; + #else + LogInfo( ( "Disconnecting TCP connection.\n" ) ); + PlaintextTransportStatus_t xNetworkStatus = PLAINTEXT_TRANSPORT_CONNECT_FAILURE; + xNetworkStatus = Plaintext_FreeRTOS_Disconnect( pxNetworkContext ); + xDisconnected = ( xNetworkStatus == PLAINTEXT_TRANSPORT_SUCCESS ) ? pdPASS : pdFAIL; + #endif + return xDisconnected; +} + +/*-----------------------------------------------------------*/ + +static void prvMQTTClientSocketWakeupCallback( Socket_t pxSocket ) +{ + CommandInfo_t xCommandParams = { 0 }; + + /* Just to avoid compiler warnings. The socket is not used but the function + * prototype cannot be changed because this is a callback function. */ + ( void ) pxSocket; + + /* A socket used by the MQTT task may need attention. Send an event + * to the MQTT task to make sure the task is not blocked on xCommandQueue. */ + if( ( uxQueueMessagesWaiting( xCommandQueue.queue ) == 0U ) && ( FreeRTOS_recvcount( pxSocket ) > 0 ) ) + { + /* Don't block as this is called from the context of the IP task. */ + xCommandParams.blockTimeMs = 0U; + MQTTAgent_ProcessLoop( &xGlobalMqttAgentContext, &xCommandParams ); + } +} + +/*-----------------------------------------------------------*/ + +static void prvIncomingPublishCallback( MQTTAgentContext_t * pMqttAgentContext, + uint16_t packetId, + MQTTPublishInfo_t * pxPublishInfo ) +{ + bool xPublishHandled = false; + char cOriginalChar, * pcLocation; + + ( void ) packetId; + + /* Fan out the incoming publishes to the callbacks registered using + * subscription manager. */ + xPublishHandled = handleIncomingPublishes( ( SubscriptionElement_t * ) pMqttAgentContext->pIncomingCallbackContext, + pxPublishInfo ); + + /* If there are no callbacks to handle the incoming publishes, + * handle it as an unsolicited publish. */ + if( xPublishHandled != true ) + { + /* Ensure the topic string is terminated for printing. This will over- + * write the message ID, which is restored afterwards. */ + pcLocation = ( char * ) &( pxPublishInfo->pTopicName[ pxPublishInfo->topicNameLength ] ); + cOriginalChar = *pcLocation; + *pcLocation = 0x00; + LogWarn( ( "WARN: Received an unsolicited publish from topic %s", pxPublishInfo->pTopicName ) ); + *pcLocation = cOriginalChar; + } +} + +/*-----------------------------------------------------------*/ + +static void prvMQTTAgentTask( void * pvParameters ) +{ + BaseType_t xNetworkResult = pdFAIL; + MQTTStatus_t xMQTTStatus = MQTTSuccess, xConnectStatus = MQTTSuccess; + MQTTContext_t * pMqttContext = &( xGlobalMqttAgentContext.mqttContext ); + + ( void ) pvParameters; + + do + { + /* MQTTAgent_CommandLoop() is effectively the agent implementation. It + * will manage the MQTT protocol until such time that an error occurs, + * which could be a disconnect. If an error occurs the MQTT context on + * which the error happened is returned so there can be an attempt to + * clean up and reconnect however the application writer prefers. */ + xMQTTStatus = MQTTAgent_CommandLoop( &xGlobalMqttAgentContext ); + + /* Success is returned for disconnect or termination. The socket should + * be disconnected. */ + if( ( xMQTTStatus == MQTTSuccess ) && ( xGlobalMqttAgentContext.mqttContext.connectStatus == MQTTNotConnected ) ) + { + /* MQTT Disconnect. Disconnect the socket. */ + xNetworkResult = prvSocketDisconnect( &xNetworkContext ); + configASSERT( xNetworkResult == pdPASS ); + } + else if( xMQTTStatus == MQTTSuccess ) + { + /* MQTTAgent_Terminate() was called, but MQTT was not disconnected. */ + xMQTTStatus = MQTT_Disconnect( &( xGlobalMqttAgentContext.mqttContext ) ); + configASSERT( xMQTTStatus == MQTTSuccess ); + xNetworkResult = prvSocketDisconnect( &xNetworkContext ); + configASSERT( xNetworkResult == pdPASS ); + } + /* Error. */ + else + { + /* Reconnect TCP. */ + xNetworkResult = prvSocketDisconnect( &xNetworkContext ); + configASSERT( xNetworkResult == pdPASS ); + xNetworkResult = prvSocketConnect( &xNetworkContext ); + configASSERT( xNetworkResult == pdPASS ); + pMqttContext->connectStatus = MQTTNotConnected; + /* MQTT Connect with a persistent session. */ + xConnectStatus = prvMQTTConnect( false ); + configASSERT( xConnectStatus == MQTTSuccess ); + } + } while( xMQTTStatus != MQTTSuccess ); +} + +/*-----------------------------------------------------------*/ + +static void prvConnectToMQTTBroker( void ) +{ + BaseType_t xNetworkStatus = pdFAIL; + MQTTStatus_t xMQTTStatus; + + /* Connect a TCP socket to the broker. */ + xNetworkStatus = prvSocketConnect( &xNetworkContext ); + configASSERT( xNetworkStatus == pdPASS ); + + /* Initialize the MQTT context with the buffer and transport interface. */ + xMQTTStatus = prvMQTTInit(); + configASSERT( xMQTTStatus == MQTTSuccess ); + + /* Form an MQTT connection without a persistent session. */ + xMQTTStatus = prvMQTTConnect( true ); + configASSERT( xMQTTStatus == MQTTSuccess ); +} +/*-----------------------------------------------------------*/ + +static void prvConnectAndCreateDemoTasks( void * pvParameters ) +{ + ( void ) pvParameters; + + /* Miscellaneous initialization. */ + ulGlobalEntryTimeMs = prvGetTimeMs(); + + /* Set the pParams member of the network context with desired transport. */ + #if defined( democonfigUSE_TLS ) && ( democonfigUSE_TLS == 1 ) + xNetworkContext.pParams = &xTlsTransportParams; + #else + xNetworkContext.pParams = &xPlaintextTransportParams; + #endif + + /* Create the TCP connection to the broker, then the MQTT connection to the + * same. */ + prvConnectToMQTTBroker(); + + /* Selectively create demo tasks as per the compile time constant settings. */ + + #if ( democonfigNUM_SIMPLE_SUB_PUB_TASKS_TO_CREATE > 0 ) + { + vStartSimpleSubscribePublishTask( democonfigNUM_SIMPLE_SUB_PUB_TASKS_TO_CREATE, + democonfigSIMPLE_SUB_PUB_TASK_STACK_SIZE, + tskIDLE_PRIORITY ); + } + #endif + + + /* This task has nothing left to do, so rather than create the MQTT + * agent as a separate thread, it simply calls the function that implements + * the agent - in effect turning itself into the agent. */ + prvMQTTAgentTask( NULL ); + + /* Should not get here. Force an assert if the task returns from + * prvMQTTAgentTask(). */ + configASSERT( pvParameters == ( void * ) ~1 ); +} + +/*-----------------------------------------------------------*/ + +static uint32_t prvGetTimeMs( void ) +{ + TickType_t xTickCount = 0; + uint32_t ulTimeMs = 0UL; + + /* Get the current tick count. */ + xTickCount = xTaskGetTickCount(); + + /* Convert the ticks to milliseconds. */ + ulTimeMs = ( uint32_t ) xTickCount * mqttexampleMILLISECONDS_PER_TICK; + + /* Reduce ulGlobalEntryTimeMs from obtained time so as to always return the + * elapsed time in the application. */ + ulTimeMs = ( uint32_t ) ( ulTimeMs - ulGlobalEntryTimeMs ); + + return ulTimeMs; +} + +/*-----------------------------------------------------------*/ diff --git a/FreeRTOS-Plus/Demo/coreMQTT_Windows_Simulator/MQTT_Multitask/DemoTasks/simple_sub_pub_demo.c b/FreeRTOS-Plus/Demo/coreMQTT_Windows_Simulator/MQTT_Multitask/DemoTasks/simple_sub_pub_demo.c new file mode 100644 index 0000000000..1c62ff6dc4 --- /dev/null +++ b/FreeRTOS-Plus/Demo/coreMQTT_Windows_Simulator/MQTT_Multitask/DemoTasks/simple_sub_pub_demo.c @@ -0,0 +1,528 @@ +/* + * FreeRTOS V202012.00 + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * https://www.FreeRTOS.org + * https://github.com/FreeRTOS + * + */ + +/* + * This file demonstrates numerous tasks all of which use the MQTT agent API + * to send unique MQTT payloads to unique topics over the same MQTT connection + * to the same MQTT agent. Some tasks use QoS0 and others QoS1. + * + * Each created task is a unique instance of the task implemented by + * prvSimpleSubscribePublishTask(). prvSimpleSubscribePublishTask() + * subscribes to a topic then periodically publishes a message to the same + * topic to which it has subscribed. The command context sent to + * MQTTAgent_Publish() contains a unique number that is sent back to the task + * as a task notification from the callback function that executes when the + * PUBLISH operation is acknowledged (or just sent in the case of QoS 0). The + * task checks the number it receives from the callback equals the number it + * previously set in the command context before printing out either a success + * or failure message. + */ + + +/* Standard includes. */ +#include +#include +#include + +/* Kernel includes. */ +#include "FreeRTOS.h" +#include "task.h" +#include "queue.h" + +/* Demo Specific configs. */ +#include "demo_config.h" + +/* MQTT library includes. */ +#include "core_mqtt.h" + +/* MQTT agent include. */ +#include "mqtt_agent.h" + +/* Subscription manager header include. */ +#include "subscription_manager.h" + +/** + * @brief This demo uses task notifications to signal tasks from MQTT callback + * functions. mqttexampleMS_TO_WAIT_FOR_NOTIFICATION defines the time, in ticks, + * to wait for such a callback. + */ +#define mqttexampleMS_TO_WAIT_FOR_NOTIFICATION ( 10000 ) + +/** + * @brief Size of statically allocated buffers for holding topic names and + * payloads. + */ +#define mqttexampleSTRING_BUFFER_LENGTH ( 100 ) + +/** + * @brief Delay for each task between publishes. + */ +#define mqttexampleDELAY_BETWEEN_PUBLISH_OPERATIONS_MS ( 1000U ) + +/** + * @brief Number of publishes done by each task in this demo. + */ +#define mqttexamplePUBLISH_COUNT ( 0xffffffffUL ) + +/** + * @brief The maximum amount of time in milliseconds to wait for the commands + * to be posted to the MQTT agent should the MQTT agent's command queue be full. + * Tasks wait in the Blocked state, so don't use any CPU time. + */ +#define mqttexampleMAX_COMMAND_SEND_BLOCK_TIME_MS ( 500 ) + +/*-----------------------------------------------------------*/ + +/** + * @brief Defines the structure to use as the command callback context in this + * demo. + */ +struct CommandContext +{ + MQTTStatus_t xReturnStatus; + TaskHandle_t xTaskToNotify; + uint32_t ulNotificationValue; + void * pArgs; +}; + +/*-----------------------------------------------------------*/ + +/** + * @brief Passed into MQTTAgent_Subscribe() as the callback to execute when the + * broker ACKs the SUBSCRIBE message. Its implementation sends a notification + * to the task that called MQTTAgent_Subscribe() to let the task know the + * SUBSCRIBE operation completed. It also sets the xReturnStatus of the + * structure passed in as the command's context to the value of the + * xReturnStatus parameter - which enables the task to check the status of the + * operation. + * + * See https://freertos.org/mqtt/mqtt-agent-demo.html#example_mqtt_api_call + * + * @param[in] pxCommandContext Context of the initial command. + * @param[in].xReturnStatus The result of the command. + */ +static void prvSubscribeCommandCallback( void * pxCommandContext, + MQTTAgentReturnInfo_t * pxReturnInfo ); + +/** + * @brief Passed into MQTTAgent_Publish() as the callback to execute when the + * broker ACKs the PUBLISH message. Its implementation sends a notification + * to the task that called MQTTAgent_Publish() to let the task know the + * PUBLISH operation completed. It also sets the xReturnStatus of the + * structure passed in as the command's context to the value of the + * xReturnStatus parameter - which enables the task to check the status of the + * operation. + * + * See https://freertos.org/mqtt/mqtt-agent-demo.html#example_mqtt_api_call + * + * @param[in] pxCommandContext Context of the initial command. + * @param[in].xReturnStatus The result of the command. + */ +static void prvPublishCommandCallback( CommandContext_t * pxCommandContext, + MQTTAgentReturnInfo_t * pxReturnInfo ); + +/** + * @brief Called by the task to wait for a notification from a callback function + * after the task first executes either MQTTAgent_Publish()* or + * MQTTAgent_Subscribe(). + * + * See https://freertos.org/mqtt/mqtt-agent-demo.html#example_mqtt_api_call + * + * @param[in] pxCommandContext Context of the initial command. + * @param[out] pulNotifiedValue The task's notification value after it receives + * a notification from the callback. + * + * @return pdTRUE if the task received a notification, otherwise pdFALSE. + */ +static BaseType_t prvWaitForCommandAcknowledgment( uint32_t * pulNotifiedValue ); + +/** + * @brief Passed into MQTTAgent_Subscribe() as the callback to execute when + * there is an incoming publish on the topic being subscribed to. Its + * implementation just logs information about the incoming publish including + * the publish messages source topic and payload. + * + * See https://freertos.org/mqtt/mqtt-agent-demo.html#example_mqtt_api_call + * + * @param[in] pvIncomingPublishCallbackContext Context of the initial command. + * @param[in] pxPublishInfo Deserialized publish. + */ +static void prvIncomingPublishCallback( void * pvIncomingPublishCallbackContext, + MQTTPublishInfo_t * pxPublishInfo ); + +/** + * @brief Subscribe to the topic the demo task will also publish to - that + * results in all outgoing publishes being published back to the task + * (effectively echoed back). + * + * @param[in] xQoS The quality of service (QoS) to use. Can be zero or one + * for all MQTT brokers. Can also be QoS2 if supported by the broker. AWS IoT + * does not support QoS2. + */ +static bool prvSubscribeToTopic( MQTTQoS_t xQoS, + char * pcTopicFilter ); + +/** + * @brief The function that implements the task demonstrated by this file. + */ +static void prvSimpleSubscribePublishTask( void * pvParameters ); + +/*-----------------------------------------------------------*/ + +/** + * @brief The MQTT agent manages the MQTT contexts. This set the handle to the + * context used by this demo. + */ +extern MQTTAgentContext_t xGlobalMqttAgentContext; + +/*-----------------------------------------------------------*/ + +/** + * @brief The buffer to hold the topic filter. The topic is generated at runtime + * by adding the task names. + * + * @note The topic strings must persist until unsubscribed. + */ +static char topicBuf[ democonfigNUM_SIMPLE_SUB_PUB_TASKS_TO_CREATE ][ mqttexampleSTRING_BUFFER_LENGTH ]; + +/*-----------------------------------------------------------*/ + +void vStartSimpleSubscribePublishTask( uint32_t ulNumberToCreate, + configSTACK_DEPTH_TYPE uxStackSize, + UBaseType_t uxPriority ) +{ + char pcTaskNameBuf[ 15 ]; + uint32_t ulTaskNumber; + + /* Each instance of prvSimpleSubscribePublishTask() generates a unique name + * and topic filter for itself from the number passed in as the task + * parameter. */ + /* Create a few instances of vSimpleSubscribePublishTask(). */ + for( ulTaskNumber = 0; ulTaskNumber < ulNumberToCreate; ulTaskNumber++ ) + { + memset( pcTaskNameBuf, 0x00, sizeof( pcTaskNameBuf ) ); + snprintf( pcTaskNameBuf, 10, "SubPub%d", ( int ) ulTaskNumber ); + xTaskCreate( prvSimpleSubscribePublishTask, + pcTaskNameBuf, + uxStackSize, + ( void * ) ulTaskNumber, + uxPriority, + NULL ); + } +} + +/*-----------------------------------------------------------*/ + +static void prvPublishCommandCallback( CommandContext_t * pxCommandContext, + MQTTAgentReturnInfo_t * pxReturnInfo ) +{ + /* Store the result in the application defined context so the task that + * initiated the publish can check the operation's status. */ + pxCommandContext->xReturnStatus = pxReturnInfo->returnCode; + + if( pxCommandContext->xTaskToNotify != NULL ) + { + /* Send the context's ulNotificationValue as the notification value so + * the receiving task can check the value it set in the context matches + * the value it receives in the notification. */ + xTaskNotify( pxCommandContext->xTaskToNotify, + pxCommandContext->ulNotificationValue, + eSetValueWithOverwrite ); + } +} + +/*-----------------------------------------------------------*/ + +static void prvSubscribeCommandCallback( void * pxCommandContext, + MQTTAgentReturnInfo_t * pxReturnInfo ) +{ + bool xSubscriptionAdded = false; + CommandContext_t * pxApplicationDefinedContext = ( CommandContext_t * ) pxCommandContext; + MQTTAgentSubscribeArgs_t * pxSubscribeArgs = ( MQTTAgentSubscribeArgs_t * ) pxApplicationDefinedContext->pArgs; + + /* Store the result in the application defined context so the task that + * initiated the subscribe can check the operation's status. Also send the + * status as the notification value. These things are just done for + * demonstration purposes. */ + pxApplicationDefinedContext->xReturnStatus = pxReturnInfo->returnCode; + + /* Check if the subscribe operation is a success. Only one topic is + * subscribed by this demo. */ + if( pxReturnInfo->returnCode == MQTTSuccess ) + { + /* Add subscription so that incoming publishes are routed to the application + * callback. */ + xSubscriptionAdded = addSubscription( ( SubscriptionElement_t * ) xGlobalMqttAgentContext.pIncomingCallbackContext, + pxSubscribeArgs->pSubscribeInfo->pTopicFilter, + pxSubscribeArgs->pSubscribeInfo->topicFilterLength, + prvIncomingPublishCallback, + NULL ); + + if( xSubscriptionAdded == false ) + { + LogError( ( "Failed to register an incoming publish callback for topic %.*s.", + pxSubscribeArgs->pSubscribeInfo->topicFilterLength, + pxSubscribeArgs->pSubscribeInfo->pTopicFilter ) ); + } + } + + xTaskNotify( pxApplicationDefinedContext->xTaskToNotify, + ( uint32_t ) ( pxReturnInfo->returnCode ), + eSetValueWithOverwrite ); +} + +/*-----------------------------------------------------------*/ + +static BaseType_t prvWaitForCommandAcknowledgment( uint32_t * pulNotifiedValue ) +{ + BaseType_t xReturn; + + /* Wait for this task to get notified, passing out the value it gets + * notified with. */ + xReturn = xTaskNotifyWait( 0, + 0, + pulNotifiedValue, + pdMS_TO_TICKS( mqttexampleMS_TO_WAIT_FOR_NOTIFICATION ) ); + return xReturn; +} + +/*-----------------------------------------------------------*/ + +static void prvIncomingPublishCallback( void * pvIncomingPublishCallbackContext, + MQTTPublishInfo_t * pxPublishInfo ) +{ + static char cTerminatedString[ mqttexampleSTRING_BUFFER_LENGTH ]; + + ( void ) pvIncomingPublishCallbackContext; + + /* Create a message that contains the incoming MQTT payload to the logger, + * terminating the string first. */ + if( pxPublishInfo->payloadLength < mqttexampleSTRING_BUFFER_LENGTH ) + { + memcpy( ( void * ) cTerminatedString, pxPublishInfo->pPayload, pxPublishInfo->payloadLength ); + cTerminatedString[ pxPublishInfo->payloadLength ] = 0x00; + } + else + { + memcpy( ( void * ) cTerminatedString, pxPublishInfo->pPayload, mqttexampleSTRING_BUFFER_LENGTH ); + cTerminatedString[ mqttexampleSTRING_BUFFER_LENGTH - 1 ] = 0x00; + } + + LogInfo( ( "Received incoming publish message %s", cTerminatedString ) ); +} + +/*-----------------------------------------------------------*/ + +static bool prvSubscribeToTopic( MQTTQoS_t xQoS, + char * pcTopicFilter ) +{ + MQTTStatus_t xCommandAdded; + BaseType_t xCommandAcknowledged = pdFALSE; + uint32_t ulSubscribeMessageID; + MQTTAgentSubscribeArgs_t xSubscribeArgs; + MQTTSubscribeInfo_t xSubscribeInfo; + static int32_t ulNextSubscribeMessageID = 0; + CommandContext_t xApplicationDefinedContext = { 0 }; + CommandInfo_t xCommandParams = { 0 }; + + /* Create a unique number of the subscribe that is about to be sent. The number + * is used as the command context and is sent back to this task as a notification + * in the callback that executed upon receipt of the subscription acknowledgment. + * That way this task can match an acknowledgment to a subscription. */ + xTaskNotifyStateClear( NULL ); + taskENTER_CRITICAL(); + { + ulNextSubscribeMessageID++; + ulSubscribeMessageID = ulNextSubscribeMessageID; + } + taskEXIT_CRITICAL(); + + /* Complete the subscribe information. The topic string must persist for + * duration of subscription! */ + xSubscribeInfo.pTopicFilter = pcTopicFilter; + xSubscribeInfo.topicFilterLength = ( uint16_t ) strlen( pcTopicFilter ); + xSubscribeInfo.qos = xQoS; + xSubscribeArgs.pSubscribeInfo = &xSubscribeInfo; + xSubscribeArgs.numSubscriptions = 1; + + /* Complete an application defined context associated with this subscribe message. + * This gets updated in the callback function so the variable must persist until + * the callback executes. */ + xApplicationDefinedContext.ulNotificationValue = ulNextSubscribeMessageID; + xApplicationDefinedContext.xTaskToNotify = xTaskGetCurrentTaskHandle(); + xApplicationDefinedContext.pArgs = ( void * ) &xSubscribeArgs; + + xCommandParams.blockTimeMs = mqttexampleMAX_COMMAND_SEND_BLOCK_TIME_MS; + xCommandParams.cmdCompleteCallback = prvSubscribeCommandCallback; + xCommandParams.pCmdCompleteCallbackContext = ( void * ) &xApplicationDefinedContext; + + /* Loop in case the queue used to communicate with the MQTT agent is full and + * attempts to post to it time out. The queue will not become full if the + * priority of the MQTT agent task is higher than the priority of the task + * calling this function. */ + LogInfo( ( "Sending subscribe request to agent for topic filter: %s with id %d", + pcTopicFilter, + ( int ) ulSubscribeMessageID ) ); + + do + { + /* TODO: prvIncomingPublish as publish callback. */ + xCommandAdded = MQTTAgent_Subscribe( &xGlobalMqttAgentContext, + &xSubscribeArgs, + &xCommandParams ); + } while( xCommandAdded != MQTTSuccess ); + + /* Wait for acks to the subscribe message - this is optional but done here + * so the code below can check the notification sent by the callback matches + * the ulNextSubscribeMessageID value set in the context above. */ + xCommandAcknowledged = prvWaitForCommandAcknowledgment( NULL ); + + /* Check both ways the status was passed back just for demonstration + * purposes. */ + if( ( xCommandAcknowledged != pdTRUE ) || + ( xApplicationDefinedContext.xReturnStatus != MQTTSuccess ) ) + { + LogInfo( ( "Error or timed out waiting for ack to subscribe message topic %s", + pcTopicFilter ) ); + } + else + { + LogInfo( ( "Received subscribe ack for topic %s containing ID %d", + pcTopicFilter, + ( int ) xApplicationDefinedContext.ulNotificationValue ) ); + } + + return xCommandAcknowledged; +} + +/*-----------------------------------------------------------*/ + +static void prvSimpleSubscribePublishTask( void * pvParameters ) +{ + extern UBaseType_t uxRand( void ); + MQTTPublishInfo_t xPublishInfo = { 0 }; + char payloadBuf[ mqttexampleSTRING_BUFFER_LENGTH ]; + char taskName[ mqttexampleSTRING_BUFFER_LENGTH ]; + CommandContext_t xCommandContext; + uint32_t ulNotification = 0U, ulValueToNotify = 0UL; + MQTTStatus_t xCommandAdded; + uint32_t ulTaskNumber = ( uint32_t ) pvParameters; + MQTTQoS_t xQoS; + TickType_t xTicksToDelay; + CommandInfo_t xCommandParams = { 0 }; + char * pcTopicBuffer = topicBuf[ ulTaskNumber ]; + + /* Have different tasks use different QoS. 0 and 1. 2 can also be used + * if supported by the broker. */ + xQoS = ( MQTTQoS_t ) ( ulTaskNumber % 2UL ); + + /* Create a unique name for this task from the task number that is passed into + * the task using the task's parameter. */ + snprintf( taskName, mqttexampleSTRING_BUFFER_LENGTH, "Publisher%d", ( int ) ulTaskNumber ); + + /* Create a topic name for this task to publish to. */ + snprintf( pcTopicBuffer, mqttexampleSTRING_BUFFER_LENGTH, "/filter/%s", taskName ); + + /* Subscribe to the same topic to which this task will publish. That will + * result in each published message being published from the server back to + * the target. */ + prvSubscribeToTopic( xQoS, pcTopicBuffer ); + + /* Configure the publish operation. */ + memset( ( void * ) &xPublishInfo, 0x00, sizeof( xPublishInfo ) ); + xPublishInfo.qos = xQoS; + xPublishInfo.pTopicName = pcTopicBuffer; + xPublishInfo.topicNameLength = ( uint16_t ) strlen( pcTopicBuffer ); + xPublishInfo.pPayload = payloadBuf; + + /* Store the handler to this task in the command context so the callback + * that executes when the command is acknowledged can send a notification + * back to this task. */ + memset( ( void * ) &xCommandContext, 0x00, sizeof( xCommandContext ) ); + xCommandContext.xTaskToNotify = xTaskGetCurrentTaskHandle(); + + xCommandParams.blockTimeMs = mqttexampleMAX_COMMAND_SEND_BLOCK_TIME_MS; + xCommandParams.cmdCompleteCallback = prvPublishCommandCallback; + xCommandParams.pCmdCompleteCallbackContext = &xCommandContext; + + /* For a finite number of publishes... */ + for( ulValueToNotify = 0UL; ulValueToNotify < mqttexamplePUBLISH_COUNT; ulValueToNotify++ ) + { + /* Create a payload to send with the publish message. This contains + * the task name and an incrementing number. */ + snprintf( payloadBuf, + mqttexampleSTRING_BUFFER_LENGTH, + "%s publishing message %d", + taskName, + ( int ) ulValueToNotify ); + + xPublishInfo.payloadLength = ( uint16_t ) strlen( payloadBuf ); + + /* Also store the incrementing number in the command context so it can + * be accessed by the callback that executes when the publish operation + * is acknowledged. */ + xCommandContext.ulNotificationValue = ulValueToNotify; + + LogInfo( ( "Sending publish request to agent with message \"%s\" on topic \"%s\"", + payloadBuf, + pcTopicBuffer ) ); + + /* To ensure ulNotification doesn't accidentally hold the expected value + * as it is to be checked against the value sent from the callback.. */ + ulNotification = ~ulValueToNotify; + + xCommandAdded = MQTTAgent_Publish( &xGlobalMqttAgentContext, + &xPublishInfo, + &xCommandParams ); + configASSERT( xCommandAdded == MQTTSuccess ); + + /* For QoS 1 and 2, wait for the publish acknowledgment. For QoS0, + * wait for the publish to be sent. */ + LogInfo( ( "Task %s waiting for publish %d to complete.", + taskName, + ulValueToNotify ) ); + prvWaitForCommandAcknowledgment( &ulNotification ); + + /* The value received by the callback that executed when the publish was + * completed came from the context passed into MQTTAgent_Publish() above, + * so should match the value set in the context above. */ + configASSERT( ulNotification == ulValueToNotify ); + + /* Log statement to indicate successful reception of publish. */ + LogInfo( ( "Demo completed successfully.\r\n" ) ); + LogInfo( ( "Short delay before next publish... \r\n\r\n" ) ); + + /* Add a little randomness into the delay so the tasks don't remain + * in lockstep. */ + xTicksToDelay = pdMS_TO_TICKS( mqttexampleDELAY_BETWEEN_PUBLISH_OPERATIONS_MS ) + + ( uxRand() % 0xff ); + vTaskDelay( xTicksToDelay ); + } + + /* Delete the task if it is complete. */ + LogInfo( ( "Task %s completed.", taskName ) ); + vTaskDelete( NULL ); +} diff --git a/FreeRTOS-Plus/Demo/coreMQTT_Windows_Simulator/MQTT_Multitask/WIN32.vcxproj b/FreeRTOS-Plus/Demo/coreMQTT_Windows_Simulator/MQTT_Multitask/WIN32.vcxproj index 32d52291c9..83953b69b6 100644 --- a/FreeRTOS-Plus/Demo/coreMQTT_Windows_Simulator/MQTT_Multitask/WIN32.vcxproj +++ b/FreeRTOS-Plus/Demo/coreMQTT_Windows_Simulator/MQTT_Multitask/WIN32.vcxproj @@ -58,7 +58,7 @@ Disabled - ..\..\..\..\Source\FreeRTOS-Plus-Trace\Include;..\..\..\..\FreeRTOS-Plus\Source\FreeRTOS-Plus-TCP\include;..\..\..\..\FreeRTOS-Plus\Source\FreeRTOS-Plus-TCP\portable\BufferManagement;..\..\..\..\FreeRTOS-Plus\Source\FreeRTOS-Plus-TCP\portable\Compiler\MSVC;..\..\..\..\FreeRTOS-Plus\Source\Utilities\logging;..\common\WinPCap;..\..\..\..\FreeRTOS\Source\include;..\..\..\..\FreeRTOS\Source\portable\MSVC-MingW;..\..\..\Source\Application-Protocols\coreMQTT\source\include;..\..\..\Source\Application-Protocols\coreMQTT\source\interface;..\..\..\Source\Utilities\backoff_algorithm\source\include;..\..\..\Source\Application-Protocols\network_transport\freertos_plus_tcp;..\..\..\Source\Application-Protocols\network_transport\freertos_plus_tcp\using_plaintext;..\..\..\Source\Application-Protocols\network_transport\freertos_plus_tcp\using_mbedtls;..\..\..\Source\Utilities\mbedtls_freertos;..\..\..\..\Source\mbedtls_utils;..\..\..\ThirdParty\mbedtls\include;.;%(AdditionalIncludeDirectories) + ..\..\..\..\Source\FreeRTOS-Plus-Trace\Include;..\..\..\..\FreeRTOS-Plus\Source\FreeRTOS-Plus-TCP\include;..\..\..\..\FreeRTOS-Plus\Source\FreeRTOS-Plus-TCP\portable\BufferManagement;..\..\..\..\FreeRTOS-Plus\Source\FreeRTOS-Plus-TCP\portable\Compiler\MSVC;..\..\..\..\FreeRTOS-Plus\Source\Utilities\logging;..\common\WinPCap;..\..\..\..\FreeRTOS\Source\include;..\..\..\..\FreeRTOS\Source\portable\MSVC-MingW;..\..\..\Source\Application-Protocols\coreMQTT\source\include;..\..\..\Source\Application-Protocols\coreMQTT\source\interface;..\..\..\Source\Utilities\backoff_algorithm\source\include;..\..\..\Source\Application-Protocols\network_transport\freertos_plus_tcp;..\..\..\Source\Application-Protocols\network_transport\freertos_plus_tcp\using_plaintext;..\..\..\Source\Application-Protocols\network_transport\freertos_plus_tcp\using_mbedtls;..\..\..\Source\Utilities\mbedtls_freertos;..\..\..\..\Source\mbedtls_utils;..\..\..\ThirdParty\mbedtls\include;..\..\..\Source\Application-Protocols\coreMQTT-Agent\source\include;..\..\Common\coreMQTT_Agent_Interface\include/;subscription-manager;.;%(AdditionalIncludeDirectories) MBEDTLS_CONFIG_FILE="mbedtls_config.h";WIN32;_DEBUG;_CONSOLE;_WIN32_WINNT=0x0500;WINVER=0x400;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) false EnableFastChecks @@ -157,6 +157,8 @@ + + @@ -483,8 +485,12 @@ TurnOffAllWarnings + + - + + + @@ -513,6 +519,9 @@ + + + @@ -604,13 +613,16 @@ + + + - + \ No newline at end of file diff --git a/FreeRTOS-Plus/Demo/coreMQTT_Windows_Simulator/MQTT_Multitask/WIN32.vcxproj.filters b/FreeRTOS-Plus/Demo/coreMQTT_Windows_Simulator/MQTT_Multitask/WIN32.vcxproj.filters index dbfe60ff40..711d29786f 100644 --- a/FreeRTOS-Plus/Demo/coreMQTT_Windows_Simulator/MQTT_Multitask/WIN32.vcxproj.filters +++ b/FreeRTOS-Plus/Demo/coreMQTT_Windows_Simulator/MQTT_Multitask/WIN32.vcxproj.filters @@ -77,6 +77,21 @@ {0e25b4a0-53aa-4abd-a4bc-b3658c9c3bc9} + + {a081aee5-1e00-443e-98b5-ca297fc5b4ac} + + + {01a3e0d6-b836-4f1d-8d9d-e83c247a50c7} + + + {7113221b-1ca6-4504-9993-75664336ef8a} + + + {d6d1e5f0-f23c-47d1-ba70-3dbc26acf362} + + + {775bbd2e-ac31-4bb4-9de2-468c05269332} + @@ -375,7 +390,7 @@ - + DemoTasks @@ -405,6 +420,24 @@ FreeRTOS+\FreeRTOS IoT Libraries\standard\coreMQTT + + DemoTasks + + + FreeRTOS+\FreeRTOS IoT Libraries\standard\coreMQTT-Agent + + + FreeRTOS+\FreeRTOS IoT Libraries\standard\coreMQTT-Agent + + + FreeRTOS+\FreeRTOS IoT Libraries\platform\mqtt-agent-interface + + + FreeRTOS+\FreeRTOS IoT Libraries\platform\mqtt-agent-interface + + + subscription-manager + @@ -773,5 +806,23 @@ FreeRTOS+\FreeRTOS IoT Libraries\platform\mbedtls + + FreeRTOS+\FreeRTOS IoT Libraries\standard\coreMQTT-Agent\include + + + FreeRTOS+\FreeRTOS IoT Libraries\standard\coreMQTT-Agent\include + + + FreeRTOS+\FreeRTOS IoT Libraries\standard\coreMQTT-Agent\include + + + FreeRTOS+\FreeRTOS IoT Libraries\platform\mqtt-agent-interface\include + + + FreeRTOS+\FreeRTOS IoT Libraries\platform\mqtt-agent-interface\include + + + subscription-manager + - + \ No newline at end of file diff --git a/FreeRTOS-Plus/Demo/coreMQTT_Windows_Simulator/MQTT_Multitask/core_mqtt_config.h b/FreeRTOS-Plus/Demo/coreMQTT_Windows_Simulator/MQTT_Multitask/core_mqtt_config.h index 8842e44f45..733d22eb4b 100644 --- a/FreeRTOS-Plus/Demo/coreMQTT_Windows_Simulator/MQTT_Multitask/core_mqtt_config.h +++ b/FreeRTOS-Plus/Demo/coreMQTT_Windows_Simulator/MQTT_Multitask/core_mqtt_config.h @@ -19,10 +19,9 @@ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * - * http://www.FreeRTOS.org - * http://aws.amazon.com/freertos + * https://www.FreeRTOS.org + * https://github.com/FreeRTOS * - * 1 tab == 4 spaces! */ #ifndef CORE_MQTT_CONFIG_H #define CORE_MQTT_CONFIG_H @@ -75,6 +74,35 @@ extern void vLoggingPrintf( const char * pcFormatString, * macro sets the limit on how many simultaneous PUBLISH states an MQTT * context maintains. */ -#define MQTT_STATE_ARRAY_MAX_COUNT 20U +#define MQTT_STATE_ARRAY_MAX_COUNT 20U + +/*********************** coreMQTT Agent Configurations **********************/ +/** + * @brief The maximum number of pending acknowledgments to track for a single + * connection. + * + * @note The MQTT agent tracks MQTT commands (such as PUBLISH and SUBSCRIBE) th + * at are still waiting to be acknowledged. MQTT_AGENT_MAX_OUTSTANDING_ACKS set + * the maximum number of acknowledgments that can be outstanding at any one time. + * The higher this number is the greater the agent's RAM consumption will be. + */ +#define MQTT_AGENT_MAX_OUTSTANDING_ACKS ( 20U ) + +/** + * @brief Time in MS that the MQTT agent task will wait in the Blocked state (so + * not using any CPU time) for a command to arrive in its command queue before + * exiting the blocked state so it can call MQTT_ProcessLoop(). + * + * @note It is important MQTT_ProcessLoop() is called often if there is known + * MQTT traffic, but calling it too often can take processing time away from + * lower priority tasks and waste CPU time and power. + */ +#define MQTT_AGENT_MAX_EVENT_QUEUE_WAIT_TIME ( 1000 ) + +/** + * @brief The number of command structures to allocate in the pool + * for the agent. + */ +#define MQTT_COMMAND_CONTEXTS_POOL_SIZE 10 #endif /* ifndef CORE_MQTT_CONFIG_H */ diff --git a/FreeRTOS-Plus/Demo/coreMQTT_Windows_Simulator/MQTT_Multitask/demo_config.h b/FreeRTOS-Plus/Demo/coreMQTT_Windows_Simulator/MQTT_Multitask/demo_config.h index 5587ef8917..415cdcd0b0 100644 --- a/FreeRTOS-Plus/Demo/coreMQTT_Windows_Simulator/MQTT_Multitask/demo_config.h +++ b/FreeRTOS-Plus/Demo/coreMQTT_Windows_Simulator/MQTT_Multitask/demo_config.h @@ -246,13 +246,13 @@ extern void vLoggingPrintf( const char * pcFormatString, * symbol. */ #include "core_mqtt.h" /* Include coreMQTT header for MQTT_LIBRARY_VERSION macro. */ -#define democonfigMQTT_LIB "core-mqtt@"MQTT_LIBRARY_VERSION +#define democonfigMQTT_LIB "core-mqtt@"MQTT_LIBRARY_VERSION /** * @brief Whether to use mutual authentication. If this macro is not set to 1 * or not defined, then plaintext TCP will be used instead of TLS over TCP. */ -#define democonfigUSE_TLS 1 +#define democonfigUSE_TLS 1 /** * @brief Set the stack size of the main demo task. @@ -260,7 +260,18 @@ extern void vLoggingPrintf( const char * pcFormatString, * In the Windows port, this stack only holds a structure. The actual * stack is created by an operating system thread. */ -#define democonfigDEMO_STACKSIZE configMINIMAL_STACK_SIZE +#define democonfigDEMO_STACKSIZE configMINIMAL_STACK_SIZE + +/** + * @brief The number of simple sub-pub tasks to create. + */ +#define democonfigNUM_SIMPLE_SUB_PUB_TASKS_TO_CREATE 2 +#define democonfigSIMPLE_SUB_PUB_TASK_STACK_SIZE ( configMINIMAL_STACK_SIZE ) + +/** + * @brief The length of the queue used to hold commands for the coreMQTT Agent. + */ +#define MQTT_AGENT_COMMAND_QUEUE_LENGTH 10 diff --git a/FreeRTOS-Plus/Demo/coreMQTT_Windows_Simulator/MQTT_Multitask/readme.txt b/FreeRTOS-Plus/Demo/coreMQTT_Windows_Simulator/MQTT_Multitask/readme.txt index 3b9c5ace56..419dc3808e 100644 --- a/FreeRTOS-Plus/Demo/coreMQTT_Windows_Simulator/MQTT_Multitask/readme.txt +++ b/FreeRTOS-Plus/Demo/coreMQTT_Windows_Simulator/MQTT_Multitask/readme.txt @@ -2,8 +2,6 @@ The multi threaded example creates an MQTT agent (or daemon task). It is thread safe because only the agent task is allowed to access the coreMQTT API - hence the API is only accessed from one FreeRTOS task. Other tasks and interrupts needing to interact with the MQTT agent do so through a thread safe queue. -We are generalising this technique for future coreMQTT releases, which will have -a re-usable agent component. ! Plain text examples are for ease of evaluation only - product devices should ! always use authenticated and encrypted communication. Never send private or diff --git a/FreeRTOS-Plus/Demo/coreMQTT_Windows_Simulator/MQTT_Multitask/readme.url b/FreeRTOS-Plus/Demo/coreMQTT_Windows_Simulator/MQTT_Multitask/readme.url index f7ceb65229..d081119d0a 100644 --- a/FreeRTOS-Plus/Demo/coreMQTT_Windows_Simulator/MQTT_Multitask/readme.url +++ b/FreeRTOS-Plus/Demo/coreMQTT_Windows_Simulator/MQTT_Multitask/readme.url @@ -2,4 +2,4 @@ Prop3=19,11 [InternetShortcut] IDList= -URL=https://www.freertos.org/mqtt/rtos_mqtt_example.html +URL=https://www.freertos.org/mqtt/mqtt-agent-demo.html? diff --git a/FreeRTOS-Plus/Demo/coreMQTT_Windows_Simulator/MQTT_Multitask/readme_also_coreMQTT_multithreading_agent.url b/FreeRTOS-Plus/Demo/coreMQTT_Windows_Simulator/MQTT_Multitask/readme_also_coreMQTT_multithreading_agent.url deleted file mode 100644 index d081119d0a..0000000000 --- a/FreeRTOS-Plus/Demo/coreMQTT_Windows_Simulator/MQTT_Multitask/readme_also_coreMQTT_multithreading_agent.url +++ /dev/null @@ -1,5 +0,0 @@ -[{000214A0-0000-0000-C000-000000000046}] -Prop3=19,11 -[InternetShortcut] -IDList= -URL=https://www.freertos.org/mqtt/mqtt-agent-demo.html? diff --git a/FreeRTOS-Plus/Demo/coreMQTT_Windows_Simulator/MQTT_Multitask/subscription-manager/subscription_manager.c b/FreeRTOS-Plus/Demo/coreMQTT_Windows_Simulator/MQTT_Multitask/subscription-manager/subscription_manager.c new file mode 100644 index 0000000000..69259e1241 --- /dev/null +++ b/FreeRTOS-Plus/Demo/coreMQTT_Windows_Simulator/MQTT_Multitask/subscription-manager/subscription_manager.c @@ -0,0 +1,170 @@ +/* + * FreeRTOS V202012.00 + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * https://www.FreeRTOS.org + * https://github.com/FreeRTOS + * + */ + +/** + * @file subscription_manager.c + * @brief Functions for managing MQTT subscriptions. + */ + +/* Standard includes. */ +#include + +/* Subscription manager header include. */ +#include "subscription_manager.h" + + +bool addSubscription( SubscriptionElement_t * pxSubscriptionList, + const char * pcTopicFilterString, + uint16_t usTopicFilterLength, + IncomingPubCallback_t pxIncomingPublishCallback, + void * pvIncomingPublishCallbackContext ) +{ + int32_t lIndex = 0; + size_t xAvailableIndex = SUBSCRIPTION_MANAGER_MAX_SUBSCRIPTIONS; + bool xReturnStatus = false; + + if( ( pxSubscriptionList == NULL ) || + ( pcTopicFilterString == NULL ) || + ( usTopicFilterLength == 0U ) || + ( pxIncomingPublishCallback == NULL ) ) + { + LogError( ( "Invalid parameter. pxSubscriptionList=%p, pcTopicFilterString=%p," + " usTopicFilterLength=%u, pxIncomingPublishCallback=%p.", + pxSubscriptionList, + pcTopicFilterString, + ( unsigned int ) usTopicFilterLength, + pxIncomingPublishCallback ) ); + } + else + { + /* Start at end of array, so that we will insert at the first available index. + * Scans backwards to find duplicates. */ + for( lIndex = ( int32_t ) SUBSCRIPTION_MANAGER_MAX_SUBSCRIPTIONS - 1; lIndex >= 0; lIndex-- ) + { + if( pxSubscriptionList[ lIndex ].usFilterStringLength == 0 ) + { + xAvailableIndex = lIndex; + } + else if( ( pxSubscriptionList[ lIndex ].usFilterStringLength == usTopicFilterLength ) && + ( strncmp( pcTopicFilterString, pxSubscriptionList[ lIndex ].pcSubscriptionFilterString, ( size_t ) usTopicFilterLength ) == 0 ) ) + { + /* If a subscription already exists, don't do anything. */ + if( ( pxSubscriptionList[ lIndex ].pxIncomingPublishCallback == pxIncomingPublishCallback ) && + ( pxSubscriptionList[ lIndex ].pvIncomingPublishCallbackContext == pvIncomingPublishCallbackContext ) ) + { + LogWarn( ( "Subscription already exists.\n" ) ); + xAvailableIndex = SUBSCRIPTION_MANAGER_MAX_SUBSCRIPTIONS; + xReturnStatus = true; + break; + } + } + } + + if( xAvailableIndex < SUBSCRIPTION_MANAGER_MAX_SUBSCRIPTIONS ) + { + pxSubscriptionList[ xAvailableIndex ].pcSubscriptionFilterString = pcTopicFilterString; + pxSubscriptionList[ xAvailableIndex ].usFilterStringLength = usTopicFilterLength; + pxSubscriptionList[ xAvailableIndex ].pxIncomingPublishCallback = pxIncomingPublishCallback; + pxSubscriptionList[ xAvailableIndex ].pvIncomingPublishCallbackContext = pvIncomingPublishCallbackContext; + xReturnStatus = true; + } + } + + return xReturnStatus; +} + +/*-----------------------------------------------------------*/ + +void removeSubscription( SubscriptionElement_t * pxSubscriptionList, + const char * pcTopicFilterString, + uint16_t usTopicFilterLength ) +{ + int32_t lIndex = 0; + + if( ( pxSubscriptionList == NULL ) || + ( pcTopicFilterString == NULL ) || + ( usTopicFilterLength == 0U ) ) + { + LogError( ( "Invalid parameter. pxSubscriptionList=%p, pcTopicFilterString=%p," + " usTopicFilterLength=%u.", + pxSubscriptionList, + pcTopicFilterString, + ( unsigned int ) usTopicFilterLength ) ); + } + else + { + for( lIndex = 0; lIndex < SUBSCRIPTION_MANAGER_MAX_SUBSCRIPTIONS; lIndex++ ) + { + if( pxSubscriptionList[ lIndex ].usFilterStringLength == usTopicFilterLength ) + { + if( strncmp( pxSubscriptionList[ lIndex ].pcSubscriptionFilterString, pcTopicFilterString, usTopicFilterLength ) == 0 ) + { + memset( &( pxSubscriptionList[ lIndex ] ), 0x00, sizeof( SubscriptionElement_t ) ); + } + } + } + } +} + +/*-----------------------------------------------------------*/ + +bool handleIncomingPublishes( SubscriptionElement_t * pxSubscriptionList, + MQTTPublishInfo_t * pxPublishInfo ) +{ + int32_t lIndex = 0; + bool isMatched = false, publishHandled = false; + + if( ( pxSubscriptionList == NULL ) || + ( pxPublishInfo == NULL ) ) + { + LogError( ( "Invalid parameter. pxSubscriptionList=%p, pxPublishInfo=%p,", + pxSubscriptionList, + pxPublishInfo ) ); + } + else + { + for( lIndex = 0; lIndex < SUBSCRIPTION_MANAGER_MAX_SUBSCRIPTIONS; lIndex++ ) + { + if( pxSubscriptionList[ lIndex ].usFilterStringLength > 0 ) + { + MQTT_MatchTopic( pxPublishInfo->pTopicName, + pxPublishInfo->topicNameLength, + pxSubscriptionList[ lIndex ].pcSubscriptionFilterString, + pxSubscriptionList[ lIndex ].usFilterStringLength, + &isMatched ); + + if( isMatched == true ) + { + pxSubscriptionList[ lIndex ].pxIncomingPublishCallback( pxSubscriptionList[ lIndex ].pvIncomingPublishCallbackContext, + pxPublishInfo ); + publishHandled = true; + } + } + } + } + + return publishHandled; +} diff --git a/FreeRTOS-Plus/Demo/coreMQTT_Windows_Simulator/MQTT_Multitask/subscription-manager/subscription_manager.h b/FreeRTOS-Plus/Demo/coreMQTT_Windows_Simulator/MQTT_Multitask/subscription-manager/subscription_manager.h new file mode 100644 index 0000000000..0e9cb12074 --- /dev/null +++ b/FreeRTOS-Plus/Demo/coreMQTT_Windows_Simulator/MQTT_Multitask/subscription-manager/subscription_manager.h @@ -0,0 +1,150 @@ +/* + * FreeRTOS V202012.00 + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * https://www.FreeRTOS.org + * https://github.com/FreeRTOS + * + */ + +/** + * @file subscription_manager.h + * @brief Functions for managing MQTT subscriptions. + */ +#ifndef SUBSCRIPTION_MANAGER_H +#define SUBSCRIPTION_MANAGER_H + +/**************************************************/ +/******* DO NOT CHANGE the following order ********/ +/**************************************************/ + +/* Logging related header files are required to be included in the following order: + * 1. Include the header file "logging_levels.h". + * 2. Define LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL. + * 3. Include the header file "logging_stack.h". + */ + +/* Include header that defines log levels. */ +#include "logging_levels.h" + +/* Logging configuration for the Subscription Manager module. */ +#ifndef LIBRARY_LOG_NAME + #define LIBRARY_LOG_NAME "Subscription Manager" +#endif +#ifndef LIBRARY_LOG_LEVEL + #define LIBRARY_LOG_LEVEL LOG_ERROR +#endif + +#include "logging_stack.h" + + +/* Demo config include. */ +#include "demo_config.h" + +/* core MQTT include. */ +#include "core_mqtt.h" + + +/** + * @brief Maximum number of subscriptions maintained by the subscription manager + * simultaneously in a list. + */ +#ifndef SUBSCRIPTION_MANAGER_MAX_SUBSCRIPTIONS + #define SUBSCRIPTION_MANAGER_MAX_SUBSCRIPTIONS 10U +#endif + +/** + * @brief Callback function called when receiving a publish. + * + * @param[in] pvIncomingPublishCallbackContext The incoming publish callback context. + * @param[in] pxPublishInfo Deserialized publish information. + */ +typedef void (* IncomingPubCallback_t )( void * pvIncomingPublishCallbackContext, + MQTTPublishInfo_t * pxPublishInfo ); + +/** + * @brief An element in the list of subscriptions. + * + * This subscription manager implementation expects that the array of the + * subscription elements used for storing subscriptions to be initialized to 0. + * + * @note This implementation allows multiple tasks to subscribe to the same topic. + * In this case, another element is added to the subscription list, differing + * in the intended publish callback. Also note that the topic filters are not + * copied in the subscription manager and hence the topic filter strings need to + * stay in scope until unsubscribed. + */ +typedef struct subscriptionElement +{ + IncomingPubCallback_t pxIncomingPublishCallback; + void * pvIncomingPublishCallbackContext; + uint16_t usFilterStringLength; + const char * pcSubscriptionFilterString; +} SubscriptionElement_t; + +/** + * @brief Add a subscription to the subscription list. + * + * @note Multiple tasks can be subscribed to the same topic with different + * context-callback pairs. However, a single context-callback pair may only be + * associated to the same topic filter once. + * + * @param[in] pxSubscriptionList The pointer to the subscription list array. + * @param[in] pcTopicFilterString Topic filter string of subscription. + * @param[in] usTopicFilterLength Length of topic filter string. + * @param[in] pxIncomingPublishCallback Callback function for the subscription. + * @param[in] pvIncomingPublishCallbackContext Context for the subscription callback. + * + * @return `true` if subscription added or exists, `false` if insufficient memory. + */ +bool addSubscription( SubscriptionElement_t * pxSubscriptionList, + const char * pcTopicFilterString, + uint16_t usTopicFilterLength, + IncomingPubCallback_t pxIncomingPublishCallback, + void * pvIncomingPublishCallbackContext ); + +/** + * @brief Remove a subscription from the subscription list. + * + * @note If the topic filter exists multiple times in the subscription list, + * then every instance of the subscription will be removed. + * + * @param[in] pxSubscriptionList The pointer to the subscription list array. + * @param[in] pcTopicFilterString Topic filter of subscription. + * @param[in] usTopicFilterLength Length of topic filter. + */ +void removeSubscription( SubscriptionElement_t * pxSubscriptionList, + const char * pcTopicFilterString, + uint16_t usTopicFilterLength ); + +/** + * @brief Handle incoming publishes by invoking the callbacks registered + * for the incoming publish's topic filter. + * + * @param[in] pxSubscriptionList The pointer to the subscription list array. + * @param[in] pxPublishInfo Info of incoming publish. + * + * @return `true` if an application callback could be invoked; + * `false` otherwise. + */ +bool handleIncomingPublishes( SubscriptionElement_t * pxSubscriptionList, + MQTTPublishInfo_t * pxPublishInfo ); + +#endif /* SUBSCRIPTION_MANAGER_H */ diff --git a/FreeRTOS-Plus/Source/Application-Protocols/coreMQTT-Agent b/FreeRTOS-Plus/Source/Application-Protocols/coreMQTT-Agent index 541402274f..d61dd0921b 160000 --- a/FreeRTOS-Plus/Source/Application-Protocols/coreMQTT-Agent +++ b/FreeRTOS-Plus/Source/Application-Protocols/coreMQTT-Agent @@ -1 +1 @@ -Subproject commit 541402274fc6bf52f35e14f39c0c1dd2f9205ad3 +Subproject commit d61dd0921bd651c0bfbaa2e41bb0eda56245a36b diff --git a/History.txt b/History.txt index 072dbd1b55..5f16d7d6b7 100644 --- a/History.txt +++ b/History.txt @@ -1,5 +1,12 @@ Documentation and download available at https://www.FreeRTOS.org/ +Changes after FreeRTOS 202012.00 release + + + Added new coreMQTT Agent library. + + Demo Updates + - MQTT Agent + + Changes between FreeRTOS V202011.00 and FreeRTOS V202012.00 released December 2020 + Includes libraries that are part of the FreeRTOS 202012.00 LTS release. diff --git a/manifest.yml b/manifest.yml index ce4282e0c8..df1abbf92e 100644 --- a/manifest.yml +++ b/manifest.yml @@ -38,6 +38,13 @@ dependencies: url: "https://github.com/FreeRTOS/coreMQTT.git" path: "FreeRTOS-Plus/Source/Application-Protocols/coreMQTT" + - name: "coreMQTT Agent" + version: "v1.0.0" + repository: + type: "git" + url: "https://github.com/FreeRTOS/coreMQTT-Agent.git" + path: "FreeRTOS-Plus/Source/Application-Protocols/coreMQTT-Agent" + - name: "corePKCS11" version: "v3.0.0" repository: