2 * FreeRTOS Kernel V10.3.0
\r
3 * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
\r
5 * Permission is hereby granted, free of charge, to any person obtaining a copy of
\r
6 * this software and associated documentation files (the "Software"), to deal in
\r
7 * the Software without restriction, including without limitation the rights to
\r
8 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
\r
9 * the Software, and to permit persons to whom the Software is furnished to do so,
\r
10 * subject to the following conditions:
\r
12 * The above copyright notice and this permission notice shall be included in all
\r
13 * copies or substantial portions of the Software.
\r
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
\r
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
\r
17 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
\r
18 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
\r
19 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
\r
20 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\r
22 * http://www.FreeRTOS.org
\r
23 * http://aws.amazon.com/freertos
\r
25 * 1 tab == 4 spaces!
\r
28 /* This demo executes Jobs obtained from AWS IoT. An AWS IoT Job is used to define
\r
29 * a set of remote operations that are sent to and executed on one or more devices
\r
30 * connected to AWS IoT. Please refer to AWS documentation for more information
\r
31 * about AWS IoT Jobs.
\r
32 * https://docs.aws.amazon.com/iot/latest/developerguide/iot-jobs.html
\r
34 * This demo creates a single application task that sets a callback for the
\r
35 * jobs/notify-next topic and executes Jobs created from the AWS IoT console or AWS
\r
36 * CLI. Please refer to AWS CLI documentation for more information in creating a
\r
38 * https://docs.aws.amazon.com/cli/latest/reference/iot/create-job.html
\r
40 * This demo expects Job documents to have an "action" JSON key. Actions can
\r
41 * be one "print", "publish", or "exit".
\r
42 * Print Jobs log a message to the local console, and must contain a "message",
\r
43 * e.g. { "action": "print", "message": "Hello World!" }.
\r
44 * Publish Jobs publish a message to an MQTT Topic. The Job document must
\r
45 * contain a "message" and "topic" to publish to, e.g.
\r
46 * { "action": "publish", "topic": "demo/jobs", "message": "Hello World!" }.
\r
47 * The exit Job exits the demo. Sending { "action": "exit" } will end the program.
\r
50 /* Standard includes. */
\r
54 /* Kernel includes. */
\r
55 #include "FreeRTOS.h"
\r
58 /* FreeRTOS+TCP includes. */
\r
59 #include "FreeRTOS_IP.h"
\r
61 /* IoT SDK includes. */
\r
62 #include "aws_iot_jobs.h"
\r
63 #include "aws_iot_demo_profile.h"
\r
64 #include "iot_mqtt.h"
\r
65 #include "iot_taskpool_freertos.h"
\r
66 #include "aws_iot_doc_parser.h"
\r
67 #include "platform/iot_clock.h"
\r
68 #include "platform/iot_threads.h"
\r
69 #include "platform/iot_network_freertos.h"
\r
73 /* Preprocessor check iot configuration. */
\r
74 #include "aws_iot_setup_check.h"
\r
76 /* Demo specific includes. */
\r
77 #include "demo_config.h"
\r
79 /*-----------------------------------------------------------*/
\r
82 * @brief The keep-alive interval used for this example.
\r
84 * An MQTT ping request will be sent periodically at this interval.
\r
86 * @note: This value is set to zero to disable MQTT
\r
87 * keep alive for the Windows simulator project.
\r
88 * The FreeRTOS kernel does not accurately calculate time for the Windows
\r
89 * Simulator. Therefore, MQTT PING Request messages may be sent
\r
90 * at an incorrect time interval to the broker. If the broker does
\r
91 * not receive a ping request within 1.5x the time sent in a
\r
92 * connection request, the broker may close the connection.
\r
93 * To enable the keep alive feature, set this value
\r
94 * to the desired interval in seconds.
\r
96 #define jobsexampleKEEP_ALIVE_SECONDS ( 0 )
\r
99 * @brief The timeout for MQTT operations in this example.
\r
101 #define jobsexampleMQTT_TIMEOUT_MS ( 5000 )
\r
104 * @brief Use default timeout when calling AwsIotJobs_Init.
\r
106 #define jobsexampleUSE_DEFAULT_MQTT_TIMEOUT ( 0 )
\r
109 * @brief The bit which is set in the demo task's notification value from the
\r
110 * disconnect callback to inform the demo task about the MQTT disconnect.
\r
112 #define jobsexampleDISCONNECTED_BIT ( 1UL << 0UL )
\r
115 * @brief The bit which is set in the demo task's notification value from the
\r
116 * operation complete callback to inform the demo task to exit.
\r
118 #define jobsexampleEXIT_BIT ( 1UL << 1UL )
\r
121 * @brief Length of the client identifier for this demo.
\r
123 #define jobsexampleCLIENT_IDENTIFIER_LENGTH ( sizeof( awsiotdemoprofileCLIENT_IDENTIFIER ) - 1 )
\r
126 * @brief The JSON key of the Job ID.
\r
128 * Job documents are in JSON documents received from the AWS IoT Jobs service.
\r
129 * All such JSON documents will contain this key, whose value represents the unique
\r
130 * identifier of a Job.
\r
132 #define jobsexampleID_KEY "jobId"
\r
135 * @brief The length of #jobsexampleID_KEY.
\r
137 #define jobsexampleID_KEY_LENGTH ( sizeof( jobsexampleID_KEY ) - 1 )
\r
140 * @brief The JSON key of the Job document.
\r
142 * Job documents are in JSON documents received from the AWS IoT Jobs service.
\r
143 * All such JSON documents will contain this key, whose value is an application-specific
\r
146 #define jobsexampleDOC_KEY "jobDocument"
\r
149 * @brief The length of #jobsexampleDOC_KEY.
\r
151 #define jobsexampleDOC_KEY_LENGTH ( sizeof( jobsexampleDOC_KEY ) - 1 )
\r
154 * @brief The JSON key whose value represents the action this demo should take.
\r
156 * This demo program expects this key to be in the Job document. It is a key
\r
157 * specific to this demo.
\r
159 #define jobsexampleACTION_KEY "action"
\r
162 * @brief The length of #jobsexampleACTION_KEY.
\r
164 #define jobsexampleACTION_KEY_LENGTH ( sizeof( jobsexampleACTION_KEY ) - 1 )
\r
167 * @brief A message associated with the Job action.
\r
169 * This demo program expects this key to be in the Job document if the "action"
\r
170 * is either "publish" or "print". It represents the message that should be
\r
171 * published or printed, respectively.
\r
173 #define jobsexampleMESSAGE_KEY "message"
\r
176 * @brief The length of #jobsexampleMESSAGE_KEY.
\r
178 #define jobsexampleMESSAGE_KEY_LENGTH ( sizeof( jobsexampleMESSAGE_KEY ) - 1 )
\r
181 * @brief An MQTT topic associated with the Job "publish" action.
\r
183 * This demo program expects this key to be in the Job document if the "action"
\r
184 * is "publish". It represents the MQTT topic on which the message should be
\r
187 #define jobsexampleTOPIC_KEY "topic"
\r
190 * @brief The length of #jobsexampleTOPIC_KEY.
\r
192 #define jobsexampleTOPIC_KEY_LENGTH ( sizeof( jobsexampleTOPIC_KEY ) - 1 )
\r
195 * @brief The minimum length of a string in a JSON Job document.
\r
197 * At the very least the Job ID must have the quotes that identify it as a JSON
\r
198 * string and 1 character for the string itself (the string must not be empty).
\r
200 #define jobsexampleJSON_STRING_MIN_LENGTH ( ( size_t ) 3 )
\r
203 * @brief The maximum length of a Job ID.
\r
205 * This limit is defined by AWS service limits. See the following page for more
\r
208 * https://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html#job-limits
\r
210 #define jobsexampleID_MAX_LENGTH ( ( size_t ) 64 )
\r
213 * @brief A value passed as context to #prvOperationCompleteCallback to specify that
\r
214 * it should notify the demo task of an exit request.
\r
216 #define jobsexampleSHOULD_EXIT ( ( void * ) ( ( intptr_t ) 1 ) )
\r
219 * @brief Time to wait before exiting demo.
\r
221 * The milliseconds to wait before exiting. This is because the MQTT Broker
\r
222 * will disconnect us if we are idle too long, and we have disabled keep alive.
\r
224 #define jobsexampleMS_BEFORE_EXIT ( 10 * 60 * 1000 )
\r
226 /*-----------------------------------------------------------*/
\r
229 * @brief Currently supported actions that a Job document can specify.
\r
231 typedef enum _jobAction
\r
233 JOB_ACTION_PRINT, /**< Print a message. */
\r
234 JOB_ACTION_PUBLISH, /**< Publish a message to an MQTT topic. */
\r
235 JOB_ACTION_EXIT, /**< Exit the demo. */
\r
236 JOB_ACTION_UNKNOWN /**< Unknown action. */
\r
240 * @brief The task used to demonstrate Jobs.
\r
242 * @param[in] pvParameters Parameters as passed at the time of task creation. Not
\r
243 * used in this example.
\r
245 static void prvJobsDemoTask( void * pvParameters );
\r
248 * @brief The callback invoked by the MQTT library when the MQTT connection gets
\r
251 * @param[in] pvCallbackContext Callback context as provided at the time of
\r
253 * @param[in] pxCallbackParams Contains the reason why the MQTT connection was
\r
256 static void prvExample_OnDisconnect( void * pvCallbackContext,
\r
257 IotMqttCallbackParam_t * pxCallbackParams );
\r
260 * @brief Connects to the MQTT broker as specified in awsiotdemoprofileAWS_ENDPOINT
\r
261 * and awsiotdemoprofileAWS_MQTT_PORT.
\r
263 static void prvMQTTConnect( void );
\r
266 * @brief Disconnects from the MQTT broker gracefully by sending an MQTT
\r
267 * DISCONNECT message.
\r
269 static void prvMQTTDisconnect( void );
\r
272 * @brief Set callback for publishes to the jobs/notify-next topic.
\r
274 static void prvSetNotifyNextCallback( void );
\r
277 * @brief Converts a string in a Job document to a #_jobAction_t.
\r
279 * @param[in] pcAction The Job action as a string.
\r
280 * @param[in] xActionLength The length of `pcAction`.
\r
282 * @return A #_jobAction_t equivalent to the given string.
\r
284 static _jobAction_t prvGetAction( const char * pcAction,
\r
285 size_t xActionLength );
\r
288 * @brief Extracts a JSON string from the Job document.
\r
290 * @param[in] pcJsonDoc The JSON document to search.
\r
291 * @param[in] xJsonDocLength Length of `pcJsonDoc`.
\r
292 * @param[in] pcKey The JSON key to search for.
\r
293 * @param[in] xKeyLength Length of `pcKey`.
\r
294 * @param[out] ppcValue The extracted JSON value.
\r
295 * @param[out] pxValueLength Length of ppcValue.
\r
297 * @return `pdTRUE` if the key was found and the value is valid; `pdFALSE` otherwise.
\r
299 static BaseType_t prvGetJsonString( const char * pcJsonDoc,
\r
300 size_t xJsonDocLength,
\r
301 const char * pcKey,
\r
303 const char ** ppcValue,
\r
304 size_t * pxValueLength );
\r
307 * @brief Job operation completion callback. This function is invoked when an
\r
308 * asynchronous Job operation finishes.
\r
310 * @param[in] pvCallbackContext Set to a non-NULL value to exit the demo.
\r
311 * @param[in] pxCallbackParam Information on the Job operation that completed.
\r
313 static void prvOperationCompleteCallback( void * pvCallbackContext,
\r
314 AwsIotJobsCallbackParam_t * pxCallbackParam );
\r
318 * @brief Process an action with a message, such as "print" or "publish".
\r
320 * @param[in] xMqttConnection The MQTT connection to use if the action is "publish".
\r
321 * @param[in] xAction Either #JOB_ACTION_PRINT or #JOB_ACTION_PUBLISH.
\r
322 * @param[in] pcJobDoc A pointer to the Job document.
\r
323 * @param[in] xJobDocLength The length of the Job document.
\r
325 * @return #AWS_IOT_JOB_STATE_SUCCEEDED on success; #AWS_IOT_JOB_STATE_FAILED otherwise.
\r
327 static AwsIotJobState_t prvProcessMessage( IotMqttConnection_t xMqttConnection,
\r
328 _jobAction_t xAction,
\r
329 const char * pcJobDoc,
\r
330 size_t xJobDocLength );
\r
333 * @brief Process a Job received from the Notify Next callback.
\r
335 * @param[in] pxJobInfo The parameter to the Notify Next callback that contains
\r
336 * information about the received Job.
\r
337 * @param[in] pcJobId A pointer to the Job ID.
\r
338 * @param[in] xJobIdLength The length of the Job ID.
\r
339 * @param[in] pcJobDoc A pointer to the Job document.
\r
340 * @param[in] xJobDocLength The length of the Job document.
\r
342 static void prvProcessJob( const AwsIotJobsCallbackParam_t * pxJobInfo,
\r
343 const char * pcJobId,
\r
344 size_t xJobIdLength,
\r
345 const char * pcJobDoc,
\r
346 size_t xJobDocLength );
\r
349 * @brief Jobs Notify Next callback. This function is invoked when a new Job is
\r
350 * received from the Jobs service.
\r
352 * @param[in] pCallbackContext Ignored.
\r
353 * @param[in] pxCallbackInfo Contains the received Job.
\r
355 static void prvJobsCallback( void * pCallbackContext,
\r
356 AwsIotJobsCallbackParam_t * pxCallbackInfo );
\r
358 /*-----------------------------------------------------------*/
\r
361 * @brief The MQTT connection handle used in this example.
\r
363 static IotMqttConnection_t xMQTTConnection = IOT_MQTT_CONNECTION_INITIALIZER;
\r
366 * @brief The main task handle in this demo.
\r
368 static TaskHandle_t xMainTaskHandle;
\r
371 * @brief Parameters used to create the system task pool.
\r
373 static const IotTaskPoolInfo_t xTaskPoolParameters =
\r
375 /* Minimum number of threads in a task pool.
\r
376 * Note the slimmed down version of the task
\r
377 * pool used by this library does not auto-scale
\r
378 * the number of tasks in the pool so in this
\r
379 * case this sets the number of tasks in the
\r
383 /* Maximum number of threads in a task pool.
\r
384 * Note the slimmed down version of the task
\r
385 * pool used by this library does not auto-scale
\r
386 * the number of tasks in the pool so in this
\r
387 * case this parameter is just ignored. */
\r
390 /* Stack size for every task pool thread - in
\r
391 * bytes, hence multiplying by the number of bytes
\r
392 * in a word as configMINIMAL_STACK_SIZE is
\r
393 * specified in words. */
\r
394 configMINIMAL_STACK_SIZE * sizeof( portSTACK_TYPE ),
\r
395 /* Priority for every task pool thread. */
\r
399 /***************** Structures that define the connection. *********************/
\r
402 static const struct IotNetworkServerInfo xMQTTBrokerInfo =
\r
404 .pHostName = awsiotdemoprofileAWS_ENDPOINT,
\r
405 .port = awsiotdemoprofileAWS_MQTT_PORT
\r
408 static struct IotNetworkCredentials xNetworkSecurityCredentials =
\r
410 /* Optional TLS extensions. For this demo, they are disabled. */
\r
411 .pAlpnProtos = NULL,
\r
412 .maxFragmentLength = 0,
\r
414 /* SNI is enabled by default. */
\r
415 .disableSni = false,
\r
417 /* Provide the certificate for validating the server. Only required for
\r
418 demos using TLS. */
\r
419 .pRootCa = awsiotdemoprofileAWS_CERTIFICATE_PEM,
\r
420 .rootCaSize = sizeof( awsiotdemoprofileAWS_CERTIFICATE_PEM ),
\r
422 /* Strong mutual authentication to authenticate both the broker and
\r
424 .pClientCert = awsiotdemoprofileCLIENT_CERTIFICATE_PEM,
\r
425 .clientCertSize = sizeof( awsiotdemoprofileCLIENT_CERTIFICATE_PEM ),
\r
426 .pPrivateKey = awsiotdemoprofileCLIENT_PRIVATE_KEY_PEM,
\r
427 .privateKeySize = sizeof( awsiotdemoprofileCLIENT_PRIVATE_KEY_PEM )
\r
430 static IotMqttNetworkInfo_t xNetworkInfo =
\r
432 /* No connection to the MQTT broker has been established yet and we want to
\r
433 * establish a new connection. */
\r
434 .createNetworkConnection = true,
\r
435 .u.setup.pNetworkServerInfo = &( xMQTTBrokerInfo ),
\r
437 /* Set the TLS credentials for the new MQTT connection. */
\r
438 .u.setup.pNetworkCredentialInfo = &xNetworkSecurityCredentials,
\r
440 /* Use FreeRTOS+TCP network interface. */
\r
441 .pNetworkInterface = IOT_NETWORK_INTERFACE_FREERTOS,
\r
443 /* Setup the callback which is called when the MQTT connection is
\r
444 * disconnected. The task handle is passed as the callback context which
\r
445 * is used by the callback to send a task notification to this task.*/
\r
446 .disconnectCallback.function = prvExample_OnDisconnect
\r
449 static const IotMqttConnectInfo_t xConnectInfo =
\r
451 /* Set this flag to true if connecting to the AWS IoT MQTT broker. */
\r
452 .awsIotMqttMode = false,
\r
454 /* Start with a clean session i.e. direct the MQTT broker to discard any
\r
455 * previous session data. Also, establishing a connection with clean session
\r
456 * will ensure that the broker does not store any data when this client
\r
457 * gets disconnected. */
\r
458 .cleanSession = true,
\r
460 /* Since we are starting with a clean session, there are no previous
\r
461 * subscriptions to be restored. */
\r
462 .pPreviousSubscriptions = NULL,
\r
463 .previousSubscriptionCount = 0,
\r
465 /* We do not want to publish Last Will and Testament (LWT) message if the
\r
466 * client gets disconnected. */
\r
469 /* Send an MQTT PING request every minute to keep the connection open if
\r
470 * there is no other MQTT traffic. */
\r
471 .keepAliveSeconds = jobsexampleKEEP_ALIVE_SECONDS,
\r
473 /* The client identifier is used to uniquely identify this MQTT client to
\r
474 * the MQTT broker. In a production device the identifier can be something
\r
475 * unique, such as a device serial number. */
\r
476 .pClientIdentifier = awsiotdemoprofileCLIENT_IDENTIFIER,
\r
477 .clientIdentifierLength = ( uint16_t ) sizeof( awsiotdemoprofileCLIENT_IDENTIFIER ) - 1,
\r
479 /* This example does not authenticate the client and therefore username and
\r
480 * password fields are not used. */
\r
482 .userNameLength = 0,
\r
484 .passwordLength = 0
\r
486 /*-----------------------------------------------------------*/
\r
488 static void prvExample_OnDisconnect( void * pvCallbackContext,
\r
489 IotMqttCallbackParam_t * pxCallbackParams )
\r
491 TaskHandle_t xDemoTaskHandle = ( TaskHandle_t ) pvCallbackContext;
\r
493 /* Ensure that we initiated the disconnect. */
\r
494 configASSERT( pxCallbackParams->u.disconnectReason == IOT_MQTT_DISCONNECT_CALLED );
\r
496 /* Inform the demo task about the disconnect. */
\r
497 xTaskNotify( xDemoTaskHandle,
\r
498 jobsexampleDISCONNECTED_BIT,
\r
499 eSetBits /* Set the jobsexampleDISCONNECTED_BIT in the demo task's notification value. */
\r
502 /*-----------------------------------------------------------*/
\r
504 void vStartJobsDemo( void )
\r
506 TickType_t xShortDelay = ( TickType_t ) pdMS_TO_TICKS( ( TickType_t ) 500 );
\r
508 /* Wait a short time to allow receipt of the ARP replies. */
\r
509 vTaskDelay( xShortDelay );
\r
511 /* This example uses a single application task, which in turn is used to
\r
512 * connect, subscribe, publish, unsubscribe and disconnect from the MQTT
\r
514 xTaskCreate( prvJobsDemoTask, /* Function that implements the task. */
\r
515 "JobsDemo", /* Text name for the task - only used for debugging. */
\r
516 democonfigDEMO_STACKSIZE, /* Size of stack (in words, not bytes) to allocate for the task. */
\r
517 NULL, /* Task parameter - not used in this case. */
\r
518 tskIDLE_PRIORITY, /* Task priority, must be between 0 and configMAX_PRIORITIES - 1. */
\r
519 NULL ); /* Used to pass out a handle to the created task - not used in this case. */
\r
521 /*-----------------------------------------------------------*/
\r
523 static void prvJobsDemoTask( void * pvParameters )
\r
525 IotMqttError_t xResult;
\r
526 IotNetworkError_t xNetworkInit;
\r
527 uint32_t ulNotificationValue = 0;
\r
528 const TickType_t xNoDelay = ( TickType_t ) 0;
\r
529 AwsIotJobsError_t xStatus = AWS_IOT_JOBS_SUCCESS;
\r
530 AwsIotJobsCallbackInfo_t xCallbackInfo = AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER;
\r
531 AwsIotJobsRequestInfo_t xRequestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER;
\r
533 /* Remove compiler warnings about unused parameters. */
\r
534 ( void ) pvParameters;
\r
536 xMainTaskHandle = xTaskGetCurrentTaskHandle();
\r
538 /* The MQTT library needs a task pool, so create the system task pool. */
\r
539 xResult = IotTaskPool_CreateSystemTaskPool( &( xTaskPoolParameters ) );
\r
540 configASSERT( xResult == IOT_TASKPOOL_SUCCESS );
\r
542 /* Initialize the network stack abstraction for FreeRTOS. */
\r
543 xNetworkInit = IotNetworkFreeRTOS_Init();
\r
544 configASSERT( xNetworkInit == IOT_NETWORK_SUCCESS );
\r
546 /* MQTT library must be initialized before it can be used. This is just one
\r
547 * time initialization. */
\r
548 xResult = IotMqtt_Init();
\r
549 configASSERT( xResult == IOT_MQTT_SUCCESS );
\r
551 /* Initialize Jobs library. */
\r
552 xResult = AwsIotJobs_Init( jobsexampleUSE_DEFAULT_MQTT_TIMEOUT );
\r
553 configASSERT( xResult == AWS_IOT_JOBS_SUCCESS );
\r
555 /****************************** Connect. ******************************/
\r
557 /* Establish a connection to the AWS IoT MQTT broker. This example connects to
\r
558 * the MQTT broker as specified in awsiotdemoprofileAWS_ENDPOINT and
\r
559 * awsiotdemoprofileAWS_MQTT_PORT at the top of this file.
\r
561 configPRINTF( ( "Attempt to connect to %s\r\n", awsiotdemoprofileAWS_ENDPOINT ) );
\r
563 configPRINTF( ( "Connected to %s\r\n", awsiotdemoprofileAWS_ENDPOINT ) );
\r
565 /* Don't expect any notifications to be pending yet. */
\r
566 configASSERT( ulTaskNotifyTake( pdTRUE, xNoDelay ) == 0 );
\r
568 configPRINTF( ( "Setting callback for jobs/notify-next\r\n" ) );
\r
569 prvSetNotifyNextCallback();
\r
571 /* Call DescribeAsync to see if there are any pending jobs. */
\r
572 xRequestInfo.mqttConnection = xMQTTConnection;
\r
573 xRequestInfo.pThingName = awsiotdemoprofileCLIENT_IDENTIFIER;
\r
574 xRequestInfo.thingNameLength = jobsexampleCLIENT_IDENTIFIER_LENGTH;
\r
575 xRequestInfo.pJobId = AWS_IOT_JOBS_NEXT_JOB;
\r
576 xRequestInfo.jobIdLength = AWS_IOT_JOBS_NEXT_JOB_LENGTH;
\r
578 /* Use the same callback as notify-next so any pending jobs will be
\r
579 * executed the same way. */
\r
580 xCallbackInfo.function = prvJobsCallback;
\r
582 xStatus = AwsIotJobs_DescribeAsync( &xRequestInfo, AWS_IOT_JOBS_NO_EXECUTION_NUMBER, true, 0, &xCallbackInfo, NULL );
\r
583 configPRINTF( ( "Describe queued with result %s.\r\n", AwsIotJobs_strerror( xStatus ) ) );
\r
585 /* Print out a short user guide to the console. The default logging
\r
586 * limit of 255 characters can be changed in demo_logging.c, but breaking
\r
587 * up the only instance of a 1000+ character string is more practical. */
\r
590 "/*-----------------------------------------------------------*/\r\n"
\r
592 "The Jobs demo is now ready to accept Jobs.\r\n"
\r
593 "Jobs may be created using the AWS IoT console or AWS CLI.\r\n"
\r
594 "See the following link for more information.\r\n"
\r
598 "https://docs.aws.amazon.com/cli/latest/reference/iot/create-job.html\r\n"
\r
600 "This demo expects Job documents to have an \"action\" JSON key.\r\n"
\r
601 "The following actions are currently supported:\r\n" ) );
\r
605 " Logs a message to the local console. The Job document must also contain a \"message\".\r\n"
\r
606 " For example: { \"action\": \"print\", \"message\": \"Hello world!\"} will cause\r\n"
\r
607 " \"Hello world!\" to be printed on the console.\r\n" ) );
\r
611 " Publishes a message to an MQTT topic. The Job document must also contain a \"message\" and \"topic\".\r\n" ) );
\r
614 " For example: { \"action\": \"publish\", \"topic\": \"demo/jobs\", \"message\": \"Hello world!\"} will cause\r\n"
\r
615 " \"Hello world!\" to be published to the topic \"demo/jobs\".\r\n" ) );
\r
619 " Exits the demo program. This program will run until { \"action\": \"exit\" } is received.\r\n"
\r
621 "/*-----------------------------------------------------------*/\r\n" ) );
\r
623 /* Wait for an exit job to be received. If an exit job is not received within
\r
624 * jobsexampleMS_BEFORE_EXIT, exit anyway. This is because we have disabled
\r
625 * keep-alive, and the server will disconnect as after some time. */
\r
626 xTaskNotifyWait( 0UL, /* Don't clear any bits on entry. */
\r
627 jobsexampleEXIT_BIT, /* Clear bit on exit. */
\r
628 &( ulNotificationValue ), /* Obtain the notification value. */
\r
629 pdMS_TO_TICKS( jobsexampleMS_BEFORE_EXIT) );
\r
630 /* Check was due to receiving an exit job. */
\r
631 if( ( ulNotificationValue & jobsexampleEXIT_BIT ) != jobsexampleEXIT_BIT )
\r
633 configPRINTF( ( "Disconnecting as %u milliseconds have elapsed.\r\n", jobsexampleMS_BEFORE_EXIT ) );
\r
636 /* Disconnect MQTT gracefully. */
\r
637 prvMQTTDisconnect();
\r
638 configPRINTF( ( "Disconnected from %s\r\n\r\n", awsiotdemoprofileAWS_ENDPOINT ) );
\r
640 /* Wait for the disconnect operation to complete which is informed to us
\r
641 * by the disconnect callback (prvExample_OnDisconnect)by setting
\r
642 * the jobsexampleDISCONNECTED_BIT in this task's notification value. */
\r
643 xTaskNotifyWait( 0UL, /* Don't clear any bits on entry. */
\r
644 jobsexampleDISCONNECTED_BIT, /* Clear bit on exit. */
\r
645 &( ulNotificationValue ), /* Obtain the notification value. */
\r
646 pdMS_TO_TICKS( jobsexampleMQTT_TIMEOUT_MS ) );
\r
647 configASSERT( ( ulNotificationValue & jobsexampleDISCONNECTED_BIT ) == jobsexampleDISCONNECTED_BIT );
\r
649 configPRINTF( ( "prvJobsDemoTask() completed successfully. Total free heap is %u\r\n", xPortGetFreeHeapSize() ) );
\r
650 configPRINTF( ( "Demo completed successfully.\r\n" ) );
\r
652 /* Clean up initialized libraries. */
\r
653 AwsIotJobs_Cleanup();
\r
655 IotNetworkFreeRTOS_Cleanup();
\r
657 /* FreeRTOS Tasks must _vTaskDelete( NULL )_ before exiting the function. */
\r
658 vTaskDelete( NULL );
\r
660 /*-----------------------------------------------------------*/
\r
662 static void prvMQTTConnect( void )
\r
664 IotMqttError_t xResult;
\r
666 /* Set the context to pass into the disconnect callback function. */
\r
667 xNetworkInfo.disconnectCallback.pCallbackContext = ( void * ) xTaskGetCurrentTaskHandle();
\r
669 /* Establish the connection to the MQTT broker - It is a blocking call and
\r
670 * will return only when connection is complete or a timeout occurs. */
\r
671 xResult = IotMqtt_Connect( &( xNetworkInfo ),
\r
673 jobsexampleMQTT_TIMEOUT_MS,
\r
674 &( xMQTTConnection ) );
\r
675 configASSERT( xResult == IOT_MQTT_SUCCESS );
\r
677 /*-----------------------------------------------------------*/
\r
679 static void prvMQTTDisconnect( void )
\r
681 /* Send a MQTT DISCONNECT packet to the MQTT broker to do a graceful
\r
683 IotMqtt_Disconnect( xMQTTConnection,
\r
684 0 /* flags - 0 means a graceful disconnect by sending MQTT DISCONNECT. */
\r
687 /*-----------------------------------------------------------*/
\r
689 static void prvSetNotifyNextCallback( void )
\r
691 AwsIotJobsError_t xCallbackStatus = AWS_IOT_JOBS_SUCCESS;
\r
692 AwsIotJobsCallbackInfo_t xCallbackInfo = AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER;
\r
694 /* Set the jobs callback function. */
\r
695 xCallbackInfo.function = prvJobsCallback;
\r
697 /************************ Set notify-next callbacks **********************/
\r
699 xCallbackStatus = AwsIotJobs_SetNotifyNextCallback( xMQTTConnection,
\r
700 awsiotdemoprofileCLIENT_IDENTIFIER,
\r
701 jobsexampleCLIENT_IDENTIFIER_LENGTH,
\r
705 configASSERT( xCallbackStatus == AWS_IOT_JOBS_SUCCESS );
\r
707 /*-----------------------------------------------------------*/
\r
709 static _jobAction_t prvGetAction( const char * pcAction,
\r
710 size_t xActionLength )
\r
712 _jobAction_t xAction = JOB_ACTION_UNKNOWN;
\r
714 configASSERT( pcAction != NULL );
\r
716 if( strncmp( pcAction, "print", xActionLength ) == 0 )
\r
718 xAction = JOB_ACTION_PRINT;
\r
720 else if( strncmp( pcAction, "publish", xActionLength ) == 0 )
\r
722 xAction = JOB_ACTION_PUBLISH;
\r
724 else if( strncmp( pcAction, "exit", xActionLength ) == 0 )
\r
726 xAction = JOB_ACTION_EXIT;
\r
731 /*-----------------------------------------------------------*/
\r
733 static BaseType_t prvGetJsonString( const char * pcJsonDoc,
\r
734 size_t xJsonDocLength,
\r
735 const char * pcKey,
\r
737 const char ** ppcValue,
\r
738 size_t * pxValueLength )
\r
740 BaseType_t xKeyFound = pdFALSE;
\r
742 configASSERT( pcJsonDoc != NULL );
\r
743 configASSERT( pcKey != NULL );
\r
746 * Note: This parser used is specific for parsing AWS IoT document received
\r
747 * through a mutually authenticated connection. This parser will not check
\r
748 * for the correctness of the document as it is designed for low memory
\r
749 * footprint rather than checking for correctness of the document. This
\r
750 * parser is not meant to be used as a general purpose JSON parser.
\r
752 xKeyFound = ( BaseType_t ) AwsIotDocParser_FindValue(
\r
760 if( xKeyFound == pdTRUE )
\r
762 /* Exclude empty strings. */
\r
763 if( *pxValueLength < jobsexampleJSON_STRING_MIN_LENGTH )
\r
765 xKeyFound = pdFALSE;
\r
769 /* Adjust the value to remove the quotes. */
\r
771 ( *pxValueLength ) -= 2;
\r
777 /*-----------------------------------------------------------*/
\r
779 static void prvOperationCompleteCallback( void * pvCallbackContext,
\r
780 AwsIotJobsCallbackParam_t * pxCallbackParam )
\r
782 configASSERT( pxCallbackParam != NULL );
\r
784 /* This function is invoked when either a StartNext or Update completes. */
\r
785 if( pxCallbackParam->callbackType == AWS_IOT_JOBS_START_NEXT_COMPLETE )
\r
787 configPRINTF( ( "Job StartNext complete with result %s.\r\n",
\r
788 AwsIotJobs_strerror( pxCallbackParam->u.operation.result ) ) );
\r
792 configPRINTF( ( "Job Update complete with result %s.\r\n",
\r
793 AwsIotJobs_strerror( pxCallbackParam->u.operation.result ) ) );
\r
796 /* If a non-NULL context is given, set the flag to exit the demo. */
\r
797 if( pvCallbackContext != NULL )
\r
799 xTaskNotify( xMainTaskHandle,
\r
800 jobsexampleEXIT_BIT,
\r
801 eSetBits /* Set the jobsexampleEXIT_BIT in the demo task's notification value. */
\r
805 /*-----------------------------------------------------------*/
\r
807 static AwsIotJobState_t prvProcessMessage( IotMqttConnection_t xMqttConnection,
\r
808 _jobAction_t xAction,
\r
809 const char * pcJobDoc,
\r
810 size_t xJobDocLength )
\r
812 AwsIotJobState_t xStatus = AWS_IOT_JOB_STATE_SUCCEEDED;
\r
813 IotMqttError_t xMqttStatus = IOT_MQTT_STATUS_PENDING;
\r
814 IotMqttPublishInfo_t xPublishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER;
\r
815 const char * pcMessage = NULL, * pcTopic = NULL;
\r
816 size_t xMessageLength = 0, xTopicLength = 0;
\r
818 configASSERT( pcJobDoc != NULL );
\r
820 /* Both "print" and "publish" require a "message" key. Search the Job
\r
821 * document for this key. */
\r
822 if( prvGetJsonString( pcJobDoc,
\r
824 jobsexampleMESSAGE_KEY,
\r
825 jobsexampleMESSAGE_KEY_LENGTH,
\r
827 &xMessageLength ) == pdFALSE )
\r
829 configPRINTF( ( "Job document for \"print\" or \"publish\" does not contain a %s key.\r\n",
\r
830 jobsexampleMESSAGE_KEY ) );
\r
832 xStatus = AWS_IOT_JOB_STATE_FAILED;
\r
835 if( xStatus == AWS_IOT_JOB_STATE_SUCCEEDED )
\r
837 if( xAction == JOB_ACTION_PRINT )
\r
839 /* Print the given message if the action is "print". */
\r
842 "/*-----------------------------------------------------------*/\r\n"
\r
846 "/*-----------------------------------------------------------*/\r\n"
\r
847 "\r\n", xMessageLength, pcMessage ) );
\r
851 /* Extract the topic if the action is "publish". */
\r
852 if( prvGetJsonString( pcJobDoc,
\r
854 jobsexampleTOPIC_KEY,
\r
855 jobsexampleTOPIC_KEY_LENGTH,
\r
857 &xTopicLength ) == pdFALSE )
\r
859 configPRINTF( ( "Job document for action \"publish\" does not contain a %s key.\r\n",
\r
860 jobsexampleTOPIC_KEY ) );
\r
862 xStatus = AWS_IOT_JOB_STATE_FAILED;
\r
865 if( xStatus == AWS_IOT_JOB_STATE_SUCCEEDED )
\r
867 xPublishInfo.qos = IOT_MQTT_QOS_0;
\r
868 xPublishInfo.pTopicName = pcTopic;
\r
869 xPublishInfo.topicNameLength = ( uint16_t ) xTopicLength;
\r
870 xPublishInfo.pPayload = pcMessage;
\r
871 xPublishInfo.payloadLength = xMessageLength;
\r
873 xMqttStatus = IotMqtt_PublishAsync( xMqttConnection, &xPublishInfo, 0, NULL, NULL );
\r
875 if( xMqttStatus != IOT_MQTT_SUCCESS )
\r
877 xStatus = AWS_IOT_JOB_STATE_FAILED;
\r
885 /*-----------------------------------------------------------*/
\r
887 static void prvProcessJob( const AwsIotJobsCallbackParam_t * pxJobInfo,
\r
888 const char * pcJobId,
\r
889 size_t xJobIdLength,
\r
890 const char * pcJobDoc,
\r
891 size_t xJobDocLength )
\r
893 AwsIotJobsError_t xStatus = AWS_IOT_JOBS_SUCCESS;
\r
894 AwsIotJobsUpdateInfo_t xUpdateInfo = AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER;
\r
895 AwsIotJobsCallbackInfo_t xCallbackInfo = AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER;
\r
896 const char * pcAction = NULL;
\r
897 size_t xActionLength = 0;
\r
898 _jobAction_t xAction = JOB_ACTION_UNKNOWN;
\r
899 AwsIotJobsRequestInfo_t xRequestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER;
\r
901 configASSERT( pxJobInfo != NULL );
\r
902 configASSERT( pcJobId != NULL );
\r
903 configASSERT( pcJobDoc != NULL );
\r
905 configPRINTF( ( "Job document received: %.*s\r\n", xJobDocLength, pcJobDoc ) );
\r
907 xRequestInfo.mqttConnection = pxJobInfo->mqttConnection;
\r
908 xRequestInfo.pThingName = pxJobInfo->pThingName;
\r
909 xRequestInfo.thingNameLength = pxJobInfo->thingNameLength;
\r
910 xRequestInfo.pJobId = pcJobId;
\r
911 xRequestInfo.jobIdLength = xJobIdLength;
\r
913 /* Tell the Jobs service that the device has started working on the Job.
\r
914 * Use the StartNext API to set the Job's status to IN_PROGRESS. */
\r
915 xCallbackInfo.function = prvOperationCompleteCallback;
\r
917 xStatus = AwsIotJobs_StartNextAsync( &xRequestInfo, &xUpdateInfo, 0, &xCallbackInfo, NULL );
\r
919 configPRINTF( ( "Jobs StartNext queued with result %s.\r\n", AwsIotJobs_strerror( xStatus ) ) );
\r
921 /* Get the action for this device. */
\r
922 if( prvGetJsonString( pcJobDoc,
\r
924 jobsexampleACTION_KEY,
\r
925 jobsexampleACTION_KEY_LENGTH,
\r
927 &xActionLength ) == pdTRUE )
\r
929 xAction = prvGetAction( pcAction, xActionLength );
\r
933 case JOB_ACTION_EXIT:
\r
934 xCallbackInfo.pCallbackContext = jobsexampleSHOULD_EXIT;
\r
935 xUpdateInfo.newStatus = AWS_IOT_JOB_STATE_SUCCEEDED;
\r
938 case JOB_ACTION_PRINT:
\r
939 case JOB_ACTION_PUBLISH:
\r
940 xUpdateInfo.newStatus = prvProcessMessage( pxJobInfo->mqttConnection,
\r
947 configPRINTF( ( "Received Job document with unknown action %.*s.\r\n",
\r
951 xUpdateInfo.newStatus = AWS_IOT_JOB_STATE_FAILED;
\r
957 configPRINTF( ( "Received Job document does not contain an %s key.\r\n",
\r
958 jobsexampleACTION_KEY ) );
\r
960 /* The given Job document is not valid for this demo. */
\r
961 xUpdateInfo.newStatus = AWS_IOT_JOB_STATE_FAILED;
\r
964 configPRINTF( ( "Setting state of %.*s to %s.\r\n",
\r
967 AwsIotJobs_StateName( xUpdateInfo.newStatus ) ) );
\r
969 /* Tell the Jobs service that the device has finished the Job. */
\r
970 xStatus = AwsIotJobs_UpdateAsync( &xRequestInfo, &xUpdateInfo, 0, &xCallbackInfo, NULL );
\r
972 configPRINTF( ( "Jobs Update queued with result %s.\r\n", AwsIotJobs_strerror( xStatus ) ) );
\r
974 /*-----------------------------------------------------------*/
\r
976 static void prvJobsCallback( void * pCallbackContext,
\r
977 AwsIotJobsCallbackParam_t * pxCallbackInfo )
\r
979 BaseType_t xIdKeyFound = pdFALSE, xDocKeyFound = pdFALSE;
\r
980 const char * pcJobId = NULL;
\r
981 size_t xJobIdLength = 0;
\r
982 const char * pcJobDoc = NULL;
\r
983 size_t xJobDocLength = 0;
\r
984 const char * pcRawDocument = NULL;
\r
985 size_t xRawDocumentLength = 0;
\r
987 /* Silence warnings about unused parameters. */
\r
988 ( void ) pCallbackContext;
\r
990 configASSERT( pxCallbackInfo != NULL );
\r
992 /* Check if this callback was called from a describe operation or
\r
993 * due to notify-next. */
\r
994 if( pxCallbackInfo->callbackType == AWS_IOT_JOBS_DESCRIBE_COMPLETE )
\r
996 pcRawDocument = pxCallbackInfo->u.operation.pResponse;
\r
997 xRawDocumentLength = pxCallbackInfo->u.operation.responseLength;
\r
1001 pcRawDocument = pxCallbackInfo->u.callback.pDocument;
\r
1002 xRawDocumentLength = pxCallbackInfo->u.callback.documentLength;
\r
1005 /* Get the Job ID. */
\r
1006 xIdKeyFound = prvGetJsonString( pcRawDocument,
\r
1007 xRawDocumentLength,
\r
1008 jobsexampleID_KEY,
\r
1009 jobsexampleID_KEY_LENGTH,
\r
1013 if( xIdKeyFound == pdTRUE )
\r
1015 if( xJobIdLength > jobsexampleID_MAX_LENGTH )
\r
1017 configPRINTF( ( "Received Job ID %.*s longer than %lu, which is the "
\r
1018 "maximum allowed by AWS IoT. Ignoring Job.\r\n",
\r
1021 ( unsigned long ) jobsexampleID_MAX_LENGTH ) );
\r
1023 xIdKeyFound = pdFALSE;
\r
1027 configPRINTF( ( "Job %.*s received.\r\n", xJobIdLength, pcJobId ) );
\r
1031 /* Get the Job document.
\r
1033 * Note: This parser used is specific for parsing AWS IoT document received
\r
1034 * through a mutually authenticated connection. This parser will not check
\r
1035 * for the correctness of the document as it is designed for low memory
\r
1036 * footprint rather than checking for correctness of the document. This
\r
1037 * parser is not meant to be used as a general purpose JSON parser.
\r
1039 xDocKeyFound = ( BaseType_t ) AwsIotDocParser_FindValue(
\r
1041 xRawDocumentLength,
\r
1042 jobsexampleDOC_KEY,
\r
1043 jobsexampleDOC_KEY_LENGTH,
\r
1047 /* When both the Job ID and Job document are available, process the Job. */
\r
1048 if( ( xIdKeyFound == pdTRUE ) && ( xDocKeyFound == pdTRUE ) )
\r
1050 /* Process the Job document. */
\r
1051 prvProcessJob( pxCallbackInfo,
\r
1059 /* The Jobs service sends an empty Job document when all Jobs are complete. */
\r
1060 if( ( xIdKeyFound == pdFALSE ) && ( xDocKeyFound == pdFALSE ) )
\r
1064 "/*-----------------------------------------------------------*/\r\n"
\r
1066 "All available Jobs complete.\r\n"
\r
1068 "/*-----------------------------------------------------------*/\r\n"
\r
1073 configPRINTF( ( "Received an invalid Job document: %.*s\r\n",
\r
1074 xRawDocumentLength,
\r
1075 pcRawDocument ) );
\r
1079 /*-----------------------------------------------------------*/
\r