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