Working with events and application data in a Storyboard (RTOS) Real-time Executable environment

Introduction

This document explores some examples of using the SDK API to send and receive Storyboard events and set application data variables in a Real-time Executable environment.

The examples and source code are suitable for use with the Storyboard Lite and Storyboard Real-time Executive (RTExec) SDKs from Crank Software. These are available on request from sales@cranksoftware.com for a number of supported hardware platforms including the i.MXRT1050 and i.MXRT1060 family of crossover processors from NXP and the STM32F4xx and STM32F7xx families from ST Microelectonics.

Sending events to the ‘back-end’ from Storyboard

When testing events with Storyboard the runtime command line utilities can be used to prototype sending the event with data in a desktop environment on an SBIO connection channel, in this example 'Outgoing'.

For example, using the iogen utility to send an event ‘outgoingDataChange’ with one single byte (unit8_t) data parameter ‘requestState’ in the payload to the channel:

c:\Program Files\Crank_Software\Storyboard_Engine\5.3.201807051156\win32-x86-opengles_2.0-obj\bin>iogen Outgoing no_target outgoingDataChange 1u1:requestState 2
Connecting to Storyboard IO channel [Outgoing]

To receive the event use the iorcv utility. You should use the following command line to listen on the same named channel ‘Outgoing’:

c:\Program Files\Crank_Software\Storyboard_Engine\5.3.201807051156\win32-x86-opengles_2.0-obj\bin>iorcv.exe Outgoing
Waiting on channel [Outgoing]
outgoingDataChange,no_target,1u1:requestState 2

Within the Storyboard application model we can initiate the same event with data to the back-end using a Lua function eg: RequestCalibration() as follows:


stateValue=2

--[ On calibration timer trigger (1 second interval) send event "outgoingDataChange" to back-end channel "Outgoing"
function RequestCalibration()
   local eventData = {}
   local format = "1u1 requestState"
   eventData["requestState"] = stateValue
   local success = gre.send_event_data("outgoingDataChange",format,eventData,"Outgoing")
   if(success == false) then
       print(error)
       --TODO : handle error
   end
end

You should see the same output using the iorcv utility as before but this time the event is sent from within a Storyboard application

Now within the Storyboard RTExec environment, several changes need to be made in order to link the 'front-end' Storyboard application with the 'back-end' FreeRTOS code for receiving UI events.

Configure the IO channel with the runtime engine

In the desktop and OS runtime environment the IO 'channel' is normally communicated on the sbengine command line whereas for RTExec this channel name is passed in as a greio argument to the app launch API call.

In a typical RTExec project, the Storyboard task code is located in file sbengine_task.c . To enable the Storyboard runtime engine to access and use a named IO channel, add the name as an argument parameter ultimately via the API call gr_application_create_args().

We have again used the channel name 'Outgoing' as in the above example:

/**
* This is the main runtime task for Storyboard.  It should be possible to call this
* task directly from within your FreeRTOS core application.
*/
void
sbengine_main_task(void *arg) {
  char *args[20];
  int n = 0;

  args[n++] = "greio";
  args[n++] = "channel=Outgoing";

  // If using VFS, the Storyboard application model will be loaded via string data
  // which is generally present in an sbengine_model.h file named 'sb_model'. This
  // string data would be passed to gr_application_create_args with the GR_APP_LOAD_STRING
  // option flag.
  //
  // In the case a filesystem is present, then the Storyboard model will be loaded
  // from a file on the filesystem. The path to the file should be passed to
  // gr_application_create_args with the GR_APP_LOAD_FILE option flag.
  run_storyboard_app(sb_model, GR_APP_LOAD_STRING, args, n);

Add an event listener and handler for receiving events from Storyboard

For the 'back-end' FreeRTOS code to receive event notifications from the ‘front-end’ Storyboard UI we need to add an event listener and associated call-back handler to receive each type of these asynchronous events.

This should normally be done after the app object has been initialized but before it has been launched so that the call-back is ready to receive the events on start-up.

Again in sbengine_task.c you can add a call-back (here we just dump the event data by example) and add the listener for it with a convenient place, here in run_storyboard_app() works perfectly.

You should be able to write a single generic handler for multiple received events but the listeners are specific to each event name and channel so you may need to register several if you have lots of events.

static void CrankEventListenerCallback(gr_application_t *app, gr_event_t *event, void *arg)
{
  uint8_t *pData;
  PRINTF("Received Event[%s]: %s, %d bytes;", event->name, event->format, event->nbytes);

  pData = (uint8_t *)event->data;
  for( int i=0; i< event->nbytes; i++)
  {
    PRINTF(" 0x%X", *pData++ );
  }
  PRINTF("\n\r");
  return;
}

static void
run_storyboard_app(const char *bundle, int flags, char * const *options, int option_count) {

  app = gr_application_create_args(bundle, flags, options, option_count);

  if(!app) {
    return;
  }

  // Sets the application logging verbosity. For more logging verbosities, refer to gre.h.
  gr_application_debug(app, GR_DEBUG_CMD_VERBOSITY, GR_LOG_TRACE2);

   // Register our debug console logger callback
   gr_register_log_cb(app, &logger_cb, NULL, NULL);

   // Add event listener for event 'outgoingDataChange' via channel 'Outgoing'
  gr_application_event_listener_t *mEventListener = NULL;
  mEventListener = gr_application_add_event_listener(app,        "outgoingDataChange", &CrankEventListenerCallback, NULL);
  if (mEventListener == NULL)
  {
    PRINTF("failed to add event listener\n");
  }

  // Start the application
  gr_application_run(app);

  // Free the application instance and close any resources.
  gr_application_free(app);
}

Sending an event with data to Storyboard ‘front-end’

The mechanism is just the same for the FreeRTOS platform as you use with Storyboard on Linux or another high-level OS. If you want to use a named SBIO channel, instead of specifying the channel name on the sbengine launcher command line you need to pass this name as an argument to the run_storyboard_app() API call as described in the section Configure the IO channel with the runtime engine above.


With the FreeRTOS platform you can use a slightly different, more direct approach to send an event to the Storyboard ‘front-end’ UI by using the gr_application_send_event() API call from the SDK (check in include/gre/gre.h) which uses the application handle and app object.

Note: Be sure to first check that this app handle is valid before calling an API function eg: from an interrupt or another RTOS task.

From gre.h:

/**
* Inject an event into the application event queue.  
*
* After making this call the arguments are copied into the internal event queue and
* the content can be freely modified without affecting the queue'ed event.
*
* @param app The application handle
* @param event_target The name of the event target, or NULL to send to the default target
* @param event_name The name of the event to send, must not be NULL
* @param event_format The format of the data (see <data_format.h>, or NULL if no data is being sent
* @param event_data A pointer do the data to transmit, or NULL if no data is transmitted
* @param event_nbytes The number of data bytes to transmit, or NULL if no data is transmitted
* @return 0 on success otherwise an error.
*/
DLLExport int gr_application_send_event(gr_application_t *app,
                    const char *event_target,
                    const char *event_name,
                    const char *event_format,
                    const void *event_data,
                    int event_nbytes); 

If you have a copy of the Storyboard SDK document (Storyboard_Suite_SDK.pdf) there are more details of the various APIs.

Below is an example ‘back-end’ code snippet for FreeRTOS which posts an 'update_angle' event to the Storyboard UI model with a single floating point data parameter payload as event data 'dial_angle' using the API call gr_application_send_event().

gr_application_t *g_app = NULL;
float angle = 90.0f;
int status = 0;

if( g_app != NULL ) {
 status = gr_application_send_event(g_app, NULL, "update_angle", "4f1 dial_angle", &angle, sizeof(angle));
}

Generating user input with key events to the Storyboard ‘front-end’

Below is some code that you can use with hardware resources to send a key press-release cycle triggered from for example an IO pin interrupt trigger, UART character received etc:

#include <gre/greio.h>
#include <gre/iodefs.h>

gr_application_t *g_app = NULL;
gr_key_event_t              key_event_data;
memset(&key_event_data, 0, sizeof(key_event_data));

key_event_data.code = 0;
key_event_data.key = c; // Read key value to send from serial port, keyboard device or sample IO pin etc
key_event_data.modifiers = 0;
           
if( g_app != NULL ) {
   // key is pressed and it was not a repeat key so send new press event
   gr_application_send_event(g_app, NULL, GR_EVENT_KEY_DOWN, GR_EVENT_KEY_FMT, &key_event_data, sizeof(key_event_data));
           
   greal_nanosleep(&sleep_time, NULL);
   // and release
   gr_application_send_event(g_app, NULL, GR_EVENT_KEY_UP, GR_EVENT_KEY_FMT, &key_event_data, sizeof(key_event_data));
}


The key-presses and touch events are posted to the model event queue and are handled at the application scope. Check out the definitions in gre/greio.h and gre/iodefs.h for more details of the events and formats.

Interacting with Storyboard data variables 

Storyboard application model data variables can be accessed directly from the C code via the Storyboard data manager either by using the gr_application_set_data() API call to update the variables, triggering a data change in the UI directly or alternatively by using the gr_application_send_event() API call as above to post an event and handle it using a call-back in Lua to set and update the variables.

When a data variable is updated in this way Storyboard is able to trigger data change events and subsequent event actions that you have defined in the UI model automatically.

The following code example shows an RTOS task function which updates several Storyboard application data variables and types (mySignedInt32, and myUnsignedInt32) once every second:

void sbengine_gre_io_data(void *arg)
{
  updatedata_event_t evUpdateData;
  char myMessage[] = "Hello Storyboard!";
  //char myFormat[4];
  int32_t *p_myInt32 = NULL;
  uint32_t *p_myUInt32 = NULL;

  int status = 0;

  strncpy(evUpdateData.myString, myMessage, sizeof(myMessage));
  evUpdateData.myInt32 = -1000;
  evUpdateData.myUInt32 = 1234;

  while (1) {

    vTaskDelay( 1000 );

    if( app != NULL ) {

//Dispatch data to Storyboard by writing directly to variables using the data manager API
// Set
p_myInt32 = malloc(sizeof(int32_t));
*p_myInt32 = evUpdateData.myInt32;
status = gr_application_set_data(app, "mySignedInt32", "4s1", p_myInt32 );

p_myUInt32 = malloc(sizeof(uint32_t));
*p_myUInt32 = evUpdateData.myUInt32;
status = gr_application_set_data(app, "myUnsignedInt32", "4u1", p_myUInt32 );

//Alternative method: Dispatch data to Storyboard using an event API call
//status = gr_application_send_event(app, NULL, UPDATEDATA_EVENT, UPDATEDATA_FMT, &evUpdateData, sizeof(evUpdateData) );

//Increment values to show change
evUpdateData.myInt32 += 10;
evUpdateData.myUInt32 += 1;
    }
  }
}

 Here are some helper functions that you can use to simplify setting data variables and UI control parameters:

void setKeyToIntValue(gr_application_t *app, char *keyName, int32_t value){
void *pdata;

pdata = pvPortMalloc( sizeof(value) );
*(int32_t *)pdata = value;
// the API call will free() pdata when finished with..
gr_application_set_data(app, keyName, "4s1", pdata);
}

void setKeyToUIntValue(gr_application_t *app, char *keyName, uint32_t value){
void *pdata;

pdata = pvPortMalloc( sizeof(value) );
*(uint32_t *)pdata = value;
// the API call will free() pdata when finished with..
gr_application_set_data(app, keyName, "4u1", pdata);
}

void setKeyToStringValue(gr_application_t *app, char *keyName, char* string){
void *pdata;
size_t len;

len = strlen(string);
pdata = pvPortMalloc( len + 1 );
memset(pdata, '\0', len + 1 );
memcpy(pdata, string, len);
// the API call will free() pdata when finished with..
gr_application_set_data(app, keyName, "1s0", pdata);
}


examples:

setKeyToIntValue( app, "mySignedInt32", mySignedInt-- );
setKeyToUIntValue( app, "myUnsignedInt32", myUnsignedInt++ );
setKeyToStringValue(app, "topLayerMain.SetPoint.Temperature.text", strbuf);

Note: The Storyboard data variables must be defined at the ‘application’ scope.

There are several different data variable formats supported in Storyboard that are selectable via the dialog at creation:

 

Was this article helpful?
1 out of 1 found this helpful
Have more questions? Submit a request

Comments

0 comments

Please sign in to leave a comment.