From Initializing to Sending a Message Through Sidewalk#

Note that in Amazon Sidewalk, you can add the support for an external radio for the FSK and CSS modulation. For instructions on configuring the Sidewalk stack to use an external radio transceiver, refer to Integrating External Semtech Radios.

Initialize Sidewalk Protocol Stack#

You can find an example of Sidewalk initialization in the main_thread function in app_process.c.

To initiate a link with Amazon Sidewalk, it is essential to first populate the sid_config structure with the necessary configuration details. This structure is defined as follows:

struct sid_config {
    uint32_t link_mask;
    struct sid_event_callbacks *callbacks;
    const struct sid_ble_link_config *link_config;
    const struct sid_sub_ghz_links_config *sub_ghz_link_config;
};

The link_mask in the sid_config structure specifies which Sidewalk radio links the device will set up and use. For instance, to configure a single link, you would assign SID_LINK_TYPE_1 to link_mask. For multiple links, you would combine them using the bitwise OR operator, such as SID_LINK_TYPE_1 | SID_LINK_TYPE_2 | SID_LINK_TYPE_3. The enumeration sid_link_type lists the possible values that can be combined for the link_mask.

enum sid_link_type {
    /** Bluetooth Low Energy link */
    SID_LINK_TYPE_1 = 1 << 0,
    /** 900 MHz link for FSK */
    SID_LINK_TYPE_2 = 1 << 1,
    /** 900 MHz link for LORA */
    SID_LINK_TYPE_3 = 1 << 2,
    /** Any Link Type */
    SID_LINK_TYPE_ANY = INT_MAX,
};

The callbacks member holds the event callbacks for the selected link, which must be stored in static const storage since the Sidewalk stack accesses this member without making a copy.

For BLE link-specific configuration, a pointer to struct sid_ble_link_config is passed. Similarly, the sub_ghz_link_config is used for configuring the Sub-GHz link.

Once the sid_config structure is correctly filled out, the sid_init function can be called with this configuration to initiate the link. The function must be called only once for a given link type unless sid_deinit() is called to deinitialize it.

sid_error_t sid_init(const struct sid_config *config, struct sid_handle **handle);

This initialization process is crucial as it sets up the foundational parameters for the Sidewalk stack to operate correctly. It ensures that the device is ready to start communication over the Sidewalk network according to the specified configurations. Remember, proper initialization must precede any attempt to start a link using sid_start. This structured approach to initialization and starting links ensures a robust and error-free operation of the Amazon Sidewalk stack.

You can find an example code to initialize the Amazon Sidewalk stack in the main_thread function of app_process.c. Below is also a small example. The sub_ghz_link_config can be removed if the application should only support BLE radio.

  // Set configuration parameters
  struct sid_config config =
  {
    .link_mask = SID_LINK_TYPE_1 | SID_LINK_TYPE_2, //BLE and FSK
    .callbacks   = &event_callbacks,
    .link_config = &ble_config,
    .sub_ghz_link_config = &sub_ghz_link_config,
  };

  struct sid_handle *sidewalk_handle = NULL;

  sid_error_t ret = sid_init(&config, &sidewalk_handle);
  if (ret != SID_ERROR_NONE) {
    SL_SID_LOG_APP_ERROR("sidewalk initialization failed, link mask: %x, error: %d", (int)config.link_mask, (int)ret);
    goto error;
  }

The ble_config parameter controls the configuration of the BLE stack, including the name of your device, the advertising and connection parameters and the output power. The sub_ghz_link_config member controls the Sub-GHz radio configuration, including maximum output power and registration over Sub-GHz enablement.

The details for the event_callbacks structure will be elaborated upon in the subsequent sections.

Start and Stop the Sidewalk Protocol Stack#

Once the platform for Amazon Sidewalk is initialized, the Sidewalk stack can be activated using the sid_start API. This action is contingent upon the prior initialization of the link. Thanks to the multi-link and auto connect features, multiple links can be initiated simultaneously, but only a single link will be used for uplink and downlink at a given moment. The stack can be started by the main_thread function in app_process.c of the Amazon Sidewalk Empty application.

sid_error_t sid_start(struct sid_handle *handle, uint32_t link_mask);

The sid_start function is versatile, allowing for the initiation of one or multiple links simultaneously. For a single link, you would set link_mask to the specific link type, such as SID_LINK_TYPE_1. For multiple links, you would use the bitwise OR operator to combine them, like SID_LINK_TYPE_1 | SID_LINK_TYPE_3.

However, it's crucial to remember that you can only start a link type that was previously initialized with sid_init.

In summary, sid_start is the function that activates the Sidewalk stack, enabling it to perform its intended tasks and ensuring that the device is ready for communication over the Sidewalk network.

Here is an example code to start the Amazon Sidewalk stack:

uint32_t link_mask = SID_LINK_TYPE_1 | SID_LINK_TYPE_2; // BLE and FSK

sid_error_t ret = sid_start(sidewalk_handle, link_mask);
if (ret != SID_ERROR_NONE) {
    SL_SID_LOG_APP_ERROR("sidewalk start failed, link mask: %x, error: %d", (int)config.link_mask, (int)ret);
    goto error;
}

To halt the operations of the Sidewalk stack, use the sid_stop function. When this function is called, the stack will cease to send or receive messages, and all notifications will be suspended. The link status will be updated to reflect a disconnected state, and the time synchronization status will be preserved for future use.

sid_error_t sid_stop(struct sid_handle *handle, uint32_t link_mask);

The function allows for stopping either a single link or multiple links at once. For example, to stop a single link, set link_mask to SID_LINK_TYPE_1. To stop multiple links, combine them using the bitwise OR operator, like SID_LINK_TYPE_1 | SID_LINK_TYPE_3.

It's important to note that sid_stop can only be used to stop links that were previously initialized with sid_init. This ensures that the stack is properly configured and that the stop operation is performed on an active link.

In essence, sid_stop is a control function that provides the ability to gracefully shut down the Sidewalk stack's operations, ensuring that the device can safely transition to a non-operational state while preserving essential status information for future operations.

Here is an example code to stop the Amazon Sidewalk stack:

uint32_t link_mask = SID_LINK_TYPE_1 | SID_LINK_TYPE_2;

sid_error_t ret = sid_stop(sidewalk_handle, link_mask);
if(ret != SID_ERROR_NONE) {
    SL_SID_LOG_APP_ERROR("failed to stop the stack: %d", (int)ret);
    return false;
}

Amazon Sidewalk and RTOS#

The Amazon Sidewalk stack operates on a real-time OS (FreeRTOS). To manage events and ensure the stack remains operational, you need to implement a loop in the main thread. In the main_thread function, there's already a while(1) loop that can be utilized for this purpose. Essentially, you need to handle events as they occur. The primary event to keep the stack running is EVENT_TYPE_SIDEWALK, and you must process this event to maintain the stack's operation. Additional events can be defined as needed for other purposes. A basic structure for the main loop could be as follows:

At the top of app_process.c, initialize a few structures:

static QueueHandle_t g_event_queue;
// Sidewalk Events
enum event_type{
  EVENT_TYPE_SIDEWALK = 0,
  EVENT_TYPE_SEND_MESSAGE,
  //Add your custom event types here
  EVENT_TYPE_INVALID
};
#define MSG_QUEUE_LEN (10U)

Then, main_thread will display as follows:

g_event_queue = xQueueCreate(MSG_QUEUE_LEN, sizeof(enum event_type));
app_assert(g_event_queue != NULL, "app: queue creation failed");

while (1) {
    // Add your code
    enum event_type event = EVENT_TYPE_INVALID;
    if (xQueueReceive(g_event_queue, &event, portMAX_DELAY) == pdTRUE) {
        switch (event) {
        case EVENT_TYPE_SIDEWALK:
            sid_process(sidewalk_handle);
            break;
        case EVENT_TYPE_SEND_MESSAGE:
            //call to sid_put_msg here
            break;
        default:
            SL_SID_LOG_APP_ERROR("app: unexpected evt: %d", (int)event);
            break;
        }
    }
}

Finally the on_sidewalk_event callback should be implemented to add the EVENT_TYPE_SIDEWALK to the queue.

static void on_sidewalk_event(bool in_isr,
                              void *context)
{
  UNUSED(in_isr);
  UNUSED(context);
  queue_event(g_event_queue, EVENT_TYPE_SIDEWALK);
}

The queue_event function used in this example is the same as the one implemented in Hello Neighbor:

static void queue_event(QueueHandle_t queue,
                        enum event_type event)
{
  // Check if queue_event was called from ISR
  if ((bool)xPortIsInsideInterrupt()) {
    BaseType_t task_woken = pdFALSE;

    xQueueSendFromISR(queue, &event, &task_woken);
    portYIELD_FROM_ISR(task_woken);
  } else {
    xQueueSend(queue, &event, 0);
  }
}

This consists of the minimum to have the Sidewalk stack running.

Check Sidewalk Protocol Status#

To check the current status of the Amazon Sidewalk stack, use the on_status_changed or the sid_get_status API. Both return detailed information encapsulated within the sid_status structure.

Here is the on_status_changed callback trace. You can find it in app_process.c file of the Amazon Sidewalk Empty sample application:

void (*on_status_changed)(const struct sid_status *status, void *context);

Here is the sid_get_status function trace:

sid_error_t sid_get_status(struct sid_handle *handle, struct sid_status *current_status);

Here is the sid_status structure definition:

struct sid_status {
    /** The current state */
    enum sid_state state;
    /** Details of Sidewalk stack status */
    struct sid_status_detail detail;
};

To use the function, pass the handle obtained from sid_init() and a pointer to a sid_status structure where the current status will be stored. If the function succeeds, the current_status will contain the latest status information from the Sidewalk library.

The sid_state enumeration and the sid_status_detail structure provide a comprehensive overview of the current status and state of the Sidewalk stack. Here's an explanation of their components:

The sid_state enumeration describes the operational state of the Sidewalk stack:

  • SID_STATE_READY: Indicates that the Sidewalk stack is operational and ready to send and receive messages.

  • SID_STATE_NOT_READY: Used when the Sidewalk stack cannot send or receive messages, such as when the device is not registered, the link is disconnected, or time is not synchronized.

  • SID_STATE_ERROR: Signifies that the Sidewalk stack has encountered an error. In this case, sid_get_error() should be called to obtain a diagnostic error code.

  • SID_STATE_SECURE_CHANNEL_READY: This state means that the Sidewalk stack can send and receive messages with a secure channel established, but the device is not registered, and time is not synchronized.

The sid_status_detail structure contains several fields:

  • registration_status: This indicates the registration status of the Sidewalk device.

  • time_sync_status: This indicates whether the Sidewalk device has successfully synchronized its time with the Sidewalk network.

  • link_status_mask: This is a bitmask used to determine which links are currently active. If the bit corresponding to a link is set, that link is up; otherwise, it is down. For example, to check if SID_LINK_TYPE_1 is up, the expression !!(link_status_mask & SID_LINK_TYPE_1) needs to be true.

  • supported_link_modes: This array holds the supported modes for each link type, where a link type may support more than one mode simultaneously.

These components are crucial to understand the status and state of the Sidewalk stack, as they provide insights into the device's connectivity, registration, and readiness to function within the Sidewalk network.

Here is an example code to check the Amazon Sidewalk stack status:

struct sid_status state = {};

sid_error_t ret = sid_get_status(sidewalk_handle, &state);
if(ret != SID_ERROR_NONE) {
    SL_SID_LOG_APP_ERROR("Sidewalk stack is not initialized: %d", (int)ret);
    return false;
}

Here is an example of the on_status_changed callback implementation to display the Sidewalk status in the logs:

static void on_sidewalk_status_changed(const struct sid_status *status,
                                       void *context)
{
  SL_SID_LOG_APP_INFO("app: REG: %u, TIME: %u, LINK: %lu",
               status->detail.registration_status,
               status->detail.time_sync_status,
               status->detail.link_status_mask);
}

Send a Message Through Sidewalk#

The sid_put_msg function is designed to queue a message for transmission in the Sidewalk stack.

sid_error_t sid_put_msg(struct sid_handle *handle, const struct sid_msg *msg, struct sid_msg_desc *msg_desc);

Here's an explanation of how it works:

When you call sid_put_msg, you need to provide it with three parameters:

  • handle: This is a pointer to the handle that you received when you called sid_init(). It's essentially a reference to the initialized Sidewalk stack.

  • msg: This is the actual message data that you want to send.

  • msg_desc: This is a message descriptor that the function will fill out. It serves as an identifier for the message you're sending.

In summary, sid_put_msg is a function that queues messages for transmission over the Sidewalk network. Here is an example code to send a message over Amazon Sidewalk:

static void send_sidewalk_message(struct sid_handle *app_context)
{
  char message_buff[15] = "Hello Sidewalk!";

  struct sid_msg msg = {
          .data = (void *)message_buff,
          .size = sizeof(message_buff)
      };
  struct sid_status status = {};

  sid_error_t ret = sid_get_status(app_context, &status);
  if(ret != SID_ERROR_NONE) {
      SL_SID_LOG_APP_ERROR("Sidewalk stack is not initialized: %d", (int)ret);
      return;
  }

  if (status.state == SID_STATE_READY || status.state == SID_STATE_SECURE_CHANNEL_READY) {
      SL_SID_LOG_APP_INFO("sending message through Sidewalk");

      struct sid_msg_desc desc = {
          .type = SID_MSG_TYPE_NOTIFY,
          .link_type = SID_LINK_TYPE_ANY,
      };

      sid_error_t ret = sid_put_msg(app_context, &msg, &desc);
      if (ret != SID_ERROR_NONE) {
          SL_SID_LOG_APP_ERROR("queueing data failed: %d", (int)ret);
      } else {
          SL_SID_LOG_APP_INFO("queued data msg id: %u", desc.id);
      }

  } else {
      SL_SID_LOG_APP_ERROR("sidewalk is not ready yet");
  }
}

You can call the send_sidewalk_message function from the previously implemented main loop. Just add the function call to the EVENT_TYPE_SEND_MESSAGE event type and set up a trigger to queue this event (such as a CLI command, button press, or timer).

Receive a Message Through Sidewalk#

When a message is received from the Sidewalk network, the on_msg_received callback is called. You can find its definition in app_process.c of the Amazon Sidewalk Empty application.

void (*on_msg_received)(const struct sid_msg_desc *msg_desc, const struct sid_msg *msg, void *context);

When a message is received, the Sidewalk stack invokes this callback, passing the message descriptor and payload. We can then use these parameters to process the message accordingly. It is crucial to handle the received message efficiently within the callback to ensure the application responds correctly to incoming data from the Sidewalk network.

Here is an example of the on_msg_received callback implementation:

static void on_sidewalk_msg_received(const struct sid_msg_desc *msg_desc,
                                     const struct sid_msg *msg,
                                     void *context)
{
  UNUSED(context);
  SL_SID_LOG_APP_INFO("downlink message received");
  SL_SID_LOG_APP_INFO("msg (type: %d, id: %u, size: %u)", (int)msg_desc->type, msg_desc->id, msg->size);
  if (msg->size != 0) {
    SL_SID_LOG_APP_INFO("received message: %.*s", msg->size, (char *)msg->data);
  }
}

Amazon Sidewalk Callbacks#

The Amazon Sidewalk stack operates on an event-driven architecture, which enables applications to respond to events as they occur in real-time. This architecture relies heavily on the use of callbacks to handle various events. Ensuring that these callbacks are implemented correctly is crucial for the reliable functioning of devices that utilize the Sidewalk network. As seen above during Sidewalk stack initialization, the list of callbacks is defined within the struct sid_event_callbacks as follows:

struct sid_event_callbacks {
    void *context;
    void (*on_event)(bool in_isr, void *context);
    void (*on_msg_received)(const struct sid_msg_desc *msg_desc, const struct sid_msg *msg, void *context);
    void (*on_msg_sent)(const struct sid_msg_desc *msg_desc, void *context);
    void (*on_send_error)(sid_error_t error, const struct sid_msg_desc *msg_desc, void *context);
    void (*on_status_changed)(const struct sid_status *status, void *context);
    void (*on_factory_reset)(void *context);
    void (*on_control_event_notify)(const struct sid_control_event_data *data, void *context);
};

Each callback serves a specific purpose within the Sidewalk stack's lifecycle, from handling events and message reception to dealing with errors and status changes. The .context member is a pointer to the application context, which provides a reference that can be used within the callbacks to access application-specific data. For example, the callback on_status_changed can be used to update the status of the stack in the context user-defined structure. The callbacks prefixed with on_ represent the functions that will be called when the corresponding event occurs, ensuring that the application can handle these events appropriately.

The event_callbacks structure is already present in the Amazon Sidewalk Empty example with all function callbacks already present but not implemented. The callbacks are defined as follows:

  • on_event: is called for any generic Sidewalk event not already handled by another callback.

  • on_msg_received: is triggered when a message is received by Sidewalk.

  • on_msg_sent: is triggered when a message is effectively sent and corresponding ACK (from the MAC layer) is received.

  • on_send_error: is triggered for any error during message sending, including if message was sent but ACK (from the MAC layer) was not received.

  • on_status_changed: is called when the Sidewalk status changes, for example when the stack changes from unregistered to time sync registered state (SIDEWALK_NOT_READY > SIDEWALK_READY).

  • on_factory_reset: is triggered when a factory reset command was issued. A factory reset unregisters a Sidewalk device.

Amazon Sidewalk Multi-link and Auto Connect Feature#

Starting with Sidewalk stack v1.16, the Multi-link feature was introduced. This feature abstracts the process of connection establishment and maintenance for the radio links supported by the Sidewalk stack, making it easier for developers. It offers developers varying degrees of flexibility to control not only the connection behavior of the links but also the transfer of messages over them.

You can find the dedicated page on Multi-link in our documentation here.