Wi-SUN API Calls to Connect to a Wi-SUN Network#

In the first part of this series, you connected to a Wi-SUN Network without looking at how this is achieved.

Connection was easy because you relied on the Wi-SUN Configurator GUI and the underlying Wi-SUN Application Core component.

This is a lecture on how the Wi-SUN Application Core component uses the Wi-SUN Stack API functions to achieve successful connection. Key points to understand are:

  • Some general principles used when developing a Wi-SUN application

  • Details of the connection process

Wi-SUN Application Core Component#

There are at least four ways to check that the Wi-SUN Application Core component is part of the project.

  1. Check the installed components in the SOFTWARE COMPONENT tab. You get access to an abstract of the component's readme, indicating the use of app_wisun_core_init() to initialize it.

    application core software components

  2. Check the content of wisun_soc_empty.slcp with a text editor.

    application core in slcp

  3. In the source code, you can see that the main() function calls sl_system_init(), which calls sl_internal_app_init(), which calls app_wisun_core_init(). This code is automatically added when adding the component via the GUI.

    application core flow

  4. In app.c/app_task(), you see that the code surrounded by SL_CATALOG_WISUN_APP_CORE_PRESENT is not grayed out, indicating that SL_CATALOG_WISUN_APP_CORE_PRESENT is defined and the corresponding code will be compiled.

    application_core_in_app_c.png

The Wi-SUN Application Core component uses the Wi-SUN Configurator settings to achieve connection to a Wi-SUN Network with only GUI actions. This is the simplest way to connect to any Wi-SUN Network.

Now consider which function calls are required to connect to a Wi-SUN Network.

Wi-SUN Network Connection Walkthrough#

You saw earlier the connection process in /wisun_soc_empty/gecko_sdk_x.y.z/app/wisun/component/app_core/sl_wisun_app_core.c, with several steps during the connection preparation.

in app_wisun_network_connect.png

Preparation of the PHY and Network Settings#

The grayed out part is not compiled, so the code uses line 409.

  memcpy(&_setting, &_app_default_settings, sizeof(app_setting_wisun_t));

This line copies the _app_default_settings into _setting, a app_setting_wisun_t structure defined earlier as:

/// Internal setting storage
static app_setting_wisun_t _setting = { 0 };

app_setting_wisun_t structure#

You can check the declaration of the app_setting_wisun_t structure by selecting the Open Declaration menu item.

app_setting_wisun_t open declaration.png

This brings you higher up in /wisun_soc_empty/gecko_sdk_x.y.z/app/wisun/component/app_core/sl_wisun_app_core.c to the declaration of the structure.

app_setting_wisun_t declaration.png

You can see here that the structure stores:

  • The network name (as a string, which always end up with an additional 0x00 byte as the string terminator, hence the + 1 in the array definition)

  • The network_size

  • The Tx power

  • The device type

  • The LFN profile (only used for LFNs)

  • A flag indicating if the device is using the default PHY

  • Another sl_wisun_phy_config_t structure describing the PHY

TIP: You can also check the definition of a structure by hovering over it until a pop-up appears with the definition.

app_setting_wisun_t popup

sl_wisun_phy_config_t structure#

sl_wisun_phy_config_t declaration

The sl_wisun_phy_config_t structure is a more complex union type, which requires clarification for people not familiar with C coding.

A union is a place where several underlying structures can be stored, but to save memory, only one will be stored at a time. In your example, you know which underlying structures can be used by looking at each struct {} <STRUCT_NAME>; block. You can locate:

  • fan10 on lines 503-512

  • fan11 on lines 515-522

  • explicit on lines 525-534

sl-wisun-phy-config-t-subs

Usually, unions start with a type information, here on line 499, which is used to select the underlying structure.

In your example application, you're using fan11, so the type will be set accordingly and the FAN 1.1 values will be accessed as:

  • phy.fan11.reg_domain

  • phy.fan11.chan_plan_id

  • phy.fan11.phy_mode_id

if phy is a sl_wisun_phy_config_t

or

  • phy->fan11.reg_domain

  • phy->fan11.chan_plan_id

  • phy->fan11.phy_mode_id

if phy is a pointer to a sl_wisun_phy_config_t

TIP: When moving around files in Simplicity Studio, use Alt+left_arrow or Alt+Right_arrow to move between recent locations.

Check of the Current Join State#

Back to app_wisun_network_connect(), you see that there is a check of the current join state. This is because, before calling the join function, the device must be disconnected so that it properly starts using the new parameters and clears its state machine.

check join state

Here it's worth looking at what the __CHECK_FOR_STATUS is doing, since you may see it used often. Use the pop-up since it's relatively short.

check for status macro

You can understand looking at the macro definition that an error message will be printed with the error number when the ret value is not 0x0000, and if ret is 0x0000, nothing will happen. If an error message will be printed, it will also contain the name of the function from where it is originating, (here app_wisun_network_connect), as a replacement for the __PRETTY_FUNCTION__ identifier. Defining macros such as these is a good way to avoid repeating the same code block in your code. It makes the code more compact and less error prone (once the macros are properly tested).

TIP: Generally, in C code, specific identifiers such as those declared by the compilers are prefixed and suffixed with __. Avoid using this in your own variable names to avoid confusion. Here, using the C++ __PRETTY_FUNCTION__ is not necessary, since the code is compiled as C code, not C++. Using __FUNCTION__ is sufficient.

_app_wisun_mutex_acquire() and _return_and_mtx_release()#

You see _app_wisun_mutex_acquire() and _return_and_mtx_release() in the code around some code blocks.

The _app_wisun_mutex_acquire() name is a bit more explicit. It indicates that you are acquiring a 'mutex', i.e, a mutual-exclusion flag. This is commonly used in C code when several parts of the code (from different threads) may need to access common variables.

Whenever the code needs to access such data, you want to make sure no other function is changing it at the same time, which could lead to unexpected results.

When calling _app_wisun_mutex_acquire(), you're waiting for the number of current users of the flag to be 0. Until the flag is 0, you wait. Once it is 0, the underlying system sets it to 1 and gives you the access. From this point on, you 'own' the mutex flag and the right to access the shared data.

This means that, if you never release it, all other users (including yourself) will never get access to the shared data!

When you're done, release the mutex by calling _return_and_mtx_release(), which, if you look into it, starts by releasing the mutex and then returns.

A typical pattern when using a mutex is:

  • Acquiring the mutex at the beginning of the function (as you see here on line 354)

  • Releasing the mutex before every return

TIP: You can see several occurrences of _return_and_mtx_release() in a single function, because you need to release the mutex before returning. To achieve this, all return calls are replaced by _return_and_mtx_release() on every place in the function where you return from.

Filling the Network Settings#

Next you get to the application settings, on line 379.

application settings

You see here that the code calls _app_wisun_application_setting with the content of _setting from the preparation of the PHY and Network settings.

Now look into this function (only showing code which is not grayed out, i.e. compiled code).

application settings func

On line 605, the sl_wisun_get_conn_param_by_nw_size() function is called to set the conn_param structure with values matching the network size selected by the user.

conn_param is a sl_wisun_connection_params_t structure (declared on line 600), defined as:

connection_params_t

As you can see, there are several underlying structures, used to store:

Your current Network Size setting is Small.

network_size

sl_wisun_get_conn_param_by_nw_size simply selects the corresponding settings based on this selection:

network_size case

In the declaration of SL_WISUN_PARAMS_PROFILE_SMALL, you see all parameters and their values.

network_size small

TIP: By default, the Wi-SUN Stack API defines three Network Sizes for general use: SMALL, MEDIUM, and LARGE. Each set of parameters corresponds to a good compromise for most applications. The application may customize these or add more if required; it's up to the user. An application not using the Wi-SUN Application Core component will need to define its own sl_wisun_connection_params_t structure.

TIP: If you want to customize the connection parameters and start editing the file, Simplicity Studio will ask you whether you want to create a copy of the file inside your project or edit the GSDK file. (Remember that GSDK files are normally only logical links to the GSDK files.) If you opt for the copy, the logical link will be replaced by a copy of the GSDK file, and your changes will be local to your project. If you opt for editing the GSDK file, your changes will affect all your Wi-SUN projects, but may be 'lost' when you update the GSDK. (They are not really lost, since they will still be present in the previous GSDK folder, but you will need to patch the new GSDK files with similar changes.)

Setting Security#

Next you get to the security settings, on line 427.

security settings

Going to the declaration of _app_wisun_security_setting(), you find the calls to set:

  • The CA (Certification Authority) certificate

  • The Device's certificate

  • The Device Private Key

CA certificate

Hovering over the corresponding variables, you can check their values. You can also open their declarations, which you find in autogen/sl_wisun_config.c. As you've already seen, these can be set on the Wi-SUN Configurator's Security tab.

TIP: For a production environment, the Device certificate and Private Key need to be unique per device.

Joining a Network with a Given Name and PHY Settings#

Finally, you reach the point where you call the Wi-SUN Stack API sl_wisun_join() function and check its return value.

This is where you use the Network Name you set earlier (using the Wi-SUN Configurator) in autogen/sl_wisun_config.h as WISUN_CONFIG_NETWORK_NAME.

sl_wisun_join

As you see in the code, the sl_wisun_join() function returns rapidly, before connection is complete, and then there is a check of the sl_wisun_join() return value.

  • If the call is successful, it means that your network settings are all okay and match the available PHY(s) that you added using the Wi-SUN Configurator.

    • A timestamp is set to the current time to indicate how much time it takes to connect.

  • If there is an error, most probably the PHY you selected is not in the list of PHYs in your configuration.

    • If you have been using the Wi-SUN Configurator and the default PHY, this should not happen.

Waiting for Connection#

To check the connection state, follow the join state of the Wi-SUN device. This is done by app_wisun_wait_for_connection().

wait

wait_for_connection

What you see here is that there is an endless loop calling app_wisun_network_is_connected().

network_is_connected

app_wisun_network_is_connected() checks the join_state and returns only when it's SL_WISUN_JOIN_STATE_OPERATIONAL = (5).

The join_state is returned by app_wisun_get_join_state().

get_join_state

app_wisun_get_join_state() acquires the mutex to read _join_state (note that there is a _ prefix, indicating that _join_state is an internal variable), stores it into join_state, releases the mutex, and returns join_state.

Using the mutex means that _join_state is set by another part of the code. To see how, look for occurrences of _join_state.

First, it's declared and initialized with SL_WISUN_JOIN_STATE_DISCONNECTED.

_join_state_declaration

Possible join state values are defined in gsdk/protocol/wisun/stack/inc/sl_wisun_types.h.

join states

The sl_wisun_join_state_t enumeration tells us what steps are used for connecting (re-ordered below by chronological order):

Join state

enum

meaning

actions

0

SL_WISUN_JOIN_STATE_DISCONNECTED

Disconnected

Device not attempting to connect. Border Router and connected routers send 'PAN Advert' on all channels

1

SL_WISUN_JOIN_STATE_SELECT_PAN

Select PAN

Device listens for 'PAN Advert'. Sends 'PAN Advert Solicit' to shorten the transmission delay

2

SL_WISUN_JOIN_STATE_AUTHENTICATE

Authenticate

Security key exchange between device and Border Router (can use an external Radius server)

3

SL_WISUN_JOIN_STATE_ACQUIRE_PAN_CONFIG

Acquire PAN config

Device listens for 'PAN Config'. Sends 'PAN Advert Solicit' to shorten the transmission delay

4

SL_WISUN_JOIN_STATE_CONFIGURE_ROUTING

Configure routing

Network Route Selection (see sub-steps below)

41

SL_WISUN_JOIN_STATE_PARENT_SELECT

Preferred parent selection

Comparison between potential parents. Selection of 'best parent'

42

SL_WISUN_JOIN_STATE_DHCP

DHCP address acquisition

DHCP address received from Border Router

43

SL_WISUN_JOIN_STATE_EARO

Address registration

44

SL_WISUN_JOIN_STATE_DAO

DAO registration

5

SL_WISUN_JOIN_STATE_OPERATIONAL

Operational

Device is operational. Starts sending 'PAN Advert' for other nodes to connect (if FFN/router)

  • By default, Wi-SUN SoC Empty doesn't track the intermediate 41-44 sub-steps between join state 4 and join state 5.

  • When reconnecting, provided that the credentials are still valid, the connection process is sped up by avoiding steps 1 and 2.

Now look at how events are handled, and the concepts behind the Wi-SUN Stack API.

Wi-SUN Stack API Concepts#

As stated in the Wi-SUN Stack API documentation:

  • Wi-SUN Stack API is based on requests from the application to the stack and events from the stack to the application.

  • Requests are made using function calls, where a function call either performs the required action immediately or initiates an internal operation within the stack, which terminates with an event. All events contain a status code, indicating the result of the requested operation. Events are also used by the stack to notify the application of any important information, such as the state of the connection.

  • The application is expected to override sl_wisun_on_event() to handle events from the stack. Because all events share a common header, the function may be implemented as a switch statement. The event-specific data can be accessed through the sl_wisun_evt_t::evt union.

Event Handling with the Event Manager Component#

gsdk/protocol/wisun/stack/inc/sl_wisun_events.h contains the list of possible events from the stack.

sl_wisun_evt_t

The first member of the sl_wisun_event_t structure is a sl_wisun_msg_header_t header.

sl_wisun_msg_header_t

From this header, the application needs to check the id value to know what the evt corresponds to, and then use the rest of the structure to get access to the event data.

Now see how this is implemented in gsdk/app/wisun/component/event_manager/sl_wisun_event_mgr.c/sl_wisun_on_event().

sl_wisun_on_event

On line 263, the _decode_ind() function is checking the evt->header.id with the list of the events your application has decided to process (i.e. 'handle').

_decode_ind

TIP: See the last value in the table, clearly indicating that any value not matching the preceding lines will return EVENT_IDX_NOTVALID.

In sl_wisun_on_event():

  • A message is printed whenever an 'invalid' (i.e. unprocessed) event is received for information purposes. Since this is informational, it's only displayed as an Unknown event.

  • If the event is one of those that the application decided to handle, the corresponding callback function in the Application Core Component is called with the evt data.

    • If a 'custom callback' function exists in the customer application , it is also called.

In GSDK/app/wisun/component/event_manager/sl_wisun_event_mgr.c:

  • You see all .callback functions

  • You see that no custom_callback is defined by default

wisun_events

The Wi-SUN Event Manager component provides app_wisun_em_custom_callback_register() to register custom callbacks.

If you want to add some custom processing on join state changes:

  • You create a custom callback function and add into this function your custom actions.

void join_state_custom_callback(sl_wisun_evt_t *evt) {
  sl_wisun_join_state_t join_state;
  join_state = (sl_wisun_join_state_t)evt->evt.join_state.join_state;
  printf("Join State %d\n", join_state);
}
  • You call app_wisun_em_custom_callback_register(SL_WISUN_MSG_JOIN_STATE_IND_ID, join_state_custom_callback); to register it.

The callback function for join state changes is sl_wisun_join_state_event_hnd().

You can see it in three places:

sl_wisun_join_state_event_hnd

  • Defined in gsdk/app/wisun/component/event_manager/sl_wisun_event_mgr.h (describing the prototype of the function).

  • Declared as SL_WEAK in gsdk/app/wisun/component/event_manager/sl_wisun_event_mgr.c.

    • A 'weak' declaration (i.e. implementation) of a function is a default implementation, which can be superseded by another located elsewhere in the code.

    • Most 'weak' functions do nothing. They are there to avoid compilation errors when there is no other implementation.

  • Declared as a normal function in gsdk/app/wisun/component/app_core/sl_wisun_app_core.c.

    • This implementation supersedes the 'weak' implementation.

Finally, check the sl_wisun_join_state_event_hnd() function in gsdk/app/wisun/component/app_core/sl_wisun_app_core.c to follow what it does.

join_state_event

  • This 'event handler' is called only when there are changes to the internal join state.

  • _join_state is set on line 355 in the 'event handler' function sl_wisun_join_state_event_hnd(), as evt->evt.join_state.join_state.

  • If there is a change of the join_state, the app_wisun_trace_util_conn_state_to_str() function returns a valid string to translate the state's decimal value into a human-readable string. If this string is not empty, it is traced in the application's console.

    • Printing a message is useful to follow the connection process when having access to the device's UART console. It's not actually required to achieve connection, but it is convenient.

Remarks on Function Names#

  • All Wi-SUN Stack API functions are prefixed with sl_wisun_.

    • Customers can't read the underlying Stack source code, which is under the responsibility of the Wi-SUN Stack developers.

    • The OK return code is SL_STATUS_OK = 0x0000.

    • All return codes are defined in gsdk/platform/common/inc/sl_status.h. Checking this file in case of an error can help get minimal information on the error.

    • Wi-SUN Stack functions don't print traces in the device's UART console, instead in the form of RTT traces, accessible using J-LINK connected to the device via an evaluation kit (which embed SEGGER debug capabilities) or a external debugger.

  • GSDK functions ultimately use the sl_wisun_ Wi-SUN Stack API functions.

  • GSDK component top-level API functions are also prefixed with sl_wisun_.

  • If there are 'GSDK-internal' functions in the components, they are prefixed with sli_, the i meaning 'internal'.

  • Additional API functions provided by GSDK components to be called by customer application code are prefixed with app_.

  • As is common usage in C coding, functions that are internal to a single source file are prefixed with _, meaning that they are not intended to be called by other parts of the code.

  • Customers have access to the GSDK function's source code.

Takeaway#

What you have accomplished in this session is the join process available when you add the Wi-SUN Application Core Component to your project.

  • The Wi-SUN Application Core Component uses calls to these Wi-SUN Stack API functions, in the following order (minimal set of calls for connecting to a basic Wi-SUN network as FFN). It also checks the return values for all calls to raise errors if needed:

  • If the call to sl_wisun_join() fails, most probably there is an issue with your PHY selection.

    • If you use a single PHY and the Wi-SUN Configurator, this should not happen.

  • Once the join call is made, connection starts. The customer application should wait for the connection before acting, since it can't communicate with the rest of the Wi-SUN network until it's connected.

  • An application can directly use the Wi-SUN Stack API functions, while using the Wi-SUN Application Core Component makes the join process much easier.

  • Calling sl_wisun_join() returns rapidly, once connection settings are set.

  • The application communicates with the Wi-SUN Stack using function calls.

  • The Wi-SUN Stack communicates with the application using events.

  • As a consequence, following the connection process from the application is done using the SL_WISUN_MSG_JOIN_STATE_IND_ID event message.

    • Triggered by the Wi-SUN Stack.

    • That the application can monitor using an event-specific handler.

It's now time to add a custom application to a Wi-SUN network.