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.

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

eva_sfa_cb_login_success, eva_sfa_cb_login_error

The following two variables contain functions called when the authentication either succeeded or failed (data parameter is equal to jQuery post):

eva_sfa_cb_login_success = null;
eva_sfa_cb_login_error = null;

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;

The function is called with data parameter containing HTTP error data, or without parameter if such data is not available (e. g. the error occurred when attempting to send data via WebSocket).

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_rule_monitor_interval

Interval (seconds) for updating settings of the decision-making matrix rules. Rule settings are updated via AJAX only.

eva_sfa_rule_monitor_interval = 60;

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_reload_handler

This variable contains function which’s 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’s 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.

Initialization, authentication

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_start_rule_monitor

After the initialization succeeds, you may additionally start reloading the decision rules. The following function is not called by init/start and you should call it separately:

eva_sfa_start_rule_monitor();

eva_sfa_stop

To stop the framework, call:

eva_sfa_stop();

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.

eva_sfa_groups(p, g, cb_success, cb_error);

where

  • p item type (U for unit, S for sensor, LV for lvar)
  • g optional group filter (MQTT-style wildcards)
  • 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’s 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.

eva_sfa_register_rule

Similarly, you can process the decision rules settings. When rule params are changed, the framework runs the function registered by

eva_sfa_register_rule(rule_id, cb);

where:

  • rule_id rule id to monitor
  • cb function which’s called with props param containing all the rule props (similar to LM API list_rule_props<lm_list_rule_props>)

Macro execution and unit management

eva_sfa_run

To execute macro, call the function:

eva_sfa_run(macro_id, args, wait, priority, uuid, cb_success, cb_error);

where macro_id - macro id (in a full format, group/macro_id) to execute, other params are 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 data param which contains the API response.

eva_sfa_action

To run the unit action, call the function:

eva_sfa_action(unit_id, nstatus, nvalue, wait, priority, uuid, cb_success,
cb_error);

Where unit_id - full unit id (group/id), other parameters are 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 data 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, wait, priority, uuid, cb_success, cb_error);

eva_sfa_result, eva_sfa_result_by_uuid

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

eva_sfa_result(unit_id, g, s, cb_success, cb_error);
eva_sfa_result_by_uuid(uuid, cb_success, cb_error);

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

Modifying decision rules

eva_sfa_set_rule_prop

To change decision rules properties, call:

eva_sfa_set_rule_prop(rule_id, prop, value, save, cb_success, cb_error);

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 autoscroll 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, timeframe, fill, update, prop);

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, comma separated): type:group/id
  • timeframe timeframe to display, e.g. 5T - last 5 min, 2H - last 2 hours, 2D last 2 days etc.
  • fill precision, 10T-60T recommended. The more accurate precision is, the more data points are displayed (but chart is slower)
  • 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 popups

eva_sfa_popup(
    ctx,
    pclass,
    title,
    msg,
    ct,
    btn1,
    btn2,
    btn1a,
    btn2a,
    va
    );

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 (i.e. !info)
  • title popup window title
  • msg popup window message
  • 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', 2);
// .......
// reload handler
function reload_me() {
    document.location='/ui/';
}
eva_sfa_reload_handler = function() {
    eva_sfa_popup(
      'popup',
      'warning',
      null,
      'Reloading interface',
      2,
      null,
      null,
      reload_me,
      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',
    '8H',
    '15T',
    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 frontend 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);
        }
   }