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.
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.Check the content of
wisun_soc_empty.slcp
with a text editor.In the source code, you can see that the
main()
function callssl_system_init()
, which callssl_internal_app_init()
, which callsapp_wisun_core_init()
. This code is automatically added when adding the component via the GUI.In
app.c/app_task()
, you see that the code surrounded bySL_CATALOG_WISUN_APP_CORE_PRESENT
is not grayed out, indicating thatSL_CATALOG_WISUN_APP_CORE_PRESENT
is defined and the corresponding code will be compiled.
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.
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.
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.
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.
sl_wisun_phy_config_t
structure#
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-512fan11
on lines 515-522explicit
on lines 525-534
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.
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.
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, allreturn
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.
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).
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:
As you can see, there are several underlying structures, used to store:
Your current Network Size setting is Small.
sl_wisun_get_conn_param_by_nw_size
simply selects the corresponding settings based on this selection:
In the declaration of SL_WISUN_PARAMS_PROFILE_SMALL
, you see all parameters and their values.
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.
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
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
.
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()
.
What you see here is that there is an endless loop calling app_wisun_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()
.
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
.
Possible join state values are defined in gsdk/protocol/wisun/stack/inc/sl_wisun_types.h
.
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 thesl_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.
The first member of the sl_wisun_event_t
structure is a sl_wisun_msg_header_t
header.
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()
.
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').
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
functionsYou see that no
custom_callback
is defined by default
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:
Defined in
gsdk/app/wisun/component/event_manager/sl_wisun_event_mgr.h
(describing the prototype of the function).Declared as
SL_WEAK
ingsdk/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.
This 'event handler' is called only when there are changes to the internal join state.
_join_state
is set on line355
in the 'event handler' functionsl_wisun_join_state_event_hnd()
, asevt->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 isSL_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_
, thei
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.