Morse Micro IoT SDK  2.9.7
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
504#include <string.h>
505#include "mmhal.h"
506#include "mmosal.h"
507#include "mmconfig.h"
508#include "mm_app_loadconfig.h"
509#include "mmipal.h"
510#include "mqtt_agent_task.h"
511#if defined(ENABLE_SHADOW_APP) && ENABLE_SHADOW_APP
512#include "shadow_device_task.h"
513#endif
514#if defined(ENABLE_OTA_APP) && ENABLE_OTA_APP
515#include "ota_update_task.h"
516#endif
517#if defined(ENABLE_PROVISIONING_APP) && ENABLE_PROVISIONING_APP
518#include "fleet_provisioning_task.h"
519#endif
520#include "sntp_client.h"
521#include "core_json.h"
522#include "mm_app_common.h"
523#include "aws_iot_config.h"
524
526#define NTP_MIN_TIMEOUT 3000
528#define NTP_MIN_BACKOFF 60000
530#define NTP_MIN_BACKOFF_JITTER 3000
532#define NTP_MAX_BACKOFF_JITTER 60000
533
553#define SHADOW_PUBLISH_JSON \
554 "{" \
555 "\"state\":{" \
556 "\"reported\":{" \
557 "\"powerOn\":%lu" \
558 "}" \
559 "}," \
560 "\"clientToken\":\"%06lu\"" \
561 "}"
562
563#if defined(ENABLE_OTA_APP) && ENABLE_OTA_APP
572void ota_preupdate_callback(void)
573{
574 /* An OTA update has been triggered, print a message notifying the user */
575 printf("An OTA update has been triggered, downloading the file in the background...\n");
576
577 /* Perform any cleanup for the file system such as deleting logs, uploading data, etc.
578 * If the device runs out of space in the filesystem while downloading the update then
579 * the update will fail. You may block till cleanup is completed. */
580}
581
595void ota_postupdate_callback(const char *update_file, int status)
596{
597 (void)update_file;
598
599 if (status == 0)
600 {
601 printf("OTA Update completed successfully\n");
602 }
603 else
604 {
605 printf("OTA Update failed with error code %d\n", status);
606 }
607}
608#endif
609
610#if defined(ENABLE_SHADOW_APP) && ENABLE_SHADOW_APP
611
613static uint32_t ulCurrentPowerOnState = 0;
614
616static uint32_t ulDesiredPowerOnState = 0;
617
619struct mmosal_sem *state_change_sem = NULL;
620
628void shadow_update_callback(char *json, size_t json_len,
629 enum shadow_update_status status)
630{
631 /* Make sure the payload is a valid json document. */
632 int result = JSON_Validate(json, json_len);
633
634 if (result != JSONSuccess)
635 {
636 printf("ERR:Invalid JSON document received\n");
637 return;
638 }
639
640 static uint32_t ulCurrentVersion = 0; /* Remember the latest version number we've received */
641 char *pcOutValue = NULL;
642 uint32_t ulOutValueLength = 0UL;
643 uint32_t ulVersion = 0;
644 uint32_t ulCode = 0;
645
646 switch (status)
647 {
648 case UPDATE_DELTA:
649 /* The json will look similar to this:
650 * @code
651 * {
652 * "state": {
653 * "powerOn": 1
654 * },
655 * "metadata": {
656 * "powerOn": {
657 * "timestamp": 1595437367
658 * }
659 * },
660 * "timestamp": 1595437367,
661 * "clientToken": "388062",
662 * "version": 12
663 * }
664 * @endcode
665 */
666
667 /* Obtain the version value. */
668 result = JSON_Search(json, json_len,
669 "version", strlen("version"),
670 &pcOutValue,
671 (size_t *)&ulOutValueLength);
672
673 if (result != JSONSuccess)
674 {
675 printf("ERR:Version field not found in JSON document\n");
676 return;
677 }
678 /* Convert the extracted value to an unsigned integer value. */
679 ulVersion = (uint32_t)strtoul(pcOutValue, NULL, 10);
680 /* Make sure the version is newer than the last one we received. */
681 if (ulVersion <= ulCurrentVersion)
682 {
683 /* In this demo, we discard the incoming message
684 * if the version number is not newer than the latest
685 * that we've received before. Your application may use a
686 * different approach. */
687 printf("ERR:Received unexpected delta update with version %u, "
688 "current version is %u\n",
689 (unsigned int)ulVersion,
690 (unsigned int)ulCurrentVersion);
691 return;
692 }
693 /* Set received version as the current version. */
694 ulCurrentVersion = ulVersion;
695
696 /* Get powerOn state from json documents. */
697 result = JSON_Search(json, json_len,
698 "state.powerOn",
699 sizeof("state.powerOn") - 1,
700 &pcOutValue,
701 (size_t *)&ulOutValueLength);
702
703 if (result != JSONSuccess)
704 {
705 printf("ERR:powerOn field not found in JSON document\n");
706 }
707 else
708 {
709 /* Convert the powerOn state value to an unsigned integer value. */
710 ulDesiredPowerOnState = (uint32_t)strtoul(pcOutValue, NULL, 10);
711
712 /* Signal user task about change of state */
713 mmosal_sem_give(state_change_sem);
714 }
715 break;
716
717 case UPDATE_ACCEPTED:
718 /* Handle the reported state with state change in /update/accepted topic.
719 * Thus we will retrieve the client token from the JSON document to see if
720 * it's the same one we sent with reported state on the /update topic.
721 * The payload will look similar to this:
722 * @code
723 * {
724 * "state": {
725 * "reported": {
726 * "powerOn": 1
727 * }
728 * },
729 * "metadata": {
730 * "reported": {
731 * "powerOn": {
732 * "timestamp": 1596573647
733 * }
734 * }
735 * },
736 * "version": 14698,
737 * "timestamp": 1596573647,
738 * "clientToken": "022485"
739 * }
740 * @endcode
741 */
742
743 /* We do not need to do anything here unless we want to implement positive confirmation
744 * that our reported state was received and acted on by the server. In which case ensure
745 * you check that the @c clientToken matches the one we sent in the report. */
746 break;
747
748 case UPDATE_REJECTED:
749 /* The payload will look similar to this:
750 * {
751 * "code": error-code,
752 * "message": "error-message",
753 * "timestamp": timestamp,
754 * "clientToken": "token"
755 * }
756 */
757
758 /* Obtain the error code. */
759 result = JSON_Search(json, json_len,
760 "code",
761 sizeof("code") - 1,
762 &pcOutValue,
763 (size_t *)&ulOutValueLength);
764
765 /* Convert the code to an unsigned integer value. */
766 ulCode = (uint32_t)strtoul(pcOutValue, NULL, 10);
767
768 printf("ERR:Received rejected response code %lu\n",
769 ulCode);
770
771 /* Obtain the message string. */
772 result = JSON_Search(json, json_len,
773 "message",
774 sizeof("message") - 1,
775 &pcOutValue,
776 (size_t *)&ulOutValueLength);
777 printf(" Message: %.*s\n", (int)ulOutValueLength, pcOutValue);
778
779 break;
780 }
781}
782
788void aws_shadow_loop(char *shadow_name)
789{
790 for (;;)
791 {
792 mmosal_sem_wait(state_change_sem, UINT32_MAX);
793
794 if (ulDesiredPowerOnState == 1)
795 {
796 /* Set the new powerOn state. */
797 printf("INF:Setting powerOn state to 1.\n");
798 mmhal_set_led(LED_BLUE, LED_ON);
799 ulCurrentPowerOnState = ulDesiredPowerOnState;
800 }
801 else if (ulDesiredPowerOnState == 0)
802 {
803 /* Set the new powerOn state. */
804 printf("INF:Setting powerOn state to 0.\n");
805 mmhal_set_led(LED_BLUE, LED_OFF);
806 ulCurrentPowerOnState = ulDesiredPowerOnState;
807 }
808 else
809 {
810 /* Set the new powerOn state. */
811 printf("ERR:Invalid power state %lu requested.\n", ulDesiredPowerOnState);
812 }
813
814 char UpdateDocument[MAX_JSON_LEN];
815 printf("INF:Reporting change in PowerOn state to %lu.\n", ulCurrentPowerOnState);
816
817 /* Create a new client token and save it for use in the callbacks */
818 uint32_t ulClientToken = (mmosal_get_time_ticks() % 1000000);
819
820 /* Generate update report. */
821 (void)memset(UpdateDocument, 0x00, sizeof(UpdateDocument));
822 snprintf(UpdateDocument, MAX_JSON_LEN, SHADOW_PUBLISH_JSON,
823 ulCurrentPowerOnState, ulClientToken);
824
825 /* Send update. */
826 aws_publish_shadow(shadow_name, UpdateDocument);
827
828 printf("INF:Publishing to /update with following client token %lu.\n", ulClientToken);
829 }
830}
831#endif
832
837void app_init(void)
838{
839 printf("\n\nMorse AWS IoT Demo (Built " __DATE__ " " __TIME__ ")\n\n");
840
841 /* Initialize and connect to Wi-Fi, blocks till connected */
844
845 /* Wi-Fi is connected, sync to NTP - required for certificate expiry validation */
846 char sntp_server[64];
847 strncpy(sntp_server, "0.pool.ntp.org", sizeof(sntp_server)); /* default if key is not found */
848 (void)mmconfig_read_string("sntp.server", sntp_server, sizeof(sntp_server));
849 sntp_sync_with_backoff(sntp_server, NTP_MIN_TIMEOUT, NTP_MIN_BACKOFF,
851
852 /* Display current time */
853 time_t now;
854 now = mmhal_get_time();
855 printf("Current Time (UTC) is : %s\r\n", ctime(&now));
856
857 /* First spool up the MQTT agent task */
858 start_mqtt_agent_task();
859
860 /* Look for shadow name in config store, if none found use classic shadow (NULL) */
861 char *shadow_name = NULL;
862 mmconfig_alloc_and_load(AWS_KEY_SHADOW_NAME, (void **)&shadow_name);
863
864 /* Check if fleet provisioning is needed */
866 {
867#if defined(ENABLE_PROVISIONING_APP) && ENABLE_PROVISIONING_APP
868 /* Ensure provisioning template is set */
870 {
871 /* Device not registered, so start fleet provisioning, returns only on failure. */
872 printf("Initiating fleet provisioning...\n");
873 do_fleet_provisioning();
874 printf("Failed to provision device, unable to continue.\n"
875 "Please see getting started guide on how to provision.\n");
876 return;
877 }
878#else
879 printf("Device is not provisioned, "
880 "please see getting started guide on how to provision.\n");
881#endif
882 }
883
884#if defined(ENABLE_OTA_APP) && ENABLE_OTA_APP
885 /* Now spool up the OTA task */
886 start_ota_update_task(ota_preupdate_callback, ota_postupdate_callback);
887#endif
888
889#if defined(ENABLE_SHADOW_APP) && ENABLE_SHADOW_APP
890 state_change_sem = mmosal_sem_create(1, 1, "state_change_sem");
891
892 /* Start the device shadow processing loop */
893 aws_create_shadow(shadow_name, shadow_update_callback);
894 aws_shadow_loop(shadow_name);
895#endif
896}
#define NTP_MIN_BACKOFF_JITTER
Minimum back-off jitter per attempt.
Definition: aws_iot.c:530
#define NTP_MIN_TIMEOUT
Minimum NTP timeout per attempt.
Definition: aws_iot.c:526
#define SHADOW_PUBLISH_JSON
Format string representing a Shadow document with a "reported" state.
Definition: aws_iot.c:553
#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:528
void app_init(void)
Main entry point to the application.
Definition: aws_iot.c:837
#define NTP_MAX_BACKOFF_JITTER
Maximum back-off jitter per attempt.
Definition: aws_iot.c:532
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.h:66
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.h:75
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.