Morse Micro IoT SDK  2.11.2
aws_iot.c
Go to the documentation of this file.
1/*
2 * Copyright 2023 Morse Micro
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7/* clang-format off */
359/* clang-format on */
360
361#include <string.h>
362#include "mmhal_app.h"
363#include "mmosal.h"
364#include "mmconfig.h"
365#include "mm_app_loadconfig.h"
366#include "mmipal.h"
367#include "mqtt_agent_task.h"
368#if defined(ENABLE_SHADOW_APP) && ENABLE_SHADOW_APP
369#include "shadow_device_task.h"
370#endif
371#if defined(ENABLE_PROVISIONING_APP) && ENABLE_PROVISIONING_APP
372#include "fleet_provisioning_task.h"
373#endif
374#include "sntp_client.h"
375#include "core_json.h"
376#include "mm_app_common.h"
377#include "aws_iot_config.h"
378
380#define NTP_MIN_TIMEOUT 3000
382#define NTP_MIN_BACKOFF 60000
384#define NTP_MIN_BACKOFF_JITTER 3000
386#define NTP_MAX_BACKOFF_JITTER 60000
387
407#define SHADOW_PUBLISH_JSON \
408 "{" \
409 "\"state\":{" \
410 "\"reported\":{" \
411 "\"powerOn\":%lu" \
412 "}" \
413 "}," \
414 "\"clientToken\":\"%06lu\"" \
415 "}"
416
417#if defined(ENABLE_SHADOW_APP) && ENABLE_SHADOW_APP
418
420static uint32_t ulCurrentPowerOnState = 0;
421
423static uint32_t ulDesiredPowerOnState = 0;
424
426struct mmosal_sem *state_change_sem = NULL;
427
435void shadow_update_callback(char *json, size_t json_len, enum shadow_update_status status)
436{
437 /* Make sure the payload is a valid json document. */
438 int result = JSON_Validate(json, json_len);
439
440 if (result != JSONSuccess)
441 {
442 printf("ERR:Invalid JSON document received\n");
443 return;
444 }
445
446 static uint32_t ulCurrentVersion = 0; /* Remember the latest version number we've received */
447 char *pcOutValue = NULL;
448 uint32_t ulOutValueLength = 0UL;
449 uint32_t ulVersion = 0;
450 uint32_t ulCode = 0;
451
452 switch (status)
453 {
454 case UPDATE_DELTA:
455 /* The json will look similar to this:
456 * @code
457 * {
458 * "state": {
459 * "powerOn": 1
460 * },
461 * "metadata": {
462 * "powerOn": {
463 * "timestamp": 1595437367
464 * }
465 * },
466 * "timestamp": 1595437367,
467 * "clientToken": "388062",
468 * "version": 12
469 * }
470 * @endcode
471 */
472
473 /* Obtain the version value. */
474 result = JSON_Search(json,
475 json_len,
476 "version",
477 strlen("version"),
478 &pcOutValue,
479 (size_t *)&ulOutValueLength);
480
481 if (result != JSONSuccess)
482 {
483 printf("ERR:Version field not found in JSON document\n");
484 return;
485 }
486 /* Convert the extracted value to an unsigned integer value. */
487 ulVersion = (uint32_t)strtoul(pcOutValue, NULL, 10);
488 /* Make sure the version is newer than the last one we received. */
489 if (ulVersion <= ulCurrentVersion)
490 {
491 /* In this demo, we discard the incoming message
492 * if the version number is not newer than the latest
493 * that we've received before. Your application may use a
494 * different approach. */
495 printf("ERR:Received unexpected delta update with version %u, "
496 "current version is %u\n",
497 (unsigned int)ulVersion,
498 (unsigned int)ulCurrentVersion);
499 return;
500 }
501 /* Set received version as the current version. */
502 ulCurrentVersion = ulVersion;
503
504 /* Get powerOn state from json documents. */
505 result = JSON_Search(json,
506 json_len,
507 "state.powerOn",
508 sizeof("state.powerOn") - 1,
509 &pcOutValue,
510 (size_t *)&ulOutValueLength);
511
512 if (result != JSONSuccess)
513 {
514 printf("ERR:powerOn field not found in JSON document\n");
515 }
516 else
517 {
518 /* Convert the powerOn state value to an unsigned integer value. */
519 ulDesiredPowerOnState = (uint32_t)strtoul(pcOutValue, NULL, 10);
520
521 /* Signal user task about change of state */
522 mmosal_sem_give(state_change_sem);
523 }
524 break;
525
526 case UPDATE_ACCEPTED:
527 /* Handle the reported state with state change in /update/accepted topic.
528 * Thus we will retrieve the client token from the JSON document to see if
529 * it's the same one we sent with reported state on the /update topic.
530 * The payload will look similar to this:
531 * @code
532 * {
533 * "state": {
534 * "reported": {
535 * "powerOn": 1
536 * }
537 * },
538 * "metadata": {
539 * "reported": {
540 * "powerOn": {
541 * "timestamp": 1596573647
542 * }
543 * }
544 * },
545 * "version": 14698,
546 * "timestamp": 1596573647,
547 * "clientToken": "022485"
548 * }
549 * @endcode
550 */
551
552 /* We do not need to do anything here unless we want to implement positive confirmation
553 * that our reported state was received and acted on by the server. In which case ensure
554 * you check that the @c clientToken matches the one we sent in the report. */
555 break;
556
557 case UPDATE_REJECTED:
558 /* The payload will look similar to this:
559 * {
560 * "code": error-code,
561 * "message": "error-message",
562 * "timestamp": timestamp,
563 * "clientToken": "token"
564 * }
565 */
566
567 /* Obtain the error code. */
568 result = JSON_Search(json,
569 json_len,
570 "code",
571 sizeof("code") - 1,
572 &pcOutValue,
573 (size_t *)&ulOutValueLength);
574
575 /* Convert the code to an unsigned integer value. */
576 ulCode = (uint32_t)strtoul(pcOutValue, NULL, 10);
577
578 printf("ERR:Received rejected response code %lu\n", ulCode);
579
580 /* Obtain the message string. */
581 result = JSON_Search(json,
582 json_len,
583 "message",
584 sizeof("message") - 1,
585 &pcOutValue,
586 (size_t *)&ulOutValueLength);
587 printf(" Message: %.*s\n", (int)ulOutValueLength, pcOutValue);
588
589 break;
590 }
591}
592
598void aws_shadow_loop(char *shadow_name)
599{
600 for (;;)
601 {
602 mmosal_sem_wait(state_change_sem, UINT32_MAX);
603
604 if (ulDesiredPowerOnState == 1)
605 {
606 /* Set the new powerOn state. */
607 printf("INF:Setting powerOn state to 1.\n");
608 mmhal_set_led(LED_BLUE, LED_ON);
609 ulCurrentPowerOnState = ulDesiredPowerOnState;
610 }
611 else if (ulDesiredPowerOnState == 0)
612 {
613 /* Set the new powerOn state. */
614 printf("INF:Setting powerOn state to 0.\n");
615 mmhal_set_led(LED_BLUE, LED_OFF);
616 ulCurrentPowerOnState = ulDesiredPowerOnState;
617 }
618 else
619 {
620 /* Set the new powerOn state. */
621 printf("ERR:Invalid power state %lu requested.\n", ulDesiredPowerOnState);
622 }
623
624 char UpdateDocument[MAX_JSON_LEN];
625 printf("INF:Reporting change in PowerOn state to %lu.\n", ulCurrentPowerOnState);
626
627 /* Create a new client token and save it for use in the callbacks */
628 uint32_t ulClientToken = (mmosal_get_time_ticks() % 1000000);
629
630 /* Generate update report. */
631 (void)memset(UpdateDocument, 0x00, sizeof(UpdateDocument));
632 snprintf(UpdateDocument,
635 ulCurrentPowerOnState,
636 ulClientToken);
637
638 /* Send update. */
639 aws_publish_shadow(shadow_name, UpdateDocument);
640
641 printf("INF:Publishing to /update with following client token %lu.\n", ulClientToken);
642 }
643}
644#endif
645
650void app_init(void)
651{
652 printf("\n\nMorse AWS IoT Demo (Built " __DATE__ " " __TIME__ ")\n\n");
653
654 /* Initialize and connect to Wi-Fi, blocks till connected */
657
658 /* Wi-Fi is connected, sync to NTP - required for certificate expiry validation */
659 char sntp_server[64];
660 strncpy(sntp_server, "time.aws.com", sizeof(sntp_server)); /* default if key is not found */
661 (void)mmconfig_read_string("sntp.server", sntp_server, sizeof(sntp_server));
662 sntp_sync_with_backoff(sntp_server,
667 UINT32_MAX);
668
669 /* Display current time */
670 time_t now;
671 now = mmhal_get_time();
672 printf("Current Time (UTC) is : %s\r\n", ctime(&now));
673
674 /* First spool up the MQTT agent task */
675 start_mqtt_agent_task();
676
677 /* Look for shadow name in config store, if none found use classic shadow (NULL) */
678 char *shadow_name = NULL;
679 mmconfig_alloc_and_load(AWS_KEY_SHADOW_NAME, (void **)&shadow_name);
680
681 /* Check if fleet provisioning is needed */
683 {
684#if defined(ENABLE_PROVISIONING_APP) && ENABLE_PROVISIONING_APP
685 /* Ensure provisioning template is set */
687 {
688 /* Device not registered, so start fleet provisioning, returns only on failure. */
689 printf("Initiating fleet provisioning...\n");
690 do_fleet_provisioning();
691 printf("Failed to provision device, unable to continue.\n"
692 "Please see getting started guide on how to provision.\n");
693 return;
694 }
695#else
696 printf("Device is not provisioned, "
697 "please see getting started guide on how to provision.\n");
698#endif
699 }
700
701#if defined(ENABLE_SHADOW_APP) && ENABLE_SHADOW_APP
702 state_change_sem = mmosal_sem_create(1, 1, "state_change_sem");
703
704 /* Start the device shadow processing loop */
705 aws_create_shadow(shadow_name, shadow_update_callback);
706 aws_shadow_loop(shadow_name);
707#endif
708}
#define NTP_MIN_BACKOFF_JITTER
Minimum back-off jitter per attempt.
Definition: aws_iot.c:384
#define NTP_MIN_TIMEOUT
Minimum NTP timeout per attempt.
Definition: aws_iot.c:380
#define SHADOW_PUBLISH_JSON
Format string representing a Shadow document with a "reported" state.
Definition: aws_iot.c:407
#define NTP_MIN_BACKOFF
We need to back-off at least 60 seconds or most NTP Servers will tell us to go away.
Definition: aws_iot.c:382
void app_init(void)
Main entry point to the application.
Definition: aws_iot.c:650
#define NTP_MAX_BACKOFF_JITTER
Maximum back-off jitter per attempt.
Definition: aws_iot.c:386
Contains the configuration parameters for the AWS IoT application.
#define AWS_KEY_PROVISIONING_TEMPLATE
The config store key storing the fleet provisioning template name.
#define MAX_JSON_LEN
Maximum length of JSON strings.
#define AWS_KEY_SHADOW_NAME
The config store key storing the AWS shadow name.
#define AWS_KEY_THING_NAME
The config store key storing the thing name, if not found it is assumed the device needs provisioning...
int mmconfig_read_string(const char *key, char *buffer, int bufsize)
Returns the persistent store string value identified by the key.
int mmconfig_alloc_and_load(const char *key, void **data)
Allocates memory and loads the data from persistent memory into it returning a pointer.
#define LED_OFF
A value of 0 turns OFF an LED.
Definition: mmhal_app.h:44
void mmhal_set_led(uint8_t led, uint8_t level)
Set the specified LED to the requested level.
time_t mmhal_get_time(void)
Returns the time of day as set in the RTC.
#define LED_ON
A value of 255 turns an LED ON fully.
Definition: mmhal_app.h:53
bool mmosal_sem_give(struct mmosal_sem *sem)
Give a counting semaphore.
struct mmosal_sem * mmosal_sem_create(unsigned max_count, unsigned initial_count, const char *name)
Create a new counting semaphore.
bool mmosal_sem_wait(struct mmosal_sem *sem, uint32_t timeout_ms)
Wait for a counting semaphore.
uint32_t mmosal_get_time_ticks(void)
Get the system time in ticks.
Morse Micro application helper routines for initializing/de-initializing the Wireless LAN interface a...
void app_wlan_init(void)
Initializes the WLAN interface (and dependencies) using settings specified in the config store.
void app_wlan_start(void)
Starts the WLAN interface and connects to Wi-Fi using settings specified in the config store.