SFA Framework

SFA Framework is a component of SCADA Final Aggregator that allows you to quickly create a web application with EVA interface for a specific configuration.

ui folder contains js/eva_sfa.js file, the framework itself and lib/jquery*.js - jQuery, necessary for correct operation. Lib folder also contains Bootstrap files often used for web application development.

Note

SFA Framework requires API session tokens (enabled by default)

Framework connection

Open the file ui/index.html in the editor, connect jQuery and SFA Framework:

<script src="lib/jquery.min.js"></script>
<script src="js/eva_sfa.min.js"></script>

Framework variables

eva_sfa_login, eva_sfa_password

The following variables contain the user login/password, and are used for the initial authentication:

eva_sfa_login = '';
eva_sfa_password = '';

eva_sfa_apikey

Another way is to use the variable

eva_sfa_apikey = null;

in case its value is not NULL, the authentication is done with API key

Note

If you have frontend server installed before UI and it handles HTTP basic authentication, you can leave eva_sfa_login and eva_sfa_apikey variables empty and let framework log in without them.

In this case authorization data will be parsed by SFA server from Authorization HTTP header (frontend server should pass it as-is to back-end SFA).

eva_sfa_cb_login_success, eva_sfa_cb_login_error

The following two variables contain functions called when the authentication either succeeded or failed.

Success callback is called with result parameter which contains server method response.

Error callback is called with parameters code, mesage and data (contains full server response), where code and message contain error code and error message. Error codes are equal to API client errors.

eva_sfa_cb_login_success = null;
eva_sfa_cb_login_error = null;

eva_sfa_logged_in

True if framework engine is started and user is logged in, false if not. Should not be changed outside framework functions.

eva_sfa_api_token

Contains current API token after log in. Filled by framework automatically

eva_sfa_set_auth_cookies

Set/clear auth cookies for /ui, /pvt and /rpvt.

eva_sfa_set_auth_cookies = true;

eva_sfa_cb_states_loaded

This function called after framework loads initial item states

eva_sfa_cb_states_loaded = null;

eva_sfa_heartbeat_interval

The interval for a server ping test (heartbeat)

eva_sfa_heartbeat_interval = 5;

eva_sfa_heartbeat_error

The following function is automatically called in case of a server heartbeat error:

eva_sfa_heartbeat_error = eva_sfa_restart;

Error callback is called with parameters code, mesage and data, where code and message contain error code and error message. Error codes are equal to API client errors.

If error information is not available (e. g. the error occurred when attempting to send data via WebSocket), the function is called without any params.

eva_sfa_ajax_reload_interval

Interval (seconds) for updating data when framework is in AJAX mode:

eva_sfa_ajax_reload_interval = 2;

eva_sfa_force_reload_interval

The next variable forces AJAX updates if the framework is running in WebSocket mode. 0 value disables updating via AJAX completely, but it’s recommended to keep some value to be sure the interface has the actual data even if some websocket events are lost.

eva_sfa_force_reload_interval = 5;

eva_sfa_server_info

The next variable is updated by heartbeat and contains API test call results. This variable may be used by the application to check whether the framework has established connection to the server - if not, the variable is null.

eva_sfa_server_info = null;

eva_sfa_tsdiff

This variable contains the time difference (in seconds) between server and connected client. The value is updated every time client gets new server info.

eva_sfa_tsdiff = null;

eva_sfa_ws_mode

This variable sets the framework working mode. If its value is true, SFA framework operates via WebSocket, if false - via AJAX. This value is changed by eva_sfa_init() which tries to detect if web browser is compatible with web socket. To change the mode manually, change the variable after the initial framework initialization.

eva_sfa_ws_mode = true;

eva_sfa_ws_event_handler

The next variable contains function processing WebSocket data. If the user declares this function, it should return true (in case the data processing is possible hereafter) or false (if the data has already been processed). The function is called via data parameter with the event data set herein.

eva_sfa_ws_event_handler = null;

eva_sfa_state_updates

Update item states via AJAX and subscribe to state updates via websocket

Possible values:

  • true get states of all items API key has access to
  • {‘p’: [types], ‘g’: [groups]} subscribe to specified types and groups
  • false - disable state updates
eva_sfa_state_updates = true;

eva_sfa_reload_handler

This variable contains function which is called when SCADA Final Aggregator asks connected clients to reload the interface. If you want the interface to handle the reload event, you must define this function.

Note

reload event can be processed only when the framework is in a websocket mode

eva_sfa_reload_handler = null;

eva_sfa_server_restart_handler

This variable contains function which is called when SCADA Final Aggregator notifies connected clients about server restart. Client application can prepare user for the server restart (e.g. display warning message) and forcibly reload data when the server is back online.

SFA cvars

All user-defined SFA variables are directly available in SFA Framework after login with any valid user or API key.

Primary functions

eva_sfa_init

To initialize the framework run

eva_sfa_init();

eva_sfa_start

To start the framework, run

eva_sfa_start();

that will authorize the user and run the data update and event handling threads.

eva_sfa_stop

To stop the framework, call:

eva_sfa_stop();

eva_sfa_call

Calls any available API function (SFA API or SYS API), params - object, containing function parameters.

eva_sfa_call(func, params, cb_success, cb_error)

Event Handling

eva_sfa_state

To manually get item state, use the function

eva_sfa_state(oid);

where:

  • oid item id in the following format: type:group/item_id, i.e. sensor:env/temperature/temp1

The function returns state object or undefined if the item state is unknown.

You can use a simple mask for oid (like *id, id*, *id*, i*d), in this case the function returns the array of all item with OIDs matching the specified mask.

eva_sfa_state_history

Returns state history for the chosen item(s)

eva_sfa_state_history(oid, params, cb_success, cb_error);

where:

  • oid item id in the following format: type:group/item_id, i.e. sensor:env/temperature/temp1, or multiple items comma separated
  • params dict with history formatting params equal to SFA API function state_history.

eva_sfa_groups

Returns a list of item groups, params - object containing function parameters (p - item type, g - group filter, mqtt style):

eva_sfa_groups(params, cb_success, cb_error);
  • cb_success, cb_error - functions called when the access to API has either succeeded or failed.

eva_sfa_register_update_state

When the new data is obtained from the server, the framework may run a specified function to handle events. To register such function in the framework, use

eva_sfa_register_update_state(oid, cb);

where:

  • oid item id in the following format: type:group/item_id, i.e. sensor:env/temperature/temp1
  • cb function which is called with state param containing the new item state data (state.status, state.value etc. equal to the regular state notification event.)

You can use a simple mask for oid (like *id, id*, *id*, i*d), in this case the specified state update function will be called always when item oid matches the specified mask.

Macro execution and unit management

eva_sfa_run

To execute macro, call the function:

eva_sfa_run(macro_id, params, cb_success, cb_error);

where macro_id - macro id (in a full format, group/macro_id) to execute, params - object containing parameters equal to LM API run function, and cb_success, cb_error - functions called when the access to API has either succeeded or failed. The functions are called with result param which contains the API response.

eva_sfa_action

To run the unit action, call the function:

eva_sfa_action(unit_id, params, cb_success, cb_error);

Where unit_id - full unit id (group/id), params - object containing parameters, equal to UC API action, and cb_success, cb_error - functions called when the access to API has either succeeded or failed. The functions are called with result param which contains the API response.

eva_sfa_action_toggle

In case you want to switch unit status between 0 and 1, call:

eva_sfa_action_toggle(unit_id, params, cb_success, cb_error);

eva_sfa_result

To obtain a result of the executed actions, use the functions:

eva_sfa_result(params, cb_success, cb_error);

where params - object containing function parameters:

  • i - object oid (type:group/id), unit or lmacro
  • u - action uuid (either i or u must be specified)
  • g - filter by group
  • s - filter by status (Q, R, F - queued, running, finished)

eva_sfa_kill

Terminate unit action and clean up queued commands:

eva_sfa_kill(unit_id, cb_success, cb_error);

eva_sfa_q_clean

Clean unit action queue but keep the current action running:

eva_sfa_q_clean(unit_id, cb_success, cb_error);

eva_sfa_terminate, eva_sfa_terminate_by_uuid

Terminate the current unit action either by unit id, or by action uuid:

eva_sfa_terminate(unit_id, cb_success, cb_error);
eva_sfa_terminate_by_uuid(uuid, cb_success, cb_error);

Working with logic variables

eva_sfa_set

To set the logic variable status, use the function:

eva_sfa_set(lvar_id, value, cb_success, cb_error);

eva_sfa_toggle

To switch lvar value between 0 and 1 use

eva_sfa_toggle(lvar_id, cb_success, cb_error);

eva_sfa_reset

To reset lvar when used as timer or flag:

eva_sfa_reset(lvar_id, cb_success, cb_error);

eva_sfa_clear

To clear lvar flag or stop the timer:

eva_sfa_clear(lvar_id, cb_success, cb_error);

eva_sfa_expires_in

Get timer expiration (in seconds). Allows to display timers and interactive progress bars of the production cycles.

eva_sfa_expires_in(lvar_id);

Returns float number of seconds to timer expiration, or:

  • undefined if lvar is not found, or eva_sfa_tsdiff is not set yet.
  • null if lvar has no expiration set
  • -1 if the timer is expired
  • -2 if the timer is disabled (stopped) and has status 0

Processing logs

SFA Framework has built-in functions to display SFA logs. In case SFA is a log aggregator, this allows to view logs from the whole EVA installation.

Note

For log processing the client API key should have sysfunc=yes permission.

eva_sfa_log_reload_interval

This variable sets log reload interval if the framework works in AJAX mode.

eva_sfa_log_reload_interval = 2;

eva_sfa_log_records_max

Maximum number of log records to get initially

eva_sfa_log_records_max = 200;

eva_sfa_process_log_record

Function called with log record param, when the new log event arrives

eva_sfa_process_log_record = null;

eva_sfa_log_postprocess

Function called when all new log records are processed, i.e. to auto scroll the log viewer

eva_sfa_log_postprocess = null;

eva_sfa_log_start

This function starts log processing engine

eva_sfa_log_start(log_level);

log_level - optional param, log level records with level >= 20 (INFO) are processed by default, if not specified.

eva_sfa_change_log_level

This function allows to change log level processing

eva_sfa_change_log_level(log_level);

Here log_level param is required. The function reloads all log records with the specified level, so it’s a good idea to clean log viewer before.

eva_sfa_log_level_name

This function returns log level name matching the given log level code:

eva_sfa_log_level_name(log_level);

Returns DEBUG for 10, INFO for 20, WARNING for 30, ERROR for 40, CRITICAL for 50.

UI functions

eva_sfa_load_animation

Draws load animation inside specified <div />

eva_sfa_load_animation(div_id);

eva_sfa_chart

Calls eva_sfa_load_animation, then eva_sfa_state_history and builds a chart inside specified <div />

eva_sfa_chart(ctx, cfg, oid, params);

where:

  • ctx HTML element (<div />) ID to draw a chart in.
  • cfg chart configuration. SFA Framework uses Chart.js library. At this moment, line and bar charts are supported
  • oid item OID (or multiple, array or comma separated): type:group/id
  • params (object):
    • timeframe time frame to display, e.g. 5T - last 5 min, 2H - last 2 hours, 2D last 2 days etc.
    • fill precision[:np], 10T-60T recommended. The more accurate precision
      is, the more data points are displayed (but chart is slower). np - optional parameter, number precision. Default: 30T:2
    • update chart update interval, in seconds. Set 0 or null to disable
      updates
    • prop item state property to use (default: ‘value’)

Note

To work with charts you should include Chart.js library, which is located in file lib/chart.min.js (ui folder).

See Chart example.

eva_sfa_popup

Opens HTML5 popup

eva_sfa_popup(ctx, pclass, title, msg, params);

where:

  • ctx html element id to use as popup (any empty <div /> is fine)
  • pclass popup class: info, warning or error. opens big popup window if ‘!’ is put before the class (e.g. !info)
  • title popup window title
  • msg popup window message
  • params (object):
    • ct popup auto close time (sec), equal to pressing escape
    • btn1 button 1 name (default: ‘OK’)
    • btn2 button 2 name
    • btn1a function to run if button 1 (or enter) is pressed
    • btn2a function(arg) to run if button 2 (or escape) is pressed. arg is true if the button was pressed, false if escape key or auto close.
    • va validate function which runs before btn1a. If the function returns true, the popup is closed and btn1a function is executed. Otherwise the popup is kept and the function btn1a is not executed. va function is used to validate input, e.g. if popup contains any input fields.

Example (consider <div id=”popup” style=”display: none”></div> is placed somewhere in HTML):

// after successful login
eva_sfa_popup('popup', 'info', 'Logged in', 'You are logged in', {ct:2});
// .......
// reload handler
function reload_me() {
    document.location='/ui/';
}
eva_sfa_reload_handler = function() {
    eva_sfa_popup(
      'popup',
      'warning',
      null,
      'Reloading interface',
      {
      ct:2,
      btn1a: reload_me,
      btn2a: reload_me}
      );
}

Examples

Examples of the SFA framework usage are provided in “Building an interface with SFA Framework” part of the EVA tutorial.

Timer example

The following example shows how to display the timer countdown. The countdown is updated every 500 ms.

function show_countdown() {
    var t = eva_sfa_expires_in('timers/timer1');
    if (t === undefined || t == null) {
        $('#timer').html('');
    } else {
        if (t == -2) {
            $('#timer').html('STOPPED');
        } else if (t == -1 ) {
            $('#timer').html('FINISHED');
        } else {
            t = Number(Math.round(t * 10) / 10).toFixed(1);
            $('#timer').html(t);
        }
    }
}

setInterval(show_countdown, 500);

Chart example

We have 2 sensors, for internal and external air temperature and want their data to be placed in one chart.

Chart options:

var chart_opts = {
        responsive: false,
        //animation: false,
        legend: {
            display: true
        },
        scales: {
            xAxes: [{
                type: "time",
                time: {
                    unit: 'hour',
                    unitStepSize: 1,
                    round: 'minute',
                    tooltipFormat: "H:mm:ss",
                    displayFormats: {
                      hour: 'MMM D, H:mm'
                    }
                },
                ticks: {
                    minRotation: 90,
                    maxTicksLimit: 12,
                    autoSkip: true
                },
                display: true,
            }],
            yAxes: [{
                display: true,
                ticks: {
                },
                scaleLabel: {
                    display: true,
                    labelString: 'Degrees'
                }
            }]
        }
    }

Chart configuration:

var chart_cfg = {
    type: 'line',
    data: {
        labels: [],
        datasets: [
            {
            label: 'Temperature inside',
            data: [],
            fill: false,
            backgroundColor: 'red',
            borderColor: 'red'
            },
            {
            label: 'Temperature outside',
            data: [],
            fill: false,
            backgroundColor: 'blue',
            borderColor: 'blue'
            }
        ],
    },
    options: chart_opts
}

Chart code (consider <div id=”chart1” style=”display: none”></div> is placed somewhere in HTML), data for last 8 hours, 15 min precision, update every 10 seconds:

eva_sfa_chart(
    'chart1',
    chart_cfg,
    'sensor:env/temp_inside,sensor:env/temp_outside',
    {timeframe: '8H', fill:'15T', update:10});

Log viewer example

The following example shows how to build a log viewer, similar to included in UC EI and LM EI.

<html>
  <head>
  <script src="lib/jquery.min.js"></script>
  <script src="js/eva_sfa.js"></script>
  <style type="text/css">
    #logr {
      outline: none;
      width: 100%;
      height: 60% !important;
      font-size: 11px;
      overflow: scroll;
      overflow-x: hidden;
      margin-bottom: 10px;
      border-style : solid;
      border-color : #3ab0ea;
      border-color : rgba(58, 176, 234, 1);
      border-width : 2px;
      border-radius : 5px;
      -moz-border-radius : 5px;
      -webkit-border-radius : 5px;
      }
    .logentry.logentry_color_10 { color: grey }
    .logentry.logentry_color_20 { color: black }
    .logentry.logentry_color_30 {
      color: orange;
      font-weight: bold;
      font-size: 14px
      }
    .logentry.logentry_color_40 {
      color: red;
      font-weight: bold;
      font-size: 16px
    }
    .logentry.logentry_color_50 {
      color: red;
      font-weight: bold;
      font-size: 20px;
      animation: blinker 0.5s linear infinite;
    }
    @keyframes blinker {
      50% { opacity: 0; }
    }
  </style>
  </head>
  <body>
  <div id="logr"></div>
  <script type="text/javascript">
      function time_converter(UNIX_timestamp) {
        var a = new Date(UNIX_timestamp * 1000);
        var year = a.getFullYear();
        var month = a.getMonth() + 1;
        var date = a.getDate();
        var hour = a.getHours();
        var min = a.getMinutes();
        var sec = a.getSeconds();
        var time =
          year +
          '-' +
          pad(month, 2) +
          '-' +
          pad(date, 2) +
          ' ' +
          pad(hour, 2) +
          ':' +
          pad(min, 2) +
          ':' +
          pad(sec, 2);
        return time;
      }

      function pad(num, size) {
        var s = num + '';
        while (s.length < size) s = '0' + s;
        return s;
      }

      function format_log_record(l) {
        return (
          '<div class="logentry logentry_color_' +
          l.l +
          '">' +
          time_converter(l.t) +
          ' ' +
          l.h +
          ' ' +
          l.p +
          ' ' +
          eva_sfa_log_level_name(l.l) +
          ' ' +
          l.mod +
          ' ' +
          l.th +
          ': ' +
          l.msg +
          '</div>'
        );
      }
      eva_sfa_process_log_record = function(l) {
        $('#logr').append(format_log_record(l));
        while ($('.logentry').length > eva_sfa_log_records_max) {
        $('#logr')
          .find('.logentry')
          .first()
          .remove();
        }
      }
      eva_sfa_log_postprocess = function() {
        $('#logr').scrollTop($('#logr').prop('scrollHeight'));
      }

      eva_sfa_init();
      eva_sfa_apikey="SECRET_KEY_JUST_FOR_EXAMPLE_DONT_STORE_KEYS_IN_JS";
      eva_sfa_cb_login_success = function(data) {
          eva_sfa_log_records_max = 100;
          eva_sfa_log_start();
      }
      eva_sfa_start();
  </script>
  </body>
  </html>

Updating multiple values

The following example will show how to update displayed values of 3 sensors with one function. Define HTML elements:

<div>Sensor 1 value: <span id="sensor:group1/sensor1"></span></div>
<div>Sensor 2 value: <span id="sensor:group1/sensor2"></span></div>
<div>Sensor 3 value: <span id="sensor:group1/sensor3"></span></div>

Then register update event function:

eva_sfa_register_update_state('sensor:group1/*', function(state) {
    $('#' + $.escapeSelector(state.oid)).html('S: ' + state.value);
}

Controlling reliability of the connection

An important moment of the web interface chosen for automation systems is reliability of the connection.

Common problems which may arise:

  • SFA server reboot and loss of session data.
  • Breaking the WebSocket connection due to front-end reboot or another reason.

To control the session, SFA Framework requests SFA API test every eva_sfa_heartbeat_interval (5 seconds by default). WebSocket is additionally controlled by the framework using { ‘s’: ‘ping’ } packet, whereto the server should send a response { ‘s’: ‘pong’ }. If there is no response within the time exceeding heartbeat interval, the connection is considered broken.

In case of short-term problems with the server, it will be enough to set the default value

eva_sfa_heartbeat_error = eva_sfa_restart;

and keep login/password in eva_sfa_login and eva_sfa_password variables, or API key in eva_sfa_apikey. If an error occurs, heartbeat will attempt to restart the framework once. If it fails or the variable data has been deleted after the initial authorization, the function specified in eva_sfa_cb_login_error will be called.

If your interface cleans up the authorization data, eva_sfa_heartbeat_error should do the following:

eva_sfa_heartbeat_error = function() {
    // stop framework, make another attempt to log out
    // if the login/password were used
   eva_sfa_stop(
        // your function that displays the authorization form
        show_login_form
        );
    }

In case reconnection is automatic, heartbeat error calls eva_sfa_restart() that, in turn, calls eva_sfa_cb_login_error in case of failure.

And for automatic reconnection it should look like:

eva_sfa_cb_login_error = function(data) {
    if (data.status == 403) {
        // if the server returned error 403 (authentication failed
        // due to invalid auth data), the user should get a login form
        show_login_form();
        } else {
        // in case of other errors - try to restart framework in 3 seconds
        // and attempt to connect again
        setTimeout(eva_sfa_start, 3 * 1000);
        }
   }